深度模型在情感分析中的应用
数据准备
本次实验将继续加载两个数据,一个是已经标注好的用户评论数据,另外一个是用户评价主题句,通过标注过的用户评论数据研究不同粒度的用户评价处理对深度情感分析模型的性能的影响,并比较字符级模型在用户评价情感极性推理上的差异。
使用 Pandas 加载已经标注好的在线用户评论情感数据表格,并查看数据维度和前 5 行数据。
import pandas as pd
train_data = pd.read_csv(
'https://labfile.oss.aliyuncs.com/courses/2628/hotel_comment.csv')
print(train_data.shape)
train_data.head()
加载民宿评论数据,并打印第一行。
comment_source = pd.read_csv(
'https://labfile.oss.aliyuncs.com/courses/2628/1-1.csv')
print(comment_source.shape)
comment_source.head(1)
数据属性如下表所示
数据预处理
打印评论情感标注数据集上的标签统计,我们标注好的用户评论情感数据是一个标签平衡的数据。
train_data['label'].groupby(train_data['label']).count()
用户评论分词
jieba 分词器预热,第一次使用需要加载字典和缓存,通过结果看出返回的是分词的列表。
import jieba
' '.join(jieba.lcut(str('使用分词器进行分词')))
批量对用户评价进行分词,并打印第一行数据,处理过程需要一些时间。
%time train_data['text_word'] = train_data['text'].apply(lambda x: " ".join(jieba.lcut(str(x))))
train_data.head(1)
将用户评论处理成字符级别,为字符级模型提供训练集,并打印第一行数据,此预处理速度远远快于分词速度。
%time train_data['text_character'] = train_data['text'].apply(lambda x: " ".join(list(x)))
train_data.head(1)
TextCNN 情感分析模型
TextCNN 使用的卷积神经网络是一个典型的空间上的深度神经网络,基于卷积神经网络的情感分析具有优秀的特征抽取能力,能显著降低情感分类中人工抽取特征的难度。这类方法又根据文本嵌入粒度的不同可以分为字符级嵌入和词嵌入两种,一种是以分词后的词为情感分析粒度,另一种为字符级粒度的情感分析粒度,最后一层将包含全文潜在信息的最终编码送入 Sigmoid 做情感强度计算,即可对用户评论进行情感极性推理,是目前在文本分类经常使用的模型。
from tensorflow.keras.layers import Dense, Embedding, Flatten, Dropout, Convolution1D, Activation, MaxPooling1D
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.callbacks import EarlyStopping
from sklearn.model_selection import train_test_split
from tensorflow.keras.models import Sequential
import numpy as np
def build_cnn_model():
# 序列模型初始化
model = Sequential()
# 嵌入层
model.add(Embedding(max_words, embedding_dim, input_length=maxlen))
# CNN 结构
model.add(Convolution1D(64, 3, input_shape=(-1, embedding_dim)))
# 激活层
model.add(Activation('relu'))
# 特征降维层
model.add(MaxPooling1D(2, 2))
# 特征拉平
model.add(Flatten())
# 全连接层
model.add(Dense(128, activation='relu'))
# 参数随机丢弃层
model.add(Dropout(0.5))
# 输出层
model.add(Dense(1, activation='sigmoid'))
# 使用二分类交叉熵计算损失,并使用 adam 进行损失调节,调节指标是准确度
model.compile(loss='binary_crossentropy',
optimizer='adam',
metrics=['accuracy'])
return model
词级别模型训练
# 设置将用户评论映射到空间的维度
embedding_dim = 300
# 设置用户评论的最大长度
maxlen = 100
# 设置词频字典大小
max_words = 1000
# 加载 TextCNN 结构
model_w_cnn = build_cnn_model()
# 打印网络结构
model_w_cnn.summary()
# 标签数据转换为 np.array
label_data = train_data['label'].to_numpy()
# 创建分词器 Tokenizer 对象
tokenizer = Tokenizer(num_words=max_words)
# 将Tokenizer拟合语料,生成字典,形成新的tokenizer
tokenizer.fit_on_texts(train_data['text_word'].tolist())
# 文本数据转换和填充
train_data_word = pad_sequences(
tokenizer.texts_to_sequences(train_data['text_word'].tolist()), maxlen)
按照训练集 8 成和测试集 2 成的比例对数据集进行划分。
# 设置固定划分数据集
x_train_word, y_train_word, x_test, y_test = train_test_split(train_data_word,
label_data,
test_size=0.2,
random_state=1)
词级 TextCNN 模型训练,设置 128 条数据为一个批次,2 轮模型训练,训练集中的 20% 作为验证集,并加入早停设置。
# 加载记时模块
import time
# 记录开始时间
st = time.time()
# 模型训练
model_w_cnn.fit(
x_train_word,
x_test,
batch_size=128,
epochs=2,
validation_split=0.2,
callbacks=[EarlyStopping(monitor='val_loss', min_delta=0.0001)])
# 计算模型耗时
time_used = time.time() - st
通过传入原始的标签和预测的标签可以直接将分类器性能进行度量,并对指标收集,包含:模型的训练时间、accuracy_score 表示被正确预测的样本占总样本的比例、f1_score 值表示精确率与召回率的调和平均数和模型标签。
# 使用列表的形式收集数据指标
from sklearn import metrics
method, acc_list, f1_list, time_use = list(), list(), list(), list()
# 开始对测试集合进行批量计算
model_w_cnn_result = model_w_cnn.predict_classes(y_train_word,
batch_size=128,
verbose=0)
# 收集训练标签
method.append('W_TextCNN')
# 收集 accuracy_score 分数
acc_list.append(metrics.accuracy_score(y_test, model_w_cnn_result))
# 收集 f1 分数
f1_list.append(metrics.f1_score(y_test, model_w_cnn_result))
# 收集模型训练耗时
time_use.append(time_used)
对训练的模型进行加载,并打印网络结构。
# 加载 TextCNN 结构
model_c_cnn = build_cnn_model()
# 打印网络结构
model_c_cnn.summary()
# 创建分词器 Tokenizer 对象
tokenizer = Tokenizer(num_words=max_words)
# 将Tokenizer拟合语料,生成字典,形成新的tokenizer
tokenizer.fit_on_texts(train_data['text_character'].tolist())
# 文本数据转换和填充
train_data_character = pad_sequences(
tokenizer.texts_to_sequences(train_data['text_character'].tolist()),
maxlen)
设置固定划分数据集,划分比例为 0.2 即训练集是测试集的 4 倍量。
# 设置固定划分数据集
x_train_character, y_train_character, x_test, y_test = train_test_split(
train_data_character, label_data, test_size=0.2, random_state=1)
字符级别模型训练
字符级 TextCNN 模型训练,设置 128 条数据为一个批次,2 轮模型训练,训练集中的 20% 作为验证集,并加入早停设置。
# 记录开始时间
st = time.time()
# 模型训练
model_c_cnn.fit(
x_train_character,
x_test,
batch_size=128,
epochs=2,
validation_split=0.2,
callbacks=[EarlyStopping(monitor='val_loss',
min_delta=0.0001)] # 当val-loss不再提升时停止训练
)
# 计算模型耗时
time_used = time.time() - st
对字符级 TextCNN 的预测结果进行收集。
# 开始对测试集合进行批量计算
model_c_cnn_result = model_c_cnn.predict_classes(y_train_character,
batch_size=128,
verbose=0)
# 收集训练标签
method.append('C_TextCNN')
# 收集 accuracy_score 分数
acc_list.append(metrics.accuracy_score(y_test, model_c_cnn_result))
# 收集 f1 分数
f1_list.append(metrics.f1_score(y_test, model_c_cnn_result))
# 收集模型训练耗时
time_use.append(time_used)
GRU 情感分析模型
GRU 属于 RNN(recurrent neural networks,循环神经网络),是 LSTM 最流行的一个变体,比 LSTM 模型要简单,GRU 的门控单元减少了一个,GRU 与 LSTM 一样都是旨在解决标准 RNN 中出现的梯度消失问题,GRU 比 LSTM 在减少了计算量的条件下,做到了精度与 LSTM 持平,是目前在文本分类经常使用的模型。
我们使用函数定义的方式进行 GRU 模型的初始化。
from tensorflow.keras.layers import GRU
def build_gru_model():
# 序列模型初始化
model = Sequential()
# 嵌入层
model.add(Embedding(max_words, embedding_dim, input_length=maxlen))
# GRU 结构
model.add(GRU(units=64, return_sequences=False))
# 全连接层
model.add(Dense(128, activation='relu'))
# 参数随机丢弃层
model.add(Dropout(0.5))
# 输出层
model.add(Dense(1, activation='sigmoid'))
# 使用二分类交叉熵计算损失,并使用 adam 进行损失调节,调节指标是准确度
model.compile(loss='binary_crossentropy',
optimizer='adam',
metrics=['accuracy'])
return model
对训练的模型进行加载,并打印网络结构。
# 加载 GRU 结构
model_w_gru = build_gru_model()
# 打印结构
model_w_gru.summary()
词级别模型训练
词级 GRU 模型训练,设置 128 条数据为一个批次,2 轮模型训练,训练集中的 20% 作为验证集,并加入早停设置。
# 记录模型训练开始时间
st = time.time()
# 模型训练
model_w_gru.fit(
x_train_word,
x_test,
batch_size=128,
epochs=2,
validation_split=0.2,
# 当 val_loss 不再提升时停止训练
callbacks=[EarlyStopping(monitor='val_loss', min_delta=0.0001)])
# 计算模型耗时
time_used = time.time() - st
# 开始对测试集合进行批量计算
model_w_gru_result = model_w_gru.predict_classes(y_train_word,
batch_size=128,
verbose=0)
# 收集训练标签
method.append('W_GRU')
# 收集 accuracy_score 分数
acc_list.append(metrics.accuracy_score(y_test, model_w_gru_result))
# 收集 f1 分数
f1_list.append(metrics.f1_score(y_test, model_w_gru_result))
# 收集模型训练耗时
time_use.append(time_used)
对训练的模型进行加载,并打印网络结构。
# 加载字符级 GRU 结构
model_c_gru = build_gru_model()
# 打印结构
model_c_gru.summary()
字符级别模型训练
将处理好的用户评论数据进行字符级处理即可输入字符级 GRU 模型训练,设置 128 条数据为一个批次,2 轮模型训练,训练集中的 20% 作为验证集,并加入早停设置。
# 记录开始时间
st = time.time()
# 模型训练
model_c_gru.fit(
x_train_character,
x_test,
batch_size=128,
epochs=2,
validation_split=0.2,
# 当val-loss不再提升时停止训练
callbacks=[EarlyStopping(monitor='val_loss', min_delta=0.0001)])
# 计算模型耗时
time_used = time.time() - st
对字符级 GRU 的测试集预测性能进行记录。
# 开始对测试集合进行批量计算
model_c_gru_result = model_c_gru.predict_classes(y_train_character,
batch_size=128,
verbose=0)
# 收集训练标签
method.append('C_GRU')
# 收集 accuracy_score 分数
acc_list.append(metrics.accuracy_score(y_test, model_c_gru_result))
# 收集 f1 分数
f1_list.append(metrics.f1_score(y_test, model_c_gru_result))
# 收集模型训练耗时
time_use.append(time_used)
模型性能分析
通过控制参数变量的方式进行,并使用同样的数据集合观察性能指数测试结果。字符级能使用较小的字符级词典对语料的覆盖度更高,字符级预处理在测试集上的表现基本接近词级模型,并从耗时来看字符级都是最少的。TextCNN 架构总体高于 GRU 的准确度和综合值,并且训练时间相对较短。字符级语言建模的思想来自于信号处理,使用语言最小的文字单元去模拟复杂的语义关系,因为我们相信模型可以捕捉到这些语法和单词语义信息,在后续我们继续使用这种方式。
result_analysis = pd.DataFrame({
'method': method,
'time_use': time_use,
'acc_list': acc_list,
'f1_list': f1_list,
})
result_analysis.head()
字符级深度模型对比
对用户评论数据预处理
# 批量将用用户评论进行字符级处理,并打印第一行处理后的结果。
%time comment_source['text_character'] = comment_source['content'].apply(lambda x: " ".join(list(x)))
comment_source.head(1)
对用户评论进行字符向量化。
# 创建字符位置的字典
tokenizer = Tokenizer(num_words=max_words)
# 利用 Tokenizer 的字典对字符化的用户评论进行转化
tokenizer.fit_on_texts(train_data['text_character'].tolist())
# 找不到的和字符数不够的进行填补
test_text = pad_sequences(
tokenizer.texts_to_sequences(comment_source['text_character'].tolist()),
maxlen)
情感极性推理
使用训练好的字符级 TextCNN 对用户评论进行情感预测,需要一些时间,请耐心等待。
# 批量用户情感极性预测,设置 128 条数据为一个处理批次
%time model_c_textcnn_result = model_c_cnn.predict_proba(test_text, batch_size=128)
# 输出用户评论对应的积极情感对应的概率值作为用户情感极性映射
comment_source['model_c_textcnn_result'] = [i[0]
for i in model_c_textcnn_result]
使用训练好的字符级 GRU 对用户评论进行情感预测,需要一些时间,请耐心等待。
# 批量用户情感极性预测,设置 128 条数据为一个处理批次
%time model_c_gru_result = model_c_gru.predict_proba(test_text, batch_size=128)
# 输出用户评论对应的积极情感对应的概率值作为用户情感极性映射
comment_source['model_c_gru_result'] = [i[0] for i in model_c_gru_result]
情感极性推理结果可视化
将两种字符级神经网络情感极性推理模型的结果取出来。
# 取出字符级 TextCNN 的推理结果
model_textcnn_result = comment_source['model_c_textcnn_result'].tolist()
# 取出字符级 GRU 的推理结果
model_gru_result = comment_source['model_c_gru_result'].tolist()
对全量的用户评论分别使用两个模型进行情感极性预测,并进行可视化,我们发现两种模型在全量的用户评论上的表现基本一致,字符级 TextCNN 在用户两极情感极性上表现更好。
from matplotlib import pyplot as plt
%matplotlib inline
# 定义画布大小
plt.rcParams['figure.figsize'] = (8.0, 8.0)
# 使用红色代表字符级 TextCNN 模型的推理结果
plt.hist(model_textcnn_result,
bins=np.arange(0, 1, 0.01),
color='red',
label='model_c_textcnn_result')
# 使用蓝色代表字符级 GRU 模型的推理结果
plt.hist(model_gru_result,
bins=np.arange(0, 1, 0.01),
color='blue',
label='model_c_gru_result')
plt.legend()
# 定义横轴为评论统计量
plt.xlabel('count')
# 定义纵轴为模型打分
plt.ylabel('model_sa_score')
plt.title('Diff_Character_Model_Customer_Satisfaction_Analysis')
plt.show()