当前位置: 代码迷 >> 综合 >> 基于LSTM电商评论情感分析-多评价指标可视化版(内附源码)【自然语言处理NLP-100例】
  详细解决方案

基于LSTM电商评论情感分析-多评价指标可视化版(内附源码)【自然语言处理NLP-100例】

热度:48   发布时间:2023-12-14 08:02:13.0
  • ? 运行环境:python3
  • ? 作者:K同学啊
  • ? 精选专栏:《深度学习100例》
  • ? 推荐专栏:《新手入门深度学习》
  • ? 极品专栏:《Matplotlib教程》
  • ? 选自专栏:《自然语言处理NLP-实例教程》
  • ? 优秀专栏:《Python入门100题》

大家好,我是K同学啊!

在上一篇文章中,我使用LSTM对电商评论做了一个较为复杂的情感分析,本文就继续上次的工作做进一部分的分析。本次主要是在评价指标metrics处增加了PrecisionRecallAUC等值,实现了训练模型的同时记录这些指标,是实现方式上与以往也有所不同。与此同时,本次全连接层Dense的输出也被设置为1,之前很少这样操作的,可以对这块针对性学习一下。

文章目录

    • 一、前期工作
      • 1. 导入数据
      • 2. 数据分析
    • 二、数据预处理
      • 1. 打乱数据
      • 2. 分词处理
      • 3. 去除停用词
      • 4. Word2vec处理
      • 5. 划分训练集与测试集
    • 三、结果分析
    • 四、情感预测

一、前期工作

1. 导入数据

#源码内可阅读
df
evaluation label
0 用了一段时间,感觉还不错,可以 正面
1 电视非常好,已经是家里的第二台了。第一天下单,第二天就到本地了,可是物流的人说车坏了,一直催... 正面
2 电视比想象中的大好多,画面也很清晰,系统很智能,更多功能还在摸索中 正面
3 不错 正面
4 用了这么多天了,感觉还不错。夏普的牌子还是比较可靠。希望以后比较耐用,现在是考量质量的时候。 正面
... ... ...
4278 一般,差强人意,还弄了点不愉快,投诉了好久才解决 负面
4279 屏幕拐角明显暗,图像不到边。工程师上门尽然说没问题!退货还要收100元的开箱费,帮别人买的,... 负面
4280 一分都不想给,京东这次让我太失望了,买的电视没有声音,说是退货上门取件,规定好的时间不去,一... 负面
4281 新电视买回家不到十多天,底座支架因质量问题断裂,电视从桌子上摔坏,打售后电话,人员一直推脱不... 负面
4282 一般般。这个价位也不会抱太多的期望。比某某TV还是好很多。 负面

4283 rows × 2 columns

2. 数据分析

df.groupby('label')["evaluation"].count()
label
正面    1908
负面    2375
Name: evaluation, dtype: int64
df.label.value_counts().plot(kind='pie', autopct='%0.05f%%', colors=['lightblue', 'lightgreen'], explode=(0.01, 0.01))
<AxesSubplot:ylabel='label'>

df['length'] = df['evaluation'].apply(lambda x: len(x))
df.head()
evaluation label length
0 用了一段时间,感觉还不错,可以 正面 15
1 电视非常好,已经是家里的第二台了。第一天下单,第二天就到本地了,可是物流的人说车坏了,一直催... 正面 97
2 电视比想象中的大好多,画面也很清晰,系统很智能,更多功能还在摸索中 正面 33
3 不错 正面 2
4 用了这么多天了,感觉还不错。夏普的牌子还是比较可靠。希望以后比较耐用,现在是考量质量的时候。 正面 46
# 源码内可阅读
plt.show()

# 源码内可阅读
plt.show()
分位点为0.9的句子长度:172。

二、数据预处理

1. 打乱数据

将正面文本数据与负面文本数据进行打乱

