当前位置:   article > 正文

越学越有趣:『手把手带你学NLP』系列项目05 ——文本情感分析的那些事儿

metric.accumulate公式

点击左上方蓝字关注我们

课程简介

“手把手带你学NLP”是基于飞桨PaddleNLP的系列实战项目。本系列由百度多位资深工程师精心打造,提供了从词向量、预训练语言模型,到信息抽取、情感分析、文本问答、结构化数据问答、文本翻译、机器同传、对话系统等实践项目的全流程讲解,旨在帮助开发者更全面清晰地掌握百度飞桨框架在NLP领域的用法,并能够举一反三、灵活使用飞桨框架和PaddleNLP进行NLP深度学习实践。

从6月7日起,百度飞桨 & 自然语言处理部携手推出了12节NLP精品课,课程中会介绍到这里的实践项目。

课程报名请戳:

https://aistudio.baidu.com/aistudio/course/introduce/24177

欢迎来课程QQ群(群号:758287592)交流吧~~


背景介绍

众所周知,人类自然语言中包含了丰富的情感色彩:表达人的情绪(如悲伤、快乐)、表达人的心情(如倦怠、忧郁)、表达人的喜好(如喜欢、讨厌)、表达人的个性特征和表达人的立场等等。情感分析在商品喜好、消费决策、舆情分析等场景中均有应用。利用机器自动分析这些情感倾向,不但有助于帮助企业了解消费者对其产品的感受,为产品改进提供依据;同时还有助于企业分析商业伙伴们的态度,以便更好地进行商业决策。

被人们所熟知的情感分析任务是将一段文本分类,如分为情感极性为正向、负向、其他的三分类问题:

情感分析任务

正向: 表示正面积极的情感,如高兴,幸福,惊喜,期待等。

负向: 表示负面消极的情感,如难过,伤心,愤怒,惊恐等。

其他: 其他类型的情感。

实际上,以上熟悉的情感分析任务是句子级情感分析任务。


情感分析预训练模型SKEP

近年来,大量的研究表明基于大型语料库的预训练模型(Pretrained Models, PTM)可以学习通用的语言表示,有利于下游NLP任务,同时能够避免从零开始训练模型。随着计算能力的发展,深度模型的出现(即 Transformer)和训练技巧的增强使得 PTM 不断发展,由浅变深。

情感预训练模型SKEP(Sentiment Knowledge Enhanced Pre-training for Sentiment Analysis)。SKEP利用情感知识增强预训练模型, 在14项中英情感分析典型任务上全面超越SOTA,此工作已经被ACL 2020录用。SKEP是百度研究团队提出的基于情感知识增强的情感预训练算法,此算法采用无监督方法自动挖掘情感知识,然后利用情感知识构建预训练目标,从而让机器学会理解情感语义。SKEP为各类情感分析任务提供统一且强大的情感语义表示。

论文地址:https://arxiv.org/abs/2005.05635

百度研究团队在三个典型情感分析任务,句子级情感分类(Sentence-level Sentiment Classification),评价目标级情感分类(Aspect-level Sentiment Classification)、观点抽取(Opinion Role Labeling),共计14个中英文数据上进一步验证了情感预训练模型SKEP的效果。

具体实验效果参考:

https://github.com/baidu/Senta#skep

情感分析任务还可以进一步分为句子级情感分析、目标级情感分析等任务。在下面章节将会详细介绍两种任务及其应用场景。


句子级情感分析 

&

目标级情感分析

本项目将详细全面介绍情感分析任务的两种子任务,句子级情感分析和目标级情感分析。

记得给PaddleNLP点个小小的Star

开源不易,希望大家多多支持~

GitHub地址:

https://github.com/PaddlePaddle/PaddleNLP

AI Studio平台后续会默认安装PaddleNLP最新版,在此之前可使用如下命令更新安装。

!pip install --upgrade paddlenlp -i https://pypi.org/simple


1 句子级情感分析

