您當前的位置:首頁 > 舞蹈

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

作者:由 seven 發表于 舞蹈時間:2019-08-24

最近在系統地接觸學習NER,但是發現這方面的小帖子還比較零散。所以我把學習的記錄放出來給大家作參考,其中匯聚了很多其他博主的知識,在本文中也放出了他們的原鏈。希望能夠以這篇文章為載體,幫助其他跟我一樣的學習者梳理、串起NER的各個小知識點,最後上手NER的主流模型(Bilstm+CRF)(文中講的是pytorch,但是懂了pytorch去看keras十分容易相信我哈)

全文結構:

一、NER資料(主要介紹NER)

二、主流模型Bilstm-CRF實現詳解(Pytorch篇)

三、實現程式碼的拓展(在第二點的基礎上進行拓展)

程式碼執行環境

電腦:聯想小新Air 13 pro

CPU:i5 ,4G執行記憶體

顯示卡:NVIDIA GeForce 940MX,2G視訊記憶體

系統:windows10 64位系統

軟體:Anaconda 5。3。0 python 3。6。6 Pytorch1。0

一、NER資料

參考:

NLP之CRF應用篇(序列標註任務)(CRF++的詳細解析、Bi-LSTM+CRF中CRF層的詳細解析、Bi-LSTM後加CRF的原因、CRF和Bi-LSTM+CRF最佳化目標的區別)

CRF++完成的是學習和解碼的過程:訓練即為學習的過程,預測即為解碼的過程。

參考:

Bilstm+crf中的crf詳解(這份資料對後面程式碼的理解是有幫助的)

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

參考:

BiLSTM-CRF中CRF層解析-2

在上一篇的參考中提到,會在每一句話的開始加上“START”,在句尾加上“END”,這點我們可能會有疑惑。

這篇參考給予瞭解答:

這是為了使轉移得分矩陣的魯棒性更好,才額外加兩個標籤:START和END,START表示一句話的開始,注意這不是指該句話的第一個單詞,START後才是第一個單詞,同樣的,END代表著這句話的結束。

下表就是一個轉移得分矩陣的示例,該示例包含了START和END標籤。

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

每一個格里的值表示的意思是:這個格的行值轉成列值的機率大小。打個比方:上圖中紅框(B-Person,I-person)的值為0。9,表示的意思就是B-person轉移至I-person的機率為0。9,這是合乎BIO標註的規定的(B是實體的開始,I是實體的內部)。類推一下,藍框的意思代表的就是B-Organization轉移至I-Organization的機率為0。8。

參考:

BiLSTM-CRF中CRF層解析-3(看完前面的參考來看這份,簡直不要太良心了,易懂很多)

但是前面很多概念有提到,就不贅述了,只是加深一下印象,順帶推一下這個博主對CRF的一系類解析

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

其中

Pi,yi

第 i 個位置 softmax 輸出為 yi 的機率

Ayi,yi+1

從 yi 到 yi+1 的轉移機率

,當tag(B-person,B-location……)個數為n的時候,轉移機率矩陣為(n+2)*(n+2),因為額外增加了一個開始位置和結束位置。這個得分函式S就很好地彌補了傳統BiLSTM的不足,因為我們當一個預測序列得分很高時,並不是各個位置都是softmax輸出最大機率值對應的label,還要考慮前面

轉移機率相加最大

即還要符合輸出規則(B後面不能再跟B)

,比如假設BiLSTM輸出的最有可能序列為BBIBIOOO,那麼因為我們的轉移機率矩陣中B->B的機率很小甚至為負,那麼根據s得分,這種序列不會得到最高的分數,即就不是我們想要的序列。

整個過程中需要訓練的引數為:

BiLSTM中的引數

轉移機率矩陣A

BiLSTM+CRF的預測:

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

作為預測結果輸出。

參考:

BiLSTM+crf的一些理解(也是很有幫助的資料,記錄如下)

model中由於CRF中有

轉移特徵

,即它會考慮輸出label之間的順序性,所以考慮用CRF去做BiLSTM的輸出層。

二、NER主流模型——Bilstm-CRF程式碼詳解部分(pytorch篇)

參考1:

ADVANCED: MAKING DYNAMIC DECISIONS AND THE BI-LSTM CRF(PyTorch關於BILSTM+CRF的tutorial)

從參考1中 找到 pytorch 關於 Bilstm-CRF 模型的tutorial,然後執行它,我這裡講一下幾個主體部分的作用(我是用jupyter notebook跑的,大家最好也跑完帶著疑惑往下看):

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

(定義函式)log_sum_exp:先做減法的原因在於,減去最大值可以避免e的指數次導致計算機上溢

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

訓練資料集的格式:list內為tuple,然後分字以及bio欄位

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

建立text欄位以及bio標籤對映成文字的索引,這一步是可替換的,因為是抽象對映為數字

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

建立BiLSTM_CRF model,及最佳化器

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

在該demo中建立model的四個引數

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

