用条件随机场CRF进行字标注中文分词(Python实现)
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
⽤条件随机场CRF进⾏字标注中⽂分词(Python实现)
主题
本⽂运⽤字标注法进⾏中⽂分词,使⽤4-tag对语料进⾏字标注,观察分词效果。
模型⽅⾯选⽤开源的条件随机场⼯具包“ ”进⾏分词。
本⽂使⽤的中⽂语料资源是SIGHAN提供的语料,⽬前封闭测试最好的结果是4-tag+CFR标注分词,在北⼤语料库上可以在准确率,召回率以及F值上达到92%以上的效果,在微软语料库上可以到达96%以上的效果。
第⼀部分条件随机场模型⼯具包安装说明
在Linux或者Mac OS系统下,下载C++源代码安装包(这⾥⽤的是)之后,按照如下步骤进⾏安装:
1.进⼊到代码主⽬录后,正常按照“configure & make & (sudo) make install就可以完成C++库的安装。
2.再进⼊到⼦⽬录python下,安装python包:python setup.py build & (sudo) python setup.py install,这个python库是通过强⼤的⽣成的。
3.安装完毕之后,可以在python解释器下测试,是否能成功import CRFPP,如果ok,则准备⼯作就绪。
注意:在安装过程中或者是后⾯的运⾏过程中(具体什么时候我忘记了),如果报出下⾯的错误:
ImportError: libcrfpp.so.0: cannot open shared object file: No such file or directory
错误的原因是未能引⼊libcrfpp.so.0库,查找库⽂件存在,于是建⽴链接:
32位系统 ln -s /usr/local/lib/libcrfpp.so.* /usr/lib/
64位系统 ln -s /usr/local/lib/libcrfpp.so.* /usr/lib64/
问题解决。
第⼆部分模型相关知识介绍
在CRF++ example⾥有个seg⽬录,这个seg⽬录对应的是⼀个⽇⽂分词的样例,正好可以套⽤到我们的中⽂分词中来。
在安装包⽬录下,执⾏cd example和cd seg命令后,切换到seg⽬录后,发现有4个⽂件:
exec.sh(执⾏脚本)
template(特征模板)
test.data(测试集)
train.data(训练集)
有了这4个⽂件,我们可以做得事情就⽐较简单,只要按测试集,训练集的格式准备数据就可以了,特征模板和执⾏脚本可以套⽤,不过这⾥简单解读⼀下这⼏个CRF++⽂件。
⾸先来看训练集:
毎 k B
⽇ k I
新 k I
聞 k I
社 k I
特 k B
別 k I
顧 k B
問 k I
4 n B
这⾥第⼀列是待分词的⽇⽂字,第⼆列暂且认为其是词性标记,第三列是字标注中的2-tag(B, I)标记,这个很重要,对于我们需要准备的训练集,主要是把这⼀列的标记做好,不过需要注意的是,其断句是靠空⾏来完成的。
再来看测试集的格式:
よ h I
っ h I
て h I
私 k B
た h B
ち h I
の h B
世 k B
代 k I
が h B
同样也有3列,第⼀列是⽇⽂字,第⼆列第三列与上⾯是相似的,不过在测试集⾥第三列主要是占位作⽤。
事实上,CRF++对于训练集和测试集⽂件格式的要求是⽐较灵活的,⾸先需要多列,但不能不⼀致,既在⼀个⽂件⾥有的⾏是两列,有的⾏是三列;其次第⼀列代表的是需要标注的“字或词”,最后⼀列是输出位”标记tag”,如果有额外的特征,例如词性什么的,可以加到中间列⾥,所以训练集或者测试集的⽂件最少要有两列。
接下⾥我们再来详细的分析⼀下特征模板⽂件:
# Unigram
U00:%x[-2,0]
U01:%x[-1,0]
U02:%x[0,0]
U03:%x[1,0]
U04:%x[2,0]
U05:%x[-2,0]/%x[-1,0]/%x[0,0]
U06:%x[-1,0]/%x[0,0]/%x[1,0]
U07:%x[0,0]/%x[1,0]/%x[2,0]
U08:%x[-1,0]/%x[0,0]
U09:%x[0,0]/%x[1,0]
# Bigram
B
关于CRF++中特征模板的说明和举例,请⼤家参考官⽅⽂档上的“
”这⼀节,⽽以下部分的说明拿上述⽇⽂分词数据举例。
在特征模板⽂件中,每⼀⾏(如U00:%x[-2,0])代表⼀个特征,⽽宏“%x[⾏位置,列位置]”则代表了相对于当前指向的token的⾏偏移和列的绝对位置,以上述训练集为例,如果当前扫描到“新 k I”这⼀⾏,
毎 k B
⽇ k I
新 k I <== 扫描到这⼀⾏,代表当前位置
聞 k I
社 k I
特 k B
別 k I
顧 k B
問 k I
4 n B
那么依据特征模板⽂件抽取的特征如下:
# Unigram
U00:%x[-2,0] ==> 毎
U01:%x[-1,0] ==> ⽇
U02:%x[0,0] ==> 新
U03:%x[1,0] ==> 聞
U04:%x[2,0] ==> 社
U05:%x[-2,0]/%x[-1,0]/%x[0,0] ==> 每/⽇/新
U06:%x[-1,0]/%x[0,0]/%x[1,0] ==> ⽇/新/聞
U07:%x[0,0]/%x[1,0]/%x[2,0] ==> 新/聞/社
U08:%x[-1,0]/%x[0,0] ==> ⽇/新
U09:%x[0,0]/%x[1,0] ==> 新/聞
# Bigram
B
CRF++⾥将特征分成两种类型,⼀种是Unigram的,“U”起头,另外⼀种是Bigram的,“B”起头。
对于Unigram的特征,假如⼀个特征模板是”U01:%x[-1,0]“, CRF++会⾃动的⽣成⼀组特征函数(func1 … funcN) 集合:
func1 = if (output = B and feature="U01:⽇") return 1 else return 0
func2 = if (output = I and feature="U01:⽇") return 1 else return 0
....
funcXX = if (output = B and feature="U01:問") return 1 else return 0
funcXY = if (output = I and feature="U01:問") return 1 else return 0
⽣成的特征函数的数⽬ = (L * N),其中L是输出的类型的个数,这⾥是B,I这两个tag,N是通过模板扩展出来的所有单个字符串(特征)的个数,这⾥指的是在扫描所有训练集的过程中找到的⽇⽂字(特征)。
⽽Bigram特征主要是当前的token和前⾯⼀个位置token的⾃动组合⽣成的bigram特征集合。
最后需要注意的是U01和U02这些标志位,与特征token组合到⼀起主要是区分“U01:問”和“U02:問”这类特征,虽然抽取的⽇⽂”字”特征是⼀样的,但是在CRF++中这是有区别的特征。
最后我们再来看⼀下执⾏脚本:
#!/bin/sh
../../crf_learn -f 3 -c 4.0 template train.data model
../../crf_test -m model test.data
../../crf_learn -a MIRA -f 3 template train.data model
../../crf_test -m model test.data
rm -f model
执⾏脚本告诉了我们如何训练⼀个CRF模型,以及如何利⽤这个模型来进⾏测试,执⾏这个脚本之后,对于输⼊的测试集,输出结果多了⼀列:
よ h I B
っ h I I
て h I B
私 k B B
た h B B
ち h I I
の h B B
世 k B B
代 k I I
が h B B
⽽这⼀列才是模型预测的改字的标记tag,也正是我们所需要的结果。
到此为⽌,关于⽇⽂分词样例的介绍已经完毕,读者应该可以猜测到接下来我们会如何做中⽂分词吧?
第三部分中⽂分词实践
1.将backoff2005⾥的训练数据转化为CRF++所需的训练数据格式,语料选取的是以北京⼤学《⼈民⽇报》的语料,采⽤4-tag(
B(Begin,词⾸), E(End,词尾), M(Middle,词中), S(Single,单字词))标记集,处理utf-8编码⽂本。
原始训练集./icwb2-data/training/pku_training.utf8的形式是⼈⼯分好词的中⽂句⼦形式。
根据如下的脚本 make_train.py,将这个训练语料转换为CRF++训练⽤的语料格式(2列,4-tag):
#!/usr/bin/env python
#-*-coding:utf-8-*-
#4-tags for character tagging: B(Begin),E(End),M(Middle),S(Single)
import codecs
import sys
def character_tagging(input_file, output_file):
input_data = codecs.open(input_file, 'r', 'utf-8')
output_data = codecs.open(output_file, 'w', 'utf-8')
for line in input_data.readlines():
word_list = line.strip().split()
for word in word_list:
if len(word) == 1:
output_data.write(word + "\tS\n")
else:
output_data.write(word[0] + "\tB\n")
for w in word[1:len(word)-1]:
output_data.write(w + "\tM\n")
output_data.write(word[len(word)-1] + "\tE\n")
output_data.write("\n")
input_data.close()
output_data.close()
if __name__ == '__main__':
if len(sys.argv) != 3:
print ("Usage: python " + argv[0] + " input output")
sys.exit(-1)
input_file = sys.argv[1]
output_file = sys.argv[2]
character_tagging(input_file, output_file)
结果(假定⽂件名为pku_training_tag.utf8,后⽂会⽤到)样例如下:
“ S
⼈ B
们 E
常 S
说 S
⽣ B
活 E
是 S
⼀ S
部 S
...
2.有了这份训练语料,就可以利⽤crf的训练⼯具crf_learn来训练模型了,执⾏如下命令即可:crf_learn -f 3 -c 4.0 template
pku_training_tag.utf8 crf_model
训练的时间稍微有些长,在我的2G内存的机器上跑了将近567轮,⼤约21个⼩时,最终训练的crf_model约37M。
有了模型,现在我们需要做得还是准备⼀份CRF++⽤的测试语料,然后利⽤CRF++的测试⼯具crf_test进⾏字标注。
原始的测试语料是icwb2-
data/testing/pku_test.utf8 ,下⾯的脚本会把测试语料转化为模型需要的格式(详见第⼆部分的介绍),并且把切分好的⽂件展现出来。
脚本如下:
#!/usr/bin/env python
#-*-coding:utf-8-*-
#CRF Segmenter based character tagging:
# 4-tags for character tagging: B(Begin), E(End), M(Middle), S(Single)
import codecs
import sys
import CRFPP
def crf_segmenter(input_file, output_file, tagger):
input_data = codecs.open(input_file, 'r', 'utf-8')
output_data = codecs.open(output_file, 'w', 'utf-8')
for line in input_data.readlines():
tagger.clear()
for word in line.strip():
word = word.strip()
if word:
tagger.add((word + "\to\tB").encode('utf-8'))
tagger.parse()
size = tagger.size()
xsize = tagger.xsize()
for i in range(0, size):
for j in range(0, xsize):
char = tagger.x(i, j).decode('utf-8')
tag = tagger.y2(i)
if tag == 'B':
output_data.write(' ' + char)
elif tag == 'M':
output_data.write(char)
elif tag == 'E':
output_data.write(char + ' ')
else:
output_data.write(' ' + char + ' ')
output_data.write('\n')
input_data.close()
output_data.close()
if __name__ == '__main__':
if len(sys.argv) != 4:
print "Usage: python " + sys.argv[0] + " model input output"
sys.exit(-1)
crf_model = sys.argv[1]
input_file = sys.argv[2]
output_file = sys.argv[3]
tagger = CRFPP.Tagger("-m " + crf_model)
crf_segmenter(input_file, output_file, tagger)
第四部分分词结果
有了CRF字标注分词结果,我们就可以利⽤backoff2005的测试脚本来测⼀下这次分词的效果了:
./icwb2-data/scripts/score ./icwb2-data/gold/pku_training_words.utf8 ./icwb2-data/gold/pku_test_gold.utf8 pku_result.utf8 > pku_result.score 注:
pku_result.utf8为最终得到的分词结果⽂件
结果如下:
从上图可以看出,CRF分词的准确率为93.7%,召回率为92.3%。