命名實體識別(二)

原文鏈接:https://blog.csdn.net/xxzhix/article/details/81514040

命名實體識別問題可以看做是一個序列標註問題,傳統的機器學習算法有三種方法做序列標註,分別是隱馬爾科夫(HMM)模型,最大熵模型和條件隨機場(CRF)模型。
https://blog.csdn.net/Losteng/article/details/51037927
HMM模型將標註看做馬爾科夫鏈,一階馬爾科夫鏈針對相鄰標註的關係進行建模,其中每個標記對應一個概率函數。HM是一種生成模型,定義了聯合概率分佈,其中x和y分別表示觀察序列和相對應的標註序列的隨機變量。爲了能夠定義這種聯合概率分佈,生成模型需要枚舉出所有可能的觀察序列,這在實際運算過程中很困難,因爲我們需要將觀察序列的元素看做是彼此孤立的個體即假設每個元素彼此獨立,任何時刻的觀察結果只依賴於改時刻的狀態。
HMM模型的這個假設前提在較小的數據集上是合適的,但實際上在大量真實語料中觀察序列更多的是以一種多重的交互特徵形式表現,觀察元素之間廣泛存在長程相關性。在命名實體識別任務中,由於實體本身結構所具有的複雜性,利用簡單的特徵函數往往無法覆蓋所有的特性,這時HMM的假設前提使得它無法使用複雜特徵(無法使用多於一個標記的特徵)。
最大熵模型可以使用任意的複雜相關特徵,在性能上最大熵分類器超過了Byaes分類器。但是,作爲一種分類器模型,這兩種方法有一個共同的缺點:每個詞都是單獨進行分類的,標記之間的關係無法得到充分利用,具有馬爾科夫鏈的HMM模型可以建立標記之間的馬爾科夫關聯性,這是最大熵模型所沒有的。
最大熵模型的優點:首先,最大上統計模型獲得的是所有滿足約束條件的模型中信息熵極大的模型;其次,最大上統計模型可以靈活的設置約束條件,通過約束條件的多少可以調節模型對於未知數據的適應度和對已知數據的擬合程度;再次,它還能自然地解決統計模型中參數平滑的問題。
最大熵的不足:首先,最大熵統計模型中二值化特徵只是記錄特徵是否出現,而文本分類需要知道特徵的強度,因此,它在分類方法中不是最優的;其次,由於算法收斂速度較慢,所以導致最大熵統計模型的計算代價較大,時空開銷大;再次,數據稀疏問題比較嚴重。
最大熵馬爾科夫模型:把HMM模型和maximum-entropy模型的優點集合成一個生成模型,這個模型允許狀態轉移概率依賴於序列中彼此之間非獨立的特徵,從而將上下文信息引入到模型的學習和識別過程中,提高了識別的精確度,召回率也大大的提高。
CRF模型:首先,CRF在給定了觀察序列的情況下,對整個的序列的聯合概率有一個統一的指數模型,而且其損失函數是凸的。其次,CRF相比較改進的HMM模型可以更好更多的利用待識別文本中所提供的上下文信息以得到更好的實驗結果。CRF中中文組塊識別方面有效,並避免了嚴格獨立性假設和數學歸納偏置問題。CRF模型應用到中文命名實體識別中,並根據中文的特點,定義了多種特徵模板。並且有測試結果表明,在採用相同特徵集合的條件下,CRF模型較其他概率模型有更好的性能表現。再次,詞性標註主要面臨類詞消歧以及未知詞標註的難題,傳統HMM方法不易融合新特徵,而最大熵馬爾科夫模型存在標註偏置問題。相比之下,CRF模型易於融合新的特徵,並能解決標註偏置問題。
CRF具有很強的推理能力,並且能夠使用複雜、有重疊性和非獨立的特徵進行訓練和推理,能夠充分地利用上下文信息作爲特徵,還可以任意地添加其他外部特徵,使得模型能夠獲得地信息非常豐富。同時,CRF解決了最大熵模型中地‘label bias’問題。CRF與最大熵模型本質區別是:最大熵模型在每個狀態都有一個概率模型,在每個狀態轉移時都要進行歸一化。如果某個狀態只有一個後續狀態,那麼該狀態到後續狀態地跳轉概率爲1.這樣,不管輸入爲任何內容,它都向後續狀態跳轉。而CRF是在所有地狀態上建立一個統一的概率模型,這樣在進行歸一化時,即使某個狀態只有一個後續狀態,它到該後續狀態地跳轉概率也不爲1,從而解決了’label bias’問題。因此,理論上講,CRF非常適用於中文地詞性標註。
CRF的優點:首先,CRF模型由於自身在結合多種特徵方面的優勢和避免了標記偏置問題;其次,CRF的性能更好,CRF的特徵融合能力比較強,對於實例較小的時間類ME來說,CRF的識別效果明顯高於ME的識別結果。
CRF的缺點:
首先,通過對基於CRF的結合多種特徵的方法識別英語命名實體的分析,發現在使用CRF方法中,特徵的選擇和優化是影響結果的關鍵因素,特徵選擇問題的好與壞,直接決定了系統性能的高低。其次,訓練模型的時間比ME更長,且獲得的模型很大,在一般的PC機上無法運行。

