本文运用字标注法进行中文分词,使用4-tag对语料进行字标注,观察分词效果。模型方面选用开源的条件随机场工具包“ CRF++: Yet Another CRF toolkit ”进行分词。

本文使用的中文语料资源是SIGHAN提供的backoff 2005语料,目前封闭测试最好的结果是4-tag+CFR标注分词,在北大语料库上可以在准确率,召回率以及F值上达到92%以上的效果,在微软语料库上可以到达96%以上的效果。

第一部分 条件随机场模型工具包安装说明

在Linux或者Mac OS系统下,下载C++源代码安装包(这里用的是 CRF++-0.58.tar.gz )之后,按照如下步骤进行安装:

1.进入到代码主目录后,正常按照“configure & make & (sudo) make install就可以完成C++库的安装。

2.再进入到子目录python下,安装python包:python setup.py build & (sudo) python setup.py install,这个python库是通过强大的SWIG生成的。

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++文件。首先来看训练集:

[python] view plaincopy
  1. 毎 k   B
  2. 日 k   I
  3. 新 k   I
  4. 聞 k   I
  5. 社 k   I
  6. 特 k   B
  7. 別 k   I
  8. 顧 k   B
  9. 問 k   I
  10. 4 n   B
[python] view plain copy
  1. 毎 k   B
  2. 日 k   I
  3. 新 k   I
  4. 聞 k   I
  5. 社 k   I
  6. 特 k   B
  7. 別 k   I
  8. 顧 k   B
  9. 問 k   I
  10. 4 n   B

这里第一列是待分词的日文字,第二列暂且认为其是词性标记,第三列是字标注中的2-tag(B, I)标记,这个很重要,对于我们需要准备的训练集,主要是把这一列的标记做好,不过需要注意的是,其断句是靠空行来完成的。

再来看测试集的格式:

[python] view plaincopy
  1. よ h   I
  2. っ h   I
  3. て h   I
  4. 私 k   B
  5. た h   B
  6. ち h   I
  7. の h   B
  8. 世 k   B
  9. 代 k   I
  10. が h   B
[python] view plain copy
  1. よ h   I
  2. っ h   I
  3. て h   I
  4. 私 k   B
  5. た h   B
  6. ち h   I
  7. の h   B
  8. 世 k   B
  9. 代 k   I
  10. が h   B

同样也有3列,第一列是日文字,第二列第三列与上面是相似的,不过在测试集里第三列主要是占位作用。事实上,CRF++对于训练集和测试集文件格式的要求是比较灵活的,首先需要多列,但不能不一致,既在一个文件里有的行是两列,有的行是三列;其次第一列代表的是需要标注的“字或词”,最后一列是输出位”标记tag”,如果有额外的特征,例如词性什么的,可以加到中间列里,所以训练集或者测试集的文件最少要有两列。

接下里我们再来详细的分析一下特征模板文件:

[python] view plaincopy
  1. # Unigram
  2. U00:%x[-2,0]
  3. U01:%x[-1,0]
  4. U02:%x[0,0]
  5. U03:%x[1,0]
  6. U04:%x[2,0]
  7. U05:%x[-2,0]/%x[-1,0]/%x[0,0]
  8. U06:%x[-1,0]/%x[0,0]/%x[1,0]
  9. U07:%x[0,0]/%x[1,0]/%x[2,0]
  10. U08:%x[-1,0]/%x[0,0]
  11. U09:%x[0,0]/%x[1,0]
  12. # Bigram
  13. B
[python] view plain copy
  1. # Unigram
  2. U00:%x[-2,0]
  3. U01:%x[-1,0]
  4. U02:%x[0,0]
  5. U03:%x[1,0]
  6. U04:%x[2,0]
  7. U05:%x[-2,0]/%x[-1,0]/%x[0,0]
  8. U06:%x[-1,0]/%x[0,0]/%x[1,0]
  9. U07:%x[0,0]/%x[1,0]/%x[2,0]
  10. U08:%x[-1,0]/%x[0,0]
  11. U09:%x[0,0]/%x[1,0]
  12. # Bigram
  13. B

关于CRF++中特征模板的说明和举例,请大家参考官方文档上的“ Preparing feature templates ”这一节,而以下部分的说明拿上述日文分词数据举例。在特征模板文件中,每一行(如U00:%x[-2,0])代表一个特征,而宏“%x[行位置,列位置]”则代表了相对于当前指向的token的行偏移和列的绝对位置,以上述训练集为例,如果当前扫描到“新 k I”这一行,

