Natu Matu
文章65
标签20
分类4
本站总访问量
本站访客数人次
TXT——文本中的情绪极性

TXT——文本中的情绪极性

言葉之庭,弦外之音

简介

用RNN对文本的情绪倾向进行预测和分类

用Tensorflow进行中文自然语言处理–情感分析

$$f(‘真好喝’)=1$$
$$f(‘太难喝了’)=0$$

环境搭建

需要的库
numpy
jieba
gensim
tensorflow
matplotlib

1
2
3
4
5
6
7
8
9
10
11
12
# 首先加载必用的库
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import re
import jieba # 结巴分词
# gensim用来加载预训练word vector
from gensim.models import KeyedVectors
import warnings
warnings.filterwarnings("ignore")
# 用来解压
import bz2

语料

中文对话情绪语料
sentiment_XS_test.txt包含11577个手动标记的实例(文中提到的XS_test)。 sentiment_XS_30k.txt包含几乎30k个自动标记的实例(文中提到的XS_30k)。

所有数据均来自人机对话日志,并由Jieba工具进行分词。

如果你使用这个数据集,请参阅第12届计算智能与安全国际会议(CIS2016),论文:卷积神经网络的情感分类:大规模中文会话语料库的实验研究。

下载地址

说明:数据来自腾讯微博 1。评测数据全集包括 20 个话题,每个话题采集大约1000条微博,共约20000条微博。数据采用xml格式,已经预先切分好句子。每条句子的所有标注信息都包含在元素的属性中。其中opinionated表示是否观点句,polarity表示句子情感倾向。

下载地址: https://pan.baidu.com/s/1psjysSXpKOEb1ciem7DsRw 密码:7hb4

预处理

预训练词向量
使用北京师范大学中文信息处理研究所与中国人民大学 DBIIR 实验室的研究者开源的”chinese-word-vectors” github链接为:
https://github.com/Embedding/Chinese-Word-Vectors
如果你不知道word2vec是什么,推荐以下一篇文章:
https://zhuanlan.zhihu.com/p/26306795
这里使用了”chinese-word-vectors”知乎Word + Ngram的词向量,可以从上面github链接下载,我先加载预训练模型并进行一些简单测试:

1
2
3
4
5
6
7
8
```
```python
# 请将下载的词向量压缩包放置在根目录 embeddings 文件夹里
# 解压词向量, 有可能需要等待1-2分钟
with open("embeddings/sgns.zhihu.bigram", 'wb') as new_file, open("embeddings/sgns.zhihu.bigram.bz2", 'rb') as file:
decompressor = bz2.BZ2Decompressor()
for data in iter(lambda : file.read(100 * 1024), b''):
new_file.write(decompressor.decompress(data))
1
2
3
# 使用gensim加载预训练中文分词embedding, 有可能需要等待1-2分钟
cn_model = KeyedVectors.load_word2vec_format('embeddings/sgns.zhihu.bigram',
binary=False, unicode_errors="ignore")

词向量模型
在这个词向量模型里,每一个词是一个索引,对应的是一个长度为300的向量,我们今天需要构建的LSTM神经网络模型并不能直接处理汉字文本,需要先进行分次并把词汇转换为词向量,步骤请参考下图,步骤的讲解会跟着代码一步一步来,如果你不知道RNN,GRU,LSTM是什么,我推荐deeplearning.ai的课程,网易公开课有免费中文字幕版,但我还是推荐有习题和练习代码部分的的coursera原版:

