說明: 本篇文章為Pytorch官網上BiLSTM CRF的說明

一。 BILSTM + CRF介紹

https://www。

jianshu。com/p/97cb3b6db

573

1。介紹

基於神經網路的方法,在命名實體識別任務中非常流行和普遍。 如果你不知道Bi-LSTM和CRF是什麼,你只需要記住他們分別是命名實體識別模型中的兩個層。

1。1開始之前

我們假設我們的資料集中有兩類實體——人名和地名,與之相對應在我們的訓練資料集中,有五類標籤:

B-Person, I- Person,B-Organization,I-Organization

假設句子x由五個字元w1,w2,w3,w4,w5組成,其中【w1,w2】為人名類實體,【w3】為地名類實體,其他字元標籤為“O”。

1。2BiLSTM-CRF模型

以下將給出模型的結構:

第一,句子x中的每一個單元都代表著由字嵌入或詞嵌入構成的向量。其中,字嵌入是隨機初始化的,詞嵌入是透過資料訓練得到的。所有的嵌入在訓練過程中都會調整到最優。

第二,這些字或詞嵌入為BiLSTM-CRF模型的輸入,輸出的是句子x中每個單元的標籤。

Pytorch BiLSTM + CRF做NER

儘管一般不需要詳細瞭解BiLSTM層的原理,但是為了更容易知道CRF層的執行原理,我們需要知道BiLSTM的輸出層。

Pytorch BiLSTM + CRF做NER

如上圖所示,BiLSTM層的輸出為每一個標籤的預測分值,例如,對於單元w0,BiLSTM層輸出的是

1。5 (B-Person), 0。9 (I-Person), 0。1 (B-Organization), 0。08 (I-Organization) 0。05 (O)。

這些分值將作為CRF的輸入。 ### 1。3 如果沒有CRF層會怎樣

你也許已經發現了,即使沒有CRF層,我們也可以訓練一個BiLSTM命名實體識別模型,如圖所示:

Pytorch BiLSTM + CRF做NER

由於BiLSTM的輸出為單元的每一個標籤分值,我們可以挑選分值最高的一個作為該單元的標籤。例如,對於單元w0,“B-Person”有最高分值—— 1。5,因此我們可以挑選“B-Person”作為w0的預測標籤。同理,我們可以得到w1——“I-Person”,w2—— “O” ,w3——“B-Organization”,w4——“O”。

雖然我們可以得到句子x中每個單元的正確標籤,但是我們不能保證標籤每次都是預測正確的。例如,圖4。中的例子,標籤序列是“I-Organization I-Person” and “B-Organization I-Person”,很顯然這是錯誤的。

Pytorch BiLSTM + CRF做NER

1。4 CRF層能從訓練資料中獲得約束性的規則

CRF層可以為最後預測的標籤新增一些約束來保證預測的標籤是合法的。在訓練資料訓練過程中,這些約束可以透過CRF層自動學習到。 這些約束可以是: I:句子中第一個詞總是以標籤“B-“ 或 “O”開始,而不是“I-” II:標籤“B-label1 I-label2 I-label3 I-…”,label1, label2, label3應該屬於同一類實體。例如,“B-Person I-Person” 是合法的序列, 但是“B-Person I-Organization” 是非法標籤序列。 III:標籤序列“O I-label” is 非法的。實體標籤的首個標籤應該是 “B-“ ,而非 “I-“, 換句話說,有效的標籤序列應該是“O B-label”。 有了這些約束,標籤序列預測中非法序列出現的機率將會大大降低。

二。 標籤的score和損失函式的定義

https://

zhuanlan。zhihu。com/p/27

338210

Bi-LSTM layer的輸出維度是tag size,這就相當於是每個詞 w~i~ 對映到tag的發射機率值,設Bi-LSTM的輸出矩陣為P,其中P~i,j~代表詞w~i~對映到tag~j~的非歸一化機率。對於CRF來說,我們假定存在一個轉移矩陣A,則A~i,j~代表tag~i~轉移到tag~j~的轉移機率。 對於輸入序列

X

對應的輸出tag序列 y,定義分數為

Pytorch BiLSTM + CRF做NER

利用Softmax函式,我們為每一個正確的tag序列y定義一個機率值(Y~X~代表所有的tag序列,包括不可能出現的)

Pytorch BiLSTM + CRF做NER

因而在訓練中,我們只需要最大化似然機率p(y|X)即可,這裡我們利用對數似然

Pytorch BiLSTM + CRF做NER

所以我們將損失函式定義為-log(p(y|X)),就可以利用梯度下降法來進行網路的學習了。

loss function:

Pytorch BiLSTM + CRF做NER

