解壓後取出以下檔案:
訓練資料:icwb2-data/training/pku_ training.utf8
測試資料:icwb2-data/testing/pku_ test.utf8
正確分詞結果:icwb2-data/gold/pku_ test_ gold.utf8
評分工具:icwb2-data/script/socre
2 演算法描述
演算法是最簡單的正向最大匹配(FMM):
用訓練資料生成一個字典
對測試資料從左到右掃描,遇到一個最長的詞,就切分下來,直到句子結束
注:這是最初的演算法,這樣做程式碼可以控制在60行內,後來看測試結果發現沒有很好地處理數字問題, 才又增加了對數字的處理。
3 原始碼及註釋
#! /usr/bin/env python # -*- coding: utf-8 -*- # Author: minix # Date: 2013-03-20 import codecs import sys # 由規則處理的一些特殊符號 numMath = [u'0', u'1', u'2', u'3', u'4', u'5', u'6', u'7', u'8', u'9'] numMath_suffix = [u'.', u'%', u'億', u'萬', u'千', u'百', u'十', u'個'] numCn = [u'一', u'二', u'三', u'四', u'五', u'六', u'七', u'八', u'九', u'〇', u'零'] numCn_suffix_date = [u'年', u'月', u'日'] numCn_suffix_unit = [u'億', u'萬', u'千', u'百', u'十', u'個'] special_char = [u'(', u')'] def proc_num_math(line, start): """ 處理句子中出現的數學符號 """ oldstart = start while line[start] in numMath or line[start] in numMath_suffix: start = start + 1 if line[start] in numCn_suffix_date: start = start + 1 return start - oldstart def proc_num_cn(line, start): """ 處理句子中出現的中文數字 """ oldstart = start while line[start] in numCn or line[start] in numCn_suffix_unit: start = start + 1 if line[start] in numCn_suffix_date: start = start + 1 return start - oldstart def rules(line, start): """ 處理特殊規則 """ if line[start] in numMath: return proc_num_math(line, start) elif line[start] in numCn: return proc_num_cn(line, start) def genDict(path): """ 獲取詞典 """ f = codecs.open(path,'r','utf-8') contents = f.read() contents = contents.replace(u'\r', u'') contents = contents.replace(u'\n', u'') # 將檔案內容按空格分開 mydict = contents.split(u' ') # 去除詞典List中的重複 newdict = list(set(mydict)) newdict.remove(u'') # 建立詞典 # key為詞首字,value為以此字開始的詞構成的List truedict = {} for item in newdict: if len(item)>0 and item[0] in truedict: value = truedict[item[0]] value.append(item) truedict[item[0]] = value else: truedict[item[0]] = [item] return truedict def print_unicode_list(uni_list): for item in uni_list: print item, def divideWords(mydict, sentence): """ 根據詞典對句子進行分詞, 使用正向匹配的演算法,從左到右掃描,遇到最長的詞, 就將它切下來,直到句子被分割完閉 """ ruleChar = [] ruleChar.extend(numCn) ruleChar.extend(numMath) result = [] start = 0 senlen = len(sentence) while start < senlen: curword = sentence[start] maxlen = 1 # 首先檢視是否可以匹配特殊規則 if curword in numCn or curword in numMath: maxlen = rules(sentence, start) # 尋找以當前字開頭的最長詞 if curword in mydict: words = mydict[curword] for item in words: itemlen = len(item) if sentence[start:start+itemlen] == item and itemlen > maxlen: maxlen = itemlen result.append(sentence[start:start+maxlen]) start = start + maxlen return result def main(): args = sys.argv[1:] if len(args) < 3: print 'Usage: python dw.py dict_path test_path result_path' exit(-1) dict_path = args[0] test_path = args[1] result_path = args[2] dicts = genDict(dict_path) fr = codecs.open(test_path,'r','utf-8') test = fr.read() result = divideWords(dicts,test) fr.close() fw = codecs.open(result_path,'w','utf-8') for item in result: fw.write(item + ' ') fw.close() if __name__ == "__main__": main()
4 測試及評分結果
使用 dw.py 訓練資料 測試資料, 生成結果檔案
使用 score 根據訓練資料,正確分詞結果,和我們生成的結果進行評分
使用 tail 檢視結果檔案最後幾行的總體評分,另外socre.utf8中還提供了大量的比較結果, 可以用於發現自己的分詞結果在哪兒做的不夠好
注:整個測試過程都在Ubuntu下完成
$ python dw.py pku_training.utf8 pku_test.utf8 pku_result.utf8
$ perl score pku_training.utf8 pku_test_gold.utf8 pku_result.utf8 > score.utf8
$ tail -22 score.utf8
INSERTIONS: 0
DELETIONS: 0
SUBSTITUTIONS: 0
NCHANGE: 0
NTRUTH: 27
NTEST: 27
TRUE WORDS RECALL: 1.000
TEST WORDS PRECISION: 1.000
=== SUMMARY:
=== TOTAL INSERTIONS: 4623
=== TOTAL DELETIONS: 1740
=== TOTAL SUBSTITUTIONS: 6650
=== TOTAL NCHANGE: 13013
=== TOTAL TRUE WORD COUNT: 104372
=== TOTAL TEST WORD COUNT: 107255
=== TOTAL TRUE WORDS RECALL: 0.920
=== TOTAL TEST WORDS PRECISION: 0.895
=== F MEASURE: 0.907
=== OOV Rate: 0.940
=== OOV Recall Rate: 0.917
=== IV Recall Rate: 0.966
基於詞典的FMM演算法是非常基礎的分詞演算法,效果沒那麼好,不過足夠簡單,也易於入手,隨著學習的深入,我可能還會用Python實現其它的分詞演算法。另外一個感受是,看書的時候儘量多去實現,這樣會讓你有足夠的熱情去關注理論的每一個細節,不會感到那麼枯燥無力。