中文NER任务实验小结:BERT-MRC的再优化
?作者 | 邱震宇
單位 | 華泰證券算法工程師
研究方向 | NLP方向
前言
熟悉我的讀者,應(yīng)該看過我之前寫過的一篇關(guān)于中文 NER 任務(wù)實踐的文章(邱震宇:中文 NER 任務(wù)實驗小結(jié)報告——深入模型實現(xiàn)細(xì)節(jié) [1],在那篇文章中,我介紹了一個做 NER 任務(wù)的新范式:將實體抽取任務(wù)轉(zhuǎn)化為抽取式閱讀理解任務(wù)。
其核心思想就是將待抽取的實體標(biāo)簽描述作為 query 與原始的文本進(jìn)行拼接,然后基于 BERT 做對應(yīng) span 的抽取。通過這種方式能夠讓模型學(xué)習(xí)到實體標(biāo)簽本身的語義信息,引入先驗知識,從而提升模型的能力。在我之后的實現(xiàn)中,也進(jìn)一步驗證了這個方法的有效性。但我嘗試將這個方法應(yīng)用到實際場景任務(wù)時,卻遇到了很多限制。
其中,影響最大的就是在線預(yù)測的效率。在實際場景中,我們的實體或者其他序列標(biāo)注要素的標(biāo)簽類型通常會有很多(假定有 |C| 個),這意味著我們要將每個待預(yù)測的文本與 |C| 個標(biāo)簽 query 模板進(jìn)行拼接得到模型輸入,并需要調(diào)用 |C| 次模型的前向計算才能完成一個樣本的抽取。假設(shè) query 的最長文本長度為 n,原始文本的文本長度為 m,由于進(jìn)行了 self-attention 的計算,整個計算的復(fù)雜度為 ,這樣的在線預(yù)測效率是無法滿足線上的需求的。
針對這個問題,我嘗試了一些優(yōu)化方法,但是都不太理想,最近終于在一篇? EMNLP2021 的論文中找到了比較好的方案,論文如下:Enhanced Language Representation with Label Knowledge forSpan Extraction [2]。這篇論文提出了一個?LEAR(Label knowledge EnhAnced Representation)模型架構(gòu),嘗試優(yōu)化 BERT-MRC 的一些缺陷,下面我將詳細(xì)給大家描述這個方法,并對其進(jìn)行有效性的驗證。
本文涉及源碼:
https://github.com/qiufengyuyi/lear_ner_extraction
LEAR原理
2.1 BERT-MRC的缺陷
首先,論文提到了之前使用 BERT-MRC 的方式做序列標(biāo)注問題,雖然相比傳統(tǒng)的 BERT-CRF 方法有一定的效果提升,但是仍然有兩個缺陷。其中一個就是前言中提到的效率問題。另外一個則是 BERT-MRC 并沒有充分利用標(biāo)簽的知識信息。前言中提到,BERT-MRC 引入了標(biāo)簽的先驗知識,然而 LEAR 論文中通過對 attention 部分進(jìn)行可視化分析,發(fā)現(xiàn)模型可能沒怎么用到 query 的信息。如圖 1 所示,實體 judge 對應(yīng)的高分 attention 并非如預(yù)期一樣集中在問題的核心部分,而是分散在了一些無關(guān)的信息中(如 [CLS] 等 token)。
▲?圖1 attention 可視化
通過上述分析,可以發(fā)現(xiàn) BERT-MRC 對于先驗知識的利用率并不如預(yù)期。因此,LEAR 設(shè)計了一種新的標(biāo)簽知識整合方式,讓模型高效利用標(biāo)簽先驗知識,同時解決 BERT-MRC 的效率和知識利用率問題。
2.2 LEAR模型架構(gòu)
LEAR 的模型架構(gòu)如圖 2 所示。
▲?圖2 LEAR 模型
其中,模型的輸入分為兩部分:原始的待抽取文本以及所有標(biāo)簽對應(yīng)的描述文本(先驗知識)。與 BERT-MRC 不同,我們不會將標(biāo)簽描述文本與原始文本進(jìn)行拼接,而是使用 BERT 的編碼器分別進(jìn)行編碼得到不同的文本表示。
值得注意的是,為了提升訓(xùn)練效率和減小模型的尺寸,原始文本和標(biāo)簽描述文本共享 BERT 的編碼器權(quán)重;之后我們引入一個標(biāo)簽知識融合模塊,將所有標(biāo)簽描述文本的表示融合到原始文本中;最后我們使用引入分類器來識別待抽取 span 的 start 和 end 位置的概率分布,并使用一些 decoding 策略來抽取實體。下面,我將詳細(xì)描述融合模塊和分類模塊的具體內(nèi)容。
標(biāo)簽信息融合模塊
假設(shè)我們通過 BERT 編碼器得到了原始文本的表示 以及所有標(biāo)簽的描述文本表示 ,其中 n 表示原始文本的長度,m 為標(biāo)簽文本的統(tǒng)一長度(經(jīng)過 padding 之后),|C| 表示標(biāo)簽類別數(shù)量,d 表示 hidden_size。標(biāo)簽信息融合模塊本質(zhì)上是一個注意力機制模塊,其目的就是要讓模型學(xué)習(xí)到原始文本中的各個 token 會關(guān)注到標(biāo)簽描述文本中的哪些內(nèi)容。
對每個標(biāo)簽 c,我們將 視作 attention 計算中的 key 和 value 元素,將 作為 attention 中的 query 元素(關(guān)于 attention 中的Q、K、V 可以參考我以前的 attention 文章介紹 Attention 機制簡單總結(jié) [3],進(jìn)行點乘模式的 attention 計算并得到 attention 分?jǐn)?shù)。在做點乘計算之前,我們參考 attention 典型的做法分別對原始文本表示和標(biāo)簽描述文本表示進(jìn)行線性映射:
然后我們對其進(jìn)行 attention 計算:
接下來就是對 value 元素應(yīng)用所得到的 attention 分?jǐn)?shù)進(jìn)行加權(quán)求和操作,得到原始文本某個 token ?的 context 信息(這里在實現(xiàn)時要使用標(biāo)簽描述文本的 mask 信息,把 padding 位置的值 mask 掉):
我們接下來要做的就是將 context 信息融合到原始文本向量表示中。論文使用的方式是直接用 ADD 操作,將 與 context 相加:
我嘗試過 concat 拼接融合,最后發(fā)現(xiàn)效果并不如直接 add。在此架構(gòu)下,add 操作的融合有更高的信息利用率。
之后,我們引入一個 dense layer,將最終的文本表達(dá)輸出,其中激活函數(shù)使用了 tanh,相比 sigmoid,其值域更廣,而且防止向量表示的絕對值過大:
最后,我們對每個文本 token、標(biāo)簽類別都進(jìn)行上述流程,最終得到完整的融合向量表示:。
在模型實現(xiàn)時,所有標(biāo)簽類別和所有 token 的計算可以在一次矩陣的計算中完成。
span抽取分類模塊
與 BERT-MRC 類似,LEAR 最后也是預(yù)測某個 span 的 start 和 end 的位置 token。但由于 LEAR 中將所有標(biāo)簽類別的預(yù)測都放在的一次前向計算中,因此最后的分類層與傳統(tǒng)的方式有所不同。對于某個原文文本的 token ,我們計算其作為某類實體的 start 概率分布:
其中, 和 分別是可訓(xùn)練的權(quán)重,而 ? 表示 element-wise的矩陣乘法,而 表示對輸入矩陣中的 hidden_size 所在維度進(jìn)行求和,最終得到一個 |C| 維的向量。
雖然上面的公式看起來還是有點繞,但實現(xiàn)起來也不是很復(fù)雜,在 TensorFlow 的框架下,可以這么實現(xiàn):
def?classifier_layers(self,input_features,type="start"):#input?features#?batch_size,input_seq_len,num_labels,hidden_size#?input_shape?=?modeling.get_shape_list(input_features)#?batch_size?=?input_shape[0]#?seq_length?=?input_shape[1]classifer_weight?=?tf.get_variable(name="classifier_weight"+"_"+type,shape=[self.num_labels,?self.attention_size],initializer=modeling.create_initializer(self.bert_config.initializer_range))classifer_bias?=?tf.get_variable(name="classifier_bias"+"_"+type,shape=[self.attention_size],initializer=tf.constant_initializer(0))output?=?tf.multiply(input_features,classifer_weight)output?+=?classifer_bias#[batch_size,?input_seq_len,?num_labels]output?=?tf.reduce_sum(output,axis=-1)return?output其中 tf.multiply 是一個 element-wise 矩陣乘法,且支持 broadcasting 機制。
2.3 span decoding
對于某類實體的 end 概率計算,與 start 的流程是一樣的。最后每個樣本將得到兩個概率輸出:, 。根據(jù)這兩個輸出與預(yù)先設(shè)計的概率閾值(一般是 0.5),我們就能一次性得到所有標(biāo)簽類別的起始位置列表,但是要得到具體的 span 實體,還需要設(shè)計一些 decoding 策略。
論文中提出了兩種 decoding 策略,分別為最近距離策略和啟發(fā)式策略。最近距離策略就是先定位所有的 start 位置,然后找距離 start 位置最近的 token 作為 end。而啟發(fā)式策略則稍微復(fù)雜一些,若定位到一個 start1 位置,不會馬上決定它作為一個 span 的起始位置,若后面的 token 如果也是一個候選的 start2,且其概率還要大于 start1,則會選擇新的 start2 作為 start 候選。具體大家可以參考論文后面的附錄,有詳細(xì)的算法流程。
說實話,我感覺論文中附錄的算法偽代碼有些問題,對于 end 位置的判斷應(yīng)該不需要像 start 一樣選擇概率最高的。另外,我自己的訓(xùn)練數(shù)據(jù)預(yù)處理時,對于單字成實體的情況,我的 end 位置是置為 0 的,而論文和開源代碼中的設(shè)置卻不同,因此不好做直接對比。
我自己實現(xiàn)時,使用的是變種的最近距離策略。因為選擇與 start 最近的 end 時,有可能會越過下一個新的 start 位置,這有可能是算法本身預(yù)測有問題,或者有實體嵌套的情況(目前還未考慮嵌套實體的 case),如圖 3 所示。
▲ 圖3 最近距離decoding策略
我這邊采取了比較簡單的策略,就是在 start1 和 start2 的區(qū)間中,來定位與 start1 最近的 end 位置。那么上圖中的情況下,就不會選擇 s1-e1 作為結(jié)果,而是只選擇 s1 位置處的單個 token 作為結(jié)果輸出:
▲ 圖4 自定義的最近距離decoding策略
大家可以自行嘗試不同的 decoding 策略,我感覺每種策略的適用場景都不一樣,需要根據(jù)實際情況來判斷。
2.4 LEAR的效率
LEAR 相對于 BERT-MRC 的最大優(yōu)勢在于其較高的 inference 效率。由于 LEAR 不需要為每個原始文本構(gòu)造 |C| 個新樣本,理論上 LEAR 的計算復(fù)雜度為 ??梢钥吹?#xff0c;這個復(fù)雜度是遠(yuǎn)小于 BERT-MRC 的,在后面的驗證中,我也得到了預(yù)期的效果。
LEAR實現(xiàn)
我最近分享的一些方法大部分都經(jīng)過實驗并驗證有效的,這次介紹的 LEAR 也不例外。論文有放出 pytorch 的源碼:https://github.com/ Akeepers/LEAR。我照例還是使用 tensorflow 來實現(xiàn),最近重新看了下之前的 NER 框架代碼,感覺有很多地方寫的不太合理,因此我又重新開了個 repo。LEAR 的實現(xiàn)也不是太復(fù)雜,需要注意的地方主要有以下幾個方面:
1. 構(gòu)造模型輸入時,要專門針對實體標(biāo)簽的描述文本進(jìn)行預(yù)處理。在做 input_fn 的時候,使用 tf.data.Dataset.from_generator 來進(jìn)行 batch 數(shù)據(jù)流的構(gòu)建,但是我們的標(biāo)簽文本本來是沒有 batch 概念的,所有訓(xùn)練樣本都只用一份標(biāo)簽文本,因此在 model 方法定義中,要人為將 tf 添加的 batch 維度去掉:
if?label_token_ids.shape.ndims?==?3:label_token_ids?=?label_token_ids[0,:,:]label_token_type_ids?=?label_token_type_ids[0,:,:]label_token_masks?=?label_token_masks[0,:,:]label_token_length?=?label_token_length[0,:]label_lexicons?=?label_lexicons[0,:,:,:]2. 原始文本和標(biāo)簽文本共享 encoder 參數(shù),這里要對 google原始的 BERT 的 modeling.py 進(jìn)行修改,在模型定義的時候,添加 auto_reuse 配置,同時在調(diào)用 bert 的時候,傳入 scope="bert":
3. 論文和官方開源的代碼都沒有對 attention 計算后的分?jǐn)?shù)進(jìn)行 scaling。我覺得可能的原因是融合模塊中的 attention 操作只有一層, 且注意力的分?jǐn)?shù)并沒有直接用于詞 softmax 的計算,而是融合到了原始文本的向量表示中,另外在最后輸出的時候使用了 tanh 進(jìn)行了激活,輸出的值不會太大,因此即使不做 scale,也不會產(chǎn)生很嚴(yán)重的梯度消失問題,并影響后面的分類器的計算。我這邊也驗證了,加入了 scaling 之后,效果相差不多。
最后,我將 LEAR 在中文的 MSRA 的 NER 數(shù)據(jù)集上進(jìn)行了驗證,同時與之前已經(jīng)實現(xiàn)過的 BERT-MRC 進(jìn)行了對比。驗證指標(biāo)則是考慮了實體類型后的 micro-level 的 f1 分?jǐn)?shù),具體來說我會將實體類型字符串與實體內(nèi)容拼接,做去重后,進(jìn)行 exact match 匹配。最終,得到的結(jié)果大體如下:
可以看到,LEAR 的效果相比 BERT-MRC 來說有一定提升,但是不夠明顯,但是 inference 的效率卻是顯著提升了,這也是我最關(guān)注的地方,這意味著這個方法可以應(yīng)用在實際的業(yè)務(wù)場景中!
彩蛋!
因為很長時間沒寫文章了,所以寫一次就盡量多包含點內(nèi)容。為了獎勵讀到這邊的同學(xué),我再奉送一段額外的 NER 優(yōu)化技巧。
讀過我上一篇中文 NER 總結(jié)的同學(xué)應(yīng)該記得我在那篇文章中提到嘗試在 BERT 中引入詞匯信息,當(dāng)時嘗試的辦法很依賴分詞的質(zhì)量,且融合詞匯信息的方式也比較簡單。因此,這次我參考了最近比較火的一篇論文來做詞匯增強:Simplify the Usage of Lexicon in Chinese NER [4]。
這篇論文解讀我就不做了,大家可以參考這篇:JayJay:中文 NER 的正確打開方式: 詞匯增強方法總結(jié)(從 Lattice LSTM 到 FLAT)[5]。這個方法的核心思想是先準(zhǔn)備一份詞向量和詞表,然后對當(dāng)前所有語料中的文本字符統(tǒng)計其分別屬于 BMES 的信息,B 代表詞的開頭,M 代表詞的中間,E 代表詞的結(jié)束,S 代表單獨成詞。
假設(shè)對于某個 token x,若某個樣本 A 中存在片段 span,其開頭為 x,則將 span 對應(yīng)的詞向量添加到 B 對應(yīng)的列表中,其他類型同樣操作。最后,我們將每個類型中的詞向量做加權(quán)平均,得到增加的詞匯信息與原始的 token 向量表示拼接。
▲ 圖5 詞匯增強方法
這個方法實現(xiàn)的最大難點在于如何快速找到包含某個字符,且符合特定位置關(guān)系的 span 詞。原論文開放了代碼:
https://github.com/v-mipeng/LexiconAugmentedNER?
其使用了 trie 數(shù)來構(gòu)建詞典樹,這也是海量字符串匹配場景中會使用的一種方法。我在實現(xiàn)的時候,沒有用這種方式,為了快速得到效果,使用了一種比較基礎(chǔ)的方式,就是在數(shù)據(jù)預(yù)處理的時候,將每個字符對應(yīng)的 BMES 的詞列表信息都預(yù)先存儲起來,在模型輸入的時候直接讀取信息,這樣做速度不慢,但是對內(nèi)存的要求就比價高了,類似于空間換時間。
另外,在使用 tensorflow 實現(xiàn)完整的功能時,我發(fā)現(xiàn)坑有點多。因為靜態(tài)圖中,你要將每個字符的 BMES 詞表序列作為輸入傳到模型圖中,然后用 embedding_lookup 分別找到詞對應(yīng)的詞向量,再做加權(quán)平均,而每個字符實際的 BMES 詞表 size 都不一樣,這意味著更多 padding 和 mask 的處理,想想都頭大。。。所以我偷了個懶,不讓詞匯增強部分參與訓(xùn)練,而是將其作為固定的向量表示與原始 token 向量拼接,看其是否仍有增益的效果,結(jié)果居然也有一定的增益,結(jié)合 LEAR 架構(gòu),我得到了如下的結(jié)果:
TIPS:我在構(gòu)建詞表的時候,為了提高速度,將出現(xiàn)頻率小于 5 的詞都過濾掉了。
可以看到增加了詞匯增強信息后,LEAR 的效果有一定的提升。如果對這個方法感興趣,可以嘗試實現(xiàn)完整功能的增強,讓增強信息也參與到模型學(xué)習(xí)中,最后應(yīng)該會有很大的提升。
小結(jié)
本文主要介紹了一種優(yōu)化 BERT-MRC 的方法,針對其預(yù)測效率低、沒有充分利用標(biāo)簽信息的兩個缺陷,設(shè)計了一種專門針對標(biāo)簽文本的注意力機制融合方法,在提升模型效果的同時,大大提升了基于 MRC 做 NER 的效率,使其能夠應(yīng)用在實際的業(yè)務(wù)場景中。另外,本文也額外介紹了一種不依賴于分詞工具的詞匯增強方法,經(jīng)過驗證,證明其與其他 BERT 類的方法結(jié)合能夠提升模型的抽取效果。
參考文獻(xiàn)
[1] https://zhuanlan.zhihu.com/p/103779616
[2] https://aclanthology.org/2021.emnlp-main.379.pdf
[3]? https://zhuanlan.zhihu.com/p/46313756
[4] https://aclanthology.org/2020.acl-main.528.pdf
[5] https://zhuanlan.zhihu.com/p/142615620
特別鳴謝
感謝 TCCI 天橋腦科學(xué)研究院對于 PaperWeekly 的支持。TCCI 關(guān)注大腦探知、大腦功能和大腦健康。
更多閱讀
#投 稿?通 道#
?讓你的文字被更多人看到?
如何才能讓更多的優(yōu)質(zhì)內(nèi)容以更短路徑到達(dá)讀者群體,縮短讀者尋找優(yōu)質(zhì)內(nèi)容的成本呢?答案就是:你不認(rèn)識的人。
總有一些你不認(rèn)識的人,知道你想知道的東西。PaperWeekly 或許可以成為一座橋梁,促使不同背景、不同方向的學(xué)者和學(xué)術(shù)靈感相互碰撞,迸發(fā)出更多的可能性。?
PaperWeekly 鼓勵高校實驗室或個人,在我們的平臺上分享各類優(yōu)質(zhì)內(nèi)容,可以是最新論文解讀,也可以是學(xué)術(shù)熱點剖析、科研心得或競賽經(jīng)驗講解等。我們的目的只有一個,讓知識真正流動起來。
📝?稿件基本要求:
? 文章確系個人原創(chuàng)作品,未曾在公開渠道發(fā)表,如為其他平臺已發(fā)表或待發(fā)表的文章,請明確標(biāo)注?
? 稿件建議以?markdown?格式撰寫,文中配圖以附件形式發(fā)送,要求圖片清晰,無版權(quán)問題
? PaperWeekly 尊重原作者署名權(quán),并將為每篇被采納的原創(chuàng)首發(fā)稿件,提供業(yè)內(nèi)具有競爭力稿酬,具體依據(jù)文章閱讀量和文章質(zhì)量階梯制結(jié)算
📬?投稿通道:
? 投稿郵箱:hr@paperweekly.site?
? 來稿請備注即時聯(lián)系方式(微信),以便我們在稿件選用的第一時間聯(lián)系作者
? 您也可以直接添加小編微信(pwbot02)快速投稿,備注:姓名-投稿
△長按添加PaperWeekly小編
🔍
現(xiàn)在,在「知乎」也能找到我們了
進(jìn)入知乎首頁搜索「PaperWeekly」
點擊「關(guān)注」訂閱我們的專欄吧
·
總結(jié)
以上是生活随笔為你收集整理的中文NER任务实验小结:BERT-MRC的再优化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 斯巴达克主板怎么设置光盘启动 斯巴达克主
- 下一篇: 华硕零耀U3300怎么分盘 华硕零耀U3