使用PaddleFluid和TensorFlow训练序列标注模型
專欄介紹:Paddle Fluid 是用來(lái)讓用戶像 PyTorch 和 Tensorflow Eager Execution 一樣執(zhí)行程序。在這些系統(tǒng)中,不再有模型這個(gè)概念,應(yīng)用也不再包含一個(gè)用于描述 Operator 圖或者一系列層的符號(hào)描述,而是像通用程序那樣描述訓(xùn)練或者預(yù)測(cè)的過(guò)程。
本專欄將推出一系列技術(shù)文章,從框架的概念、使用上對(duì)比分析 TensorFlow 和 Paddle Fluid,為對(duì) PaddlePaddle 感興趣的同學(xué)提供一些指導(dǎo)。
上一篇我們通過(guò) RNN 語(yǔ)言模型初識(shí) PaddleFluid 和 TensorFlow 中的循環(huán)神經(jīng)網(wǎng)絡(luò)模型。了解了:
在 PaddleFluid 和 TensorFlow 平臺(tái)下如何組織序列輸入數(shù)據(jù);
如何使用循環(huán)神經(jīng)網(wǎng)絡(luò)單元;
使用中的注意事項(xiàng)。
可以看到 PaddleFluid 中的各種循環(huán)神經(jīng)網(wǎng)絡(luò)單元都直接支持非填充序列作為輸入,用戶在使用時(shí)無(wú)需對(duì) mini-batch 中的不等長(zhǎng)序列進(jìn)行填充,無(wú)需關(guān)心填充位是否會(huì)對(duì)代價(jià)(loss)計(jì)算產(chǎn)生影響,從而需要在計(jì)算損失時(shí)對(duì)填充位置進(jìn)行過(guò)濾這樣的細(xì)節(jié),對(duì)使用來(lái)說(shuō)無(wú)疑是十分方便的。?
循環(huán)神經(jīng)網(wǎng)絡(luò)的是深度學(xué)習(xí)模型中最為重要的一部分,這一篇我們以序列標(biāo)注任務(wù)為例將會(huì)構(gòu)造一個(gè)更加復(fù)雜的循環(huán)神經(jīng)網(wǎng)絡(luò)模型用于命名實(shí)體識(shí)別任務(wù)。我們的關(guān)注點(diǎn)始終放在:在兩個(gè)平臺(tái)下:(1)如何組織序列數(shù)據(jù);(2)如何使用序列處理單元(不限于循環(huán)神經(jīng)網(wǎng)絡(luò))。
這一篇會(huì)看到:?
1. PaddleFluid Data Feeder vs. 使用 TensorFlow r1.4 之后 release 的 Dataset API 讀取數(shù)據(jù);?
2. 在 PaddleFluid 和 TensorFlow 中,使用條件隨機(jī)場(chǎng)(Conditional Random Field,CRF)單元;?
3. 在 PaddleFluid 和 TensorFlow 中,通過(guò)數(shù)據(jù)并行方式使用多塊 GPU 卡進(jìn)行訓(xùn)練。
如何使用代碼
本篇文章配套有完整可運(yùn)行的代碼, 請(qǐng)隨時(shí)從 github [1] 上獲取最新代碼。代碼包括以下幾個(gè)文件:
在執(zhí)行訓(xùn)練任務(wù)前,請(qǐng)首先在終端執(zhí)行下面的命令進(jìn)行訓(xùn)練數(shù)據(jù)下載以及預(yù)處理。
在終端運(yùn)行以下命令便可以使用默認(rèn)結(jié)構(gòu)和默認(rèn)參數(shù)運(yùn)行 PaddleFluid 訓(xùn)練序列標(biāo)注模型。
在終端運(yùn)行以下命令便可以使用默認(rèn)結(jié)構(gòu)和默認(rèn)參數(shù)運(yùn)行 TensorFlow 訓(xùn)練序列標(biāo)注模型。
背景介紹
序列標(biāo)注和命名實(shí)體識(shí)別
序列標(biāo)注是自然語(yǔ)言處理任務(wù)中的重要基礎(chǔ)任務(wù)之一。常見(jiàn)的分詞,詞性標(biāo)注,語(yǔ)義角色標(biāo)注,命名實(shí)體識(shí)別,甚至自動(dòng)問(wèn)答(QA)都可以通過(guò)序列標(biāo)注模型來(lái)實(shí)現(xiàn)。這一篇我們將訓(xùn)練一個(gè)序列標(biāo)注模型完成命名實(shí)體識(shí)別的任務(wù)。
我們先來(lái)看看,什么是序列標(biāo)注問(wèn)題呢?請(qǐng)看下面一幅圖:
▲?圖1. 序列標(biāo)注問(wèn)題
序列標(biāo)注任務(wù)是為一個(gè)一維的線性輸入序列中的每個(gè)元素打上標(biāo)簽集合中的某個(gè)標(biāo)簽。在上面的例子中,序列標(biāo)注就是為圖像序列中的每個(gè)元素貼上一個(gè)描述它們形狀的標(biāo)簽。而序列標(biāo)注任務(wù)的難點(diǎn)在于:序列中 元素的標(biāo)記和 它們?cè)谛蛄兄械奈恢妹芮邢嚓P(guān)。?
那么, 什么是命名實(shí)體識(shí)別呢?命名實(shí)體識(shí)別(Named Entity Recognition,NER)又稱作“專名識(shí)別”,是指識(shí)別文本中具有特定意義的實(shí)體,主要包括:人名、地名、機(jī)構(gòu)名、專有名詞等。
BIO 表示法?
序列標(biāo)注任務(wù)一般都會(huì)采用 BIO 表示方式來(lái)定義序列標(biāo)注的標(biāo)簽集,B 代表句子的開(kāi)始,I 代表句子中間,O 代表句子結(jié)束。通過(guò) B、I、O 三種標(biāo)記將不同的語(yǔ)塊賦予不同的標(biāo)簽,例如:對(duì)于一個(gè)標(biāo)記為 A 的命名實(shí)體,將它所包含的第一個(gè)語(yǔ)塊賦予標(biāo)簽 B-A,將它所包含的其它語(yǔ)塊賦予標(biāo)簽 I-A,不屬于任何命名實(shí)體的語(yǔ)塊賦予標(biāo)簽 O。圖 2 是用 BIO 表示標(biāo)注序列中命名實(shí)體的具體示例。
▲?圖2. BIO標(biāo)注方法示例
模型概覽
圖 3 是本篇模型的模型結(jié)構(gòu)概覽。?
▲?圖3. 序列標(biāo)注模型結(jié)構(gòu)概覽
我們要訓(xùn)練的序列標(biāo)注模型,接受:一個(gè)文本序列作為輸入,另一個(gè)與輸入文本序列等長(zhǎng)的標(biāo)記序列作為學(xué)習(xí)的目標(biāo)。首先通過(guò)上一篇介紹過(guò)的 word embedding 層的取詞作用得到詞向量, 接著經(jīng)過(guò)一個(gè)雙向 LSTM 單元學(xué)習(xí)序列的特征表示,這個(gè)特別表示最終作為條件隨機(jī)場(chǎng) CRF 的輸入完成最終的序列標(biāo)注任務(wù)。?
下面是對(duì)各個(gè)子模塊的進(jìn)一步說(shuō)明。
雙向循環(huán)神經(jīng)網(wǎng)絡(luò)?
在循環(huán)神經(jīng)網(wǎng)絡(luò)模型中,t 時(shí)刻輸出的隱藏層向量編碼了到 t 時(shí)刻為止所有輸入的信息,但由于循環(huán)神經(jīng)網(wǎng)絡(luò)單元計(jì)算的串行行:t 時(shí)刻循環(huán)神經(jīng)網(wǎng)絡(luò)但愿可以看到歷史(t 時(shí)刻之前),卻無(wú)法看到未來(lái)(t 時(shí)刻之后)。
一些自然語(yǔ)言處理任務(wù)總是能一次性拿到整個(gè)句子,這種情況下,在 t 時(shí)刻計(jì)算時(shí),如果能夠像獲取歷史信息一樣得到未來(lái)的信息,對(duì)序列學(xué)習(xí)任務(wù)會(huì)有很大幫助,雙向循環(huán)神經(jīng)網(wǎng)絡(luò)的出現(xiàn)正是為了解決這一問(wèn)題。
它的思想簡(jiǎn)單且直接:使用兩個(gè)循環(huán)神經(jīng)網(wǎng)絡(luò)單元( simple RNN,GRU 或者 LSTM 均可)分別以正向和反向順序?qū)W習(xí)輸入序列,再將兩者的輸出 向量進(jìn)行橫向拼接。這樣的一個(gè)輸出向量中就既包含了 t 時(shí)刻之前的信息,也包含了 t 時(shí)刻之后的信息。
條件隨機(jī)場(chǎng)?
使用神經(jīng)網(wǎng)絡(luò)模型解決問(wèn)題的思路通常都是:前層網(wǎng)絡(luò)學(xué)習(xí)輸入的特征表示,網(wǎng)絡(luò)的最后一層在特征基礎(chǔ)上完成最終任務(wù)。在序列 標(biāo)注任務(wù)中,雙向循環(huán)神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)輸入的特征表示,條件隨機(jī)場(chǎng)(Conditional Random Filed, CRF)正是在特征的基礎(chǔ)上完成序列標(biāo)注的一種計(jì)算單元,處于整個(gè)網(wǎng)絡(luò)的末端。
CRF 是一種概率化結(jié)構(gòu)模型,可以看作是一個(gè)概率無(wú)向圖模型(也叫作馬爾科夫隨機(jī)場(chǎng)),結(jié)點(diǎn)表示隨機(jī)變量,邊表示隨機(jī)變量之間的概率依賴關(guān)系。簡(jiǎn)單來(lái)講 CRF 學(xué)習(xí)條件概率:P(X|Y),其中 X=(x1,x2,...,xn) 是輸入序列,Y=(y1,y2,...,yn) 是標(biāo)記序列;解碼過(guò)程是給定 X 序列求解令 P(Y|X) 最大的 Y 序列,即。
條件隨機(jī)場(chǎng)是的定義:設(shè) G=(V,E) 是一個(gè)無(wú)向圖, V 是結(jié)點(diǎn)的集合,E 是無(wú)向邊的集合。V 中的每個(gè)結(jié)點(diǎn)對(duì)應(yīng)一個(gè)隨機(jī)變量 Yv,,其取值范圍為可能的標(biāo)記集合 {y},如果以隨機(jī)變量 X 為條件,每個(gè)隨機(jī)變量 Yv 都滿足以下馬爾科夫特性:?
其中,ω~v 表示兩個(gè)結(jié)點(diǎn)在圖 G 中是鄰近結(jié)點(diǎn),那么,(X,Y) 是一個(gè)條件隨機(jī)場(chǎng)。
線性鏈條件隨機(jī)場(chǎng)?
上面的定義并沒(méi)有對(duì) X 和 Y 的結(jié)構(gòu)給出更多約束,理論上來(lái)講只要標(biāo)記序列表示了一定的條件獨(dú)立性,G 的圖結(jié)構(gòu)可以是任意的。對(duì)序列標(biāo)注任務(wù),只需要考慮 X 和 Y 都是一個(gè)序列,于是可以形成一個(gè)如圖 4 所示的簡(jiǎn)單鏈?zhǔn)浇Y(jié)構(gòu)圖。在圖中,輸入序列 X 的元素之間并不存在圖結(jié)構(gòu),因?yàn)槲覀冎皇菍⑺鳛闂l件,并不做任何條件獨(dú)立假設(shè)。
▲?圖4. 輸入序列和標(biāo)記序列具有相同結(jié)構(gòu)的線性鏈條件隨機(jī)場(chǎng)
序列標(biāo)注問(wèn)題使用的是以上這種定義在線性鏈上的特殊條件隨機(jī)場(chǎng),稱之為線性鏈條件隨機(jī)場(chǎng)(Linear Chain Conditional Random Field)。下面,我們給出線性鏈條件隨機(jī)場(chǎng)的數(shù)學(xué)定義:?
定義 2 :線性鏈條件隨機(jī)場(chǎng) :設(shè) X=(x1,x2,...,xn),Y=(y1,y2,...,yn) 均為線性鏈表示的隨機(jī)變量序列,若在給定隨機(jī)變量序列 X 的條件下,隨機(jī)變量序列 Y 的條件概率分布 P(Y|X) 滿足馬爾科夫性:
i=1,2,...,n(在i=1和n時(shí)只考慮單邊)則稱 P(Y|X) 為線性鏈條件隨機(jī)場(chǎng)。X 表示輸入序列,Y 表示與之對(duì)應(yīng)的標(biāo)記序列。
根據(jù)線性鏈條件隨機(jī)場(chǎng)上的因子分解定理,在給定觀測(cè)序列 X 時(shí),一個(gè)特定標(biāo)記序列 Y 的概率可以定義為:
其中:
是規(guī)范化因子。
上面的式子中 tj 是定義在邊上的特征函數(shù),依賴于當(dāng)前和前一個(gè)位置,稱為轉(zhuǎn)移特征,表示對(duì)于觀察序列 X 及其標(biāo)注序列在 i 及 i?1 位置上標(biāo)記的轉(zhuǎn)移概率。sk 是定義在結(jié)點(diǎn)上的特征函數(shù),稱為狀態(tài)特征,依賴于當(dāng)前位置,表示對(duì)于觀察序列 X 及其 i 位置的標(biāo)記概率。λj 和 μk 分別是轉(zhuǎn)移特征函數(shù)和狀態(tài)特征函數(shù)對(duì)應(yīng)的權(quán)值。
線性鏈條件隨機(jī)場(chǎng)的優(yōu)化目標(biāo)?
實(shí)際上 ,t 和 s 可以用相同的數(shù)學(xué)形式表示,s 可以同樣也寫(xiě)為以下形式:
假設(shè)有 K1 個(gè)轉(zhuǎn)移特征,K2 個(gè)狀態(tài)特征,定義特征函數(shù):
再對(duì)轉(zhuǎn)移特征和狀態(tài)特在各個(gè)位置 i 求和有:
于是條件概率 P(Y|X) 可以寫(xiě)為:
我們把 f 統(tǒng)稱為特征函數(shù),ω 是權(quán)值,是 CRF 模型要求解的參數(shù)。
學(xué)習(xí)時(shí),對(duì)于給定的輸入序列和對(duì)應(yīng)的標(biāo)記序列的集合 D=[(X1,Y1),(X2,Y2),...,(XN,YN)] ,通過(guò)正則化的極大似然估計(jì),可以得到如下優(yōu)化目標(biāo):
這個(gè)優(yōu)化目標(biāo),可以通過(guò)反向傳播算法和整個(gè)神經(jīng)網(wǎng)絡(luò)一起更新求解。?
解碼時(shí),對(duì)于給定的輸入序列 X,通過(guò)解碼算法(通常有:維特比算法、Beam Search)求令出條件概率最大的輸出序列。
CRF小結(jié)
條件隨機(jī)場(chǎng)是這一篇網(wǎng)絡(luò)中一個(gè)相對(duì)復(fù)雜的計(jì)算單元。值得慶幸的是,在各個(gè)深度學(xué)習(xí)框架的幫助下,大多數(shù)情況下,我們只需要知道其原理便可以非常方便的使用,而不必過(guò)于關(guān)注 其內(nèi)部的實(shí)現(xiàn)細(xì)節(jié)。
這里我們?cè)賹?duì)上面的內(nèi)容進(jìn)行一個(gè)簡(jiǎn)單的總結(jié),方便大家使用 CRF 單元:
1. 在序列標(biāo)注網(wǎng)絡(luò)中, CRF 以循環(huán)神經(jīng)網(wǎng)絡(luò)單元輸出向量作為輸入,學(xué)習(xí)狀態(tài)特征和轉(zhuǎn)移特征。
2. 狀態(tài)特征只與當(dāng)然輸入有關(guān);轉(zhuǎn)移特征是一個(gè)矩陣,刻畫(huà)了標(biāo)記兩兩之間互相轉(zhuǎn)移的強(qiáng)度。
3. 假設(shè)循環(huán)神經(jīng)網(wǎng)絡(luò)單元輸出向量維度為 h ,序列中含有 t 個(gè)詞語(yǔ),共有 d 個(gè)標(biāo)記:
循環(huán)神經(jīng)網(wǎng)絡(luò)輸入矩陣的大小為:Out=t×h;
?
CRF 層以 Out 為輸入學(xué)習(xí)轉(zhuǎn)移特征:通過(guò)一個(gè) 全連接層將 Out 映射為一個(gè) t×d 的矩陣,也就是轉(zhuǎn)移特征;?
狀態(tài)特征是一個(gè):(d+2)×d 維的矩陣,刻畫(huà)了標(biāo)記之前轉(zhuǎn)移的強(qiáng)度。 這里的 +2 是需要學(xué)習(xí)序列開(kāi)始 <s> 向句子首詞轉(zhuǎn)移和和句子末尾詞向序列結(jié)束 <e> 轉(zhuǎn)移這樣兩種特殊的狀態(tài)轉(zhuǎn)移;
CRF 本質(zhì)上計(jì)算了一個(gè) softmax:給定標(biāo)記序列出現(xiàn)的概率。但困難之處在于 softmax 的歸一化分母是所有可能標(biāo)記序列,計(jì)算量很大。但由于引入了馬爾科夫假設(shè),這個(gè)歸一化分母可以巧妙地通過(guò)一個(gè)動(dòng)態(tài)規(guī)劃算法求解。?
4. CRF 的學(xué)習(xí)準(zhǔn)則是令 negative log likelihood 最大化。
數(shù)據(jù)集介紹
這一篇我們使用 Standford CS224d 課程中作業(yè) 2 [2] 的 NER 任務(wù)數(shù)據(jù)作為訓(xùn)練數(shù)據(jù)源。 進(jìn)入 data 目錄運(yùn)行 data/download.sh 腳本下載數(shù)據(jù)并預(yù)處理訓(xùn)練數(shù)據(jù)。預(yù)處理包括:1. 為輸入文本序列建立詞典;2. 組織輸入數(shù)據(jù)格式。?
運(yùn)行結(jié)束將會(huì)在 data 目錄下看到如下內(nèi)容。
├──?dev
├──?dev_src.txt
├──?dev_src.vocab
├──?dev_trg.txt
├──?dev_trg.vocab
├──?download.sh
├──?preprocess.py
├──?train
├──?train_src.txt
├──?train_src.vocab
├──?train_trg.txt
└──?train_trg.vocab
其中需要重點(diǎn)關(guān)注的是 train_src.txt 、 train_trg.txt 、 train_src.vocab和train_trg.vocab 文件。它們分別是:輸入文本序列;文本對(duì)應(yīng)的標(biāo)記序列;輸入文本序列的詞典以及標(biāo)記序列詞典。 train_src.txt 和 train_trg.txt 的一行是一條訓(xùn)練樣本,他們嚴(yán)格一一對(duì)應(yīng)。分別執(zhí)行 head -n 1 train_src.txt 和 head -n 1 train_trg.t xt 會(huì)看到如下內(nèi)容:
程序結(jié)構(gòu)
我們首先在此整體回顧一下使用 PaddleFluid 平臺(tái)和 TensorFlow 運(yùn)行神經(jīng)網(wǎng)絡(luò)模型的整體流程。
PaddleFluid
1. 調(diào)用 PaddleFluid API 描述神經(jīng)網(wǎng)絡(luò)模型。PaddleFluid 中 一個(gè)神經(jīng)網(wǎng)絡(luò)訓(xùn)練任務(wù)被稱之為一段 Fluid Program 。?
2. 定義 Fluid Program 執(zhí)行設(shè)備: place 。常見(jiàn)的有 fluid.CUDAPlace(0) 和 fluid.CPUPlace() 。
注:PaddleFluid 支持混合設(shè)備運(yùn)行,一些 運(yùn)算(operator)沒(méi)有特定設(shè)備實(shí)現(xiàn),或者為了提高全局資源利用率,可以為他們指定不同的計(jì)算設(shè)備。
3. 創(chuàng)建 PaddleFluid 執(zhí)行器(Executor),需要為執(zhí)行器指定運(yùn)行設(shè)備。
4. 讓執(zhí)行器執(zhí)行 fluid.default_startup_program() ,初始化神經(jīng)網(wǎng)絡(luò)中的可學(xué)習(xí)參數(shù),完成必要的初始化工作。?
5. 定義 DataFeeder,編寫(xiě) data reader,只需要關(guān)注如何返回一條訓(xùn)練/測(cè)試數(shù)據(jù)。?
6. 進(jìn)入訓(xùn)練的雙層循環(huán)(外層在 epoch 上循環(huán),內(nèi)層在 mini-batch 上循環(huán)),直到訓(xùn)練結(jié)束。
TensorFlow?
1. 調(diào)用 TensorFlow API 描述神經(jīng)網(wǎng)絡(luò)模型。 TensorFlow 中一個(gè)神經(jīng)網(wǎng)絡(luò)模型是一個(gè) Computation Graph。
2. 創(chuàng)建 TensorFlow Session 用來(lái)執(zhí)行計(jì)算圖。
3. 調(diào)用 sess.run(tf.global_variables_initializer()) 初始化神經(jīng)網(wǎng)絡(luò)中的可學(xué)習(xí)參數(shù)。
4. 編寫(xiě)返回每個(gè) mini-batch 數(shù)據(jù)的數(shù)據(jù)讀取腳本。
5. 進(jìn)入訓(xùn)練的雙層循環(huán)(外層在 epoch 上循環(huán),內(nèi)層在 mini-batch 上循環(huán)),直到訓(xùn)練結(jié)束。
如果不顯示地指定使用何種設(shè)備進(jìn)行訓(xùn)練,TensorFlow 會(huì)對(duì)機(jī)器硬件進(jìn)行檢測(cè)(是否有 GPU), 選擇能夠盡可能利用機(jī)器硬件資源的方式運(yùn)行。?
構(gòu)建網(wǎng)絡(luò)
基于 PaddleFluid 和 TensorFlow 的序列標(biāo)注網(wǎng)絡(luò)分別定義在 sequence_tagging_fluid.py 和 sequence_tagging_tensorflow.py 的 NER_net 類中,詳細(xì)信息請(qǐng)參考完整代碼,這里對(duì)重要部分進(jìn)行說(shuō)明。?
加載訓(xùn)練數(shù)據(jù)?
PaddleFluid:編寫(xiě)Data Reader?
PaddleFluid 模型通過(guò) fluid.layers.data 來(lái)接收輸入數(shù)據(jù)。序列標(biāo)注網(wǎng)絡(luò)以圖片以及圖片對(duì)應(yīng)的類別標(biāo)簽作為網(wǎng)絡(luò)的輸入:
????name="source",?shape=[1],?dtype="int64",?lod_level=1)
self.target?=?fluid.layers.data(
????name="target",?shape=[1],?dtype="int64",?lod_level=1)
定義 data layer 的核心是指定輸入 Tensor 的形狀( shape )和類型。
序列標(biāo)注中,輸入文本序列和標(biāo)記序列都使用 one-hot 特征作為輸入,一個(gè)詞用一個(gè)和字典大小相同的向量表示,每一個(gè)位置對(duì)應(yīng)了字典中的 一個(gè)詞語(yǔ)。one-hot 向量?jī)H有一個(gè)維度為 1, 其余全部為 0。在上面定義的 data layer 中 source 和 target 的形狀都是 1,類型是 int64 。?
PaddleFluid 支持非填充的序列輸入,這是通過(guò) LoD Tensor 實(shí)現(xiàn)的。關(guān)于什么是 LoD Tensor 請(qǐng)參考上一篇使用 PaddleFluid 和 TensorFlow 訓(xùn)練 RNN 語(yǔ)言模型中的介紹,這一篇不再贅述。有了 LoD Tensor 的概念后,在 PaddleFluid 中,通過(guò) DataFeeder 模塊來(lái)為網(wǎng)絡(luò)中的 data layer 提供數(shù)據(jù),調(diào)用方式如下面的代碼所示:
????paddle.reader.shuffle(
????????data_reader(conf.train_src_file_name,?conf.train_trg_file_name,
????????????????????conf.src_vocab_file,?conf.trg_vocab_file),
????????buf_size=1024000),
????batch_size=conf.batch_size)
place?=?fluid.CUDAPlace(0)?if?conf.use_gpu?else?fluid.CPUPlace()
feeder?=?fluid.DataFeeder(feed_list=[net.source,?net.target],?place=place)
觀察以上代碼,需要用戶完成的僅有:編寫(xiě)一個(gè)實(shí)現(xiàn)讀取一條數(shù)據(jù)的 python 函數(shù): data_reader 。 data_reader 的代碼非常簡(jiǎn)單,我們?cè)賮?lái)看一下它的具體實(shí)現(xiàn):
????def?__load_dict(dict_file_path):
????????word_dict?=?{}
????????with?open(dict_file_path,?"r")?as?fdict:
????????????for?idx,?line?in?enumerate(fdict):
????????????????if?idx?<?2:?continue
????????????????word_dict[line.strip().split("\t")[0]]?=?idx?-?2
????????return?word_dict
????def?__reader():
????????src_dict?=?__load_dict(src_vocab_file)
????????trg_dict?=?__load_dict(trg_vocab_file)
????????with?open(src_file_name,?"r")?as?fsrc,?open(trg_file_name,
????????????????????????????????????????????????????"r")?as?ftrg:
????????????for?src,?trg?in?izip(fsrc,?ftrg):
????????????????src_words?=?src.strip().split()
????????????????trg_words?=?trg.strip().split()
????????????????src_ids?=?[src_dict[w]?for?w?in?src_words]
????????????????trg_ids?=?[trg_dict[w]?for?w?in?trg_words]
????????????????yield?src_ids,?trg_ids
????return?__reader
在上面的代碼中:?
1. data_reader 是一個(gè) python generator ,函數(shù)名字可以任意指定,無(wú)需固定。?
2. data_reader 打開(kāi)輸入序列文件和標(biāo)記序列文件,每次從這兩個(gè)文件讀取一行,一行既是一條訓(xùn)練數(shù)據(jù),返回一個(gè) python list,這個(gè) python list 既是序列中所有時(shí)間步。具體的數(shù)據(jù)組織方式如下表所示,其中?i 代表一個(gè)整數(shù):
3.?paddle.batch() 接口用來(lái)構(gòu)造 mini-batch 輸入,會(huì)調(diào)用 data_reader 將數(shù)據(jù)讀入一個(gè) pool 中,對(duì) pool 中的數(shù)據(jù)進(jìn)行 shuffle,然后依次返回每個(gè) mini-batch 的數(shù)據(jù)。
TensorFlow:使用Dataset API
在之前的篇章中我們都使用 TensorFlow 的 placeholder 接入訓(xùn)練數(shù)據(jù),這一篇我們使用一種新的方式 TensorFlow 在 r1.3 版本之后引入的 Dataset API 來(lái)讀取數(shù)據(jù)。
參考 Google 官方給出的 Dataset API 中的類圖 [3],使用 TensorFlow 的 Dataset API,首先引入兩個(gè)抽象概念:
1. tf.data.Dataset 表示一系列元素,其中每個(gè)元素包含一個(gè)或多個(gè) Tensor 對(duì)象。
2. tf.data.Iterator 提供了從數(shù)據(jù)集中取出元素的方法。 Iterator.get_next() 會(huì)在執(zhí)行時(shí)生成 Dataset 的下一個(gè) /mini-batch 元素。
定義 Dataset?
目前 Dataset API 還提供了三種預(yù)定義好的定義 Dataset 的方式。這一篇中我們主要面向文本數(shù)據(jù)的處理,使用其中的 TextLineDataset 接口。?
tf.data.TextLineDataset:接口的輸入是一個(gè)文件列表,輸出是一個(gè) TensorFlow dataset , dataset 中的每一個(gè)元素就對(duì)應(yīng)了文件中的一行。通過(guò)下面的調(diào)用傳入輸入序列文本路徑和標(biāo)記序列文本路徑便可返回一個(gè) Dataset 。
trg_dataset?=?tf.data.TextLineDataset(trg_file_name)
獲取 Iterator?
需要說(shuō)明的是,TensorFlow 中的循環(huán)神經(jīng)網(wǎng)絡(luò)要求一個(gè) mini-batch 之內(nèi)序列長(zhǎng)度相等,使用 Dynamic RNN 時(shí),batch 和 batch 之間序列長(zhǎng)度可以不相等,因此對(duì)一個(gè) mini-batch 之內(nèi)的數(shù)據(jù)需要進(jìn)行填充。?
Dataset API 提供了 padded_batch 幫助構(gòu)造填充后的 mini-batch 數(shù)據(jù)。?
提示:使用 bucket 分桶,從桶內(nèi)取 mini-batch 數(shù)據(jù),填充至一個(gè) batch 中的最長(zhǎng)序列長(zhǎng)度能夠有效提高 dynamic rnn 的計(jì)算效率。?
下面的代碼返回 Iterator ,使用先分桶,然后再取 mini-batch 數(shù)據(jù)填充至? batch 中最長(zhǎng)序列長(zhǎng)度的方式。完整代碼請(qǐng)參考:iterator_helper_tf [4]。
??????????????????????trg_file_name,
??????????????????????src_vocab_file,
??????????????????????trg_vocab_file,
??????????????????????batch_size,
??????????????????????pad_token="</p>",
??????????????????????max_sequence_length=None,
??????????????????????unk_id=1,
??????????????????????num_parallel_calls=4,
??????????????????????num_buckets=5,
??????????????????????output_buffer_size=102400,
??????????????????????is_training=True):
????def?__get_word_dict(vocab_file_path,?unk_id):
????????return?tf.contrib.lookup.index_table_from_file(
????????????vocabulary_file=vocab_file_path,
????????????key_column_index=0,
????????????default_value=unk_id)
????src_dataset?=?tf.data.TextLineDataset(src_file_name)
????trg_dataset?=?tf.data.TextLineDataset(trg_file_name)
????dataset?=?tf.data.Dataset.zip((src_dataset,?trg_dataset))
????if?is_training:
????????dataset?=?dataset.shuffle(
????????????buffer_size=output_buffer_size,?reshuffle_each_iteration=True)
????src_trg_dataset?=?dataset.map(
????????lambda?src,?trg:?(tf.string_split([src]).values,?\
????????????????tf.string_split([trg]).values),
????????num_parallel_calls=num_parallel_calls).prefetch(output_buffer_size)
????src_dict?=?__get_word_dict(src_vocab_file,?unk_id)
????trg_dict?=?__get_word_dict(trg_vocab_file,?unk_id)
????src_pad_id?=?tf.cast(src_dict.lookup(tf.constant(pad_token)),?tf.int32)
????trg_pad_id?=?tf.cast(trg_dict.lookup(tf.constant(pad_token)),?tf.int32)
????#?convert?word?string?to?word?index
????src_trg_dataset?=?src_trg_dataset.map(
????????lambda?src,?trg:?(
????????????????tf.cast(src_dict.lookup(src),?tf.int32),
????????????????tf.cast(trg_dict.lookup(trg),?tf.int32)),
????????num_parallel_calls=num_parallel_calls).prefetch(output_buffer_size)
????#?Add?in?sequence?lengths.
????src_trg_dataset?=?src_trg_dataset.map(
????????lambda?src,?trg:?(src,?trg,?tf.size(src)),
????????num_parallel_calls=num_parallel_calls).prefetch(output_buffer_size)
????def?__batching_func(x):
????????return?x.padded_batch(
????????????batch_size,
????????????padded_shapes=(
????????????????tf.TensorShape([None]),??#?src
????????????????tf.TensorShape([None]),??#?trg
????????????????tf.TensorShape([]),??#seq_len
????????????),
????????????padding_values=(src_pad_id,?trg_pad_id,?0,?))
????if?num_buckets?>?1:
????????def?__key_func(unused_1,?unused_2,?seq_len):
????????????if?max_sequence_length:
????????????????bucket_width?=?(
????????????????????max_sequence_length?+?num_buckets?-?1)?//?num_buckets
????????????else:
????????????????bucket_width?=?10
????????????bucket_id?=?seq_len?//?bucket_width,
????????????return?tf.to_int64(tf.minimum(num_buckets,?bucket_id))
????????def?__reduce_func(unused_key,?windowed_data):
????????????return?__batching_func(windowed_data)
????????batched_dataset?=?src_trg_dataset.apply(
????????????tf.contrib.data.group_by_window(
????????????????key_func=__key_func,
????????????????reduce_func=__reduce_func,
????????????????window_size=batch_size))
????else:
????????batched_dataset?=?__batching_func(curwd_nxtwd_dataset)
????batched_iter?=?batched_dataset.make_initializable_iterator()
????src_ids,?trg_ids,?seq_len?=?batched_iter.get_next()
????return?BatchedInput(
????????initializer=batched_iter.initializer,
????????source=src_ids,
????????target=trg_ids,
????????sequence_length=seq_len)
構(gòu)建網(wǎng)絡(luò)結(jié)構(gòu)及運(yùn)行
構(gòu)建網(wǎng)絡(luò)結(jié)構(gòu)及運(yùn)行的過(guò)程對(duì)兩個(gè)平臺(tái)上都是常規(guī)流程。
1. 構(gòu)建網(wǎng)絡(luò)時(shí)調(diào)用相關(guān)的 API 接口,令一個(gè) 計(jì)算單元的輸出成為下一個(gè)計(jì)算單元的輸入建立起網(wǎng)絡(luò)的連通性;具體請(qǐng)參考 sequence_tagging_fluid.py 和 sequence_tagging_tensorflow.py 中 NER_net 類的實(shí)現(xiàn)。
2. 運(yùn)行訓(xùn)練以及解碼具體請(qǐng)參考 sequence_tagging_fluid.py 和 sequence_tagging_tensorflow.py 中 train 函數(shù)的實(shí)現(xiàn)。
模型中核心模塊:LSTM 單元在兩個(gè)平臺(tái)下的差異及注意事項(xiàng)請(qǐng)參考上一篇:使用 PaddleFluid 和 TensorFlow 訓(xùn)練 RNN 語(yǔ)言模型,這里不再贅述。
總結(jié)
這一篇繼續(xù)在序列標(biāo)注模型中了解 PaddleFluid 和 TensorFlow 在接受序列輸入,序列處理策略上的不同。?
1. PaddleFluid 引入了 LoD Tensor 的概念,所有序列處理模塊(包括所有循環(huán)神經(jīng)網(wǎng)絡(luò)單元,文本卷積)都支持非填充的序列輸入,使用時(shí)無(wú)需對(duì) mini-batch 數(shù)據(jù)進(jìn)行填充,也就避免了對(duì)填充位的各種特殊處理,這一點(diǎn)非常方便。?
2. TensorFlow 中的 Dynamic RNN 支持 mini-batch 之間序列不等長(zhǎng),但仍要求一個(gè) mini-batch 內(nèi)的數(shù)據(jù)填充至一樣長(zhǎng)。?
3. PaddleFluid 中通過(guò) Data Feeder 提供訓(xùn)練數(shù)據(jù),只需要編寫(xiě)一個(gè) python generator 實(shí)現(xiàn)從原始輸入文件中讀取一條訓(xùn)練樣本, 框架會(huì)完成數(shù)據(jù) shuffle 和組織 mini-batchd 工作。?
4. 這一篇使用了 TensorFlow r1.3 后 release 的 Dataset API,數(shù)據(jù)讀取部分也是一個(gè) computation graph,能夠提高 I/O 效率,使用相對(duì)復(fù)雜一些。?
本篇代碼中提供了通過(guò)數(shù)據(jù)并行策略在 PaddleFluid 平臺(tái)下使用多塊 GPU 卡進(jìn)行訓(xùn)練,在 TensorFlow 中使用多卡相對(duì)復(fù)雜一些,這些主題會(huì)在下面繼續(xù)討論。
參考文獻(xiàn)
[1]. 本文配套代碼
https://github.com/JohnRabbbit/TF2Fluid/tree/master/05_sequence_tagging
[2].? Standford CS224d課程作業(yè)2
http://cs224d.stanford.edu/assignment2/index.html
[3].?Google官方Dataset API
https://developers.googleblog.com/2017/09/introducing-tensorflow-datasets.html
[4].?iterator_helper_tf
https://github.com/JohnRabbbit/TF2Fluid/blob/master/05_sequence_tagging/iterator_helper_tf.py
關(guān)于PaperWeekly
PaperWeekly 是一個(gè)推薦、解讀、討論、報(bào)道人工智能前沿論文成果的學(xué)術(shù)平臺(tái)。如果你研究或從事 AI 領(lǐng)域,歡迎在公眾號(hào)后臺(tái)點(diǎn)擊「交流群」,小助手將把你帶入 PaperWeekly 的交流群里。
▽ 點(diǎn)擊 |?閱讀原文?| 加入社區(qū)刷論文
總結(jié)
以上是生活随笔為你收集整理的使用PaddleFluid和TensorFlow训练序列标注模型的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 图像压缩哪家强?请看这份超详细对比
- 下一篇: 论文解读 | 基于递归联合注意力的句子匹