使用 TensorFlow 做文本情感分析
本文將通過使用TensorFlow中的LSTM神經網路方法探索高效的深度學習方法。
基於LSTM方法的情感分析
在這篇筆記中,我們將研究如何將深度學習技術應用在情感分析任務中。情感分析可以理解為擇取段落、文檔或任意一種自然語言的片段,然後決定文本的情緒色彩是正面的、負面的還是中性的。
這篇筆記將會講到數個話題,如詞向量,時間遞歸神經網路和長短期記憶等。對這些術語有了好的理解後,我們將在最後詳細介紹具體的代碼示例和完整的Tensorflow情緒分類器。
在進入具體細節之前,讓我們首先來討論深度學習適用於自然語言處理(NLP)任務的原因。
深度學習在自然語言處理方面的應用
自然語言處理是關於處理或「理解」語言以執行某些任務的創造系統。這些任務可能包括:
- 問題回答 - Siri,Alexa和Cortana等技術的主要工作
- 情緒分析 - 確定一段文本背後的情緒色調
- 圖像到文本映射 - 生成輸入圖像的說明文字
- 機器翻譯 - 將一段文本翻譯成另一種語言
- 語音識別 - 電腦識別口語
在深度學習時代,NLP是一個蓬勃發展中的領域,取得了很多不同的進步。然而,在上述任務的所有成就中,需要做很多特徵工程的工作,因此需要有很多語言領域的專業知識。作為從業人員需要掌握對音素和語素等術語,乃至花費四年讀取學位專門學習這個領域。近幾年來,深度學習取得了驚人的進步,大大消除了對豐富專業知識要求。由於進入門檻較低,對NLP的應用已成為深度學習研究的最大領域之一。
詞向量
為了理解如何應用深度學習,可以思考應用在機器學習或深度學習模型中的所有不同數據形式。卷積神經網路使用像素值向量,邏輯線性回歸使用量化特徵,強化學習模型使用回饋信號。共同點是都需要標量或者標量矩陣來作為輸入。當你思考NLP任務時,可能會在你的思路中出現這樣的數據管道。
這種通道是有問題的。我們無法在單個字元串上進行像點乘或者反向傳播這樣的常見操作。我們需要把句子中的每個單詞轉換成一個向量而不是僅僅輸入字元串。
你可以將情緒分析模塊的輸入看做一個16 x D維矩陣。
我們希望以方便表示單詞及其上下文、意義和語義的方式來創建這些向量。例如,我們希望「愛」和「崇拜」這些向量駐留在向量空間中相對相同的區域中,因為它們都具有相似的定義,並且在相似的上下文中使用。一個單詞的向量表示也稱為詞嵌入。
Word2Vec
為了創建這些單詞嵌入,我們將使用通常被稱為「Word2Vec」的模型。模型通過查看語句在句子中出現的上下文來創建詞矢量而忽略細節。具有相似上下文的單詞將在向量空間中放置在相近的位置。在自然語言中,當嘗試確定其含義時,單詞的上下文可能非常重要。正如我們之前」崇拜「和」愛「的例子,
從句子的上下文可以看出,這兩個詞通常用於具有正面內涵的句子,通常在名詞或名詞短語之前。這表明這兩個詞都有一些共同點,可能是同義詞。考慮句子中的語法結構時,語境也很重要。大多數句子將遵循具有動詞跟隨名詞的傳統範例,形容詞先於名詞等等。因此,該模型更有可能將名詞與其他名詞相同。該模型採用大量句子數據集(例如英文維基百科),並為語料庫中的每個不同詞輸出向量。Word2Vec模型的輸出稱為嵌入矩陣(embedding matrix)。
該嵌入矩陣將包含訓練語料庫中每個不同單詞的向量。按照傳統做法,嵌入矩陣可以包含超過300萬個字向量。
Word2Vec模型是通過將數據集中的每個句子進行訓練,在其上滑動固定大小的窗口,並嘗試根據給出的其他單詞預測窗口中心的單詞。使用損失函數和優化程序,模型為每個不同詞生成向量。這個訓練過程的具體細節可能會有點複雜,所以我們現在要跳過細節,但重要的是,任何深度學習方法對NLP任務的都很可能會有詞矢量作為輸入。
有關Word2Vec背後的理論以及如何創建自己的嵌入矩陣的更多信息,請查看Tensorflow的教程
遞歸神經網路(RNNs)
現在我們用我們的詞向量作為輸入,首先來看看將要建立的實際網路架構。NLP數據的獨特之處在於它有一個時間方面的差異。一句話中的每一個詞的含義都很大程度上依賴於發生在過去還是未來。
你很快就會看到,遞歸神經網路結構和傳統的前饋神經網路有點不同。前饋神經網路由輸入節點,隱藏單元和輸出節點組成。
前饋神經網路和遞歸神經網路的主要區別在於後者的時間性。在RNN中,輸入序列的每個單詞都與特定的時間步長相關聯。實際上,時間步長的數量將等於最大序列長度。
一個稱為隱藏狀態向量(ht)的新組件也與每個時間步相聯繫。從高層次來看,這個向量旨在封裝並總結在之前的時間步中看到的所有信息。就像(xt)是封裝特定單詞的所有信息的向量,htht是一個向量,總結了之前時間步長的所有信息。
隱藏狀態是當前詞向量和前一時間步的的函數。σ表示兩項和代入一個激活函數(通常為S形或tanh)
上述公式中的2個W項代表權重矩陣。如果你仔細看看上標,你會看到有一個權重矩陣WXWX ,它將與我們的輸入相乘,並且有一個循環權重矩陣WHWH,它將與上一時間步的隱藏狀態相乘。WHWH是在所有時間步長中保持不變的矩陣,權重矩陣WXWX相對於每個輸入是不同的。
這些權重矩陣的大小會影響當前隱藏狀態或之前隱藏狀態所影響的變數。作為練習,參考上面的公式,思考WXWX或者WHWH的值大小變化對htht有怎樣的影響。
來看一個簡單的例子,當WHWH很大而WXWX很小時,我很知道(ht)很大程度上受(h{t-1})的影響而受xtxt影響較小。換句話說,當前隱藏狀態向量對應的單詞在句子全局上是無關緊要的,那麼它和上一時間步的向量值基本相同。
權重矩陣通過稱為反向傳播的優化過程隨著時間進行更新。
末尾時間步處的隱藏狀態向量被饋送到二進位softmax分類器中,在其中與另一個權重矩陣相乘,並經過softmax函數(輸出0和1之間的值)的處理,有效地給出情緒偏向正面或負面的概率。
長短期記憶單元(LSTM)
長短期記憶單元式放置在遞歸神經網路中的模塊。在高層次上,它們確定隱藏狀態向量h能夠在文本中封裝有關長期依賴關係的信息。正如我們上一節所見,在傳統RNN方法中h的構想相對簡單。但這種方法無法有效地將由多個時間步長分開的信息連接在一起。我們可以通過QA問答系統(question answering)闡明處理長期依賴關係的思路。QA問答系統的功能是提出一段文本,然後根據這段文本的內容提出問題。我們來看下面的例子:
我們可以看出中間的句子對被提出的問題沒有影響。然而,第一句和第三句之間有很強的聯繫。使用經典的RNN,網路末端的隱藏狀態向量可能存儲有關狗的句子的更多信息,而不是關於該數字的第一句。從根本上來說,額外的LSTM單元會增加可能性來查明應該被導入隱藏狀態向量的正確有用信息。
從更技術的角度來看LSTM單元,單元導入當前的詞向量(xt)並輸出隱藏狀態向量(ht)。在這些單元中,htht的構造將比典型的RNN更複雜一點。計算分為4個組件,一個輸入門(input gate),一個遺忘門(forget gate),一個輸出門(output gate)和一個新的存儲容器。
每個門將使用(xt)和(ht)(圖中未顯示)作為輸入,並對它們執行一些計算以獲得中間狀態。每個中間狀態被反饋到不同的管道中並最終把信息聚合成(ht)的形式。為了簡便起見,我們不會對每個門的具體構造進行說明,但值得注意的是,每個門都可以被認為是LSTM內的不同模塊,每個模塊各有不同的功能。輸入門決定了每個輸入的權重,遺忘門決定我們將要丟棄什麼樣的信息,輸出門決定最終基於中間狀態的(ht)。若想了解不同門的功能和全部方程式,更詳細的信息請查看Christopher Olah的博客文章(譯者註:或者中文譯文)。
回顧第一個例子,問題是「兩個數字的和是多少」,該模型必須接受相似問答的訓練,然後,LSTM單位將能認識到沒有數字的任何句子可能不會對問題的答案產生影響,因此該單位將能夠利用其遺忘門來丟棄關於狗的不必要的信息,而保留有關數字的信息。
把情緒分析表述為深度學習問題
如前所屬,情緒分析的任務主要是輸入一序列句子並判斷情緒是正面的、負面的還是中性的。我們可以將這個特別的任務(和大多數其他NLP任務)分成5個不同的步驟。
- 訓練一個詞向量生成模型(比如Word2Vec)或者載入預訓練的詞向量
- 為我們的訓練集建立一個ID矩陣(稍後討論)
- RNN(使用LSTM單元)圖形創建
- 訓練
- 測試
載入數據
首先,我們要創建詞向量。為簡單起見,我們將使用預訓練好的模型。
作為機器學習這個遊戲中的最大玩家,Google能夠在包含超過1000億個不同單詞的大規模Google新聞訓練集上訓練Word2Vec模型!從那個模型來看,Google能夠創建300萬個詞向量,每個向量的維數為300。
在理想情況下,我們將使用這些向量,但由於詞向量矩陣相當大(3.6GB!),我們將使用一個更加可管理的矩陣,該矩陣由一個類似的詞向量生成模型Glove訓練。矩陣將包含40萬個詞向量,每個維數為50。
我們將要導入兩個不同的數據結構,一個是一個40萬個單詞的Python列表,一個是擁有所有單詞向量值得40萬x50維嵌入矩陣。
為了確保一切都已正確載入,我們可以查看辭彙列表的維度和嵌入矩陣的維度。
我們還可以搜索單詞列表中的一個單詞,如「棒球」,然後通過嵌入矩陣訪問其對應的向量。
現在我們有了自己的向量,首先是輸入一個句子,然後構造它的向量表示。假如我們有輸入句子「I thought the movie was incredible and inspiring」。為了獲取詞向量,我們可以使用Tensorflow的內嵌查找函數。這個函數需要兩個參數,一個是嵌入矩陣(在我們的例子中為詞向量矩陣),一個用於每個單詞的id。id向量可以認為是訓練集的整數表示。這基本只是每個單詞的行索引。讓我們來看一個具體的例子,使之具體化。
數據流水線如下圖所示。
10 x 50的輸出應包含序列中10個單詞中的每一個的50維字向量。
在為整個訓練集創建id矩陣之前,首先花一些時間為擁有的數據類型做一下可視化。這會幫助我們確定設定最大序列長度的最佳值。在先前的例子中,我們用的最大長度為10,但這個值很大程度取決於你的輸入。
我們要使用的訓練集是Imdb電影評論數據集。這個集合中有25000個電影評論,12,500次正面評論和12,500次評論。每個評論都存儲在我們需要解析的txt文件中。積極的評論存儲在一個目錄中,負面評論存儲在另一個目錄中。以下代碼將確定每個評論中的平均字數和總和。
我們還可以使用Matplot庫以直方圖的形式來顯示數據。
從直方圖及每個文件的平均字數來看,我們可以確定大多數評論低於250詞,這時我們設置最大序列長度值。 maxSeqLength = 250 下面將展示如何將一個單一的文件轉換成id矩陣。如下是一條看起來像文本文件格式的評論。
現在,轉換成一個id矩陣
現在,對我們這25000條評論做同樣的工作。載入電影訓練集並整理它以獲得一個25000 x 250的矩陣。這是一個計算上昂貴的過程,因此,你不用再次運行整個程序,我們將載入預先計算的ID矩陣。
輔助函數
下面你會發現一些在之後神經網路訓練過程中很有用的輔助函數。
RNN模型
現在,我們準備開始創建我們的Tensorflow圖。首先要定義一些超參數,例如批處理大小,LSTM單元數,輸出類數和訓練次數。
與大多數Tensorflow圖一樣,我們現在需要指定兩個佔位符,一個用於輸入到網路中,一個用於標籤。定義這些佔位符的最重要的部分是了解每個維度。
標籤佔位符是一組值,每個值分別為[1,0]或[0,1],具體取決於每個訓練示例是正還是負。輸入佔位符中的整數每一行代表著我們在批處理中包含的每個訓練示例的整數表示。
一旦我們有了輸入數據佔位符,我們將調用tf.nn.lookup()函數來獲取詞向量。對該函數的調用會通過詞向量的維度返回長達最大序列長度的批大小(batch size)的3-D張量。為了可視化這個3-D張量,你可以簡單的把整數化輸入張量中的每個數據點看做對應的相關D維向量。
現在我們有了想要的形式的數據,嘗試如何把這些輸入填充進LSTM網路。我們將調用tf.nn.rnn_cell.BasicLSTMCell函數。這個函數輸入一個整數代表我們要用到的LSTM單元數。這是用來調整以利於確定最優值的超參數之一。然後我們將LSTM單元包裝在一個退出層,以防止網路過擬合。
最後,我們將充滿輸入數據的LSTM單元和3-D張量引入名為tf.nn.dynamic_rnn的函數中。該函數負責展開整個網路,並為數據流過RNN圖創建路徑。
作為一個備註,另一個更先進的網路架構選擇是將多個LSTM神經元堆疊在一起。也就是說第一個LSTM神經元最後一個隱藏狀態向量導入第二個LSTM神經元。堆疊這些神經元是幫助模型保留更多長期以來信息的一個很好的方法,但也會在模型中引入更多的參數,從而可能增加訓練時間,增加更多對訓練樣本的需求和過擬合的概率。有關如何把堆疊LSTM加入模型的更多信息,請查看Tensorflow文檔。
動態RNN函數的第一個輸出可以被認為是最後一個隱藏的狀態向量。該向量將重新定形,然後乘以最終權重矩陣和偏置項以獲得最終輸出值。
接下來,我們將定義正確的預測和精度指標,以跟蹤網路的運行情況。正確的預測公式通過查看2個輸出值的最大值的索引,然後查看它是否與訓練標籤相匹配來工作。
我們將基於最終預測值上的激活函數層定義標準交叉熵,使用Adam優化器,默認學習率為0.01。
如果你想使用Tensorboard來顯示損失和準確度的值,還可以運行和修改以下代碼。
超參數調優
為您的超參數選擇正確的值是有效訓練深層神經網路的關鍵部分。您會發現,您的訓練損失曲線可能因您選擇的優化器(Adam,Adadelta,SGD等),學習率和網路架構而不同。特別是使用RNN和LSTM時,要注意其他一些重要因素,包括LSTM單元的數量和字向量的大小。
- 由於有著大量的時間步,RNN的難以訓練臭名昭著。學習率變得非常重要,因為我們不希望權重值因為學習率高而波動,也不想由於學習率低而需要緩慢地訓練。默認值為0.001是個好的開始,如果訓練損失變化非常緩慢,你應該增加此值,如果損失不穩定,則應減少。
- 優化器:在研究人員之間尚沒有一致的選擇,但是由於具有自適應學習速率這個屬性,Adam很受歡迎(請記住,優化學習率可能隨著優化器的選擇而不同)。
- LSTM單位數:該值在很大程度上取決於輸入文本的平均長度。雖然更多的單位會使模型表達地更好,並允許模型存儲更多的信息用於較長的文本,但網路將需要更長的時間才能訓練,並且計算費用昂貴。
- 詞向量大小:詞向量的維度一般在50到300之間。更大的尺寸意味著詞向量能夠封裝更多關於該詞的信息,但模型也將花費更多計算量。
訓練
訓練循環的基本思路是首先定義一個Tensorflow session,然後載入一批評論及其相關標籤。接下來,我們調用session的run函數,該函數有兩個參數,第一個被稱為「fetches」參數,它定義了我們想要計算的期望值,我們希望優化器能夠計算出來,因為這是使損失函數最小化的組件。第二個參數需要輸入我們的feed_dict,這個數據結構是我們為所有佔位符提供輸入的地方。我們需要提供評論和標籤的批次,然後這個循環在一組訓練迭代器上重複執行。
我們將會載入一個預訓練模型而不是在這款notebook上訓練網路(這需要幾個小時)。
如果你決定在自己的機器上訓練這個模型,你可以使用TensorBoard來跟蹤訓練過程。當以下代碼在運行時,使用你的終端進入此代碼的執行目錄,輸入tensorboard --logdir=tensorboard,並使用瀏覽器訪問http://localhost:6006/,以對訓練過程保持關注。
載入預訓練模型
我們的預訓練模型在訓練過程中的精度和損失曲線如下所示。
查看如上訓練曲線,似乎模型的訓練進展順利。虧損穩步下降,準確率接近100%。然而,在分析訓練曲線時,我們還應該特別注意模型對訓練數據集過擬合的可能。過擬合是機器學習中的常見現象,模型變得適合於訓練模型而失去了推廣到測試集的能力。這意味著訓練一個網路直達到0訓練損失可能不是一個最好的方式,來獲取在一個從未見過的數據集上表現良好的準確模型。早停(early stopping)是一種直觀的技術,普遍應用於LSTM網路來解決過擬合問題。基本思想是在訓練集上訓練模型,同時還可以一次次在測試集上測量其性能。一旦測試錯誤停止了穩定的下降並開始增加,我們會知道該停止訓練,以為這是神經網路開始過擬合的信號。
載入預訓練模型涉及定義另一個Tensorflow會話,創建Saver對象,然後使用該對象調用恢復功能。此函數接受2個參數,一個用於當前會話,另一個用於保存模型的名稱。
然後我們將從測試集載入一些電影評論,注意,這些評論是模型從未訓練過的。運行以下代碼時可以看到每批測試的精準度。
結論
在這篇筆記中,我們對情緒分析進行了深入地學習。我們研究了整個流程中涉及的不同組件,然後研究了在實踐中編寫Tensorflow代碼來實現模型的過程。最後,我們對模型進行了培訓和測試,以便能夠對電影評論進行分類。
在Tensorflow的幫助下,你可以創建自己的情緒分類器,以了解世界上大量的自然語言,並使用結果形成具有說服力的論點。感謝您的閱讀。
推薦閱讀:
※理論上說,什麼是數據工程師,什麼是數據科學家
※手機市場銷售數據分析報告
TAG:数据科学家 |