使用 TensorFlow 做文本情感分析

<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ù)管道。

data_pipeline

這種通道是有問題的。我們無法在單個字符串上進(jìn)行像點乘或者反向傳播這樣的常見操作。我們需要把句子中的每個單詞轉(zhuǎn)換成一個向量而不是僅僅輸入字符串。


word_vector

你可以將情緒分析模塊的輸入看做一個16 x D維矩陣。

我們希望以方便表示單詞及其上下文、意義和語義的方式來創(chuàng)建這些向量。例如,我們希望“愛”和“崇拜”這些向量駐留在向量空間中相對相同的區(qū)域中,因為它們都具有相似的定義,并且在相似的上下文中使用。一個單詞的向量表示也稱為詞嵌入。


love_adore

Word2Vec

為了創(chuàng)建這些單詞嵌入,我們將使用通常被稱為“Word2Vec”的模型。模型通過查看語句在句子中出現(xiàn)的上下文來創(chuàng)建詞矢量而忽略細(xì)節(jié)。具有相似上下文的單詞將在向量空間中放置在相近的位置。在自然語言中,當(dāng)嘗試確定其含義時,單詞的上下文可能非常重要。正如我們之前”崇拜“和”愛“的例子,


sample2

從句子的上下文可以看出,這兩個詞通常用于具有正面內(nèi)涵的句子,通常在名詞或名詞短語之前。這表明這兩個詞都有一些共同點,可能是同義詞??紤]句子中的語法結(jié)構(gòu)時,語境也很重要。大多數(shù)句子將遵循具有動詞跟隨名詞的傳統(tǒng)范例,形容詞先于名詞等等。因此,該模型更有可能將名詞與其他名詞相同。該模型采用大量句子數(shù)據(jù)集(例如英文維基百科),并為語料庫中的每個不同詞輸出向量。Word2Vec模型的輸出稱為嵌入矩陣(embedding matrix)。


wikipedia

該嵌入矩陣將包含訓(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é)點組成。


feedforwardNN

前饋神經(jīng)網(wǎng)絡(luò)和遞歸神經(jīng)網(wǎng)絡(luò)的主要區(qū)別在于后者的時間性。在RNN中,輸入序列的每個單詞都與特定的時間步長相關(guān)聯(lián)。實際上,時間步長的數(shù)量將等于最大序列長度。


time_steps

一個稱為隱藏狀態(tài)向量\(h_t\)的新組件也與每個時間步相聯(lián)系。從高層次來看,這個向量旨在封裝并總結(jié)在之前的時間步中看到的所有信息。就像\(x_t\)是封裝特定單詞的所有信息的向量,\(h_t\)是一個向量,總結(jié)了之前時間步長的所有信息。

隱藏狀態(tài)是當(dāng)前詞向量和前一時間步的的函數(shù)。σ表示兩項和代入一個激活函數(shù)(通常為S形或tanh)


hidden_state

上述公式中的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ù)面的概率。

softmax_classify

長短期記憶單元(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)容提出問題。我們來看下面的例子:

passage_question

我們可以看出中間的句子對被提出的問題沒有影響。然而,第一句和第三句之間有很強(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)和一個新的存儲容器。

LSTM_unit

每個門將使用\(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個不同的步驟。

  1. 訓(xùn)練一個詞向量生成模型(比如Word2Vec)或者加載預(yù)訓(xùn)練的詞向量
  2. 為我們的訓(xùn)練集建立一個ID矩陣(稍后討論)
  3. RNN(使用LSTM單元)圖形創(chuàng)建
  4. 訓(xùn)練
  5. 測試

加載數(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ù)表示。

integerized_input
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維向量。


integerized_input2
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)練過程中的精度和損失曲線如下所示。


accuracy
loss

查看如上訓(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é)果形成具有說服力的論點。感謝您的閱讀。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內(nèi)容

  • 作者 | 武維AI前線出品| ID:ai-front 前言 自然語言處理(簡稱NLP),是研究計算機(jī)處理人類語言的...
    AI前線閱讀 2,581評論 0 8
  • 前面的文章主要從理論的角度介紹了自然語言人機(jī)對話系統(tǒng)所可能涉及到的多個領(lǐng)域的經(jīng)典模型和基礎(chǔ)知識。這篇文章,甚至之后...
    我偏笑_NSNirvana閱讀 13,965評論 2 64
  • 近日,谷歌官方在 Github開放了一份神經(jīng)機(jī)器翻譯教程,該教程從基本概念實現(xiàn)開始,首先搭建了一個簡單的NMT模型...
    MiracleJQ閱讀 6,434評論 1 11
  • 承接前面的《淺談機(jī)器學(xué)習(xí)基礎(chǔ)》、《淺談深度學(xué)習(xí)基礎(chǔ)》和《淺談自然語言處理基礎(chǔ)》,主要參考了《解析深度學(xué)習(xí):語音識別...
    我偏笑_NSNirvana閱讀 23,577評論 6 67
  • 今天有雨,我出門,沒有車,沒有傘,沒有人陪。從801到149,三年的時光在車廂里跌跌撞撞。摩卡在胃里翻江倒海,我想...
    薄荷糖花閱讀 905評論 0 0