如何微调 HuggingFace BERT 模型进行文本分类

mon*_*mon 2 machine-learning transfer-learning huggingface-transformers

是否有关于如何微调 HuggingFace BERT模型进行文本分类的分步说明

mon*_*mon 21

微调方法

\n

有多种方法可以针对目标任务微调 BERT。

\n
    \n
  1. 进一步预训练基础 BERT 模型
  2. \n
  3. 可训练的基本 BERT 模型之上的自定义分类层
  4. \n
  5. 基础 BERT 模型之上的自定义分类层不可训练(冻结)
  6. \n
\n

请注意,与原始论文一样,BERT 基础模型仅针对两个任务进行了预训练。

\n\n
\n

3.1 预训练 BERT ...我们使用两个无监督任务预训练 BERT

\n
    \n
  • 任务#1:蒙面LM
  • \n
  • 任务#2:下一句预测 (NSP)
  • \n
\n
\n

因此,基本 BERT 模型就像半生不熟的模型,可以针对目标域进行完全烘焙(第一种方式)。我们可以将它用作我们的自定义模型训练的一部分,其中包含基础可训练(第二个)或不可训练(第三个)。

\n
\n

第一种方法

\n

如何微调 BERT 进行文本分类?演示了进一步预训练的第一种方法,并指出学习率是避免灾难性遗忘的关键,即在学习新知识时预训练的知识被擦除。

\n
\n

我们发现较低的学习率(例如 2e-5)对于让 BERT 克服灾难性遗忘问题是必要的。当学习率达到 4e-4 时,训练集无法收敛。
\n在此输入图像描述

\n
\n

大概这就是BERT论文使用5e-5、4e-5、3e-5、2e-5进行微调的原因。

\n
\n

我们使用 32 的批量大小,并对所有 GLUE 任务的数据进行 3 轮微调。对于每个任务,我们在开发集上选择最佳的微调学习率(5e-5、4e-5、3e-5 和 2e-5 之间)

\n
\n

请注意,基础模型预训练本身使用了更高的学习率。

\n\n
\n

该模型在 Pod 配置中的 4 个云 TPU(总共 16 个 TPU 芯片)上进行了 100 万步训练,批量大小为 256。序列长度限制为 90% 的步骤为 128 个令牌,其余 10% 的步骤为 512 个令牌。使用的优化器是 Adam,学习率为1e-4、 \xce\xb21=0.9和 \xce\xb22= 0.999,权重衰减为0.01,学习率预热 10,000 步,之后学习率线性衰减。

\n
\n

将在下面将第一种方法描述为第三种方法的一部分。

\n

仅供参考:\n TFDistilBertModel是裸基模型,其名称为distilbert

\n
Model: "tf_distil_bert_model_1"\n_________________________________________________________________\nLayer (type)                 Output Shape              Param #   \n=================================================================\ndistilbert (TFDistilBertMain multiple                  66362880  \n=================================================================\nTotal params: 66,362,880\nTrainable params: 66,362,880\nNon-trainable params: 0\n
Run Code Online (Sandbox Code Playgroud)\n
\n

第二种方法

\n

Huggingface 采用第二种方法,如使用本机 PyTorch/TensorFlow 进行微调,其中在基础之上TFDistilBertForSequenceClassification添加了自定义分类层classifierdistilbert在可训练的小学习率要求也将适用,以避免灾难性遗忘。

