基于Paddle微调ERNIE的中文情感分析实战教程
1. 引言
欢迎来到 PaddleNLP 实战教程系列!本次教程将带您进入自然语言处理 (NLP) 中最经典、最广泛应用的任务之一:情感分析 (Sentiment Analysis) 。我们将使用强大的预训练模型 ERNIE 来对中文文本进行情感倾向的判断。
1.1 什么是情感分析?
情感分析,又称为意见挖掘 (Opinion Mining),是利用自然语言处理、文本分析和计算语言学等方法,对带有情感色彩的主观性文本进行提取、分析、归纳和推理的过程。简单来说,它的目标就是识别和判断一段文本所表达的情感是积极的(正面)、消极的(负面),还是中性的。
例如,给定一条用户评价:"这家餐厅的烤鸭味道绝了,环境也很棒!",情感分析系统应该能判断出这是一条积极 的评价。而对于"等了半个多小时才上菜,味道也很一般",则应判断为消极。
1.2 情感分析的应用场景和项目意义
情感分析技术在商业和研究领域都有着巨大的应用价值,是许多智能应用的基础模块。
| 应用领域 | 具体场景 | 项目意义 |
|---|---|---|
| 商业智能 | 舆情监控、品牌声誉管理、产品评价分析 | 实时了解公众情绪,优化产品,进行危机公关 |
| 客户关系 | 分析客服对话、服务工单、用户满意度问卷 | 自动评估客户满意度,发现服务痛点,提升服务质量 |
| 金融科技 | 分析财经新闻、社交媒体情绪、股评 | 辅助量化交易和投资决策 |
| 社会科学 | 分析公众对特定社会事件或政策的情感倾向 | 为社会学、传播学等领域的研究提供数据参考 |
本项目旨在通过一个完整的实战案例,让您掌握如何利用预训练模型解决真实场景下的文本分类问题,为您在自己的业务或研究中应用NLP技术打下坚实的基础。
1.3 为什么选择 ERNIE 模型?
在众多预训练模型中,我们选择 ERNIE 作为本次教程的核心,主要有以下原因:
1. 更懂中文的预训练机制 :ERNIE 是由百度提出的、针对中文语言特点进行深度优化的预训练模型。它不仅仅学习字与字之间的关系,还通过知识掩码策略 (Knowledge Masking Strategies) 学习了更完整的语义单元(如词、短语和实体)。
- 一个生动的比喻:如果把普通模型的学习比作做"完形填空",每次只挖掉一个字让它猜;那么ERNIE的学习就像是在做更难的"成语填空"或"人名填空"。例如,它会把"哈利·波特"整个词挖掉,然后去预测这个被挖掉的实体。通过这种方式,ERNIE被迫学习到了"哈利·波特"是一个人名,以及与它相关的复杂语义,而不是仅仅把这几个字看作孤立的符号。
这种对知识的深度学习使得ERNIE在处理中文任务时,语义理解能力更胜一筹。
2. 丰富的模型选择与适中的规模 :PaddleNLP 提供了覆盖从大到小的完整ERNIE模型家族。对于本教程,我们选择 ernie-3.0-medium-zh,它在性能和资源消耗之间取得了很好的平衡。
下表展示了PaddleNLP中支持的部分ERNIE中文模型:
| 模型名称 (Pretrained Weight) | 层数 | 隐藏层大小 | 注意力头数 | 参数量 | 特点 |
|---|---|---|---|---|---|
ernie-1.0-base-zh |
12 | 768 | 12 | 108M | 经典的基础版ERNIE |
ernie-3.0-base-zh |
12 | 768 | 12 | 118M | ERNIE 3.0的基础版 |
ernie-3.0-medium-zh |
6 | 768 | 12 | 75M | 本教程选用,规模适中,效果优异 |
ernie-3.0-mini-zh |
6 | 384 | 12 | 27M | 更轻量的版本 |
ernie-3.0-micro-zh |
4 | 384 | 12 | 23M | 微型版本 |
ernie-3.0-xbase-zh |
20 | 1024 | 16 | 296M | 更大更强的版本 |
可以看到,ernie-3.0-medium-zh (75M参数) 比 base (118M参数) 更轻量,使得我们在普通的GPU上也能快速完成训练,非常适合作为入门和教学的模型。
1.4 本教程的目标与内容
本教程面向对NLP和深度学习有基本了解,并希望动手实践解决真实问题的开发者和学习者。通过本教程,您将:
- 理解情感分析任务的基本流程。
- 学会使用 PaddleNLP 加载和处理中文数据集。
- 掌握如何加载预训练的 ERNIE 模型,并针对特定任务进行微调 (Fine-tuning)。
- 从零开始,用不到100行核心代码,训练一个高精度的中文情感分析模型。
- 学会评估模型性能,并使用训练好的模型对新的文本进行情感预测。
让我们开始吧!
2. 环境准备
首先,我们需要确保安装了必需的库。本教程主要依赖 PaddlePaddle 和 PaddleNLP。
In [36]
css
!pip install --upgrade --pre paddlenlp==3.0.0b4
安装完成后,我们可以导入并检查版本信息,以确保环境配置正确。
In [37]
python
import paddle
import paddlenlp
print(f"PaddlePaddle Version: {paddle.__version__}")
print(f"PaddleNLP Version: {paddlenlp.__version__}")
# 检查GPU是否可用
if paddle.is_compiled_with_cuda():
print("PaddlePaddle is compiled with CUDA and GPU is available.")
else:
print("PaddlePaddle is not compiled with CUDA. It will use CPU.")
环境准备非常简单。接下来,我们将加载用于本次任务的数据集。
3. 数据准备
对于情感分析任务,我们需要一个已标注的数据集,其中每条文本都对应一个情感标签(如"积极"或"消极")。
3.1 加载 ChnSentiCorp 数据集
PaddleNLP 内置了许多常用的NLP数据集,包括中文情感分析领域的经典数据集 ChnSentiCorp 。这个数据集包含酒店、书籍、电脑等多个领域的评论数据,每条评论被标注为正面(标签1)或负面(标签0)。
我们可以使用 paddlenlp.datasets.load_dataset 函数一键加载它。
In [38]
python
from paddlenlp.datasets import load_dataset
# 加载ChnSentiCorp数据集的训练集、开发集和测试集
train_ds, dev_ds, test_ds = load_dataset(
'chnsenticorp', splits=['train', 'dev', 'test']
)
# 查看数据集信息
print(f"训练集样本数量: {len(train_ds)}")
print(f"开发集样本数量: {len(dev_ds)}") #开发集用于模型的调优,如调整超参数??
print(f"测试集样本数量: {len(test_ds)}")
3.2 探索数据
我们来看一下数据集中的样本长什么样。每个样本都是一个字典,包含 text (评论文本) 和 label (情感标签) 两个字段。
In [39]
python
# 打印几条训练集样本
# lable值为1,则为积极;值为0,则为消极。模型学习的目标就是看到text,猜label
print("\n--- 训练集样本示例 ---")
for i in range(3):
print(f"样本 {i}:")
print(f" 文本: {train_ds[i]['text']}")
print(f" 标签: {train_ds[i]['label']} ({'积极' if train_ds[i]['label'] == 1 else '消极'})")
# 打印几条开发集样本
print("\n--- 开发集样本示例 ---")
for i in range(3):
print(f"样本 {i}:")
print(f" 文本: {dev_ds[i]['text']}")
print(f" 标签: {dev_ds[i]['label']} ({'积极' if dev_ds[i]['label'] == 1 else '消极'})")
3.3 数据预处理与 Tokenization
为了让数据能被ERNIE模型处理,我们需要进行两个关键步骤:
- Tokenization :将原始的文本字符串转换为模型能够理解的数字ID序列。这个过程由 Tokenizer 完成。
- 格式转换 :将Tokenized后的数据整理成模型训练时接受的格式(如字典),并转换为
paddle.Tensor。
3.3.1 加载 Tokenizer
PaddleNLP为每个预训练模型都提供了配套的Tokenizer。我们选择与 ernie-3.0-medium-zh 模型匹配的 ErnieTokenizer。
In [40]
ini
from paddlenlp.transformers import AutoTokenizer
# 定义要使用的模型名称
MODEL_NAME = "ernie-3.0-medium-zh"
# 加载对应的Tokenizer,AutoTokenizer是PaddleNLP提供的一个自动化工具,会根据给定的模型名称,加载对应的分词器
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
3.3.2 构造数据处理函数
我们将定义一个 convert_example 函数,它负责对单个样本进行处理。该函数会:
- 调用
tokenizer对文本进行编码。 - 返回一个包含
input_ids,token_type_ids和label的字典。
tokenizer() 函数会为我们自动完成大部分工作,包括:
- 将文本切分成字或词 (Token)。
- 将Token转换为数字ID。
- 添加特殊的Token,如
[CLS](通常用作句子级别的表示)和[SEP](分隔符)。
In [41]
python
import functools
# example 表示一个输入的数据
# 这个函数的作用是:将每条数据转化为模型所需要的输入格式
def convert_example(example, tokenizer, max_seq_length=128):
"""
将单个样本转换为模型可接受的格式。
"""
try:
# 对输入文本进行编码
encoded_inputs = tokenizer(text=example["text"], max_seq_len=max_seq_length)
# 提取 input_ids 和 token_type_ids
# input_ids:每个字对应的数字ID
# token_type_ids 用于区分不同句子,单句分类任务全是0
input_ids = encoded_inputs["input_ids"]
token_type_ids = encoded_inputs["token_type_ids"]
label_str = example.get("label", "") #获取的标签是"1"或"0" 字符串格式
if label_str == "":
return None # 如果label为空,则跳过该样本
# 把情感标签转化为整数,并包装为tensor格式
label = paddle.to_tensor([int(label_str)], dtype="int64")
# 返回模型接受的格式
return {"input_ids": input_ids, "token_type_ids": token_type_ids, "labels": label}
except (ValueError, TypeError):
# 如果 int() 转换失败或 example 格式不正确,也跳过该样本
print(f"Skipping invalid sample: {example}")
return None
# functools.partial:可以创建一个新的函数,固定了原始函数的某些参数
# 固定了convert_example函数的tokenizer 和 max_seq_length参数
trans_func = functools.partial(convert_example, tokenizer=tokenizer, max_seq_length=128)
# -- 鲁棒性检查 --
# 检查 train_ds 是否已被处理 (即其元素是否为元组)。如果是,则重新加载原始数据集。
# 为了防止代码被多次运行时出现数据重复处理的情况
if train_ds and isinstance(train_ds[0], tuple):
print("检测到数据集已被处理,正在重新加载原始数据集...")
train_ds, dev_ds, test_ds = load_dataset('chnsenticorp', splits=['train', 'dev', 'test'])
print("原始数据集已重新加载。")
# 使用 map 方法对数据集中的所有样本应用 trans_func
# lazy=False 表示立刻对数据集进行处理;如果是True,直到实际访问时才会转换
# map会将每个样本都传递给trans_func,从而将文本转化为需要的格式
print("\n正在对数据集进行预处理...")
train_ds_processed_raw = train_ds.map(trans_func, lazy=False)
dev_ds_processed_raw = dev_ds.map(trans_func, lazy=False)
test_ds_processed_raw = test_ds.map(trans_func, lazy=False)
# 过滤掉返回值为 None 的无效样本
train_ds_processed = [item for item in train_ds_processed_raw if item is not None]
dev_ds_processed = [item for item in dev_ds_processed_raw if item is not None]
test_ds_processed = [item for item in test_ds_processed_raw if item is not None]
# 重新包装为 MapDataset (可选,但为了后续API一致性更好)
from paddlenlp.datasets import MapDataset
train_ds_processed = MapDataset(train_ds_processed)
dev_ds_processed = MapDataset(dev_ds_processed)
test_ds_processed = MapDataset(test_ds_processed)
print("数据预处理和清洗完成!")
print(f"处理后训练集样本数量: {len(train_ds_processed)}")
# 查看一个处理后的样本
if len(train_ds_processed) > 0:
print("处理后的第一个训练样本:")
print(f" Input IDs: {train_ds_processed[0]['input_ids']}")
print(f" Token Type IDs: {train_ds_processed[0]['token_type_ids']}")
print(f" Label: {train_ds_processed[0]['labels']}")
else:
print("没有有效的训练样本被处理。")
现在,我们的数据集已经准备好了,每个样本都包含了模型训练所需的 input_ids, token_type_ids, 和 label。
3.4 构造 DataLoader
最后一步是将处理好的数据集组织成批次 (Batch) 的形式,以便在训练时高效地送入模型。我们使用 paddle.io.DataLoader 来完成这个任务。它还可以帮助我们进行数据的打乱 (shuffle) 和自动的批处理。
我们会使用 paddlenlp.data.DataCollatorWithPadding 来自动地将每个批次内的样本填充 (padding) 到该批次中最长样本的长度。
In [42]
ini
from paddle.io import DataLoader
from paddlenlp.data import DataCollatorWithPadding
# 初始化 DataCollator,它会自动处理批数据的padding
collator = DataCollatorWithPadding(tokenizer)
# 定义批大小
batch_size = 32
# 创建DataLoader,负责按批次加载数据
train_loader = DataLoader(
dataset=train_ds,
batch_size=batch_size,
shuffle=True, # 训练时打乱数据
collate_fn=collator # 使用DataCollatorWithPadding对每批数据进行填充,使得所有句子的长度相同
)
dev_loader = DataLoader(
dataset=dev_ds,
batch_size=batch_size,
shuffle=False, # 评估时不需要打乱
collate_fn=collator
)
test_loader = DataLoader(
dataset=test_ds,
batch_size=batch_size,
shuffle=False,
collate_fn=collator
)
# 查看一个批次的数据形状
print("\nDataLoader准备完成!")
for batch in train_loader:
print("一个批次的数据:")
for key, value in batch.items():
print(f" {key}: shape={value.shape}")
break # 只看第一个批次
至此,数据准备的全部工作已经完成。我们拥有了可以随时送入模型进行训练的DataLoader。
4. 模型与微调原理
在数据准备就绪后,我们需要理解我们将要使用的模型以及如何让它适应我们的情感分析任务。
4.1 ERNIE 模型简介:一位懂知识的语言大师
正如引言中所说,ERNIE 是一个更"懂"中文的模型。它的核心优势在于其独特的知识整合预训练任务。
-
一个更深入的比喻: 想象一个学生(其他模型)在学习一句话:"中国四大发明之一的活字印刷术是由毕昇发明的。" 这个学生可能通过"单字填空"学会了"(活)字印刷术"和"毕(昇)"这些字。
而ERNIE这位"学霸"则不同,老师会直接将"活字印刷术 "和"毕昇"这两个完整的知识单元盖住,让他去预测。为了能答对,ERNIE不仅仅要学习语言的流畅性,更被迫去理解"活字印刷术"是一个完整的技术概念,"毕昇"是一个人名,并且这两个知识单元之间存在"发明"的关联。
通过在海量文本上进行这种"知识点挖掘"式的学习,ERNIE构建了更深层次的语义理解能力,而不仅仅是表面的文字组合。
这就是为什么ERNIE在需要理解句子深层含义的任务(如情感分析)上通常表现得更好的原因。
4.2 微调 (Fine-tuning) 原理:为语言大师配上"情感分析眼镜"
我们加载的 ernie-3.0-medium-zh 模型是一位博学的"通才",它懂得语言的各种知识,但默认情况下,它并不知道自己需要做"情感分析"这项具体工作。微调 (Fine-tuning) 的过程,就好比是为这位语言大师配上一副特制的"情感分析眼镜",并指导他如何使用。
这个过程分为几步:
-
获取句子的"灵魂"------[CLS]输出:
- 我们将一条评论文本(例如"这家店的拉面真好吃!")送入ERNIE模型。在送入前,我们在文本最前面加上一个特殊的标志
[CLS](Classification)。 - 经过ERNIE内部复杂的计算(多层Transformer),每个字词都会得到一个富含上下文信息的向量。而这个特殊的
[CLS]标志,在经过整个模型后,它对应的输出向量 (pooled_output) 就被设计为凝聚了整个句子的核心语义。我们可以把它比作ERNIE读完一句话后,在脑海中形成的对这句话的"整体印象"或"灵魂"向量。
- 我们将一条评论文本(例如"这家店的拉面真好吃!")送入ERNIE模型。在送入前,我们在文本最前面加上一个特殊的标志
-
安装"情感分析镜片"------添加分类头:
- 这个"灵魂"向量(例如一个768维的向量)本身还只是一串数字,不直接代表"积极"或"消极"。
- 我们在这串数字后面接上一个非常简单的"镜片"------一个全连接层 (Linear Layer) 。这个全连接层就是"分类头",它的任务非常专一:接收代表句子灵魂的向量,然后将其转换为对我们任务有意义的输出。
- 在我们的二分类任务中,这个"镜片"有两个输出神经元,一个代表"消极"的可能性,一个代表"积极"的可能性。
-
"校准眼镜"------训练与反向传播:
- 现在,我们有了一个戴着"情感分析眼镜"的ERNIE模型。我们把准备好的标注数据(成千上万条已知情感的评论)喂给它。
- 对于每条评论,模型会通过"眼镜"给出一个初步的判断(例如,"消极"得分0.1,"积极"得分0.9)。
- 我们拿这个判断和真实标签(比如这条评论的真实标签是"积极")去比较。如果判断准确,说明"眼镜"校准得不错;如果判断错误,就计算出一个"误差"(即交叉熵损失)。
- 这个"误差"信号会反向传播,像一个校准指令一样,不仅会微调"镜片"(分类头)的参数,让它判断得更准,更重要的是,它也会微调ERNIE这位语言大师本身的参数,让他以后在阅读文本时,能更侧重于那些与情感相关的语义信息。
经过成千上万次这样的"校准",ERNIE大师和他的"情感分析眼镜"就配合得天衣无缝了。他不仅能读懂句子的意思,还能准确地判断出其中蕴含的情感。
大模型精调原理介绍
图3: 大模型精调原理示意图 (来源: 大模型精调新手指南.md)
上图宏观地展示了对大模型进行精调的两种主要方式。我们本教程采用的是 SFT (Supervised Fine-Tuning) ,也称为全参数微调。这意味着在训练过程中,我们会更新ERNIE模型的所有参数(图中的蓝色部分)以及新添加的分类头,让整个模型都来适配我们的情感分析任务。
幸运的是,PaddleNLP 的 AutoModelForSequenceClassification 已经帮我们完成了"装配眼镜"的工作。我们只需要加载模型,它就会自动为我们搭建好"ERNIE大师 + 分类头"的结构,让我们能专注于"校准眼镜"的训练过程。
5. 模型微调
现在,我们将所有部分组合起来,开始训练我们的情感分析模型。我们将定义模型、优化器、损失函数,并编写训练和评估的逻辑。
5.1 加载模型
我们使用 paddlenlp.transformers.AutoModelForSequenceClassification 来加载一个带有序列分类头的ERNIE模型。我们只需指定预训练模型的名称 MODEL_NAME 和分类的类别数 num_classes。
In [43]
ini
from paddlenlp.transformers import AutoModelForSequenceClassification
# 类别数量 (消极, 积极)
num_classes = len(train_ds.label_list)
# 加载一个适合分类任务的模型jia
model = AutoModelForSequenceClassification.from_pretrained(MODEL_NAME, num_classes=num_classes)
# 打印模型结构
print(model)
您可以看到模型结构,最后一部分是一个 (classifier): Linear 层,这就是我们添加的分类头。
5.2 定义优化器和损失函数
- 优化器 (Optimizer) :我们使用经典的
AdamW优化器,它在Transformer模型的训练中表现良好。 - 学习率调度器 (Learning Rate Scheduler) :为了更好的训练效果,我们使用动态学习率。这里采用
LinearDecayWithWarmup,它会在训练初期有一个"预热 (warmup)"阶段,学习率从0线性增长到设定的初始值,之后再随着训练步数线性衰减到0。这有助于模型在训练初期更稳定,在后期更精细地收敛。 - 损失函数 (Loss Function) :对于分类任务,我们使用标准的交叉熵损失函数
paddle.nn.CrossEntropyLoss。 - 评估指标 (Metric) :我们使用准确率 (Accuracy) 来衡量模型在开发集和测试集上的性能。
In [44]
ini
import paddle
from paddlenlp.transformers import LinearDecayWithWarmup
# 训练的总步数
num_training_steps = len(train_loader) * 3 # 假设我们训练3个epoch
# 定义学习率调度器
# 前10%学习率会从0慢慢增加,到设定的初始学习率,学习率逐渐变大,可以让模型更加稳定地学习。
# 再预热结束后,会从初始学习率逐渐减少,指导训练结束。
# deacy:当模型接近收敛时,学习率变得太大会导致模型跳过最优解,因此我们希望学习率逐渐减小,让模型能精细调整,找到最优的参数。
lr_scheduler = LinearDecayWithWarmup(
learning_rate=3e-5, # 初始学习率
total_steps=num_training_steps,
warmup=0.1 # 使用10%的steps进行warmup
)
# 定义优化器
optimizer = paddle.optimizer.AdamW(
learning_rate=lr_scheduler, # 优化器的学习率
parameters=model.parameters() # 指定优化器要优化的参数,这里是优化所有参数
)
# 定义损失函数,交叉熵损失函数是分类问题中最常用的损失函数
criterion = paddle.nn.CrossEntropyLoss()
# 定义评估指标
metric = paddle.metric.Accuracy()
5.3 编写训练与评估循环
现在,我们编写核心的训练和评估逻辑。
-
train_epoch()函数: 负责一个epoch的完整训练流程。- 将模型设置为训练模式 (
model.train())。 - 遍历
train_loader中的所有批次。 - 对于每个批次,执行前向计算、计算损失、反向传播、更新参数。
- 定期打印日志,显示当前的loss和学习率等信息。
- 将模型设置为训练模式 (
-
evaluate()函数: 负责在开发集或测试集上进行评估。- 将模型设置为评估模式 (
model.eval())。 - 关闭梯度计算 (
@paddle.no_grad()) 以节省显存和加速。 - 遍历
dev_loader中的所有批次。 - 计算模型预测的准确率。
- 将模型设置为评估模式 (
In [45]
python
import paddle.nn.functional as F
import os # 导入os模块
@paddle.no_grad() # 关闭梯度计算
def evaluate(model, criterion, metric, data_loader):
"""
1. 在给定数据集上评估模型,返回精确度。
2. model 是一个已经定义并训练过的深度学习模型,它用于对输入数据进行推理(即预测)
"""
model.eval() # 设置模型为评估模式
metric.reset() # 重置评估指标
losses = [] # 用来存储每个批次的损失
for batch in data_loader:
input_ids, token_type_ids, labels = batch['input_ids'], batch['token_type_ids'], batch['labels']
logits = model(input_ids, token_type_ids) # 前向计算,得到模型输出
loss = criterion(logits, labels) # 计算损失
losses.append(loss.numpy()) # 将损失值添加到损失列表
correct = metric.compute(logits, labels) # 计算当前批次的准确度
metric.update(correct) # 更新评估指标
accuracy = metric.accumulate() # 计算整体准确率
print(f"Eval loss: {paddle.to_tensor(losses).mean().item():.5f}, Accuracy: {accuracy:.5f}")
model.train() # 将模型恢复为训练模式
return accuracy
def do_train(epochs=3):
"""
执行完整的训练和评估流程。
"""
best_accuracy = 0.0 # 初始化最佳准确率
for epoch in range(1, epochs + 1):
print(f"\n--- Epoch {epoch} ---")
# 训练
model.train() # 设置模型为训练模式
for step, batch in enumerate(train_loader, start=1):
input_ids, token_type_ids, labels = batch['input_ids'], batch['token_type_ids'], batch['labels']
# 前向计算
logits = model(input_ids, token_type_ids)
# 计算损失
loss = criterion(logits, labels)
# 每100步打印一次log
if step % 100 == 0:
print(
f"Epoch {epoch}, Step {step}/{len(train_loader)}, "
f"Loss: {loss.item():.5f}, "
f"LR: {lr_scheduler.get_lr():.7f}"
)
# 反向传播和参数更新
loss.backward() # 计算梯度
optimizer.step() # 更新模型参数
lr_scheduler.step() # 更新学习率
optimizer.clear_grad() # 清除梯度,为下一次迭代做准备
# 每个epoch结束后,在开发集上进行评估
print(f"--- Evaluating on Dev Set after Epoch {epoch} ---")
accuracy = evaluate(model, criterion, metric, dev_loader)
# 保存表现最好的模型
if accuracy > best_accuracy:
best_accuracy = accuracy
# 创建保存目录
save_dir = "ernie_senti_checkpoint_best"
os.makedirs(save_dir, exist_ok=True) # 创建目录,如果已存在则不报错
# 保存模型和tokenizer
model.save_pretrained(save_dir)
tokenizer.save_pretrained(save_dir) # 保存训练好的模型
print(f"Best model saved with accuracy: {best_accuracy:.5f} at '{save_dir}'") # 保存模型使用的分词器
# 开始训练!
# 通常在GPU上,每个epoch大约需要几分钟。
do_train(epochs=3)
日志解读: 在训练过程中,您会看到类似下面的日志:
yaml
--- Epoch 1 ---
Epoch 1, Step 100/300, Loss: 0.34567, LR: 0.0000280
Epoch 1, Step 200/300, Loss: 0.21345, LR: 0.0000250
Epoch 1, Step 300/300, Loss: 0.18765, LR: 0.0000220
--- Evaluating on Dev Set after Epoch 1 ---
Eval loss: 0.25012, Accuracy: 0.92500
Best model saved with accuracy: 0.92500 at 'ernie_senti_checkpoint_best'
我们期望看到:
- Loss:在训练过程中稳步下降。
- Accuracy:在开发集上的准确率稳步提升。
- 模型保存 :当开发集上的准确率超过之前的最佳记录时,模型会被保存到
ernie_senti_checkpoint_best目录中。
训练完成后,这个目录里就包含了我们性能最好的情感分析模型。
6. 模型预测
训练完成后,并使用它来对新文本进行情感预测。
In [46]
python
# 加载之前保存的最佳模型
# 如果训练中断或在不同环境中运行,请确保 'ernie_senti_checkpoint_best' 目录存在
# 并且其中包含模型文件
save_dir = "ernie_senti_checkpoint_best"
try:
# 重新加载最佳模型和对应的tokenizer
model = AutoModelForSequenceClassification.from_pretrained(save_dir)
tokenizer = AutoTokenizer.from_pretrained(save_dir)
print("最佳模型已成功加载!")
except Exception as e:
print(f"加载模型或评估时发生错误: {e}")
print("请确保训练已成功完成,并且'ernie_senti_checkpoint_best'目录存在且包含模型文件。")
现在,让我们来创建一个简单易用的预测函数,它可以接收一个或多个中文句子,并返回它们的情感分析结果。
In [47]
python
import numpy as np
def predict(model, tokenizer, texts, label_map):
"""
给定模型、分词器、文本列表和标签映射,预测情感。
"""
# 将模型设置为评估模式
model.eval()
# 对输入文本进行分词和处理
# texts可以是单个字符串或字符串列表
if isinstance(texts, str):
texts = [texts]
encoded_inputs = tokenizer(text=texts, max_seq_len=128, padding=True)
input_ids = paddle.to_tensor(encoded_inputs["input_ids"])
token_type_ids = paddle.to_tensor(encoded_inputs["token_type_ids"])
# 关闭梯度计算
with paddle.no_grad():
logits = model(input_ids, token_type_ids)
# 通过softmax获取概率,并通过argmax获取最可能的类别索引
probs = F.softmax(logits, axis=1)
label_ids = paddle.argmax(probs, axis=1).numpy()
# 将类别索引映射回标签文字
results = [label_map[label_id] for label_id in label_ids]
return results
# 定义标签映射
label_map = {0: '消极', 1: '积极'}
# 待预测的数据
data_to_predict = [
'这家酒店的早餐太好吃了,下次还来!',
'房间隔音太差了,晚上根本睡不着。',
'服务态度一般,没什么特别的感觉。', # 这条可能比较中性,看看模型的判断
'风景不错,但是交通不太方便。' # 包含正负两方面信息
]
# 进行预测
if 'model' in globals():
results = predict(model, tokenizer, data_to_predict, label_map)
for text, label in zip(data_to_predict, results):
print(f"文本: {text} -> 预测情感: {label}")
else:
print("模型未加载,无法进行预测。")
通过这个 predict 函数,您就可以方便地将训练好的情感分析能力集成到您自己的应用中,或者用它来分析任何您感兴趣的中文文本了!
7. 总结与展望
本教程从零开始,带您完成了基于ERNIE模型的中文情感分析任务的整个流程。
我们回顾一下核心步骤:
- 环境准备:安装了PaddlePaddle和PaddleNLP。
- 数据探索 :加载并分析了
ChnSentiCorp数据集。 - 数据预处理:使用ERNIE Tokenizer对文本进行编码,并创建了DataLoader。
- 模型微调 :加载
ErnieForSequenceClassification模型,定义优化器和损失函数,并编写了训练和评估循环,最终训练并保存了最佳模型。 - 评估与预测:在测试集上验证了模型的最终性能,并创建了一个实用的预测函数来分析新文本。
后续可以探索的方向:
- 尝试更强大的模型 :可以尝试
ernie-3.0-base-zh或ernie-3.0-xbase-zh等更大规模的ERNIE模型,可能会获得更高的准确率。 - 参数高效微调 (PEFT) :对于更大的模型,全参数微调成本较高。可以学习使用LoRA等PEFT方法,在冻结大部分ERNIE参数的情况下进行微调,显著降低资源消耗。
- 三分类任务:在更复杂的数据集上进行(积极、消极、中性)三分类任务。
- 模型部署:学习如何将训练好的模型导出为静态图格式,并使用Paddle Serving或FastDeploy进行服务化部署,以供生产环境使用。
- 应用到您自己的数据:将本教程的流程应用到您自己领域的文本数据上,构建一个定制化的情感分析系统。
希望本教程能帮助您成功入门中文文本分类任务!如果您有任何问题,欢迎查阅PaddleNLP的官方文档或向社区提问。