深度學習模型效果比較好的是雙向LSTM+CRF。
(1)爲什麼使用Bi_LSTM? 爲了使特徵提取自動化。當使用CRF++工具來進行命名實體識別時,需要自定義模板(或者使用默認的模板)。
(2)爲什麼我們不直接把所有的地點,常見姓名和組織名保存成一個列表呢?是因爲有很多實體,例如姓名和組織名是人爲構造的,而怎麼構造,沒有先驗知識。所以,需要能夠從句子中提取出上下文信息的工具。
(3)模型介紹
模型分爲3部分:
1.詞表示。使用緊密(dense)向量表示每個詞,加載預訓練好的詞向量(glove,word2vec,senna等)。也將從單個字(單個字母)中提取一些含義。爲什麼也需要單個字呢?因爲很多實體沒有預訓練好的向量,而且開頭是大寫字母會對識別出實體有用。
2.上下文單詞表示。對上下文中的每個詞,需要有一個有意義的向量表示,使用LSTM來獲取上下文中單詞的向量表示。
3.解碼。當我們有每個詞的向量表示後,來進行實體標籤的預測。
詞向量表示
對於每個詞,需要構建一個向量來獲取這個詞的意思以及對實體識別有用的一些特徵,這個向量由Glove訓練的詞向量和從字母提取出的特徵的向量堆疊而成。
一種選擇是使用手動提取的特徵,例如單詞是否是大寫字母開頭等,另一種更好的選擇是使用某種神經網絡來自動提取特徵。在這裏,對單詞字母使用Bi_LSTM,當然也可以使用其他循環神經網絡,或者對單個字母或n-gram使用cnn.
組成一個單詞的每個字母都由一個向量表示(注意區分大小寫),對每個字母使用Bi-LSTM,並將最後狀態堆疊起來獲得一個固定長度的向量。直覺上,這個向量獲取了這個詞的形態,然後將詞向量和字母向量合併,獲得這個詞最終的向量表示。
tensorflow處理批量的詞和數據,因此需要將句子填充到相同的長度,定義2個placeholder:

# shape = (batch size, max length of sentence in batch)
word_ids = tf.placeholder(tf.int32, shape=[None, None])
 
# shape = (batch size)
sequence_lengths = tf.placeholder(tf.int32, shape=[None])

使用tensorflow內置的函數來加載詞向量。假設embeddings是保存這Glove向量的數組,那麼embeddings[i]就是第i個單詞的詞向量。


L = tf.Variable(embeddings, dtype=tf.float32, trainable=False)
# shape = (batch, sentence, word_vector_size)
pretrained_embeddings = tf.nn.embedding_lookup(L, word_ids)

接下來構建單個字母的標書,對詞也需要填充到相同的長度,定義2個placeholder:

# shape = (batch size, max length of sentence, max length of word)
char_ids = tf.placeholder(tf.int32, shape=[None, None, None])
 
# shape = (batch_size, max_length of sentence)
word_lengths = tf.placeholder(tf.int32, shape=[None, None])