在對損失函式進行計算的時候,S(X,y)的計算很簡單,而

Pytorch BiLSTM + CRF做NER

(下面記作logsumexp)的計算稍微複雜一些,因為需要計算每一條可能路徑的分數。這裡用一種簡便的方法,對於到詞w~i+1~的路徑,可以先把到詞w~i~的logsumexp計算出來,因為

Pytorch BiLSTM + CRF做NER

因此先計算每一步的路徑分數和直接計算全域性分數相同,但這樣可以大大減少計算的時間。

三。 對於損失函式的詳細解釋

這篇文章對於理解十分有用

https://

blog。csdn。net/cuihuijun

1hao/article/details/79405740

舉例說 【我 愛 中國人民】對應標籤【N V N】那這個標籤就是一個完整的路徑,也就對應一個Score值。 接下來我想講的是這個公式:

Pytorch BiLSTM + CRF做NER

這個公式成立是很顯然的,動筆算一算就知道了,程式碼裡其實就是用了這個公式的原理。

def

_forward_alg

self

feats

):

# Do the forward algorithm to compute the partition function

init_alphas

=

torch

full

((

1

self

tagset_size

),

-

10000。

# START_TAG has all of the score。

init_alphas

0

][

self

tag_to_ix

START_TAG

]]

=

0。

# Wrap in a variable so that we will get automatic backprop

forward_var

=

init_alphas

# Iterate through the sentence

for

feat

in

feats

alphas_t

=

[]

# The forward tensors at this timestep

for

next_tag

in

range

self

tagset_size

):

# broadcast the emission score: it is the same regardless of

# the previous tag

emit_score

=

feat

next_tag

view

1

-

1

expand

1

self

tagset_size

# the ith entry of trans_score is the score of transitioning to

# next_tag from i

trans_score

=

self

transitions

next_tag

view

1

-

1

# The ith entry of next_tag_var is the value for the

# edge (i -> next_tag) before we do log-sum-exp

next_tag_var

=

forward_var

+

trans_score

+

emit_score

# The forward variable for this tag is log-sum-exp of all the

# scores。

alphas_t

append

log_sum_exp

next_tag_var

view

1

))

forward_var

=

torch

cat

alphas_t

view

1

-

1

terminal_var

=

forward_var

+

self

transitions

self

tag_to_ix

STOP_TAG

]]

alpha

=

log_sum_exp

terminal_var

return

alpha

我們看到有這麼一段程式碼 :

next_tag_var

=

forward_var

+

trans_score

+

emit_score

我們主要就是來講講他。 首先這個演算法的思想是:假設我們要做一個詞性標註的任務,對句子【我 愛 中華人民】,我們要對這個句子做

Pytorch BiLSTM + CRF做NER

意思就是 對這個句子所有可能的標註,都算出來他們的Score,然後按照指數次冪加起來,再取對數。一般來說取所有可能的標註情況比較複雜,我們這裡舉例是長度為三,但是實際過程中,可能比這個要大得多,所以我們需要有一個簡單高效得演算法。也就是我們程式中得用得演算法, 他是這麼算得: 先算出【我, 愛】可能標註得所有情況,取 log_sum_exp 然後加上 轉換到【中國人民】得特徵值 再加上【中國人民】對應得某個標籤得特徵值。其等價於【我,愛,中國人民】所有可能特徵值指數次冪相加,然後取對數。 接下來我們來驗證一下是不是這樣

首先我們假設詞性一共只有兩種 名詞N 和 動詞 V 那麼【我,愛】得詞性組合一共有四種 N + N,N + V, V + N, V + V 那麼【愛】標註為N時得log_sum_exp 為

Pytorch BiLSTM + CRF做NER

【愛】 標註為 V時的 log_sum_exp為

Pytorch BiLSTM + CRF做NER

我們的forward列表裡就是存在著這兩個值,即:

Pytorch BiLSTM + CRF做NER

假設【中華人民】得詞性為N,我們按照程式碼來寫一下公式,在forward列表對應位置相加就是這樣

Pytorch BiLSTM + CRF做NER

Pytorch BiLSTM + CRF做NER

四。 程式碼塊詳細說明:

https://

blog。csdn。net/Jason__Li

ang/article/details/81772632

先說明兩個重要的矩陣:

feats: 發射矩陣(emit score)是sentence 在embedding後,再經過LSTM後得到的矩陣(也就是LSTM的輸出), 維度為11 * 5 (11為sentence 的length,5是標籤數)。這個矩陣表示經過LSTM後sentence的每個word對應的每個labels的得分)。 表示發射機率。

self。transitions:轉移矩陣,維度為5

5,transitions[i][j]表示label j轉移到label i的機率。transtion[i]維度為1