对给定的一段文本进行情感极性分类,常用于影评分析、网络论坛舆情分析等场景。如:

  1. ① 选择珠江花园的原因就是方便,有电动扶梯直接到达海边,周围餐馆、食廊、商场、超市、摊位一应俱全。酒店装修一般,但还算整洁。泳池在大堂的屋顶,因此很小,不过女儿倒是喜欢。包的早餐是西式的,还算丰富。服务吗,一般 1
  2. 15.4寸笔记本的键盘确实爽,基本跟台式机差不多了,蛮喜欢数字小键盘,输数字特方便,样子也很美观,做工也相当不错  1
  3. ③ 房间太小。其他的都一般。。。。。。。。。 0

其中1表示正向情感,0表示负向情感。

句子级情感分析任务

常用数据集

ChnSenticorp数据集是公开中文情感分析常用数据集, 其为二分类数据集。PaddleNLP已经内置该数据集,一键即可加载。

  1. from paddlenlp.datasets import load_dataset
  2. train_ds, dev_ds, test_ds = load_dataset("chnsenticorp", splits=["train""dev""test"])
  3. print(train_ds[0])
  4. print(train_ds[1])
  5. print(train_ds[2])
  6. {'text''选择珠江花园的原因就是方便,有电动扶梯直接到达海边,周围餐馆、食廊、商场、超市、摊位一应俱全。酒店装修一般,但还算整洁。 泳池在大堂的屋顶,因此很小,不过女儿倒是喜欢。 包的早餐是西式的,还算丰富。 服务吗,一般''label'1'qid'''}

1.1 SKEP模型加载

PaddleNLP已经实现了SKEP预训练模型,可以通过一行代码实现SKEP加载。

句子级情感分析模型是SKEP fine-tune 文本分类常用模型SkepForSequenceClassification。其首先通过SKEP提取句子语义特征,之后将语义特征进行分类。

  1. from paddlenlp.transformers import SkepForSequenceClassification, SkepTokenizer
  2. # 指定模型名称,一键加载模型
  3. model = SkepForSequenceClassification.from_pretrained(pretrained_model_name_or_path="skep_ernie_1.0_large_ch", num_classes=len(train_ds.label_list))# 同样地,通过指定模型名称一键加载对应的Tokenizer,用于处理文本数据,如切分token,转token_id等。
  4. tokenizer = SkepTokenizer.from_pretrained(pretrained_model_name_or_path="skep_ernie_1.0_large_ch")

SkepForSequenceClassification可用于句子级情感分析和目标级情感分析任务。其通过预训练模型SKEP获取输入文本的表示,之后将文本表示进行分类。

  • pretrained_model_name_or_path:模型名称。支持"skep_ernie_1.0_large_ch","skep_ernie_2.0_large_en"。

  • "skep_ernie_1.0_large_ch":是SKEP模型在预训练ernie_1.0_large_ch基础之上在海量中文数据上继续预训练得到的中文预训练模型;

  • "skep_ernie_2.0_large_en":是SKEP模型在预训练ernie_2.0_large_en基础之上在海量英文数据上继续预训练得到的英文预训练模型;

  • num_classes: 数据集分类类别数。

关于SKEP模型实现详细信息参考:

https://github.com/PaddlePaddle/PaddleNLP/tree/develop/paddlenlp/transformers/skep


1.2 数据处理

同样地,我们需要将原始ChnSentiCorp数据处理成模型可以读入的数据格式。

