解压后取出以下文件:
训练数据: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 源代码及注释
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
|
#! /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实现其它的分词算法。另外一个感受是,看书的时候尽量多去实现,这样会让你有足够的热情去关注理论的每一个细节,不会感到那么枯燥无力。
暂无评论内容