一直想用統計學習方法來改善撥云搜索,這次先在命名實體上小小嘗試一下。
線性鏈條件隨機場
對于無向圖中的節點,定義一組特征函數,使其狀態僅受鄰近節點和觀測序列的影響。
在標注任務中,節點只有前后兩個鄰近節點,即線性鏈條件隨機場。
按照定義,節點有兩類特征函數:轉移特征函數和狀態特征函數。轉移特征函數表示上一個節點狀態及觀測序列對當前節點的影響,狀態特征函數表示觀測序列對當前節點的影響。每個函數都有個權重,模型經過訓練后,通過函數及其權重即可推算節點狀態。
直觀地看,對于 NER 任務,文本為觀測序列,一個詞對應一個節點,標簽為節點狀態。我們為每個節點定義一組特征函數,因為節點對應的詞不一樣,它們的特征函數也不一樣。經過訓練后,模型通過詞對應的特征函數集合,即可計算出最可能的標簽。
python-CRFsuite
因為要同 python 程序銜接,這里選用 CRFsuite 庫。
與 CRF++晦澀的模板文件不同,CRFsuite 以鍵值對的形式定義特征函數。優點是靈活直觀,缺點嘛就是不太好自定義轉移特征了,不過對本文任務沒影響。
方案設計
此次任務目標是識別小說里的地名、道具、技能、門派及特殊人名等實體。由于文本的特殊性,可以預見通用的訓練語料效果不會太好。所以我用基于規則的方法來標注八本有代表性的小說,以此作為訓練語料。用另外的四本小說來評測,然后與前述規則方法的結果做比較。
訓練語料的八本小說:凡人修仙傳, 問題妹妹戀上我, 輪回劍典, 極品家丁, 雪鷹領主, 武林半俠傳, 儒道之天下霸主, 無限之高端玩家。
評測的四本小說:擇天記,大道爭鋒,魔魂啟臨,俗人回檔。
標簽采用 BMEO 四種狀態,B 實體開始,M 實體中間詞,E 實體結尾詞,O 無關詞。
預期目標
原有的規則識別是在匹配到特殊字詞后,按照特定句式、字詞及詞頻來判定是否命名實體。CRF 在定義特征時,綜合考慮了上下文內容和詞性,預計泛化會更好一點。但是語料標注及分詞詞性本身并不靠譜,估計準確率有所降低。
實現
1. 生成訓練語料
將文本分詞后, 送入原識別程序。然后對結果標注,以 json 格式保存。格式如:
["這是", "r", "O"], ["清", "a", "B"], ["虛", "d", "M"], ["門", "n", "E"], ["的", "uj", "O"], ["飛行", "vn", "O"], ["法器", "n", "O"], ["雪", "n", "O"], ["虹", "n", "O"],
單元里有文字、詞性和標簽 3 個屬性。
2. 定義特征
以當前詞及前后兩個詞的文字和詞性為特征函數。這里都是狀態特征,轉移特征是默認的。
def _word2features(wordlist, i):
""" 返回特征列表 """
features = [
'bias',
'word:'+wordlist[i][0],
'word_attr:'+wordlist[i][1]
]
if i > 0:
features.append('word[-1]:'+wordlist[i-1][0])
features.append('word[-1]_attr:'+wordlist[i-1][1])
if i > 1:
features.append('word[-2]:'+wordlist[i-2][0])
features.append('word[-2,-1]:'+wordlist[i-2][0]+wordlist[i-1][0])
features.append('word[-2]_attr:'+wordlist[i-2][1])
if i < len(wordlist)-1:
features.append('word[1]:'+wordlist[i+1][0])
features.append('word[1]_attr:'+wordlist[i+1][1])
if i < len(wordlist)-2:
features.append('word[2]:'+wordlist[i+2][0])
features.append('word[1,2]:'+wordlist[i+1][0]+wordlist[i+2][0])
features.append('word[2]_attr:'+wordlist[i+2][1])
return features
這里直接把特征屬性組合為名稱, 這樣其值就是函數權重,默認為 1。
3. 訓練模型
默認用 L-BFGS 算法訓練。 參數也并沒有調到最優。
model = pycrfsuite.Trainer(verbose=True)
model.append(train_x, train_y)
model.set_params({
'c1': 1.0, # coefficient for L1 penalty
'c2': 1e-3, # coefficient for L2 penalty
'max_iterations': 100, # stop earlier
'feature.possible_states': True,
# include transitions that are possible, but not observed
'feature.possible_transitions': True,
'feature.minfreq': 3
})
model.train("./test.crf")
4. 評測
tagger = pycrfsuite.Tagger()
tagger.open('./test.crf')
test_y = tagger.tag(test_x)
這里抽選大道爭鋒的標注結果,其他 3 本也差不多,如下:
'少清派', '邪派', '玄門正宗', '正宗玄門', '丹師', '魔宗', '瑩云', '種法術', '蛇出來', '琴楠', '大玄門', '一玄門', '一身皮', '了瑩', '大派', '丹術', '丹三', '紫眉', '丹主', '黑衣道人', '巧巧', '陣圖', '喜歡道士', '
十大玄門', '玄袍道人', '小派', '銅爐', '玄功', '劍招', '十多人', '二島', '世家玄門', '九曲溪宮', ',玄門', '派玄門', '諸多玄門', '丹成功', '劍出來', '了玄門', '責翠', '源劍', '玄門小派', '魔門', '玄門十大派', '棲鷹', '三大玄門', '丹功', '丹境', '于各個玄門', '各大派', '一桿長槍', '丹出來', '陣門', '仙派', '九轉功', '丹爐', '楚玄門', '青云一', '禽鷹', '凝功', '源經', '瑩瑩', '半聲', '祭出來', '了出來', '黑風', '玄族', '可玄門', '師出來', '主宮', '玄門大派'
規則方法結果:
'此陣', '六大魔宗', '女冠', '化形丹', '蓬遠派', '桂從堯', '功德院中', '參神契', '此水', '血魄', '自思', '玄門世家', '廣源派', '一觀', '雙翅', '闖陣', '四象陣', '沉香舟', '瀾云密冊', '討爭', '補天閣', '道姑', '晁掌閣',
'入山', '派中', '玄靈山', '清羽門下', '符書', '元陽派', '大門大派', '觀容師妹', '入陣', '善淵觀', '越真觀', '幽陰重水', '水行真光', '玄光境界', '景管事', '破陣', '寶豐觀中', '劍符', '修道者', '沉香教中', '管事', '萬福一禮', '化
丹修士', '山河童子', 'jī動', '年輕道人', '符謅', '紫眉道人', '斬神陣', '丹鼎院', '太昊派', '敗下陣', '歉然', '此丹', '劍丸', '疑 huò', '此老', '溟滄派', '張衍神色', '甫一', '碭域水國', '血衣修士', '正 sè', '少清派', '兩名老道', '
魔門', '丹煞', '觀中', '師徒一脈', '德修觀', '候伯敘', '魔簡', '入門弟子', '蒼梧山', '老道', '小金丹', '寧沖玄一', '下院入門', '化丹', '小童', '符御卿', '力士', '眾弟子', '中年修士', '文安', '文俊', '老魔', '熬通', '道訣', '丘老道', '手一', '玄光境', '丹鼎院中', '玄門正宗', '天閣', '血魄宗', '南華派', '年輕修士', '拿眼', '陣中', '小派', '北辰派', '太昊門', '化一', '陣門', '風師兄', '道書', '玲兒', '玄功', '琴楠', '儒雅道人', '神梭', '凝丹', '珍茗', '玄門十派', '沉香教', '張衍灑然', '無需多', '中年道人', '魔宗', '凕滄派', '清羽門', '守陣', '魔穴', '解讀道書', '十六派', '九魁妖王', '秀兒', '執事道童', '了聲', '化丹境界', '待張衍', '玄門大派', '陶真人門下', '兩派'
統計結果:
| 項目 | CRF | 規則 |
| ---- | ---- | ---- |
| 實體數量 | 71 | 138 |
| 都有 | 9 | 9 |
| 差異數量 | 62 | 129 |
| 準確數 | 25(35%) | 105(76%) |
| 獨有準確數 | 16(8) | -- |
正確的標準是組合正確,且確實為新實體。有多種組合方案的,都算正確,例如:紫眉和紫眉道人。
獨有準確數括號里的數字去掉了因詞頻按規則方法應拋棄的實體。
可以看出幾點:
規則方法識別的數量比 CRF 要多。這是因為語料偏少,CRF 只訓練了 8 本小說,并沒有學習到所有的規則信息。
兩者的準確率都有問題。例如 CRF 里的“了出來”,“師出來”。規則方式里的“甫一”,“正 sè”。都是錯誤地識別了人名,其原因各有不同,但 CRF 受到了標注語料的影響,有可能將錯誤率放大。
CRF 識別出了超出規則的實體,這是期望的目標。
小結
在不靠譜的訓練語料及不靠譜的評價標準下,CRF 仍然給出了有意義的結果。