SKEP模型对中文文本处理按照字粒度进行处理,我们可以使用PaddleNLP内置的SkepTokenizer完成一键式处理。

  1. import os
  2. from functools import partial
  3. import numpy as np
  4. import paddle
  5. import paddle.nn.functional as F
  6. from paddlenlp.data import Stack, Tuple, Pad
  7. from utils import create_dataloader
  8. def convert_example(example,
  9.                     tokenizer,
  10.                     max_seq_length=512,
  11.                     is_test=False): 
  12.     # 将原数据处理成model可读入的格式,enocded_inputs是一个dict,包含input_ids、token_type_ids等字段
  13.     encoded_inputs = tokenizer(
  14.         text=example["text"], max_seq_len=max_seq_length)
  15.     # input_ids:对文本切分token后,在词汇表中对应的token id
  16.     input_ids = encoded_inputs["input_ids"]
  17.     # token_type_ids:当前token属于句子1还是句子2,即上述图中表达的segment ids
  18.     token_type_ids = encoded_inputs["token_type_ids"]
  19.     if not is_test:
  20.         # label:情感极性类别
  21.         label = np.array([example["label"]], dtype="int64")
  22.         return input_ids, token_type_ids, label
  23.     else:
  24.         # qid:每条数据的编号
  25.         qid = np.array([example["qid"]], dtype="int64")
  26.         return input_ids, token_type_ids, qid
  1. # 批量数据大小
  2. batch_size = 32# 文本序列最大长度
  3. max_seq_length = 256
  4. # 将数据处理成模型可读入的数据格式
  5. trans_func = partial(
  6.     convert_example,
  7.     tokenizer=tokenizer,
  8.     max_seq_length=max_seq_length)
  9. # 将数据组成批量式数据,如# 将不同长度的文本序列padding到批量式数据中最大长度# 将每条数据label堆叠在一起
  10. batchify_fn = lambda samples, fn=Tuple(
  11.     Pad(axis=0, pad_val=tokenizer.pad_token_id),  # input_ids
  12.     Pad(axis=0, pad_val=tokenizer.pad_token_type_id),  # token_type_ids
  13.     Stack()  # labels
  14. ): [data for data in fn(samples)]
  15. train_data_loader = create_dataloader(
  16.     train_ds,
  17.     mode='train',
  18.     batch_size=batch_size,
  19.     batchify_fn=batchify_fn,
  20.     trans_fn=trans_func)
  21. dev_data_loader = create_dataloader(
  22.     dev_ds,
  23.     mode='dev',
  24.     batch_size=batch_size,
  25.     batchify_fn=batchify_fn,
  26.     trans_fn=trans_func)

1.3 模型训练和评估

定义损失函数、优化器以及评价指标后,即可开始训练。

推荐超参设置:

  • max_seq_length=256

  • batch_size=48

  • learning_rate=2e-5

  • epochs=10

实际运行时可以根据显存大小调整batch_size和max_seq_length大小。

  1. import time
  2. from utils import evaluate
  3. # 训练轮次
  4. epochs = 1# 训练过程中保存模型参数的文件夹
  5. ckpt_dir = "skep_ckpt" # len(train_data_loader)一轮训练所需要的step数
  6. num_training_steps = len(train_data_loader) * epochs
  7. # Adam优化器
  8. optimizer = paddle.optimizer.AdamW(
  9.     learning_rate=2e-5,
  10.     parameters=model.parameters())# 交叉熵损失函数
  11. criterion = paddle.nn.loss.CrossEntropyLoss()# accuracy评价指标
  12. metric = paddle.metric.Accuracy()
  1. # 开启训练
  2. global_step = 0
  3. tic_train = time.time()for epoch in range(1, epochs + 1):
  4.     for step, batch in enumerate(train_data_loader, start=1):
  5.         input_ids, token_type_ids, labels = batch
  6.         # 喂数据给model
  7.         logits = model(input_ids, token_type_ids)
  8.         # 计算损失函数值
  9.         loss = criterion(logits, labels)
  10.         # 预测分类概率值
  11.         probs = F.softmax(logits, axis=1)
  12.         # 计算acc
  13.         correct = metric.compute(probs, labels)
  14.         metric.update(correct)
  15.         acc = metric.accumulate()
  16.         global_step += 1
  17.         if global_step % 10 == 0:
  18.             print(
  19.                 "global step %d, epoch: %d, batch: %d, loss: %.5f, accu: %.5f, speed: %.2f step/s"
  20.                 % (global_step, epoch, step, loss, acc,
  21.                     10 / (time.time() - tic_train)))
  22.             tic_train = time.time()
  23.         # 反向梯度回传,更新参数
  24.         loss.backward()
  25.         optimizer.step()
  26.         optimizer.clear_grad()
  27.         if global_step % 100 == 0:
  28.             save_dir = os.path.join(ckpt_dir, "model_%d" % global_step)
  29.             if not os.path.exists(save_dir):
  30.                 os.makedirs(save_dir)
  31.             # 评估当前训练的模型
  32.             evaluate(model, criterion, metric, dev_data_loader)
  33.             # 保存当前模型参数等
  34.             model.save_pretrained(save_dir)
  35.             # 保存tokenizer的词表等
  36.             tokenizer.save_pretrained(save_dir)
  37. global step 10, epoch: 1, batch: 10, loss: 0.59440, accu: 0.60625, speed: 0.76 step/s
  38. global step 20, epoch: 1, batch: 20, loss: 0.42311, accu: 0.72969, speed: 0.78 step/s
  39. global step 30, epoch: 1, batch: 30, loss: 0.09735, accu: 0.78750, speed: 0.72 step/s
  40. ··········
  41. global step 290, epoch: 1, batch: 290, loss: 0.14512, accu: 0.92778, speed: 0.71 step/s
  42. global step 300, epoch: 1, batch: 300, loss: 0.12470, accu: 0.92781, speed: 0.77 step/s
  43. eval loss: 0.19412, accu: 0.92667

