本文做者:在線實驗室javascript
文章結構:html
背景介紹——模型概覽——數據集介紹——配置模型——訓練模型——應用模型——應用模型並進行預測——總結——參考文獻java
本教程源代碼目錄在book/understand_sentiment,初次使用請您參考Book文檔使用說明。python
在天然語言處理中,情感分析通常是指判斷一段文本所表達的情緒狀態。其中,一段文本能夠是一個句子,一個段落或一個文檔。情緒狀態能夠是兩類,如(正面,負面),(高興,悲傷);也能夠是三類,如(積極,消極,中性)等等。情感分析的應用場景十分普遍,如把用戶在購物網站(亞馬遜、天貓、淘寶等)、旅遊網站、電影評論網站上發表的評論分紅正面評論和負面評論;或爲了分析用戶對於某一產品的總體使用感覺,抓取產品的用戶評論並進行情感分析等等。表格1展現了對電影評論進行情感分析的例子:git
在天然語言處理中,情感分析屬於典型的文本分類問題,即把須要進行情感分析的文本劃分爲其所屬類別。文本分類涉及文本表示和分類方法兩個問題。在深度學習的方法出現以前,主流的文本表示方法爲詞袋模型BOW(bag of words),話題模型等等;分類方法有SVM(support vector machine), LR(logistic regression)等等。github
對於一段文本,BOW表示會忽略其詞順序、語法和句法,將這段文本僅僅看作是一個詞集合,所以BOW方法並不能充分表示文本的語義信息。例如,句子「這部電影糟糕透了」和「一個乏味,空洞,沒有內涵的做品」在情感分析中具備很高的語義類似度,可是它們的BOW表示的類似度爲0。又如,句子「一個空洞,沒有內涵的做品」和「一個不空洞並且有內涵的做品」的BOW類似度很高,但實際上它們的意思很不同。web
本章咱們所要介紹的深度學習模型克服了BOW表示的上述缺陷,它在考慮詞順序的基礎上把文本映射到低維度的語義空間,而且以端對端(end to end)的方式進行文本表示及分類,其性能相對於傳統方法有顯著的提高[1]。算法
模型概覽緩存
本章所使用的文本表示模型爲卷積神經網絡(Convolutional Neural Networks)和循環神經網絡(Recurrent Neural Networks)及其擴展。下面依次介紹這幾個模型。網絡
文本卷積神經網絡簡介(CNN)
咱們在推薦系統一節介紹過應用於文本數據的卷積神經網絡模型的計算過程,這裏進行一個簡單的回顧。
對卷積神經網絡來講,首先使用卷積處理輸入的詞向量序列,產生一個特徵圖(feature map),對特徵圖採用時間維度上的最大池化(max pooling over time)操做獲得此卷積覈對應的整句話的特徵,最後,將全部卷積核獲得的特徵拼接起來即爲文本的定長向量表示,對於文本分類問題,將其鏈接至softmax即構建出完整的模型。在實際應用中,咱們會使用多個卷積核來處理句子,窗口大小相同的卷積核堆疊起來造成一個矩陣,這樣能夠更高效的完成運算。另外,咱們也可以使用窗口大小不一樣的卷積核來處理句子,推薦系統一節的圖3做爲示意畫了四個卷積核,既文本圖1,不一樣顏色表示不一樣大小的卷積核操做。
圖1. 卷積神經網絡文本分類模型
對於通常的短文本分類問題,上文所述的簡單的文本卷積網絡便可達到很高的正確率[1]。若想獲得更抽象更高級的文本特徵表示,能夠構建深層文本卷積神經網絡[2,3]。
循環神經網絡(RNN)
循環神經網絡是一種能對序列數據進行精確建模的有力工具。實際上,循環神經網絡的理論計算能力是圖靈完備的[4]。天然語言是一種典型的序列數據(詞序列),近年來,循環神經網絡及其變體(如long short term memory[5]等)在天然語言處理的多個領域,如語言模型、句法解析、語義角色標註(或通常的序列標註)、語義表示、圖文生成、對話、機器翻譯等任務上均表現優異甚至成爲目前效果最好的方法。
圖2. 循環神經網絡按時間展開的示意圖
循環神經網絡按時間展開後如圖2所示:在第tt時刻,網絡讀入第tt個輸入xtxt(向量表示)及前一時刻隱層的狀態值ht−1ht−1(向量表示,h0h0通常初始化爲00向量),計算得出本時刻隱層的狀態值htht,重複這一步驟直至讀完全部輸入。若是將循環神經網絡所表示的函數記爲ff,則其公式可表示爲:
其中WxhWxh是輸入到隱層的矩陣參數,WhhWhh是隱層到隱層的矩陣參數,bhbh爲隱層的偏置向量(bias)參數,σσ爲sigmoidsigmoid函數。
在處理天然語言時,通常會先將詞(one-hot表示)映射爲其詞向量表示,而後再做爲循環神經網絡每一時刻的輸入xtxt。此外,能夠根據實際須要的不一樣在循環神經網絡的隱層上鍊接其它層。如,能夠把一個循環神經網絡的隱層輸出鏈接至下一個循環神經網絡的輸入構建深層(deep or stacked)循環神經網絡,或者提取最後一個時刻的隱層狀態做爲句子表示進而使用分類模型等等。
長短時間記憶網絡(LSTM)
對於較長的序列數據,循環神經網絡的訓練過程當中容易出現梯度消失或爆炸現象[6]。爲了解決這一問題,Hochreiter S, Schmidhuber J. (1997)提出了LSTM(long short term memory[5])。
相比於簡單的循環神經網絡,LSTM增長了記憶單元cc、輸入門ii、遺忘門ff及輸出門oo。這些門及記憶單元組合起來大大提高了循環神經網絡處理長序列數據的能力。若將基於LSTM的循環神經網絡表示的函數記爲FF,則其公式爲:
F由下列公式組合而成[7]:
其中,分別表示輸入門,遺忘門,記憶單元及輸出門的向量值,帶角標的W及b爲模型參數,tanh爲雙曲正切函數,⊙表示逐元素(elementwise)的乘法操做。輸入門控制着新輸入進入記憶單元cc的強度,遺忘門控制着記憶單元維持上一時刻值的強度,輸出門控制着輸出記憶單元的強度。三種門的計算方式相似,但有着徹底不一樣的參數,它們各自以不一樣的方式控制着記憶單元cc,如圖3所示:
圖3. 時刻tt的LSTM [7]
LSTM經過給簡單的循環神經網絡增長記憶及控制門的方式,加強了其處理遠距離依賴問題的能力。相似原理的改進還有Gated Recurrent Unit (GRU)[8],其設計更爲簡潔一些。這些改進雖然各有不一樣,可是它們的宏觀描述卻與簡單的循環神經網絡同樣(如圖2所示),即隱狀態依據當前輸入及前一時刻的隱狀態來改變,不斷地循環這一過程直至輸入處理完畢:
其中,RecrurentRecrurent能夠表示簡單的循環神經網絡、GRU或LSTM。
棧式雙向LSTM(Stacked Bidirectional LSTM)
對於正常順序的循環神經網絡,htht包含了tt時刻以前的輸入信息,也就是上文信息。一樣,爲了獲得下文信息,咱們可使用反方向(將輸入逆序處理)的循環神經網絡。結合構建深層循環神經網絡的方法(深層神經網絡每每能獲得更抽象和高級的特徵表示),咱們能夠經過構建更增強有力的基於LSTM的棧式雙向循環神經網絡[9],來對時序數據進行建模。
如圖4所示(以三層爲例),奇數層LSTM正向,偶數層LSTM反向,高一層的LSTM使用低一層LSTM及以前全部層的信息做爲輸入,對最高層LSTM序列使用時間維度上的最大池化便可獲得文本的定長向量表示(這一表示充分融合了文本的上下文信息,而且對文本進行了深層次抽象),最後咱們將文本表示鏈接至softmax構建分類模型。
圖4. 棧式雙向LSTM用於文本分類
數據集介紹
咱們以IMDB情感分析數據集爲例進行介紹。IMDB數據集的訓練集和測試集分別包含25000個已標註過的電影評論。其中,負面評論的得分小於等於4,正面評論的得分大於等於7,滿分10分。
aclImdb |- test |-- neg |-- pos |- train |-- neg |-- pos
Paddle在dataset/imdb.py
中提實現了imdb數據集的自動下載和讀取,並提供了讀取字典、訓練數據、測試數據等API。
配置模型
在該示例中,咱們實現了兩種文本分類算法,分別基於推薦系統一節介紹過的文本卷積神經網絡,以及棧式雙向LSTM。咱們首先引入要用到的庫和定義全局變量:
from __future__ import print_function import paddle import paddle.fluid as fluid import numpy as np import sys import math CLASS_DIM = 2 #情感分類的類別數 EMB_DIM = 128 #詞向量的維度 HID_DIM = 512 #隱藏層的維度 STACKED_NUM = 3 #LSTM雙向棧的層數 BATCH_SIZE = 128 #batch的大小
文本卷積神經網絡
咱們構建神經網絡convolution_net
,示例代碼以下。 須要注意的是:fluid.nets.sequence_conv_pool
包含卷積和池化層兩個操做。
#文本卷積神經網絡 def convolution_net(data, input_dim, class_dim, emb_dim, hid_dim): emb = fluid.layers.embedding( input=data, size=[input_dim, emb_dim], is_sparse=True) conv_3 = fluid.nets.sequence_conv_pool( input=emb, num_filters=hid_dim, filter_size=3, act="tanh", pool_type="sqrt") conv_4 = fluid.nets.sequence_conv_pool( input=emb, num_filters=hid_dim, filter_size=4, act="tanh", pool_type="sqrt") prediction = fluid.layers.fc( input=[conv_3, conv_4], size=class_dim, act="softmax") return prediction
網絡的輸入表示的是詞典的大小,表示類別數。這裏,咱們使用 API實現了卷積和池化操做。input_dimclass_dimsequence_conv_pool
棧式雙向LSTM
棧式雙向神經網絡stacked_lstm_net
的代碼片斷以下:
#棧式雙向LSTM def stacked_lstm_net(data, input_dim, class_dim, emb_dim, hid_dim, stacked_num): #計算詞向量 emb = fluid.layers.embedding( input=data, size=[input_dim, emb_dim], is_sparse=True) #第一層棧 #全鏈接層 fc1 = fluid.layers.fc(input=emb, size=hid_dim) #lstm層 lstm1, cell1 = fluid.layers.dynamic_lstm(input=fc1, size=hid_dim) inputs = [fc1, lstm1] #其他的全部棧結構 for i in range(2, stacked_num + 1): fc = fluid.layers.fc(input=inputs, size=hid_dim) lstm, cell = fluid.layers.dynamic_lstm( input=fc, size=hid_dim, is_reverse=(i % 2) == 0) inputs = [fc, lstm] #池化層 fc_last = fluid.layers.sequence_pool(input=inputs[0], pool_type='max') lstm_last = fluid.layers.sequence_pool(input=inputs[1], pool_type='max') #全鏈接層,softmax預測 prediction = fluid.layers.fc( input=[fc_last, lstm_last], size=class_dim, act='softmax') return prediction
以上的棧式雙向LSTM抽象出了高級特徵並把其映射到和分類類別數一樣大小的向量上。最後一個全鏈接層的'softmax'激活函數用來計算分類屬於某個類別的機率。
重申一下,此處咱們能夠調用convolution_net
或stacked_lstm_net
的任何一個網絡結構進行訓練學習。咱們以convolution_net
爲例。
接下來咱們定義預測程序(inference_program
)。預測程序使用convolution_net
來對fluid.layer.data
的輸入進行預測。
def inference_program(word_dict): data = fluid.layers.data( name="words", shape=[1], dtype="int64", lod_level=1) dict_dim = len(word_dict) net = convolution_net(data, dict_dim, CLASS_DIM, EMB_DIM, HID_DIM) # net = stacked_lstm_net(data, dict_dim, CLASS_DIM, EMB_DIM, HID_DIM, STACKED_NUM) return net
咱們這裏定義了training_program
。它使用了從inference_program
返回的結果來計算偏差。咱們同時定義了優化函數optimizer_func
。
由於是有監督的學習,訓練集的標籤也在fluid.layers.data
中定義了。在訓練過程當中,交叉熵用來在fluid.layer.cross_entropy
中做爲損失函數。
在測試過程當中,分類器會計算各個輸出的機率。第一個返回的數值規定爲cost。
def train_program(prediction): label = fluid.layers.data(name="label", shape=[1], dtype="int64") cost = fluid.layers.cross_entropy(input=prediction, label=label) avg_cost = fluid.layers.mean(cost) accuracy = fluid.layers.accuracy(input=prediction, label=label) return [avg_cost, accuracy] #返回平均cost和準確率acc #優化函數 def optimizer_func(): return fluid.optimizer.Adagrad(learning_rate=0.002)
訓練模型
定義訓練環境
定義您的訓練是在CPU上仍是在GPU上:
use_cuda = False #在cpu上進行訓練 place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
定義數據提供器
下一步是爲訓練和測試定義數據提供器。提供器讀入一個大小爲 BATCH_SIZE的數據。paddle.dataset.imdb.word_dict 每次會在亂序化後提供一個大小爲BATCH_SIZE的數據,亂序化的大小爲緩存大小buf_size。
注意:讀取IMDB的數據可能會花費幾分鐘的時間,請耐心等待。
print("Loading IMDB word dict....") word_dict = paddle.dataset.imdb.word_dict() print ("Reading training data....") train_reader = paddle.batch( paddle.reader.shuffle( paddle.dataset.imdb.train(word_dict), buf_size=25000), batch_size=BATCH_SIZE) print("Reading testing data....") test_reader = paddle.batch( paddle.dataset.imdb.test(word_dict), batch_size=BATCH_SIZE)
word_dict是一個字典序列,是詞和label的對應關係,運行下一行能夠看到具體內容:
word_dict
每行是如('limited': 1726)的對應關係,該行表示單詞limited所對應的label是1726。
構造訓練器
訓練器須要一個訓練程序和一個訓練優化函數。
exe = fluid.Executor(place) prediction = inference_program(word_dict) [avg_cost, accuracy] = train_program(prediction)#訓練程序 sgd_optimizer = optimizer_func()#訓練優化函數 sgd_optimizer.minimize(avg_cost)
該函數用來計算訓練中模型在test數據集上的結果
def train_test(program, reader): count = 0 feed_var_list = [ program.global_block().var(var_name) for var_name in feed_order ] feeder_test = fluid.DataFeeder(feed_list=feed_var_list, place=place) test_exe = fluid.Executor(place) accumulated = len([avg_cost, accuracy]) * [0] for test_data in reader(): avg_cost_np = test_exe.run( program=program, feed=feeder_test.feed(test_data), fetch_list=[avg_cost, accuracy]) accumulated = [ x[0] + x[1][0] for x in zip(accumulated, avg_cost_np) ] count += 1 return [x / count for x in accumulated]
提供數據並構建主訓練循環
feed_order
用來定義每條產生的數據和fluid.layers.data
之間的映射關係。好比,imdb.train
產生的第一列的數據對應的是words
這個特徵。
# Specify the directory path to save the parameters params_dirname = "understand_sentiment_conv.inference.model" feed_order = ['words', 'label'] pass_num = 1 #訓練循環的輪數 #程序主循環部分 def train_loop(main_program): #啓動上文構建的訓練器 exe.run(fluid.default_startup_program()) feed_var_list_loop = [ main_program.global_block().var(var_name) for var_name in feed_order ] feeder = fluid.DataFeeder( feed_list=feed_var_list_loop, place=place) test_program = fluid.default_main_program().clone(for_test=True) #訓練循環 for epoch_id in range(pass_num): for step_id, data in enumerate(train_reader()): #運行訓練器 metrics = exe.run(main_program, feed=feeder.feed(data), fetch_list=[avg_cost, accuracy]) #測試結果 avg_cost_test, acc_test = train_test(test_program, test_reader) print('Step {0}, Test Loss {1:0.2}, Acc {2:0.2}'.format( step_id, avg_cost_test, acc_test)) print("Step {0}, Epoch {1} Metrics {2}".format( step_id, epoch_id, list(map(np.array, metrics)))) if step_id == 30: if params_dirname is not None: fluid.io.save_inference_model(params_dirname, ["words"], prediction, exe)#保存模型 return
訓練過程處理
咱們在訓練主循環裏打印了每一步輸出,能夠觀察訓練狀況。
開始訓練
最後,咱們啓動訓練主循環來開始訓練。訓練時間較長,若是爲了更快的返回結果,能夠經過調整損耗值範圍或者訓練步數,以減小準確率的代價來縮短訓練時間。
train_loop(fluid.default_main_program())
應用模型
構建預測器
和訓練過程同樣,咱們須要建立一個預測過程,並使用訓練獲得的模型和參數來進行預測,params_dirname
用來存放訓練過程當中的各個參數。
place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace() exe = fluid.Executor(place) inference_scope = fluid.core.Scope()
生成測試用輸入數據
爲了進行預測,咱們任意選取3個評論。請隨意選取您看好的3個。咱們把評論中的每一個詞對應到word_dict
中的id。若是詞典中沒有這個詞,則設爲unknown
。 而後咱們用create_lod_tensor
來建立細節層次的張量,關於該函數的詳細解釋請參照API文檔。
reviews_str = [ 'read the book forget the movie', 'this is a great movie', 'this is very bad' ] reviews = [c.split() for c in reviews_str] UNK = word_dict['<unk>'] lod = [] for c in reviews: lod.append([word_dict.get(words, UNK) for words in c]) base_shape = [[len(c) for c in lod]] tensor_words = fluid.create_lod_tensor(lod, base_shape, place)
應用模型並進行預測
如今咱們能夠對每一條評論進行正面或者負面的預測啦。
with fluid.scope_guard(inference_scope): [inferencer, feed_target_names, fetch_targets] = fluid.io.load_inference_model(params_dirname, exe) assert feed_target_names[0] == "words" results = exe.run(inferencer, feed={feed_target_names[0]: tensor_words}, fetch_list=fetch_targets, return_numpy=False) np_data = np.array(results[0]) for i, r in enumerate(np_data): print("Predict probability of ", r[0], " to be positive and ", r[1], " to be negative for review \'", reviews_str[i], "\'")
總結
本章咱們以情感分析爲例,介紹了使用深度學習的方法進行端對端的短文本分類,而且使用PaddlePaddle完成了所有相關實驗。同時,咱們簡要介紹了兩種文本處理模型:卷積神經網絡和循環神經網絡。在後續的章節中咱們會看到這兩種基本的深度學習模型在其它任務上的應用。
參考文獻
本教程 由 PaddlePaddle 創做,採用 知識共享 署名-相同方式共享 4.0 國際 許可協議進行許可。