命名實體識別,Named Entity Recognition,簡稱NER。指的是構建合適的模型,從給定的數據(常常是文本)中得到所需實體的過程。
文章目錄
1、什麼是命名實體
命名實體指的就是所有以名稱來作爲標識的實體。在有的資料1中,將命名實體分爲三大類(實體類、時間類和數字類)七小類(人名、地名、機構名、時間、日期、貨幣和百分比)。
隨着技術的發展以及語言習慣的更新,上述分類方法可能並不適用於所有場景,根據具體業務的不同,可以增減適當的實體種類。
2、NER的關鍵
明白了什麼是實體之後,也就很容易搞懂什麼是實體的識別了。在進行實體識別的過程中,有兩個問題是十分關鍵的:
- 實體邊界的確認
- 實體類別的判斷
這兩個關鍵問題也是很好理解的。
所謂實體邊界的確認,指的是對一個句子中的實體詞進行正確的劃分。例如在句子江澤民同志發表新年講話
中,一個好的識別算法必須將實體詞江澤民
進行正確的標記,而不是在其他的位置進行劃分。
所謂實體類別的判斷,仍以上例說明,算法必須判定江澤民
爲人名實體,而不是其他類型的實體。
在解釋第一個關鍵問題時說到,一個好的算法應該能夠對句子中每個字進行正確的標記以區分該字是否爲實體;進一步,如果是實體,還需表明該詞在實體詞中的位置信息,比如,是實體詞的第一個字,還是中間位置的字,還是最後一個字。讀者不難理解這些標記對於實體邊界確認的重要性。
最簡單的表示法有B-I-O
表示法,即B
表示實體的起始字,I
表示實體的其他字,O
表示非實體字。按照這種方法,上面的句子貼標籤後表示如下(每個字均對應一個標籤):
江 澤 民 同 志 發 表 新 年 講 話
B I I O O O O O O O O
這種方法最簡單,問題也是顯而易見的:實體的末尾字不容易得到區分。在實踐中發現,如果語料庫採用上述方式進行標記,在進行機構實體識別時可能產生錯誤,具體表現爲,將機構實體末尾字的下一個字(也可能兩個或多個,視具體情況而定)也貼上I
標籤,從而得到錯誤的實體名稱。
另外一種相對複雜的表示方法可以表示爲B-M-E-S-O
,類似地,B
表示實體的開始字(Begin),M
表示實體的中間字(Middle),E
表示實體的末尾字(End),S
爲實體只有一個字時的標記(Single,在中文任務中並不常見),O
表示非實體字。上述句子採用這種表示方法得到的結果是:
江 澤 民 同 志 發 表 新 年 講 話
B M E O O O O O O O O
當然,還有其他的方法,但目的都是一致的,這裏不過多介紹了。
3、NER的研究現狀
在吳軍老師的《數學之美》一書中對中文分詞有這樣的總結2:這個問題屬於已經解決的問題,不是什麼難題了。人們再怎麼花精力去研究,所得到的提升也是有限的。現在來看,這也是學術界對NER的研究熱情並不是十分高漲的原因之一了。
另一個原因,個人認爲則是語料庫的限制。爲了比較算法的性能,學術界一般採用固定的語料庫,而如果將其研究成果進行落體實施,那麼語料庫的選取則是一個需要考量的問題。大部分公司可能都不會花精力去構建一個本領域的預料庫來訓練算法,只能利用現有的語料庫,那麼,再優秀的算法最後也可能是落得個“巧婦難爲無米之炊”的下場。
一般來說,現在的NER工作的“標配”是BiLSTM+CRF3,即“雙向長短記憶網絡”+“條件隨機場”模型,也有人提出新的改進方法4。關於CRF的理論性介紹,可以參考我的另一篇翻譯博客。
事實上,單純根據CRF或者BiLSTM也可以做NER(文章最後附上單獨採用CRF模型的NER數據及代碼),但單純根據CRF的弊端在於需要人工手動設置特徵模板,特徵模板決定了特徵函數,特徵函數的輸出對於CRF的工作效果有至關重要的影響。而單獨根據BiLSTM進行NER雖然不需要進行什麼手工操作,但是我們知道BiLSTM進行的實質上就是一個分類工作,而它在分類時是單獨對每個字進行操作的,也就是說,不會利用到上下文已經分好的類標籤,這就容易出現一種邏輯錯誤,比如M
標籤在E
標籤後面,B
後面接O
等情況。
4、基於條件隨機場NER模型訓練
本部分將說明如何基於條件隨機場進行以及現有的工具包來訓練一個粗糙的NER模型,主要參考了這篇博客,在此表示感謝。
4.1 數據集介紹
採用了標註後的人民日報1998年1月語料庫進行訓練,該語料庫的前五行展示如下:
19980101-01-001-001/m 邁向/v 充滿/v 希望/n 的/u 新/a 世紀/n ——/w 一九九八年/t 新年/t 講話/n (/w 附/v 圖片/n 1/m 張/q )/w
19980101-01-001-002/m 中共中央/nt 總書記/n 、/w 國家/n 主席/n 江/nr 澤民/nr
19980101-01-001-003/m (/w 一九九七年/t 十二月/t 三十一日/t )/w
19980101-01-001-004/m 12月/t 31日/t ,/w 中共中央/nt 總書記/n 、/w 國家/n 主席/n 江/nr 澤民/nr 發表/v 1998年/t 新年/t 講話/n 《/w 邁向/v 充滿/v 希望/n 的/u 新/a 世紀/n 》/w 。/w (/w 新華社/nt 記者/n 蘭/nr 紅光/nr 攝/Vg )/w
19980101-01-001-005/m 同胞/n 們/k 、/w 朋友/n 們/k 、/w 女士/n 們/k 、/w 先生/n 們/k :/w
關於語料庫中各個標註的含義,網上有很多說明,可以參考這裏,本文不再贅述。
4.2 任務說明及語料庫預處理
任務: 作爲一個簡單的實踐,目標是從給定的句子中抽取出人名、地名、機構名和時間四類實體。
預處理: 預處理的最終目的是將預料庫中每一個字符與其所屬的實體標籤進行一一對應,舉例如下:
國 家 主 席 江 澤 民 1 9 9 8 年 的 講 話
O O O O B_Per I_Per I_Per B_T I_T I_T I_T I_T O O O
即:對於不屬於實體的詞(字),以O
進行標記;對於實體詞,不但要標記類別(如上例中的Per
表示人名,T
表示時間),而且要標記實體邊界。
要做到這一點,我們進行的主要步驟包括:
- 字符轉換
- 時間詞進行合併
- 人名進行合併
- 大粒度詞的合併
下面一一進行說明。
1、字符轉換
將一個字符串中的全角字符(如果有的話)轉換爲半角字符。實現函數爲q_to_b(str)
。
2、時間詞合併
語料庫中存在類似12月/t 31日/t
的文本,這裏應該將其合併爲12月31日/t
。實現這個功能的函數是process_t(words)
。
存在的問題:例如對文本1998年/t 新年/t
,也會按照上面的形式進行合併,而這並不是我們想要的。
3、人名合併
語料庫中的中文人名都是按姓、名分開標註的:江/nr 澤民/nr
,因此將其合併:江澤民/nr
。實現這個功能的函數是process_nr(words)
。
存在的問題:由於外國人名在本語料庫中不區分姓和名,因此只佔一個詞,如果連續的多箇中國人名中間存在一個外國人名而且它們之間沒有標點間隔時,會出錯。舉例如下:
#有標點間隔時不會出錯
a = '盧/nr 嘉錫/nr 、/w 布赫/nr 、/w 鐵木爾·達瓦買提/nr 、/w 吳/nr 階平/nr 、/w 宋/nr 健/nr'
print(process_nr(a.split())
#輸出爲:['盧嘉錫/nr', '、/w', '布赫/nr', '、/w', '鐵木爾·達瓦買提/nr', '、/w', '吳階平/nr', '、/w', '宋健/nr']
b = '盧/nr 嘉錫/nr 布赫/nr 鐵木爾·達瓦買提/nr 吳/nr 階平/nr 宋/nr 健/nr'
print(process_nr(b.split())
# 結果錯誤
#輸出爲:['盧嘉錫/nr', '布赫鐵木爾·達瓦買提/nr', '吳階平/nr', '宋健/nr']
4、大粒度詞合併
將預料庫中以“[]”括起來的詞進行合併,並以大粒度的標籤進行標註,例如對於[香港/ns 普通話/n 臺/n]nt
,處理爲:香港普通話臺/nt
,實現這個功能的函數是process_k(words)
。
4.3 訓練流程
經過上述預處理之後,該語料仍不能直接進行計算,還需要進行以下操作:
4.3.1 定義映射文件及處理函數
定義一個_maps
字典,其功能是根據語料庫中詞的詞性來對應其實體屬性,內容如下:
_maps = {u't': u'T',
u'nr': u'PER',
u'ns': u'LOC',
u'nt': u'ORG'}
_maps
的鍵爲詞性,值爲實體標籤。
有了映射文件,接下來對語料庫中的所有詞的詞性進行替換,這一步通過pos_to_tag(p)
函數實現。
此時,每個詞都有對應的實體標籤了,接下來確定實體的邊界,即對一個實體中首字貼上B_
,非首字貼上I_
。這個功能通過tag_perform(tag, index)
函數實現。
至此,我們得到的數據應該包含每個單字以及與之對應的實體標籤,當然,還有一些函數用於對語料庫中的數據進行批量處理,具體內容及說明可以看代碼註釋。標籤一共有9種:O
,B_PER
,I_PER
,B_T
,I_T
,B_LOC
,I_LOC
,B_ORG
,I_ORG
。
4.3.2 調用sklearn_crfsuite
sklearn_crfsuite是一個python工具包,它將CRF的功能進行了包裝,並且使用了類似於sklearn的模型語法,使用戶可以在python環境下訓練、保存自己的CRF模型。
特徵模板與特徵函數
特徵模板的含義從字面上理解最好:就是產生你所需要的特徵的一個“模具”。它就是一個框架,可以在你所輸入的數據中提取出指定的特徵。
在代碼中,這個功能是通過extract_feature(word_gram)
函數實現的。該函數中有一行代碼如下:
feature = {u'w-1': word_gram[0], u'w': word_gram[1], u'w+1': word_gram[2],
u'w-1:w': word_gram[0]+word_gram[1], u'w:w+1': word_gram[1]+word_gram[2],
u'bias': 1.0}
這個字典中的內容就是特徵模板了。可以看出這裏一共用了5個模板來“製造”特徵,分別是:當前字、當前字的前一個字、當前字的後一個字、前一個字與當前字的組合,當前字與後一個字的組合。
有了模板之後,就可以對語料提取特徵了。將這一組模板想象爲一個窗口,然後將其沿着語料進行移動,移動到一個字w
處時,通過該模板自然就獲取了w-1
、w+1
、w-1:w
、w:w+1
的內容。如果這些內容第一次出現,就將它們加入到一個集合中,否則不添加。遍歷整個語料庫後,得到的該集合中的內容就是所有的特徵函數的輸入。例如,我得到的模型的該集合前幾個內容是:
['w-1:<BOS> ', 'w:邁', 'w+1:向', 'w-1:w:<BOS>邁', 'w:w+1:邁向', 'bias', 'w-1:邁', 'w:向', 'w+1:充', 'w-1:向', 'w:充', 'w+1:滿', 'w:滿', 'w+1:希', 'w-1:滿', 'w:希', 'w+1:望', 'w:w+1:希望',...]
其中的每一個元素都可以理解爲一種“現象”,滿足這種現象,那麼該函數在該字出的輸出爲1,否則爲0。比如對於第一個字邁
,顯然它滿足前5個現象,即w-1:<BOS>
,w:邁
,w+1:向
,w-1:w:<BOS>邁
,w:w+1:邁向
。因此這5個特徵函數的輸出爲1,其餘的輸出均爲0。
對語料庫中的每一個字都要計算其在所有特徵函數下面的值,因此總的計算次數爲,其中爲特徵函數的個數,爲語料庫大小。
模型的訓練及其他
模型的訓練與sklearn十分相似,首先聲明一個模型實例,然後喂入數據,訓練之後的實例有predict
屬性可以用來進行預測,詳見代碼。
這裏應用的只是sklearn_crfsuite中一小部分功能,用戶還可以查看特徵轉移的概率以及利用交叉驗證等功能,具體可以查看原始文檔。
5、備註
訓練好的基於CRF的NER模型、數據以及詳細地訓練流程已經上傳到github上了,歡迎下載、查看、運行。
後續會增加BiLSTM模型,總結完善學習BiLSTM及CRF理論的筆記。