原标题:情感分析神器!再也不怕女朋友生气
背景介绍
众所周知,人类自然语言中包含了丰富的情感色彩:表达人的情绪(如悲伤、快乐)、表达人的心情(如倦怠、忧郁)、表达人的喜好(如喜欢、讨厌)、表达人的个性特征和表达人的立场等等。情感分析在商品喜好、消费决策、舆情分析等场景中均有应用。利用机器自动分析这些情感倾向,不但有助于帮助企业了解消费者对其产品的感受,为产品改进提供依据;同时还有助于企业分析商业伙伴们的态度,以便更好地进行商业决策。
被人们所熟知的情感分析任务是将一段文本分类,如分为情感极性为 正向、负向、其他的三分类问题:

情感分析任务
正向:表示正面积极的情感,如高兴,幸福,惊喜,期待等。
负向:表示负面消极的情感,如难过,伤心,愤怒,惊恐等。
其他:其他类型的情感。
实际上,以上熟悉的情感分析任务是句子级情感分析任务。
情感分析预训练模型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 5.4寸笔记本的键盘确实爽,基本跟台式机差不多了,蛮喜欢数字小键盘,输数字特方便,样子也很美观,做工也相当不错 1
③ 房间太小。其他的都一般。。。。。。。。。 0
其中1表示正向情感,0表示负向情感。

句子级情感分析任务
常用数据集
ChnSenticorp数据集是公开中文情感分析常用数据集, 其为二分类数据集。PaddleNLP已经内置该数据集,一键即可加载。
frompaddlenlp.datasets importload_dataset
train_ds, dev_ds, test_ds = load_dataset( “chnsenticorp”, splits=[ “train”, “dev”, “test”])
print(train_ds[ 0])
print(train_ds[ 1])
print(train_ds[ 2])
{ ‘text’: ‘选择珠江花园的原因就是方便,有电动扶梯直接到达海边,周围餐馆、食廊、商场、超市、摊位一应俱全。酒店装修一般,但还算整洁。 泳池在大堂的屋顶,因此很小,不过女儿倒是喜欢。 包的早餐是西式的,还算丰富。 服务吗,一般’, ‘label’: 1, ‘qid’: ”}
1.1 SKEP模型加载
PaddleNLP已经实现了SKEP预训练模型,可以通过一行代码实现SKEP加载。
句子级情感分析模型是SKEP fine-tune 文本分类常用模型SkepForSequenceClassification。其首先通过SKEP提取句子语义特征,之后将语义特征进行分类。

