前言
本篇文章會從代碼的角度說明如何基於TFlearn使用LSTM進行文本的情感分類。如果對於TFlearn和LSTM都不熟悉,沒有關係,先硬着頭皮將代碼看下(使用LSTM對IMDB數據集進行情感分類)。從代碼的角度看都是很簡潔的,所以即使不熟悉,多看看代碼,當代碼已經熟練於心了,後面如果有一天你漠然回首理解了其中的不解後,你的記憶更加深刻。所以不懂、不熟悉沒關係,堅持下去就回明白的。
由於實例代碼使用的是IMDB數據集,所以這裏會優先介紹 一下這個數據集。
IMDB數據集
該數據集包含了電影的評論以及評論對應的情感分類的標籤(0,1分類)。作者的初衷是希望該數據集會成爲情緒分類的一個基準。這裏介紹該數據集如何生成的以及如何使用提供的文件。
核心數據集包含了5萬條評論數據,這些數據被均分成訓練集和測試集(訓練和測試集各2.5萬)。標籤也是均衡分佈的(正負樣本各2.5萬)。也提供了5萬條無標籤數據,以用於無監督學習。
在數據集中,每個電影最多收集30條評論,因爲同一個電影的評論往往具有相關性。同時訓練集和測試集採集的是不同的電影,所以嘗試去記住和電影強相關的詞彙以及相關的標籤是不會取得顯著的提升效果的。
在訓練和測試集中,負面結果的分值<=4,正面結果的分值>=10.中性的評論沒有包含在測試和訓練集合中。在無監督的數據集中包含任意評分的評論。
對於下載下來的數據集的文件結構大致如下:
有兩個頂級文件夾[train/, test/],對應訓練集和測試集。每個都包含了[pos/, neg/]目錄,在這些文件夾中,評論數據以如下方式存儲:[[id]_[rating].txt]。這裏id表示唯一性ID,rating表示評分,例如[test/pos/200_8.txt]表示正面評論,id是200,評分是8分。
無監督數據集中[train/unsup/]所有的評分都是0,因爲所有的評分都被省略了。
數據集中也包含了每個評論對應電影的評論頁面的URL,由於電影的評論數據是動態變化的,所以不能指定評論的URL,只能指定電影評論頁面的URL。評論文件在如下文件中:
[urls_[pos, neg, unsup].txt]
對於評論的數據文件,數據集中已經包含了訓練好的詞袋模型(BoW).這些數據存儲在.feat文件中。
每個.feat文件都是LIBSVM格式,一種用於標記數據的ascii的稀疏向量格式。
這些文件中的特徵索引從0開始,且特徵索引對應的詞彙對應着[imdb.vocab]中相應的詞彙。所以一個在.feat文件中以0:7的形式表示[imdb.vocab]中的第一個單詞,在該評論中出現7次
LIBSVM相關資料參見:LIBSVM
數據集中也包含了一個[imdbEr.txt]文件,這裏存儲了[imdb.vocab]中每個詞的情感評分。預期評級是瞭解數據集中單詞的平均極性的好方法。
數據集介紹就到這裏,下面開始代碼解讀。
代碼解讀
# -*- coding: utf-8 -*-
"""
https://www.tensorflow.org/versions/master/programmers_guide/embedding
https://github.com/tflearn/tflearn/blob/master/tflearn/datasets/imdb.py
採用LSTM進行情感分類的實例,數據集採用IMDB數據集
LSTM論文鏈接:
http://deeplearning.cs.cmu.edu/pdfs/Hochreiter97_lstm.pdf
IMDB數據集鏈接
http://ai.stanford.edu/~amaas/data/sentiment/
"""
引入相關的模塊和方法
from __future__ import division, print_function, absolute_import
import tflearn
from tflearn.data_utils import to_categorical, pad_sequences
from tflearn.datasets import imdb
獲得測試和訓練數據
# 導入IMDB的數據集,n_words表示構建詞向量的時候,考慮最常用的10000個詞,valid_portion表示訓練過程中採用1/10的數據進行驗證
# load_data的結果是train[0][0:]表示訓練數據,train[1][0:]表示對應的標籤,即train[0]是訓練矩陣,train[1]是標籤矩陣
train, test, _ = imdb.load_data(path='imdb.pkl', n_words=10000,valid_portion=0.1)
# 獲得訓練集對應的數據和標籤
trainX, trainY = train
# 獲得測試集對應的數據和標籤
testX, testY = test
數據預處理
# 進行數據處理,補充長度,長度全爲100,不足的0補位,每條訓練數據都變成100位的向量
trainX = pad_sequences(trainX, maxlen=100, value=0.)
testX = pad_sequences(testX, maxlen=100, value=0.)
# 將數據的打標轉化爲向量 原來是0->[1,0],原來是1->[0,1]
trainY = to_categorical(trainY, nb_classes=2)
testY = to_categorical(testY, nb_classes=2)
網絡構建
# 構建網絡
# 1.先指定輸入數據數據量大小不指定,和placeholder類似,在運行時指定,每個向量100維 <tf.Tensor 'InputData/X:0' shape=(?, 100) dtype=float32>
net = tflearn.input_data([None, 100])
# 2.進行詞嵌套,相當於將離散的變爲連續的,輸入詞詞有10000個ID,每個ID對應一個詞,將每個詞變爲一個128維的向量
# <tf.Tensor 'Embedding/embedding_lookup:0' shape=(?, 100, 128) dtype=float32>
#Embedding可以將離散的輸入應用於機器學習處理方法中。傳統的分類器和神經網絡一般來講更適合處理連續的向量,
#如果有些離散對象自然被編碼爲離散的原子,例如獨特的ID,它們不利於機器學習的使用和泛化。
#可以理解embedding是將非矢量對象轉換爲利於機器學習處理的輸入。
net = tflearn.embedding(net, input_dim=10000, output_dim=128)
# 3.LSTM,輸出<tf.Tensor 'LSTM/LSTM/cond_199/Merge:0' shape=(?, 128) dtype=float32>
net = tflearn.lstm(net, 128, dropout=0.8)
# 4.全連接層 輸出<tf.Tensor 'FullyConnected/Softmax:0' shape=(?, 2) dtype=float32>
# 就是將學到的特徵表示映射到樣本標記空間
net = tflearn.fully_connected(net, 2, activation='softmax')
# 5.迴歸層 指定優化方法、學習速率(步長)、以及損失函數
net = tflearn.regression(net, optimizer='adam', learning_rate=0.001,loss='categorical_crossentropy')
構建模型並訓練
# 構建深度模型,tensorboard需要的日誌文件存儲在/tmp/tflearn_logs中
model = tflearn.DNN(net, tensorboard_verbose=0)
# 訓練模型,指定訓練數據集、測試數據集
model.fit(trainX, trainY, validation_set=(testX, testY), show_metric=True,batch_size=32)
到這裏整個代碼就結束了,這裏有兩個地方需要說明一下,一個是embedding,一個是fully_connected,分別表示詞嵌套和全連接。這裏對這兩部分簡要說下,不會進行詳盡的公式推導和闡釋。
首先說說這裏的embedding.
在註釋部分已經註釋的比較明確了,其目的就是將要表示的東西進行向量化表示。原來每個字用一個ID表示,這樣能表示的信息太少了,不能夠表達詞所在語料內更多的意思,比如和那個詞更相近。通過詞嵌套將一個單一的ID表示爲一個128緯度(此處是128)的向量,能夠表達更多的意思。詞嵌套是向量化的一個重要手段,這個技巧一定要掌握的。
再聊聊這裏的fully_connected
全連接層(fully connected layers,FC),在整個神經網絡中起到類似於“分類器”的作用。如果說卷積層、池化層和激活函數層等操作是將原始數據映射到隱層特徵空間的話,那麼全連接層則起到將學到的“分佈式特徵表示”映射到樣本標記空間的作用。在代碼中tflearn.fully_connected(net, 2,activation=’softmax’),這裏的第二個參數2,表示的就是輸出神經元的個數,即訓練集中對應的打標的向量,也就是說將學習到的分佈式特徵轉化爲一個只包含兩個元素的向量。
全連接層的每一個結點都與上一層的所有結點相連,用來把前邊提取到的特徵綜合起來。由於其全相連的特性,一般全連接層的參數也是最多的。不負責任的講,全連接層一般由兩部分組成,即線性部分和非線性部分。線性部分主要做線性轉換,輸入用X表示,輸出用Z表示。
線性部分的運算方法基本上就是線性加權求和的感覺,如果對於一個輸入向量
x=[x_0,x_1,...x_n]^T,
線性部分的輸出向量是
z=[z_0,z_1,z_2,...z_m]^T,
那麼線性部分的參數就可以想象一個m*n的矩陣W,再加上一個偏置項
b=[b_0,...b_m]^T
於是有:
W*x+b=z
對於非線性部分,那當然是做非線性變換了,輸入用線性部分的輸出Z表示,輸出用Y表示,假設用sigmoid作爲非線性激活,那麼有
Y = sigmoid(Z)
那麼爲什麼要有非線性部分呢?個人理解,其一是作數據的歸一化。不管前面的線性部分做了怎樣的工作,到了非線性這裏,所有的數值將被限制在一個範圍內,這樣後面的網絡層如果要基於前面層的數據繼續計算,這個數值就相對可控了。其二就是打破之前的線性映射關係。如果全連接層沒有非線性部分,只有線性部分,我們在模型中疊加多層神經網絡是沒有意義的,我們假設有一個2層全連接神經網絡,其中沒有非線性層,那麼對於第一層有:
W^0*x^0+b^0=z^1
對於第二層有:
W^1*z^1+b^1=z^2
兩式合併,有:
W^1*(W^0*x^0+b^0)+b^1=z^2
W^1*W^0*x^0+(W^1*b^0+b^1)=z^2
所以我們只要令:
W^{0'}=W^1*W^0 ,
b^{0'}=W^1*b^0+b^1,
就可以用一層神經網絡表示之前的兩層神經網絡了。所以非線性層的加入,使得多層神經網絡的存在有了意義。
關於非線性激活常用的函數,以及什麼樣的激活函數纔是好的激活函數後面會專門介紹,這裏不再贅述。
我們在看一下tflearn中fully_connected的函數的參數解釋,當然tensorflow中也有相關函數,這裏暫不進行註解。
tflearn.layers.core.fully_connected
incoming: 輸入Tensor,維度不小於2
n_units: 整型,表示輸出神經元個數
activation: 激活函數,輸入激活函數的名稱或者函數定義(自定義非線性激活函數),默認值:'linear',參見 tflearn.activations
bias: 布爾值,表示是否使用偏置項
weights_init: 初始化權重W的參數,String 或者一個Tensor,默認是truncated_normal,參見tflearn.initializations
bias_init: 初始化偏置項,String或Tensor,默認'zeros'。參見tflearn.initializations
regularizer: 規範化函數,String或者一個Tensor,對權重W進行規範化操作,默認不進行,參見tflearn.regularizers
weight_decay: 浮點數,規範化方法的衰減參數,默認值 0.001.
trainable: 可選參數,布爾值,如果爲True,那麼變量將會加到圖模型中。同tf.Variable的trainable
restore: bool. If True, this layer weights will be restored when loading a model.
reuse: 可選參數,布爾類型,如果指定了Scope且當前參數置爲True,那麼該layer的變量可複用
scope: 可選參數,String類型,定義layer的Scope(指定Scope可以在不同層間共享變量,但需要注意Scope可被覆蓋,需自行控制其唯一性)
name: 可選值,表示該層的名稱,默認值: 'FullyConnected'.
總結
基於tflearn進行深度學習相關功能的實現,要比基於原生的tensorflow要簡單的多,封裝的比較乾淨。本篇只介紹了使用LSTM進行NLP的相關任務處理,讀者完全可以自行下載代碼進行改造,這裏希望讀者能夠了解embedding和fully_connected的意思,知道其作用,如果希望深入瞭解,可以參考相關論文和他人註解的blog,本篇就寫到這裏,歡迎拍磚。