df = df.sample(frac=1)
df.head()
evaluation label length
2105 电视不错,不过今年的价格比去年贵了…… 负面 19
996 电视很清晰大品牌值得信赖 正面 12
4171 电视不错,没有坏点,漏光也基本看不出来,看了下电视剧,有点拖影,网上换个接口就好了,暂时没试... 负面 118
3206 喇叭太差劲,有点小卡,界面不是很友好,与泰捷盒子差太远了,但播放效果色彩不错,漏光较多 负面 43
1748 好,送货速度快,服务好。.3333333 正面 20

2. 分词处理

import jiebaword_cut = lambda x: jieba.lcut(x)
df['words'] = df["evaluation"].apply(word_cut)
df.head()
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\ADMINI~1\AppData\Local\Temp\jieba.cache
Loading model cost 0.442 seconds.
Prefix dict has been built successfully.
evaluation label length words
2105 电视不错,不过今年的价格比去年贵了…… 负面 19 [电视, 不错, ,, 不过, 今年, 的, 价格比, 去年, 贵, 了, …, …]
996 电视很清晰大品牌值得信赖 正面 12 [电视, 很, 清晰, 大, 品牌, 值得, 信赖]
4171 电视不错,没有坏点,漏光也基本看不出来,看了下电视剧,有点拖影,网上换个接口就好了,暂时没试... 负面 118 [电视, 不错, ,, 没有, 坏点, ,, 漏光, 也, 基本, 看不出来, ,, 看, ...
3206 喇叭太差劲,有点小卡,界面不是很友好,与泰捷盒子差太远了,但播放效果色彩不错,漏光较多 负面 43 [喇叭, 太, 差劲, ,, 有点, 小卡, ,, 界面, 不是, 很, 友好, ,, 与,...
1748 好,送货速度快,服务好。.3333333 正面 20 [好, ,, 送货, 速度, 快, ,, 服务, 好, 。, ., 3333333]

3. 去除停用词

with open("hit_stopwords.txt", "r", encoding='utf-8') as f:stopwords = f.readlines()stopwords_list = []
for each in stopwords:stopwords_list.append(each.strip('\n'))# 添加自定义停用词
stopwords_list += ["…","去","还","西","一件","月","年",".","都"]def remove_stopwords(ls):  # 去除停用词return [word for word in ls if word not in stopwords_list]df['去除停用词后的数据']=df["words"].apply(lambda x: remove_stopwords(x))
df["y"] = np.array([1 if i=="正面" else 0 for i in df['label']])
df.head()
evaluation label length words 去除停用词后的数据 y
2105 电视不错,不过今年的价格比去年贵了…… 负面 19 [电视, 不错, ,, 不过, 今年, 的, 价格比, 去年, 贵, 了, …, …] [电视, 不错, 今年, 价格比, 去年, 贵] 0
996 电视很清晰大品牌值得信赖 正面 12 [电视, 很, 清晰, 大, 品牌, 值得, 信赖] [电视, 很, 清晰, 大, 品牌, 值得, 信赖] 1
4171 电视不错,没有坏点,漏光也基本看不出来,看了下电视剧,有点拖影,网上换个接口就好了,暂时没试... 负面 118 [电视, 不错, ,, 没有, 坏点, ,, 漏光, 也, 基本, 看不出来, ,, 看, ... [电视, 不错, 没有, 坏点, 漏光, 基本, 看不出来, 看, 下, 电视剧, 有点, ... 0
3206 喇叭太差劲,有点小卡,界面不是很友好,与泰捷盒子差太远了,但播放效果色彩不错,漏光较多 负面 43 [喇叭, 太, 差劲, ,, 有点, 小卡, ,, 界面, 不是, 很, 友好, ,, 与,... [喇叭, 太, 差劲, 有点, 小卡, 界面, 不是, 很, 友好, 泰捷, 盒子, 差太远... 0
1748 好,送货速度快,服务好。.3333333 正面 20 [好, ,, 送货, 速度, 快, ,, 服务, 好, 。, ., 3333333] [好, 送货, 速度, 快, 服务, 好, 3333333] 1

4. Word2vec处理

Word2vec是一个用来产生词向量的模型。是一个将单词转换成向量形式的工具。 通过转换,可以把对文本内容的处理简化为向量空间中的向量运算,计算出向量空间上的相似度,来表示文本语义上的相似度。

from gensim.models.word2vec  import Word2Vecx = df["去除停用词后的数据"]# 训练 Word2Vec 浅层神经网络模型
w2v = Word2Vec(vector_size=300,  #是指特征向量的维度,默认为100。min_count=10)     #可以对字典做截断. 词频少于min_count次数的单词会被丢弃掉, 默认值为5。
w2v.build_vocab(x)
w2v.train(x,                         total_examples=w2v.corpus_count, epochs=20)
# 保存 Word2Vec 模型及词向量
w2v.save('w2v_model.pkl')# 将文本转化为向量
def average_vec(text):vec = np.zeros(300).reshape((1, 300))for word in text:try:vec += w2v.wv[word].reshape((1, 300))except KeyError:continuereturn vec# 将词向量保存为 Ndarray
x_vec = np.concatenate([average_vec(z) for z in x])
y     = df['y']

5. 划分训练集与测试集

from sklearn.model_selection import train_test_splitX_train,X_test,y_train,y_test = train_test_split(x_vec,y,test_size=0.2)
from keras.models          import Sequential
from keras.layers          import Dense,LSTM,Bidirectional,Embedding
import tensorflow as tf#定义模型
model = Sequential()
model.add(Embedding(100000, 100))
model.add(LSTM(100, dropout=0.2, recurrent_dropout=0.2))
model.add(Dense(1, activation='sigmoid'))METRICS = [tf.keras.metrics.TruePositives(name='tp'),tf.keras.metrics.FalsePositives(name='fp'),tf.keras.metrics.TrueNegatives(name='tn'),tf.keras.metrics.FalseNegatives(name='fn'), tf.keras.metrics.BinaryAccuracy(name='accuracy'),  # 注意需要根据loss改变tf.keras.metrics.Precision(name='precision'),tf.keras.metrics.Recall(name='recall'),tf.keras.metrics.AUC(name='auc'),tf.keras.metrics.AUC(name='prc', curve='PR'), # precision-recall curve
]model.compile(loss='binary_crossentropy', optimizer='adam', metrics=METRICS)model.summary()
WARNING:tensorflow:Layer lstm will not use cuDNN kernels since it doesn't meet the criteria. It will use a generic GPU kernel as fallback when running on GPU.
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding (Embedding)        (None, None, 100)         10000000  
_________________________________________________________________
lstm (LSTM)                  (None, 100)               80400     
_________________________________________________________________
dense (Dense)                (None, 1)                 101       
=================================================================
Total params: 10,080,501
Trainable params: 10,080,501
Non-trainable params: 0
_________________________________________________________________
epochs = 30
batch_size = 64history = model.fit(X_train, y_train,epochs=epochs,batch_size=batch_size,validation_split=0.2)
Epoch 1/30
43/43 [==============================] - 40s 879ms/step - loss: 0.6343 - tp: 691.0000 - fp: 397.0000 - tn: 1132.0000 - fn: 520.0000 - accuracy: 0.6653 - precision: 0.6351 - recall: 0.5706 - auc: 0.6972 - prc: 0.6335 - val_loss: 0.5641 - val_tp: 194.0000 - val_fp: 76.0000 - val_tn: 304.0000 - val_fn: 112.0000 - val_accuracy: 0.7259 - val_precision: 0.7185 - val_recall: 0.6340 - val_auc: 0.7814 - val_prc: 0.7354
......
Epoch 29/30
43/43 [==============================] - 37s 869ms/step - loss: 0.4196 - tp: 985.0000 - fp: 279.0000 - tn: 1250.0000 - fn: 226.0000 - accuracy: 0.8157 - precision: 0.7793 - recall: 0.8134 - auc: 0.8859 - prc: 0.8316 - val_loss: 0.4754 - val_tp: 244.0000 - val_fp: 74.0000 - val_tn: 306.0000 - val_fn: 62.0000 - val_accuracy: 0.8017 - val_precision: 0.7673 - val_recall: 0.7974 - val_auc: 0.8592 - val_prc: 0.8097
Epoch 30/30
43/43 [==============================] - 37s 855ms/step - loss: 0.4189 - tp: 992.0000 - fp: 301.0000 - tn: 1228.0000 - fn: 219.0000 - accuracy: 0.8102 - precision: 0.7672 - recall: 0.8192 - auc: 0.8852 - prc: 0.8336 - val_loss: 0.4716 - val_tp: 247.0000 - val_fp: 73.0000 - val_tn: 307.0000 - val_fn: 59.0000 - val_accuracy: 0.8076 - val_precision: 0.7719 - val_recall: 0.8072 - val_auc: 0.8589 - val_prc: 0.8040

三、结果分析

import matplotlib as mpl
mpl.rcParams['figure.figsize'] = (16, 8)
colors = plt.rcParams['axes.prop_cycle'].by_key()['color']def plot_metrics(history):metrics = ['accuracy','loss', 'prc', 'precision', 'recall']for n, metric in enumerate(metrics):name = metric.replace("_"," ").capitalize()plt.subplot(2,3,n+1)plt.plot(history.epoch, history.history[metric], color=colors[2], label='Train')plt.plot(history.epoch, history.history['val_'+metric],color=colors[1], linestyle="--", label='Val')plt.xlabel('Epoch',fontsize=14)plt.ylabel(name,fontsize=14)plt.legend()plot_metrics(history)

四、情感预测

# 读取 Word2Vec 并对新输入进行词向量计算
def average_vec(words):# 读取 Word2Vec 模型w2v = Word2Vec.load('w2v_model.pkl')vec = np.zeros(300).reshape((1, 300))for word in words:try:vec += w2v.wv[word].reshape((1, 300))except KeyError:continuereturn vec# 对电影评论进行情感判断
def model_predict(string):# 对评论分词words = jieba.lcut(str(string))words_vec = average_vec(words)# 读取支持向量机模型# model = joblib.load('svm_model.pkl')result = np.argmax(model.predict(words_vec))# 实时返回积极或消极结果if int(result) == 1:# print(string, '[积极]')return "积极"else:# print(string, '[消极]')return "消极"comment_sentiment = []# 用10条数据做测试
for index, row in df.iloc[:10].iterrows():print(row["evaluation"],end=" | ")result = model_predict(row["去除停用词后的数据"])comment_sentiment.append(result)print(result)#将情绪结果与原数据合并为新数据
merged = pd.concat([df, pd.Series(comment_sentiment, name='用户情绪')], axis=1)
# 储存文件
pd.DataFrame.to_csv(merged,'comment_sentiment.csv',encoding="utf-8-sig")
print('done.')
电视不错,不过今年的价格比去年贵了…… | 消极
电视很清晰大品牌值得信赖 | 消极
电视不错,没有坏点,漏光也基本看不出来,看了下电视剧,有点拖影,网上换个接口就好了,暂时没试。京东的预约客服真的要给差评,没经过我同意擅自把送货时间改到星期五,害送货大哥白跑趟。这里要给送货大哥好评,30几度的天气,大中午把电视扛到5楼 | 消极
喇叭太差劲,有点小卡,界面不是很友好,与泰捷盒子差太远了,但播放效果色彩不错,漏光较多 | 消极
好,送货速度快,服务好。.3333333 | 消极
买的第一台微鲸电视 感觉很不错 系统很流畅 清晰度也可以 就是个人感觉遥控的时候会有短暂的延迟 外观各方面还是感觉挺好的 这个价位性价比还可以 | 消极
6.18买的,活动力度大,画面感有待提高,目前没有质量问题,待观察 | 消极
挺好的,看久了下面底部很热,售后安装220元,被兜售一个HIDMI高清线99元,还有58元的有线电视线,一共花了快400块钱,有点被售后忽悠了,后来网上一看两根线最多40快!线上给你优惠,线下想法搞你! | 消极
挺好的 就是开发票太慢了 催了好久到现在还没到呢 | 消极
图电视剧的尺寸大性价比较高,最主要的就是搞活动的时候价格也不是太高啦 | 消极
done.


  相关解决方案