from paddlenlp.transformers import SkepForSequenceClassification, SkepTokenizer
# 指定模型名称,一键加载模型
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等。
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完成一键式处理。
importos
fromfunctools importpartial
importnumpy asnp
importpaddle
importpaddle.nn.functional asF
frompaddlenlp.data importStack, Tuple, Pad
fromutils importcreate_dataloader
defconvert_example(example,
tokenizer,
max_seq_length= 512,
is_test=False) :
# 将原数据处理成model可读入的格式,enocded_inputs是一个dict,包含input_ids、token_type_ids等字段
encoded_inputs = tokenizer(
text=example[ “text”], max_seq_len=max_seq_length)
# input_ids:对文本切分token后,在词汇表中对应的token id
input_ids = encoded_inputs[ “input_ids”]
# token_type_ids:当前token属于句子1还是句子2,即上述图中表达的segment ids
token_type_ids = encoded_inputs[ “token_type_ids”]
ifnotis_test:
# label:情感极性类别
label = np.array([example[ “label”]], dtype= “int64”)
returninput_ids, token_type_ids, label
else:
# qid:每条数据的编号
qid = np.array([example[ “qid”]], dtype= “int64”)
returninput_ids, token_type_ids, qid
# 批量数据大小
batch_size = 32 # 文本序列最大长度
max_seq_length = 256
# 将数据处理成模型可读入的数据格式
trans_func = partial(
convert_example,
tokenizer=tokenizer,
max_seq_length=max_seq_length)
# 将数据组成批量式数据,如# 将不同长度的文本序列padding到批量式数据中最大长度# 将每条数据label堆叠在一起
batchify_fn = lambda samples, fn=Tuple(
Pad(axis=0, pad_val=tokenizer.pad_token_id), # input_ids
Pad(axis=0, pad_val=tokenizer.pad_token_type_id), # token_type_ids
Stack # labels
): [data for data in fn(samples)]
train_data_loader = create_dataloader(
train_ds,
mode=’train’,
batch_size=batch_size,
batchify_fn=batchify_fn,
trans_fn=trans_func)
dev_data_loader = create_dataloader(
dev_ds,
mode=’dev’,
batch_size=batch_size,
batchify_fn=batchify_fn,
trans_fn=trans_func)
1.3 模型训练和评估
定义损失函数、优化器以及评价指标后,即可开始训练。
推荐超参设置:
- max_seq_length=256
- batch_size=48
- learning_rate=2e-5
- epochs=10
实际运行时可以根据显存大小调整batch_size和max_seq_length大小。
importtime
fromutils importevaluate
# 训练轮次
epochs = 1# 训练过程中保存模型参数的文件夹
ckpt_dir = “skep_ckpt”# len(train_data_loader)一轮训练所需要的step数
num_training_steps = len(train_data_loader) * epochs
# Adam优化器
optimizer = paddle.optimizer.AdamW(
learning_rate= 2e-5,
parameters=model.parameters) # 交叉熵损失函数
criterion = paddle.nn.loss.CrossEntropyLoss # accuracy评价指标
metric = paddle.metric.Accuracy
# 开启训练
global_step = 0
tic_train = time.time forepoch inrange( 1, epochs + 1):
forstep, batch inenumerate(train_data_loader, start= 1):
input_ids, token_type_ids, labels = batch
# 喂数据给model
logits = model(input_ids, token_type_ids)
# 计算损失函数值
loss = criterion(logits, labels)
# 预测分类概率值
probs = F.softmax(logits, axis= 1)
# 计算acc
correct = metric.compute(probs, labels)
metric.update(correct)
acc = metric.accumulate
global_step += 1
ifglobal_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
ifglobal_step % 100== 0:
save_dir = os.path.join(ckpt_dir, “model_%d”% global_step)
ifnotos.path.exists(save_dir):
os.makedirs(save_dir)
# 评估当前训练的模型
evaluate(model, criterion, metric, dev_data_loader)
# 保存当前模型参数等
model.save_pretrained(save_dir)
# 保存tokenizer的词表等
tokenizer.save_pretrained(save_dir)
globalstep 10, epoch: 1, batch: 10, loss: 0.59440, accu: 0.60625, speed: 0.76step/s
globalstep 20, epoch: 1, batch: 20, loss: 0.42311, accu: 0.72969, speed: 0.78step/s
globalstep 30, epoch: 1, batch: 30, loss: 0.09735, accu: 0.78750, speed: 0.72step/s
··········
globalstep 290, epoch: 1, batch: 290, loss: 0.14512, accu: 0.92778, speed: 0.71step/s
globalstep 300, epoch: 1, batch: 300, loss: 0.12470, accu: 0.92781, speed: 0.77step/s
eval loss: 0.19412, accu: 0.92667
1.4预测提交结果
使用训练得到的模型还可以对文本进行情感预测。
importnumpy asnp
importpaddle
# 处理测试集数据
trans_func = partial(
convert_example,
tokenizer=tokenizer,
max_seq_length=max_seq_length,
is_test= True)
batchify_fn = lambdasamples, fn=Tuple(
Pad(axis= 0, pad_val=tokenizer.pad_token_id), # input
Pad(axis= 0, pad_val=tokenizer.pad_token_type_id), # segment
Stack # qid
): [data fordata infn(samples)]
test_data_loader = create_dataloader(
test_ds,
mode= ‘test’,
batch_size=batch_size,
batchify_fn=batchify_fn,
trans_fn=trans_func)
# 根据实际运行情况,更换加载的参数路径
params_path = ‘skep_ckp/model_500/model_state.pdparams’
ifparams_path andos.path.isfile(params_path):
# 加载模型参数
state_dict = paddle.load(params_path)
model.set_dict(state_dict)
print( “Loaded parameters from %s”% params_path)
label_map = { 0: ‘0’, 1: ‘1’}
results = [] # 切换model模型为评估模式,关闭dropout等随机因素
model.eval
forbatch intest_data_loader:
input_ids, token_type_ids, qids = batch
# 喂数据给模型
logits = model(input_ids, token_type_ids)
# 预测分类
probs = F.softmax(logits, axis= -1)
idx = paddle.argmax(probs, axis= 1).numpy
idx = idx.tolist
labels = [label_map[i] fori inidx]
qids = qids.numpy.tolist
results.extend(zip(qids, labels))
res_dir = “./results”
ifnotos.path.exists(res_dir):
os.makedirs(res_dir) # 写入预测结果with open(os.path.join(res_dir, “ChnSentiCorp.tsv”), ‘w’, encoding=”utf8″) as f:
f.write( “indextpredictionn”)
forqid, label inresults:
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)”,另一个句子描述”对该方面的评论”。如下图所示。