[python] view plaincopy
  1. 毎 k   B
  2. 日 k   I
  3. 新 k   I   <== 扫描到这一行,代表当前位置
  4. 聞 k   I
  5. 社 k   I
  6. 特 k   B
  7. 別 k   I
  8. 顧 k   B
  9. 問 k   I
  10. 4 n   B
[python] view plain copy
  1. 毎 k   B
  2. 日 k   I
  3. 新 k   I   <== 扫描到这一行,代表当前位置
  4. 聞 k   I
  5. 社 k   I
  6. 特 k   B
  7. 別 k   I
  8. 顧 k   B
  9. 問 k   I
  10. 4 n   B

那么依据特征模板文件抽取的特征如下:

[python] view plaincopy
  1. # Unigram
  2. U00:%x[-2,0] ==> 毎
  3. U01:%x[-1,0] ==> 日
  4. U02:%x[0,0]  ==> 新
  5. U03:%x[1,0]  ==> 聞
  6. U04:%x[2,0]  ==> 社
  7. U05:%x[-2,0]/%x[-1,0]/%x[0,0] ==> 每/日/新
  8. U06:%x[-1,0]/%x[0,0]/%x[1,0]  ==> 日/新/聞
  9. U07:%x[0,0]/%x[1,0]/%x[2,0]   ==> 新/聞/社
  10. U08:%x[-1,0]/%x[0,0]          ==> 日/新
  11. U09:%x[0,0]/%x[1,0]           ==> 新/聞
  12. # Bigram
  13. B
[python] view plain copy
  1. # Unigram
  2. U00:%x[-2,0] ==> 毎
  3. U01:%x[-1,0] ==> 日
  4. U02:%x[0,0]  ==> 新
  5. U03:%x[1,0]  ==> 聞
  6. U04:%x[2,0]  ==> 社
  7. U05:%x[-2,0]/%x[-1,0]/%x[0,0] ==> 每/日/新
  8. U06:%x[-1,0]/%x[0,0]/%x[1,0]  ==> 日/新/聞
  9. U07:%x[0,0]/%x[1,0]/%x[2,0]   ==> 新/聞/社
  10. U08:%x[-1,0]/%x[0,0]          ==> 日/新
  11. U09:%x[0,0]/%x[1,0]           ==> 新/聞
  12. # Bigram
  13. B

CRF++里将特征分成两种类型,一种是Unigram的,“U”起头,另外一种是Bigram的,“B”起头。对于Unigram的特征,假如一个特征模板是”U01:%x[-1,0]“, CRF++会自动的生成一组特征函数(func1 … funcN) 集合:

[python] view plaincopy
  1. func1 = if (output = B and feature="U01:日") return 1 else return 0
  2. func2 = if (output = I and feature="U01:日") return 1 else return 0
  3. ....
  4. funcXX = if (output = B and feature="U01:問") return 1  else return 0
  5. funcXY = if (output = I and feature="U01:問") return 1  else return 0
[python] view plain copy
  1. func1 = if (output = B and feature="U01:日") return 1 else return 0
  2. func2 = if (output = I and feature="U01:日") return 1 else return 0
  3. ....
  4. funcXX = if (output = B and feature="U01:問") return 1  else return 0
  5. 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++中这是有区别的特征。

最后我们再来看一下执行脚本:

[python] view plaincopy
  1. #!/bin/sh
  2. ../../crf_learn -f 3 -c 4.0 template train.data model
  3. ../../crf_test -m model test.data
  4. ../../crf_learn -a MIRA -f 3 template train.data model
  5. ../../crf_test -m model test.data
  6. rm -f model
[python] view plain copy
  1. #!/bin/sh
  2. ../../crf_learn -f 3 -c 4.0 template train.data model
  3. ../../crf_test -m model test.data
  4. ../../crf_learn -a MIRA -f 3 template train.data model
  5. ../../crf_test -m model test.data
  6. rm -f model

执行脚本告诉了我们如何训练一个CRF模型,以及如何利用这个模型来进行测试,执行这个脚本之后,对于输入的测试集,输出结果多了一列:

[python] view plaincopy
  1. よ h   I   B
  2. っ h   I   I
  3. て h   I   B
  4. 私 k   B   B
  5. た h   B   B
  6. ち h   I   I
  7. の h   B   B
  8. 世 k   B   B
  9. 代 k   I   I
  10. が h   B   B
[python] view plain copy
  1. よ h   I   B
  2. っ h   I   I
  3. て h   I   B
  4. 私 k   B   B
  5. た h   B   B
  6. ち h   I   I
  7. の h   B   B
  8. 世 k   B   B
  9. 代 k   I   I
  10. が 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):

