使用 Trainer API 微调模型
🤗 Transformers提供了一个 Trainer 类来帮助您在自己的数据集上微调任何预训练模型。完成上一节中的所有数据预处理工作后,您只需要执行几个步骤来创建 Trainer .最难的部分可能是为 Trainer.train()配置运行环境,因为它在 CPU 上运行速度会非常慢。如果您没有设置 GPU,您可以访问免费的 GPU 或 TPUGoogle Colab.
下面的示例假设您已经执行了上一节中的示例。下面这段代码,概括了您需要提前运行的代码:
from datasets import load_dataset
from transformers import AutoTokenizer, DataCollatorWithPadding
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)
Training
在我们定义我们的 Trainer 之前首先要定义一个 TrainingArguments 类,它将包含 Trainer用于训练和评估的所有超参数。您唯一必须提供的参数是保存训练模型的目录,以及训练过程中的检查点。对于其余的参数,您可以保留默认值,这对于基本微调应该非常有效。
from transformers import TrainingArguments
training_args = TrainingArguments("test-trainer")
💡 如果您想在训练期间自动将模型上传到 Hub,请将push_to_hub=True添加到TrainingArguments之中. 我们将在第四章中详细介绍这部分。
第二步是定义我们的模型。正如在[之前的章节](/2_Using Transformers/Introduction)一样,我们将使用 AutoModelForSequenceClassification 类,它有两个参数:
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
你会注意到,和第二章不一样的是,在实例化此预训练模型后会收到警告。这是因为 BERT 没有在句子对分类方面进行过预训练,所以预训练模型的头部已经被丢弃,而是添加了一个适合句子序列分类的新头部。警告表明一些权重没有使用(对应于丢弃的预训练头的那些),而其他一些权重被随机初始化(新头的那些)。最后鼓励您训练模型,这正是我们现在要做的。
一旦我们有了我们的模型,我们就可以定义一个 Trainer 通过将之前构造的所有对象传递给它——我们的model 、training_args ,训练和验证数据集,data_collator ,和 tokenizer :
from transformers import Trainer
trainer = Trainer(
model,
training_args,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["validation"],
data_collator=data_collator,
tokenizer=tokenizer,
)
请注意,当您在这里完成tokenizer后,默认 Trainer使用 的data_collator会使用之前预定义的 DataCollatorWithPadding ,因此您可以在这个例子中跳过 data_collator=data_collator。在第 2 节中向您展示这部分处理仍然很重要!
为了让预训练模型在在我们的数据集上微调,我们只需要调用Trainer的train() 方法 :
trainer.train()
这将开始微调(在GPU上应该需要几分钟),并每500步报告一次训练损失。但是,它不会告诉您模型的性能如何(或质量如何)。这是因为:
- 我们没有通过将evaluation_strategy设置为“steps”(在每次更新参数的时候评估)或“epoch”(在每个epoch结束时评估)来告诉Trainer在训练期间进行评估。
- 我们没有为Trainer提供一个compute_metrics()函数来直接计算模型的好坏(否则评估将只输出loss,这不是一个非常直观的数字)。
评估
让我们看看如何构建一个有用的 compute_metrics() 函数并在我们下次训练时使用它。该函数必须采用 EvalPrediction 对象(带有 predictions 和 label_ids 字段的参数元组)并将返回一个字符串到浮点数的字典(字符串是返回的指标的名称,而浮点数是它们的值)。我们可以使用 Trainer.predict() 命令来使用我们的模型进行预测:
predictions = trainer.predict(tokenized_datasets["validation"])
print(predictions.predictions.shape, predictions.label_ids.shape)
(408, 2) (408,)
predict() 的输出结果是具有三个字段的命名元组: predictions , label_ids , 和 metrics .这 metrics 字段将只包含传递的数据集的loss,以及一些运行时间(预测所需的总时间和平均时间)。如果我们定义了自己的 compute_metrics() 函数并将其传递给 Trainer ,该字段还将包含compute_metrics()的结果。
predict() 方法是具有三个字段的命名元组: predictions , label_ids , 和 metrics .这 metrics 字段将只包含传递的数据集的loss,以及一些运行时间(预测所需的总时间和平均时间)。如果我们定义了自己的 compute_metrics() 函数并将其传递给 Trainer ,该字段还将包含compute_metrics() 的结果。如你看到的, predictions 是一个形状为 408 x 2 的二维数组(408 是我们使用的数据集中元素的数量)。这些是我们传递给predict()的数据集的每个元素的结果(logits)(正如你在之前的章节看到的情况)。要将我们的预测的可以与真正的标签进行比较,我们需要在第二个轴上取最大值的索引:
import numpy as np
preds = np.argmax(predictions.predictions, axis=-1)
现在建立我们的 compute_metric() 函数来较为直观地评估模型的好坏,我们将使用 🤗 Evaluate 库中的指标。我们可以像加载数据集一样轻松加载与 MRPC 数据集关联的指标,这次使用 evaluate.load() 函数。返回的对象有一个 compute()方法我们可以用来进行度量计算的方法:
import evaluate
metric = evaluate.load("glue", "mrpc")
metric.compute(predictions=preds, references=predictions.label_ids)
{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542}
您获得的确切结果可能会有所不同,因为模型头的随机初始化可能会影响最终建立的模型。在这里,我们可以看到我们的模型在验证集上的准确率为 85.78%,F1 分数为 89.97。这是用于评估 GLUE 基准的 MRPC 数据集结果的两个指标。而在BERT 论文中展示的基础模型的 F1 分数为 88.9。那是 uncased 模型,而我们目前正在使用 cased 模型,通过改进得到了更好的结果。
最后将所有东西打包在一起,我们得到了我们的 compute_metrics() 函数:
def compute_metrics(eval_preds):
metric = evaluate.load("glue", "mrpc")
logits, labels = eval_preds
predictions = np.argmax(logits, axis=-1)
return metric.compute(predictions=predictions, references=labels)
为了查看模型在每个训练周期结束的好坏,下面是我们如何使用compute_metrics()函数定义一个新的 Trainer :
training_args = TrainingArguments("test-trainer", evaluation_strategy="epoch")
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
trainer = Trainer(
model,
training_args,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["validation"],
data_collator=data_collator,
tokenizer=tokenizer,
compute_metrics=compute_metrics,
)
请注意,我们设置了了一个新的 TrainingArguments 它的evaluation_strategy 设置为 epoch 并创建了一个新模型。如果不创建新的模型就直接训练,就只会继续训练之前我们已经训练过的模型。要启动新的训练运行,我们执行:
trainer.train()
这一次,它将在训练loss之外,还会输出每个 epoch 结束时的验证loss和指标。同样,由于模型的随机头部初始化,您达到的准确率/F1 分数可能与我们发现的略有不同,但它应该在同一范围内。
这 Trainer 将在多个 GPU 或 TPU 上开箱即用,并提供许多选项,例如混合精度训练(在训练的参数中使用 fp16 = True )。我们将在第 10 章讨论它支持的所有内容。
使用Trainer API微调的介绍到此结束。对最常见的 NLP 任务执行此操作的示例将在第 7 章中给出,但现在让我们看看如何在纯 PyTorch 中执行相同的操作。
✏️ 试试看! 使用您在第 2 节中进行的数据处理,在 GLUE SST-2 数据集上微调模型。