5,表示每個label轉移到label i的機率。 表示機率矩陣

1。 def log_sum_exp(vec)

# compute log sum exp in numerically stable way for the forward algorithm

def

log_sum_exp

vec

):

#vec是1*5, type是Variable

max_score

=

vec

0

argmax

vec

)]

# max_score維度是1,max_score。view(1,-1)維度是1*1,

# max_score。view(1, -1)。expand(1, vec。size()[1])的維度是1*5

max_score_broadcast

=

max_score

view

1

-

1

expand

1

vec

size

()[

1

])

# 裡面先做減法,減去最大值可以避免e的指數次,計算機上溢

return

max_score

+

\

torch

log

torch

sum

torch

exp

vec

-

max_score_broadcast

)))

你可能會疑問return 的結果為什麼先減去max_score。其實這是一個小技巧,因為一上來就做指數運算可能會引起計算結果溢位,先減去score,經過log_sum_exp後,再把max_score給加上。 其實就等同於:

return

torch

log

torch

sum

torch

exp

vec

)))

2。 def neg_log_likelihood(self, sentence, tags)

如果你完整地把程式碼讀完,你會發現neg_log_likelihood()這個函式是loss function。

loss

=

model

neg_log_likelihood

sentence_in

targets

我們來分析一下neg_log_likelihood()函式程式碼:

def

neg_log_likelihood

self

sentence

tags

):

# feats: 11*5 經過了LSTM+Linear矩陣後的輸出,之後作為CRF的輸入。

feats

=

self

_get_lstm_features

sentence

forward_score

=

self

_forward_alg

feats

gold_score

=

self

_score_sentence

feats

tags

return

forward_score

-

gold_score

你在這裡可能會有疑問:問什麼forward_score - gold_score可以作為loss呢。 這裡,我們回顧一下我們在上文中說明的loss function函式公式:

Pytorch BiLSTM + CRF做NER

你就會發現forward_score和gold_score分別表示上述等式右邊的兩項。

3。 def _forward_alg(self, feats):

我們透過上一個函式的分析得知這個函式就是用來求forward_score的,也就是loss function等式右邊的第一項:

Pytorch BiLSTM + CRF做NER

# 預測序列的得分

# 只是根據隨機的transitions,前向傳播算出的一個score

#用到了動態規劃的思想,但因為用的是隨機的轉移矩陣,算出的值很大score>20

def

_forward_alg

self

feats

):

# do the forward algorithm to compute the partition function

init_alphas

=

torch

full

((

1

self

tagset_size

),

-

10000。

# 1*5 而且全是-10000

# START_TAG has all of the score

# 因為start tag是4,所以tensor([[-10000。, -10000。, -10000。, 0。, -10000。]]),

# 將start的值為零,表示開始進行網路的傳播,

init_alphas

0

][

self

tag_to_ix

START_TAG

]]

=

0

# warp in a variable so that we will get automatic backprop

forward_var

=

init_alphas

# 初始狀態的forward_var,隨著step t變化

# iterate through the sentence

# 會迭代feats的行數次

for

feat

in

feats

#feat的維度是5 依次把每一行取出來~

alphas_t

=

[]

# the forward tensors at this timestep

for

next_tag

in

range

self

tagset_size

):

#next tag 就是簡單 i,從0到len

# broadcast the emission(發射) score:

# it is the same regardless of the previous tag

# 維度是1*5 LSTM生成的矩陣是emit score

emit_score

=

feat

next_tag

view

1

-

1

expand

1

self

tagset_size

# the i_th entry of trans_score is the score of transitioning

# to next_tag from i

trans_score

=

self

transitions

next_tag

view

1

-

1

# 維度是1*5

# The ith entry of next_tag_var is the value for the

# edge (i -> next_tag) before we do log-sum-exp

# 第一次迭代時理解:

# trans_score所有其他標籤到B標籤的機率

# 由lstm執行進入隱層再到輸出層得到標籤B的機率,emit_score維度是1*5,5個值是相同的

next_tag_var

=

forward_var

+

trans_score

+

emit_score

# The forward variable for this tag is log-sum-exp of all the scores

alphas_t

append

log_sum_exp

next_tag_var

view

1

))

# 此時的alphas t 是一個長度為5,例如

# [tensor(0。8259), tensor(2。1739), tensor(1。3526), tensor(-9999。7168), tensor(-0。7102)]

forward_var

=

torch

cat

alphas_t

view

1

-

1

#到第(t-1)step時5個標籤的各自分數

# 最後只將最後一個單詞的forward var與轉移 stop tag的機率相加

# tensor([[ 21。1036, 18。8673, 20。7906, -9982。2734, -9980。3135]])

terminal_var