[python] view plaincopy
  1. #!/usr/bin/env python
  2. #-*-coding:utf-8-*-
  3. #4-tags for character tagging: B(Begin),E(End),M(Middle),S(Single)
  4. import codecs
  5. import sys
  6. def character_tagging(input_file, output_file):
  7. input_data = codecs.open(input_file, 'r', 'utf-8')
  8. output_data = codecs.open(output_file, 'w', 'utf-8')
  9. for line in input_data.readlines():
  10. word_list = line.strip().split()
  11. for word in word_list:
  12. if len(word) == 1:
  13. output_data.write(word + "\tS\n")
  14. else:
  15. output_data.write(word[0] + "\tB\n")
  16. for w in word[1:len(word)-1]:
  17. output_data.write(w + "\tM\n")
  18. output_data.write(word[len(word)-1] + "\tE\n")
  19. output_data.write("\n")
  20. input_data.close()
  21. output_data.close()
  22. if __name__ == '__main__':
  23. if len(sys.argv) != 3:
  24. print ("Usage: python " + argv[0] + " input output")
  25. sys.exit(-1)
  26. input_file = sys.argv[1]
  27. output_file = sys.argv[2]
  28. character_tagging(input_file, output_file)
[python] view plain copy
  1. #!/usr/bin/env python
  2. #-*-coding:utf-8-*-
  3. #4-tags for character tagging: B(Begin),E(End),M(Middle),S(Single)
  4. import codecs
  5. import sys
  6. def character_tagging(input_file, output_file):
  7. input_data = codecs.open(input_file, 'r', 'utf-8')
  8. output_data = codecs.open(output_file, 'w', 'utf-8')
  9. for line in input_data.readlines():
  10. word_list = line.strip().split()
  11. for word in word_list:
  12. if len(word) == 1:
  13. output_data.write(word + "\tS\n")
  14. else:
  15. output_data.write(word[0] + "\tB\n")
  16. for w in word[1:len(word)-1]:
  17. output_data.write(w + "\tM\n")
  18. output_data.write(word[len(word)-1] + "\tE\n")
  19. output_data.write("\n")
  20. input_data.close()
  21. output_data.close()
  22. if __name__ == '__main__':
  23. if len(sys.argv) != 3:
  24. print ("Usage: python " + argv[0] + " input output")
  25. sys.exit(-1)
  26. input_file = sys.argv[1]
  27. output_file = sys.argv[2]
  28. character_tagging(input_file, output_file)

结果(假定文件名为pku_training_tag.utf8,后文会用到)样例如下:

[python] view plaincopy
  1. “ S
  2. 人 B
  3. 们 E
  4. 常 S
  5. 说 S
  6. 生 B
  7. 活 E
  8. 是 S
  9. 一 S
  10. 部 S
  11. ...
[python] view plain copy
  1. “ S
  2. 人 B
  3. 们 E
  4. 常 S
  5. 说 S
  6. 生 B
  7. 活 E
  8. 是 S
  9. 一 S
  10. 部 S
  11. ...

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 ,下面的脚本会把测试语料转化为模型需要的格式(详见第二部分的介绍),并且把切分好的文件展现出来。脚本如下:

[python] view plaincopy
  1. #!/usr/bin/env python
  2. #-*-coding:utf-8-*-
  3. #CRF Segmenter based character tagging:
  4. # 4-tags for character tagging: B(Begin), E(End), M(Middle), S(Single)
  5. import codecs
  6. import sys
  7. import CRFPP
  8. def crf_segmenter(input_file, output_file, tagger):
  9. input_data = codecs.open(input_file, 'r', 'utf-8')
  10. output_data = codecs.open(output_file, 'w', 'utf-8')
  11. for line in input_data.readlines():
  12. tagger.clear()
  13. for word in line.strip():
  14. word = word.strip()
  15. if word:
  16. tagger.add((word + "\to\tB").encode('utf-8'))
  17. tagger.parse()
  18. size = tagger.size()
  19. xsize = tagger.xsize()
  20. for i in range(0, size):
  21. for j in range(0, xsize):
  22. char = tagger.x(i, j).decode('utf-8')
  23. tag = tagger.y2(i)
  24. if tag == 'B':
  25. output_data.write(' ' + char)
  26. elif tag == 'M':
  27. output_data.write(char)
  28. elif tag == 'E':
  29. output_data.write(char + ' ')
  30. else:
  31. output_data.write(' ' + char + ' ')
  32. output_data.write('\n')
  33. input_data.close()
  34. output_data.close()
  35. if __name__ == '__main__':
  36. if len(sys.argv) != 4:
  37. print "Usage: python " + sys.argv[0] + " model input output"
  38. sys.exit(-1)
  39. crf_model = sys.argv[1]
  40. input_file = sys.argv[2]
  41. output_file = sys.argv[3]
  42. tagger = CRFPP.Tagger("-m " + crf_model)
  43. crf_segmenter(input_file, output_file, tagger)
