00217 NLP Course - Fine-tuning a model with the Trainer API ubuntu
前言
🤗 Transformers 提供了一个 Trainer 类,帮助您在您自己的数据集上微调它提供的任何预训练模型。一旦您完成了上一节中的所有数据预处理工作,您只需几个步骤就可以定义 Trainer 了。最困难的部分可能是准备运行 Trainer.train() 的环境,因为它在 CPU 上运行会非常慢。如果您没有设置 GPU,您可以在 Google Colab 上获得免费的 GPU 或 TPU 访问权限。
下面的代码示例假设你已经执行了前一节的示例。这里是一个简短的总结,回顾你需要的内容:
1 | from datasets import load_dataset |
src link: https://huggingface.co/learn/nlp-course/chapter3/3
Operating System: Ubuntu 22.04.4 LTS
参考文档
训练
在我们定义 Trainer 之前的第一步是定义一个 TrainingArguments 类,它将包含 Trainer 用于训练和评估的所有超参数。你必须提供的唯一参数是一个目录,训练好的模型将保存在这里,以及沿途的检查点。对于所有其他的参数,你可以保留默认值,这对于基本的微调应该已经足够好了。
1 | from transformers import TrainingArguments |
💡 如果你希望在训练过程中自动将你的模型上传到 Hub,可以在 TrainingArguments 中传入 push_to_hub=True。我们将在第4章中学习更多关于这方面的内容。
第二步是定义我们的模型。与上一章一样,我们将使用 AutoModelForSequenceClassification 类,并设置两个标签:
1 | from transformers import AutoModelForSequenceClassification |
你会注意到,与第2章不同,在实例化这个预训练模型后,你会收到一个警告。这是因为 BERT 没有在分类句子对上进行预训练,所以预训练模型的头部已经被丢弃,取而代之的是一个适合序列分类的新头部。这些警告表明一些权重没有被使用(与被丢弃的预训练头部相对应的权重),而其他一些权重是随机初始化的(为新头部设置的权重)。它最后鼓励你训练模型,这正是我们现在要做的。
一旦我们有了模型,我们可以通过传递到目前为止构建的所有对象来定义一个 Trainer —— 模型、training_args、训练和验证数据集、我们的 data_collator 和我们的 tokenizer:
1 | from transformers import Trainer |
请注意,当你像我们在这里所做的那样传递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 | predictions = trainer.predict(tokenized_datasets["validation"]) |
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 | import numpy as np |
现在我们可以将这些preds与标签进行比较。为了构建我们的compute_metric()函数,我们将依赖于🤗 Evaluate库中的指标。我们可以像加载数据集一样轻松地加载与MRPC数据集相关的指标,这次使用evaluate.load()函数。返回的对象有一个compute()方法,我们可以用它来计算指标:
1 | import evaluate |
1 | {'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} |
你得到的结果可能会有所不同,因为模型头的随机初始化可能会改变它实现的指标。在这里,我们可以看到我们的模型在验证集上的准确率为85.78%,F1得分为89.97。这两个指标用于评估GLUE基准上的MRPC数据集的结果。BERT论文中的表格报告了基础模型的F1得分为88.9。那是小写模型,而我们目前使用的是大小写敏感模型,这解释了为什么结果更好。
将所有内容整合在一起,我们得到了compute_metrics()函数:
1 | def compute_metrics(eval_preds): |
为了看到它在每个epoch结束时报告指标的实际应用,下面是我们如何定义一个带有这个compute_metrics()函数的新Trainer:
1 | training_args = TrainingArguments("test-trainer", evaluation_strategy="epoch") |
注意,我们创建了一个新的TrainingArguments,将其evaluation_strategy设置为"epoch",并创建了一个新的模型——否则,我们只是在继续训练我们已经训练过的模型。要启动新的训练运行,我们执行:
1 | trainer.train() |
这次,在每个 epoch 结束时,它将报告验证损失和指标,以及训练损失。同样,由于模型的随机头初始化,您达到的准确率/F1 分数可能与我们的发现略有不同,但应该在同一范围内。
Trainer 可以开箱即用,支持多 GPU 或 TPU,并提供许多选项,如混合精度训练(在您的训练参数中使用 fp16 = True)。我们将在第 10 章中详细介绍它支持的所有功能。
这结束了使用Trainer API进行微调的介绍。在第七章中将会给出针对大多数常见NLP任务的示例,但现在让我们来看看如何使用纯PyTorch完成同样的操作。
结语
第二百一十七篇博文写完,开心!!!!
今天,也是充满希望的一天。