使用IMDb评论进行文本分类的Transformers
在本教程中,我们将使用PyTorch-Ignite对Transformers库中的模型进行微调,用于文本分类。我们将遵循微调预训练模型教程来进行文本预处理,并定义模型、优化器和数据加载器。
然后我们将使用Ignite来:
- 训练和评估模型
- 计算指标
- 设置实验和监控模型
根据教程,我们将使用 IMDb电影评论数据集来将评论分类为正面或负面。
必需的依赖项
!pip install pytorch-ignite transformers datasets
在我们深入之前,我们将使用
manual_seed来播种一切。
from ignite.utils import manual_seed
manual_seed(42)
基本设置
接下来,我们将按照教程加载我们的数据集和分词器来预处理数据。
数据预处理
from datasets import load_dataset
raw_datasets = load_dataset("imdb")
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 = raw_datasets.map(tokenize_function, batched=True)
我们即将结束关于PyTorch特定指令的教程。在这里,我们从原始数据集中提取了一个更大的子集。由于我们在开始时已经对所有内容进行了种子设置,因此我们不需要再提供种子。
tokenized_datasets = tokenized_datasets.remove_columns(["text"])
tokenized_datasets = tokenized_datasets.rename_column("label", "labels")
tokenized_datasets.set_format("torch")
small_train_dataset = tokenized_datasets["train"].shuffle().select(range(5000))
small_eval_dataset = tokenized_datasets["test"].shuffle().select(range(5000))
数据加载器
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=2)
优化器
from transformers import AdamW
optimizer = AdamW(model.parameters(), lr=5e-5)
LR 调度器
我们将使用内置的Ignite替代方案linear调度器,即
PiecewiseLinear。我们还将增加epoch的数量。
from ignite.contrib.handlers import PiecewiseLinear
num_epochs = 10
num_training_steps = num_epochs * len(train_dataloader)
milestones_values = [
(0, 5e-5),
(num_training_steps, 0.0),
]
lr_scheduler = PiecewiseLinear(
optimizer, param_name="lr", milestones_values=milestones_values
)
设置设备
import torch
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
model.to(device)
创建训练器
Ignite的
Engine 允许用户定义一个 process_function 来处理给定的一批数据。这个函数应用于数据集的所有批次。这是一个可以应用于训练和验证模型的通用类。一个 process_function 有两个参数 engine 和 batch。
教程中处理一批训练数据的代码如下:
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)
因此我们将定义一个process_function(下面称为train_step)来执行上述任务:
- 将
model设置为训练模式。 - 将
batch中的项目移动到device。 - 执行前向传播并生成
output。 - 提取损失。
- 使用损失执行反向传播,以计算模型参数的梯度。
- 使用梯度和优化器优化模型参数。
最后,我们选择返回loss,以便我们可以利用它进行进一步处理。
你还会注意到我们没有在train_step中更新lr_scheduler和progress_bar。这是因为Ignite会自动处理这些,我们将在本教程的后面部分看到。
def train_step(engine, batch):
model.train()
batch = {k: v.to(device) for k, v in batch.items()}
outputs = model(**batch)
loss = outputs.loss
loss.backward()
optimizer.step()
optimizer.zero_grad()
return loss
然后我们通过将train_step附加到训练引擎来创建一个模型trainer。稍后,我们将使用trainer来循环遍历训练数据集num_epochs次。
from ignite.engine import Engine
trainer = Engine(train_step)
我们之前定义的lr_scheduler是一个处理程序。
Handlers 可以是任何类型的函数(lambda函数、类方法等)。除此之外,Ignite提供了几个内置的处理程序以减少冗余代码。我们将这些处理程序附加到引擎上,引擎在特定的事件触发时执行。这些事件可以是任何情况,比如迭代的开始或一个周期的结束。这里是内置事件的完整列表。
因此,我们将通过add_event_handler()将lr_scheduler(处理器)附加到trainer(engine)上,以便它可以在Events.ITERATION_STARTED(迭代开始时)自动触发。
from ignite.engine import Events
trainer.add_event_handler(Events.ITERATION_STARTED, lr_scheduler)
这是我们没有在train_step()中包含lr_scheduler.step()的原因。
进度条
接下来我们创建了一个 Ignite 的
ProgessBar() 实例,并将其附加到训练器上,以替换 progress_bar.update(1)。
from ignite.contrib.handlers import ProgressBar
pbar = ProgressBar()
我们可以简单地跟踪进度:
pbar.attach(trainer)
或者也可以跟踪 trainer(或 train_step)的输出:
pbar.attach(trainer, output_transform=lambda x: {'loss': x})
创建评估器
类似于训练process_function,我们设置了一个函数来评估单批的训练/验证/测试数据。
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"])
以下是evaluate_step()的作用:
- 将模型设置为评估模式。
- 将
batch中的项目移动到device。 - 使用
torch.no_grad(),后续步骤将不计算任何梯度。 - 在模型上执行前向传播,以计算
outputs来自batch - 获取真实的
predictions来自logits(正类和负类的概率)。
最后,我们返回预测值和实际标签,以便我们可以计算指标。
你会注意到我们没有在evaluate_step()中计算指标。这是因为Ignite提供了内置的metrics,我们稍后可以将其附加到引擎上。
注意: Ignite 建议将指标附加到评估器而不是训练器上,因为在训练过程中模型参数不断变化,最好在静态模型上评估模型。这一点很重要,因为训练和评估的函数有所不同。训练返回一个单一的标量损失。评估返回 y_pred 和 y,因为该输出用于计算整个数据集的每批次指标。
Ignite中的所有指标都需要y_pred和y作为附加到Engine的函数的输出。
def evaluate_step(engine, batch):
model.eval()
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)
return {'y_pred': predictions, 'y': batch["labels"]}
下面我们创建两个引擎,一个训练评估器和一个验证评估器。train_evaluator 和 validation_evaluator 使用相同的函数,但它们有不同的用途,我们将在本教程的后面看到。
train_evaluator = Engine(evaluate_step)
validation_evaluator = Engine(evaluate_step)
附加指标
🤗 教程定义了一个用于评估的指标,准确率:
metric= load_metric("accuracy")
我们可以轻松地将Ignite内置的Accuracy()指标附加到train_evaluator和validation_evaluator上。我们还需要指定指标名称(如下面的accuracy)。在内部,它将使用y_pred和y来计算准确率。
from ignite.metrics import Accuracy
Accuracy().attach(train_evaluator, 'accuracy')
Accuracy().attach(validation_evaluator, 'accuracy')
日志指标
现在我们将定义自定义处理程序(函数)并将它们附加到训练过程的各个Events上。
以下两个函数都实现了类似的任务。它们打印了在数据集上运行的evaluator的结果。log_training_results()在训练评估器和训练数据集上执行此操作,而log_validation_results()在验证评估器和验证数据集上执行此操作。另一个区别是这些函数在训练器引擎中的附加方式。
第一种方法涉及使用装饰器,语法简单 - @ trainer.on(Events.EPOCH_COMPLETED),意味着装饰的函数将被附加到训练器上,并在每个周期结束时调用。
第二种方法涉及使用trainer的add_event_handler方法 - trainer.add_event_handler(Events.EPOCH_COMPLETED, custom_function)。这实现了与上述相同的结果。
@trainer.on(Events.EPOCH_COMPLETED)
def log_training_results(engine):
train_evaluator.run(train_dataloader)
metrics = train_evaluator.state.metrics
avg_accuracy = metrics['accuracy']
print(f"Training Results - Epoch: {engine.state.epoch} Avg accuracy: {avg_accuracy:.3f}")
def log_validation_results(engine):
validation_evaluator.run(eval_dataloader)
metrics = validation_evaluator.state.metrics
avg_accuracy = metrics['accuracy']
print(f"Validation Results - Epoch: {engine.state.epoch} Avg accuracy: {avg_accuracy:.3f}")
trainer.add_event_handler(Events.EPOCH_COMPLETED, log_validation_results)
早停
现在我们将为训练过程设置一个EarlyStopping处理程序。EarlyStopping需要一个score_function,允许用户定义停止训练的任何标准。在这种情况下,如果验证集的损失在2个epochs(patience)内没有减少,训练过程将提前停止。
from ignite.handlers import EarlyStopping
def score_function(engine):
val_accuracy = engine.state.metrics['accuracy']
return val_accuracy
handler = EarlyStopping(patience=2, score_function=score_function, trainer=trainer)
validation_evaluator.add_event_handler(Events.COMPLETED, handler)
模型检查点
最后,我们希望保存最佳的模型权重。因此,我们将使用 Ignite 的
ModelCheckpoint 处理程序在每个 epoch 结束时对模型进行检查点保存。这将创建一个 models 目录,并保存 2 个最佳模型(n_saved),前缀为 bert-base-cased。
from ignite.handlers import ModelCheckpoint
checkpointer = ModelCheckpoint(dirname='models', filename_prefix='bert-base-cased', n_saved=2, create_dir=True)
trainer.add_event_handler(Events.EPOCH_COMPLETED, checkpointer, {'model': model})
开始训练!
接下来,我们将运行训练器进行10个周期,并监控结果。下面我们可以看到ProgessBar打印每次迭代的损失,并按照我们在自定义函数中指定的方式打印训练和验证的结果。
trainer.run(train_dataloader, max_epochs=num_epochs)
就是这样!我们已经成功训练并评估了一个用于文本分类的Transformer。