[python] view plain copy
  1. #!/usr/bin/env python
  2. #-*-coding:utf-8-*-
  3. #CRF Segmenter based character tagging:
  4. # 4-tags for character tagging: B(Begin), E(End), M(Middle), S(Single)
  5. import codecs
  6. import sys
  7. import CRFPP
  8. def crf_segmenter(input_file, output_file, tagger):
  9. input_data = codecs.open(input_file, 'r', 'utf-8')
  10. output_data = codecs.open(output_file, 'w', 'utf-8')
  11. for line in input_data.readlines():
  12. tagger.clear()
  13. for word in line.strip():
  14. word = word.strip()
  15. if word:
  16. tagger.add((word + "\to\tB").encode('utf-8'))
  17. tagger.parse()
  18. size = tagger.size()
  19. xsize = tagger.xsize()
  20. for i in range(0, size):
  21. for j in range(0, xsize):
  22. char = tagger.x(i, j).decode('utf-8')
  23. tag = tagger.y2(i)
  24. if tag == 'B':
  25. output_data.write(' ' + char)
  26. elif tag == 'M':
  27. output_data.write(char)
  28. elif tag == 'E':
  29. output_data.write(char + ' ')
  30. else:
  31. output_data.write(' ' + char + ' ')
  32. output_data.write('\n')
  33. input_data.close()
  34. output_data.close()
  35. if __name__ == '__main__':
  36. if len(sys.argv) != 4:
  37. print "Usage: python " + sys.argv[0] + " model input output"
  38. sys.exit(-1)
  39. crf_model = sys.argv[1]
  40. input_file = sys.argv[2]
  41. output_file = sys.argv[3]
  42. tagger = CRFPP.Tagger("-m " + crf_model)
  43. crf_segmenter(input_file, output_file, tagger)

转自:http://blog.csdn.net/u010189459/article/details/38546115

第四部分 分词结果

有了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%。

本文运用字标注法进行中文分词,使用4-tag对语料进行字标注,观察分词效果。模型方面选用开源的条件随机场工具包“ CRF++: Yet Another CRF toolkit ”进行分词。

本文使用的中文语料资源是SIGHAN提供的backoff 2005语料,目前封闭测试最好的结果是4-tag+CFR标注分词,在北大语料库上可以在准确率,召回率以及F值上达到92%以上的效果,在微软语料库上可以到达96%以上的效果。

第一部分 条件随机场模型工具包安装说明

在Linux或者Mac OS系统下,下载C++源代码安装包(这里用的是 CRF++-0.58.tar.gz )之后,按照如下步骤进行安装:

1.进入到代码主目录后,正常按照“configure & make & (sudo) make install就可以完成C++库的安装。

2.再进入到子目录python下,安装python包:python setup.py build & (sudo) python setup.py install,这个python库是通过强大的SWIG生成的。

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++文件。首先来看训练集:

[python] view plaincopy
  1. 毎 k   B
  2. 日 k   I
  3. 新 k   I
  4. 聞 k   I
  5. 社 k   I
  6. 特 k   B
  7. 別 k   I
  8. 顧 k   B
  9. 問 k   I
  10. 4 n   B
[python] view plain copy
  1. 毎 k   B
  2. 日 k   I
  3. 新 k   I
  4. 聞 k   I
  5. 社 k   I
  6. 特 k   B
  7. 別 k   I
  8. 顧 k   B
  9. 問 k   I
  10. 4 n   B

这里第一列是待分词的日文字,第二列暂且认为其是词性标记,第三列是字标注中的2-tag(B, I)标记,这个很重要,对于我们需要准备的训练集,主要是把这一列的标记做好,不过需要注意的是,其断句是靠空行来完成的。

再来看测试集的格式:

[python] view plaincopy
  1. よ h   I
  2. っ h   I
  3. て h   I
  4. 私 k   B
  5. た h   B
  6. ち h   I
  7. の h   B
  8. 世 k   B
  9. 代 k   I
  10. が h   B
[python] view plain copy
  1. よ h   I
  2. っ h   I
  3. て h   I
  4. 私 k   B
  5. た h   B
  6. ち h   I
  7. の h   B
  8. 世 k   B
  9. 代 k   I
  10. が h   B

