本文主要是实现句子级文本情感分析
如今句子级情感分析已经很成熟
7.1 ERNIE3.0模型加载
PaddleNLP中Auto模块(包括AutoModel, AutoTokenizer及各种下游任务类)提供了方便易用的接口,无需指定模型类别,即可调用不同网络结构的预训练模型。PaddleNLP的预训练模型可以很容易地通过from_pretrained()方法加载,Transformer预训练模型汇总包含了40多个主流预训练模型,500多个模型权重。
AutoModelForSequenceClassification可用于句子级情感分析和目标级情感分析任务,通过预训练模型获取输入文本的表示,之后将文本表示进行分类。PaddleNLP已经实现了ERNIE 3.0预训练模型,可以通过一行代码实现ERNIE 3.0预训练模型和分词器的加载。
from paddlenlp.transformers import AutoTokenizer, AutoModelForSequenceClassification
model_name = "ernie-3.0-medium-zh"
label_list = [0,1]
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_classes=len(label_list),hidden_dropout_prob=0.4)
tokenizer = AutoTokenizer.from_pretrained(model_name)
7.2 数据处理
Dataset中通常为原始数据,需要经过一定的数据处理并进行采样组batch。通过`Dataset`的map函数,使用分词器将数据集从原始文本处理成模型的输入。定义paddle.io.BatchSampler和collate_fn构建 paddle.io.DataLoader。实际训练中,根据显存大小调整批大小batch_size和文本最大长度max_seq_length。
import functools
import numpy as np
from paddle.io import DataLoader, BatchSampler
from paddlenlp.data import DataCollatorWithPadding
# 数据预处理函数,利用分词器将文本转化为整数序列
def preprocess_function(examples, tokenizer, max_seq_length, is_test=False):
result = tokenizer(text=examples["text"], max_seq_len=max_seq_length)
if not is_test:
result["labels"] = examples["label"]
return result
trans_func = functools.partial(preprocess_function, tokenizer=tokenizer, max_seq_length=128)
train_ds = train_ds.map(trans_func)
dev_ds = dev_ds.map(trans_func)
# collate_fn函数构造,将不同长度序列充到批中数据的最大长度,再将数据堆叠
collate_fn = DataCollatorWithPadding(tokenizer)
# 定义BatchSampler,选择批大小和是否随机乱序,进行DataLoader
train_batch_sampler = BatchSampler(train_ds, batch_size=32, shuffle=True)
dev_batch_sampler = BatchSampler(dev_ds, batch_size=64, shuffle=True)
train_data_loader = DataLoader(dataset=train_ds, batch_sampler=train_batch_sampler, collate_fn=collate_fn)
dev_data_loader = DataLoader(dataset=dev_ds, batch_sampler=dev_batch_sampler, collate_fn=collate_fn)
7.3 数据训练和评估
定义训练所需的优化器、损失函数、评价指标等,就可以开始进行预模型微调任务。
# Adam优化器、交叉熵损失函数、accuracy评价指标
optimizer = paddle.optimizer.AdamW(learning_rate=0.000007, parameters=model.parameters())
criterion = paddle.nn.loss.CrossEntropyLoss()
metric = paddle.metric.Accuracy()
import paddle
import numpy as np
accuracy = paddle.metric.Accuracy()
def evaluate(model, criterion, metric, data_loader):
model.eval()
accuracy.reset()
losses = [] # record loss
for batch in data_loader:
input_ids = batch['input_ids']
token_type_ids = batch['token_type_ids']
labels = batch['labels']
logits = model(input_ids, token_type_ids)
loss = criterion(logits, labels)
losses.append(loss.numpy())
# compute accuracy
correct = accuracy.compute(logits, labels)
accuracy.update(correct)
# accumulate and print accuracy
accu = accuracy.accumulate()
print("eval loss: %.5f, accuracy: %.5f" % (np.mean(losses), accu))
model.train()
accuracy.reset()
return np.mean(losses), accu
7.4 模型训练
接下来,将对模型ERNIE3.0模型进行训练。
# 开始训练
import time
import paddle.nn.functional as F
# 定义空列表来存储损失和准确率
train_loss_values = []
train_acc_values = []
val_loss_values = []
val_acc_values = []
# from eval import evaluate
# import evaluate
epochs = 5 # 训练轮次
ckpt_dir = "ernie_ckpt" #训练过程中保存模型参数的文件夹
best_acc = 0
best_step = 0
global_step = 0 #迭代次数
tic_train = time.time()
for epoch in range(1, epochs + 1):
for step, batch in enumerate(train_data_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)
probs = F.softmax(logits, axis=1)
correct = metric.compute(probs, labels)
metric.update(correct)
acc = metric.accumulate()
train_loss_values.append(loss.numpy())
train_acc_values.append(acc)
# 每迭代10次,打印损失函数值、准确率、计算速度
global_step += 1
if global_step % 10 == 0:
print(
"global step %d, epoch: %d, batch: %d, loss: %.5f, accu: %.5f, speed: %.2f step/s"
% (global_step, epoch, step, loss, acc,
10 / (time.time() - tic_train)))
tic_train = time.time()
# 反向梯度回传,更新参数
loss.backward()
optimizer.step()
optimizer.clear_grad()
# 每迭代100次,评估当前训练的模型、保存当前模型参数和分词器的词表等
if global_step % 100 == 0:
save_dir = ckpt_dir
if not os.path.exists(save_dir):
os.makedirs(save_dir)
val_loss, val_acc = evaluate(model, criterion, metric, dev_data_loader)
# 记录验证损失和准确率
val_loss_values.append(val_loss)
val_acc_values.append(val_acc)
print(global_step, end=' ')
acc_eval = evaluate(model, criterion, metric, dev_data_loader)
if val_acc > best_acc:
best_acc = val_acc
best_step = global_step
model.save_pretrained(save_dir)
tokenizer.save_pretrained(save_dir)
部分训练结果如下:
7.5 模型评价
提高绘制训练集和验证集的准确率损失率曲线来观察ERNIE3.0模型的效果。
import matplotlib.pyplot as plt
# 初始化用于存储每100步的平均值的新数组
average_train_loss_values = []
average_train_acc_values = []
# 循环计算每100个元素的平均值并存储到新数组中
for i in range(0, len(train_loss_values), 100):
# 取出当前100个元素
subset_train_loss = train_loss_values[i:i+100]
subset_train_acc = train_acc_values[i:i+100]
# 计算平均值并存储到新数组中
average_train_loss = np.mean(subset_train_loss)
average_train_acc = np.mean(subset_train_acc)
average_train_loss_values.append(average_train_loss)
average_train_acc_values.append(average_train_acc)
plt.figure(figsize=(10, 5))
plt.plot(average_train_loss_values, label='Train Loss')
plt.plot(val_loss_values, label='Validation Loss')
plt.xlabel('Steps')
plt.ylabel('Loss')
plt.legend()
plt.title('Training and Validation Loss')
plt.show()
# 绘制训练和验证的准确率图表
plt.figure(figsize=(10, 5))
plt.plot(average_train_acc_values, label='Train Accuracy')
plt.plot(val_acc_values, label='Validation Accuracy')
plt.xlabel('Steps')
plt.ylabel('Accuracy')
plt.legend()
plt.title('Training and Validation Accuracy')
plt.show()
经过多次调参,我们的到了最好的模型效果如下图5:
图5 ERNIE3.0模型准确率损失率曲线
7.6 模型测试
我们还需要测试模型在验证集上的表现能力。
#from eval import evaluate
# 加载ERNIR 3.0最佳模型参数
params_path = 'ernie_ckpt/model_state.pdparams'
state_dict = paddle.load(params_path)
model.set_dict(state_dict)
# 也可以选择加载预先训练好的模型参数结果查看模型训练结果
# model.set_dict(paddle.load('ernie_ckpt_trained/model_state.pdparams'))
print('ERNIE 3.0-Medium 在ChnSentiCorp的dev集表现', end=' ')
eval_acc = evaluate(model, criterion, metric, dev_data_loader)
从上述模型在验证集上的表现结果来看,ERNIE模型的准确率比SKEP模型提高了0.1083,损失下降了0.03206,效果明显比SKEP模型的表现能力更好。
7.7 情感分析结果预测与保存
加载微调好的模型参数进行情感分析预测,并保存预测结果。
# 测试集数据预处理,利用分词器将文本转化为整数序列
trans_func_test = functools.partial(preprocess_function, tokenizer=tokenizer, max_seq_length=128, is_test=True)
test_ds_trans = test_ds.map(trans_func_test)
# 进行采样组batch
collate_fn_test = DataCollatorWithPadding(tokenizer)
test_batch_sampler = BatchSampler(test_ds_trans, batch_size=32, shuffle=False)
test_data_loader = DataLoader(dataset=test_ds_trans, batch_sampler=test_batch_sampler, collate_fn=collate_fn_test)
# 模型预测分类结果
import paddle.nn.functional as F
label_map = {0: '负面', 1: '正面'}
results = []
model.eval()
for batch in test_data_loader:
input_ids, token_type_ids = batch['input_ids'], batch['token_type_ids']
logits = model(batch['input_ids'], batch['token_type_ids'])
probs = F.softmax(logits, axis=-1)
idx = paddle.argmax(probs, axis=1).numpy()
idx = idx.tolist()
preds = [label_map[i] for i in idx]
results.extend(preds)
# 存储预测结果
#test_ds = load_dataset(read_local_dataset, path=data_path_test, is_test=True, lazy=False)
test_ds = load_dataset("chnsenticorp", splits=["test"])
res_dir = "./results"
if not os.path.exists(res_dir):
os.makedirs(res_dir)
with open(os.path.join(res_dir, "ERNIE_RedBookletReview.tsv"), 'w', encoding="utf8") as f:
f.write("qid\ttext\tprediction\n")
for i, pred in enumerate(results):
f.write(test_ds[i]['qid']+"\t"+test_ds[i]['text']+"\t"+pred+"\n")
7.8 测试结果和模型保存
我们将两个模型的测试数据和模型文件分别进行压缩保存到本地。
import os
import zipfile
from IPython.display import FileLink
# 创建一个新的压缩文件。
zip_file = zipfile.ZipFile('input.zip', 'w')
# 需要打包的文件夹列表
folders_to_zip = ['ernie_ckpt', 'results', 'sample_data', 'skep_ckpt']
# 获取当前工作目录
current_directory = os.getcwd()
# 遍历当前工作目录下的所有文件和文件夹
for root, dirs, files in os.walk(current_directory):
# 过滤出需要打包的文件夹
if any(folder in root for folder in folders_to_zip):
for file in files:
# 将需要打包的文件添加到压缩文件中
zip_file.write(os.path.join(root, file), arcname=os.path.relpath(os.path.join(root, file), current_directory))
# 关闭压缩文件。
zip_file.close()
# 创建下载链接
FileLink(r'./input.zip')
八、总结
经过上述分别对SKEP模型和ERNIE3.0模型在数据集ChnSentiCorp上的情感分析能力进行训练,最终得到结果:从上述两个模型分别在验证集上的表现结果来看,ERNIE模型的准确率比SKEP模型提高了0.1083,损失下降了0.03206,文本分类明显比SKEP模型的表现能力更好,泛化能力和鲁棒性更高。但实际应该是skep_ernie_1.0_large_ch比ERNIE-3.0-medium-zh模型在情感分类任务上的效果更好,经过我们分析发现,有可能是和chnsenticorp数据集的分布有关。
ChnSentiCorp数据集包含酒店、笔记本电脑和书籍的网购评论,其中酒店评论的比例最高,其次是笔记本电脑评论,最后是书籍评论。而ERNIE模型是在一个更大的数据集上进行预训练的,其中包括各种类型的文本,因此在处理酒店评论时可能表现更好。ChnSentiCorp数据集是从互联网上收集的,因此存在一定程度的噪声。而ERNIE模型是一个大型语言模型,因此更容易受到噪声的影响。ERNIE模型和skep_ernie_1.0_large_ch模型的参数设置可能存在差异,这可能会影响模型在不同数据集上的表现。
https://blog.csdn.net/weixin_62828995/article/details/135728604