Transformer实战(13)——微调Transformer语言模型用于文本分类

Transformer实战(13)------微调Transformer语言模型用于文本分类

    • [0. 前言](#0. 前言)
    • [1. 文本分类介绍](#1. 文本分类介绍)
    • [2. 微调 BERT 模型进行情感分析](#2. 微调 BERT 模型进行情感分析)
    • [3. 模型推理](#3. 模型推理)
    • 小结
    • 系列链接

0. 前言

文本分类 (Text Classification) 广泛应用于舆情监测、邮件过滤、新闻聚类、产品评价等场景。传统方法通常难以充分挖掘文本的深层语义信息,基于 Transformer 的预训练模型(如 BERTDistilBERT 等)凭借强大的语义表示能力,已在各类文本分类任务上取得了显著提升。本文从文本分类的基本概念出发,学习如何利用 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})

需要注意的是,在推理过程中,需要将参数 id2labellabel2id 传递给模型。或者,可以实例化一个特定的 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 类(对于 TensorFlowTFTrainer )和 TrainingArguments 类(对于 TensorFlowTFTrainingArguments )能够简化大部分训练的复杂性。在 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-bit32-bit 浮点类型,可加速模型训练并减少内存占用
load_best_model_at_end 该选项会在训练结束时自动加载验证损失最优的模型权重
logging_dir TensorBoard 日志目录

(8) 长短期记忆 (Long Short Term Memory, LSTM) 网络之类的深度学习架构需要多个 epoch 进行微调,但是对于基于 Transformer 的模型,由于迁移学习的存在,通常 3epoch 就足够了。大多数情况下,3epoch 足以完成微调,因为预训练模型在预训练阶段已经学习了足够多的语言知识。为了确定合适的训练 epoch,我们需要检测训练过程中模型的损失变化。对于多数下游任务问题来说,3epoch 已经足够了。在训练过程中,每隔 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() 方法,帮助我们根据特定的指标(如精准度、RMSEPearson 相关系数、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 类是一个非常强大的工具,能够为 PyTorchTensorFlow (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 步,3epoch 总共需要 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 对应哪个标签 (POSNEG),还会输出类别的概率。

小结

本文介绍了基于 Transformer 架构的文本分类技术,重点介绍了如何使用 Hugging Facetransformers 库进行情感分析任务,使用 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的文本到文本模型