情感分析算法从原理到PaddlePaddle实战全解
在自然語言處理中,情感分析一般是指判斷一段文本所表達的情緒狀態。其中,一段文本可以是一個句子,一個段落或一個文檔。情緒狀態可以是兩類,如(正面,負面),(高興,悲傷);也可以是三類,如(積極,消極,中性)等等。
情感分析的應用場景十分廣泛,如把用戶在購物網站(亞馬遜、天貓、淘寶等)、旅游網站、電影評論網站上發表的評論分成正面評論和負面評論;或為了分析用戶對于某一產品的整體使用感受,抓取產品的用戶評論并進行情感分析等等。
今天是5月20日,PaddlePaddle教你用情感分析算法體會女神心意。
在下文中,我們將以情感分析為例,介紹使用深度學習的方法進行端對端的短文本分類,并使用PaddlePaddle完成全部相關實驗。
項目地址:
https://github.com/PaddlePaddle/book/blob/develop/06.understand_sentiment/README.cn.md
應用背景
在自然語言處理中,情感分析屬于典型的文本分類問題,即把需要進行情感分析的文本劃分為其所屬類別。文本分類涉及文本表示和分類方法兩個問題。
在深度學習的方法出現之前,主流的文本表示方法為詞袋模型BOW(bag of words),話題模型等等;分類方法有SVM(support vector machine), LR(logistic regression)等等。
對于一段文本,BOW表示會忽略其詞順序、語法和句法,將這段文本僅僅看做是一個詞集合,因此BOW方法并不能充分表示文本的語義信息。
例如,句子“這部電影糟糕透了”和“一個乏味,空洞,沒有內涵的作品”在情感分析中具有很高的語義相似度,但是它們的BOW表示的相似度為0。又如,句子“一個空洞,沒有內涵的作品”和“一個不空洞而且有內涵的作品”的BOW相似度很高,但實際上它們的意思很不一樣。
在本教程中,我們所要介紹的深度學習模型克服了BOW表示的上述缺陷,它在考慮詞順序的基礎上把文本映射到低維度的語義空間,并且以端對端(end to end)的方式進行文本表示及分類,其性能相對于傳統方法有顯著的提升[1]。
模型概覽
本教程所使用的文本表示模型為卷積神經網絡(Convolutional Neural Networks)和循環神經網絡(Recurrent Neural Networks)及其擴展。下面依次介紹這幾個模型。
文本卷積神經網絡簡介(CNN)
對卷積神經網絡來說,首先使用卷積處理輸入的詞向量序列,產生一個特征圖(feature map),對特征圖采用時間維度上的最大池化(max pooling over time)操作得到此卷積核對應的整句話的特征,最后,將所有卷積核得到的特征拼接起來即為文本的定長向量表示,對于文本分類問題,將其連接至softmax即構建出完整的模型。
在實際應用中,我們會使用多個卷積核來處理句子,窗口大小相同的卷積核堆疊起來形成一個矩陣,這樣可以更高效的完成運算。另外,我們也可使用窗口大小不同的卷積核來處理句子,圖1表示卷積神經網絡文本分類模型,不同顏色表示不同大小的卷積核操作。
對于一般的短文本分類問題,上文所述的簡單的文本卷積網絡即可達到很高的正確率[1]。若想得到更抽象更高級的文本特征表示,可以構建深層文本卷積神經網絡[2,3]。
循環神經網絡(RNN)
循環神經網絡是一種能對序列數據進行精確建模的有力工具。實際上,循環神經網絡的理論計算能力是圖靈完備的[4]。自然語言是一種典型的序列數據(詞序列),近年來,循環神經網絡及其變體(如long short term memory[5]等)在自然語言處理的多個領域,如語言模型、句法解析、語義角色標注(或一般的序列標注)、語義表示、圖文生成、對話、機器翻譯等任務上均表現優異甚至成為目前效果最好的方法。
循環神經網絡按時間展開后如圖2所示:在第t時刻,網絡讀入第t個輸入。
其中,在處理自然語言時,一般會先將詞(one-hot表示)映射為其詞向量表示,然后再作為循環神經網絡每一時刻的輸入。
長短期記憶網絡(LSTM)
對于較長的序列數據,循環神經網絡的訓練過程中容易出現梯度消失或爆炸現象[6]。LSTM能夠解決這一問題。相比于簡單的循環神經網絡,LSTM增加了記憶單元c、輸入門i、遺忘門f及輸出門o。這些門及記憶單元組合起來大大提升了循環神經網絡處理長序列數據的能力。若將基于LSTM的循環神經網絡表示的函數記為F,則其公式為:
F由下列公式組合而成[7]:
其中,
LSTM通過給簡單的循環神經網絡增加記憶及控制門的方式,增強了其處理遠距離依賴問題的能力。類似原理的改進還有Gated Recurrent Unit (GRU)[8],其設計更為簡潔一些。這些改進雖然各有不同,但是它們的宏觀描述卻與簡單的循環神經網絡一樣(如圖2所示),即隱狀態依據當前輸入及前一時刻的隱狀態來改變,不斷地循環這一過程直至輸入處理完畢:
其中,Recrurent可以表示簡單的循環神經網絡、GRU或LSTM。
棧式雙向LSTM(Stacked Bidirectional LSTM)
對于正常順序的循環神經網絡,
如圖4所示(以三層為例),奇數層LSTM正向,偶數層LSTM反向,高一層的LSTM使用低一層LSTM及之前所有層的信息作為輸入,對最高層LSTM序列使用時間維度上的最大池化即可得到文本的定長向量表示(這一表示充分融合了文本的上下文信息,并且對文本進行了深層次抽象),最后我們將文本表示連接至softmax構建分類模型。
基于PaddlePaddle的實戰PaddlePaddle簡介
PaddlePaddle(paddlepaddle.org)是百度研發的深度學習框架。除了核心框架之外,PaddlePaddle還提供了豐富的工具組件。官方開源了多個工業級應用模型,涵蓋自然語言處理、計算機視覺、推薦引擎等多個領域,并開放了多個領先的預訓練中文模型。4月23日深度學習開發者峰會上,PaddlePaddle發布了一系列新特性和應用案例。
數據集介紹
我們以IMDB情感分析數據集為例進行介紹。IMDB數據集的訓練集和測試集分別包含25000個已標注過的電影評論。其中,負面評論的得分小于等于4,正面評論的得分大于等于7,滿分10分。
aclImdb
|-?test
???|--?neg
???|--?pos
|-?train
???|--?neg
???|--?pos
PaddlePaddle在 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
網絡的輸入 input_dim 表示的是詞典的大小,class_dim 表示類別數。這里,我們使用 sequence_conv_pool API實現了卷積和池化操作。
棧式雙向LSTM
棧式雙向神經網絡stacked_lstm_net的代碼片段如下:
#棧式雙向LSTM
def?stacked_lstm_net(data,?input_dim,?class_dim,?emb_dim,?hid_dim,?stacked_num):
????assert?stacked_num?%?2?==?1
????#計算詞向量
????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)
feed_order?=?['words',?'label']
pass_num?=?1
word_dict 是一個字典序列,是詞和label的對應關系,運行下一行可以看到具體內容:
word_dict
每行是如(’limited’: 1726)的對應關系,該行表示單詞limited所對應的label是1726。
構造訓練器
訓練器需要一個訓練程序和一個訓練優化函數。
main_program?=?fluid.default_main_program()
star_program?=?fluid.default_startup_program()
prediction?=?inference_program(word_dict)
train_func_outputs?=?train_program(prediction)
avg_cost?=?train_func_outputs[0]
test_program?=?main_program.clone(for_test=True)
sgd_optimizer?=?optimizer_func()
sgd_optimizer.minimize(avg_cost)
exe?=?fluid.Executor(place)
該函數用來計算訓練中模型在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():
#啟動上文構建的訓練器
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)?
exe.run(star_program)
????#訓練循環
????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=[var.name?for?var?in?train_func_outputs])
????????????#測試結果
????print("step:?{0},?Metrics?{1}".format(
????????????????????step_id,?list(map(np.array,?metrics))))
????if?(step_id?+?1)?%?10?==?0:
????????????????????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?math.isnan(float(metrics[0])):
????????????????????sys.exit("got?NaN?loss,?training?failed.")
????????????if?params_dirname?is?not?None:
????????????????fluid.io.save_inference_model(params_dirname,?["words"],
??????????????????????????????????????????????prediction,?exe)?#保存模型
train_loop()
訓練過程處理
我們在訓練主循環里打印了每一步輸出,可以觀察訓練情況。
開始訓練
最后,我們啟動訓練主循環來開始訓練。訓練時間較長,如果為了更快的返回結果,可以通過調整損耗值范圍或者訓練步數,以減少準確率的代價來縮短訓練時間。
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來創建細節層次的張量
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)
reviews_str?=?[
????'read?the?book?forget?the?moive’,’this?is?a?great?moive',
????'this?is?very?bad'
]
reviews?=?[c.split()?for?c?in?reviews_str]
UNK?=?word_dict['<unk>']
lod?=?[]
for?c?in?reviews:
????lod.append([np.int64(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)
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官網上閱讀其他相關文檔內容:http://www.paddlepaddle.org/
參考文獻:
Kim Y. Convolutional neural networks for sentence classification[J]. arXiv preprint arXiv:1408.5882, 2014.
Kalchbrenner N, Grefenstette E, Blunsom P. A convolutional neural network for modelling sentences[J]. arXiv preprint arXiv:1404.2188, 2014.
Yann N. Dauphin, et al. Language Modeling with Gated Convolutional Networks[J] arXiv preprint arXiv:1612.08083, 2016.
Siegelmann H T, Sontag E D. On the computational power of neural nets[C]//Proceedings of the fifth annual workshop on Computational learning theory. ACM, 1992: 440-449.
Hochreiter S, Schmidhuber J. Long short-term memory[J]. Neural computation, 1997, 9(8): 1735-1780.
Bengio Y, Simard P, Frasconi P. Learning long-term dependencies with gradient descent is difficult[J]. IEEE transactions on neural networks, 1994, 5(2): 157-166.
Graves A. Generating sequences with recurrent neural networks[J]. arXiv preprint arXiv:1308.0850, 2013.
Cho K, Van Merri?nboer B, Gulcehre C, et al. Learning phrase representations using RNN encoder-decoder for statistical machine translation[J]. arXiv preprint arXiv:1406.1078, 2014.
Zhou J, Xu W. End-to-end learning of semantic role labeling using recurrent neural networks[C]//Proceedings of the Annual Meeting of the Association for Computational Linguistics. 2015.
————
編輯?∑Gemini????
來源:新浪專欄·創事記
更多精彩:
?泰勒定理的奇聞軼事
?丘成桐:漫談微分幾何
?Leibniz?如何想出微積分?(一)
?線性相關和秩的物理意義
?數學史上你認為最丑陋的公式是什么?
?陶哲軒談什么是好的數學
?田淵棟:數學的用處(下篇)
?你絕對沒想過原來數學家這么流氓,一言不合就進行暴力證明
?世界上最牛的五篇博士論文
?數學中有哪些巧合讓人眼前一亮?
?算法立功!清華畢業教授美國被搶車,警察無能為力自己用“貪心算法”找回
?學術史上的奇文:怎樣用數學抓獅子
?臺大教授的反思:最難的一課?我們卻沒教給學生
?麻省理工學院(MIT)研究生學習指導—— 怎樣做研究生
?分享 數學,常識和運氣 ——投資大師詹姆斯·西蒙斯2010年在MIT的講座
算法數學之美微信公眾號歡迎賜稿
稿件涉及數學、物理、算法、計算機、編程等相關領域,經采用我們將奉上稿酬。
投稿郵箱:math_alg@163.com
總結
以上是生活随笔為你收集整理的情感分析算法从原理到PaddlePaddle实战全解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 地球十大人类禁区!让人不寒而栗
- 下一篇: 令人窒息的数学动态图