<script type="text/javascript" async
src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-MML-AM_CHTML">
</script>
使用 TensorFlow 做文本情感分析
本文將通過使用TensorFlow中的LSTM神經(jīng)網(wǎng)絡(luò)方法探索高效的深度學(xué)習(xí)方法。
作者: Adit Deshpande
July 13, 2017
翻譯來源:https://www.oreilly.com/learning/perform-sentiment-analysis-with-lstms-using-tensorflow
基于LSTM方法的情感分析
在這篇筆記中,我們將研究如何將深度學(xué)習(xí)技術(shù)應(yīng)用在情感分析任務(wù)中。情感分析可以理解為擇取段落、文檔或任意一種自然語言的片段,然后決定文本的情緒色彩是正面的、負(fù)面的還是中性的。
這篇筆記將會講到數(shù)個話題,如詞向量,時間遞歸神經(jīng)網(wǎng)絡(luò)和長短期記憶等。對這些術(shù)語有了好的理解后,我們將在最后詳細(xì)介紹具體的代碼示例和完整的Tensorflow情緒分類器。
在進(jìn)入具體細(xì)節(jié)之前,讓我們首先來討論深度學(xué)習(xí)適用于自然語言處理(NLP)任務(wù)的原因。
深度學(xué)習(xí)在自然語言處理方面的應(yīng)用
自然語言處理是關(guān)于處理或“理解”語言以執(zhí)行某些任務(wù)的創(chuàng)造系統(tǒng)。這些任務(wù)可能包括:
- 問題回答 - Siri,Alexa和Cortana等技術(shù)的主要工作
- 情緒分析 - 確定一段文本背后的情緒色調(diào)
- 圖像到文本映射 - 生成輸入圖像的說明文字
- 機(jī)器翻譯 - 將一段文本翻譯成另一種語言
- 語音識別 - 電腦識別口語
在深度學(xué)習(xí)時代,NLP是一個蓬勃發(fā)展中的領(lǐng)域,取得了很多不同的進(jìn)步。然而,在上述任務(wù)的所有成就中,需要做很多特征工程的工作,因此需要有很多語言領(lǐng)域的專業(yè)知識。作為從業(yè)人員需要掌握對音素和語素等術(shù)語,乃至花費四年讀取學(xué)位專門學(xué)習(xí)這個領(lǐng)域。近幾年來,深度學(xué)習(xí)取得了驚人的進(jìn)步,大大消除了對豐富專業(yè)知識要求。由于進(jìn)入門檻較低,對NLP的應(yīng)用已成為深度學(xué)習(xí)研究的最大領(lǐng)域之一。
詞向量
為了理解如何應(yīng)用深度學(xué)習(xí),可以思考應(yīng)用在機(jī)器學(xué)習(xí)或深度學(xué)習(xí)模型中的所有不同數(shù)據(jù)形式。卷積神經(jīng)網(wǎng)絡(luò)使用像素值向量,邏輯線性回歸使用量化特征,強(qiáng)化學(xué)習(xí)模型使用回饋信號。共同點是都需要標(biāo)量或者標(biāo)量矩陣來作為輸入。當(dāng)你思考NLP任務(wù)時,可能會在你的思路中出現(xiàn)這樣的數(shù)據(jù)管道。
這種通道是有問題的。我們無法在單個字符串上進(jìn)行像點乘或者反向傳播這樣的常見操作。我們需要把句子中的每個單詞轉(zhuǎn)換成一個向量而不是僅僅輸入字符串。
你可以將情緒分析模塊的輸入看做一個16 x D維矩陣。
我們希望以方便表示單詞及其上下文、意義和語義的方式來創(chuàng)建這些向量。例如,我們希望“愛”和“崇拜”這些向量駐留在向量空間中相對相同的區(qū)域中,因為它們都具有相似的定義,并且在相似的上下文中使用。一個單詞的向量表示也稱為詞嵌入。
Word2Vec
為了創(chuàng)建這些單詞嵌入,我們將使用通常被稱為“Word2Vec”的模型。模型通過查看語句在句子中出現(xiàn)的上下文來創(chuàng)建詞矢量而忽略細(xì)節(jié)。具有相似上下文的單詞將在向量空間中放置在相近的位置。在自然語言中,當(dāng)嘗試確定其含義時,單詞的上下文可能非常重要。正如我們之前”崇拜“和”愛“的例子,
從句子的上下文可以看出,這兩個詞通常用于具有正面內(nèi)涵的句子,通常在名詞或名詞短語之前。這表明這兩個詞都有一些共同點,可能是同義詞??紤]句子中的語法結(jié)構(gòu)時,語境也很重要。大多數(shù)句子將遵循具有動詞跟隨名詞的傳統(tǒng)范例,形容詞先于名詞等等。因此,該模型更有可能將名詞與其他名詞相同。該模型采用大量句子數(shù)據(jù)集(例如英文維基百科),并為語料庫中的每個不同詞輸出向量。Word2Vec模型的輸出稱為嵌入矩陣(embedding matrix)。
該嵌入矩陣將包含訓(xùn)練語料庫中每個不同單詞的向量。按照傳統(tǒng)做法,嵌入矩陣可以包含超過300萬個字向量。
Word2Vec模型是通過將數(shù)據(jù)集中的每個句子進(jìn)行訓(xùn)練,在其上滑動固定大小的窗口,并嘗試根據(jù)給出的其他單詞預(yù)測窗口中心的單詞。使用損失函數(shù)和優(yōu)化程序,模型為每個不同詞生成向量。這個訓(xùn)練過程的具體細(xì)節(jié)可能會有點復(fù)雜,所以我們現(xiàn)在要跳過細(xì)節(jié),但重要的是,任何深度學(xué)習(xí)方法對NLP任務(wù)的都很可能會有詞矢量作為輸入。
有關(guān)Word2Vec背后的理論以及如何創(chuàng)建自己的嵌入矩陣的更多信息,請查看Tensorflow的教程
遞歸神經(jīng)網(wǎng)絡(luò)(RNNs)
現(xiàn)在我們用我們的詞向量作為輸入,首先來看看將要建立的實際網(wǎng)絡(luò)架構(gòu)。NLP數(shù)據(jù)的獨特之處在于它有一個時間方面的差異。一句話中的每一個詞的含義都很大程度上依賴于發(fā)生在過去還是未來。
你很快就會看到,遞歸神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)和傳統(tǒng)的前饋神經(jīng)網(wǎng)絡(luò)有點不同。前饋神經(jīng)網(wǎng)絡(luò)由輸入節(jié)點,隱藏單元和輸出節(jié)點組成。
前饋神經(jīng)網(wǎng)絡(luò)和遞歸神經(jīng)網(wǎng)絡(luò)的主要區(qū)別在于后者的時間性。在RNN中,輸入序列的每個單詞都與特定的時間步長相關(guān)聯(lián)。實際上,時間步長的數(shù)量將等于最大序列長度。
一個稱為隱藏狀態(tài)向量\(h_t\)的新組件也與每個時間步相聯(lián)系。從高層次來看,這個向量旨在封裝并總結(jié)在之前的時間步中看到的所有信息。就像\(x_t\)是封裝特定單詞的所有信息的向量,\(h_t\)是一個向量,總結(jié)了之前時間步長的所有信息。
隱藏狀態(tài)是當(dāng)前詞向量和前一時間步的的函數(shù)。σ表示兩項和代入一個激活函數(shù)(通常為S形或tanh)
上述公式中的2個W項代表權(quán)重矩陣。如果你仔細(xì)看看上標(biāo),你會看到有一個權(quán)重矩陣\(W^X\) ,它將與我們的輸入相乘,并且有一個循環(huán)權(quán)重矩陣\(WH\),它將與上一時間步的隱藏狀態(tài)相乘。\(WH\)是在所有時間步長中保持不變的矩陣,權(quán)重矩陣\(W^X\)相對于每個輸入是不同的。
這些權(quán)重矩陣的大小會影響當(dāng)前隱藏狀態(tài)或之前隱藏狀態(tài)所影響的變量。作為練習(xí),參考上面的公式,思考\(WX\)或者\(WH\)的值大小變化對\(h_t\)有怎樣的影響。
來看一個簡單的例子,當(dāng)\(WH\)很大而\(WX\)很小時,我很知道\(h_t\)很大程度上受\(h_{t-1}\)的影響而受\(x_t\)影響較小。換句話說,當(dāng)前隱藏狀態(tài)向量對應(yīng)的單詞在句子全局上是無關(guān)緊要的,那么它和上一時間步的向量值基本相同。
權(quán)重矩陣通過稱為反向傳播的優(yōu)化過程隨著時間進(jìn)行更新。
末尾時間步處的隱藏狀態(tài)向量被饋送到二進(jìn)制softmax分類器中,在其中與另一個權(quán)重矩陣相乘,并經(jīng)過softmax函數(shù)(輸出0和1之間的值)的處理,有效地給出情緒偏向正面或負(fù)面的概率。
長短期記憶單元(LSTM)
長短期記憶單元式放置在遞歸神經(jīng)網(wǎng)絡(luò)中的模塊。在高層次上,它們確定隱藏狀態(tài)向量h能夠在文本中封裝有關(guān)長期依賴關(guān)系的信息。正如我們上一節(jié)所見,在傳統(tǒng)RNN方法中h的構(gòu)想相對簡單。但這種方法無法有效地將由多個時間步長分開的信息連接在一起。我們可以通過QA問答系統(tǒng)(question answering)闡明處理長期依賴關(guān)系的思路。QA問答系統(tǒng)的功能是提出一段文本,然后根據(jù)這段文本的內(nèi)容提出問題。我們來看下面的例子:
我們可以看出中間的句子對被提出的問題沒有影響。然而,第一句和第三句之間有很強(qiáng)的聯(lián)系。使用經(jīng)典的RNN,網(wǎng)絡(luò)末端的隱藏狀態(tài)向量可能存儲有關(guān)狗的句子的更多信息,而不是關(guān)于該數(shù)字的第一句。從根本上來說,額外的LSTM單元會增加可能性來查明應(yīng)該被導(dǎo)入隱藏狀態(tài)向量的正確有用信息。
從更技術(shù)的角度來看LSTM單元,單元導(dǎo)入當(dāng)前的詞向量\(x_t\)并輸出隱藏狀態(tài)向量\(h_t\)。在這些單元中,\(h_t\)的構(gòu)造將比典型的RNN更復(fù)雜一點。計算分為4個組件,一個輸入門(input gate),一個遺忘門(forget gate),一個輸出門(output gate)和一個新的存儲容器。
每個門將使用\(x_t\)和\(h_t\)(圖中未顯示)作為輸入,并對它們執(zhí)行一些計算以獲得中間狀態(tài)。每個中間狀態(tài)被反饋到不同的管道中并最終把信息聚合成\(h_t\)的形式。為了簡便起見,我們不會對每個門的具體構(gòu)造進(jìn)行說明,但值得注意的是,每個門都可以被認(rèn)為是LSTM內(nèi)的不同模塊,每個模塊各有不同的功能。輸入門決定了每個輸入的權(quán)重,遺忘門決定我們將要丟棄什么樣的信息,輸出門決定最終基于中間狀態(tài)的\(h_t\)。若想了解不同門的功能和全部方程式,更詳細(xì)的信息請查看Christopher Olah的博客文章(譯者注:或者中文譯文)。
回顧第一個例子,問題是“兩個數(shù)字的和是多少”,該模型必須接受相似問答的訓(xùn)練,然后,LSTM單位將能認(rèn)識到?jīng)]有數(shù)字的任何句子可能不會對問題的答案產(chǎn)生影響,因此該單位將能夠利用其遺忘門來丟棄關(guān)于狗的不必要的信息,而保留有關(guān)數(shù)字的信息。
把情緒分析表述為深度學(xué)習(xí)問題
如前所屬,情緒分析的任務(wù)主要是輸入一序列句子并判斷情緒是正面的、負(fù)面的還是中性的。我們可以將這個特別的任務(wù)(和大多數(shù)其他NLP任務(wù))分成5個不同的步驟。
- 訓(xùn)練一個詞向量生成模型(比如Word2Vec)或者加載預(yù)訓(xùn)練的詞向量
- 為我們的訓(xùn)練集建立一個ID矩陣(稍后討論)
- RNN(使用LSTM單元)圖形創(chuàng)建
- 訓(xùn)練
- 測試
加載數(shù)據(jù)
首先,我們要創(chuàng)建詞向量。為簡單起見,我們將使用預(yù)訓(xùn)練好的模型。
作為機(jī)器學(xué)習(xí)這個游戲中的最大玩家,Google能夠在包含超過1000億個不同單詞的大規(guī)模Google新聞訓(xùn)練集上訓(xùn)練Word2Vec模型!從那個模型來看,Google能夠創(chuàng)建300萬個詞向量,每個向量的維數(shù)為300。
在理想情況下,我們將使用這些向量,但由于詞向量矩陣相當(dāng)大(3.6GB!),我們將使用一個更加可管理的矩陣,該矩陣由一個類似的詞向量生成模型Glove訓(xùn)練。矩陣將包含40萬個詞向量,每個維數(shù)為50。
我們將要導(dǎo)入兩個不同的數(shù)據(jù)結(jié)構(gòu),一個是一個40萬個單詞的Python列表,一個是擁有所有單詞向量值得40萬x50維嵌入矩陣。
import numpy as np
wordsList = np.load('wordsList.npy')
print('Loaded the word list!')
wordsList = wordsList.tolist() #Originally loaded as numpy array
wordsList = [word.decode('UTF-8') for word in wordsList] #Encode words as UTF-8
wordVectors = np.load('wordVectors.npy')
print ('Loaded the word vectors!')
為了確保一切都已正確加載,我們可以查看詞匯列表的維度和嵌入矩陣的維度。
print(len(wordsList))
print(wordVectors.shape)
我們還可以搜索單詞列表中的一個單詞,如“棒球”,然后通過嵌入矩陣訪問其對應(yīng)的向量。
baseballIndex = wordsList.index('baseball')
wordVectors[baseballIndex]
現(xiàn)在我們有了自己的向量,首先是輸入一個句子,然后構(gòu)造它的向量表示。假如我們有輸入句子“I thought the movie was incredible and inspiring”。為了獲取詞向量,我們可以使用Tensorflow的內(nèi)嵌查找函數(shù)。這個函數(shù)需要兩個參數(shù),一個是嵌入矩陣(在我們的例子中為詞向量矩陣),一個用于每個單詞的id。id向量可以認(rèn)為是訓(xùn)練集的整數(shù)表示。這基本只是每個單詞的行索引。讓我們來看一個具體的例子,使之具體化。
import tensorflow as tf
maxSeqLength = 10 #Maximum length of sentence
numDimensions = 300 #Dimensions for each word vector
firstSentence = np.zeros((maxSeqLength), dtype='int32')
firstSentence[0] = wordsList.index("i")
firstSentence[1] = wordsList.index("thought")
firstSentence[2] = wordsList.index("the")
firstSentence[3] = wordsList.index("movie")
firstSentence[4] = wordsList.index("was")
firstSentence[5] = wordsList.index("incredible")
firstSentence[6] = wordsList.index("and")
firstSentence[7] = wordsList.index("inspiring")
#firstSentence[8] and firstSentence[9] are going to be 0
print(firstSentence.shape)
print(firstSentence) #Shows the row index for each word
數(shù)據(jù)流水線如下圖所示。
[圖片上傳失敗...(image-4dde76-1510800115715)]
10 x 50的輸出應(yīng)包含序列中10個單詞中的每一個的50維字向量。
with tf.Session() as sess:
print(tf.nn.embedding_lookup(wordVectors,firstSentence).eval().shape)
在為整個訓(xùn)練集創(chuàng)建id矩陣之前,首先花一些時間為擁有的數(shù)據(jù)類型做一下可視化。這會幫助我們確定設(shè)定最大序列長度的最佳值。在先前的例子中,我們用的最大長度為10,但這個值很大程度取決于你的輸入。
我們要使用的訓(xùn)練集是Imdb電影評論數(shù)據(jù)集。這個集合中有25000個電影評論,12,500次正面評論和12,500次評論。每個評論都存儲在我們需要解析的txt文件中。積極的評論存儲在一個目錄中,負(fù)面評論存儲在另一個目錄中。以下代碼將確定每個評論中的平均字?jǐn)?shù)和總和。
from os import listdir
from os.path import isfile, join
positiveFiles = ['positiveReviews/' + f for f in listdir('positiveReviews/') if isfile(join('positiveReviews/', f))]
negativeFiles = ['negativeReviews/' + f for f in listdir('negativeReviews/') if isfile(join('negativeReviews/', f))]
numWords = []
for pf in positiveFiles:
with open(pf, "r", encoding='utf-8') as f:
line=f.readline()
counter = len(line.split())
numWords.append(counter)
print('Positive files finished')
for nf in negativeFiles:
with open(nf, "r", encoding='utf-8') as f:
line=f.readline()
counter = len(line.split())
numWords.append(counter)
print('Negative files finished')
numFiles = len(numWords)
print('The total number of files is', numFiles)
print('The total number of words in the files is', sum(numWords))
print('The average number of words in the files is', sum(numWords)/len(numWords))
我們還可以使用Matplot庫以直方圖的形式來顯示數(shù)據(jù)。
import matplotlib.pyplot as plt
%matplotlib inline
plt.hist(numWords, 50)
plt.xlabel('Sequence Length')
plt.ylabel('Frequency')
plt.axis([0, 1200, 0, 8000])
plt.show()
從直方圖及每個文件的平均字?jǐn)?shù)來看,我們可以確定大多數(shù)評論低于250詞,這時我們設(shè)置最大序列長度值。
maxSeqLength = 250
下面將展示如何將一個單一的文件轉(zhuǎn)換成id矩陣。如下是一條看起來像文本文件格式的評論。
fname = positiveFiles[3] #Can use any valid index (not just 3)
with open(fname) as f:
for lines in f:
print(lines)
exit
現(xiàn)在,轉(zhuǎn)換成一個id矩陣
# Removes punctuation, parentheses, question marks, etc., and leaves only alphanumeric characters
import re
strip_special_chars = re.compile("[^A-Za-z0-9 ]+")
def cleanSentences(string):
string = string.lower().replace("<br />", " ")
return re.sub(strip_special_chars, "", string.lower())
firstFile = np.zeros((maxSeqLength), dtype='int32')
with open(fname) as f:
indexCounter = 0
line=f.readline()
cleanedLine = cleanSentences(line)
split = cleanedLine.split()
for word in split:
try:
firstFile[indexCounter] = wordsList.index(word)
except ValueError:
firstFile[indexCounter] = 399999 #Vector for unknown words
indexCounter = indexCounter + 1
firstFile
現(xiàn)在,對我們這25000條評論做同樣的工作。加載電影訓(xùn)練集并整理它以獲得一個25000 x 250的矩陣。這是一個計算上昂貴的過程,因此,你不用再次運行整個程序,我們將加載預(yù)先計算的ID矩陣。
# ids = np.zeros((numFiles, maxSeqLength), dtype='int32')
# fileCounter = 0
# for pf in positiveFiles:
# with open(pf, "r") as f:
# indexCounter = 0
# line=f.readline()
# cleanedLine = cleanSentences(line)
# split = cleanedLine.split()
# for word in split:
# try:
# ids[fileCounter][indexCounter] = wordsList.index(word)
# except ValueError:
# ids[fileCounter][indexCounter] = 399999 #Vector for unkown words
# indexCounter = indexCounter + 1
# if indexCounter >= maxSeqLength:
# break
# fileCounter = fileCounter + 1
# for nf in negativeFiles:
# with open(nf, "r") as f:
# indexCounter = 0
# line=f.readline()
# cleanedLine = cleanSentences(line)
# split = cleanedLine.split()
# for word in split:
# try:
# ids[fileCounter][indexCounter] = wordsList.index(word)
# except ValueError:
# ids[fileCounter][indexCounter] = 399999 #Vector for unkown words
# indexCounter = indexCounter + 1
# if indexCounter >= maxSeqLength:
# break
# fileCounter = fileCounter + 1
# #Pass into embedding function and see if it evaluates.
# np.save('idsMatrix', ids)
ids = np.load('idsMatrix.npy')
輔助函數(shù)
下面你會發(fā)現(xiàn)一些在之后神經(jīng)網(wǎng)絡(luò)訓(xùn)練過程中很有用的輔助函數(shù)。
from random import randint
def getTrainBatch():
labels = []
arr = np.zeros([batchSize, maxSeqLength])
for i in range(batchSize):
if (i % 2 == 0):
num = randint(1,11499)
labels.append([1,0])
else:
num = randint(13499,24999)
labels.append([0,1])
arr[i] = ids[num-1:num]
return arr, labels
def getTestBatch():
labels = []
arr = np.zeros([batchSize, maxSeqLength])
for i in range(batchSize):
num = randint(11499,13499)
if (num <= 12499):
labels.append([1,0])
else:
labels.append([0,1])
arr[i] = ids[num-1:num]
return arr, labels
RNN模型
現(xiàn)在,我們準(zhǔn)備開始創(chuàng)建我們的Tensorflow圖。首先要定義一些超參數(shù),例如批處理大小,LSTM單元數(shù),輸出類數(shù)和訓(xùn)練次數(shù)。
batchSize = 24
lstmUnits = 64
numClasses = 2
iterations = 100000
與大多數(shù)Tensorflow圖一樣,我們現(xiàn)在需要指定兩個占位符,一個用于輸入到網(wǎng)絡(luò)中,一個用于標(biāo)簽。定義這些占位符的最重要的部分是了解每個維度。
標(biāo)簽占位符是一組值,每個值分別為[1,0]或[0,1],具體取決于每個訓(xùn)練示例是正還是負(fù)。輸入占位符中的整數(shù)每一行代表著我們在批處理中包含的每個訓(xùn)練示例的整數(shù)表示。
import tensorflow as tf
tf.reset_default_graph()
labels = tf.placeholder(tf.float32, [batchSize, numClasses])
input_data = tf.placeholder(tf.int32, [batchSize, maxSeqLength])
一旦我們有了輸入數(shù)據(jù)占位符,我們將調(diào)用tf.nn.lookup()函數(shù)來獲取詞向量。對該函數(shù)的調(diào)用會通過詞向量的維度返回長達(dá)最大序列長度的批大?。╞atch size)的3-D張量。為了可視化這個3-D張量,你可以簡單的把整數(shù)化輸入張量中的每個數(shù)據(jù)點看做對應(yīng)的相關(guān)D維向量。
data = tf.Variable(tf.zeros([batchSize, maxSeqLength, numDimensions]),dtype=tf.float32)
data = tf.nn.embedding_lookup(wordVectors,input_data)
現(xiàn)在我們有了想要的形式的數(shù)據(jù),嘗試如何把這些輸入填充進(jìn)LSTM網(wǎng)絡(luò)。我們將調(diào)用tf.nn.rnn_cell.BasicLSTMCell函數(shù)。這個函數(shù)輸入一個整數(shù)代表我們要用到的LSTM單元數(shù)。這是用來調(diào)整以利于確定最優(yōu)值的超參數(shù)之一。然后我們將LSTM單元包裝在一個退出層,以防止網(wǎng)絡(luò)過擬合。
最后,我們將充滿輸入數(shù)據(jù)的LSTM單元和3-D張量引入名為tf.nn.dynamic_rnn的函數(shù)中。該函數(shù)負(fù)責(zé)展開整個網(wǎng)絡(luò),并為數(shù)據(jù)流過RNN圖創(chuàng)建路徑。
lstmCell = tf.contrib.rnn.BasicLSTMCell(lstmUnits)
lstmCell = tf.contrib.rnn.DropoutWrapper(cell=lstmCell, output_keep_prob=0.75)
value, _ = tf.nn.dynamic_rnn(lstmCell, data, dtype=tf.float32)
作為一個備注,另一個更先進(jìn)的網(wǎng)絡(luò)架構(gòu)選擇是將多個LSTM神經(jīng)元堆疊在一起。也就是說第一個LSTM神經(jīng)元最后一個隱藏狀態(tài)向量導(dǎo)入第二個LSTM神經(jīng)元。堆疊這些神經(jīng)元是幫助模型保留更多長期以來信息的一個很好的方法,但也會在模型中引入更多的參數(shù),從而可能增加訓(xùn)練時間,增加更多對訓(xùn)練樣本的需求和過擬合的概率。有關(guān)如何把堆疊LSTM加入模型的更多信息,請查看Tensorflow文檔。
動態(tài)RNN函數(shù)的第一個輸出可以被認(rèn)為是最后一個隱藏的狀態(tài)向量。該向量將重新定形,然后乘以最終權(quán)重矩陣和偏置項以獲得最終輸出值。
weight = tf.Variable(tf.truncated_normal([lstmUnits, numClasses]))
bias = tf.Variable(tf.constant(0.1, shape=[numClasses]))
value = tf.transpose(value, [1, 0, 2])
last = tf.gather(value, int(value.get_shape()[0]) - 1)
prediction = (tf.matmul(last, weight) + bias)
接下來,我們將定義正確的預(yù)測和精度指標(biāo),以跟蹤網(wǎng)絡(luò)的運行情況。正確的預(yù)測公式通過查看2個輸出值的最大值的索引,然后查看它是否與訓(xùn)練標(biāo)簽相匹配來工作。
correctPred = tf.equal(tf.argmax(prediction,1), tf.argmax(labels,1))
accuracy = tf.reduce_mean(tf.cast(correctPred, tf.float32))
我們將基于最終預(yù)測值上的激活函數(shù)層定義標(biāo)準(zhǔn)交叉熵,使用Adam優(yōu)化器,默認(rèn)學(xué)習(xí)率為0.01。
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=prediction, labels=labels))
optimizer = tf.train.AdamOptimizer().minimize(loss)
如果你想使用Tensorboard來顯示損失和準(zhǔn)確度的值,還可以運行和修改以下代碼。
import datetime
tf.summary.scalar('Loss', loss)
tf.summary.scalar('Accuracy', accuracy)
merged = tf.summary.merge_all()
logdir = "tensorboard/" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S") + "/"
writer = tf.summary.FileWriter(logdir, sess.graph)
超參數(shù)調(diào)優(yōu)
為您的超參數(shù)選擇正確的值是有效訓(xùn)練深層神經(jīng)網(wǎng)絡(luò)的關(guān)鍵部分。您會發(fā)現(xiàn),您的訓(xùn)練損失曲線可能因您選擇的優(yōu)化器(Adam,Adadelta,SGD等),學(xué)習(xí)率和網(wǎng)絡(luò)架構(gòu)而不同。特別是使用RNN和LSTM時,要注意其他一些重要因素,包括LSTM單元的數(shù)量和字向量的大小。
- 由于有著大量的時間步,RNN的難以訓(xùn)練臭名昭著。學(xué)習(xí)率變得非常重要,因為我們不希望權(quán)重值因為學(xué)習(xí)率高而波動,也不想由于學(xué)習(xí)率低而需要緩慢地訓(xùn)練。默認(rèn)值為0.001是個好的開始,如果訓(xùn)練損失變化非常緩慢,你應(yīng)該增加此值,如果損失不穩(wěn)定,則應(yīng)減少。
- 優(yōu)化器:在研究人員之間尚沒有一致的選擇,但是由于具有自適應(yīng)學(xué)習(xí)速率這個屬性,Adam很受歡迎(請記住,優(yōu)化學(xué)習(xí)率可能隨著優(yōu)化器的選擇而不同)。
- LSTM單位數(shù):該值在很大程度上取決于輸入文本的平均長度。雖然更多的單位會使模型表達(dá)地更好,并允許模型存儲更多的信息用于較長的文本,但網(wǎng)絡(luò)將需要更長的時間才能訓(xùn)練,并且計算費用昂貴。
- 詞向量大?。涸~向量的維度一般在50到300之間。更大的尺寸意味著詞向量能夠封裝更多關(guān)于該詞的信息,但模型也將花費更多計算量。
訓(xùn)練
訓(xùn)練循環(huán)的基本思路是首先定義一個Tensorflow session,然后加載一批評論及其相關(guān)標(biāo)簽。接下來,我們調(diào)用session的run函數(shù),該函數(shù)有兩個參數(shù),第一個被稱為“fetches”參數(shù),它定義了我們想要計算的期望值,我們希望優(yōu)化器能夠計算出來,因為這是使損失函數(shù)最小化的組件。第二個參數(shù)需要輸入我們的feed_dict,這個數(shù)據(jù)結(jié)構(gòu)是我們?yōu)樗姓嘉环峁┹斎氲牡胤?。我們需要提供評論和標(biāo)簽的批次,然后這個循環(huán)在一組訓(xùn)練迭代器上重復(fù)執(zhí)行。
我們將會加載一個預(yù)訓(xùn)練模型而不是在這款notebook上訓(xùn)練網(wǎng)絡(luò)(這需要幾個小時)。
如果你決定在自己的機(jī)器上訓(xùn)練這個模型,你可以使用TensorBoard來跟蹤訓(xùn)練過程。當(dāng)以下代碼在運行時,使用你的終端進(jìn)入此代碼的執(zhí)行目錄,輸入tensorboard --logdir=tensorboard,并使用瀏覽器訪問http://localhost:6006/,以對訓(xùn)練過程保持關(guān)注。
# sess = tf.InteractiveSession()
# saver = tf.train.Saver()
# sess.run(tf.global_variables_initializer())
# for i in range(iterations):
# #Next Batch of reviews
# nextBatch, nextBatchLabels = getTrainBatch();
# sess.run(optimizer, {input_data: nextBatch, labels: nextBatchLabels})
# #Write summary to Tensorboard
# if (i % 50 == 0):
# summary = sess.run(merged, {input_data: nextBatch, labels: nextBatchLabels})
# writer.add_summary(summary, i)
# #Save the network every 10,000 training iterations
# if (i % 10000 == 0 and i != 0):
# save_path = saver.save(sess, "models/pretrained_lstm.ckpt", global_step=i)
# print("saved to %s" % save_path)
# writer.close()
加載預(yù)訓(xùn)練模型
我們的預(yù)訓(xùn)練模型在訓(xùn)練過程中的精度和損失曲線如下所示。
查看如上訓(xùn)練曲線,似乎模型的訓(xùn)練進(jìn)展順利。虧損穩(wěn)步下降,準(zhǔn)確率接近100%。然而,在分析訓(xùn)練曲線時,我們還應(yīng)該特別注意模型對訓(xùn)練數(shù)據(jù)集過擬合的可能。過擬合是機(jī)器學(xué)習(xí)中的常見現(xiàn)象,模型變得適合于訓(xùn)練模型而失去了推廣到測試集的能力。這意味著訓(xùn)練一個網(wǎng)絡(luò)直達(dá)到0訓(xùn)練損失可能不是一個最好的方式,來獲取在一個從未見過的數(shù)據(jù)集上表現(xiàn)良好的準(zhǔn)確模型。早停(early stopping)是一種直觀的技術(shù),普遍應(yīng)用于LSTM網(wǎng)絡(luò)來解決過擬合問題?;舅枷胧窃谟?xùn)練集上訓(xùn)練模型,同時還可以一次次在測試集上測量其性能。一旦測試錯誤停止了穩(wěn)定的下降并開始增加,我們會知道該停止訓(xùn)練,以為這是神經(jīng)網(wǎng)絡(luò)開始過擬合的信號。
加載預(yù)訓(xùn)練模型涉及定義另一個Tensorflow會話,創(chuàng)建Saver對象,然后使用該對象調(diào)用恢復(fù)功能。此函數(shù)接受2個參數(shù),一個用于當(dāng)前會話,另一個用于保存模型的名稱。
sess = tf.InteractiveSession()
saver = tf.train.Saver()
saver.restore(sess, tf.train.latest_checkpoint('models'))
然后我們將從測試集加載一些電影評論,注意,這些評論是模型從未訓(xùn)練過的。運行以下代碼時可以看到每批測試的精準(zhǔn)度。
iterations = 10
for i in range(iterations):
nextBatch, nextBatchLabels = getTestBatch();
print("Accuracy for this batch:", (sess.run(accuracy, {input_data: nextBatch, labels: nextBatchLabels})) * 100)
結(jié)論
在這篇筆記中,我們對情緒分析進(jìn)行了深入地學(xué)習(xí)。我們研究了整個流程中涉及的不同組件,然后研究了在實踐中編寫Tensorflow代碼來實現(xiàn)模型的過程。最后,我們對模型進(jìn)行了培訓(xùn)和測試,以便能夠?qū)﹄娪霸u論進(jìn)行分類。
在Tensorflow的幫助下,你可以創(chuàng)建自己的情緒分類器,以了解世界上大量的自然語言,并使用結(jié)果形成具有說服力的論點。感謝您的閱讀。