同样也有3列,第一列是日文字,第二列第三列与上面是相似的,不过在测试集里第三列主要是占位作用。事实上,CRF++对于训练集和测试集文件格式的要求是比较灵活的,首先需要多列,但不能不一致,既在一个文件里有的行是两列,有的行是三列;其次第一列代表的是需要标注的“字或词”,最后一列是输出位”标记tag”,如果有额外的特征,例如词性什么的,可以加到中间列里,所以训练集或者测试集的文件最少要有两列。

接下里我们再来详细的分析一下特征模板文件:

[python] view plaincopy
  1. # Unigram
  2. U00:%x[-2,0]
  3. U01:%x[-1,0]
  4. U02:%x[0,0]
  5. U03:%x[1,0]
  6. U04:%x[2,0]
  7. U05:%x[-2,0]/%x[-1,0]/%x[0,0]
  8. U06:%x[-1,0]/%x[0,0]/%x[1,0]
  9. U07:%x[0,0]/%x[1,0]/%x[2,0]
  10. U08:%x[-1,0]/%x[0,0]
  11. U09:%x[0,0]/%x[1,0]
  12. # Bigram
  13. B
[python] view plain copy
  1. # Unigram
  2. U00:%x[-2,0]
  3. U01:%x[-1,0]
  4. U02:%x[0,0]
  5. U03:%x[1,0]
  6. U04:%x[2,0]
  7. U05:%x[-2,0]/%x[-1,0]/%x[0,0]
  8. U06:%x[-1,0]/%x[0,0]/%x[1,0]
  9. U07:%x[0,0]/%x[1,0]/%x[2,0]
  10. U08:%x[-1,0]/%x[0,0]
  11. U09:%x[0,0]/%x[1,0]
  12. # Bigram
  13. B

关于CRF++中特征模板的说明和举例,请大家参考官方文档上的“ Preparing feature templates ”这一节,而以下部分的说明拿上述日文分词数据举例。在特征模板文件中,每一行(如U00:%x[-2,0])代表一个特征,而宏“%x[行位置,列位置]”则代表了相对于当前指向的token的行偏移和列的绝对位置,以上述训练集为例,如果当前扫描到“新 k I”这一行,

[python] view plaincopy
  1. 毎 k   B
  2. 日 k   I
  3. 新 k   I   <== 扫描到这一行,代表当前位置
  4. 聞 k   I
  5. 社 k   I
  6. 特 k   B
  7. 別 k   I
  8. 顧 k   B
  9. 問 k   I
  10. 4 n   B
[python] view plain copy
  1. 毎 k   B
  2. 日 k   I
  3. 新 k   I   <== 扫描到这一行,代表当前位置
  4. 聞 k   I
  5. 社 k   I
  6. 特 k   B
  7. 別 k   I
  8. 顧 k   B
  9. 問 k   I
  10. 4 n   B

那么依据特征模板文件抽取的特征如下:

[python] view plaincopy
  1. # Unigram
  2. U00:%x[-2,0] ==> 毎
  3. U01:%x[-1,0] ==> 日
  4. U02:%x[0,0]  ==> 新
  5. U03:%x[1,0]  ==> 聞
  6. U04:%x[2,0]  ==> 社
  7. U05:%x[-2,0]/%x[-1,0]/%x[0,0] ==> 每/日/新
  8. U06:%x[-1,0]/%x[0,0]/%x[1,0]  ==> 日/新/聞
  9. U07:%x[0,0]/%x[1,0]/%x[2,0]   ==> 新/聞/社
  10. U08:%x[-1,0]/%x[0,0]          ==> 日/新
  11. U09:%x[0,0]/%x[1,0]           ==> 新/聞
  12. # Bigram
  13. B
[python] view plain copy
  1. # Unigram
  2. U00:%x[-2,0] ==> 毎
  3. U01:%x[-1,0] ==> 日
  4. U02:%x[0,0]  ==> 新
  5. U03:%x[1,0]  ==> 聞
  6. U04:%x[2,0]  ==> 社
  7. U05:%x[-2,0]/%x[-1,0]/%x[0,0] ==> 每/日/新
  8. U06:%x[-1,0]/%x[0,0]/%x[1,0]  ==> 日/新/聞
  9. U07:%x[0,0]/%x[1,0]/%x[2,0]   ==> 新/聞/社
  10. U08:%x[-1,0]/%x[0,0]          ==> 日/新
  11. U09:%x[0,0]/%x[1,0]           ==> 新/聞
  12. # Bigram
  13. B

CRF++里将特征分成两种类型,一种是Unigram的,“U”起头,另外一种是Bigram的,“B”起头。对于Unigram的特征,假如一个特征模板是”U01:%x[-1,0]“, CRF++会自动的生成一组特征函数(func1 … funcN) 集合:

[python] view plaincopy
  1. func1 = if (output = B and feature="U01:日") return 1 else return 0
  2. func2 = if (output = I and feature="U01:日") return 1 else return 0
  3. ....
  4. funcXX = if (output = B and feature="U01:問") return 1  else return 0
  5. funcXY = if (output = I and feature="U01:問") return 1  else return 0
[python] view plain copy
  1. func1 = if (output = B and feature="U01:日") return 1 else return 0
  2. func2 = if (output = I and feature="U01:日") return 1 else return 0
  3. ....
  4. funcXX = if (output = B and feature="U01:問") return 1  else return 0
  5. 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++中这是有区别的特征。

最后我们再来看一下执行脚本:

[python] view plaincopy
  1. #!/bin/sh
  2. ../../crf_learn -f 3 -c 4.0 template train.data model
  3. ../../crf_test -m model test.data
  4. ../../crf_learn -a MIRA -f 3 template train.data model
  5. ../../crf_test -m model test.data
  6. rm -f model
[python] view plain copy
  1. #!/bin/sh
  2. ../../crf_learn -f 3 -c 4.0 template train.data model
  3. ../../crf_test -m model test.data
  4. ../../crf_learn -a MIRA -f 3 template train.data model
  5. ../../crf_test -m model test.data
  6. rm -f model

执行脚本告诉了我们如何训练一个CRF模型,以及如何利用这个模型来进行测试,执行这个脚本之后,对于输入的测试集,输出结果多了一列:

[python] view plaincopy
  1. よ h   I   B
  2. っ h   I   I
  3. て h   I   B
  4. 私 k   B   B
  5. た h   B   B
  6. ち h   I   I
  7. の h   B   B
  8. 世 k   B   B
  9. 代 k   I   I
  10. が h   B   B
[python] view plain copy
  1. よ h   I   B
  2. っ h   I   I
  3. て h   I   B
  4. 私 k   B   B
  5. た h   B   B
  6. ち h   I   I
  7. の h   B   B
  8. 世 k   B   B
  9. 代 k   I   I
  10. が 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):

[python] view plaincopy
  1. #!/usr/bin/env python
  2. #-*-coding:utf-8-*-
  3. #4-tags for character tagging: B(Begin),E(End),M(Middle),S(Single)
  4. import codecs
  5. import sys
  6. def character_tagging(input_file, output_file):
  7. input_data = codecs.open(input_file, 'r', 'utf-8')
  8. output_data = codecs.open(output_file, 'w', 'utf-8')
  9. for line in input_data.readlines():
  10. word_list = line.strip().split()
  11. for word in word_list:
  12. if len(word) == 1:
  13. output_data.write(word + "\tS\n")
  14. else:
  15. output_data.write(word[0] + "\tB\n")
  16. for w in word[1:len(word)-1]:
  17. output_data.write(w + "\tM\n")
  18. output_data.write(word[len(word)-1] + "\tE\n")
  19. output_data.write("\n")
  20. input_data.close()
  21. output_data.close()
  22. if __name__ == '__main__':
  23. if len(sys.argv) != 3:
  24. print ("Usage: python " + argv[0] + " input output")
  25. sys.exit(-1)
  26. input_file = sys.argv[1]
  27. output_file = sys.argv[2]
  28. character_tagging(input_file, output_file)
[python] view plain copy
  1. #!/usr/bin/env python
  2. #-*-coding:utf-8-*-
  3. #4-tags for character tagging: B(Begin),E(End),M(Middle),S(Single)
  4. import codecs
  5. import sys
  6. def character_tagging(input_file, output_file):
  7. input_data = codecs.open(input_file, 'r', 'utf-8')
  8. output_data = codecs.open(output_file, 'w', 'utf-8')
  9. for line in input_data.readlines():
  10. word_list = line.strip().split()
  11. for word in word_list:
  12. if len(word) == 1:
  13. output_data.write(word + "\tS\n")
  14. else:
  15. output_data.write(word[0] + "\tB\n")
  16. for w in word[1:len(word)-1]:
  17. output_data.write(w + "\tM\n")
  18. output_data.write(word[len(word)-1] + "\tE\n")
  19. output_data.write("\n")
  20. input_data.close()
  21. output_data.close()
  22. if __name__ == '__main__':
  23. if len(sys.argv) != 3:
  24. print ("Usage: python " + argv[0] + " input output")
  25. sys.exit(-1)
  26. input_file = sys.argv[1]
  27. output_file = sys.argv[2]
  28. character_tagging(input_file, output_file)