1.4预测提交结果

使用训练得到的模型还可以对文本进行情感预测。

  1. import numpy as np
  2. import paddle
  3. # 处理测试集数据
  4. trans_func = partial(
  5.     convert_example,
  6.     tokenizer=tokenizer,
  7.     max_seq_length=max_seq_length,
  8.     is_test=True)
  9. batchify_fn = lambda samples, fn=Tuple(
  10.     Pad(axis=0, pad_val=tokenizer.pad_token_id),  # input
  11.     Pad(axis=0, pad_val=tokenizer.pad_token_type_id),  # segment
  12.     Stack() # qid
  13. ): [data for data in fn(samples)]
  14. test_data_loader = create_dataloader(
  15.     test_ds,
  16.     mode='test',
  17.     batch_size=batch_size,
  18.     batchify_fn=batchify_fn,
  19.     trans_fn=trans_func)
  1. # 根据实际运行情况,更换加载的参数路径
  2. params_path = 'skep_ckp/model_500/model_state.pdparams'
  3. if params_path and os.path.isfile(params_path):
  4.     # 加载模型参数
  5.     state_dict = paddle.load(params_path)
  6.     model.set_dict(state_dict)
  7.     print("Loaded parameters from %s" % params_path)
  1. label_map = {0'0'1'1'}
  2. results = []# 切换model模型为评估模式,关闭dropout等随机因素
  3. model.eval()
  4. for batch in test_data_loader:
  5.     input_ids, token_type_ids, qids = batch
  6.     # 喂数据给模型
  7.     logits = model(input_ids, token_type_ids)
  8.     # 预测分类
  9.     probs = F.softmax(logits, axis=-1)
  10.     idx = paddle.argmax(probs, axis=1).numpy()
  11.     idx = idx.tolist()
  12.     labels = [label_map[i] for i in idx]
  13.     qids = qids.numpy().tolist()
  14. results.extend(zip(qids, labels))
  1. res_dir = "./results"
  2. if not os.path.exists(res_dir):
  3.     os.makedirs(res_dir)# 写入预测结果with open(os.path.join(res_dir, "ChnSentiCorp.tsv"), 'w', encoding="utf8") as f:
  4.     f.write("index\tprediction\n")
  5.     for qid, label in results:
  6.         f.write(str(qid[0])+"\t"+label+"\n")

2 目标级情感分析

在电商产品分析场景下,除了分析整体商品的情感极性外,还细化到以商品具体的“方面”为分析主体进行情感分析(aspect-level),如下:

  • 这个薯片口味有点咸,太辣了,不过口感很脆。

关于薯片的口味方面是一个负向评价(咸,太辣),然而对于口感方面却是一个正向评价(很脆)。

  • 我很喜欢夏威夷,就是这边的海鲜太贵了。

关于夏威夷是一个正向评价(喜欢),然而对于夏威夷的海鲜却是一个负向评价(价格太贵)。

目标级情感分析任务

常用数据集

千言数据集已提供了许多任务常用数据集。其中情感分析数据集下载链接:

https://aistudio.baidu.com/aistudio/competition/detail/50/?isFromLUGE=TRUE

SE-ABSA16_PHNS数据集是关于手机的目标级情感分析数据集。PaddleNLP已经内置了该数据集,加载方式,如下:

train_ds, test_ds = load_dataset("seabsa16""phns", splits=["train""test"])

2.1 SKEP模型加载

