情感分析(Sentiment Analysis)是自然語言處理里面比較高階的任務之一。仔細思考一下,這個任務的究極目標其實是想讓計算機理解人類的情感世界。我們自己都不一定能完全控制和了解自己的情感,更別說機器了。
不過在人工智能的認知智能階段(人工智能三階段——計算智能,感知智能,認知智能),商家還是可以用它來做一些商品或服務的評論分析,繼而有效地去優化商品或服務,為消費者們提供更好用戶體驗。
情感分析任務簡介
情感分析任務其實是個分類任務,給模型輸入一句話,讓它判斷這句話的情感是積極的,消極的,還是中性的。例子如下:
輸入:的確是專業,用心做,出品方面都給好評。
輸出:2
輸出可以是[0,1,2]其中一個,0表示情感消極,1表示情感中性,2表示情感積極。
情感分析這個任務還有一個升級版——細膩度的情感分析。升級版希望模型不僅能識別出情感的好壞,而且還希望模型能識別出是由于什么原因導致這種情感發生。舉個例子,"這家餐廳的地理位置不錯,可惜菜不怎么好吃",我們就需要識別出,在地理位置這個aspect上情感是積極的,而在菜的味道這個aspect上情感是消極的。聽起來是不是很難,所以實戰部分我只簡單介紹一下麻瓜版的情感分析任務——簡單的分類。
情感分析算法簡介
分類任務的算法,想必大家都很熟悉:SVM,Logistic,Tree等。可是對于文本分類來說,最重要的是如何將一句話的映射到向量空間,同時保持其語義特征。所以文本的向量化表示是最最重要的一個環節。而文本的向量化就是涉及到Word Embedding技術和深度學習(Deep Learning)技術。
Word Embedding指的是把文本轉換成計算機能處理的向量,而其中難點的是:將文本向量化時如何保持句子原有的語義。早期word embedding使用的是Bag of Words,TF-IDF等,這些算法有個共同的特點:就是沒有考慮語序以及上下文關系。而近幾年發展出來的Word2Vector ,Glove等考慮到了文本的上下文關系。今年NLP領域大放異彩的BERT就是在文本向量化上做出了重大的突破。
人工特征的挖掘是個極為費腦費時的過程,深度學習模型可以將特征工程自動化,通過神經網絡自動做特征的表示學習。在NLP領域中,RNN(LSTM,GRU),CNN,Transformer等各路深度學習模型各顯神通,憑借他們強大的特征表示能力,在很多任務中都吊打人工特征(吹得 有些夸張了,沒收住)。不過人工特征有時還是很重要的。
項目實戰
本次的項目實戰的總體架構可分為兩個步驟:
(1)采用Word2Vector技術去訓練詞向量;
(2)采用BiLSTM去做特征的表示學習。
其項目架構如下圖所示:
數據讀取
數據格式如下:一句評論后面標記一個label,0表示消極情感,1表示中性情感,2表示積極情感。
這里針對筆者自己的數據集定義了一個數據讀入函數。
import numpy as np
from gensim.models.word2vec import Word2Vec
from gensim.corpora.dictionary import Dictionary
from gensim import models
import pandas as pd
import jieba
import logging
from keras import Sequential
from keras.preprocessing.sequence import pad_sequences
from keras.layers import Bidirectional,LSTM,Dense,Embedding,Dropout,Activation,Softmax
from sklearn.model_selection import train_test_split
from keras.utils import np_utils
def read_data(data_path):
senlist = []
labellist = []
with open(data_path, "r",encoding='gb2312',errors='ignore') as f:
for data in f.readlines():
data = data.strip()
sen = data.split("\t")[2]
label = data.split("\t")[3]
if sen != "" and (label =="0" or label=="1" or label=="2" ) :
senlist.append(sen)
labellist.append(label)
else:
pass
assert(len(senlist) == len(labellist))
return senlist ,labellist
sentences,labels = read_data("data_train.csv")
數據讀入之后,得到一個所有評論的sentences列表 ,和一個與之一一對應的labels列表。
sentences[1] :烤鴨還是不錯的,別的菜沒什么特殊的
labels[1] :1
訓練詞向量
將所有的評論文本數據用來訓練詞向量,這里使用的gensim中的Word2Vec,原理是的Skip-gram。這里對詞向量的原理不多介紹,總之,這一步將一個詞映射成一個100維的向量,并且考慮到了上下文的語義。這里直接將上一部得到的句子列表傳給train_word2vec函數就可以了,同時需要定義一個詞向量文件保存路徑。模型保存后,以后使用就不需要再次訓練,直接加載保存好的模型就可以啦。
def train_word2vec(sentences,save_path):
sentences_seg = []
sen_str = "\n".join(sentences)
res = jieba.lcut(sen_str)
seg_str = " ".join(res)
sen_list = seg_str.split("\n")
for i in sen_list:
sentences_seg.append(i.split())
print("開始訓練詞向量")
# logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
model = Word2Vec(sentences_seg,
size=100, # 詞向量維度
min_count=5, # 詞頻閾值
window=5) # 窗口大小
model.save(save_path)
return model
model = train_word2vec(sentences,'word2vec.model')
數據預處理
這里定義了一些數據處理和變換方法。
def generate_id2wec(word2vec_model):
gensim_dict = Dictionary()
gensim_dict.doc2bow(model.wv.vocab.keys(), allow_update=True)
w2id = {v: k + 1 for k, v in gensim_dict.items()} # 詞語的索引,從1開始編號
w2vec = {word: model[word] for word in w2id.keys()} # 詞語的詞向量
n_vocabs = len(w2id) + 1
embedding_weights = np.zeros((n_vocabs, 100))
for w, index in w2id.items(): # 從索引為1的詞語開始,用詞向量填充矩陣
embedding_weights[index, :] = w2vec[w]
return w2id,embedding_weights
def text_to_array(w2index, senlist): # 文本轉為索引數字模式
sentences_array = []
for sen in senlist:
new_sen = [ w2index.get(word,0) for word in sen] # 單詞轉索引數字
sentences_array.append(new_sen)
return np.array(sentences_array)
def prepare_data(w2id,sentences,labels,max_len=200):
X_train, X_val, y_train, y_val = train_test_split(sentences,labels, test_size=0.2)
X_train = text_to_array(w2id, X_train)
X_val = text_to_array(w2id, X_val)
X_train = pad_sequences(X_train, maxlen=max_len)
X_val = pad_sequences(X_val, maxlen=max_len)
return np.array(X_train), np_utils.to_categorical(y_train) ,np.array(X_val), np_utils.to_categorical(y_val)
獲取詞向量矩陣和詞典
w2id,embedding_weights = generate_id2wec(model)
這一步主要是為了拿到傳給后續情感分析模型的詞典(w2id)和詞向量矩陣embedding_weights,
w2id格式如下:{
...
'一兩天': 454,
'一兩年': 455,
'一兩次': 456,
'一個': 457,
'一個個': 458,
'一個勁': 459,
...
'不一會': 984,
'不上': 985,
'不下': 986,
'不嚴': 987,
'不為過': 988,
'不久': 989,
}
embedding_weights格式如下:
[[ 0. , 0. , 0. , ..., 0. ,
0. , 0. ],
[-1.1513499 , -0.00520114, 1.65645397, ..., 0.50586915,
-0.03466858, 0.84113288],
[ 0.01824509, -0.23613754, -0.47191045, ..., -0.16491373,
-0.25222906, -0.00384654],
...,
[ 0.10879639, 0.05459598, -0.02946772, ..., -0.17389177,
0.10144144, 0.21539673]]
這個矩陣保存了上面通過Word2Vector方法訓練的詞向量,每個詞通過其在詞典(w2id)中的index索引到對應得詞向量,此矩陣將作為參數傳給后續的情感分析模型。
數據變換
x_train,y_trian, x_val , y_val = prepare_data(w2id,sentences,labels,200)
將數據變換成模型能夠處理的格式。
原始數據格式如下:
sen :不錯,品種齊全,上菜很快,味道也不錯
label :2
執行上面代碼后句子數據變成如下格式:
輸入:[0,0,0......,31,43,12,4,65,12,233,11,1391,131,4923,1233]
輸出:[0,0,1]
構建模型
這里定義了一個Sentiment類,封裝了模型的構建,訓練和預測方法。
class Sentiment:
def __init__(self,w2id,embedding_weights,Embedding_dim,maxlen,labels_category):
self.Embedding_dim = Embedding_dim
self.embedding_weights = embedding_weights
self.vocab = w2id
self.labels_category = labels_category
self.maxlen = maxlen
self.model = self.build_model()
def build_model(self):
model = Sequential()
#input dim(140,100)
model.add(Embedding(output_dim = self.Embedding_dim,
input_dim=len(self.vocab)+1,
weights=[self.embedding_weights],
input_length=self.maxlen))
model.add(Bidirectional(LSTM(50),merge_mode='concat'))
model.add(Dropout(0.5))
model.add(Dense(self.labels_category))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy',
optimizer='adam',
metrics=['accuracy'])
model.summary()
return model
def train(self,X_train, y_train,X_test, y_test,n_epoch=5 ):
self.model.fit(X_train, y_train, batch_size=32, epochs=n_epoch,
validation_data=(X_test, y_test))
self.model.save('sentiment.h5')
def predict(self,model_path,new_sen):
model = self.model
model.load_weights(model_path)
new_sen_list = jieba.lcut(new_sen)
sen2id =[ self.vocab.get(word,0) for word in new_sen_list]
sen_input = pad_sequences([sen2id], maxlen=self.maxlen)
res = model.predict(sen_input)[0]
return np.argmax(res)
senti = Sentiment(w2id,embedding_weights,100,200,3)
構建模型,同時傳人詞典和詞向量矩陣。
模型訓練
senti.train(x_train,y_trian, x_val ,y_val,1)
運行上述代碼讓模型跑起來,筆者只是做個實驗,所以只讓模型訓練了一個epoch。
模型預測
label_dic = {0:"消極的",1:"中性的",2:"積極的"}
sen_new = "現如今的公司能夠做成這樣已經很不錯了,微訂點單網站的信息更新很及時,內容來源很真實"
pre = senti.predict("./sentiment.h5",sen_new)
print("'{}'的情感是:\n{}".format(sen_new,label_dic.get(pre)))
模型訓練完之后,接下來就是見證奇跡的時刻了。
筆者輸入一句評論讓模型去預測,結果如上圖所示。只訓練了一個epoch,就有這樣的功力,不得不承認詞向量+深度學習真是強。
結語
至此,我們通過深度學習技術讓計算機學會人類世界中一些簡單的情感判斷。有沒有覺得有那么一絲絲可怕,會不會真有一天,你在和一個計算機進行情感交流呢?
(想著想著,筆者先跑了)