生活随笔
收集整理的這篇文章主要介紹了
信息抽取(五)实体命名识别之嵌套实体识别哪家强,我做了一个简单的对比实验
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
實體命名識別之嵌套實體識別哪家強 嵌套實體識別 方法比較 GlobalPointer TPLinker Tencent Muti-head Deep Biaffine 實驗結果 總結 參考資料
嵌套實體識別
嵌套實體識別是實體命名識別中一個子問題,何為嵌套實體即“北京天安門”中“北京”是地點實體,“北京天安門”同樣也是地點實體,兩者存在嵌套關系。使用CRF等傳統標注方法無法對嵌套實體進行區分,因此存在一定的局限性。 目前流行構建實體矩陣,即用一個矩陣Wtse(type,len,len)W_{tse}(type,len,len) W t s e ? ( t y p e , l e n , l e n ) 來代表語料中的所有實體及其類型,其中任一元素Nt,e,sN_{t,e,s} N t , e , s ? 表示類行為tt t ,起點為ss s ,結尾為ee e 的實體。通過這樣的標注方式我們可以對任何嵌套實體進行標注,從而解決訓練和解碼的問題。 本文筆者將對比目前接觸到的部分實體矩陣的構建方法在CMeEE數據集(醫學NER,有一定嵌套實體)上的表現。
實體矩陣構建框架
為了方便后續對比說明,這里定義幾個同一的變量與符號,上文中NtesN_{tes} N t e s ? 表示類行為tt t ,起點為ss s ,結尾為ee e 的實體。在本實驗中,我們均適用bert-base-chinese作為encoder,hih_i h i ? 表示最后一層隱藏層中第ii i 個token的embedding,則hsh_s h s ? 和heh_e h e ? 分別表示經過encoder之后實體Start和End token的embedding,則我們有公式Nt,e,s=p(hs,he,t)N_{t,e,s} = p(h_s,h_e,t) N t , e , s ? = p ( h s ? , h e ? , t ) ,其中p(x)p(x) p ( x ) 就表示我們所需要對比的實體矩陣構建頭(姑且這么稱呼)。 在對比實驗中,除了不同實體矩陣構建頭對應的batch_size,learning_rate不同,所使用的encoder、損失函數、評估方式以及訓練輪次均保持一致。 本文選取了GlobalPointer、TPLinker(Muti-head selection)、Tencent Muti-head、Deep Biaffine(雙仿射)共四種實體矩陣構建方法進行比較。
方法比較
GlobalPointer
GlobalPointer出自蘇神的博客GlobalPointer:用統一的方式處理嵌套和非嵌套NER 計算公式:p(hs,he,t)=qs,tTke,tp(h_s,h_e,t) =q_{s,t}^Tk_{e,t} p ( h s ? , h e ? , t ) = q s , t T ? k e , t ? ,其中qs,t=Ws,ths+bs,tq_{s,t} =W_{s,t}h_s+b_{s,t} q s , t ? = W s , t ? h s ? + b s , t ? ,ke,t=We,the+be,tk_{e,t} =W_{e,t}h_e+b_{e,t} k e , t ? = W e , t ? h e ? + b e , t ? 其核心思想為類似attention的打分機制,將多種個實體類型的識別視為Muti-head機制,將每一個head視為一種實體類型識別任務,最后利用attention的score(QK)作為最后的打分。 為了考慮到Start和end之間距離的關鍵信息,作者在此基礎上引入了旋轉式位置編碼(RoPE),在其文中顯示引入位置信息能給結果帶來極大提升,符合預期先驗。
class GlobalPointer ( Module
) : """全局指針模塊將序列的每個(start, end)作為整體來進行判斷""" def __init__ ( self
, heads
, head_size
, hidden_size
, RoPE
= True ) : super ( GlobalPointer
, self
) . __init__
( ) self
. heads
= headsself
. head_size
= head_sizeself
. RoPE
= RoPEself
. dense
= nn
. Linear
( hidden_size
, self
. head_size
* self
. heads
* 2 ) def forward ( self
, inputs
, mask
= None ) : inputs
= self
. dense
( inputs
) inputs
= torch
. split
( inputs
, self
. head_size
* 2 , dim
= - 1 ) inputs
= torch
. stack
( inputs
, dim
= - 2 ) qw
, kw
= inputs
[ . . . , : self
. head_size
] , inputs
[ . . . , self
. head_size
: ] if self
. RoPE
: pos
= SinusoidalPositionEmbedding
( self
. head_size
, 'zero' ) ( inputs
) cos_pos
= pos
[ . . . , None , 1 : : 2 ] . repeat
( 1 , 1 , 1 , 2 ) sin_pos
= pos
[ . . . , None , : : 2 ] . repeat
( 1 , 1 , 1 , 2 ) qw2
= torch
. stack
( [ - qw
[ . . . , 1 : : 2 ] , qw
[ . . . , : : 2 ] ] , 4 ) qw2
= torch
. reshape
( qw2
, qw
. shape
) qw
= qw
* cos_pos
+ qw2
* sin_poskw2
= torch
. stack
( [ - kw
[ . . . , 1 : : 2 ] , kw
[ . . . , : : 2 ] ] , 4 ) kw2
= torch
. reshape
( kw2
, kw
. shape
) kw
= kw
* cos_pos
+ kw2
* sin_poslogits
= torch
. einsum
( 'bmhd , bnhd -> bhmn' , qw
, kw
) logits
= add_mask_tril
( logits
, mask
) return logits
/ self
. head_size
** 0.5
TPLinker
TPLinker出自論文TPLinker: Single-stage Joint Extraction of Entities and Relations Through Token Pair Linking,其本為解決實體關系抽取設計,其原型為Joint entity recognition and relation extraction as a multi-head selection problem論文中的Muti-head selection機制。此處選取其中用于識別實體部分的機制,作為對比對象。 計算公式:p(hs,he,t)=Wt?hs,e+btp(h_s,h_e,t) =W_t·h_{s,e}+b_t p ( h s ? , h e ? , t ) = W t ? ? h s , e ? + b t ? ,其中hs,e=tanh(Wh?[hs;he]+bh)h_{s,e}=tanh(W_h·[h_s;h_e]+b_h) h s , e ? = t a n h ( W h ? ? [ h s ? ; h e ? ] + b h ? ) 與GlobalPointer不同的是,GlobalPointer是乘性的,而Muti-head是加性的。對于這兩種機制的不同,筆者在之前的文章信息抽取(四)中做過簡單的對比。但對于這兩種機制,誰的效果更好,我們無法僅通過理論進行分析,因此需要做相應的對比實驗,從結果進行倒推。但是在實際Implement的過程中,筆者發現加性比乘性占用更多的內存,但是與GlobalPointer中不同的是,加性仍然能實現快速并行,需要在計算設計上加入一些技巧。
class MutiHeadSelection ( Module
) : def __init__ ( self
, hidden_size
, c_size
, abPosition
= False , rePosition
= False , maxlen
= None , max_relative
= None ) : super ( MutiHeadSelection
, self
) . __init__
( ) self
. hidden_size
= hidden_sizeself
. c_size
= c_sizeself
. abPosition
= abPositionself
. rePosition
= rePositionself
. Wh
= nn
. Linear
( hidden_size
* 2 , self
. hidden_size
) self
. Wo
= nn
. Linear
( self
. hidden_size
, self
. c_size
) if self
. rePosition
: self
. relative_positions_encoding
= relative_position_encoding
( max_length
= maxlen
, depth
= 2 * hidden_size
, max_relative_position
= max_relative
) def forward ( self
, inputs
, mask
= None ) : input_length
= inputs
. shape
[ 1 ] batch_size
= inputs
. shape
[ 0 ] if self
. abPosition
: inputs
= SinusoidalPositionEmbedding
( self
. hidden_size
, 'add' ) ( inputs
) x1
= torch
. unsqueeze
( inputs
, 1 ) x2
= torch
. unsqueeze
( inputs
, 2 ) x1
= x1
. repeat
( 1 , input_length
, 1 , 1 ) x2
= x2
. repeat
( 1 , 1 , input_length
, 1 ) concat_x
= torch
. cat
( [ x2
, x1
] , dim
= - 1 ) if self
. rePosition
: relations_keys
= self
. relative_positions_encoding
[ : input_length
, : input_length
, : ] . to
( inputs
. device
) concat_x
+= relations_keyshij
= torch
. tanh
( self
. Wh
( concat_x
) ) logits
= self
. Wo
( hij
) logits
= logits
. permute
( 0 , 3 , 1 , 2 ) logits
= add_mask_tril
( logits
, mask
) return logits
Tencent Muti-head
論文EMPIRICAL ANALYSIS OF UNLABELED ENTITY PROBLEM IN NAMED ENTITY RECOGNITION 提出了一種基于片段標注解決實體數據標注缺失的訓練方法——負采用,并在部分數據集上達到了SOTA。關注其實體矩陣構建模塊,相當于Muti-head的升級版,因此我把它叫做Tencent Muti-head。 計算公式:p(hs,he,t)=U?tanh(Vss,e)p(h_s,h_e,t) =U·tanh(Vs_{s,e}) p ( h s ? , h e ? , t ) = U ? t a n h ( V s s , e ? ) ,其中ss,e=[hs;he;hs?he;hs?he]s_{s,e}=[h_s;h_e;h_s-h_e;h_s ·h_e] s s , e ? = [ h s ? ; h e ? ; h s ? ? h e ? ; h s ? ? h e ? ] 與TPLinker相比,Tencent Muti-head在加性的基礎上加入了更多信息交互元素:hs?he,hs?heh_s-h_e,h_s ·h_e h s ? ? h e ? , h s ? ? h e ? (作差與點乘),但同時也提高了內存的占用量。
class TxMutihead ( Module
) : def __init__ ( self
, hidden_size
, c_size
, abPosition
= False , rePosition
= False , maxlen
= None , max_relative
= None ) : super ( TxMutihead
, self
) . __init__
( ) self
. hidden_size
= hidden_sizeself
. c_size
= c_sizeself
. abPosition
= abPositionself
. rePosition
= rePositionself
. Wh
= nn
. Linear
( hidden_size
* 4 , self
. hidden_size
) self
. Wo
= nn
. Linear
( self
. hidden_size
, self
. c_size
) if self
. rePosition
: self
. relative_positions_encoding
= relative_position_encoding
( max_length
= maxlen
, depth
= 4 * hidden_size
, max_relative_position
= max_relative
) def forward ( self
, inputs
, mask
= None ) : input_length
= inputs
. shape
[ 1 ] batch_size
= inputs
. shape
[ 0 ] if self
. abPosition
: inputs
= SinusoidalPositionEmbedding
( self
. hidden_size
, 'add' ) ( inputs
) x1
= torch
. unsqueeze
( inputs
, 1 ) x2
= torch
. unsqueeze
( inputs
, 2 ) x1
= x1
. repeat
( 1 , input_length
, 1 , 1 ) x2
= x2
. repeat
( 1 , 1 , input_length
, 1 ) concat_x
= torch
. cat
( [ x2
, x1
, x2
- x1
, x2
. mul
( x1
) ] , dim
= - 1 ) if self
. rePosition
: relations_keys
= self
. relative_positions_encoding
[ : input_length
, : input_length
, : ] . to
( inputs
. device
) concat_x
+= relations_keyshij
= torch
. tanh
( self
. Wh
( concat_x
) ) logits
= self
. Wo
( hij
) logits
= logits
. permute
( 0 , 3 , 1 , 2 ) logits
= add_mask_tril
( logits
, mask
) return logits
Deep Biaffine
此處使用的雙仿射結構出自論文Named Entity Recognition as Dependency Parsing,原文用于識別實體依存關系,因此也可以直接用于實體命名識別。 計算公式:p(hs,he,t)=hsTUthe+Wt[hs;he]+btp(h_s,h_e,t) =h_s^TU_th_e+W_t[h_s;h_e]+b_t p ( h s ? , h e ? , t ) = h s T ? U t ? h e ? + W t ? [ h s ? ; h e ? ] + b t ? 簡單來說雙仿射分別對對s為頭e為尾的實體類別后驗概率建模 + 對s或e為尾的實體類別的后驗概率分別建模 + 對實體類別t的先驗概率建模。 不難看出Deep Biaffine是加性與乘性的結合,更詳細的解讀可以參考信息抽取(四) 在筆者復現的關系抽取任務中,雙仿射確實帶來的一定提升,但這種建模思路在實體識別中是否有效還有待驗證。
class Biaffine ( Module
) : def __init__ ( self
, in_size
, out_size
, Position
= False ) : super ( Biaffine
, self
) . __init__
( ) self
. out_size
= out_sizeself
. weight1
= Parameter
( torch
. Tensor
( in_size
, out_size
, in_size
) ) self
. weight2
= Parameter
( torch
. Tensor
( 2 * in_size
+ 1 , out_size
) ) self
. Position
= Positionself
. reset_parameters
( ) def reset_parameters ( self
) : torch
. nn
. init
. kaiming_uniform_
( self
. weight1
, a
= math
. sqrt
( 5 ) ) torch
. nn
. init
. kaiming_uniform_
( self
. weight2
, a
= math
. sqrt
( 5 ) ) def forward ( self
, inputs
, mask
= None ) : input_length
= inputs
. shape
[ 1 ] hidden_size
= inputs
. shape
[ - 1 ] if self
. Position
: inputs
= SinusoidalPositionEmbedding
( hidden_size
, 'add' ) ( inputs
) x1
= torch
. unsqueeze
( inputs
, 1 ) x2
= torch
. unsqueeze
( inputs
, 2 ) x1
= x1
. repeat
( 1 , input_length
, 1 , 1 ) x2
= x2
. repeat
( 1 , 1 , input_length
, 1 ) concat_x
= torch
. cat
( [ x2
, x1
] , dim
= - 1 ) concat_x
= torch
. cat
( [ concat_x
, torch
. ones_like
( concat_x
[ . . . , : 1 ] ) ] , dim
= - 1 ) logits_1
= torch
. einsum
( 'bxi,ioj,byj -> bxyo' , inputs
, self
. weight1
, inputs
) logits_2
= torch
. einsum
( 'bijy,yo -> bijo' , concat_x
, self
. weight2
) logits
= logits_1
+ logits_2logits
= logits
. permute
( 0 , 3 , 1 , 2 ) logits
= add_mask_tril
( logits
, mask
) return logits
代碼開源,各種實體矩陣構建方法都寫成了類,方便大家復現或直接調用 https://github.com/zhengyanzhao1997/NLP-model/tree/main/model/model/Torch_model/ExtractionEntities
實驗結果
GPU: P40 24G (x1) 為了把各方法的內存占用情況考慮在內,本次對比實驗全都在一張P40 24G的GPU上進行,并把Batch_size開到最大,其中僅GlobalPointer可以達到16,而Tencent Muti-head由于其構建了一個超大矩陣(batchsize,maxlen,manlen,4?hiddensize)(batchsize,maxlen,manlen,4*hiddensize) ( b a t c h s i z e , m a x l e n , m a n l e n , 4 ? h i d d e n s i z e ) 占用內存較大,因此batch_size只能達到4,可以看出GlobalPointer的性能優勢。 由于注冊的原因,這里只比較了各方法在訓練過程中在驗證集上的最好表現。
MethodPositionBatch_sizelearning_rateCMeEEtrain_{train} t r a i n ? /F1%CMeEEdev_{dev} d e v ? /F1% GlobalPointer RoPE 16 2e-5 73.23 64.64 TPLinker \ 8 1e-5 80.57 62.69 TPLinker Posab_{ab} a b ? 8 1e-5 83.21 63.10 TPLinker Posre_{re} r e ? 8 1e-5 76.63 64.32(64.99)_{(64.99)} ( 6 4 . 9 9 ) ? Tencent Muti-head \ 4 1e-5 83.50 63.74 Tencent Muti-head Posab_{ab} a b ? 4 1e-5 76.32 64.18 Tencent Muti-head Posre_{re} r e ? 4 1e-5 77.37 64.69 Tencent Muti-head Posre_{re} r e ? 16 4卡_{4卡} 4 卡 ? 2e-5 68.81 64.83 Deep Biaffine \ 8 1e-5 78.27 62.85 Deep Biaffine Posab_{ab} a b ? 8 1e-5 77.52 62.66
總結
GlobalPointer作為乘性方法,在空間內存占用上明顯優于其他方法,并且訓練速度較快,能達到一個具有競爭力的效果。 TPLinker 和 Tencent Muti-head作為加性方法,在優化過程中均表現出 相對位置編碼 > 絕對位置編碼 > 不加入位置編碼 的特征。這意味著在通過構建實體矩陣進行實體命名識別時位置信息具有絕對重要的優勢,且直接引入相對位置信息較優。 在絕對位置編碼和不加入位置編碼的測試中Tencent Muti-head的效果明顯優于TPLinker而兩者均差于GlobalPointer,但在引入相對位置信息后Tencent Muti-head略微超越了GlobalPointer,而TPLinker提點顯著,作為Tencent Muti-head的原型在最高得分上甚至可能有更好的表現。 Biaffine雙仿射表現不佳,意味著這種建模思路不適合用于實體命名識別。 在計算資源有限的情況下GlobalPointer是最優的baseline選擇,如果擁有足夠的計算資源且對訓練、推理時間的要求較為寬松,嘗試使用TPLinker/Tencent Muti-head + 相對位置編碼或許能取得更好的效果。
參考資料
蘇劍林. (May. 01, 2021). 《GlobalPointer:用統一的方式處理嵌套和非嵌套NER 》[Blog post]. Retrieved from https://kexue.fm/archives/8373 TPLinker: Single-stage Joint Extraction of Entities and Relations Through Token Pair Linking EMPIRICAL ANALYSIS OF UNLABELED ENTITY PROBLEM IN NAMED ENTITY RECOGNITION Named Entity Recognition as Dependency Parsing
總結
以上是生活随笔 為你收集整理的信息抽取(五)实体命名识别之嵌套实体识别哪家强,我做了一个简单的对比实验 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。