在《文本情感分類:傳統模型(1)》一文中,簡單介紹了進行文本情感分類的傳統思路。html
傳統的思路簡單易懂,並且穩定性也比較強,然而存在着兩個難以克服的侷限性:1、精度問題,傳統思路差強人意,固然通常的應用已經足夠了,可是要進一步提升精度,卻缺少比較好的方法;2、背景知識問題,傳統思路須要事先提取好情感詞典,而這一步驟,每每須要人工操做才能保證準確率,換句話說,作這個事情的人,不只僅要是數據挖掘專家,還須要語言學家,這個背景知識依賴性問題會阻礙着天然語言處理的進步。web
慶幸的是,深度學習解決了這個問題(至少很大程度上解決了),它容許咱們在幾乎「零背景」的前提下,爲某個領域的實際問題創建模型。本文延續上一篇文章所談及的文本情感分類爲例,簡單講解深度學習模型。其中上一篇文章已經詳細討論過的部分,本文再也不詳細展開。算法
近年來,深度學習算法被應用到了天然語言處理領域,得到了比傳統模型更優秀的成果。如Bengio等學者基於深度學習的思想構建了神經機率語言模型,並進一步利用各類深層神經網絡在大規模英文語料上進行語言模型的訓練,獲得了較好的語義表徵,完成了句法分析和情感分類等常見的天然語言處理任務,爲大數據時代的天然語言處理提供了新的思路。網絡
通過筆者的測試,基於深度神經網絡的情感分析模型,其準確率每每有95%以上,深度學習算法的魅力和威力可見一斑!app
關於深度學習進一步的資料,請參考如下文獻:函數
[1] Yoshua Bengio, Réjean Ducharme Pascal Vincent, Christian Jauvin. A Neural Probabilistic Language Model, 2003
[2] 一種新的語言模型: http://blog.sciencenet.cn/blog-795431-647334.html
[3] Deep Learning(深度學習)學習筆記整理: http://blog.csdn.net/zouxy09/article/details/8775360
[4] Deep Learning: http://deeplearning.net
[5] 漫話中文自動分詞和語義識別: http://www.matrix67.com/blog/archives/4212
[6] Deep Learning 在中文分詞和詞性標註任務中的應用: http://blog.csdn.net/itplus/article/details/13616045
建模環節中最重要的一步是特徵提取,在天然語言處理中也不例外。在天然語言處理中,最核心的一個問題是,如何把一個句子用數字的形式有效地表達出來?若是可以完成這一步,句子的分類就不成問題了。顯然,一個最初等的思路是:給每一個詞語賦予惟一的編號1,2,3,4...,而後把句子當作是編號的集合,好比假設1,2,3,4分別表明「我」、「你」、「愛」、「恨」,那麼「我愛你」就是[1, 3, 2],「我恨你」就是[1, 4, 2]。這種思路看起來有效,實際上很是有問題,好比一個穩定的模型會認爲3跟4是很接近的,所以[1, 3, 2]和[1, 4, 2]應當給出接近的分類結果,可是按照咱們的編號,3跟4所表明的詞語意思徹底相反,分類結果不可能相同。所以,這種編碼方式不可能給出好的結果。工具
讀者也許會想到,我將意思相近的詞語的編號湊在一堆(給予相近的編號)不就好了?嗯,確實若是,若是有辦法把相近的詞語編號放在一塊兒,那麼確實會大大提升模型的準確率。但是問題來了,若是給出每一個詞語惟一的編號,而且將相近的詞語編號設爲相近,其實是假設了語義的單一性,也就是說,語義僅僅是一維的。然而事實並不是如此,語義應該是多維的。學習
好比咱們談到「家園」,有的人會想到近義詞「家庭」,從「家庭」又會想到「親人」,這些都是有相近意思的詞語;另外,從「家園」,有的人會想到「地球」,從「地球」又會想到「火星」。換句話說,「親人」、「火星」均可以看做是「家園」的二級近似,可是「親人」跟「火星」自己就沒有什麼明顯的聯繫了。此外,從語義上來說,「大學」、「溫馨」也能夠看作是「家園」的二級近似,顯然,若是僅經過一個惟一的編號,是很難把這些詞語放到適合的位置的。測試
從上面的討論能夠知道,不少詞語的意思是各個方向發散開的,而不是單純的一個方向,所以惟一的編號不是特別理想。那麼,多個編號如何?換句話說,將詞語對應一個多維向量?不錯,這正是很是正確的思路。大數據
爲何多維向量可行?首先,多維向量解決了詞語的多方向發散問題,僅僅是二維向量就能夠360度全方位旋轉了,況且是更高維呢(實際應用中通常是幾百維)。其次,還有一個比較實際的問題,就是多維向量容許咱們用變化較小的數字來表徵詞語。怎麼說?咱們知道,就中文而言,詞語的數量就多達數十萬,若是給每一個詞語惟一的編號,那麼編號就是從1到幾十萬變化,變化幅度如此之大,模型的穩定性是很難保證的。若是是高維向量,好比說20維,那麼僅須要0和1就能夠表達220=1048576220=1048576(100萬)個詞語了。變化較小則可以保證模型的穩定性。
扯了這麼多,尚未真正談到點子上。如今思路是有了,問題是,如何把這些詞語放到正確的高維向量中?並且重點是,要在沒有語言背景的狀況下作到這件事情?(換句話說,若是我想處理英語語言任務,並不須要先學好英語,而是隻須要大量收集英語文章,這該多麼方便呀!)在這裏咱們不可能也沒必要要進行更多的原理上的展開,而是要介紹:而基於這個思路,有一個Google開源的著名的工具——Word2Vec。
簡單來講,Word2Vec就是完成了上面所說的咱們想要作的事情——用高維向量(詞向量,Word Embedding)表示詞語,並把相近意思的詞語放在相近的位置,並且用的是實數向量(不侷限於整數)。咱們只須要有大量的某語言的語料,就能夠用它來訓練模型,得到詞向量。詞向量好處前面已經提到過一些,或者說,它就是問了解決前面所提到的問題而產生的。另外的一些好處是:詞向量能夠方便作聚類,用歐氏距離或餘弦類似度均可以找出兩個具備相近意思的詞語。這就至關於解決了「一義多詞」的問題(遺憾的是,彷佛沒什麼好思路能夠解決一詞多義的問題。)
關於Word2Vec的數學原理,讀者能夠參考這系列文章。而Word2Vec的實現,Google官方提供了C語言的源代碼,讀者能夠自行編譯。而Python的Gensim庫中也提供現成的Word2Vec做爲子庫(事實上,這個版本貌似比官方的版本更增強大)。
接下來要解決的問題是:咱們已經分好詞,而且已經將詞語轉換爲高維向量,那麼句子就對應着詞向量的集合,也就是矩陣,相似於圖像處理,圖像數字化後也對應一個像素矩陣;但是模型的輸入通常只接受一維的特徵,那怎麼辦呢?一個比較簡單的想法是將矩陣展平,也就是將詞向量一個接一個,組成一個更長的向量。這個思路是能夠,可是這樣就會使得咱們的輸入維度高達幾千維甚至幾萬維,事實上是難以實現的。(若是說幾萬維對於今天的計算機來講不是問題的話,那麼對於1000x1000的圖像,就是高達100萬維了!)
事實上,對於圖像處理來講,已經有一套成熟的方法了,叫作卷積神經網絡(CNNs),它是神經網絡的一種,專門用來處理矩陣輸入的任務,可以將矩陣形式的輸入編碼爲較低維度的一維向量,而保留大多數有用信息。卷積神經網絡那一套也能夠直接搬到天然語言處理中,尤爲是文本情感分類中,效果也不錯,相關的文章有《Deep Convolutional Neural Networks for Sentiment Analysis of Short Texts》。可是句子的原理不一樣於圖像,直接將圖像那一套用於語言,雖然略有小成,但總讓人感受不三不四。所以,這並不是天然語言處理中的主流方法。
在天然語言處理中,一般用到的方法是遞歸神經網絡或循環神經網絡(都叫RNNs)。它們的做用跟卷積神經網絡是同樣的,將矩陣形式的輸入編碼爲較低維度的一維向量,而保留大多數有用信息。跟卷積神經網絡的區別在於,卷積神經網絡更注重全局的模糊感知(比如咱們看一幅照片,事實上並無看清楚某個像素,而只是總體地把握圖片內容),而RNNs則是注重鄰近位置的重構,因而可知,對於語言任務,RNNs更具備說服力(語言老是由相鄰的字構成詞,相鄰的詞構成短語,相鄰的短語構成句子,等等,所以,須要有效地把鄰近位置的信息進行有效的整合,或者叫重構)。
說到模型的分類,可真謂無窮無盡。在RNNs這個子集之下,又有不少個變種,如普通的RNNs,以及GRU、LSTM等,讀者能夠參考Keras的官方文檔:http://keras.io/models/,它是Python是一個深度學習庫,提供了大量的深度學習模型,它的官方文檔既是一個幫助教程,也是一個模型的列表——它基本實現了目前流行的深度學習模型。
吹了那麼久水,是該乾點實事了。如今咱們基於LSTM(Long-Short Term Memory,長短時間記憶人工神經網絡)搭建一個文本情感分類的深度學習模型,其結構圖以下:
模型結構很簡單,沒什麼複雜的,實現也很容易,用的就是Keras,它都爲咱們實現好了現成的算法了。
如今咱們來談談有意思的兩步。
第一步是標註語料的收集。要注意咱們的模型是監督訓練的(至少也是半監督),因此須要收集一些已經分好類的句子,數量嘛,固然越多越好。而對於中文文本情感分類來講,這一步着實不容易,中文的資料每每是至關匱乏的。筆者在作模型的時候,東拼西湊,經過各類渠道(有在網上搜索下載的、有在數據堂花錢購買的)收集了兩萬多條中文標註語料(涉及六個領域)用來訓練模型。(文末有共享)
第二步是模型閾值選取問題。事實上,訓練的預測結果是一個[0, 1]區間的連續的實數,而程序默認狀況下會將0.5設爲閾值,也就是將大於0.5的結果判斷爲正,將小於0.5的結果判斷爲負。這樣的默認值在不少狀況下並非最好的。以下圖所示,咱們在研究不一樣的閾值對真正率和真負率的影響之時,發如今(0.391, 0.394)區間內曲線曲線了陡變。
雖然從絕對值看,只是從0.99降低到了0.97,變化不大,可是其變化率是很是大的。正常來講都是平穩變化的,陡變意味着確定出現了什麼異常狀況,而顯然這個異常的緣由咱們很難發現。換句話說,這裏存在一個不穩定的區域,這個區域內的預測結果事實上是不可信的,所以,保險起見,咱們扔掉這個區間。只有結果大於0.394的,咱們才認爲是正,小於0.391的,咱們才認爲是負,是0.391到0.394之間的,咱們待定。實驗代表這個作法有助於提升模型的應用準確率。
文章很長,粗略地介紹了深度學習在文本情感分類中的思路和實際應用,不少東西都是泛泛而談。筆者並不是要寫關於深度學習的教程,而是隻想把關鍵的地方指出來,至少是那些我認爲是比較關鍵的地方。關於深度學習,有不少不錯的教程,最好仍是閱讀英文的論文,中文的比較好的就是博客http://blog.csdn.net/itplus了,筆者就不在這方面獻醜了。
下面是個人語料和代碼。讀者可能會好奇我爲何會把這些「私人珍藏」共享呢?其實很簡單,由於我不是幹這行的哈,數據挖掘對我來講只是一個愛好,一個數學與Python結合的愛好,所以在這方面,我不用擔憂別人比我領先哈。
語料下載:sentiment.zip
採集到的評論數據:sum.zip
搭建LSTM作文本情感分類的代碼:
import pandas as pd #導入Pandas import numpy as np #導入Numpy import jieba #導入結巴分詞 from keras.preprocessing import sequence from keras.optimizers import SGD, RMSprop, Adagrad from keras.utils import np_utils from keras.models import Sequential from keras.layers.core import Dense, Dropout, Activation from keras.layers.embeddings import Embedding from keras.layers.recurrent import LSTM, GRU from __future__ import absolute_import #導入3.x的特徵函數 from __future__ import print_function neg=pd.read_excel('neg.xls',header=None,index=None) pos=pd.read_excel('pos.xls',header=None,index=None) #讀取訓練語料完畢 pos['mark']=1 neg['mark']=0 #給訓練語料貼上標籤 pn=pd.concat([pos,neg],ignore_index=True) #合併語料 neglen=len(neg) poslen=len(pos) #計算語料數目 cw = lambda x: list(jieba.cut(x)) #定義分詞函數 pn['words'] = pn[0].apply(cw) comment = pd.read_excel('sum.xls') #讀入評論內容 #comment = pd.read_csv('a.csv', encoding='utf-8') comment = comment[comment['rateContent'].notnull()] #僅讀取非空評論 comment['words'] = comment['rateContent'].apply(cw) #評論分詞 d2v_train = pd.concat([pn['words'], comment['words']], ignore_index = True) w = [] #將全部詞語整合在一塊兒 for i in d2v_train: w.extend(i) dict = pd.DataFrame(pd.Series(w).value_counts()) #統計詞的出現次數 del w,d2v_train dict['id']=list(range(1,len(dict)+1)) get_sent = lambda x: list(dict['id'][x]) pn['sent'] = pn['words'].apply(get_sent) #速度太慢 maxlen = 50 print("Pad sequences (samples x time)") pn['sent'] = list(sequence.pad_sequences(pn['sent'], maxlen=maxlen)) x = np.array(list(pn['sent']))[::2] #訓練集 y = np.array(list(pn['mark']))[::2] xt = np.array(list(pn['sent']))[1::2] #測試集 yt = np.array(list(pn['mark']))[1::2] xa = np.array(list(pn['sent'])) #全集 ya = np.array(list(pn['mark'])) print('Build model...') model = Sequential() model.add(Embedding(len(dict)+1, 256)) model.add(LSTM(256, 128)) # try using a GRU instead, for fun model.add(Dropout(0.5)) model.add(Dense(128, 1)) model.add(Activation('sigmoid')) model.compile(loss='binary_crossentropy', optimizer='adam', class_mode="binary") model.fit(xa, ya, batch_size=16, nb_epoch=10) #訓練時間爲若干個小時 classes = model.predict_classes(xa) acc = np_utils.accuracy(classes, ya) print('Test accuracy:', acc)