1
2
3
4
5
# 由此可见每一个词都对应一个长度为300的向量
#print(type(cn_model))
embedding_dim = cn_model['山东大学'].shape[0]
print('词向量的长度为{}'.format(embedding_dim))
cn_model['山东大学']
1
2
3
4
5
6
7
8
9
10
11
12
# 计算相似度
cn_model.similarity('橘子', '橙子')
# dot('橘子'/|'橘子'|, '橙子'/|'橙子'| )点积计算余弦相似度,向量 /向量范数(np.linalg.norm)
np.dot(cn_model['橘子']/np.linalg.norm(cn_model['橘子']),
cn_model['橙子']/np.linalg.norm(cn_model['橙子']))
# 找出最相近的词,余弦相似度
cn_model.most_similar(positive=['大学'], topn=10)
# 找出不同的词
test_words = '老师 会计师 程序员 律师 医生 老人'
test_words_result = cn_model.doesnt_match(test_words.split())
print('在 '+test_words+' 中:\n不是同一类别的词为: %s' %test_words_result)
cn_model.most_similar(positive=['女人','劈腿'], negative=['男人'], topn=1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 获得样本的索引,样本存放于两个文件夹中,
# 分别为 正面评价'pos'文件夹 和 负面评价'neg'文件夹
# 每个文件夹中有2000个txt文件,每个文件中是一例评价
import os
pos_txts = os.listdir('pos')#正面评价
neg_txts = os.listdir('neg')#负面评价
print(len(pos_txts))
print( '样本总共: '+ str(len(pos_txts) + len(neg_txts)) )
# 现在我们将所有的评价内容放置到一个list里
# 这里和视频课程不一样, 因为很多同学反应按照视频课程里的读取方法会乱码,
# 经过检查发现是原始文本里的编码是gbk造成的,
# 这里我进行了简单的预处理, 以避免乱码
train_texts_orig = []
# 文本所对应的labels, 也就是标记
train_target = []
with open("positive_samples.txt", "r", encoding="utf-8") as f:
lines = f.readlines()
for line in lines:
dic = eval(line)
train_texts_orig.append(dic["text"])
train_target.append(dic["label"])

with open("negative_samples.txt", "r", encoding="utf-8") as f:
lines = f.readlines()
for line in lines:
dic = eval(line)
train_texts_orig.append(dic["text"])
train_target.append(dic["label"])
print(len(train_texts_orig)) #看一下长度

设计模型

分类模型建模

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# 我们使用tensorflow的keras接口来建模
from tensorflow.python.keras.models import Sequential
from tensorflow.python.keras.layers import Dense, GRU, Embedding, LSTM, Bidirectional
from tensorflow.python.keras.preprocessing.text import Tokenizer
from tensorflow.python.keras.preprocessing.sequence import pad_sequences
from tensorflow.python.keras.optimizer_v1 import RMSprop
from tensorflow.python.keras.optimizer_v1 import Adam
from tensorflow.python.keras.callbacks import EarlyStopping, ModelCheckpoint, TensorBoard, ReduceLROnPlateau

# 进行分词和tokenize
# train_tokens是一个长长的list,其中含有4000个小list,对应每一条评价
train_tokens = []
for text in train_texts_orig:
# 去掉标点
text = re.sub("[\s+\.\!\/_,$%^*(+\"\']+|[+——!,。?、~@#¥%……&*()]+", "",text)
# 结巴分词
cut = jieba.cut(text)
# 结巴分词的输出结果为一个生成器
# 把生成器转换为list
cut_list = [ i for i in cut ]
#索引化
for i, word in enumerate(cut_list):
try:
# 将词转换为索引index
cut_list[i] = cn_model.key_to_index[word]
except KeyError:
# 如果词不在字典中,则输出0
cut_list[i] = 0
train_tokens.append(cut_list)
# 获得所有tokens的长度
num_tokens = [ len(tokens) for tokens in train_tokens ]
num_tokens = np.array(num_tokens)
print(num_tokens)
# 平均tokens的长度
print(np.mean(num_tokens))
# 最长的评价tokens的长度
print(np.max(num_tokens))
# 查看样本分布
plt.hist(np.log(num_tokens), bins = 100)
plt.xlim((0,10))
plt.ylabel('number of tokens')
plt.xlabel('length of tokens')
plt.title('Distribution of tokens length')
plt.show()
{% asset_img output.png This is the image of data_lenth %}
# 取tokens平均值并加上两个tokens的标准差,
# 假设tokens长度的分布为正态分布,则max_tokens这个值可以涵盖95%左右的样本
max_tokens = np.mean(num_tokens) + 2 * np.std(num_tokens)
max_tokens = int(max_tokens)
max_tokens
# 取tokens的长度为236时,大约95%的样本被涵盖
# 我们对长度不足的进行padding,超长的进行修剪
np.sum( num_tokens < max_tokens ) / len(num_tokens)
# 用来将tokens转换为文本
def reverse_tokens(tokens):
text = ''
for i in tokens:
if i != 0:
text = text + cn_model.index_to_key[i]
else:
text = text + ' '
return text
reverse = reverse_tokens(train_tokens[0]) #试着恢复文本

准备词嵌入矩阵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 只使用前20000个词
num_words = 50000
# 初始化embedding_matrix,之后在keras上进行应用
embedding_matrix = np.zeros((num_words, embedding_dim))
# embedding_matrix为一个 [num_words,embedding_dim] 的矩阵
# 维度为 50000 * 300
for i in range(num_words):
#print(cn_model[cn_model.index_to_key[i]])
embedding_matrix[i,:] = cn_model[cn_model.index_to_key[i]]
embedding_matrix = embedding_matrix.astype('float32')
print(embedding_matrix[1,1])
# 检查index是否对应,
# 输出300意义为长度为300的embedding向量一一对应
np.sum( cn_model[cn_model.index_to_key[333]] == embedding_matrix[333] )
# embedding_matrix的维度,
# 这个维度为keras的要求,后续会在模型中用到
print(embedding_matrix.shape)
# 进行padding和truncating, 输入的train_tokens是一个list
# 返回的train_pad是一个numpy array
train_pad = pad_sequences(train_tokens, maxlen=max_tokens,
padding='pre', truncating='pre')
# 超出五万个词向量的词用0代替
train_pad[ train_pad>=num_words ] = 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#样本分割
# 进行训练和测试样本的分割
from sklearn.model_selection import train_test_split
# 90%的样本用来训练,剩余10%用来测试
X_train, X_test, y_train, y_test = train_test_split(train_pad,
train_target,
test_size=0.1,
random_state=12)#打乱顺序
# 查看训练样本,确认无误
print(reverse_tokens(X_train[30]))
print('class: ',y_train[30])
# 用LSTM对样本进行分类
model = Sequential()
# 模型第一层为embedding
model.add(Embedding(num_words,
embedding_dim,
weights=[embedding_matrix],
input_length=max_tokens,
trainable=False))
#载入模型
model.add(Bidirectional(LSTM(units=64, return_sequences=True)))
model.add(LSTM(units=16, return_sequences=False))
import tensorflow as tf
model.add(Dense(1, activation='sigmoid'))
# 我们使用adam以0.001的learning rate进行优化
optimizer = tf.keras.optimizers.Adam(lr=1e-3)
model.compile(loss='binary_crossentropy',
optimizer=optimizer,
metrics=['accuracy'])#使用默认优化器
# 建立一个权重的存储点
path_checkpoint = 'sentiment_checkpoint.keras'
checkpoint = ModelCheckpoint(filepath=path_checkpoint, monitor='val_loss',
verbose=1, save_weights_only=True,
save_best_only=True)
# 尝试加载已训练模型
try:
model.load_weights(path_checkpoint)
except Exception as e:
print(e)
# 定义early stoping如果3个epoch内validation loss没有改善则停止训练
earlystopping = EarlyStopping(monitor='val_loss', patience=5, verbose=1)
# 自动降低learning rate
lr_reduction = ReduceLROnPlateau(monitor='val_loss',
factor=0.1, min_lr=1e-8, patience=0,
verbose=1)
# 定义callback函数
callbacks = [
earlystopping,
checkpoint,
lr_reduction
]
# 开始训练
model.fit(X_train, y_train,
validation_split=0.1,
epochs=20,
batch_size=128,
callbacks=callbacks)

模型预测

预测函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def predict_sentiment(text):
print(text)
# 去标点
text = re.sub("[\s+\.\!\/_,$%^*(+\"\']+|[+——!,。?、~@#¥%……&*()]+", "",text)
# 分词
cut = jieba.cut(text)
cut_list = [ i for i in cut ]
# tokenize
for i, word in enumerate(cut_list):
try:
cut_list[i] = cn_model.key_to_index[word]
if cut_list[i] >= 50000:
cut_list[i] = 0
except KeyError:
cut_list[i] = 0
# padding
tokens_pad = pad_sequences([cut_list], maxlen=max_tokens,
padding='pre', truncating='pre')
# 预测
result = model.predict(x=tokens_pad)
coef = result[0][0]
if coef >= 0.5:
print('是一例正面评价','output=%.2f'%coef)
else:
print('是一例负面评价','output=%.2f'%coef)
# 测试预测结果
test_list = [
'酒店设施不是新的,服务态度很不好',
'酒店卫生条件非常不好',
'床铺非常舒适',
'房间很凉,不给开暖气',
'房间很凉爽,空调冷气很足',
'酒店环境不好,住宿体验很不好',
'房间隔音不到位' ,
'晚上回来发现没有打扫卫生',
'因为过节所以要我临时加钱,比团购的价格贵'
]
for text in test_list:
predict_sentiment(text)
y_pred = model.predict(X_test)
y_pred = y_pred.T[0]
y_pred = [1 if p>= 0.5 else 0 for p in y_pred]
y_pred = np.array(y_pred)

补充:查询分类错误的索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
y_actual = np.array(y_test)
# 找出错误分类的索引
misclassified = np.where( y_pred != y_actual )[0]
# 输出所有错误分类的索引
print(len(misclassified))
print(len(X_test))
# 我们来找出错误分类的样本看看
idx=101
print(reverse_tokens(X_test[idx]))
print('预测的分类', y_pred[idx])
print('实际的分类', y_actual[idx])
idx=1
print(reverse_tokens(X_test[idx]))
print('预测的分类', y_pred[idx])
print('实际的分类', y_actual[idx])
本文作者:Natu Matu
本文链接:https://631212502.github.io/2022/04/14/%E4%B8%AD%E6%96%87NLP-%E6%83%85%E6%84%9F%E5%88%86%E6%9E%90/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可
×