微调预训练模型
使用预训练模型有很多显著优势。它可以降低计算成本、减少碳足迹,并允许您使用最先进的模型,而无需从头开始训练。 🤗 Transformers 提供了数千个用于各种任务的预训练模型。当您使用预训练模型时,您需要在特定于您任务的数据集上对其进行训练。这被称为微调,这是一种非常强大的训练技巧。在本教程中,您将使用您选择的深度学习框架来微调一个预训练模型。
- 使用 🤗 Transformers 的 Trainer 微调预训练模型。
- 使用 TensorFlow 和 Keras 微调预训练模型。
- 使用原生 PyTorch 微调预训练模型。
准备数据集
在微调预训练模型之前,您需要下载数据集并将其准备用于训练。之前的教程向您展示了如何处理训练数据,现在您有机会将这些技能付诸实践!
首先加载 Yelp 评论 数据集。
>>> from datasets import load_dataset
>>> dataset = load_dataset("yelp_review_full")
>>> dataset["train"][100]
{'label': 0,
'text': 'My expectations for McDonalds are t rarely high. But for one to still fail so spectacularly...that takes something special!\\nThe cashier took my friends\'s order, then promptly ignored me. I had to force myself in front of a cashier who opened his register to wait on the person BEHIND me. I waited over five minutes for a gigantic order that included precisely one kid\'s meal. After watching two people who ordered after me be handed their food, I asked where mine was. The manager started yelling at the cashiers for \\"serving off their orders\\" when they didn\'t have their food. But neither cashier was anywhere near those controls, and the manager was the one serving food to customers and clearing the boards.\\nThe manager was rude when giving me my order. She didn\'t make sure that I had everything ON MY RECEIPT, and never even had the decency to apologize that I felt I was getting poor service.\\nI\'ve eaten at various McDonalds restaurants for over 30 years. I\'ve worked at more than one location. I expect bad days, bad moods, and the occasional mistake. But I have yet to have a decent experience at this store. It will remain a place I avoid unless someone in my party needs to avoid illness from low blood sugar. Perhaps I should go back to the racially biased service of Steak n Shake instead!'}
如您所知,您需要一个分词器来处理文本,并包含填充和截断策略来处理任何可变的序列长度。为了在一个步骤中处理您的数据集,请使用 🤗 Datasets 的 map
方法将预处理函数应用于整个数据集。
>>> from transformers import AutoTokenizer
>>> tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-cased")
>>> def tokenize_function(examples):
... return tokenizer(examples["text"], padding="max_length", truncation=True)
>>> tokenized_datasets = dataset.map(tokenize_function, batched=True)
如果您愿意,可以创建一个较小的完整数据集子集来进行微调,以减少所需时间。
>>> small_train_dataset = tokenized_datasets["train"].shuffle(seed=42).select(range(1000))
>>> small_eval_dataset = tokenized_datasets["test"].shuffle(seed=42).select(range(1000))
训练
此时,您应该按照与您要使用的框架相对应的部分进行操作。您可以使用右侧边栏中的链接跳转到您想要的框架 - 并且如果您想隐藏给定框架的所有内容,只需使用该框架块右上方的按钮即可!
使用 PyTorch Trainer 训练
🤗 Transformers 提供了一个 Trainer 类,该类针对训练 🤗 Transformers 模型进行了优化,使您可以更轻松地开始训练,而无需手动编写自己的训练循环。 Trainer API 支持各种训练选项和功能,例如日志记录、梯度累积和混合精度。
首先加载您的模型并指定预期标签的数量。从 Yelp 评论的 数据集卡 中,您可以知道有五个标签。
>>> from transformers import AutoModelForSequenceClassification
>>> model = AutoModelForSequenceClassification.from_pretrained("google-bert/bert-base-cased", num_labels=5)
您会看到一条警告,提示某些预训练权重未使用,而某些权重是随机初始化的。不用担心,这完全正常!BERT 模型的预训练头部被丢弃,并替换为随机初始化的分类头部。您将在您的序列分类任务上微调这个新的模型头部,并将预训练模型的知识转移到它上面。
训练超参数
接下来,创建一个 TrainingArguments 类,其中包含您可以调整的所有超参数以及用于激活不同训练选项的标志。对于本教程,您可以从默认的训练 超参数 开始,但可以随意尝试这些参数以找到最佳设置。
指定训练过程中保存检查点的路径。
>>> from transformers import TrainingArguments
>>> training_args = TrainingArguments(output_dir="test_trainer")
评估
Trainer 不会自动在训练期间评估模型性能。您需要向 Trainer 传递一个函数来计算和报告指标。 🤗 Evaluate 库提供了一个简单的 accuracy
函数,您可以使用 evaluate.load
加载(有关更多信息,请参阅此 快速入门)函数。
>>> import numpy as np
>>> import evaluate
>>> metric = evaluate.load("accuracy")
在 metric
上调用 compute
以计算预测的准确率。在将预测传递给 compute
之前,您需要将 logits 转换为预测(请记住所有 🤗 Transformers 模型都返回 logits)。
>>> def compute_metrics(eval_pred):
... logits, labels = eval_pred
... predictions = np.argmax(logits, axis=-1)
... return metric.compute(predictions=predictions, references=labels)
如果您希望在微调期间监控评估指标,请在训练参数中指定 eval_strategy
参数,以便在每个 epoch 结束时报告评估指标。
>>> from transformers import TrainingArguments, Trainer
>>> training_args = TrainingArguments(output_dir="test_trainer", eval_strategy="epoch")
Trainer
使用您的模型、训练参数、训练和测试数据集以及评估函数创建一个 Trainer 对象。
>>> trainer = Trainer(
... model=model,
... args=training_args,
... train_dataset=small_train_dataset,
... eval_dataset=small_eval_dataset,
... compute_metrics=compute_metrics,
... )
然后通过调用 train() 来微调您的模型。
>>> trainer.train()
使用 Keras 训练 TensorFlow 模型
您还可以使用 Keras API 在 TensorFlow 中训练 🤗 Transformers 模型!
加载 Keras 的数据
当您想要使用 Keras API 训练 🤗 Transformers 模型时,您需要将数据集转换为 Keras 可以理解的格式。如果您的数据集很小,您可以将其全部转换为 NumPy 数组并将其传递给 Keras。在执行更复杂的操作之前,让我们先尝试一下。
首先,加载一个数据集。我们将使用来自 GLUE 基准 的 CoLA 数据集,因为它是一个简单的二元文本分类任务,并且目前只获取训练拆分。
from datasets import load_dataset
dataset = load_dataset("glue", "cola")
dataset = dataset["train"] # Just take the training split for now
接下来,加载一个分词器并将数据标记化为 NumPy 数组。请注意,标签已经是 0 和 1 的列表,因此我们可以直接将其转换为 NumPy 数组而无需标记化!
from transformers import AutoTokenizer
import numpy as np
tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-cased")
tokenized_data = tokenizer(dataset["sentence"], return_tensors="np", padding=True)
# Tokenizer returns a BatchEncoding, but we convert that to a dict for Keras
tokenized_data = dict(tokenized_data)
labels = np.array(dataset["label"]) # Label is already an array of 0 and 1
最后,加载、compile
和 fit
模型。请注意,所有 Transformers 模型都具有默认的任务相关损失函数,因此除非需要,否则无需指定。
from transformers import TFAutoModelForSequenceClassification
from tensorflow.keras.optimizers import Adam
# Load and compile our model
model = TFAutoModelForSequenceClassification.from_pretrained("google-bert/bert-base-cased")
# Lower learning rates are often better for fine-tuning transformers
model.compile(optimizer=Adam(3e-5)) # No loss argument!
model.fit(tokenized_data, labels)
在 compile()
模型时,您不必传递损失参数!如果省略此参数,Hugging Face 模型会自动选择适合其任务和模型架构的损失。如果您愿意,始终可以通过自己指定损失来覆盖此设置!
这种方法对于较小的数据集非常有效,但对于较大的数据集,您可能会发现它开始成为问题。为什么?因为标记化的数组和标签必须完全加载到内存中,并且由于 NumPy 不处理“不规则”数组,因此每个标记化的样本都必须填充到整个数据集中最长样本的长度。这将使您的数组更大,所有这些填充标记也会减慢训练速度!
将数据加载为 tf.data.Dataset
如果要避免减慢训练速度,可以将数据加载为 tf.data.Dataset
。虽然可以根据需要编写自己的 tf.data
管道,但我们提供了两种便捷方法来执行此操作
- prepare_tf_dataset():在大多数情况下,我们推荐使用此方法。因为它是在模型上的方法,所以它可以检查模型以自动找出哪些列可用作模型输入,并丢弃其他列以创建更简单、更高效的数据集。
- to_tf_dataset:此方法更底层,当需要通过精确指定要包含的
columns
和label_cols
来精确控制数据集的创建方式时很有用。
在使用 prepare_tf_dataset() 之前,需要将分词器输出作为列添加到数据集中,如下面的代码示例所示
def tokenize_dataset(data):
# Keys of the returned dictionary will be added to the dataset as columns
return tokenizer(data["text"])
dataset = dataset.map(tokenize_dataset)
请记住,Hugging Face 数据集默认存储在磁盘上,因此这不会增加内存使用量!添加列后,可以从数据集中流式传输批次并为每个批次添加填充,与填充整个数据集相比,这大大减少了填充标记的数量。
>>> tf_dataset = model.prepare_tf_dataset(dataset["train"], batch_size=16, shuffle=True, tokenizer=tokenizer)
请注意,在上面的代码示例中,需要将分词器传递给 prepare_tf_dataset
,以便它在加载批次时能够正确地填充批次。如果数据集中所有样本的长度都相同,并且不需要填充,则可以跳过此参数。如果需要执行比仅填充样本更复杂的操作(例如,为掩码语言建模而损坏标记),则可以使用 collate_fn
参数传递一个函数,该函数将被调用以将样本列表转换为批次并应用任何所需的预处理。请参阅我们的 示例 或 笔记本 以了解此方法的实际应用。
创建 tf.data.Dataset
后,可以像以前一样编译和拟合模型。
model.compile(optimizer=Adam(3e-5)) # No loss argument!
model.fit(tf_dataset)
在原生 PyTorch 中训练
Trainer 负责训练循环,并允许您用一行代码微调模型。对于喜欢编写自己的训练循环的用户,您也可以在原生 PyTorch 中微调 🤗 Transformers 模型。
此时,您可能需要重新启动笔记本或执行以下代码以释放一些内存。
del model
del trainer
torch.cuda.empty_cache()
接下来,手动后处理 tokenized_dataset
以准备进行训练。
删除
text
列,因为模型不接受原始文本作为输入。>>> tokenized_datasets = tokenized_datasets.remove_columns(["text"])
将
label
列重命名为labels
,因为模型期望参数名为labels
。>>> tokenized_datasets = tokenized_datasets.rename_column("label", "labels")
将数据集的格式设置为返回 PyTorch 张量而不是列表。
>>> tokenized_datasets.set_format("torch")
然后,如前所示创建数据集的较小子集以加快微调速度。
>>> small_train_dataset = tokenized_datasets["train"].shuffle(seed=42).select(range(1000))
>>> small_eval_dataset = tokenized_datasets["test"].shuffle(seed=42).select(range(1000))
DataLoader
为训练和测试数据集创建 DataLoader
,以便可以迭代数据批次。
>>> from torch.utils.data import DataLoader
>>> train_dataloader = DataLoader(small_train_dataset, shuffle=True, batch_size=8)
>>> eval_dataloader = DataLoader(small_eval_dataset, batch_size=8)
使用预期标签数量加载模型。
>>> from transformers import AutoModelForSequenceClassification
>>> model = AutoModelForSequenceClassification.from_pretrained("google-bert/bert-base-cased", num_labels=5)
优化器和学习率调度器
创建优化器和学习率调度器以微调模型。让我们使用来自 PyTorch 的 AdamW
优化器。
>>> from torch.optim import AdamW
>>> optimizer = AdamW(model.parameters(), lr=5e-5)
从 Trainer 创建默认学习率调度器。
>>> from transformers import get_scheduler
>>> num_epochs = 3
>>> num_training_steps = num_epochs * len(train_dataloader)
>>> lr_scheduler = get_scheduler(
... name="linear", optimizer=optimizer, num_warmup_steps=0, num_training_steps=num_training_steps
... )
最后,指定 device
以在有权访问的情况下使用 GPU。否则,在 CPU 上训练可能需要几个小时而不是几分钟。
>>> import torch
>>> device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
>>> model.to(device)
如果您没有云 GPU,可以使用托管笔记本(如 Colaboratory 或 SageMaker StudioLab)免费访问。
太好了,现在您已准备好进行训练!🥳
训练循环
要跟踪训练进度,请使用 tqdm 库在训练步骤数量上添加进度条。
>>> from tqdm.auto import tqdm
>>> progress_bar = tqdm(range(num_training_steps))
>>> model.train()
>>> for epoch in range(num_epochs):
... for batch in train_dataloader:
... batch = {k: v.to(device) for k, v in batch.items()}
... outputs = model(**batch)
... loss = outputs.loss
... loss.backward()
... optimizer.step()
... lr_scheduler.step()
... optimizer.zero_grad()
... progress_bar.update(1)
评估
就像在 Trainer 中添加评估函数一样,在编写自己的训练循环时也需要执行相同的操作。但这次不是在每个 epoch 结束时计算和报告指标,而是使用 add_batch
累积所有批次,并在最后计算指标。
>>> import evaluate
>>> metric = evaluate.load("accuracy")
>>> model.eval()
>>> for batch in eval_dataloader:
... batch = {k: v.to(device) for k, v in batch.items()}
... with torch.no_grad():
... outputs = model(**batch)
... logits = outputs.logits
... predictions = torch.argmax(logits, dim=-1)
... metric.add_batch(predictions=predictions, references=batch["labels"])
>>> metric.compute()
其他资源
有关更多微调示例,请参阅
🤗 Transformers 示例 包括在 PyTorch 和 TensorFlow 中训练常见 NLP 任务的脚本。
🤗 Transformers 笔记本 包含有关如何在 PyTorch 和 TensorFlow 中针对特定任务微调模型的各种笔记本。