1. 背景介紹
文本情感分析是在文本分析領域的典型任務,實用價值很高。本模型是第一個上手實現的深度學習模型,目的是對深度學習做一個初步的了解,並入門深度學習在文本分析領域的應用。在進行模型的上手實現之前,已學習了吳恩達的機器學習和深度學習的課程,對理論有了一定的了解,感覺需要來動手實現一下了。github對應網址https://github.com/ble55ing/LSTM-Sentiment_analysis
LSTM(Long Short-Term Memory)是長短期記憶網絡,在自然語言處理的領域有着較好的效果。因此本文使用LSTM網絡來幫助進行文本情感分析。本文將從分詞、向量化和模型訓練三個方面對所訓練的模型進行講解,本文所實現的模型達到了在測試集99%的准確率。
2. 中文文本分詞
首先需要得到兩個文檔,即積極情感的文本和消極情感的文本,作為訓練用到的數據集,積極和消極的各8000條左右。然后程序在載入了這兩個文本的內容后,需要進行一部分的預處理,而預處理部分中最關鍵的就是分詞。
2.1 分詞 or 分字
一般在中文文本的分詞處理上,最常使用的就是jieba分詞,因此在一開始訓練模型的時候,也是使用的jieba分詞。但后來感覺效果並不太好,最好的時候准確率也就達到92%,而且存在較為嚴重的過擬合問題(同時測試集准確率達到99%)。因此去和搞過一段時間的自然語言處理的大佬討論了一下,大佬給出的建議是直接分字,因為所收集的訓練集還是相對來說少了一點,分詞完會導致訓練集縮小,再進行embedding(數據降維)之后詞表更小了,就不太方便獲取文本間的內在聯系。
因而最后分詞時比較了直接分字和jieba分詞的效果,最終相比之下還是直接分字的效果會更好一些(大佬就是大佬),所以選用了直接分字。直接分字的思路是將中文單字分為一個字,英文單詞分為一個字。這里需要考慮到utf-8編碼,從而正確的對文本進行分字。
2.2 去停用詞
停用詞:一些在文本中相對來說對語義的影響不明顯的詞,在分詞的同時可以將這些停用詞去掉,使得文本分類的效果更好。但同樣的由於采集到的樣本比較小的原因,在進行了嘗試之后還是沒有使用去停用詞。因為雖然對語義的影響不大,但還是存在着一些情感在里頭,這部分信息也有一定的意義。
2.3 utf-8編碼的格式
utf-8的編碼格式為:
如果該字符占用一個字節,那么第一個位為0。
如果該字符占用n個字節(4>=n>1),那么第一個字節的前n位為1,第n+1位為0。
也就是不會出現第一個字符的第一個字節為1,第二個字節為0的情況。
2.4 實現
#將中文分成一個一個的字 def onecut(doc): #print len(doc),ord(doc[0]) #print doc[0]+doc[1]+doc[2] ret = []; i=0 while i < len(doc): c="" #utf-8的編碼格式,小於128的為1個字符,n個字符的化第一個字符的前n+1個字符是1110 #print i,ord(doc[i]) if ord(doc[i])>=128 and ord(doc[i])<192: print ord(doc[i]) assert 1==0#所以其實這里是不應該到達的 c = doc[i]+doc[i+1]; i=i+2 ret.append(c) elif ord(doc[i])>=192 and ord(doc[i])<224: c = doc[i] + doc[i + 1]; i = i + 2 ret.append(c) elif ord(doc[i])>=224 and ord(doc[i])<240: c = doc[i] + doc[i + 1] + doc[i + 2]; i = i + 3 ret.append(c) elif ord(doc[i])>=240 and ord(doc[i])<248: c = doc[i] + doc[i + 1] + doc[i + 2]+doc[i + 3]; i = i + 4 ret.append(c) else : assert ord(doc[i])<128 while ord(doc[i])<128: c+=doc[i] i+=1 if (i==len(doc)) : break if doc[i] is " ": break; elif doc[i] is ".": break; elif doc[i] is ";": break; ret.append(c) return ret
3. 文本向量化
接下來是需要對分完字的文本進行向量化,這里使用到了word2Vec,一款文本向量化的常用工具。主要就是解決將語言文本處理成緊湊的向量。簡單的文本轉化往往是相當稀疏的矩陣,即One-Hot編碼。轉換的文本向量就是把文本中所含的詞的編號的位置置為1.這樣的編碼方式顯然是不適合進行深度學習模型訓練的,因為數據過於離散了。因此,需要將向量維數進行縮減。word2Vec就能夠較好的解決這個問題。
3.1 Word2Vec
Word2Vec能夠將文本生成相對緊湊的向量,這個過程稱為詞嵌入(embedding),其本身也是一個神經網絡模型。訓練完成之后,就能夠得到每個詞所對應的低維向量了。使用這個低維向量來進行訓練,能夠達到較好的訓練效果。
3.2 實現
def word2vec_train(X_Vec): model_word = Word2Vec(size=voc_dim, min_count=min_out, window=window_size, workers=cpu_count, iter=5) model_word.build_vocab(X_Vec) model_word.train(X_Vec, total_examples=model_word.corpus_count, epochs=model_word.iter) model_word.save('../model/Word2vec_model.pkl') input_dim = len(model_word.wv.vocab.keys()) + 1 #下標0空出來給不夠10的字 embedding_weights = np.zeros((input_dim, voc_dim)) w2dic={} for i in range(len(model_word.wv.vocab.keys())): embedding_weights[i+1, :] = model_word [model_word.wv.vocab.keys()[i]] w2dic[model_word.wv.vocab.keys()[i]]=i+1 return input_dim,embedding_weights,w2dic
4. 模型訓練
4.1 激活函數
LSTM模型的訓練,其激活函數選用了Softsign,是一個對於LSTM來說的時候比tanh更加合適的激活函數。
4.2 模型層數
在全連接層數的選取上,本來是使用了一層的全連接層,0.5的dropout,但在一開始的分詞方式下,產生了較為嚴重的過擬合情況,因此就嘗試着再添加一層Relu的全連接層,0.5的dropout,效果是確實可以解決過擬合的問題,但並沒有提升准確率。因此就還是回到了一層全連接層的狀況。相比之下,一層比兩層的訓練逼近速度快得多。
4.3 損失函數
損失函數的選取:這一部分嘗試了三個損失函數,mse,hinge和binary_crossentropy,最終選用了binary_crossentropy。
mse這個損失函數相對普通,hingo和binary_crossentropy是較為專用於二分類問題的,而binary_crossentropy還往往與sigmoid作為激活函數一同使用。也可能是在使用hinge的時候沒有用對激活函數吧。
4.4 評估標准
一開始的時候,評估標准定的是只有准確率(acc),然后准確率一直上不去。后來添加了平均絕對誤差(mae,mean_absolute_error),准確率一下子就上去了,很有意思。
5. 總結
總的來說,自己搭模型調參的過程還是很必要的一個過程,內心很煎熬,沒有自動調參的工具嗎。。能夠調出一個效果不錯的模型還是很開心的。感覺在深度學習這塊還是有很多的經驗在里面,是需要花些時間的。
附上結果圖一張。