爲什麼到處都使用None?爲什麼我們需要使用None?
這取決於我們如何填充,在這裏,選擇動態填充,例如在一個batch中,將batch中的句子填充到這個batch的最大長度。因此,句子長度和詞的長度取決於這個batch。
接下來構造字母向量(character embeddings)。我們沒有預訓練的字母向量,使用tf.get_variable來初始化一個矩陣。然後改變這個4維tensor形狀來滿足bidirectional_dynamic_rnn的輸入要求。
sequence_length這個參數使我們確保我們獲得的最後狀態是有效的最後狀態(因爲batch中句子的實際長度不一樣)。

# 1. get character embeddings
K = tf.get_variable(name="char_embeddings", dtype=tf.float32,
    shape=[nchars, dim_char])
# shape = (batch, sentence, word, dim of char embeddings)
char_embeddings = tf.nn.embedding_lookup(K, char_ids)

# 2. put the time dimension on axis=1 for dynamic_rnn
s = tf.shape(char_embeddings) # store old shape
# shape = (batch x sentence, word, dim of char embeddings)
char_embeddings = tf.reshape(char_embeddings, shape=[-1, s[-2], s[-1]])
word_lengths = tf.reshape(self.word_lengths, shape=[-1])

#3 bi lstm on chars
cell_fw = tf.contrib.rnn.LSTMCell(char_hidden_size,state_is_tuple=True)
cell_bw = tf.contrib.rnn.LSTMCell(char_hidden_size,state_is_tuple=True)
_,((_,outout_fw),(_,output_bw)) = tf.nn.bdirectional_dynamic_rnn(cell_fw,cell_bw,char_embeddings,sequence_length=word_lengths,dtype=tf.float32)
#shape = (batch * sentence, 2*char_hidden_size)
output = tf.concat([output_fw,output_bw],axis=1)

#shape = (batch,sentence,2*char_hidden_size)
char_rep = tf.reshape(output,shape = [-1, s[1], 2*char_hidden_size])

#shape = (batch,sentence,2*char_hidden_size+word_vector_size))
word_embeddings = tf.concat([pretrained_embeddings, char_rep], axis=1)

上下文單詞表示
當我們得到詞最終的向量表示後,對詞向量的序列進行LSTM或Bi_LSTM.
使用每個時間的隱藏狀態,而不僅僅是最終狀態。輸入m個詞向量,獲得m個隱藏狀態的向量,然而詞向量只是包含詞級別的信息,而隱藏狀態的向量考慮了上下文。


cell_fw = tf.contrib.rnn.LSTMCell(hidden_size)
cell_bw = tf.contrib.rnn.LSTMCell(hidden_size)
 
(output_fw, output_bw), _ = tf.nn.bidirectional_dynamic_rnn(cell_fw,
    cell_bw, word_embeddings, sequence_length=sequence_lengths,
    dtype=tf.float32)
 
context_rep = tf.concat([output_fw, output_bw], axis=-1)

解碼
在解碼階段計算標籤得分,使用每個詞對應的隱藏狀態向量來做最後預測,可以使用一個全連接神經網絡來獲取每個實體標籤的得分。
其中s[i]可以理解爲詞w對應標籤i的得分。

W = tf.get_variable("W", shape=[2*self.config.hidden_size, self.config.ntags],
                dtype=tf.float32)
 
b = tf.get_variable("b", shape=[self.config.ntags], dtype=tf.float32,
                initializer=tf.zeros_initializer())
 
ntime_steps = tf.shape(context_rep)[1]
context_rep_flat = tf.reshape(context_rep, [-1, 2*hidden_size])
pred = tf.matmul(context_rep_flat, W) + b
scores = tf.reshape(pred, [-1, ntime_steps, ntags])