目标级情感分析模型同样使用SkepForSequenceClassification模型,但目标级情感分析模型的输入不单单是一个句子,而是句对。一个句子描述“评价对象方面(aspect)”,另一个句子描述"对该方面的评论"。如下图所示。

  1. # 指定模型名称一键加载模型
  2. model = SkepForSequenceClassification.from_pretrained(
  3.     'skep_ernie_1.0_large_ch', num_classes=len(train_ds.label_list))# 指定模型名称一键加载tokenizer
  4. tokenizer = SkepTokenizer.from_pretrained('skep_ernie_1.0_large_ch')

2.2 数据处理

同样地,我们需要将原始SE_ABSA16_PHNS数据处理成模型可以读入的数据格式。

SKEP模型对中文文本处理按照字粒度进行处理,我们可以使用PaddleNLP内置的SkepTokenizer完成一键式处理。

  1. from functools import partial
  2. import osimport time
  3. import numpy as np
  4. import paddle
  5. import paddle.nn.functional as F
  6. from paddlenlp.data import Stack, Tuple, Pad
  7. def convert_example(example,
  8.                     tokenizer,
  9.                     max_seq_length=512,
  10.                     is_test=False,
  11.                     dataset_name="chnsenticorp"):
  12.     encoded_inputs = tokenizer(
  13.         text=example["text"],
  14.         text_pair=example["text_pair"],
  15.         max_seq_len=max_seq_length)
  16.     input_ids = encoded_inputs["input_ids"]
  17.     token_type_ids = encoded_inputs["token_type_ids"]
  18.     if not is_test:
  19.         label = np.array([example["label"]], dtype="int64")
  20.         return input_ids, token_type_ids, label
  21.     else:
  22.         return input_ids, token_type_ids
  1. # 处理的最大文本序列长度
  2. max_seq_length=256# 批量数据大小
  3. batch_size=16
  4. # 将数据处理成model可读入的数据格式
  5. trans_func = partial(
  6.     convert_example,
  7.     tokenizer=tokenizer,
  8.     max_seq_length=max_seq_length)# 将数据组成批量式数据,如# 将不同长度的文本序列padding到批量式数据中最大长度# 将每条数据label堆叠在一起
  9. batchify_fn = lambda samples, fn=Tuple(
  10.     Pad(axis=0, pad_val=tokenizer.pad_token_id),  # input_ids
  11.     Pad(axis=0, pad_val=tokenizer.pad_token_type_id),  # token_type_ids
  12.     Stack(dtype="int64")  # labels
  13. ): [data for data in fn(samples)]
  14. train_data_loader = create_dataloader(
  15.     train_ds,
  16.     mode='train',
  17.     batch_size=batch_size,
  18.     batchify_fn=batchify_fn,
  19.     trans_fn=trans_func)


2.3 模型训练

定义损失函数、优化器以及评价指标后,即可开始训练。

  1. # 训练轮次
  2. epochs = 3
  3. # 总共需要训练的step数
  4. num_training_steps = len(train_data_loader) * epochs# 优化器
  5. optimizer = paddle.optimizer.AdamW(
  6.     learning_rate=5e-5,
  7.     parameters=model.parameters())# 交叉熵损失
  8. criterion = paddle.nn.loss.CrossEntropyLoss()# Accuracy评价指标
  9. metric = paddle.metric.Accuracy()
  1. # 开启训练
  2. ckpt_dir = "skep_aspect"
  3. global_step = 0
  4. tic_train = time.time()for epoch in range(1, epochs + 1):
  5.     for step, batch in enumerate(train_data_loader, start=1):
  6.         input_ids, token_type_ids, labels = batch
  7.         # 喂数据给model
  8.         logits = model(input_ids, token_type_ids)
  9.         # 计算损失函数值
  10.         loss = criterion(logits, labels)
  11.         # 预测分类概率
  12.         probs = F.softmax(logits, axis=1)
  13.         # 计算acc
  14.         correct = metric.compute(probs, labels)
  15.         metric.update(correct)
  16.         acc = metric.accumulate()
  17.         global_step += 1
  18.         if global_step % 10 == 0:
  19.             print(
  20.                 "global step %d, epoch: %d, batch: %d, loss: %.5f, acc: %.5f, speed: %.2f step/s"
  21.                 % (global_step, epoch, step, loss, acc,
  22.                     10 / (time.time() - tic_train)))
  23.             tic_train = time.time()
  24.         # 反向梯度回传,更新参数
  25.         loss.backward()
  26.         optimizer.step()
  27.         optimizer.clear_grad()
  28.         if global_step % 100 == 0:
  29.             save_dir = os.path.join(ckpt_dir, "model_%d" % global_step)
  30.             if not os.path.exists(save_dir):
  31.                 os.makedirs(save_dir)
  32.             # 保存模型参数
  33.             model.save_pretrained(save_dir)
  34.             # 保存tokenizer的词表等
  35.             tokenizer.save_pretrained(save_dir)

