本教程源代碼目錄在book/label_semantic_roles, 初次使用請(qǐng)參考PaddlePaddle安裝教程。
自然語言分析技術(shù)大致分為三個(gè)層面:詞法分析、句法分析和語義分析。語義角色標(biāo)注是實(shí)現(xiàn)淺層語義分析的一種方式。在一個(gè)句子中,謂詞是對(duì)主語的陳述或說明,指出“做什么”、“是什么”或“怎么樣,代表了一個(gè)事件的核心,跟謂詞搭配的名詞稱為論元。語義角色是指論元在動(dòng)詞所指事件中擔(dān)任的角色。主要有:施事者(Agent)、受事者(Patient)、客體(Theme)、經(jīng)驗(yàn)者(Experiencer)、受益者(Beneficiary)、工具(Instrument)、處所(Location)、目標(biāo)(Goal)和來源(Source)等。
請(qǐng)看下面的例子,“遇到” 是謂詞(Predicate,通常簡(jiǎn)寫為“Pred”),“小明”是施事者(Agent),“小紅”是受事者(Patient),“昨天” 是事件發(fā)生的時(shí)間(Time),“公園”是事情發(fā)生的地點(diǎn)(Location)。
$$\mbox{[小明]}_{\mbox{Agent}}\mbox{[昨天]}_{\mbox{Time}}\mbox{[晚上]}_\mbox{Time}\mbox{在[公園]}_{\mbox{Location}}\mbox{[遇到]}_{\mbox{Predicate}}\mbox{了[小紅]}_{\mbox{Patient}}\mbox{。}$$
語義角色標(biāo)注(Semantic Role Labeling,SRL)以句子的謂詞為中心,不對(duì)句子所包含的語義信息進(jìn)行深入分析,只分析句子中各成分與謂詞之間的關(guān)系,即句子的謂詞(Predicate)- 論元(Argument)結(jié)構(gòu),并用語義角色來描述這些結(jié)構(gòu)關(guān)系,是許多自然語言理解任務(wù)(如信息抽取,篇章分析,深度問答等)的一個(gè)重要中間步驟。在研究中一般都假定謂詞是給定的,所要做的就是找出給定謂詞的各個(gè)論元和它們的語義角色。
傳統(tǒng)的SRL系統(tǒng)大多建立在句法分析基礎(chǔ)之上,通常包括5個(gè)流程:
1.構(gòu)建一棵句法分析樹,例如,圖1是對(duì)上面例子進(jìn)行依存句法分析得到的一棵句法樹。
2.從句法樹上識(shí)別出給定謂詞的候選論元。
3.候選論元剪除;一個(gè)句子中的候選論元可能很多,候選論元剪除就是從大量的候選項(xiàng)中剪除那些最不可能成為論元的候選項(xiàng)。
4.論元識(shí)別:這個(gè)過程是從上一步剪除之后的候選中判斷哪些是真正的論元,通常當(dāng)做一個(gè)二分類問題來解決。
5.對(duì)第4步的結(jié)果,通過多分類得到論元的語義角色標(biāo)簽??梢钥吹?,句法分析是基礎(chǔ),并且后續(xù)步驟常常會(huì)構(gòu)造的一些人工特征,這些特征往往也來自句法分析。
http://wiki.jikexueyuan.com/project/deep-learning/images/07-01.png" alt="png" />
圖1. 依存句法分析句法樹示例
然而,完全句法分析需要確定句子所包含的全部句法信息,并確定句子各成分之間的關(guān)系,是一個(gè)非常困難的任務(wù),目前技術(shù)下的句法分析準(zhǔn)確率并不高,句法分析的細(xì)微錯(cuò)誤都會(huì)導(dǎo)致SRL的錯(cuò)誤。為了降低問題的復(fù)雜度,同時(shí)獲得一定的句法結(jié)構(gòu)信息,“淺層句法分析”的思想應(yīng)運(yùn)而生。淺層句法分析也稱為部分句法分析(partial parsing)或語塊劃分(chunking)。和完全句法分析得到一顆完整的句法樹不同,淺層句法分析只需要識(shí)別句子中某些結(jié)構(gòu)相對(duì)簡(jiǎn)單的獨(dú)立成分,例如:動(dòng)詞短語,這些被識(shí)別出來的結(jié)構(gòu)稱為語塊。為了回避 “無法獲得準(zhǔn)確率較高的句法樹” 所帶來的困難,一些研究[1]也提出了基于語塊(chunk)的SRL方法。基于語塊的SRL方法將SRL作為一個(gè)序列標(biāo)注問題來解決。序列標(biāo)注任務(wù)一般都會(huì)采用BIO表示方式來定義序列標(biāo)注的標(biāo)簽集,我們先來介紹這種表示方法。在BIO表示法中,B代表語塊的開始,I代表語塊的中間,O代表語塊結(jié)束。通過B、I、O 三種標(biāo)記將不同的語塊賦予不同的標(biāo)簽,例如:對(duì)于一個(gè)角色為A的論元,將它所包含的第一個(gè)語塊賦予標(biāo)簽B-A,將它所包含的其它語塊賦予標(biāo)簽I-A,不屬于任何論元的語塊賦予標(biāo)簽O。
我們繼續(xù)以上面的這句話為例,圖1展示了BIO表示方法。
http://wiki.jikexueyuan.com/project/deep-learning/images/07-02.png" alt="png" />
圖2. BIO標(biāo)注方法示例
從上面的例子可以看到,根據(jù)序列標(biāo)注結(jié)果可以直接得到論元的語義角色標(biāo)注結(jié)果,是一個(gè)相對(duì)簡(jiǎn)單的過程。這種簡(jiǎn)單性體現(xiàn)在:
(1)依賴淺層句法分析,降低了句法分析的要求和難度;
(2)沒有了候選論元剪除這一步驟;
(3)論元的識(shí)別和論元標(biāo)注是同時(shí)實(shí)現(xiàn)的。
這種一體化處理論元識(shí)別和論元標(biāo)注的方法,簡(jiǎn)化了流程,降低了錯(cuò)誤累積的風(fēng)險(xiǎn),往往能夠取得更好的結(jié)果。
與基于語塊的SRL方法類似,在本教程中我們也將SRL看作一個(gè)序列標(biāo)注問題,不同的是,我們只依賴輸入文本序列,不依賴任何額外的語法解析結(jié)果或是復(fù)雜的人造特征,利用深度神經(jīng)網(wǎng)絡(luò)構(gòu)建一個(gè)端到端學(xué)習(xí)的SRL系統(tǒng)。我們以CoNLL-2004 and CoNLL-2005 Shared Tasks任務(wù)中SRL任務(wù)的公開數(shù)據(jù)集為例,實(shí)踐下面的任務(wù):給定一句話和這句話里的一個(gè)謂詞,通過序列標(biāo)注的方式,從句子中找到謂詞對(duì)應(yīng)的論元,同時(shí)標(biāo)注它們的語義角色。
循環(huán)神經(jīng)網(wǎng)絡(luò)(Recurrent Neural Network)是一種對(duì)序列建模的重要模型,在自然語言處理任務(wù)中有著廣泛地應(yīng)用。不同于前饋神經(jīng)網(wǎng)絡(luò)(Feed-forward Neural Network),RNN能夠處理輸入之間前后關(guān)聯(lián)的問題。LSTM是RNN的一種重要變種,常用來學(xué)習(xí)長(zhǎng)序列中蘊(yùn)含的長(zhǎng)程依賴關(guān)系,我們?cè)?a rel="nofollow" >情感分析一篇中已經(jīng)介紹過,這一篇中我們依然利用LSTM來解決SRL問題。
深層網(wǎng)絡(luò)有助于形成層次化特征,網(wǎng)絡(luò)上層在下層已經(jīng)學(xué)習(xí)到的初級(jí)特征基礎(chǔ)上,形成更復(fù)雜的高級(jí)特征。盡管LSTM沿時(shí)間軸展開后等價(jià)于一個(gè)非?!吧睢钡那梆伨W(wǎng)絡(luò),但由于LSTM各個(gè)時(shí)間步參數(shù)共享,$t-1$時(shí)刻狀態(tài)到$t$時(shí)刻的映射,始終只經(jīng)過了一次非線性映射,也就是說單層LSTM對(duì)狀態(tài)轉(zhuǎn)移的建模是 “淺” 的。堆疊多個(gè)LSTM單元,令前一個(gè)LSTM$t$時(shí)刻的輸出,成為下一個(gè)LSTM單元$t$時(shí)刻的輸入,幫助我們構(gòu)建起一個(gè)深層網(wǎng)絡(luò),我們把它稱為第一個(gè)版本的棧式循環(huán)神經(jīng)網(wǎng)絡(luò)。深層網(wǎng)絡(luò)提高了模型擬合復(fù)雜模式的能力,能夠更好地建模跨不同時(shí)間步的模式[2]。
然而,訓(xùn)練一個(gè)深層LSTM網(wǎng)絡(luò)并非易事??v向堆疊多個(gè)LSTM單元可能遇到梯度在縱向深度上傳播受阻的問題。通常,堆疊4層LSTM單元可以正常訓(xùn)練,當(dāng)層數(shù)達(dá)到4~8層時(shí),會(huì)出現(xiàn)性能衰減,這時(shí)必須考慮一些新的結(jié)構(gòu)以保證梯度縱向順暢傳播,這是訓(xùn)練深層LSTM網(wǎng)絡(luò)必須解決的問題。我們可以借鑒LSTM解決 “梯度消失梯度爆炸” 問題的智慧之一:在記憶單元(Memory Cell)這條信息傳播的路線上沒有非線性映射,當(dāng)梯度反向傳播時(shí)既不會(huì)衰減、也不會(huì)爆炸。因此,深層LSTM模型也可以在縱向上添加一條保證梯度順暢傳播的路徑。
一個(gè)LSTM單元完成的運(yùn)算可以被分為三部分:
(1)輸入到隱層的映射(input-to-hidden) :每個(gè)時(shí)間步輸入信息$x$會(huì)首先經(jīng)過一個(gè)矩陣映射,再作為遺忘門,輸入門,記憶單元,輸出門的輸入,注意,這一次映射沒有引入非線性激活;
(2)隱層到隱層的映射(hidden-to-hidden):這一步是LSTM計(jì)算的主體,包括遺忘門,輸入門,記憶單元更新,輸出門的計(jì)算;
(3)隱層到輸出的映射(hidden-to-output):通常是簡(jiǎn)單的對(duì)隱層向量進(jìn)行激活。我們?cè)诘谝粋€(gè)版本的棧式網(wǎng)絡(luò)的基礎(chǔ)上,加入一條新的路徑:除上一層LSTM輸出之外,將前層LSTM的輸入到隱層的映射作為的一個(gè)新的輸入,同時(shí)加入一個(gè)線性映射去學(xué)習(xí)一個(gè)新的變換。
圖3是最終得到的棧式循環(huán)神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)示意圖。
http://wiki.jikexueyuan.com/project/deep-learning/images/07-03.png" alt="png" />
圖3. 基于LSTM的棧式循環(huán)神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)示意圖
在LSTM中,$t$時(shí)刻的隱藏層向量編碼了到$t$時(shí)刻為止所有輸入的信息,但$t$時(shí)刻的LSTM可以看到歷史,卻無法看到未來。在絕大多數(shù)自然語言處理任務(wù)中,我們幾乎總是能拿到整個(gè)句子。這種情況下,如果能夠像獲取歷史信息一樣,得到未來的信息,對(duì)序列學(xué)習(xí)任務(wù)會(huì)有很大的幫助。
為了克服這一缺陷,我們可以設(shè)計(jì)一種雙向循環(huán)網(wǎng)絡(luò)單元,它的思想簡(jiǎn)單且直接:對(duì)上一節(jié)的棧式循環(huán)神經(jīng)網(wǎng)絡(luò)進(jìn)行一個(gè)小小的修改,堆疊多個(gè)LSTM單元,讓每一層LSTM單元分別以:正向、反向、正向 …… 的順序?qū)W習(xí)上一層的輸出序列。于是,從第2層開始,$t$時(shí)刻我們的LSTM單元便總是可以看到歷史和未來的信息。圖4是基于LSTM的雙向循環(huán)神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)示意圖。
http://wiki.jikexueyuan.com/project/deep-learning/images/07-04.png" alt="png" />
圖4. 基于LSTM的雙向循環(huán)神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)示意圖
需要說明的是,這種雙向RNN結(jié)構(gòu)和Bengio等人在機(jī)器翻譯任務(wù)中使用的雙向RNN結(jié)構(gòu)[3, 4] 并不相同,我們會(huì)在后續(xù)機(jī)器翻譯任務(wù)中,介紹另一種雙向循環(huán)神經(jīng)網(wǎng)絡(luò)。
使用神經(jīng)網(wǎng)絡(luò)模型解決問題的思路通常是:前層網(wǎng)絡(luò)學(xué)習(xí)輸入的特征表示,網(wǎng)絡(luò)的最后一層在特征基礎(chǔ)上完成最終的任務(wù)。在SRL任務(wù)中,深層LSTM網(wǎng)絡(luò)學(xué)習(xí)輸入的特征表示,條件隨機(jī)場(chǎng)(Conditional Random Filed, CRF)在特征的基礎(chǔ)上完成序列標(biāo)注,處于整個(gè)網(wǎng)絡(luò)的末端。
CRF是一種概率化結(jié)構(gòu)模型,可以看作是一個(gè)概率無向圖模型,結(jié)點(diǎn)表示隨機(jī)變量,邊表示隨機(jī)變量之間的概率依賴關(guān)系。簡(jiǎn)單來講,CRF學(xué)習(xí)條件概率$P(X|Y)$,其中 $X = (x_1, x_2, ... , x_n)$ 是輸入序列,$Y = (y_1, y_2, ... , y_n)$ 是標(biāo)記序列;解碼過程是給定 $X$序列求解令$P(Y|X)$最大的$Y$序列,即$Y^* = \mbox{arg max}_{Y} P(Y | X)$。
序列標(biāo)注任務(wù)只需要考慮輸入和輸出都是一個(gè)線性序列,并且由于我們只是將輸入序列作為條件,不做任何條件獨(dú)立假設(shè),因此輸入序列的元素之間并不存在圖結(jié)構(gòu)。綜上,在序列標(biāo)注任務(wù)中使用的是如圖5所示的定義在鏈?zhǔn)綀D上的CRF,稱之為線性鏈條件隨機(jī)場(chǎng)(Linear Chain Conditional Random Field)。
http://wiki.jikexueyuan.com/project/deep-learning/images/07-05.png" alt="png" />
圖5. 序列標(biāo)注任務(wù)中使用的線性鏈條件隨機(jī)場(chǎng)
根據(jù)線性鏈條件隨機(jī)場(chǎng)上的因子分解定理[5],在給定觀測(cè)序列$X$時(shí),一個(gè)特定標(biāo)記序列$Y$的概率可以定義為:
$$p(Y | X) = \frac{1}{Z(X)} \text{exp}\left(\sum_{i=1}^{n}\left(\sum_{j}\lambda_{j}t_{j} (y_{i - 1}, y_{i}, X, i) + \sum_{k} \mu_k s_k (y_i, X, i)\right)\right)$$
其中$Z(X)$是歸一化因子,$t_j$ 是定義在邊上的特征函數(shù),依賴于當(dāng)前和前一個(gè)位置,稱為轉(zhuǎn)移特征,表示對(duì)于輸入序列$X$及其標(biāo)注序列在 $i$及$i - 1$位置上標(biāo)記的轉(zhuǎn)移概率。$s_k$是定義在結(jié)點(diǎn)上的特征函數(shù),稱為狀態(tài)特征,依賴于當(dāng)前位置,表示對(duì)于觀察序列$X$及其$i$位置的標(biāo)記概率。$\lambda_j$ 和 $\mu_k$ 分別是轉(zhuǎn)移特征函數(shù)和狀態(tài)特征函數(shù)對(duì)應(yīng)的權(quán)值。實(shí)際上,$t$和$s$可以用相同的數(shù)學(xué)形式表示,再對(duì)轉(zhuǎn)移特征和狀態(tài)特在各個(gè)位置$i$求和有:$f_{k}(Y, X) = \sum_{i=1}^{n}f_k({y_{i - 1}, y_i, X, i})$,把$f$統(tǒng)稱為特征函數(shù),于是$P(Y|X)$可表示為:
$$p(Y|X, W) = \frac{1}{Z(X)}\text{exp}\sum_{k}\omega_{k}f_{k}(Y, X)$$
$\omega$是特征函數(shù)對(duì)應(yīng)的權(quán)值,是CRF模型要學(xué)習(xí)的參數(shù)。訓(xùn)練時(shí),對(duì)于給定的輸入序列和對(duì)應(yīng)的標(biāo)記序列集合$D = \left[(X_1, Y_1), (X_2 , Y_2) , ... , (X_N, Y_N)\right]$ ,通過正則化的極大似然估計(jì),求解如下優(yōu)化目標(biāo):
$$\DeclareMathOperator*{\argmax}{arg\,max} L(\lambda, D) = - \text{log}\left(\prod_{m=1}^{N}p(Y_m|X_m, W)\right) + C \frac{1}{2}\lVert W\rVert^{2}$$
這個(gè)優(yōu)化目標(biāo)可以通過反向傳播算法和整個(gè)神經(jīng)網(wǎng)絡(luò)一起求解。解碼時(shí),對(duì)于給定的輸入序列$X$,通過解碼算法(通常有:維特比算法、Beam Search)求令出條件概率$\bar{P}(Y|X)$最大的輸出序列 $\bar{Y}$。
在SRL任務(wù)中,輸入是 “謂詞” 和 “一句話”,目標(biāo)是從這句話中找到謂詞的論元,并標(biāo)注論元的語義角色。如果一個(gè)句子含有$n$個(gè)謂詞,這個(gè)句子會(huì)被處理$n$次。一個(gè)最為直接的模型是下面這樣:
構(gòu)造輸入;
輸入1是謂詞,輸入2是句子
將輸入1擴(kuò)展成和輸入2一樣長(zhǎng)的序列,用one-hot方式表示;
one-hot方式的謂詞序列和句子序列通過詞表,轉(zhuǎn)換為實(shí)向量表示的詞向量序列;
將步驟2中的2個(gè)詞向量序列作為雙向LSTM的輸入,學(xué)習(xí)輸入序列的特征表示;
CRF以步驟3中模型學(xué)習(xí)到的特征為輸入,以標(biāo)記序列為監(jiān)督信號(hào),實(shí)現(xiàn)序列標(biāo)注;
大家可以嘗試上面這種方法。這里,我們提出一些改進(jìn),引入兩個(gè)簡(jiǎn)單但對(duì)提高系統(tǒng)性能非常有效的特征:
謂詞上下文:上面的方法中,只用到了謂詞的詞向量表達(dá)謂詞相關(guān)的所有信息,這種方法始終是非常弱的,特別是如果謂詞在句子中出現(xiàn)多次,有可能引起一定的歧義。從經(jīng)驗(yàn)出發(fā),謂詞前后若干個(gè)詞的一個(gè)小片段,能夠提供更豐富的信息,幫助消解歧義。于是,我們把這樣的經(jīng)驗(yàn)也添加到模型中,為每個(gè)謂詞同時(shí)抽取一個(gè)“謂詞上下文” 片段,也就是從這個(gè)謂詞前后各取$n$個(gè)詞構(gòu)成的一個(gè)窗口片段;
謂詞上下文區(qū)域標(biāo)記:為句子中的每一個(gè)詞引入一個(gè)0-1二值變量,表示它們是否在“謂詞上下文”片段中;
修改后的模型如下(圖6是一個(gè)深度為4的模型結(jié)構(gòu)示意圖):
構(gòu)造輸入
輸入1是句子序列,輸入2是謂詞序列,輸入3是謂詞上下文,從句子中抽取這個(gè)謂詞前后各$n$個(gè)詞,構(gòu)成謂詞上下文,用one-hot方式表示,輸入4是謂詞上下文區(qū)域標(biāo)記,標(biāo)記了句子中每一個(gè)詞是否在謂詞上下文中;
將輸入2~3均擴(kuò)展為和輸入1一樣長(zhǎng)的序列;
輸入1~4均通過詞表取詞向量轉(zhuǎn)換為實(shí)向量表示的詞向量序列;其中輸入1、3共享同一個(gè)詞表,輸入2和4各自獨(dú)有詞表;
第2步的4個(gè)詞向量序列作為雙向LSTM模型的輸入;LSTM模型學(xué)習(xí)輸入序列的特征表示,得到新的特性表示序列;
CRF以第3步中LSTM學(xué)習(xí)到的特征為輸入,以標(biāo)記序列為監(jiān)督信號(hào),完成序列標(biāo)注;
http://wiki.jikexueyuan.com/project/deep-learning/images/07-06.png" alt="png" />
圖6. SRL任務(wù)上的深層雙向LSTM模型
在此教程中,我們選用CoNLL 2005SRL任務(wù)開放出的數(shù)據(jù)集作為示例。需要特別說明的是,CoNLL 2005 SRL任務(wù)的訓(xùn)練數(shù)集和開發(fā)集在比賽之后并非免費(fèi)進(jìn)行公開,目前,能夠獲取到的只有測(cè)試集,包括Wall Street Journal的23節(jié)和Brown語料集中的3節(jié)。在本教程中,我們以測(cè)試集中的WSJ數(shù)據(jù)為訓(xùn)練集來講解模型。但是,由于測(cè)試集中樣本的數(shù)量遠(yuǎn)遠(yuǎn)不夠,如果希望訓(xùn)練一個(gè)可用的神經(jīng)網(wǎng)絡(luò)SRL系統(tǒng),請(qǐng)考慮付費(fèi)獲取全量數(shù)據(jù)。
原始數(shù)據(jù)中同時(shí)包括了詞性標(biāo)注、命名實(shí)體識(shí)別、語法解析樹等多種信息。本教程中,我們使用test.wsj文件夾中的數(shù)據(jù)進(jìn)行訓(xùn)練和測(cè)試,并只會(huì)用到words文件夾(文本序列)和props文件夾(標(biāo)注結(jié)果)下的數(shù)據(jù)。本教程使用的數(shù)據(jù)目錄如下:
conll05st-release/
└── test.wsj
├── props # 標(biāo)注結(jié)果
└── words # 輸入文本序列
標(biāo)注信息源自Penn TreeBank[7]和PropBank[8]的標(biāo)注結(jié)果。PropBank標(biāo)注結(jié)果的標(biāo)簽和我們?cè)谖恼乱婚_始示例中使用的標(biāo)注結(jié)果標(biāo)簽不同,但原理是相同的,關(guān)于標(biāo)注結(jié)果標(biāo)簽含義的說明,請(qǐng)參考論文[9]。
原始數(shù)據(jù)需要進(jìn)行數(shù)據(jù)預(yù)處理才能被PaddlePaddle處理,預(yù)處理包括下面幾個(gè)步驟:
1.將文本序列和標(biāo)記序列其合并到一條記錄中;
2.一個(gè)句子如果含有$n$個(gè)謂詞,這個(gè)句子會(huì)被處理$n$次,變成$n$條獨(dú)立的訓(xùn)練樣本,每個(gè)樣本一個(gè)不同的謂詞;
3.抽取謂詞上下文和構(gòu)造謂詞上下文區(qū)域標(biāo)記;
4.構(gòu)造以BIO法表示的標(biāo)記;
6.依據(jù)詞典獲取詞對(duì)應(yīng)的整數(shù)索引。
# import paddle.v2.dataset.conll05 as conll05
# conll05.corpus_reader函數(shù)完成上面第1步和第2步.
# conll05.reader_creator函數(shù)完成上面第3步到第5步.
# conll05.test函數(shù)可以獲取處理之后的每條樣本來供PaddlePaddle訓(xùn)練.
預(yù)處理完成之后一條訓(xùn)練樣本包含9個(gè)特征,分別是:句子序列、謂詞、謂詞上下文(占 5 列)、謂詞上下區(qū)域標(biāo)志、標(biāo)注序列。下表是一條訓(xùn)練樣本的示例。
句子序列 | 謂詞 | 謂詞上下文(窗口 = 5) | 謂詞上下文區(qū)域標(biāo)記 | 標(biāo)注序列 |
---|---|---|---|---|
A | set | n't been set . × | 0 | B-A1 |
record | set | n't been set . × | 0 | I-A1 |
date | set | n't been set . × | 0 | I-A1 |
has | set | n't been set . × | 0 | O |
n't | set | n't been set . × | 1 | B-AM-NEG |
been | set | n't been set . × | 1 | O |
set | set | n't been set . × | 1 | B-V |
. | set | n't been set . × | 1 | O |
除數(shù)據(jù)之外,我們同時(shí)提供了以下資源:
文件名稱 | 說明 |
---|---|
word_dict | 輸入句子的詞典,共計(jì)44068個(gè)詞 |
label_dict | 標(biāo)記的詞典,共計(jì)106個(gè)標(biāo)記 |
predicate_dict | 謂詞的詞典,共計(jì)3162個(gè)詞 |
emb | 一個(gè)訓(xùn)練好的詞表,32維 |
我們?cè)谟⑽木S基百科上訓(xùn)練語言模型得到了一份詞向量用來初始化SRL模型。在SRL模型訓(xùn)練過程中,詞向量不再被更新。關(guān)于語言模型和詞向量可以參考詞向量 這篇教程。我們訓(xùn)練語言模型的語料共有995,000,000個(gè)token,詞典大小控制為4900,000詞。CoNLL 2005訓(xùn)練語料中有5%的詞不在這4900,000個(gè)詞中,我們將它們?nèi)靠醋魑吹卿浽~,用<unk>
表示。
獲取詞典,打印詞典大?。?/p>
import math
import numpy as np
import gzip
import paddle.v2 as paddle
import paddle.v2.dataset.conll05 as conll05
import paddle.v2.evaluator as evaluator
paddle.init(use_gpu=False, trainer_count=1)
word_dict, verb_dict, label_dict = conll05.get_dict()
word_dict_len = len(word_dict)
label_dict_len = len(label_dict)
pred_len = len(verb_dict)
print word_dict_len
print label_dict_len
print pred_len
定義輸入數(shù)據(jù)維度及模型超參數(shù)。
mark_dict_len = 2 # 謂上下文區(qū)域標(biāo)志的維度,是一個(gè)0-1 2值特征,因此維度為2
word_dim = 32 # 詞向量維度
mark_dim = 5 # 謂詞上下文區(qū)域通過詞表被映射為一個(gè)實(shí)向量,這個(gè)是相鄰的維度
hidden_dim = 512 # LSTM隱層向量的維度 : 512 / 4
depth = 8 # 棧式LSTM的深度
# 一條樣本總共9個(gè)特征,下面定義了9個(gè)data層,每個(gè)層類型為integer_value_sequence,表示整數(shù)ID的序列類型.
def d_type(size):
return paddle.data_type.integer_value_sequence(size)
# 句子序列
word = paddle.layer.data(name='word_data', type=d_type(word_dict_len))
# 謂詞
predicate = paddle.layer.data(name='verb_data', type=d_type(pred_len))
# 謂詞上下文5個(gè)特征
ctx_n2 = paddle.layer.data(name='ctx_n2_data', type=d_type(word_dict_len))
ctx_n1 = paddle.layer.data(name='ctx_n1_data', type=d_type(word_dict_len))
ctx_0 = paddle.layer.data(name='ctx_0_data', type=d_type(word_dict_len))
ctx_p1 = paddle.layer.data(name='ctx_p1_data', type=d_type(word_dict_len))
ctx_p2 = paddle.layer.data(name='ctx_p2_data', type=d_type(word_dict_len))
# 謂詞上下區(qū)域標(biāo)志
mark = paddle.layer.data(name='mark_data', type=d_type(mark_dict_len))
# 標(biāo)注序列
target = paddle.layer.data(name='target', type=d_type(label_dict_len))
這里需要特別說明的是hidden_dim = 512指定了LSTM隱層向量的維度為128維,關(guān)于這一點(diǎn)請(qǐng)參考PaddlePaddle官方文檔中lstmemory的說明。
將句子序列、謂詞、謂詞上下文、謂詞上下文區(qū)域標(biāo)記通過詞表,轉(zhuǎn)換為實(shí)向量表示的詞向量序列。
# 在本教程中,我們加載了預(yù)訓(xùn)練的詞向量,這里設(shè)置了:is_static=True
# is_static 為 True 時(shí)保證了在訓(xùn)練 SRL 模型過程中,詞表不再更新
emb_para = paddle.attr.Param(name='emb', initial_std=0., is_static=True)
# 設(shè)置超參數(shù)
default_std = 1 / math.sqrt(hidden_dim) / 3.0
std_default = paddle.attr.Param(initial_std=default_std)
std_0 = paddle.attr.Param(initial_std=0.)
predicate_embedding = paddle.layer.embedding(
size=word_dim,
input=predicate,
param_attr=paddle.attr.Param(
name='vemb', initial_std=default_std))
mark_embedding = paddle.layer.embedding(
size=mark_dim, input=mark, param_attr=std_0)
word_input = [word, ctx_n2, ctx_n1, ctx_0, ctx_p1, ctx_p2]
emb_layers = [
paddle.layer.embedding(
size=word_dim, input=x, param_attr=emb_para) for x in word_input
]
emb_layers.append(predicate_embedding)
emb_layers.append(mark_embedding)
8個(gè)LSTM單元以“正向/反向”的順序?qū)λ休斎胄蛄羞M(jìn)行學(xué)習(xí)。
hidden_0 = paddle.layer.mixed(
size=hidden_dim,
bias_attr=std_default,
input=[
paddle.layer.full_matrix_projection(
input=emb, param_attr=std_default) for emb in emb_layers
])
mix_hidden_lr = 1e-3
lstm_para_attr = paddle.attr.Param(initial_std=0.0, learning_rate=1.0)
hidden_para_attr = paddle.attr.Param(
initial_std=default_std, learning_rate=mix_hidden_lr)
lstm_0 = paddle.layer.lstmemory(
input=hidden_0,
act=paddle.activation.Relu(),
gate_act=paddle.activation.Sigmoid(),
state_act=paddle.activation.Sigmoid(),
bias_attr=std_0,
param_attr=lstm_para_attr)
#stack L-LSTM and R-LSTM with direct edges
input_tmp = [hidden_0, lstm_0]
for i in range(1, depth):
mix_hidden = paddle.layer.mixed(
size=hidden_dim,
bias_attr=std_default,
input=[
paddle.layer.full_matrix_projection(
input=input_tmp[0], param_attr=hidden_para_attr),
paddle.layer.full_matrix_projection(
input=input_tmp[1], param_attr=lstm_para_attr)
])
lstm = paddle.layer.lstmemory(
input=mix_hidden,
act=paddle.activation.Relu(),
gate_act=paddle.activation.Sigmoid(),
state_act=paddle.activation.Sigmoid(),
reverse=((i % 2) == 1),
bias_attr=std_0,
param_attr=lstm_para_attr)
input_tmp = [mix_hidden, lstm]
在PaddlePaddle中,CRF的狀態(tài)特征和轉(zhuǎn)移特征分別由一個(gè)全連接層和一個(gè)PaddlePaddle中的CRF層分別學(xué)習(xí)。在這個(gè)例子中,我們用線性激活的paddle.layer.mixed 來學(xué)習(xí)CRF的狀態(tài)特征(也可以使用paddle.layer.fc),而 paddle.layer.crf只學(xué)習(xí)轉(zhuǎn)移特征。paddle.layer.crf層是一個(gè) cost 層,處于整個(gè)網(wǎng)絡(luò)的末端,輸出給定輸入序列下,標(biāo)記序列的log probability作為代價(jià)。訓(xùn)練階段,該層需要輸入正確的標(biāo)記序列作為學(xué)習(xí)目標(biāo)。
# 取最后一個(gè)棧式LSTM的輸出和這個(gè)LSTM單元的輸入到隱層映射,
# 經(jīng)過一個(gè)全連接層映射到標(biāo)記字典的維度,來學(xué)習(xí) CRF 的狀態(tài)特征
feature_out = paddle.layer.mixed(
size=label_dict_len,
bias_attr=std_default,
input=[
paddle.layer.full_matrix_projection(
input=input_tmp[0], param_attr=hidden_para_attr),
paddle.layer.full_matrix_projection(
input=input_tmp[1], param_attr=lstm_para_attr)
], )
# 學(xué)習(xí) CRF 的轉(zhuǎn)移特征
crf_cost = paddle.layer.crf(
size=label_dict_len,
input=feature_out,
label=target,
param_attr=paddle.attr.Param(
name='crfw',
initial_std=default_std,
learning_rate=mix_hidden_lr))
CRF解碼和CRF層參數(shù)名字相同,即:加載了paddle.layer.crf
層學(xué)習(xí)到的參數(shù)。在訓(xùn)練階段,為paddle.layer.crf_decoding
輸入了正確的標(biāo)記序列(target),這一層會(huì)輸出是否正確標(biāo)記,evaluator.sum
用來計(jì)算序列上的標(biāo)記錯(cuò)誤率,可以用來評(píng)估模型。解碼階段,沒有輸入正確的數(shù)據(jù)標(biāo)簽,該層通過尋找概率最高的標(biāo)記序列,解碼出標(biāo)記結(jié)果。
crf_dec = paddle.layer.crf_decoding(
size=label_dict_len,
input=feature_out,
label=target,
param_attr=paddle.attr.Param(name='crfw'))
evaluator.sum(input=crf_dec)
首先依據(jù)模型配置的crf_cost
定義模型參數(shù)。
# create parameters
parameters = paddle.parameters.create(crf_cost)
可以打印參數(shù)名字,如果在網(wǎng)絡(luò)配置中沒有指定名字,則默認(rèn)生成。
print parameters.keys()
如上文提到,我們用基于英文維基百科訓(xùn)練好的詞向量來初始化序列輸入、謂詞上下文總共6個(gè)特征的embedding層參數(shù),在訓(xùn)練中不更新。
# 這里加載PaddlePaddle上版保存的二進(jìn)制模型
def load_parameter(file_name, h, w):
with open(file_name, 'rb') as f:
f.read(16)
return np.fromfile(f, dtype=np.float32).reshape(h, w)
parameters.set('emb', load_parameter(conll05.get_embedding(), 44068, 32))
然后根據(jù)網(wǎng)絡(luò)拓?fù)浣Y(jié)構(gòu)和模型參數(shù)來構(gòu)造出trainer用來訓(xùn)練,在構(gòu)造時(shí)還需指定優(yōu)化方法,這里使用最基本的SGD方法(momentum設(shè)置為0),同時(shí)設(shè)定了學(xué)習(xí)率、正則等。
# create optimizer
optimizer = paddle.optimizer.Momentum(
momentum=0,
learning_rate=1e-3,
regularization=paddle.optimizer.L2Regularization(rate=8e-4),
model_average=paddle.optimizer.ModelAverage(
average_window=0.5, max_average_window=10000), )
trainer = paddle.trainer.SGD(cost=crf_cost,
parameters=parameters,
update_equation=optimizer,
extra_layers=crf_dec)
數(shù)據(jù)介紹部分提到CoNLL 2005訓(xùn)練集付費(fèi),這里我們使用測(cè)試集訓(xùn)練供大家學(xué)習(xí)。conll05.test()
每次產(chǎn)生一條樣本,包含9個(gè)特征,shuffle和組完batch后作為訓(xùn)練的輸入。
reader = paddle.batch(
paddle.reader.shuffle(
conll05.test(), buf_size=8192), batch_size=2)
通過feeding
來指定每一個(gè)數(shù)據(jù)和data_layer的對(duì)應(yīng)關(guān)系。 例如 下面feeding
表示: conll05.test()
產(chǎn)生數(shù)據(jù)的第0列對(duì)應(yīng)word_data
層的特征。
feeding = {
'word_data': 0,
'ctx_n2_data': 1,
'ctx_n1_data': 2,
'ctx_0_data': 3,
'ctx_p1_data': 4,
'ctx_p2_data': 5,
'verb_data': 6,
'mark_data': 7,
'target': 8
}
可以使用event_handler
回調(diào)函數(shù)來觀察訓(xùn)練過程,或進(jìn)行測(cè)試等。這里我們打印了訓(xùn)練過程的cost,該回調(diào)函數(shù)是trainer.train
函數(shù)里設(shè)定。
def event_handler(event):
if isinstance(event, paddle.event.EndIteration):
if event.batch_id and event.batch_id % 10 == 0:
print "Pass %d, Batch %d, Cost %f, %s" % (
event.pass_id, event.batch_id, event.cost, event.metrics)
if event.batch_id % 400 == 0:
result = trainer.test(reader=reader, feeding=feeding)
print "\nTest with Pass %d, Batch %d, %s" % (event.pass_id, event.batch_id, result.metrics)
if isinstance(event, paddle.event.EndPass):
# save parameters
with gzip.open('params_pass_%d.tar.gz' % event.pass_id, 'w') as f:
parameters.to_tar(f)
result = trainer.test(reader=reader, feeding=feeding)
print "\nTest with Pass %d, %s" % (event.pass_id, result.metrics)
通過trainer.train
函數(shù)訓(xùn)練:
trainer.train(
reader=reader,
event_handler=event_handler,
num_passes=1,
feeding=feeding)
訓(xùn)練完成之后,需要依據(jù)某個(gè)我們關(guān)心的性能指標(biāo)選擇最優(yōu)的模型進(jìn)行預(yù)測(cè),可以簡(jiǎn)單的選擇測(cè)試集上標(biāo)記錯(cuò)誤最少的那個(gè)模型。預(yù)測(cè)時(shí)使用 paddle.layer.crf_decoding
,和訓(xùn)練不同的是,該層沒有正確的標(biāo)簽層作為輸入。如下所示:
predict = paddle.layer.crf_decoding(
size=label_dict_len,
input=feature_out,
param_attr=paddle.attr.Param(name='crfw'))
這里選用測(cè)試集的一條數(shù)據(jù)作為示例。
test_creator = paddle.dataset.conll05.test()
test_data = []
for item in test_creator():
test_data.append(item[0:8])
if len(test_data) == 1:
break
推斷接口paddle.infer
返回標(biāo)簽的索引,并查詢?cè)~典labels_reverse
,打印出標(biāo)記的結(jié)果。
labs = paddle.infer(
output_layer=predict, parameters=parameters, input=test_data, field='id')
assert len(labs) == len(test_data[0][0])
labels_reverse={}
for (k,v) in label_dict.items():
labels_reverse[v]=k
pre_lab = [labels_reverse[i] for i in labs]
print pre_lab
語義角色標(biāo)注是許多自然語言理解任務(wù)的重要中間步驟。這篇教程中我們以語義角色標(biāo)注任務(wù)為例,介紹如何利用PaddlePaddle進(jìn)行序列標(biāo)注任務(wù)。教程中所介紹的模型來自我們發(fā)表的論文[10]。由于 CoNLL 2005 SRL任務(wù)的訓(xùn)練數(shù)據(jù)目前并非完全開放,教程中只使用測(cè)試數(shù)據(jù)作為示例。在這個(gè)過程中,我們希望減少對(duì)其它自然語言處理工具的依賴,利用神經(jīng)網(wǎng)絡(luò)數(shù)據(jù)驅(qū)動(dòng)、端到端學(xué)習(xí)的能力,得到一個(gè)和傳統(tǒng)方法可比、甚至更好的模型。在論文中我們證實(shí)了這種可能性。關(guān)于模型更多的信息和討論可以在論文中找到。
本教程 由 PaddlePaddle 創(chuàng)作,采用 知識(shí)共享 署名-相同方式共享 4.0 國(guó)際 許可協(xié)議進(jìn)行許可。