\n
from transformers import TFDistilBertForSequenceClassification\n\nmodel = TFDistilBertForSequenceClassification.from_pretrained(\'distilbert-base-uncased\')\noptimizer = tf.keras.optimizers.Adam(learning_rate=5e-5)\nmodel.compile(optimizer=optimizer, loss=model.compute_loss) # can also use any keras loss fn\nmodel.fit(train_dataset.shuffle(1000).batch(16), epochs=3, batch_size=16)\n
Run Code Online (Sandbox Code Playgroud)\n
Model: "tf_distil_bert_for_sequence_classification_2"\n_________________________________________________________________\nLayer (type)                 Output Shape              Param #   \n=================================================================\ndistilbert (TFDistilBertMain multiple                  66362880  \n_________________________________________________________________\npre_classifier (Dense)       multiple                  590592    \n_________________________________________________________________\nclassifier (Dense)           multiple                  1538      \n_________________________________________________________________\ndropout_59 (Dropout)         multiple                  0         \n=================================================================\nTotal params: 66,955,010\nTrainable params: 66,955,010  <--- All parameters are trainable\nNon-trainable params: 0\n
Run Code Online (Sandbox Code Playgroud)\n

第二种方法的实施

\n
import pandas as pd\nimport tensorflow as tf\nfrom sklearn.model_selection import train_test_split\nfrom transformers import (\n    DistilBertTokenizerFast,\n    TFDistilBertForSequenceClassification,\n)\n\n\nDATA_COLUMN = \'text\'\nLABEL_COLUMN = \'category_index\'\nMAX_SEQUENCE_LENGTH = 512\nLEARNING_RATE = 5e-5\nBATCH_SIZE = 16\nNUM_EPOCHS = 3\n\n\n# --------------------------------------------------------------------------------\n# Tokenizer\n# --------------------------------------------------------------------------------\ntokenizer = DistilBertTokenizerFast.from_pretrained(\'distilbert-base-uncased\')\ndef tokenize(sentences, max_length=MAX_SEQUENCE_LENGTH, padding=\'max_length\'):\n    """Tokenize using the Huggingface tokenizer\n    Args:\n        sentences: String or list of string to tokenize\n        padding: Padding method [\'do_not_pad\'|\'longest\'|\'max_length\']\n    """\n    return tokenizer(\n        sentences,\n        truncation=True,\n        padding=padding,\n        max_length=max_length,\n        return_tensors="tf"\n    )\n\n# --------------------------------------------------------------------------------\n# Load data\n# --------------------------------------------------------------------------------\nraw_train = pd.read_csv("./train.csv")\ntrain_data, validation_data, train_label, validation_label = train_test_split(\n    raw_train[DATA_COLUMN].tolist(),\n    raw_train[LABEL_COLUMN].tolist(),\n    test_size=.2,\n    shuffle=True\n)\n\n# --------------------------------------------------------------------------------\n# Prepare TF dataset\n# --------------------------------------------------------------------------------\ntrain_dataset = tf.data.Dataset.from_tensor_slices((\n    dict(tokenize(train_data)),  # Convert BatchEncoding instance to dictionary\n    train_label\n)).shuffle(1000).batch(BATCH_SIZE).prefetch(1)\nvalidation_dataset = tf.data.Dataset.from_tensor_slices((\n    dict(tokenize(validation_data)),\n    validation_label\n)).batch(BATCH_SIZE).prefetch(1)\n\n# --------------------------------------------------------------------------------\n# training\n# --------------------------------------------------------------------------------\nmodel = TFDistilBertForSequenceClassification.from_pretrained(\n    \'distilbert-base-uncased\',\n    num_labels=NUM_LABELS\n)\noptimizer = tf.keras.optimizers.Adam(learning_rate=LEARNING_RATE)\nmodel.compile(\n    optimizer=optimizer,\n    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n)\nmodel.fit(\n    x=train_dataset,\n    y=None,\n    validation_data=validation_dataset,\n    batch_size=BATCH_SIZE,\n    epochs=NUM_EPOCHS,\n)\n
Run Code Online (Sandbox Code Playgroud)\n
\n

第三种方法

\n

基本

\n

请注意,这些图像取自首次使用 BERT 的视觉指南并进行了修改。

\n

分词器

\n

Tokenizer生成 BatchEncoding 的实例,它可以像 Python 字典一样使用,并且可以作为 BERT 模型的输入。

\n\n
\n

保存encode_plus()和batch_encode()方法的输出(tokens、attention_masks等)。\n
\n此类派生自Python字典,可以用作字典。此外,此类还公开了从单词/字符空间映射到标记空间的实用方法。

\n参数

\n
    \n
  • data (dict) \xe2\x80\x93 编码/batch_encode 方法返回的列表/数组/张量的字典 (\xe2\x80\x98input_ids\xe2\x80\x99, \xe2\x80\x98attention_mask\xe2\x80\x99 , ETC。)。
  • \n
\n
\n

data类的属性是生成的具有和input_ids元素的标记attention_mask

\n

输入ID

\n\n
\n

输入 id 通常是作为输入传递给模型的唯一必需参数。它们是标记索引,是构建将用作模型输入的序列的标记的数字表示。

\n
\n

注意掩码

\n\n
\n

该参数向模型指示哪些标记应该被关注,哪些标记不应该被关注。

\n
\n

如果attention_mask为0,则忽略token id。例如,如果填充序列以调整序列长度,则应忽略填充的单词,因此它们的 focus_mask 为 0。

\n

特殊代币

\n

[CLS]BertTokenizer 添加特殊标记,用和括起一个序列[SEP][CLS]表示分类[SEP]分隔序列。对于问答或释义任务,[SEP]将两个句子分开进行比较。

\n

伯特分词器

\n
\n
    \n
  • cls_token(str,可选,默认为“ [CLS] ”)进行序列分类时使用的分类器令牌
    (对整个序列进行分类而不是对每个令牌进行分类)。当使用特殊标记构建时,它是序列的第一个标记。
  • \n
  • sep_token(str,可选,默认为“[SEP]”)
    分隔符标记,在从多个序列构建序列时使用,例如用于序列分类的两个序列或用于问答的文本和问题。它还用作用特殊标记构建的序列的最后一个标记。
  • \n
\n
\n

首次使用 BERT 的视觉指南显示了标记化。

\n

在此输入图像描述

\n

[CLS]

\n

基础模型最后一层输出中的嵌入向量[CLS]表示基础模型已学习的分类。因此,将标记的嵌入向量输入 [CLS]到添加在基本模型之上的分类层。

\n\n
\n

每个序列的第一个标记始终是a special classification token ([CLS])。该 token 对应的最终隐藏状态用作分类任务的聚合序列表示。句子对被打包成一个序列。我们以两种方式区分句子。首先,我们用一个特殊的标记([SEP])将它们分开。其次,我们向每个标记添加学习嵌入,指示它属于句子 A 还是句子 B。

\n
\n

模型结构如下图所示。

\n

在此输入图像描述

\n

在此输入图像描述

\n

矢量大小

\n

在该模型中,每个标记都嵌入到大小为768distilbert-base-uncased的向量中。基本模型的输出形状为。这与关于 BERT/BASE 模型的 BERT 论文一致(如 distilbert- base -uncased 中所示)。(batch_size, max_sequence_length, embedding_vector_size=768)

\n\n
\n

BERT/BASE(L=12,H= 768,A=12,总参数=110M)和BERT/LARGE(L=24,H=1024,A=16,总参数=340M)。

\n
\n

基础模型 - TFDistilBertModel

\n\n
\n

TFDistilBertModel 类用于实例化基本 DistilBERT 模型,顶部没有任何特定头(与其他类(例如 TFDistilBertForSequenceClassification 确实具有添加的分类头)相反)。

\n我们不希望附加任何特定于任务的头,因为我们只是希望基本模型的预训练权重能够提供对英语的一般理解,并且我们的工作是在精细过程中添加我们自己的分类头调整过程以帮助模型区分有毒评论。

\n
\n

TFDistilBertModel生成一个实例,TFBaseModelOutputlast_hidden_state参数是模型最后一层的输出。

\n
TFBaseModelOutput([(\n    \'last_hidden_state\',\n    <tf.Tensor: shape=(batch_size, sequence_lendgth, 768), dtype=float32, numpy=array([[[...]]], dtype=float32)>\n)])\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n

参数

\n
    \n
  • last_hidden_​​state (tf.Tensor of shape (batch_size,equence_length,hidden_​​size)) \xe2\x80\x93 模型最后一层输出的隐藏状态序列。
  • \n
\n
\n

执行

\n

Python 模块

\n
import pandas as pd\nimport tensorflow as tf\nfrom sklearn.model_selection import train_test_split\nfrom transformers import (\n    DistilBertTokenizerFast,\n    TFDistilBertModel,\n)\n
Run Code Online (Sandbox Code Playgroud)\n

配置

\n
TIMESTAMP = datetime.datetime.now().strftime("%Y%b%d%H%M").upper()\n\nDATA_COLUMN = \'text\'\nLABEL_COLUMN = \'category_index\'\n\nMAX_SEQUENCE_LENGTH = 512   # Max length allowed for BERT is 512.\nNUM_LABELS = len(raw_train[LABEL_COLUMN].unique())\n\nMODEL_NAME = \'distilbert-base-uncased\'\nNUM_BASE_MODEL_OUTPUT = 768\n\n# Flag to freeze base model\nFREEZE_BASE = True\n\n# Flag to add custom classification heads\nUSE_CUSTOM_HEAD = True\nif USE_CUSTOM_HEAD == False:\n    # Make the base trainable when no classification head exists.\n    FREEZE_BASE = False\n\n\nBATCH_SIZE = 16\nLEARNING_RATE = 1e-2 if FREEZE_BASE else 5e-5\nL2 = 0.01\n
Run Code Online (Sandbox Code Playgroud)\n

分词器

\n
tokenizer = DistilBertTokenizerFast.from_pretrained(MODEL_NAME)\ndef tokenize(sentences, max_length=MAX_SEQUENCE_LENGTH, padding=\'max_length\'):\n    """Tokenize using the Huggingface tokenizer\n    Args:\n        sentences: String or list of string to tokenize\n        padding: Padding method [\'do_not_pad\'|\'longest\'|\'max_length\']\n    """\n    return tokenizer(\n        sentences,\n        truncation=True,\n        padding=padding,\n        max_length=max_length,\n        return_tensors="tf"\n    )\n
Run Code Online (Sandbox Code Playgroud)\n

输入层

\n

基本模型期望input_idsattention_mask其形状为(max_sequence_length,)。分别用层为它们生成 Keras 张量Input

\n
# Inputs for token indices and attention masks\ninput_ids = tf.keras.layers.Input(shape=(MAX_SEQUENCE_LENGTH,), dtype=tf.int32, name=\'input_ids\')\nattention_mask = tf.keras.layers.Input((MAX_SEQUENCE_LENGTH,), dtype=tf.int32, name=\'attention_mask\')\n
Run Code Online (Sandbox Code Playgroud)\n

基础模型层

\n

从基本模型生成输出。基本模型生成TFBaseModelOutput. 将 的嵌入馈送[CLS]到下一层。

\n
base = TFDistilBertModel.from_pretrained(\n    MODEL_NAME,\n    num_labels=NUM_LABELS\n)\n\n# Freeze the base model weights.\nif FREEZE_BASE:\n    for layer in base.layers:\n        layer.trainable = False\n    base.summary()\n\n# [CLS] embedding is last_hidden_state[:, 0, :]\noutput = base([input_ids, attention_mask]).last_hidden_state[:, 0, :]\n
Run Code Online (Sandbox Code Playgroud)\n

分类层

\n
if USE_CUSTOM_HEAD:\n    # -------------------------------------------------------------------------------\n    # Classifiation leayer 01\n    # --------------------------------------------------------------------------------\n    output = tf.keras.layers.Dropout(\n        rate=0.15,\n        name="01_dropout",\n    )(output)\n    \n    output = tf.keras.layers.Dense(\n        units=NUM_BASE_MODEL_OUTPUT,\n        kernel_initializer=\'glorot_uniform\',\n        activation=None,\n        name="01_dense_relu_no_regularizer",\n    )(output)\n    output = tf.keras.layers.BatchNormalization(\n        name="01_bn"\n    )(output)\n    output = tf.keras.layers.Activation(\n        "relu",\n        name="01_relu"\n    )(output)\n\n    # --------------------------------------------------------------------------------\n    # Classifiation leayer 02\n    # --------------------------------------------------------------------------------\n    output = tf.keras.layers.Dense(\n        units=NUM_BASE_MODEL_OUTPUT,\n        kernel_initializer=\'glorot_uniform\',\n        activation=None,\n        name="02_dense_relu_no_regularizer",\n    )(output)\n    output = tf.keras.layers.BatchNormalization(\n        name="02_bn"\n    )(output)\n    output = tf.keras.layers.Activation(\n        "relu",\n        name="02_relu"\n    )(output)\n
Run Code Online (Sandbox Code Playgroud)\n

Softmax层

\n
output = tf.keras.layers.Dense(\n    units=NUM_LABELS,\n    kernel_initializer=\'glorot_uniform\',\n    kernel_regularizer=tf.keras.regularizers.l2(l2=L2),\n    activation=\'softmax\',\n    name="softmax"\n)(output)\n
Run Code Online (Sandbox Code Playgroud)\n

最终定制模型

\n
name = f"{TIMESTAMP}_{MODEL_NAME.upper()}"\nmodel = tf.keras.models.Model(inputs=[input_ids, attention_mask], outputs=output, name=name)\nmodel.compile(\n    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),\n    optimizer=tf.keras.optimizers.Adam(learning_rate=LEARNING_RATE),\n    metrics=[\'accuracy\']\n)\nmodel.summary()\n---\nLayer (type)                    Output Shape         Param #     Connected to                     \n==================================================================================================\ninput_ids (InputLayer)          [(None, 256)]        0                                            \n__________________________________________________________________________________________________\nattention_mask (InputLayer)     [(None, 256)]        0                                            \n__________________________________________________________________________________________________\ntf_distil_bert_model (TFDistilB TFBaseModelOutput(la 66362880    input_ids[0][0]                  \n                                                                 attention_mask[0][0]             \n__________________________________________________________________________________________________\ntf.__operators__.getitem_1 (Sli (None, 768)          0           tf_distil_bert_model[1][0]       \n__________________________________________________________________________________________________\n01_dropout (Dropout)            (None, 768)          0           tf.__operators__.getitem_1[0][0] \n__________________________________________________________________________________________________\n01_dense_relu_no_regularizer (D (None, 768)          590592      01_dropout[0][0]                 \n__________________________________________________________________________________________________\n01_bn (BatchNormalization)      (None, 768)          3072        01_dense_relu_no_regularizer[0][0\n__________________________________________________________________________________________________\n01_relu (Activation)            (None, 768)          0           01_bn[0][0]                      \n__________________________________________________________________________________________________\n02_dense_relu_no_regularizer (D (None, 768)          590592      01_relu[0][0]                    \n__________________________________________________________________________________________________\n02_bn (BatchNormalization)      (None, 768)          3072        02_dense_relu_no_regularizer[0][0\n__________________________________________________________________________________________________\n02_relu (Activation)            (None, 768)          0           02_bn[0][0]                      \n__________________________________________________________________________________________________\nsoftmax (Dense)                 (None, 2)            1538        02_relu[0][0]                    \n==================================================================================================\nTotal params: 67,551,746\nTrainable params: 1,185,794\nNon-trainable params: 66,365,952   <--- Base BERT model is frozen\n
Run Code Online (Sandbox Code Playgroud)\n

数据分配

\n
# --------------------------------------------------------------------------------\n# Split data into training and validation\n# --------------------------------------------------------------------------------\nraw_train = pd.read_csv("./train.csv")\ntrain_data, validation_data, train_label, validation_label = train_test_split(\n    raw_train[DATA_COLUMN].tolist(),\n    raw_train[LABEL_COLUMN].tolist(),\n    test_size=.2,\n    shuffle=True\n)\n\n# X = dict(tokenize(train_data))\n# Y = tf.convert_to_tensor(train_label)\nX = tf.data.Dataset.from_tensor_slices((\n    dict(tokenize(train_data)),  # Convert BatchEncoding instance to dictionary\n    train_label\n)).batch(BATCH_SIZE).prefetch(1)\n\nV = tf.data.Dataset.from_tensor_slices((\n    dict(tokenize(validation_data)),  # Convert BatchEncoding instance to dictionary\n    validation_label\n)).batch(BATCH_SIZE).prefetch(1)\n
Run Code Online (Sandbox Code Playgroud)\n

火车

\n
# --------------------------------------------------------------------------------\n# Train the mod

  • 您的答案仅涵盖文本分类,而您自己的问题是关于一般的微调。 (2认同)