Transformers提供了一个 Trainer 类来帮助在数据集上微调任何预训练模型。
TrainingArguments
类。它将包含 Trainer用于训练和评估的所有超参数。其中唯一必须提供的参数是保存训练模型的目录——output_dir( The output directory where the model predictions and checkpoints will be written.)参数。对于其余的参数,使用默认值。
以分类句子模型为例,第二步是定义我们的模型。正如在将使用 AutoModelForSequenceClassification 类,它有两个参数:
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
在实例化此预训练模型后会收到警告。这是因为 BERT 没有在句子对分类方面进行过预训练,所以预训练模型的原来的头部(分类器或者说线性层)已经被丢弃,而是添加了一个适合句子序列分类的新头部。警告表明一些权重没有使用(对应于丢弃的预训练头的那些),而其他一些权重被随机初始化(新头的那些)。
确定了模型之后,就可以定义一个Trainer
通过将之前构造的所有对象传递给它——model
、training_args
,训练和验证数据集
,data_collator
,和tokenizer
:
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,
)
为了查看模型在每个训练周期结束的好坏,下面是使用**compute_metrics()**函数定义一个新的 Trainer。现在建立compute_metric()函数来较为直观地评估模型的好坏,可以使用 Evaluate 库中的指标。
def compute_metrics(eval_preds):
metric = evaluate.load("glue", "mrpc") # 加载与 MRPC 数据集关联的指标
logits, labels = eval_preds
predictions = np.argmax(logits, axis=-1)
return metric.compute(predictions=predictions, references=labels) # 返回的对象有一个 compute()方法我们可以用来进行度量计算的方法:
只需要调用Trainer的train() 方法 :
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,
)
trainer.train()
请注意,这里是设置了一个新的 TrainingArguments 它的evaluation_strategy 设置为 epoch 并创建了一个新模型。如果不创建新的模型就直接训练,就只会继续训练之前我们已经训练过的模型。要启动新的训练运行,我们执行:
trainer.train()
定义模型等地方就不在赘述了,直接从优化器开始。
这里使用目前预训练语言模型常用的AdamW
。
from transformers import AdamW
optimizer = AdamW(model.parameters(), lr=2e-5)
默认使用的学习率调度器只是从最大值 (5e-5) 到 0 的线性衰减。 为了定义它,需要知道我们训练的次数,即所有数据训练的次数(epochs)乘以的数据量(这是所有训练数据的数量)
from transformers import get_scheduler
num_epochs = 3
num_training_steps = num_epochs * len(train_dataloader)
lr_scheduler = get_scheduler(
"linear",
'''
可用参数
LINEAR = "linear"
COSINE = "cosine"
COSINE_WITH_RESTARTS = "cosine_with_restarts"
POLYNOMIAL = "polynomial"
CONSTANT = "constant"
CONSTANT_WITH_WARMUP = "constant_with_warmup"
'''
optimizer=optimizer,
num_warmup_steps=0,
num_training_steps=num_training_steps,
)
print(num_training_steps)
import torch
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
model.to(device)
为了了解训练何时结束,使用 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)
在这里依然使用 Evaluate 库提供的指标。其中已经了解了 metric.compute()
方法,当使用 add_batch()
方法进行预测循环时,实际上该指标可以为累积所有 batch
的结果。一旦我们累积了所有 batch
,就可以使用 metric.compute()
得到最终结果 .以下是在评估循环中实现所有这些的方法:
import evaluate
metric = evaluate.load("glue", "mrpc")
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()
使用 Accelerate库,只需进行一些调整,就可以在多个 GPU 或 TPU 上启用分布式训练。从创建训练和验证数据加载器开始,在原生Pytorch训练方法中训练循环如下所示:
from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
optimizer = AdamW(model.parameters(), lr=3e-5)
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
model.to(device)
num_epochs = 3
num_training_steps = num_epochs * len(train_dataloader)
lr_scheduler = get_scheduler(
"linear",
optimizer=optimizer,
num_warmup_steps=0,
num_training_steps=num_training_steps,
)
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)
更改如下:
+ from accelerate import Accelerator
from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler
+ accelerator = Accelerator()
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
optimizer = AdamW(model.parameters(), lr=3e-5)
- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
- model.to(device)
+ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare(
+ train_dataloader, eval_dataloader, model, optimizer
+ )
num_epochs = 3
num_training_steps = num_epochs * len(train_dataloader)
lr_scheduler = get_scheduler(
"linear",
optimizer=optimizer,
num_warmup_steps=0,
num_training_steps=num_training_steps
)
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()
+ accelerator.backward(loss)
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
progress_bar.update(1)
要添加的第一行是导入
Accelerator
。第二行实例化一个Accelerator
对象 ,它将查看环境并初始化适当的分布式设置。 Accelerate 处理数据在设备间的传递,因此可以删除将模型放在设备上的那行代码(或者可使用accelerator.device
代替device
)。然后大部分工作会在将数据加载器、模型和优化器发送到的
accelerator.prepare()
中完成。这将会把这些对象包装在适当的容器中,以确保分布式训练按预期工作。要进行的其余更改是删除将batch
放在device
的那行代码(同样,如果想保留它,可以将其更改为使用accelerator.device
) 并将loss.backward()
替换为accelerator.backward(loss)
。