微调一个预训练模型

使用预训练模型有很多好处。它可以减少计算成本和碳足迹,并且使您可以使用最先进的模型,而无需从头开始训练一个模型。🤗 Transformers 提供了数千种预训练模型,适用于各种任务。当您使用预训练模型时,您需要在与您的任务相关的数据集上进行训练。这被称为微调,是一种非常强大的训练技术。在本教程中,您将使用您选择的深度学习框架来微调一个预训练模型:

  • 使用 🤗 Transformers Trainer 来微调一个预训练模型。
  • 使用 Keras 在 TensorFlow 中微调一个预训练模型。
  • 在原生 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("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
Hide Pytorch content

使用 PyTorch Trainer 进行训练

🤗 Transformers 提供了一个经过优化的 Trainer 类,用于训练 🤗 Transformers 模型,使您能够在不手动编写自己的训练循环的情况下更轻松地开始训练。Trainer API 支持广泛的训练选项和功能,例如日志记录、梯度累积和混合精度。

首先加载模型并指定预期标签数。根据 Yelp 评论 数据集卡片 ,我们知道有五个标签:

>>> from transformers import AutoModelForSequenceClassification

>>> model = AutoModelForSequenceClassification.from_pretrained("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 之前,使用 logits(记住所有的 🤗 Transformers 模型都返回 logits)将预测转换为 logits:

>>> def compute_metrics(eval_pred):
...     logits, labels = eval_pred
...     predictions = np.argmax(logits, axis=-1)
...     return metric.compute(predictions=predictions, references=labels)

如果您想要在微调过程中监控评估指标,请在您的训练参数中指定 evaluation_strategy 参数,以在每个 epoch 结束时报告评估指标:

>>> from transformers import TrainingArguments, Trainer

>>> training_args = TrainingArguments(output_dir="test_trainer", evaluation_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()
TensorFlow
Hide TensorFlow content

使用 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

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
tokenized_data = tokenizer(dataset["sentence"], return_tensors="np", padding=True)
# 分词器返回BatchEncoding,但我们将其转换为Keras的字典格式
tokenized_data = dict(tokenized_data)

labels = np.array(dataset["label"]) # 标签已经是0和1的数组

最后,加载、编译, 和 拟合 模型。请注意,Transformer模型都具有默认的与任务相关的损失函数,因此除非您想要指定一个,否则不需要在这里指定:

from transformers import TFAutoModelForSequenceClassification
from tensorflow.keras.optimizers import Adam

# 加载并编译我们的模型
model = TFAutoModelForSequenceClassification.from_pretrained("bert-base-cased")
# 对于微调Transformer,较低的学习率通常更好
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管道,但我们有两种方便的方法可以做到这一点:

  • [~TFPreTrainedModel.prepare_tf_dataset]:在大多数情况下,我们建议使用此方法。因为它是模型的一个方法,它可以检查模型以自动确定哪些列可用作模型输入,并丢弃其他列以创建一个更简单、更高效的数据集。
  • [~datasets.Dataset.to_tf_dataset]:这种方法更低级,当您想要完全控制数据集的创建方式时,通过指定要包括的确切columns和label_cols来使用。

在使用[~TFPreTrainedModel.prepare_tf_dataset]之前,您需要将分词器的输出作为列添加到数据集中,如以下代码示例所示:

def tokenize_dataset(data):
    # 返回的字典的键将作为列添加到数据集中
    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参数,通过传递将调用以将样本列表转换为批次并应用任何您想要的预处理的函数。请参阅我们的 示例notebooks 以了解此方法的实际应用。

创建了tf.data.Dataset之后,您可以像以前一样编译和拟合模型:

model.compile(optimizer=Adam(3e-5))  # 不需要损失参数!

model.fit(tf_dataset)

在本机PyTorch中进行训练

Pytorch
Hide Pytorch content

[Trainer]负责训练循环,并允许您在一行代码中进行微调。对于更喜欢编写自己的训练循环的用户,您也可以在本机PyTorch中微调🤗 Transformers模型。

在此时,您可能需要重新启动您的笔记本电脑,或者执行以下代码以释放一些内存:

del model
del trainer
torch.cuda.empty_cache()

接下来,手动对tokenized_dataset进行后处理,以准备好进行训练。

  1. 移除text列,因为模型不接受原始文本作为输入:

    >>> tokenized_datasets = tokenized_datasets.remove_columns(["text"])
  2. label列重命名为labels,因为模型期望的参数名称是labels

    >>> tokenized_datasets = tokenized_datasets.rename_column("label", "labels")
  3. 将数据集的格式设置为返回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("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
... )

最后,如果您可以访问GPU,最好指定device为GPU以加速训练。否则,在CPU上进行训练可能需要几个小时,而不是几分钟。

>>> import torch

>>> device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
>>> model.to(device)

如果没有GPU,您可以通过像 ColaboratorySageMaker StudioLab 这样的托管笔记本免费访问云GPU。

很好,现在您已经准备好开始训练了!🥳

训练循环

为了跟踪训练进度,您可以使用 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()

更多资源

有关更多微调示例,请参考: