Transformer实战(13)------微调Transformer语言模型用于文本分类
0. 前言
文本分类 (Text Classification
) 广泛应用于舆情监测、邮件过滤、新闻聚类、产品评价等场景。传统方法通常难以充分挖掘文本的深层语义信息,基于 Transformer
的预训练模型(如 BERT
、DistilBERT
等)凭借强大的语义表示能力,已在各类文本分类任务上取得了显著提升。本文从文本分类的基本概念出发,学习如何利用 transformers
库对预训练 BERT
模型进行微调,实现高效的文本分类。
1. 文本分类介绍
文本分类 (Text Classification
) 是将一个文档(如句子、评论、书籍章节、电子邮件内容等)映射到一个预定义类别列表中的一种方法。在只有两个类别的情况下,分别为正面和负面标签,使用二分类,例如情感分析;对于超过两个类别的情况,称之为多类别分类,其中类别是互斥的;而在多标签分类中类别不是互斥的,这意味着一个文档可以有多个标签。例如,一篇新闻文章的内容可能同时涉及体育和娱乐。此外,我们可能还需要对文档进行评分(范围在 [-1,1]
之间)或对其进行排名(范围在 [1-5]
之间),可以通过回归模型来解决这类问题,因为输出类型是连续的数值,而不是离散的类别。
Transformer
架构能够高效地解决这些问题。对于句子对任务,如文档相似性或文本蕴含,输入不是单个句子,而是两个句子,如下图所示。我们可以评估两个句子在语义上的相似度,或者预测它们是否在语义上相似。另一类句子对任务是文本蕴含,可以将其定义为多类别分类,两个序列映射为在GLUE 基准测试中所定义的蕴含/矛盾/中立。

接下来,通过微调预训练的 BERT
模型开始训练过程。微调需要对现有的 BERT
模型的权重进行细微调整,以适应不同的自然语言处理任务。本节中,我们使用一个非常常见的任务进行微调:情感分析。
2. 微调 BERT 模型进行情感分析
在本节中,我们将学习如何通过使用 IMDb
情感数据集来微调预训练的 BERT
模型进行情感分析。
(1) 首先,检查并保存当前的设备信息:
python
from torch import cuda
device = 'cuda' if cuda.is_available() else 'cpu'
(2) 使用 DistilBertForSequenceClassification
类,它继承自 DistilBert
类,并在顶部添加了一个特殊的序列分类头。我们利用这个分类头来训练分类模型,默认情况下类别数为 2
:
python
from transformers import DistilBertTokenizerFast, DistilBertForSequenceClassification
model_path= 'distilbert-base-uncased'
tokenizer = DistilBertTokenizerFast.from_pretrained(model_path)
model = DistilBertForSequenceClassification.from_pretrained(model_path, id2label={0:"NEG", 1:"POS"}, label2id={"NEG":0, "POS":1})
需要注意的是,在推理过程中,需要将参数 id2label
和 label2id
传递给模型。或者,可以实例化一个特定的 config
对象并将其传递给模型:
python
config = AutoConfig.from_pre-trained(....)
SequenceClassification.from_pre-trained(.... config=config)
(3) 本节中,我们使用流行的情感分类数据集------IMDb
数据集。原始数据集包含两组数据:25000
个训练样本和 25000
个测试样本,我们将把测试数据集进一步拆分为测试集和验证集。需要注意的是,数据集的前半部分样本为正面情感,后半部分样本为负面情感。我们可以按如下方式拆分数据样本:
python
from datasets import load_dataset
# to take entire dataset from original train 25 K AND TEST 25K
imdb_train= load_dataset('imdb', split="train")
imdb_test= load_dataset('imdb', split="test[:6250]+test[-6250:]")
imdb_val= load_dataset('imdb', split="test[6250:12500]+test[-12500:-6250]")
(4) 检查数据集的形状:
python
imdb_train.shape, imdb_test.shape, imdb_val.shape
# ((25000, 2), (12500, 2), (12500, 2))
(5) 为了加速训练过程,我们也可以选择数据集的其中一部分。例如,选择 4000
个训练样本、1000
个测试样本和 1000
个验证样本:
python
imdb_train= load_dataset('imdb', split="train[:2000]+train[-2000:]")
imdb_test= load_dataset('imdb', split="test[:500]+test[-500:]")
imdb_val= load_dataset('imdb', split="test[500:1000]+test[-1000:-500]")
(6) 将数据集通过分词器模型进行处理,以便为训练做准备:
python
enc_train = imdb_train.map(lambda e: tokenizer( e['text'], padding=True, truncation=True), batched=True, batch_size=1000)
enc_test = imdb_test.map(lambda e: tokenizer( e['text'], padding=True, truncation=True), batched=True, batch_size=1000)
enc_val = imdb_val.map(lambda e: tokenizer( e['text'], padding=True, truncation=True), batched=True, batch_size=1000)
(7) 查看处理后的训练集。分词器已经将注意力掩码和输入 ID 添加到数据集中,以便 BERT 模型能够进行处理:
python
import pandas as pd
pd.DataFrame(enc_train)
输出如下所示,可以看到数据集已经准备完毕,可以进行训练和测试:

Trainer
类(对于 TensorFlow
是 TFTrainer
)和 TrainingArguments
类(对于 TensorFlow
是 TFTrainingArguments
)能够简化大部分训练的复杂性。在 TrainingArguments
类中定义参数集,然后将其传递给 Trainer
对象。每个训练参数的作用如下:
参数 | 描述 |
---|---|
outpu_dir |
模型权重以及预测结果最终保存的位置 |
do_train , do_eval |
用于在训练期间监控模型性能 |
logging_strategy |
可选值包括 no , epoch , steps (默认值) |
logging_steps |
两次保存日志到 logging_dir 目录之间的步数间隔,默认值为 500 |
save_strategy |
用于保存模型权重,可选值包括 no , epoch , steps (默认值) |
save_steps |
两次保存模型权重之间的步数间隔,默认值为 500 |
fp16 |
用于混合精度训练,同时使用 16-bit 和 32-bit 浮点类型,可加速模型训练并减少内存占用 |
load_best_model_at_end |
该选项会在训练结束时自动加载验证损失最优的模型权重 |
logging_dir |
TensorBoard 日志目录 |
(8) 长短期记忆 (Long Short Term Memory, LSTM) 网络之类的深度学习架构需要多个 epoch
进行微调,但是对于基于 Transformer
的模型,由于迁移学习的存在,通常 3
个 epoch
就足够了。大多数情况下,3
个 epoch
足以完成微调,因为预训练模型在预训练阶段已经学习了足够多的语言知识。为了确定合适的训练 epoch
,我们需要检测训练过程中模型的损失变化。对于多数下游任务问题来说,3
个 epoch
已经足够了。在训练过程中,每隔 200
步保存一次模型检查点,保存在 ./MyIMDBModel
文件夹中:
python
from transformers import TrainingArguments, Trainer
training_args = TrainingArguments(
output_dir='./MyIMDBModel',
do_train=True,
do_eval=True,
num_train_epochs=3,
per_device_train_batch_size=32,
per_device_eval_batch_size=64,
warmup_steps=100,
weight_decay=0.01,
logging_strategy='steps',
logging_dir='./logs',
logging_steps=50,
evaluation_strategy="steps",
save_strategy="steps",
fp16=cuda.is_available(),
load_best_model_at_end=True
)
(9) 在实例化 Trainer
对象之前,定义 compute_metrics()
方法,帮助我们根据特定的指标(如精准度、RMSE
、Pearson
相关系数、BLEU
等)监控训练进展。文本分类问题(例如情感分类和多类别分类问题)通常使用微平均( micro-averaging
)或宏平均( macro-averaging
)的 F1 分数
进行评估。宏平均方法对每个类别赋予相等的权重,而微平均方法则对每个文本或每个词元的分类决策赋予相等的权重。微平均等于模型正确决策次数与总决策次数之比。而宏平均方法计算每个类别的精准度、召回率和 F1 分数
的平均值。对于分类问题,更适合使用宏平均进行评估,因为我们希望对每个标签赋予相同的权重:
python
from sklearn.metrics import accuracy_score, precision_recall_fscore_support
def compute_metrics(pred):
labels = pred.label_ids
preds = pred.predictions.argmax(-1)
precision, recall, f1, _ = precision_recall_fscore_support(labels, preds, average='macro')
acc = accuracy_score(labels, preds)
return {
'Accuracy': acc,
'F1': f1,
'Precision': precision,
'Recall': recall
}
(10) 实例化 Trainer
对象并启动。得益于 transformers
库,Trainer
类是一个非常强大的工具,能够为 PyTorch
和 TensorFlow
(TensorFlow
使用 TFTrainer
) 组织复杂的训练和评估流程:
python
trainer = Trainer(
model=model,
args=training_args,
train_dataset=enc_train,
eval_dataset=enc_val,
compute_metrics= compute_metrics
)
(11) 最后,启动训练过程:
python
results=trainer.train()
以上调用会记录模型指标。整个 IMDb
数据集包含 25000
个训练样本,如果批大小为 32
,则有 25K/32 ≈ 782
步,3
个 epoch
总共需要 2346
步 (782 x 3
):

(12) Trainer
对象会保留验证损失最小的模型权重,在三个(训练/测试/验证)数据集上评估最佳模型权重:
python
q=[trainer.evaluate(eval_dataset=data) for data in [enc_train, enc_val, enc_test]]
pd.DataFrame(q, index=["train","val","test"]).iloc[:,:5]
输出结果如下所示:

完成训练/测试阶段后,可以看到模型具有 91.40
的准确率和 91.29
的宏平均 F1 分数
。为了更详细地监控训练过程,可以调用如 TensorBoard
等高级工具。这些工具会解析日志并能够跟踪各种指标进行全面分析。将性能和其他指标记录在 ./logs
文件夹中。只需运行 tensorboard
函数,就能查看日志:
python
%reload_ext tensorboard
%tensorboard --logdir logs
3. 模型推理
(1) 接下来,使用模型进行推理,检查它是否正常工作。定义一个预测函数来简化预测步骤:
python
def get_prediction(text):
inputs = tokenizer(text, padding=True, truncation=True, max_length=250, return_tensors="pt").to(device)
outputs = model(inputs["input_ids"].to(device),inputs["attention_mask"].to(device))
probs = outputs[0].softmax(1)
return probs, probs.argmax()
(2) 运行模型进行推理:
python
model.to(device)
text = "I didn't like the movie since it bored me "
get_prediction(text)[1].item()
(3) 输出结果为 0
,表示负面情感。我们已经定义了每个 ID
对应的标签,可以使用这个映射关系来获取标签。或者,我们可以将这些繁琐的步骤交给专门的 API
,即 Pipeline
。在实例化之前,先保存最佳模型,以便以后进行推理:
python
model_save_path = "MyBestIMDBModel"
trainer.save_model(model_save_path)
tokenizer.save_pretrained(model_save_path)
Pipeline API
是一个便捷的方式,用来使用预训练模型进行推理。从保存的路径加载模型,然后将其传递给 Pipeline API
,后者会完成其余的操作。我们也可以跳过保存模型的步骤,直接将模型和 tokenizer
对象传递给 Pipeline API
。
(4) 当进行二分类时,我们需要在 pipeline
中指定任务名称参数为 sentiment-analysis
:
python
from transformers import pipeline, DistilBertForSequenceClassification, DistilBertTokenizerFast
model = DistilBertForSequenceClassification.from_pretrained("MyBestIMDBModel")
tokenizer= DistilBertTokenizerFast.from_pretrained("MyBestIMDBModel")
nlp= pipeline("sentiment-analysis", model=model, tokenizer=tokenizer)
nlp("the movie was very impressive")
# [{'label': 'POS', 'score': 0.9461892247200012}]
nlp("the script of the picture was very poor")
# [{'label': 'NEG', 'score': 0.957322359085083}]
Pipeline API
处理输入,并能够自动学习哪个 ID
对应哪个标签 (POS
或 NEG
),还会输出类别的概率。
小结
本文介绍了基于 Transformer
架构的文本分类技术,重点介绍了如何使用 Hugging Face
的 transformers
库进行情感分析任务,使用 Trainer
类对 IMDb
数据集进行了情感预测模型的微调。训练后保存最佳模型权重,通过 Pipeline API
实现便捷的预测功能,自动输出标签及置信度。
系列链接
Transformer实战(1)------词嵌入技术详解
Transformer实战(2)------循环神经网络详解
Transformer实战(3)------从词袋模型到Transformer:NLP技术演进
Transformer实战(4)------从零开始构建Transformer
Transformer实战(5)------Hugging Face环境配置与应用详解
Transformer实战(6)------Transformer模型性能评估
Transformer实战(7)------datasets库核心功能解析
Transformer实战(8)------BERT模型详解与实现
Transformer实战(9)------Transformer分词算法详解
Transformer实战(10)------生成式语言模型 (Generative Language Model, GLM)
Transformer实战(11)------从零开始构建GPT模型
Transformer实战(12)------基于Transformer的文本到文本模型