=

forward_var

+

self

transitions

self

tag_to_ix

STOP_TAG

]]

alpha

=

log_sum_exp

terminal_var

# alpha是一個0維的tensor

return

alpha

4。 def _score_sentence(self, feats, tags)

由2的函式分析,我們知道這個函式就是求gold_score,即loss function的第二項

# 根據真實的標籤算出的一個score,

# 這與上面的def _forward_alg(self, feats)共同之處在於:

# 兩者都是用的隨機轉移矩陣算的score

# 不同地方在於,上面那個函式算了一個最大可能路徑,但實際上可能不是真實的各個標籤轉移的值

# 例如:真實標籤是N V V,但是因為transitions是隨機的,所以上面的函式得到其實是N N N這樣,

# 兩者之間的score就有了差距。而後來的反向傳播,就能夠更新transitions,使得轉移矩陣逼近

#真實的“轉移矩陣”

# 得到gold_seq tag的score 即根據真實的label 來計算一個score,

# 但是因為轉移矩陣是隨機生成的,故算出來的score不是最理想的值

def

_score_sentence

self

feats

tags

):

#feats 11*5 tag 11 維

# gives the score of a provied tag sequence

score

=

torch

zeros

1

# 將START_TAG的標籤3拼接到tag序列最前面,這樣tag就是12個了

tags

=

torch

cat

([

torch

tensor

([

self

tag_to_ix

START_TAG

]],

dtype

=

torch

long

),

tags

])

for

i

feat

in

enumerate

feats

):

# self。transitions[tags[i + 1], tags[i]] 實際得到的是從標籤i到標籤i+1的轉移機率

# feat[tags[i+1]], feat是step i 的輸出結果,有5個值,

# 對應B, I, E, START_TAG, END_TAG, 取對應標籤的值

# transition【j,i】 就是從i ->j 的轉移機率值

score

=

score

+

\

self

transitions

tags

i

+

1

],

tags

i

]]

+

feat

tags

i

+

1

]]

score

=

score

+

self

transitions

self

tag_to_ix

STOP_TAG

],

tags

-

1

]]

return

score

5。 def _viterbi_decode(self, feats):

# 維特比解碼, 實際上就是在預測的時候使用了, 輸出得分與路徑值

# 預測序列的得分

def

_viterbi_decode

self

feats

):

backpointers

=

[]

# initialize the viterbi variables in long space

init_vvars

=

torch

full

((

1

self

tagset_size

),

-

10000。

init_vvars

0

][

self

tag_to_ix

START_TAG

]]

=

0

# forward_var at step i holds the viterbi variables for step i-1

forward_var

=

init_vvars

for

feat

in

feats

bptrs_t

=

[]

# holds the backpointers for this step

viterbivars_t

=

[]

# holds the viterbi variables for this step

for

next_tag

in

range

self

tagset_size

):

# next-tag_var[i] holds the viterbi variable for tag i

# at the previous step, plus the score of transitioning

# from tag i to next_tag。

# we don‘t include the emission scores here because the max

# does not depend on them(we add them in below)

# 其他標籤(B,I,E,Start,End)到標籤next_tag的機率

next_tag_var

=

forward_var

+

self

transitions

next_tag

best_tag_id

=

argmax

next_tag_var

bptrs_t

append

best_tag_id

viterbivars_t

append

next_tag_var

0

][

best_tag_id

view

1

))

# now add in the emssion scores, and assign forward_var to the set

# of viterbi variables we just computed

# 從step0到step(i-1)時5個序列中每個序列的最大score

forward_var

=

torch

cat

viterbivars_t

+

feat

view

1

-

1

backpointers

append

bptrs_t

# bptrs_t有5個元素

# transition to STOP_TAG

# 其他標籤到STOP_TAG的轉移機率

terminal_var

=

forward_var

+

self

transitions

self

tag_to_ix

STOP_TAG

]]

best_tag_id

=

argmax

terminal_var

path_score

=

terminal_var

0

][

best_tag_id

# follow the back pointers to decode the best path

best_path

=

best_tag_id

for

bptrs_t

in

reversed

backpointers

):

best_tag_id

=

bptrs_t

best_tag_id

best_path

append

best_tag_id

# pop off the start tag

# we don’t want to return that ti the caller

start

=

best_path

pop

()

assert

start

==

self

tag_to_ix

START_TAG

# Sanity check

best_path

reverse

()

# 把從後向前的路徑正過來

return

path_score

best_path

如果對於該函式還沒有太理解,可以參考這篇部落格:

https://

blog。csdn。net/appleml/a

rticle/details/78579675

總結

以上就是我結合了幾篇比較不錯的部落格後的總結,歡迎大家提問。