PaddlePaddle入門:從對(duì)話系統(tǒng)中的情感分析談起
1. 背景介紹
人工智能時(shí)代,各種深度學(xué)習(xí)框架大行其道,掌握一種框架已經(jīng)成為這個(gè)時(shí)代的算法工程師標(biāo)配。但無論學(xué)習(xí)什么框架或者工具,如果不了解它是如何解決某個(gè)具體問題進(jìn)而幫助提高業(yè)務(wù),那無異于舍本逐末。
本文將從智能對(duì)話系統(tǒng)中一個(gè)基礎(chǔ)的問題——情感分析(Sentiment Analysis)——談起,詳細(xì)闡述如何“step-by-step”地運(yùn)用百度開源的深度學(xué)習(xí)框架(PaddlePaddle)來解決情感分析,并最終如何提升整個(gè)對(duì)話系統(tǒng)的質(zhì)量。
1.1一個(gè)案例
設(shè)想如下情景,一個(gè)剛剛成功轉(zhuǎn)變身份成為父母的好青年,來到了一家專業(yè)紙尿褲供應(yīng)商(例如小鹿叮叮)想要給剛出生的寶寶屯點(diǎn)紙尿褲。在做了如下一番交涉后,該青年發(fā)現(xiàn)為自己服務(wù)的是一個(gè)機(jī)器人,并且答非所問,他怒火中燒,直接辱罵機(jī)器人。若你正好是開發(fā)該智能導(dǎo)購(gòu)機(jī)器人的算法工程師,應(yīng)該怎樣應(yīng)對(duì)這種情況呢?
(圖片來源:智能一點(diǎn)為小鹿叮叮開發(fā)的智能導(dǎo)購(gòu)機(jī)器人測(cè)試賬號(hào))
在對(duì)話系統(tǒng)(Dialogue System)領(lǐng)域,由于目前的人工智能還只是弱人工智能,人工的介入在所難免,那么人工何時(shí)介入成為一個(gè)問題。若能精準(zhǔn)識(shí)別用戶目前的情緒(積極正面的情緒、消極負(fù)面的情緒),并在用戶表現(xiàn)出負(fù)面情緒時(shí)及時(shí)轉(zhuǎn)人工,這樣既能盡可能地為企業(yè)提升效率、節(jié)省開支并進(jìn)行精準(zhǔn)推薦,又能在弱人工智能無法處理時(shí)及時(shí)安撫用戶,降低客戶流失。這就是情感分析在智能對(duì)話系統(tǒng)中的一個(gè)典型應(yīng)用。
1.2 情感分析對(duì)于對(duì)話系統(tǒng)的意義
對(duì)話系統(tǒng),作為一個(gè)直接與人對(duì)話的系統(tǒng),若能完成對(duì)用戶情緒的實(shí)時(shí)感知,對(duì)提升整個(gè)對(duì)話系統(tǒng)的質(zhì)量,具有十分重大的意義,上述情景只是其在對(duì)話系統(tǒng)中的一種應(yīng)用。
首先,情感分析能直接為企業(yè)提供量化的客服質(zhì)量評(píng)估。由于一個(gè)好的客服在與客戶交互的過程中一定會(huì)注意不引起客戶的反感,那么從每個(gè)客服交互日志中可以通過情感分析可以挖掘出客戶情緒的波動(dòng)情況以及什么樣的回復(fù)容易引起客戶反感,將有助于企業(yè)效率的提高以及提升對(duì)話系統(tǒng)質(zhì)量。
再者,目前大部分面向任務(wù)的對(duì)話系統(tǒng)(Task-oriented Dialogue System)都是采用基于有限狀態(tài)自動(dòng)機(jī)(FSA-based)或者基于幀(Frame-based)的架構(gòu)。在這樣的架構(gòu)中,意圖識(shí)別顯得尤為重要。用戶的情感傾向有助于提高意圖識(shí)別的準(zhǔn)確率。
最后,對(duì)話系統(tǒng)領(lǐng)域的前沿研究正在結(jié)合強(qiáng)化學(xué)習(xí)(Reinforcement Learning)來設(shè)計(jì)對(duì)話機(jī)器人[2]。強(qiáng)化學(xué)習(xí)需要機(jī)器人能接受環(huán)境(Environment)的反饋用以量化獎(jiǎng)勵(lì)(Reward),從而選擇更加合適的動(dòng)作(Action),精準(zhǔn)的情感傾向分析是一種很強(qiáng)的環(huán)境反饋,是強(qiáng)化學(xué)習(xí)在對(duì)話系統(tǒng)中能夠發(fā)揮作用的基礎(chǔ)構(gòu)件。
2.情感分析與PaddlePaddle
情感分析[1],又稱意見挖掘,一般是指通過計(jì)算技術(shù)對(duì)一段文本的主客觀性、觀點(diǎn)、情緒的挖掘和分析,對(duì)文本的情感傾向做出分類判斷。其中,一段文本可以是一個(gè)句子,一個(gè)段落或一個(gè)文檔。情緒傾向可以是兩類,如(正面,負(fù)面),(高興,悲傷);也可以是三類,如(積極,消極,中性)等等。該問題通常建模為文本的分類問題。本文我們嘗試使用百度開源深度學(xué)習(xí)框架PaddlePaddle來解決情感分析。
PaddlePaddle是百度旗下深度學(xué)習(xí)開源平臺(tái)。Paddle是并行分布式深度學(xué)習(xí)(Parallel Distributed Deep Learning)[3]的簡(jiǎn)稱,為了方便描述,后文都使用paddle來稱呼PaddlePaddle。
3 .使用Paddle解決情感分析
3.1 安裝
筆者以前曾經(jīng)嘗試過安裝TensorFlow,安裝TF需要配置的依賴項(xiàng)之多、過程之繁瑣,至今仍心有余悸。Paddle安裝十分方便,官方[4]給出了豐富的文檔。本文只使用CPU版本,讀者只需在自己的Linux/Mac上執(zhí)行如下命令即可:
pip install paddlepaddle
在python腳本中導(dǎo)入paddle:
import paddle.v2 as paddle
若能導(dǎo)入成功,恭喜你,安裝成功。
3.2 數(shù)據(jù)
為了便于開發(fā)人員快速開展實(shí)驗(yàn),paddle提供了許多接口,提供現(xiàn)成的數(shù)據(jù),例如經(jīng)典的MNIST、Movielens。但若只是單純的調(diào)用這些接口得到數(shù)據(jù),會(huì)使得數(shù)據(jù)處理對(duì)于開發(fā)者如同黑盒子。有鑒于此,本文將使用外部數(shù)據(jù)完成情感分析實(shí)驗(yàn),首先看看數(shù)據(jù)的構(gòu)成。
(1)數(shù)據(jù)描述
數(shù)據(jù)采用中文文本數(shù)據(jù),每一條數(shù)據(jù)占一行,且每條數(shù)據(jù)由一個(gè)instance(一條分好詞的中文語句)和一個(gè)label組成。label有三種取值:0(negative)、1(neutral)、 2(positive),例如:
instance:你/家/客服/的/在線/時(shí)間 label:1
instance:好用/的/話/下次/可以/多/拍/點(diǎn) label:2
instance:什么/垃圾/喲 label:0
數(shù)據(jù)文件中一個(gè)instance和label占一行,并且使用制表符隔開
(2)Paddle對(duì)數(shù)據(jù)的支持
Paddle支持四種數(shù)據(jù)類型:
dense_vector |
稠密的浮點(diǎn)數(shù)向量 |
sparse_binary_vector |
稀疏的01向量,即大部分值為0,但有值的地方必須為1 |
sparse_float_vector |
稀疏向量,即大部分值為0,但有值的地方可以為任何浮點(diǎn)數(shù) |
integer |
整數(shù) |
三種序列模式:
NO_SEQUENCE |
不是一條序列 |
SEQUENCE |
一條時(shí)間序列 |
SUB_SEQUENCE |
一條時(shí)間序列,且每個(gè)元素還是一個(gè)序列 |
(3)paddle中的DataReader
在訓(xùn)練和測(cè)試階段,paddle都需要讀取數(shù)據(jù),為了方便,在paddle中做如下定義:
reader:reader是一個(gè)讀取數(shù)據(jù)并且提供可遍歷數(shù)據(jù)項(xiàng)的函數(shù)
這里注意,reader不一定非得是一個(gè)python generator,它只需要滿足兩個(gè)條件即可:
第一,無參;第二,可遍歷(Iterable)
在官方代碼中出現(xiàn)多次的reader.shuffle和batch其實(shí)都是一個(gè)python decorator,shuffle接收一個(gè)reader并返回一個(gè)將該reader中的數(shù)據(jù)打亂后的reader;batch也接收一個(gè)reader,并且返回一個(gè)個(gè)的batch_size大小的minibatch,通常的用法是:
train_reader = paddle.batch(
paddle.reader.shuffle(
origin_reader,
buf_size=1000),
batch_size=100)
在定義自己的reader時(shí),注意這里的origin_reader是一個(gè)function object。
在本文中,我們使用最簡(jiǎn)單的one-hot編碼來表示句子,假設(shè)我們已經(jīng)有了一個(gè)詞典來表征詞的空間,詞典中維護(hù)了每一個(gè)詞在該空間中的index,那我們可以這樣定義reader(以training reader為例):
def train(wd):
def file_reader():
with open(training_file_location) as f:
while True:
line = f.readline()
if line:
items = line.split('\t')
words = items[0].split('/')
label = items[1]
yield [[wd[word] for word in words], int(label)]
else:
return
return file_reader
3.3 模型
3.3.1 模型選擇
為了展現(xiàn)paddle使用的靈活性以及便捷性,本文將使用三種模型來解決情感分析。分別是:LogisticRegression、LogisticRegression-FM、TextCNN,下面分別對(duì)這三種模型做一下介紹。
(1)LogisticRegression
邏輯回歸(LR)在解決二分類問題時(shí),使用如下公式來表示一個(gè)實(shí)例被分類為正類的概率
若是一個(gè)多分類邏輯回歸,對(duì)于一個(gè)實(shí)例被分類為類別i的概率為
(2)LogisticRegression-FM
從(1)中看到,LR只考慮了x的線性部分,受啟發(fā)與Factorization Machine[5],可以將線性部分拓展為包含x的交互部分,如下所示:
其中
(3)TextCNN
由于CNN在圖像領(lǐng)域取得了里程碑式的進(jìn)展,TextCNN[6]將其引入了NLP處理文本分類,取得了很好的效果,其結(jié)構(gòu)如下所示。
3.3.2 模型搭建
paddle中一個(gè)模型的搭建需要完成從數(shù)據(jù)到輸出整個(gè)網(wǎng)絡(luò)的定義。本文所選的三種模型均可以由神經(jīng)網(wǎng)絡(luò)模型來表示,只是在隱層(hidden layer)以及全連接層(full connected layer)有所區(qū)別而已。
(1)數(shù)據(jù)層
從上文reader的定義可以看到,reader中的數(shù)據(jù)實(shí)際是一個(gè)list,該list中包含兩個(gè)元素:instance與label。其中instance的類型為integer_value_sequence(參考3.2節(jié)中paddle支持的數(shù)據(jù)類型),label的類型為integer,例如:
[[10, 201, 332, 103, 88], 1]
因此可以定義如下數(shù)據(jù)層:
data = paddle.layer.data("instance", paddle.data_type.integer_value_sequence(input_dim))
lbl = paddle.layer.data("label", paddle.data_type.integer_value(3))
其中instance和label都是該數(shù)據(jù)層的名字,在訓(xùn)練的時(shí)候,可以通過一個(gè)feeding參數(shù)將訓(xùn)練數(shù)據(jù)中的元素對(duì)應(yīng)到對(duì)應(yīng)的數(shù)據(jù)層。接著定義embedding層如下:
emb = paddle.layer.embedding(input=data, size=emb_dim)
在經(jīng)過embedding后,數(shù)據(jù)變?yōu)槿缦滦问剑?/p>
[[vect_10, vect_201, vect_332, vect_103, vect_88], 1]
其中vect_i代表該向量只有下標(biāo)為i的元素有值,其他都為0。
(2)模型層
首先我們搭建最簡(jiǎn)單的邏輯回歸:
pool = paddle.layer.pooling(input=emb, pooling_type=paddle.pooling.Sum())
output = paddle.layer.fc(input=pool, size=class_dim, act=paddle.activation.Softmax())
這里十分簡(jiǎn)單,先使用pooling.Sum將word embedding的每一維相加得到整個(gè)句子的embedding,再使用全連接層得到大小為class_dim的softmax結(jié)果即可。注意在paddle中,這里若不先做池化,在訓(xùn)練時(shí)會(huì)拋出實(shí)例數(shù)與標(biāo)簽數(shù)不相等的異常,這是由于emb的結(jié)果是詞的embedding的List,而paddle會(huì)把這個(gè)List中的每個(gè)元素當(dāng)做一個(gè)instance,使用pooling.Sum正好將一個(gè)List轉(zhuǎn)化為一個(gè)句子的embedding。
然后搭建帶FM的邏輯回歸:
pool = paddle.layer.pooling(input=emb, pooling_type=paddle.pooling.Sum())
linear_part = paddle.layer.fc(input=pool, size=1, act=paddle.activation.Linear())
interaction_part = paddle.layer.factorization_machine(input=pool,
factor_size=factor_size,
act=paddle.activation.Linear())
output = paddle.layer.fc(input=[linear_part, interaction_part], size=class_dim,
act=paddle.activation.Softmax())
它與邏輯回歸的唯一區(qū)別就是不止利用了x的線性,還利用了x各個(gè)維度之間的交互。這里我們用linear_part得到線性部分,再利用interaction_part得到大小為factor_size的交互部分,在全連接層將linear_part和interaction_part聯(lián)合起來得到softmax輸出。
最后搭建卷積神經(jīng)網(wǎng)絡(luò)(CNN):
pool_1 = conv_pool_layer(input=emb,
emb_dim=emb_dim,
hid_dim=hid_dim,
context_len=2,
act=paddle.activation.Relu(),
pooling_type=paddle.pooling.Max())
pool_2 = conv_pool_layer(input=emb,
emb_dim=emb_dim,
hid_dim=hid_dim,
context_len=3,
act=paddle.activation.Relu(),
pooling_type=paddle.pooling.Max())
output = paddle.layer.fc(input=[pool_1, pool_2], size=class_dim,
act=paddle.activation.Softmax())
其中定義conv_pool_layer為:
def conv_pool_layer(input, emb_dim, hid_dim, context_len, act, pooling_type):
with paddle.layer.mixed(size=emb_dim * context_len) as m:
m += paddle.layer.context_projection(input=input, context_len=context_len)
fc = paddle.layer.fc(m, size=hid_dim, act=act)
return paddle.layer.pooling(input=fc, pooling_type=pooling_type)
在這里我們只對(duì)相鄰的2個(gè)詞(Bi-Gram)和3個(gè)詞(Tri-Gram)做卷積,并使用ReLU激活函數(shù)以及最大池化操作。為了方便,paddle在做卷積和池化時(shí)封裝了許多可以直接調(diào)用的接口(例如paddle.v2.networks.sequence_conv_pool),讀者可以參考paddle的官方文檔。
(3)loss層
由于是一個(gè)多分類任務(wù),loss可以用最常用的cross-entropy來定義
lbl = paddle.layer.data("label", paddle.data_type.integer_value(3))
cost = paddle.layer.cross_entropy_cost(input=output, label=lbl)
經(jīng)過了數(shù)據(jù)層、模型層和loss層的定義,整個(gè)網(wǎng)絡(luò)結(jié)構(gòu)就搭建完成了,下邊給出上文描述的邏輯回歸的代碼示例:
def logistic_regression(input_dim,
class_dim,
emb_dim=128,
is_predict=False):
data=paddle.layer.data("instance", paddle.data_type.integer_value_sequence(input_dim))
emb = paddle.layer.embedding(input=data, size=emb_dim)
pool = paddle.layer.pooling(input=emb, pooling_type=paddle.pooling.Sum())
output = paddle.layer.fc(input=pool,
size=class_dim,
act=paddle.activation.Softmax())
if not is_predict:
lbl = paddle.layer.data("label", paddle.data_type.integer_value(3))
cost = paddle.layer.cross_entropy_cost(input=output, label=lbl)
return cost
else:
return output
其中為了測(cè)試方便,加入了一個(gè)is_predict標(biāo)識(shí)是否是測(cè)試,在訓(xùn)練的時(shí)候,它返回的是模型在訓(xùn)練數(shù)據(jù)上的Loss,在測(cè)試的時(shí)候,它直接返回softmax后的輸出。
3.3.3 模型訓(xùn)練與存儲(chǔ)
根據(jù)上文定義的Loss,直接創(chuàng)建好整個(gè)模型的參數(shù)
cost = logistic_regression(dict_dim, class_dim=class_dim)
parameters = paddle.parameters.create(cost)
parameters負(fù)責(zé)維護(hù)整個(gè)模型在訓(xùn)練階段或者測(cè)試階段網(wǎng)絡(luò)結(jié)構(gòu)中的參數(shù),也就是網(wǎng)絡(luò)結(jié)構(gòu)中的所有的w。在paddle中,目標(biāo)函數(shù)的優(yōu)化是通過一個(gè)optimizer來進(jìn)行的,為了方便用戶操作,paddle封裝好了豐富的優(yōu)化算法供用戶選擇,本文選擇adam算法作為示例。定以好optimizer后,可以直接定義trainer:
adam_optimizer = paddle.optimizer.Adam( learning_rate=2e-3,
regularization=paddle.optimizer.L2Regularization(rate=8e-4),
model_average=paddle.optimizer.ModelAverage(average_window=0.5))
trainer = paddle.trainer.SGD(cost=cost, parameters=parameters,
update_equation=adam_optimizer)
為了方便用戶對(duì)整個(gè)訓(xùn)練過程進(jìn)行監(jiān)控,paddle定義了多種event來反映訓(xùn)練的狀態(tài),用戶可以從特定的event獲取當(dāng)前的輸出、lost值等各種信息,并定制自己的event_handler,在event_handler中可以處理以下六種事件:
EndIteration |
標(biāo)識(shí)一輪迭代的結(jié)束 |
BeginIteration |
標(biāo)識(shí)一輪迭代的開始 |
BeginPass |
標(biāo)識(shí)一輪epoch的開始 |
EndPass |
標(biāo)識(shí)一輪epoch的結(jié)束 |
TestResult |
trainer.test返回的結(jié)果 |
EndForwardBackward |
標(biāo)識(shí)一輪ForwardBackward的結(jié)束 |
下邊以本文定義的event_handler為例進(jìn)行說明,它在每個(gè)minibatch結(jié)束時(shí)打印出cost等信息,并在每個(gè)pass結(jié)束后,將參數(shù)存儲(chǔ)到文件。
def event_handler(event):
if isinstance(event, paddle.event.EndIteration):
if event.batch_id % 100 == 0:
print "\nPass %d, Batch %d, Cost %f, %s" % (
event.pass_id, event.batch_id, event.cost, event.metrics)
else:
sys.stdout.write('.')
sys.stdout.flush()
if isinstance(event, paddle.event.EndPass):
with open('./params_pass_%d.tar' % event.pass_id, 'w') as f:
trainer.save_parameter_to_tar(f)
在完成所有的配置后,即可開始模型的訓(xùn)練
trainer.train( reader=train_reader, event_handler=event_handler,
eeding=feeding, num_passes=10)
其中feeding定義了數(shù)據(jù)層的映射:feeding={'instance': 0, 'label': 1},它表示將數(shù)據(jù)中的第一個(gè)元素映射到名為instance的數(shù)據(jù)層,第二個(gè)元素映射到名為label的數(shù)據(jù)層,這也是數(shù)據(jù)層中取名原因。
3.4 測(cè)試與結(jié)果分析
測(cè)試階段我們加載訓(xùn)練階段保存的parameters文件,并使用paddle的infer得到輸出:
with open("./params_pass_9.tar") as f:
parameters = paddle.parameters.Parameters.from_tar(f)
out = cn_sentiment.logistic_regression(dict_dim, class_dim=class_dim, is_predict=True)
probs = paddle.infer(output_layer=out, parameters=parameters, input=x)
在本文中其中probs存儲(chǔ)的就是每個(gè)test instance屬于三個(gè)類別的概率,概率最高的那個(gè)類別即為模型的預(yù)測(cè)結(jié)果。下面將測(cè)試結(jié)果總結(jié)匯報(bào)如下:
Model |
Acc |
LogisticRegression |
0.932857991682 |
LogisticRegression-FM |
0.933143264763 |
CNN_Sigmoid |
0.935828877005 |
CNN_Relu |
0.953060011884 |
從結(jié)果可以看出,LR-FM考慮了特征之間的交叉,其表現(xiàn)優(yōu)于LR。但CNN由于其更強(qiáng)大的表現(xiàn)能力結(jié)果相較于LR有大幅提升,在實(shí)驗(yàn)中也順便驗(yàn)證了ReLU激活函數(shù)對(duì)比于sigmoid的優(yōu)勢(shì),這是因?yàn)镽eLU可以解決訓(xùn)練過程中出現(xiàn)的梯度飽和效應(yīng)(Saturation Effect)。
4 .總結(jié)與展望
本文結(jié)合一個(gè)具體例子介紹了情感分析在對(duì)話系統(tǒng)中的應(yīng)用。情感分析對(duì)于對(duì)話系統(tǒng),至少有以下四點(diǎn)意義:為轉(zhuǎn)人工提供依據(jù);優(yōu)化意圖識(shí)別;挖掘?qū)υ挃?shù)據(jù),提升客服質(zhì)量;協(xié)助強(qiáng)化學(xué)習(xí)。筆者目前正在“智能一點(diǎn)”從事對(duì)話機(jī)器人的開發(fā),我們期望利用NLP的技術(shù)來提高對(duì)話的質(zhì)量并幫助客戶節(jié)省開支并提高效率,對(duì)這一點(diǎn)的感觸就更深了。
除此之外,本文還展示如何使用paddle step-by-step地解決情感分析問題,并在實(shí)驗(yàn)中簡(jiǎn)單對(duì)比了用paddle實(shí)現(xiàn)的幾種模型的結(jié)果。本文的目的在于,給深度學(xué)習(xí)的入門開發(fā)者提供一個(gè)如何使用paddle開發(fā)的直觀感受,起到拋磚引玉的作用。
當(dāng)然,本文只是管中窺豹。正如paddle官網(wǎng)的slogan:“易學(xué)易用的分布式深度學(xué)習(xí)平臺(tái)”,paddle無論安裝還是使用都十分方便,甚至連經(jīng)典任務(wù)的基本數(shù)據(jù)集都做了準(zhǔn)備,特別適合剛接觸深度學(xué)習(xí)的人員上手。但若以為paddle只是這么簡(jiǎn)單那就大錯(cuò)特錯(cuò)了,如同目前流行的深度學(xué)習(xí)框架一樣,paddle不僅支持GPU,還支持多種分布式集群的部署和運(yùn)行方式,包括fabric集群、openmpi集群、Kubernetes單機(jī)、Kubernetes distributed分布式等。這些特性只能留待讀者自己探索了。
5 .引用
[1] 楊立公, 朱儉, 湯世平. 文本情感分析綜述[J]. 計(jì)算機(jī)應(yīng)用, 2013, 33(6):1574-1578.
[2] Serban I V, Sankar C, Germain M, et al. A Deep Reinforcement Learning Chatbot[J]. 2017.
[3] http://ai.baidu.com/paddlepaddle
[4] http://staging.paddlepaddle.org/docs/develop/documentation/zh/getstarted/index_cn.html
[5] Rendle S. Factorization Machines[C]// IEEE, International Conference on Data Mining. IEEE, 2011:995-1000.
[6] Kim Y. Convolutional Neural Networks for Sentence Classification[J]. Eprint Arxiv, 2014.