訓練300epoch,畫紅框的是核心。將text欄位及bio label轉換為對映的數字,輸入模型即可訓練

現在的很多NLP的網紅模型,無非是將文字到數字的對映建立的更合理。是可拓展的。

另外,這裡的模型訓練是適用 model。neg_log_likelihood() 。這是程式碼中建立好的 BiLSTM_CRF 類的一部分,弄明白需繼續看 model(參考:pytorch版的bilstm+crf實現sequence label,

有模型註解

torch。nn。Parameter():首先可以把這個函數理解為

型別轉換函式

,將一個不可訓練的型別Tensor轉換成可以訓練的型別parameter並將這個parameter繫結到這個module裡面(net。parameter()中就有這個繫結的parameter,所以在引數最佳化的時候可以進行最佳化的),所以經過型別轉換這個self。v變成了模型的一部分,成為了模型中根據訓練可以改動的引數了。使用這個函式的

目的

也是想讓某些變數在學習的過程中不斷的修改其值以達到最最佳化。(參考)【一句話解釋:就是希望它能夠梯度下降,學習最佳化】

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

(建立轉移矩陣A,並加了兩個我們不會變動的約束條件:1是我們不會從其他tag轉向start。2是不會從stop開始轉向其他。所以這些位置設為-1e4)

注意:轉移矩陣是隨機的,而且放入了網路中,是會更新的)(如果轉移矩陣A的概念不懂可以理解了轉移矩陣再回來看

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

即類似於將矩陣中start那一行及stop那一列添加了約束——self。transitions。data

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

forward_var

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

lstm層:經過了embedding,lstm,linear層,output為發射矩陣——emission matrix

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

核心部分,註解如圖

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

_forward_alg

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

feats。size() = torch。Size([7, 5])

參考2:

pytorch實現BiLSTM+CRF用於NER(命名實體識別)(提到了viterbi編碼,很有啟發!記錄如下)【統籌CRF演算法code,以及forward_score - gold_score 作為loss的根本原因】

CRF是判別模型, 判別公式如下 y 是標記序列,x 是單詞序列,即已知單詞序列,求最有可能的標記序列

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

Score(x, y) 即單詞序列 x 產生標記序列 y 的得分,得分越高,說明其產生的機率越大。

在pytorch的tutorial中,其用於實體識別定義的 Score(x,y) 包含兩個特徵函式,一個是

轉移特徵函式

,一個是

狀態特徵函式

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

程式碼中用到了

前向演算法

維特比演算法(viterbi)

log_sum_exp函式

就是計算

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

前向演算法(_forward_alg)

需要用到這個函式

前向演算法,求出α(alpha),即Z(x),也就是

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

,如果不懂可以看一下

李航的書

關於CRF的前向演算法

但是不同於李航書的是,程式碼中α都取了對數,一個是為了

運算方便

,一個為了後面的

最大似然估計

這個程式碼裡面沒有進行最佳化,作者也指出來了,

其實對feats的迭代完全沒有必要用兩次迴圈,其實矩陣相乘就夠了

,作者是為了方便我們理解,所以細化了步驟

維特比演算法(viterbi)

中規中矩,可以參考

李航書上條件隨機場的預測演算法

neg_log_likelihood函式

的作用:

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

我們知道forward_score是log Z(x),即

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

gold_score是

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

我們的目標是極大化

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

兩邊取對數即

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

所以我們需要極大化 gold_score - forward_score,也就是極小化 forward_score - gold_score。

這就是為什麼 forward_score - gold_score 可以作為loss的根本原因。

參考3:

Bi-LSTM-CRF for Sequence Labeling(記錄如下)

這篇跟參考2講的是一個意思。得分

score

表示為

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

也很清晰地提到了

CRF

的作用以及

score

P

A

矩陣分別代表的含義:P為Bi-LSTM的輸出矩陣;A為tag之間的轉移矩陣

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

根據畫紅線的去看上方score的定義

在許多參考文章中都有提到score的成分包含了兩部分,一個是Bilstm的輸出結果,另一個就是CRF的轉移矩陣,而轉移矩陣的作用就是去給標註結果一些約束。例如標註B的後面不能接B這種約束。這種約束是根據轉移矩陣A提供的。而轉移矩陣A是根據你提供的訓練集,訓練學習、梯度下降得到的。根據畫紅線的去看上方score的定義,就明白定義了每一種標註情況為一條路徑,使用score去計算該路徑的得分的意思了。再囉嗦一下:Ayi, yi+1是表達這個tagyi(標註yi)轉移至下一個tagyi+1(標註yi+1)的分數(機率)。而Pi,yi就是Bilstm的輸出矩陣,可以看到每個字對應到不同tag(標註)的分數。【不懂也沒關係,有很多文章都提到了。反覆看就會有感覺了】

CRF的機率函式

表示為

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

S(X,y)的計算很簡單,而

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

(下面記作

logsumexp

)的計算稍微複雜一些,因為

需要計算每一條可能路徑的分數

。這裡用一種簡便的方法,對於到詞的路徑,可以先把到詞的logsumexp計算出來,因為

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

因此先

計算每一步的路徑分數和直接計算全域性分數相同

,但這樣可以大大

減少計算的時間

參考4:

BiLSTM-CRF中CRF層解析-4(用程式的思想去理解怎麼計算所有路徑的得分和,巨良心)

這篇文章提到了動態規劃的程式設計思想,雖然跟pytorch的tutorial有些許偏差。但已經很到位了。卡在

_foward_alg函式

的同學多看幾遍這篇文章,先理解一下動態規劃的思路吧。會有幫助的。

參考5:

BiLSTM-CRF中CRF層解析-5(還是這個系列,講預測)

上一篇在講loss的一部分:所有路徑的得分和。現在講怎麼去解碼預測。大概的思路就是根據最高的得分去反哺這條路徑,使用較多的就是Viterbi解碼了。這篇文章就很詳細很詳細地提到了怎麼去解碼這個路徑,具體就直接進到博主的解析上看吧!致敬一下參考4和參考5的作者:勤勞的凌菲

參考6:

pytorch lstm crf 程式碼理解(走心的解讀,統籌程式碼塊的作用,其心得部分十分到位)

這裡就羅列一下作者的心得體會:

反向傳播不需要一定使用forward(),而且不需要定義loss=nn。MSError()等,直接score1 - score2 (

neg_log_likelihood函式

),就可以反向傳播了。

使用self。transitions = nn。Parameter(torch。randn(self。tagset_size, self。tagset_size)) 將想要更新的矩陣,放入到module的引數中,然後兩個矩陣無論怎麼操作,只要滿足 y = f(x, w),就能夠反向傳播

從程式碼看出每個迴圈裡只是去了轉移矩陣A的一行,或者就是一個值,進行操作,轉移矩陣就能夠更新。至於為什麼能夠更新,作者也不知道,這涉及到pytorch的機制。

發射矩陣(emit score)是 BiLSTM算出來的。轉移矩陣是單獨定義的,要學習的。初始矩陣是 [-1000,-1000,-1000,0,-1000],固定的。因為當加了開始符號後,第一個位置是開始符號的機率是100%。

顯式的加入了start標記,隱式的使用了end標記(總是最後多一步轉移到end)的分數

參考7:

PyTorch高階實戰教程: 基於BI-LSTM CRF實現命名實體識別和中文分詞

對這份pytorch NER tutorial,只需要將中文分詞的資料集預處理成作者提到的格式,即可很快的就遷移了這個程式碼到中文分詞中。但這種方式並不適合處理很多的資料(資料格式遷移問題),但是對於 demo 來說非常友好,把英文改成中文,標籤改成分詞問題中的 “BEMS” 就可以跑起來了。

參考資料:

pytorch中bilstm-crf部分code解析(也很良心了,作者畫了草圖幫助理解)

pytorch版的bilstm+crf實現sequence label(比較粗的註解)

三、模型程式碼拓展部分(pytorch篇)

前面我們介紹了很久pytorch實現NER任務的主流model——Bilstm+CRF,為了便於新手入門,所以還是稍微簡陋了一些。剛好看到有份資源是移植這個tutorial去實踐的,還是很有必要學習的

資料:

ChineseNER(中文NER、有tf和torch版,市面上Bilstm+CRF的torch code基本都是出自官方tutorial)(py2。7)

因為是py2的程式碼,所以是需要改成py3的。

訓練程式碼:

train_py3。py

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

資料集地址

但這個“Bosondata。pkl”是需要我們先到路徑“ChineseNER\data\boson”下執行“data_util。py”才生成的

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

生成“Bosondata。pkl”的位置

當然,原始碼也是存在python版本的問題(原始碼是py2的)例如:

報錯:

AttributeError: ‘str’ object has no attribute ‘decode’

解決方法:把

。decode(“*”)

那部分刪除即可

溯源:

https://www。

cnblogs。com/xiaodai0/p/

10564471。html

報錯:ImportError: No module named ‘compiler。ast’

解決方法:重新寫一個函式來替代

from compiler。ast import flatten

的flatten函式

import collections

def flatten(x):

result = []

for el in x:

if isinstance(x, collections。Iterable) and not isinstance(el, str):

result。extend(flatten(el))

else:

result。append(el)

return result

溯源:

https://

blog。csdn。net/w5688414/

article/details/78489277

NLP-入門實體命名識別(NER)+Bilstm-CRF模型原理Pytorch程式碼詳解——最全攻略

當成功執行“data_util。py”生成“Bosondata。pkl”後,把“train_py3。py”裡面第38行的“word2id”修改為“id2word”(應該是作者打錯了),然後在程式碼路徑下創造資料夾“model”,就可以開始訓練了。

最後附上修改後的github原始碼

供參考借鑑,感謝大家。

標簽: CrF  矩陣  score  PyTorch  參考