- 📍本文选自专栏:《自然语言处理NLP-实例教程》
- 📍推荐精品专栏:《深度学习100例》
一句话介绍LSTM,它是RNN的进阶版,如果说RNN的最大限度是理解一句话,那么LSTM的最大限度则是理解一段话,详细介绍如下:
LSTM,全称为长短期记忆网络(Long Short Term Memory networks),是一种特殊的RNN,能够学习到长期依赖关系。LSTM由Hochreiter & Schmidhuber (1997)提出,许多研究者进行了一系列的工作对其改进并使之发扬光大。LSTM在许多问题上效果非常好,现在被广泛使用。
所有的循环神经网络都有着重复的神经网络模块形成链的形式。在普通的RNN中,重复模块结构非常简单,其结构如下:
LSTM避免了长期依赖的问题。可以记住长期信息!LSTM内部有较为复杂的结构。能通过门控状态来选择调整传输的信息,记住需要长时间记忆的信息,忘记不重要的信息,其结构如下:
神经网络程序的基本流程如下
前期工作
导入数据
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import font_manager
from itertools import accumulate
# 支持中文
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
df = pd.read_csv('data_single.csv')
df.head()
evaluation | label | |
---|---|---|
0 | 用了一段时间,感觉还不错,可以 | 正面 |
1 | 电视非常好,已经是家里的第二台了。第一天下单,第二天就到本地了,可是物流的人说车坏了,一直催… | 正面 |
2 | 电视比想象中的大好多,画面也很清晰,系统很智能,更多功能还在摸索中 | 正面 |
3 | 不错 | 正面 |
4 | 用了这么多天了,感觉还不错。夏普的牌子还是比较可靠。希望以后比较耐用,现在是考量质量的时候。 | 正面 |
数据分析
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))
df['length'] = df['evaluation'].apply(lambda x: len(x))
df.head()
evaluation | label | length | |
---|---|---|---|
0 | 用了一段时间,感觉还不错,可以 | 正面 | 15 |
1 | 电视非常好,已经是家里的第二台了。第一天下单,第二天就到本地了,可是物流的人说车坏了,一直催… | 正面 | 97 |
2 | 电视比想象中的大好多,画面也很清晰,系统很智能,更多功能还在摸索中 | 正面 | 33 |
3 | 不错 | 正面 | 2 |
4 | 用了这么多天了,感觉还不错。夏普的牌子还是比较可靠。希望以后比较耐用,现在是考量质量的时候。 | 正面 | 46 |
len_df = df.groupby('length').count()
sent_length = len_df.index.tolist()
sent_freq = len_df['evaluation'].tolist()
# 绘制句子长度及出现频数统计图
plt.bar(sent_length, sent_freq)
plt.title("句子长度及出现频数统计图")
plt.xlabel("句子长度")
plt.ylabel("句子长度出现的频数")
plt.show()
# 绘制句子长度累积分布函数(CDF)
sent_pentage_list = [(count/sum(sent_freq)) for count in accumulate(sent_freq)]
# 绘制CDF
plt.plot(sent_length, sent_pentage_list)
# 寻找分位点为quantile的句子长度
quantile = 0.9
for length, per in zip(sent_length, sent_pentage_list):
if round(per, 2) == quantile:
index = length
break
print("\n分位点为%s的句子长度:%d。" % (quantile, index))
# 绘制句子长度累积分布函数图
plt.plot(sent_length, sent_pentage_list)
plt.hlines(quantile, 0, index, colors="g", linestyles="--")
plt.vlines(index, 0, quantile, colors="g", linestyles="--")
plt.text(0, quantile, str(quantile))
plt.text(index, 0, str(index))
plt.title("句子长度累积分布函数图")
plt.xlabel("句子长度")
plt.ylabel("句子长度累积频率")
plt.show()
分位点为0.9的句子长度:172。
数据预处理
随机播放数据
将正文本数据与负文本数据混洗
df = df.sample(frac=1)
df.head()
evaluation | label | length | |
---|---|---|---|
1595 | 电视蛮好的!没想到很快就收到了!赞一个! 晒单: 收起 向左转 向右转 | 正面 | 35 |
2305 | 根本不用预约呀,1198直接买的,非传统品牌,心情坎坷不安,咬牙下单试试,收货验过屏幕没事就… | 负面 | 134 |
3831 | 屏幕不错,但是声音一般啊,看片还得配音响 | 负面 | 20 |
2119 | 价格略比去年高,但画面效果不错,独角鲸服务到位 | 负面 | 23 |
3687 | 这个电视毫无智能语音体验,只有一些简单的操作,不该贪几百块的便宜,买小米就好了,开关机不灵敏… | 负面 | 166 |
分词
import jieba
word_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.434 seconds.
Prefix dict has been built successfully.
evaluation | label | length | words | |
---|---|---|---|---|
1595 | 电视蛮好的!没想到很快就收到了!赞一个! 晒单: 收起 向左转 向右转 | 正面 | 35 | [电视, 蛮, 好, 的, !, 没想到, 很快, 就, 收到, 了, !, 赞, 一个, … |
2305 | 根本不用预约呀,1198直接买的,非传统品牌,心情坎坷不安,咬牙下单试试,收货验过屏幕没事就… | 负面 | 134 | [根本, 不用, 预约, 呀, ,, 1198, 直接, 买, 的, ,, 非传统, 品牌,… |
3831 | 屏幕不错,但是声音一般啊,看片还得配音响 | 负面 | 20 | [屏幕, 不错, ,, 但是, 声音, 一般, 啊, ,, 看片, 还, 得配, 音响] |
2119 | 价格略比去年高,但画面效果不错,独角鲸服务到位 | 负面 | 23 | [价格, 略, 比, 去年, 高, ,, 但, 画面, 效果, 不错, ,, 独角, 鲸, … |
3687 | 这个电视毫无智能语音体验,只有一些简单的操作,不该贪几百块的便宜,买小米就好了,开关机不灵敏… | 负面 | 166 | [这个, 电视, 毫无, 智能, 语音, 体验, ,, 只有, 一些, 简单, 的, 操作,… |
删除停用词
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.head()
evaluation | label | length | words | 去除停用词后的数据 | |
---|---|---|---|---|---|
1595 | 电视蛮好的!没想到很快就收到了!赞一个! 晒单: 收起 向左转 向右转 | 正面 | 35 | [电视, 蛮, 好, 的, !, 没想到, 很快, 就, 收到, 了, !, 赞, 一个, … | [电视, 蛮, 好, 没想到, 很快, 收到, !, 赞, !, , 晒单, , 收起,… |
2305 | 根本不用预约呀,1198直接买的,非传统品牌,心情坎坷不安,咬牙下单试试,收货验过屏幕没事就… | 负面 | 134 | [根本, 不用, 预约, 呀, ,, 1198, 直接, 买, 的, ,, 非传统, 品牌,… | [根本, 不用, 预约, 1198, 直接, 买, 非传统, 品牌, 心情, 坎坷, 不安,… |
3831 | 屏幕不错,但是声音一般啊,看片还得配音响 | 负面 | 20 | [屏幕, 不错, ,, 但是, 声音, 一般, 啊, ,, 看片, 还, 得配, 音响] | [屏幕, 不错, 声音, 看片, 得配, 音响] |
2119 | 价格略比去年高,但画面效果不错,独角鲸服务到位 | 负面 | 23 | [价格, 略, 比, 去年, 高, ,, 但, 画面, 效果, 不错, ,, 独角, 鲸, … | [价格, 略, 去年, 高, 画面, 效果, 不错, 独角, 鲸, 服务到位] |
3687 | 这个电视毫无智能语音体验,只有一些简单的操作,不该贪几百块的便宜,买小米就好了,开关机不灵敏… | 负面 | 166 | [这个, 电视, 毫无, 智能, 语音, 体验, ,, 只有, 一些, 简单, 的, 操作,… | [电视, 毫无, 智能, 语音, 体验, 简单, 操作, 不该, 贪, 几百块, 便宜, 买… |
Word2vec处理
Word2vec是一个用来产生词向量的模型。是一个将单词转换成向量形式的工具。 通过转换,可以把对文本内容的处理简化为向量空间中的向量运算,计算出向量空间上的相似度,来表示文本语义上的相似度。
from gensim.models.word2vec import Word2Vec
import numpy as np
x = 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:
continue
return vec
# 将词向量保存为 Ndarray
x_vec = np.concatenate([average_vec(z) for z in x])
#多类标签的one-hot展开
y = pd.get_dummies(df['label']).values
y[:5]
array([[1, 0],
[0, 1],
[0, 1],
[0, 1],
[0, 1]], dtype=uint8)
划分训练集和测试集
from sklearn.model_selection import train_test_split
X_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
#定义模型
model = Sequential()
model.add(Embedding(100000, 100))
model.add(LSTM(100, dropout=0.2, recurrent_dropout=0.2))
model.add(Dense(2, activation='softmax'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
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, 2) 202
=================================================================
Total params: 10,080,602
Trainable params: 10,080,602
Non-trainable params: 0
_________________________________________________________________
epochs = 100
batch_size = 64
history = model.fit(X_train,
y_train,
epochs=epochs,
batch_size=batch_size,
validation_split=0.2)
Epoch 1/100
43/43 [==============================] - 39s 868ms/step - loss: 0.6140 - accuracy: 0.7051 - val_loss: 0.5903 - val_accuracy: 0.6910
Epoch 2/100
43/43 [==============================] - 37s 860ms/step - loss: 0.5555 - accuracy: 0.7288 - val_loss: 0.5735 - val_accuracy: 0.7172
Epoch 3/100
43/43 [==============================] - 37s 858ms/step - loss: 0.5421 - accuracy: 0.7336 - val_loss: 0.5713 - val_accuracy: 0.7099
......
43/43 [==============================] - 36s 848ms/step - loss: 0.1602 - accuracy: 0.9409 - val_loss: 0.5864 - val_accuracy: 0.8338
Epoch 99/100
43/43 [==============================] - 36s 849ms/step - loss: 0.1757 - accuracy: 0.9292 - val_loss: 0.5823 - val_accuracy: 0.8338
Epoch 100/100
43/43 [==============================] - 36s 847ms/step - loss: 0.1410 - accuracy: 0.9467 - val_loss: 0.6176 - val_accuracy: 0.8222
情绪预测
# 读取 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:
continue
return 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 "消极"
# 用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.')
我们的最终预测如下:
电视蛮好的!没想到很快就收到了!赞一个! 晒单: 收起 向左转 向右转 | 积极
根本不用预约呀,1198直接买的,非传统品牌,心情坎坷不安,咬牙下单试试,收货验过屏幕没事就自己装,4个螺丝而已。头三次开机好慢啊,最长的几乎5分钟,赶紧拨打客服电话,可能是系统更新,后来就好了,待机开机飞快,音画满意。总算安心了。连接移动硬盘居然连原盘也能放,强啊! | 积极
屏幕不错,但是声音一般啊,看片还得配音响 | 消极
价格略比去年高,但画面效果不错,独角鲸服务到位 | 消极
这个电视毫无智能语音体验,只有一些简单的操作,不该贪几百块的便宜,买小米就好了,开关机不灵敏,边框不是金属的,底座是我见到的最简陋的 ,屏幕背景暗淡,根本看不清,也不知道设计软件的怎么想的 ,大半夜起来12点整抢的,被告知没有挂架送,已送完,其他赠品也没兑现,建议大家考虑下别的品牌吧 优点是 便宜、配置高 买电视还是要买听话的电视 | 积极
价格比之前买的高了些 送货速度和服务值得表扬 机器色彩操作界面部分发白的厉害 设置了也一样 看片子尚可 勉强接受 | 积极
电视内容丰富,画质清晰。语音搜索功能不够完善,反应慢1-2秒。希望后续更新能够再加强。 | 积极
电视很不错,价格也实惠! | 积极
速度快还包邮,已经看上了,一年会员也已经领到(需要把系统升级到最新版本才能领取哟);速度刷刷的,并没有说什么开机5分钟这一说儿;模拟信号接口有些鸡肋,谁用这种电视看模拟信号啊,usb3.0也是棒棒哒;内存16g以电视来说足够用啦;屏幕完美,就是左右有些许漏光的情况(在电视机正面看比较明显稍微一歪头就纯黑了,我不专业不知道说的对不对),总之还是物有所值的! | 消极
之前的电视坏了,今天刚换的货!希望不要有问题! | 消极
done.
版权声明:本文为博主K同学啊原创文章,版权归属原作者,如果侵权,请联系我们删除!
原文链接:https://blog.csdn.net/qq_38251616/article/details/123138717