微调预训练模型¶
🤗 Transformers 提供了涉及各种任务的成千上万的预训练模型。当你使用预训练模型时,你需要在与任务相关的数据集上训练该模型。这种操作被称为微调,是一种非常强大的训练技术。
在本教程中,将选择深度学习框架来微调一个预训练模型:
- 使用 🤗 Transformers 的
Trainer来微调预训练模型。 - 在 TensorFlow 中使用
Keras来微调预训练模型。 - 在原生 PyTorch 中微调预训练模型。
from datasets import load_dataset
dataset = load_dataset("yelp_review_full")
dataset["train"][100]
对于文本类型的数据,需要一个tokenizer来处理文本,包括填充和截断操作以处理长度可变的序列。
如果想要一次性处理你的数据集,可以定义一个预处理函数,再使用 🤗 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))
训练¶
在接下来,可以应该根据你训练所用的框架来选择对应的教程章节。
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained("google-bert/bert-base-cased", num_labels=5)
你会看到一个警告,提到一些预训练权重未被使用,以及一些权重被随机初始化。
不用担心,这是完全正常的!BERT 模型预训练的head会被废弃,替换为一个随机初始化的分类head。在你的序列分类任务上微调这个新模型的head,将预训练模型的知识转移给它。
from transformers import TrainingArguments
training_args = TrainingArguments(output_dir="test_trainer")
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(
model=model,
args=training_args,
train_dataset=small_train_dataset,
eval_dataset=small_eval_dataset,
compute_metrics=compute_metrics,
)
然后调用train()来微调模型:
trainer.train()
使用keras训练TensorFlow模型¶
在 TensorFlow 中,可以使用 Keras API 训练 🤗 Transformers 模型!
加载用于 Keras 的数据¶
当使用 Keras API 训练 🤗 Transformers 模型时,你需要先将数据集转换为 Keras 可理解的格式。
如果你的数据集很小,可以将整个数据集都转换为NumPy 数组并传递给 Keras。
首先,需要加载一个数据集。这里会使用 [GLUE benchmark](https://huggingface.co/datasets/nyu-mll/glue) 中的 CoLA 数据集,因为它是一个简单的二元文本分类任务。
from datasets import load_dataset
dataset = load_dataset("glue", "cola")
dataset = dataset["train"] # 现在只使用训练数据集
接下来,加载一个tokenizer并将数据标记为 NumPy 数组。
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("google-bert/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组成的数组,因此我们可以直接将其转换为 NumPy 数组,不需要进行分词处理。
最后,加载 compile(编译) 和 fit(训练) 模型:
当你使用 compile() 编译模型时,你可以指定损失函数以覆盖默认配置, 也可以不指定,因为 Transformers 模型会自动选择适合其任务和模型架构的损失函数。
from transformers import TFAutoModelForSequenceClassification
from tensorflow.keras.optimizers import Adam
# 加载并编译我们的模型
model = TFAutoModelForSequenceClassification.from_pretrained("google-bert/bert-base-cased")
# 指定较低的学习率(optimizer),通常更适合微调模型
model.compile(optimizer=Adam(3e-5)) # 没有指定损失函数
# 训练模型
model.fit(tokenized_data, labels)
这种方法对于较小的数据集效果很好,但对于较大的数据集,可能就会出现问题。为什么呢?
因为分词后的数组和标签必须被完全加载到内存中,由于 NumPy 无法处理“不规则的数组”,所以每个分词后的样本长度都必须被填充到与数据集中最长样本相同的长度。这会导致数组变得更大,而这些padding tokens也会减慢训练的速度!
将数据加载为 tf.data.Dataset¶
为了避免训练速度减慢,可以将数据加载为 tf.data.Dataset。你可以自定义 tf.data 流水线,也可以选择以下两种方便的方法来实现这一点:
- prepare_tf_dataset():在大多数情况下推荐使用该方法,因为它是模型上的一个方法,可以通过检查模型来自动确定哪些列可用作模型的输入,并丢弃其他列以创建一个更简单、性能更好的数据集。
to_tf_dataset:这个方法更低级,在你想要完全控制数据集的创建方式时,可以通过指定要包含的确切columns和label_cols来实现。
在使用 prepare_tf_dataset() 之前,还需要将 tokenizer 的输出作为列数据添加到数据集,如下代码示例:
def tokenize_dataset(data):
# 返回的键将被添加到数据集中作为列。
return tokenizer(data["text"])
dataset = dataset.map(tokenize_dataset)
注意!Hugging Face 的数据集存储默认在硬盘上,因此这不会增加内存使用!一旦列被添加,你可以从数据集中流式地传输批次数据,并为每个批次添加padding tokens,这与一次性为整个数据集添加 padding tokens 相比,大大减少了 padding tokens 的数量,避免减慢训练的速度。
tf_dataset = model.prepare_tf_dataset(dataset["train"], batch_size=16, shuffle=True, tokenizer=tokenizer)
在上面的代码示例中,你需要将 tokenizer 传递给 prepare_tf_dataset(),以便它可以在加载批次时正确地填充数据集。
- 如果数据集中的所有样本都具有相同的长度而且不需要填充,则可以跳过此参数。
- 如果需要执行比填充样本更复杂的操作(例如,用于掩码语言模型的tokens 替换),则可以使用
collate_fn参数,而不是传递一个自定义函数来将样本列表转换为批次并应用任何所需的预处理操作。
在创建了 tf.data.Dataset 后,你可以像以前一样编译和训练模型:
# 指定较低的学习率(optimizer),通常更适合微调模型
model.compile(optimizer=Adam(3e-5)) # 没有指定损失函数
# 训练模型
model.fit(tf_dataset)
在原生 PyTorch 中训练¶
Trainer 负责训练循环,允许你在一行代码中微调模型。
对于喜欢编写自定义的训练循环的用户,也可以在原生 PyTorch 中微调 🤗 Transformers 模型。
现在,你可能需要重新启动 notebook,或执行以下代码以释放一些内存:
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张量 而不是 lists:
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)
优化器(Optimizer)和学习率调度器(Learning Rate Scheduler)¶
- Optimizer : 优化器是用于调整模型参数以最小化损失函数的算法,常见的算法有SGD、Adam、RMSprop等。在训练过程中,优化器根据损失函数的梯度来更新模型的权重和偏置,从而逐步改进模型的性能。
- Learning Rate Scheduler : 学习率调度器用于在训练过程中动态调整学习率,常见的有StepLR、ExponentialLR、ReduceLROnPlateau等。适当调整学习率可以帮助模型更快地收敛,并避免过拟合。
创建一个optimizer和learning rate scheduler以进行模型微调。让我们使用 PyTorch 中的 [AdamW](https://pytorch.org/docs/stable/generated/torch.optim.AdamW.html) 优化器:
from torch.optim import AdamW
optimizer = AdamW(model.parameters(), lr=5e-5)
创建来自 Trainer 的默认 learning rate scheduler:
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 进行训练可能需要几个小时。
Metal Performance Shaders (MPS):PyTorch 通过 MPS 后端实现对 Apple GPU 的加速。MPS 是 Apple 提供的一套用于高性能图形和计算任务的框架,但 PyTorch 对 MPS 的支持仍处于实验阶段,可靠性未得到保证。
Compute Unified Device Architecture (CUDA):通过 CUDA,PyTorch 能够在 NVIDIA 的 GPU 上进行高效的并行计算,特别是在深度学习和高性能计算任务中。确保你的 PyTorch 版本与安装的 CUDA 版本兼容。
下面是在不同操作系统中使用不同GPU加速模型训练的示例:
# NVIDIA GPU - CUDA
import torch
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
model.to(device)
# Apple GPU - MPS
import torch
device = torch.device("mps") if torch.backends.mps.is_available() else torch.device("cpu")
model.to(device)
如果没有 GPU,可以通过notebook平台如 Colaboratory 或 SageMaker StudioLab 来免费获得云端GPU使用。
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 中训练常见自然语言处理任务的脚本。
🤗 Transformers 笔记 包含针对特定任务在 PyTorch 和 TensorFlow 中微调模型的各种 notebook。