對標籤得分進行解碼,有兩個選擇。不管哪個選擇,都會計算標籤序列的概率並找到概率最大的序列。
1.softmax:將得分轉化爲代表這個單詞屬於某個類別標籤)的概率,概率和爲1。最後,標籤序列的概率是每個位置標籤概率的乘積。
2.線性CRF:softmax方法是做局部選擇,換句話,即使Bi_LSTM產生的h中包含了一些上下文信息,但是標籤決策仍然是局部的,沒有利用周圍的標籤來幫助決策。
線性CRF定義了全局得分C:
全局得分C
其中,T是ntags * ntags的轉換矩陣,e,b是ntags維的向量,表示某個標籤作爲開頭和結尾的成本。T包含了標籤決策內的線性依賴關係,下一個標籤依賴上一個標籤。舉例如下:
舉例
如果單看每個位置的得分,標籤序列PER-PER-LOC的得分(10+4+11)比PER-O-LOC的得分(10+3+11)高,但如果考慮標籤之間的依賴關係,PER-O-LOC的得分(31)高於PER-PER-LOC的得分(26),而Pierre loves Paris的標籤序列是PER-O-LOC。
要實現CRF計算得分,需要做2件事:
1.找到得分最高的標籤序列
2.計算所有標籤序列的概率分佈?
要找到得分最高的標籤序列,不可能計算所有的ntagsmntags^m個標籤得分,並甚至將每個標籤得分標準化爲概率,其中m是句子的長度。使用動態方法找到得分最高的序列,假設有從t+1,…,m的序列得分,那麼從t,…,m的序列得分是:

每個循環步驟的複雜度是O(ntags*ntags),共m步,則總的複雜度是O(ntags*ntags*m),對於一個10個詞的句子來說,複雜度從ntags10ntags^10,下降到ntags*ntags*10
線性鏈CRF的最後一步是對所有可能序列的得分執行softmax,從而得到給定序列的概率。
所有可能序列得分的和。
Zt(yt)Z_t(y_t)是在時間t,標籤爲yty_t的所有序列得分的和。(爲什麼定義這個值?爲了對Z進行遞歸計算,減少計算量)
在這裏插入圖片描述
則給定標籤序列的概率是
使用交叉熵損失函數作爲目標函數進行訓練,交叉熵損失定以爲:

以下代碼計算損失並返回轉移矩陣T,計算CRF的對數概率只需要一行代碼:

# shape = (batch, sentence)
labels = tf.placeholder(tf.int32, shape=[None, None], name="labels")
 
log_likelihood, transition_params = tf.contrib.crf.crf_log_likelihood(
scores, labels, sequence_lengths)
 
loss = tf.reduce_mean(-log_likelihood)

直接使用softmax後計算損失時,要注意padding,使用tf.sequence_mask來將序列長度轉換成是否向量。

losses = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=scores, labels=labels)
# shape = (batch, sentence, nclasses)
mask = tf.sequence_mask(sequence_lengths)
# apply mask
losses = tf.boolean_mask(losses, mask)
 
loss = tf.reduce_mean(losses)

之後定義訓練操作:

optimizer = tf.train.AdamOptimizer(self.lr)
train_op = optimizer.minimize(self.loss)

當訓練好模型之後,如何使用模型進行預測?
當直接使用softmax時,最好的序列就是在每個時間點選擇最高得分的標籤,用以下代碼實現:

labels_pred = tf.cast(tf.argmax(self.logits, axis=-1), tf.int32)

當使用CRF時,需要動態編程,但也只需要一行代碼:

# shape = (sentence, nclasses)
score = ...
viterbi_sequence, viterbi_score = tf.contrib.crf.viterbi_decode(
                                score, transition_params)

補充一個問題:
爲什麼LSTM之後可以接CRF?
當沒有使用深度學習提取特徵之前,輸入CRF的是自定義的特徵,滿足特徵,則得分爲1,不滿足,則得分爲0.CRF可以看作當前位置的特徵得分和標籤轉移的特徵得分的得分和,然後選整個序列得分高的。
直接把LSTM輸出的拼接起來的詞向量和句向量拼接起來應該如何理解?
從LSTM輸出的向量相當於定義在節點上的特徵函數,也就是上文中的,就是看當前詞對標籤結果的影響。但LSTM每個位置輸出的向量的維度和標籤個數一般是不一樣的,標籤個數遠遠小於輸出向量的維度。
在CRF解碼時,tf.contrib.crf.veterbi_decode()是需要另外傳入transition_params參數的,就是底層的神經網絡沒有學習到轉移的特徵函數。
https://blog.csdn.net/xxzhix/article/details/81514040

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章