本文通過TensorFlow中的LSTM神經網絡方法進行中文情感分析
需要依賴的庫
- numpy
- jieba
- gensim
- tensorflow
- matplotlib
- sklearn
1.導入依賴包
# 導包
import re
import os
import tensorflow as tf
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import jieba
from gensim.models import KeyedVectors
from sklearn.model_selection import train_test_split
2.加載詞向量
可以使用自己的詞向量,也可以使用網上開源的詞向量我這里使用的是北京師范大學中文信息處理研究所與中國人民大學DBIIR實驗室的研究者開源的"chinese-word-vectors"
github鏈接為: https://github.com/Embedding/Chinese-Word-Vectors
選擇一個下載到本地,放到項目下的vectors目錄下
# 使用gensim加載預訓練中文分詞,需要等待一段時間
cn_model = KeyedVectors.load_word2vec_format('vectors/sgns.zhihu.bigram',
binary=False, unicode_errors='ignore')
3.讀取訓練語料
我這里使用的是譚松波老師的酒店評論語料
鏈接:https://pan.baidu.com/s/14hhPRrD96mkBQEbc_7Hqag 提取碼:cx5q
訓練樣本分別被放置在這兩個文件夾里面pos和neg,每個文件夾里面有3000個txt文件,每個文件內有一段評語,共有6000個訓練樣本
下載好后,把pos和neg目錄解壓到data目錄下
## 4.讀取訓練數據
pos_file_list = os.listdir('data/pos')
neg_file_list = os.listdir('data/neg')
pos_file_list = [f'data/pos/{x}' for x in pos_file_list]
neg_file_list = [f'data/neg/{x}' for x in neg_file_list]
pos_neg_file_list = pos_file_list + neg_file_list
# 讀取所有的文本,放入到x_train,前3000是正向樣本,后3000負向樣本
x_train = []
for file in pos_neg_file_list:
with open(file, 'r', encoding='utf-8') as f:
text = f.read().strip()
pass
x_train.append(text)
pass
5.生成對應的標簽
x_train = np.array(x_train)
y_train = np.concatenate((np.ones(3000), np.zeros(3000))) # 生成標簽
# 打亂訓練樣本和標簽的順序
np.random.seed(116)
np.random.shuffle(x_train)
np.random.seed(116)
np.random.shuffle(y_train)
6.對訓練數據進行分詞操作
x_train_tokens = []
for text in x_train:
# 使用jieba進行分詞
cut = jieba.cut(text)
cut_list = [x for x in cut]
for i,word in enumerate(cut_list):
try:
# 將詞轉換為索引index
cut_list[i] = cn_model.vocab[word].index
pass
except KeyError:
# 如果詞不在字典中,則輸出0
cut_list[i] = 0
pass
pass
x_train_tokens.append(cut_list)
pass
7.索引長度標准化
因為每段評語的長度是不一樣的,我們如果單純取最長的一個評語,並把其他評語填充成同樣的長度,這樣十分浪費計算資源,所以我們去一個折衷的長度
# 獲取每段語句的長度,並畫圖展示
tokens_count = [len(tokens) for tokens in x_train_tokens]
tokens_count.sort(reverse=True)
# 畫圖查看詞的長度分布
plt.plot(tokens_count)
plt.ylabel('tokens count')
plt.xlabel('tokens length')
plt.show()
# 可以看出大部分詞的長度都是在500以下的
# 當tokens長度分布滿足正態分布的時候,
# 可以使用 取tokens的平均值並且加上兩個tokens的標准差,來選用tokens的長度
tokens_length = np.mean(tokens_count) + 2 * np.std(tokens_count)
print(tokens_length)
輸出:297.3980831340084
# 可以看到當tokens的長度為297.3980831340084,大約95%的樣本被覆蓋,
# 我們需要對長度不足的tokens進行padding,超長的進行修剪
np.sum(tokens_count < tokens_length) / len(tokens_count)
輸出:0.9545
8.定義把tokens轉換回文本的方法
# 定義一個把tokens轉換成文本的方法
def reverse_tokens(tokens):
text = ''
for index in tokens:
if index != 0:
text = text + cn_model.index2word[index]
else:
text = text + ''
pass
return text
pass
# 測試
print(reverse_tokens(x_train_tokens[0]))
print(y_train[0])
# 輸出:酒店的服務簡直不好程度排第一!我住1702房間漏風。調整到1710房間!這個房間的空調是壞的!半夜給了。調整到房間明顯比1702和1710房間小的很多而且房間不能夠上網我已經被折磨的沒有力氣在調整房間了。想躺在床上看看電視我的天啊!看不了!總之我對這次攜程的服務比較滿意對該遼寧省沈陽市城市酒店提供的客房服務是特別特別特別的不滿意!我希望攜程能夠好好考慮一下自己的加盟酒店總是這樣我們還怎么相信攜程這樣的品牌。總體來說我很郁悶!也特別的傷心!
# 0.0
9.准備Embedding Matrix
現在我們來為模型准備詞向量embedding matrix(詞向量矩陣),根據keras的要求,我們需要准備一個維度為(numwords, 300)的矩陣,每一個詞匯都用一個長度為300的向量表示。
注意我們只選擇使用前50k個使用頻率最高的詞,在這個預訓練詞向量模型中,一共有260萬詞匯量,如果全部使用在分類問題上會很浪費計算資源,因為我們的訓練樣本很小,一種只用6k,如果我們有100k、200k甚至更多的訓練樣本時,在分類問題上可以考慮減少使用詞匯量
embedding_matrix = np.zeros((50000, 300))
for i in range(50000):
embedding_matrix[i, :] = cn_model[cn_model.index2word[i]]
pass
embedding_matrix = embedding_matrix.astype('float32')
# 檢查index是否對應
# 輸出300意義為長度為300的embedding向量一一對應
print(np.sum(cn_model[cn_model.index2word[300]] == embedding_matrix[300]))
# 輸出:300
10.對訓練樣本進行padding(填充)和truncating(修剪)
我們把文本轉換為tokens(索引)之后,每一串索引的長度並不相等,所以為了方便模型的訓練,我們需要把索引的長度標准化,上面我們選擇了297這個可以覆蓋95%訓練樣本的長度,接下來我們進行padding和truncating,我們一般采用'pre'的方法,這會在文本索引的前面填充0,因為根據一些研究材料中的實踐,如果在文本索引后填充0的話,會對模型造成一些不良影響
x_train_tokens_pad = tf.keras.preprocessing.sequence.pad_sequences(x_train_tokens,
maxlen=int(tokens_length),
padding='pre',
truncating='pre')
# 超出五萬個詞向量的詞用0代替
train_pad[train_pad >= num_words] = 0
# 可見padding之后前面的tokens全變成0,文本在最后面
print(train_pad[33])
11.使用sklearn的train_test_split進行拆分訓練集和測試集
# 使用90%進行訓練,10%進行測試
x_tokens_train, x_tokens_test, y_tokens_train, y_tokens_test = train_test_split(
x_train_tokens_pad,
y_train,
test_size=0.1,
random_state=12
)
# 也可手動拆分
# x_tokens_train = x_train_tokens_pad[:-int(x_train_tokens_pad.shape[0] / 10)]
# x_tokens_test = x_train_tokens_pad[-int(x_train_tokens_pad.shape[0] / 10):]
# y_tokens_train = y_train[:-int(y_train.shape[0] / 10)]
# y_tokens_test = y_train[-int(y_train.shape[0] / 10):]
12.構建模型
# 構建模型
model = tf.keras.models.Sequential([
tf.keras.layers.Embedding(50000,300,
weights=[embedding_matrix],
input_length=int(tokens_length),
trainable=False
),
tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(units=64, return_sequences=True)),
tf.keras.layers.LSTM(16, return_sequences=False),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dense(2, activation='softmax')
])
model.compile(optimizer='Adam',
loss='sparse_categorical_crossentropy',
metrics=['sparse_categorical_accuracy']
)
13.進行訓練
# 訓練20輪,每輪都進行測試集的驗證,使用1%用來測試集,每批128
history = model.fit(x_tokens_train,
y_tokens_train,
batch_size=128,
epochs=20,
validation_split=0.1,
validation_freq=1
)
14.查看網絡結構
model.summary()
15.使用測試集,驗證模型准確率
result = model.evaluate(x_tokens_test, y_tokens_test)
print(f'Accuracy : {result[1]}')
可以看出我們模型的准確率達到了90%
16.畫出訓練的loss(訓練誤差)、val_loss(測試誤差)和sparse_categorical_accuracy(訓練准確度)、val_sparse_categorical_accuracy(測試准確度)
plt.plot(history.history['loss'],label="$Loss$")
plt.plot(history.history['val_loss'],label='$val_loss$')
plt.title('Loss')
plt.xlabel('epoch')
plt.ylabel('num')
plt.legend()
plt.show()
plt.plot(history.history['sparse_categorical_accuracy'],label="$sparse_categorical_accuracy$")
plt.plot(history.history['val_sparse_categorical_accuracy'],label='$val_sparse_categorical_accuracy$')
plt.title('Accuracy')
plt.xlabel('epoch')
plt.ylabel('num')
plt.legend()
plt.show()
17.准備一些文本,用訓練好的模型進行測試
def predict_sentiment(text):
print(text)
# 分詞
cut = jieba.cut(text)
cut_list = [x for x in cut]
for i, word in enumerate(cut_list):
try:
cut_list[i] = cn_model.vocab[word].index
except KeyError:
cut_list[i] = 0
pass
# padding
tokens_pad = tf.keras.preprocessing.sequence.pad_sequences([cut_list],
maxlen=int(tokens_length),
padding='pre',
truncating='pre')
# 大於50000的歸0,不歸0模型的使用會報錯
tokens_pad[tokens_pad >= 50000] = 0
return tokens_pad
pass
test_list = [
'酒店設施不是新的,服務態度很不好',
'酒店衛生條件非常不好',
'床鋪非常舒適',
'房間很冷,還不給開暖氣',
'房間很涼爽,空調冷氣很足',
'酒店環境不好,住宿體驗很不好',
'房間隔音不到位' ,
'晚上回來發現沒有打掃衛生,心情不好',
'因為過節所以要我臨時加錢,比團購的價格貴',
'房間很溫馨,前台服務很好,'
]
for text in test_list:
try:
tokens_pad = predict_sentiment(text)
result = model.predict(x=tokens_pad)
print(result)
if result[0][0] <= result[0][1]:
print(f'正:{result[0][1]}')
else:
print(f'負:{result[0][0]}')
except Exception as ex:
print(ex.args)
pass
pass