基于深度学习的句子级情感分析

本文主要是实现句子级文本情感分析

如今句子级情感分析已经很成熟

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值