结果(假定文件名为pku_training_tag.utf8,后文会用到)样例如下:

[python] view plaincopy
  1. “ S
  2. 人 B
  3. 们 E
  4. 常 S
  5. 说 S
  6. 生 B
  7. 活 E
  8. 是 S
  9. 一 S
  10. 部 S
  11. ...
[python] view plain copy
  1. “ S
  2. 人 B
  3. 们 E
  4. 常 S
  5. 说 S
  6. 生 B
  7. 活 E
  8. 是 S
  9. 一 S
  10. 部 S
  11. ...

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 ,下面的脚本会把测试语料转化为模型需要的格式(详见第二部分的介绍),并且把切分好的文件展现出来。脚本如下:

[python] view plaincopy
  1. #!/usr/bin/env python
  2. #-*-coding:utf-8-*-
  3. #CRF Segmenter based character tagging:
  4. # 4-tags for character tagging: B(Begin), E(End), M(Middle), S(Single)
  5. import codecs
  6. import sys
  7. import CRFPP
  8. def crf_segmenter(input_file, output_file, tagger):
  9. input_data = codecs.open(input_file, 'r', 'utf-8')
  10. output_data = codecs.open(output_file, 'w', 'utf-8')
  11. for line in input_data.readlines():
  12. tagger.clear()
  13. for word in line.strip():
  14. word = word.strip()
  15. if word:
  16. tagger.add((word + "\to\tB").encode('utf-8'))
  17. tagger.parse()
  18. size = tagger.size()
  19. xsize = tagger.xsize()
  20. for i in range(0, size):
  21. for j in range(0, xsize):
  22. char = tagger.x(i, j).decode('utf-8')
  23. tag = tagger.y2(i)
  24. if tag == 'B':
  25. output_data.write(' ' + char)
  26. elif tag == 'M':
  27. output_data.write(char)
  28. elif tag == 'E':
  29. output_data.write(char + ' ')
  30. else:
  31. output_data.write(' ' + char + ' ')
  32. output_data.write('\n')
  33. input_data.close()
  34. output_data.close()
  35. if __name__ == '__main__':
  36. if len(sys.argv) != 4:
  37. print "Usage: python " + sys.argv[0] + " model input output"
  38. sys.exit(-1)
  39. crf_model = sys.argv[1]
  40. input_file = sys.argv[2]
  41. output_file = sys.argv[3]
  42. tagger = CRFPP.Tagger("-m " + crf_model)
  43. crf_segmenter(input_file, output_file, tagger)
[python] view plain copy
  1. #!/usr/bin/env python
  2. #-*-coding:utf-8-*-
  3. #CRF Segmenter based character tagging:
  4. # 4-tags for character tagging: B(Begin), E(End), M(Middle), S(Single)
  5. import codecs
  6. import sys
  7. import CRFPP
  8. def crf_segmenter(input_file, output_file, tagger):
  9. input_data = codecs.open(input_file, 'r', 'utf-8')
  10. output_data = codecs.open(output_file, 'w', 'utf-8')
  11. for line in input_data.readlines():
  12. tagger.clear()
  13. for word in line.strip():
  14. word = word.strip()
  15. if word:
  16. tagger.add((word + "\to\tB").encode('utf-8'))
  17. tagger.parse()
  18. size = tagger.size()
  19. xsize = tagger.xsize()
  20. for i in range(0, size):
  21. for j in range(0, xsize):
  22. char = tagger.x(i, j).decode('utf-8')
  23. tag = tagger.y2(i)
  24. if tag == 'B':
  25. output_data.write(' ' + char)
  26. elif tag == 'M':
  27. output_data.write(char)
  28. elif tag == 'E':
  29. output_data.write(char + ' ')
  30. else:
  31. output_data.write(' ' + char + ' ')
  32. output_data.write('\n')
  33. input_data.close()
  34. output_data.close()
  35. if __name__ == '__main__':
  36. if len(sys.argv) != 4:
  37. print "Usage: python " + sys.argv[0] + " model input output"
  38. sys.exit(-1)
  39. crf_model = sys.argv[1]
  40. input_file = sys.argv[2]
  41. output_file = sys.argv[3]
  42. tagger = CRFPP.Tagger("-m " + crf_model)
  43. crf_segmenter(input_file, output_file, tagger)

转自:http://blog.csdn.net/u010189459/article/details/38546115

第四部分 分词结果

有了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%。