2.4 模型预测

使用训练得到的模型还可以对评价对象进行情感预测。

  1. @paddle.no_grad()def predict(model, data_loader, label_map):
  2.     model.eval()
  3.     results = []
  4.     for batch in data_loader:
  5.         input_ids, token_type_ids = batch
  6.         logits = model(input_ids, token_type_ids)
  7.         probs = F.softmax(logits, axis=1)
  8.         idx = paddle.argmax(probs, axis=1).numpy()
  9.         idx = idx.tolist()
  10.         labels = [label_map[i] for i in idx]
  11.         results.extend(labels)
  12.     return results
  1. # 处理测试集数据
  2. label_map = {0'0'1'1'}
  3. trans_func = partial(
  4.     convert_example,
  5.     tokenizer=tokenizer,
  6.     max_seq_length=max_seq_length,
  7.     is_test=True)
  8. batchify_fn = lambda samples, fn=Tuple(
  9.     Pad(axis=0, pad_val=tokenizer.pad_token_id),  # input_ids
  10.     Pad(axis=0, pad_val=tokenizer.pad_token_type_id),  # token_type_ids
  11. ): [data for data in fn(samples)]
  12. test_data_loader = create_dataloader(
  13.     test_ds,
  14.     mode='test',
  15.     batch_size=batch_size,
  16.     batchify_fn=batchify_fn,
  17.     trans_fn=trans_func)
  1. # 根据实际运行情况,更换加载的参数路径
  2. params_path = 'skep_ckpt/model_900/model_state.pdparams'if params_path and os.path.isfile(params_path):
  3.     # 加载模型参数
  4.     state_dict = paddle.load(params_path)
  5.     model.set_dict(state_dict)
  6.     print("Loaded parameters from %s" % params_path)
  7. results = predict(model, test_data_loader, label_map)


加入交流群, 一起学习吧

如果你在学习过程中遇到任何问题或疑问,欢迎加入PaddleNLP的QQ技术交流群!


动手试一试

是不是觉得很有趣呀。小编强烈建议初学者参考上面的代码亲手敲一遍,因为只有这样,才能加深你对代码的理解呦。

本次项目对应的代码:

https://aistudio.baidu.com/aistudio/projectdetail/1968542

除此之外, PaddleNLP提供了多种预训练模型,可一键调用,来更换一下预训练试试吧:

https://paddlenlp.readthedocs.io/zh/latest/model_zoo/transformers.html

更多PaddleNLP信息,欢迎访问GitHub点star收藏后体验:

https://github.com/PaddlePaddle/PaddleNLP


回顾往期

越学越有趣:『手把手带你学NLP』系列项目01 ——词向量应用的那些事儿

越学越有趣:『手把手带你学NLP』系列项目02 ——语义相似度计算的那些事儿

越学越有趣:『手把手带你学NLP』系列项目03 ——快递单信息抽取的那些事儿

越学越有趣:『手把手带你学NLP』系列项目04 ——实体关系抽取的那些事儿

飞桨(PaddlePaddle)以百度多年的深度学习技术研究和业务应用为基础,集深度学习核心训练和推理框架、基础模型库、端到端开发套件和丰富的工具组件于一体,是中国首个自主研发、功能完备、开源开放的产业级深度学习平台。飞桨企业版针对企业级需求增强了相应特性,包含零门槛AI开发平台EasyDL和全功能AI开发平台BML。EasyDL主要面向中小企业,提供零门槛、预置丰富网络和模型、便捷高效的开发平台;BML是为大型企业提供的功能全面、可灵活定制和被深度集成的开发平台。

END

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/436016
推荐阅读
相关标签
  

闽ICP备14008679号