使用Keras微调模型
在上一节完成所有数据预处理工作后,您只需执行几个步骤即可训练模型。但是请注意,model.fit()
命令在CPU上运行速度非常慢。如果您没有设置GPU,则可以在Google Colab上访问免费的GPU或TPU。
下面的代码示例假设您已执行上一节中的示例。这是一个简短的摘要,回顾您需要的内容
from datasets import load_dataset
from transformers import AutoTokenizer, DataCollatorWithPadding
import numpy as np
raw_datasets = load_dataset("glue", "mrpc")
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
def tokenize_function(example):
return tokenizer(example["sentence1"], example["sentence2"], truncation=True)
tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf")
tf_train_dataset = tokenized_datasets["train"].to_tf_dataset(
columns=["attention_mask", "input_ids", "token_type_ids"],
label_cols=["labels"],
shuffle=True,
collate_fn=data_collator,
batch_size=8,
)
tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset(
columns=["attention_mask", "input_ids", "token_type_ids"],
label_cols=["labels"],
shuffle=False,
collate_fn=data_collator,
batch_size=8,
)
训练
从 🤗 Transformers 导入的TensorFlow模型已经是Keras模型。这是一个关于Keras的简短介绍。
这意味着,一旦我们拥有了数据,开始对其进行训练只需要很少的工作。
与上一章一样,我们将使用TFAutoModelForSequenceClassification
类,并使用两个标签
from transformers import TFAutoModelForSequenceClassification
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
您会注意到,与第2章不同,在实例化此预训练模型后,您会收到一个警告。这是因为BERT尚未在对句子进行分类方面进行预训练,因此预训练模型的头部已被丢弃,并且已插入一个适合序列分类的新头部。警告表明某些权重未使用(对应于已丢弃的预训练头部的权重),而其他一些权重则被随机初始化(新头部的权重)。最后,它鼓励您训练模型,这正是我们现在要做的。
要对我们的数据集微调模型,我们只需compile()
我们的模型,然后将我们的数据传递给fit()
方法。这将开始微调过程(在GPU上应该需要几分钟),并在过程中报告训练损失,以及每个epoch结束时的验证损失。
请注意, 🤗 Transformers模型具有大多数Keras模型不具备的特殊功能——它们可以自动使用它们在内部计算的适当损失。如果您在compile()
中未设置损失参数,它们将默认使用此损失。请注意,要使用内部损失,您需要将标签作为输入的一部分传递,而不是作为单独的标签,这是在Keras模型中使用标签的常规方式。您将在课程的第2部分中看到此示例,在该部分中,定义正确的损失函数可能很棘手。但是,对于序列分类,标准的Keras损失函数可以正常工作,因此我们将在此处使用它。
from tensorflow.keras.losses import SparseCategoricalCrossentropy
model.compile(
optimizer="adam",
loss=SparseCategoricalCrossentropy(from_logits=True),
metrics=["accuracy"],
)
model.fit(
tf_train_dataset,
validation_data=tf_validation_dataset,
)
请注意这里一个非常常见的陷阱——您可以只将损失的名称作为字符串传递给Keras,但默认情况下,Keras会假设您已经对输出应用了softmax。但是,许多模型在应用softmax之前输出值,这些值也称为logits。我们需要告诉损失函数这就是我们的模型所做的,而唯一的方法是直接调用它,而不是使用字符串按名称调用它。
提高训练性能
如果您尝试以上代码,它当然可以运行,但您会发现损失下降缓慢或零星。主要原因是学习率。与损失一样,当我们将Keras中的优化器名称作为字符串传递时,Keras会使用所有参数(包括学习率)的默认值初始化该优化器。但是,根据长期经验,我们知道transformer模型受益于比Adam的默认学习率(为1e-3,也写为10的-3次方,或0.001)低得多的学习率。5e-5(0.00005),大约低了20倍,是一个更好的起点。
除了降低学习率之外,我们还有一个技巧:我们可以在训练过程中缓慢降低学习率。在文献中,您有时会看到这被称为学习率的衰减或退火。在Keras中,最好的方法是使用学习率调度器。一个好的调度器是PolynomialDecay
——尽管名称如此,但在默认设置下,它只是在训练过程中将学习率从初始值线性衰减到最终值,这正是我们想要的。但是,为了正确使用调度器,我们需要告诉它训练将持续多长时间。我们在下面将其计算为num_train_steps
。
from tensorflow.keras.optimizers.schedules import PolynomialDecay
batch_size = 8
num_epochs = 3
# The number of training steps is the number of samples in the dataset, divided by the batch size then multiplied
# by the total number of epochs. Note that the tf_train_dataset here is a batched tf.data.Dataset,
# not the original Hugging Face Dataset, so its len() is already num_samples // batch_size.
num_train_steps = len(tf_train_dataset) * num_epochs
lr_scheduler = PolynomialDecay(
initial_learning_rate=5e-5, end_learning_rate=0.0, decay_steps=num_train_steps
)
from tensorflow.keras.optimizers import Adam
opt = Adam(learning_rate=lr_scheduler)
🤗 Transformers库还具有一个create_optimizer()
函数,该函数将创建一个具有学习率衰减的AdamW
优化器。这是一个方便的快捷方式,您将在课程的后续部分详细了解它。
现在我们有了全新的优化器,我们可以尝试用它进行训练。首先,让我们重新加载模型,以重置我们刚刚完成的训练运行对权重的更改,然后我们可以使用新的优化器对其进行编译
import tensorflow as tf
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
model.compile(optimizer=opt, loss=loss, metrics=["accuracy"])
现在,我们再次拟合
model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3)
💡 如果您希望在训练期间自动将模型上传到Hub,可以在model.fit()
方法中传递PushToHubCallback
。我们将在第4章中了解更多信息
模型预测
训练模型并观察损失下降固然很好,但如果我们想从训练好的模型中获取输出,无论是计算一些指标还是在生产环境中使用模型,该怎么办呢?为此,我们可以使用 `predict()` 方法。这将返回模型输出头中每个类别的 *logits*。
preds = model.predict(tf_validation_dataset)["logits"]
我们可以使用 `argmax` 找到最高的 logit 并将其转换为模型的类别预测,这对应于最可能的类别。
class_preds = np.argmax(preds, axis=1)
print(preds.shape, class_preds.shape)
(408, 2) (408,)
现在,让我们使用这些 `preds` 来计算一些指标!我们可以像加载数据集一样轻松地加载与 MRPC 数据集关联的指标,这次使用 `evaluate.load()` 函数。返回的对象有一个 `compute()` 方法,我们可以用它来进行指标计算。
import evaluate
metric = evaluate.load("glue", "mrpc")
metric.compute(predictions=class_preds, references=raw_datasets["validation"]["label"])
{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542}
您获得的确切结果可能会有所不同,因为模型头的随机初始化可能会改变它达到的指标。在这里,我们可以看到我们的模型在验证集上的准确率为 85.78%,F1 分数为 89.97。这是用于评估 GLUE 基准测试中 MRPC 数据集结果的两个指标。BERT 论文中的表格报告了基础模型的 F1 分数为 88.9。那是 `uncased` 模型,而我们目前使用的是 `cased` 模型,这解释了更好的结果。
这结束了使用 Keras API 进行微调的介绍。在 第 7 章 中将给出针对大多数常见 NLP 任务的示例。如果您想磨练 Keras API 的技能,请尝试在 GLUE SST-2 数据集上微调模型,使用您在第 2 节中进行的数据处理。