CRFPP 语言处理相关推荐

  1. Go语言的错误异常处理机制及其应用

    一.背景 在日常编写golang程序或阅读别人的golang代码时,我们总会看到如下的一堆代码块: xx, err = func(xx) if err != nil {//do sth. to tac ...

  2. Go 知识点(19)— Go 语言中的野指针

    野指针是一种指向内存位置是不可知的指针,一般是由于指针变量在声明时没有初始化所导致的.在 Go语言中,布尔类型的零值为 false,数值类型的零值为 0,字符串类型的零值为 "", ...

  3. gcc 自动识别的文件扩展名,gcc/g++ -x 选项指定语言,不同 gcc 版本 -std 编译选项支持列表

    对于执行 C 或者 C++ 程序,需要借助 gcc(g++)指令来调用 GCC 编译器. 对于以 .c 为扩展名的文件,GCC 会自动将其视为 C 源代码文件 对于以 .cpp 为扩展名的文件,GCC ...

  4. OpenCV 笔记(07)— Mat 对象输出格式设置(Python 格式、CSV 格式、NumPy 格式、C 语言格式)

    首先是下面代码中将要使用的 r 矩阵的定义.需要注意,我们可以通过用 randu 函数产生的随机值来填充矩阵, 需要给定一个上限和下限来确保随机值在期望的范围内. Mat r = Mat(2, 3, ...

  5. 利用牛顿法求平方根-Go语言实现

    牛顿法解释 百度的解释如下: 通俗的解释就是:多数方程不存在求根公式,牛顿提出了一种用迭代来求方程近似根的方法.思路就是不断取切线,用线性方程的根逼近非线性方程f(x)=0f(x)=0f(x)=0的根 ...

  6. 翻转二叉树 c语言实现 递归 栈 队列

    前言 题目比较好理解,就是翻转二叉树 代码 c语言实现 #include<stdio.h> #include<stdlib.h> #include<string.h> ...

  7. 字符串全排列的问题 python和c语言实现

    前言 这是一个的经典的问题 设计一个算法,输出一个字符串字符的全排列. 比如,String = "abc" 输出是"abc","bac",& ...

  8. 快速排序的递归和非递归实现 c语言版本

    代码 挖坑法 解释 选取一个关键字(key)作为枢轴,一般取整组记录的第一个数/最后一个,这里采用选取序列第一个数为枢轴,也是初始的坑位. 设置两个变量i = l;j = r;其中l = 0, r = ...

  9. c语言中字符串数组的地址存放以及%s输出单个字符导致程序崩溃的问题

    代码 总结下c语言中字符串数组的地址存放问题 #include <iostream> using namespace std; #include<bits/stdc++.h>i ...

最新文章

  1. 授权管理【学习笔记】《卓有成效的管理者》 第二章 掌握自己的时间
  2. 一文读懂AlphaGo背后的强化学习:它的背景知识与贝尔曼方程的原理
  3. 爬虫之selenium对cookie的处理
  4. php框架使用统计_2015 年最好的 PHP 框架调查统计
  5. ES6中的promise、async、await用法详解
  6. 一文读懂HTTP/2 及 HTTP/3特性
  7. 【温故知新】CSS学习笔记(背景)
  8. Spring AOP源码解析(一)——核心概念
  9. java swing 面试题_下面有关JAVA swing的描述,说法错误的是?
  10. Enterprise Library Step By Step系列(十一):异常处理应用程序块——入门篇
  11. VMware Horizon虚拟桌面工具箱2.0-审计,远程协助,控制台,电源
  12. java用中根后根序列构造二叉树,106. 从中序与后序遍历序列构造二叉树
  13. 毕业设计管理系统PHP,asp.net/net/c#毕业论文管理系统-成品
  14. 距离度量以及python实现(一)
  15. Netty解决TCP的粘包和分包(二)
  16. 【干货】400+页的《面向机器学习的数学》pdf
  17. Chrome打开cocos2d-html5官方Demo黑屏
  18. 一个简单的音乐网站项目
  19. QT--编写自己的TCP/IP上位机软件--demo
  20. VelocityTracker简单用法

热门文章

  1. 汽车油封在汽车行业的重要性
  2. NLP(十) 主题识别
  3. unity海边波浪效果的实现
  4. PR高清60帧补帧教学附插件下载,目前最流畅的补帧教学
  5. CORS和VRS的区别
  6. CondaSSLError——ProxyError
  7. 领带的打法 (附图)
  8. autocad 凸度_AutoCAD中凸度的概念以及求圆弧的凸度
  9. Task01:初识数据库与SQL-天池龙珠计划SQL训练营 笔记
  10. 苏州企业准备高企申报材料时,研发项目RD表这些你都了解吗