前言

🤗 Transformers 提供了一个 Trainer 类,帮助您在您自己的数据集上微调它提供的任何预训练模型。一旦您完成了上一节中的所有数据预处理工作,您只需几个步骤就可以定义 Trainer 了。最困难的部分可能是准备运行 Trainer.train() 的环境,因为它在 CPU 上运行会非常慢。如果您没有设置 GPU,您可以在 Google Colab 上获得免费的 GPU 或 TPU 访问权限。

下面的代码示例假设你已经执行了前一节的示例。这里是一个简短的总结,回顾你需要的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)

src link: https://huggingface.co/learn/nlp-course/chapter3/3

Operating System: Ubuntu 22.04.4 LTS

参考文档

  1. NLP Course - Fine-tuning a model with the Trainer API

训练

在我们定义 Trainer 之前的第一步是定义一个 TrainingArguments 类,它将包含 Trainer 用于训练和评估的所有超参数。你必须提供的唯一参数是一个目录,训练好的模型将保存在这里,以及沿途的检查点。对于所有其他的参数,你可以保留默认值,这对于基本的微调应该已经足够好了。

1
2
3
from transformers import TrainingArguments

training_args = TrainingArguments("test-trainer")

💡 如果你希望在训练过程中自动将你的模型上传到 Hub,可以在 TrainingArguments 中传入 push_to_hub=True。我们将在第4章中学习更多关于这方面的内容。

第二步是定义我们的模型。与上一章一样,我们将使用 AutoModelForSequenceClassification 类,并设置两个标签:

1
2
3
from transformers import AutoModelForSequenceClassification

model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)

你会注意到,与第2章不同,在实例化这个预训练模型后,你会收到一个警告。这是因为 BERT 没有在分类句子对上进行预训练,所以预训练模型的头部已经被丢弃,取而代之的是一个适合序列分类的新头部。这些警告表明一些权重没有被使用(与被丢弃的预训练头部相对应的权重),而其他一些权重是随机初始化的(为新头部设置的权重)。它最后鼓励你训练模型,这正是我们现在要做的。

一旦我们有了模型,我们可以通过传递到目前为止构建的所有对象来定义一个 Trainer —— 模型、training_args、训练和验证数据集、我们的 data_collator 和我们的 tokenizer:

1
2
3
4
5
6
7
8
9
10
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这一行。然而,在第二部分中展示你这部分处理过程仍然很重要!

为了在我们自己的数据集上微调模型,我们只需要调用Trainer的train()方法:

1
trainer.train()

这将开始微调过程(在GPU上应该只需要几分钟),并且每500步报告一次训练损失。然而,它不会告诉你模型的表现是好是坏。这是因为:

  • 我们没有告诉Trainer在训练过程中进行评估,通过将evaluation_strategy设置为"steps"(每eval_steps评估一次)或"epoch"(在每个epoch结束时评估)。
  • 我们没有为Trainer提供一个compute_metrics()函数来在所述评估期间计算一个指标(否则评估只会打印损失,这并不是一个很直观的数字)。

评估

让我们看看如何构建一个有用的compute_metrics()函数,并在下次训练时使用它。这个函数必须接受一个EvalPrediction对象(这是一个带有predictions字段和label_ids字段的命名元组),并将返回一个将字符串映射到浮点数的字典(字符串是返回的指标名称,浮点数是它们的值)。为了从我们的模型中得到一些预测,我们可以使用Trainer.predict()命令:

1
2
predictions = trainer.predict(tokenized_datasets["validation"])
print(predictions.predictions.shape, predictions.label_ids.shape)
1
(408, 2) (408,)

predict()方法的输出是另一个带有三个字段的命名元组:predictions、label_ids和metrics。metrics字段将只包含在传递的数据集上的损失,以及一些时间指标(总共预测需要多长时间,以及平均时间)。一旦我们完成compute_metrics()函数并传递给Trainer,该字段也将包含由compute_metrics()返回的指标。

正如你所看到的,predictions是一个二维数组,形状为408 x 2(408是我们使用的数据集中的元素数量)。这些是我们传递给predict()的数据集每个元素的logits(正如你在上一章看到的,所有Transformer模型都返回logits)。为了将它们转换为我们可以与标签进行比较的预测,我们需要在第二个轴上取最大值的索引:

1
2
3
import numpy as np

preds = np.argmax(predictions.predictions, axis=-1)

现在我们可以将这些preds与标签进行比较。为了构建我们的compute_metric()函数,我们将依赖于🤗 Evaluate库中的指标。我们可以像加载数据集一样轻松地加载与MRPC数据集相关的指标,这次使用evaluate.load()函数。返回的对象有一个compute()方法,我们可以用它来计算指标:

1
2
3
4
import evaluate

metric = evaluate.load("glue", "mrpc")
metric.compute(predictions=preds, references=predictions.label_ids)
1
{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542}

你得到的结果可能会有所不同,因为模型头的随机初始化可能会改变它实现的指标。在这里,我们可以看到我们的模型在验证集上的准确率为85.78%,F1得分为89.97。这两个指标用于评估GLUE基准上的MRPC数据集的结果。BERT论文中的表格报告了基础模型的F1得分为88.9。那是小写模型,而我们目前使用的是大小写敏感模型,这解释了为什么结果更好。

将所有内容整合在一起,我们得到了compute_metrics()函数:

1
2
3
4
5
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)

为了看到它在每个epoch结束时报告指标的实际应用,下面是我们如何定义一个带有这个compute_metrics()函数的新Trainer:

1
2
3
4
5
6
7
8
9
10
11
12
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",并创建了一个新的模型——否则,我们只是在继续训练我们已经训练过的模型。要启动新的训练运行,我们执行:

1
trainer.train()

这次,在每个 epoch 结束时,它将报告验证损失和指标,以及训练损失。同样,由于模型的随机头初始化,您达到的准确率/F1 分数可能与我们的发现略有不同,但应该在同一范围内。

Trainer 可以开箱即用,支持多 GPU 或 TPU,并提供许多选项,如混合精度训练(在您的训练参数中使用 fp16 = True)。我们将在第 10 章中详细介绍它支持的所有功能。

这结束了使用Trainer API进行微调的介绍。在第七章中将会给出针对大多数常见NLP任务的示例,但现在让我们来看看如何使用纯PyTorch完成同样的操作。

结语

第二百一十七篇博文写完,开心!!!!

今天,也是充满希望的一天。