# 指定模型名称一键加载模型
model = SkepForSequenceClassification.from_pretrained(
‘skep_ernie_1.0_large_ch’, num_classes=len(train_ds.label_list)) # 指定模型名称一键加载tokenizer
tokenizer = SkepTokenizer.from_pretrained( ‘skep_ernie_1.0_large_ch’)
2.2 数据处理
同样地,我们需要将原始SE_ABSA16_PHNS数据处理成模型可以读入的数据格式。
SKEP模型对中文文本处理按照字粒度进行处理,我们可以使用PaddleNLP内置的SkepTokenizer完成一键式处理。
fromfunctools importpartial
importosimport time
importnumpy asnp
importpaddle
importpaddle.nn.functional asF
frompaddlenlp.data importStack, Tuple, Pad
defconvert_example(example,
tokenizer,
max_seq_length= 512,
is_test=False,
dataset_name= “chnsenticorp”) :
encoded_inputs = tokenizer(
text=example[ “text”],
text_pair=example[ “text_pair”],
max_seq_len=max_seq_length)
input_ids = encoded_inputs[ “input_ids”]
token_type_ids = encoded_inputs[ “token_type_ids”]
ifnotis_test:
label = np.array([example[ “label”]], dtype= “int64”)
returninput_ids, token_type_ids, label
else:
returninput_ids, token_type_ids
# 处理的最大文本序列长度
max_seq_length= 256# 批量数据大小
batch_size= 16
# 将数据处理成model可读入的数据格式
trans_func = partial(
convert_example,
tokenizer=tokenizer,
max_seq_length=max_seq_length) # 将数据组成批量式数据,如# 将不同长度的文本序列padding到批量式数据中最大长度# 将每条数据label堆叠在一起
batchify_fn = lambdasamples, fn=Tuple(
Pad(axis= 0, pad_val=tokenizer.pad_token_id), # input_ids
Pad(axis= 0, pad_val=tokenizer.pad_token_type_id), # token_type_ids
Stack(dtype= “int64”) # labels
): [data fordata infn(samples)]
train_data_loader = create_dataloader(
train_ds,
mode= ‘train’,
batch_size=batch_size,
batchify_fn=batchify_fn,
trans_fn=trans_func)
2.3 模型训练
定义损失函数、优化器以及评价指标后,即可开始训练。
# 训练轮次
epochs = 3
# 总共需要训练的step数
num_training_steps = len(train_data_loader) * epochs # 优化器
optimizer = paddle.optimizer.AdamW(
learning_rate= 5e-5,
parameters=model.parameters) # 交叉熵损失
criterion = paddle.nn.loss.CrossEntropyLoss # Accuracy评价指标
metric = paddle.metric.Accuracy
# 开启训练
ckpt_dir = “skep_aspect”
global_step = 0
tic_train = time.time forepoch inrange( 1, epochs + 1):
forstep, batch inenumerate(train_data_loader, start= 1):
input_ids, token_type_ids, labels = batch
# 喂数据给model
logits = model(input_ids, token_type_ids)
# 计算损失函数值
loss = criterion(logits, labels)
# 预测分类概率
probs = F.softmax(logits, axis= 1)
# 计算acc
correct = metric.compute(probs, labels)
metric.update(correct)
acc = metric.accumulate
global_step += 1
ifglobal_step % 10== 0:
print(
“global step %d, epoch: %d, batch: %d, loss: %.5f, acc: %.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
ifglobal_step % 100== 0:
save_dir = os.path.join(ckpt_dir, “model_%d”% global_step)
ifnotos.path.exists(save_dir):
os.makedirs(save_dir)
# 保存模型参数
model.save_pretrained(save_dir)
# 保存tokenizer的词表等
tokenizer.save_pretrained(save_dir)
2.4 模型预测
使用训练得到的模型还可以对评价对象进行情感预测。
@paddle.no_graddef predict(model, data_loader, label_map):
model.eval
results = []
forbatch indata_loader:
input_ids, token_type_ids = batch
logits = model(input_ids, token_type_ids)
probs = F.softmax(logits, axis= 1)
idx = paddle.argmax(probs, axis= 1).numpy
idx = idx.tolist
labels = [label_map[i] fori inidx]
results.extend(labels)
returnresults
# 处理测试集数据
label_map = { 0: ‘0’, 1: ‘1’}
trans_func = partial(
convert_example,
tokenizer=tokenizer,
max_seq_length=max_seq_length,
is_test= True)
batchify_fn = lambdasamples, fn=Tuple(
Pad(axis= 0, pad_val=tokenizer.pad_token_id), # input_ids
Pad(axis= 0, pad_val=tokenizer.pad_token_type_id), # token_type_ids
): [data fordata infn(samples)]
test_data_loader = create_dataloader(
test_ds,
mode= ‘test’,
batch_size=batch_size,
batchify_fn=batchify_fn,
trans_fn=trans_func)
# 根据实际运行情况,更换加载的参数路径
params_path = ‘skep_ckpt/model_900/model_state.pdparams’ifparams_path andos.path.isfile(params_path):
# 加载模型参数
state_dict = paddle.load(params_path)
model.set_dict(state_dict)
print( “Loaded parameters from %s”% params_path)
results = predict(model, test_data_loader, label_map)
动手试一试
是不是觉得很有趣呀。小编强烈建议初学者参考上面的代码亲手敲一遍,因为只有这样,才能加深你对代码的理解呦。
本次项目对应的代码:
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
END
责任编辑: