CRF++中文分词使用指南

http://blog.csdn.net/marising/article/details/5769653

前段时间写了中文分词的一些记录里面提到了CRF的分词方法,近段时间又研究了一下,特把方法写下来,以备忘,另外,李沫南同学优化过CRF++,见:http://www.coreseek.cn/opensource/CRF/。我觉得CRF++还有更大的优化空间,以后有时间再搞。

1 下载和安装

CRF的概念,请google,我就不浪费资源啦。官方地址如下:http://crfpp.sourceforge.net/

我用的是Ubutnu,所以,下载的是源码:http://sourceforge.net/projects/crfpp/files/ 下载CRF++-0.54.tar.gz

没有gcc/g++/make请安装
% ./configure 
% make
% sudo make install

2 测试和体验 
在源码包中有example,可以执行./exec.sh体验一下
exec.sh   #训练和测试脚本
template #模板文件
test.data #测试文件
train.data #训练文件
可以打开看看


3 语料整理和模板编写 

我采用的是6Tag和6Template的方式
S,单个词;B,词首;E,词尾;M1/M2/M,词中


1个字的词:
和 S
2个字的词(注意是实际上是一个字一行,我为了排版,改为横排的了):
中 B 国 E
3个字的词:
进 B 一 M 步 E
5个字的词:
发 B 展 M1 中 M2 国 M 家 E
跟多字的词
中 B 华 M1 人 M2 民 M 共 M 和 M国 E
标点符号作为单词(S表示)

bamboo 项目中下载:people-daily.txt.gz
pepoledata.py文件

[python] view plaincopy
 
  1. #!/usr/bin/python  
  2.   
  3. # -*- coding: utf-8 -*-  
  4.   
  5.   
  6.   
  7. import sys  
  8.   
  9.   
  10.   
  11. #home_dir = "D:/source/NLP/people_daily//"  
  12.   
  13. home_dir = "/home/lhb/workspace/CRF_data/"  
  14. def splitWord(words):  
  15.     uni = words.decode('utf-8')  
  16.     li = list()      
  17.     for u in uni:  
  18.     li.append(u.encode('utf-8'))  
  19.     return li     
  20.   
  21.   
  22. #4 tag  
  23.   
  24. #S/B/E/M  
  25. def get4Tag(li):  
  26.     length = len(li)  
  27.     #print length  
  28.     if length   == 1:  
  29.     return ['S']  
  30.     elif length == 2:  
  31.     return ['B','E']  
  32.     elif length > 2:  
  33.     li = list()  
  34.     li.append('B')  
  35.     for i in range(0,length-2):  
  36.         li.append('M')  
  37.     li.append('E')  
  38.     return li  
  39. #6 tag  
  40. #S/B/E/M/M1/M2  
  41. def get6Tag(li):  
  42.     length = len(li)  
  43.     #print length  
  44.     if length   == 1:  
  45.     return ['S']  
  46.     elif length == 2:  
  47.     return ['B','E']  
  48.     elif length == 3:  
  49.     return ['B','M','E']  
  50.     elif length == 4:  
  51.     return ['B','M1','M','E']  
  52.     elif length == 5:  
  53.     return ['B','M1','M2','M','E']  
  54.     elif length > 5:  
  55.     li = list()  
  56.     li.append('B')  
  57.     li.append('M1')  
  58.     li.append('M2')  
  59.     for i in range(0,length-4):  
  60.         li.append('M')  
  61.     li.append('E')  
  62.     return li  
  63.   
  64. def saveDataFile(trainobj,testobj,isTest,word,handle,tag):  
  65.     if isTest:  
  66.     saveTrainFile(testobj,word,handle,tag)  
  67.     else:  
  68.     saveTrainFile(trainobj,word,handle,tag)  
  69.   
  70. def saveTrainFile(fiobj,word,handle,tag):   
  71.     if len(word) > 0:  
  72.     wordli = splitWord(word)  
  73.     if tag == '4':  
  74.         tagli = get4Tag(wordli)  
  75.     if tag == '6':  
  76.         tagli = get6Tag(wordli)  
  77.     for i in range(0,len(wordli)):  
  78.         w = wordli[i]  
  79.         h = handle  
  80.         t = tagli[i]  
  81.         fiobj.write(w + '/t' + h + '/t' + t + '/n')  
  82.     else:  
  83.     #print 'New line'  
  84.     fiobj.write('/n')  
  85.   
  86. #B,M,M1,M2,M3,E,S  
  87. def convertTag(tag):      
  88.     fiobj    = open( home_dir + 'people-daily.txt','r')  
  89.     trainobj = open( home_dir + tag + '.train.data','w' )  
  90.     testobj  = open( home_dir + tag + '.test.data','w')  
  91.   
  92.     arr = fiobj.readlines()  
  93.     i = 0  
  94.     for a in arr:  
  95.     i += 1  
  96.     a = a.strip('/r/n/t ')  
  97.     words = a.split(' ')  
  98.     test = False  
  99.     if i % 10 == 0:  
  100.         test = True  
  101.     for word in words:  
  102.         word = word.strip('/t ')  
  103.         if len(word) > 0:          
  104.         i1 = word.find('[')  
  105.         if i1 >= 0:  
  106.             word = word[i1+1:]  
  107.         i2 = word.find(']')  
  108.         if i2 > 0:  
  109.             word = word[:i2]  
  110.         word_hand = word.split('/')  
  111.         w,h = word_hand  
  112.         #print w,h  
  113.         if h == 'nr':    #ren min  
  114.             #print 'NR',w  
  115.             if w.find('·') >= 0:  
  116.             tmpArr = w.split('·')  
  117.             for tmp in tmpArr:  
  118.                 saveDataFile(trainobj,testobj,test,tmp,h,tag)  
  119.             continue  
  120.         if h != 'm':  
  121.             saveDataFile(trainobj,testobj,test,w,h,tag)  
  122.           
  123.         if h == 'w':  
  124.             saveDataFile(trainobj,testobj,test,"","",tag) #split  
  125.   
  126.     trainobj.flush()  
  127.     testobj.flush()  
  128.   
  129. if __name__ == '__main__':      
  130.     if len(sys.argv) < 2:  
  131.     print 'tag[6,4] convert raw data to train.data and tag.test.data'  
  132.     else:  
  133.     tag = sys.argv[1]  
  134.     convertTag(tag)  


下载下来并解压,然后用脚本整理数据,注意home_dir改为语料的目录:
python ./peopledata.py 6

90%数据作为训练数据,10%的数据作为测试数据,生成的文件如:
6.test.data
6.train.data

模板文件的写法如下
template:

[python] view plaincopy
 
  1. # Unigram  
  2. U00:%x[-1,0]  
  3. U01:%x[0,0]  
  4. U02:%x[1,0]  
  5. U03:%x[-1,0]/%x[0,0]  
  6. U04:%x[0,0]/%x[1,0]  
  7. U05:%x[-1,0]/%x[1,0]  
  8.   
  9. # Bigram  
  10. B  



%x[row,column]代表的是行和列,[-1,0]表示前1个字的第1列,[0,0]当前字的第1列,[1,0]后1个字的第1列

4 执行和结果查看 
6exec.sh文件

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



WordCount from test result: 109805
WordCount from golden data: 109948
WordCount of correct segs : 106145
P = 0.966668, R = 0.965411, F-score = 0.966039 

5 调整Tag和模板
4 Tag S/B/M/E 比 6Tag 去掉了M1和M2
python ./peopledata.py 4
4exec.sh文件为

[python] view plaincopy
 
  1. #!/bin/sh  
  2. ./crf_learn -f 3 -c 4.0 template 4.train.data 4.model > 4.train.rst  
  3. ./crf_test -m 4.model 4.test.data > 4.test.rst  
  4. ./crfeval.py 4.test.rst  


4Tag的效果为
lhb@localhost:~/workspace/CRF_data$ ./crfeval.py 4.test.rst 
ordCount from test result: 109844
WordCount from golden data: 109948
WordCount of correct segs : 105985
P = 0.964868, R = 0.963956, F-score = 0.964412

6Tag的效果比4Tag有细微的差距,当然是6Tag好。


6Tag 训练时间为
10062.00s
4tag的训练时间为
4208.71s

6Tag的标注方法差异

1)把M放在E之前:
发 B 展 M1 中 M2 国 M 家 E
2)把M放在B后
发 B 展 M 中 M1 国 M2 家 E
3)把M放在M1和M2之间:
发 B 展 M1 中 M 国 M2 家 E
第1种方式效果最好,有细微的差距。
template的编写

我尝试过12行模板的编写,把词性作为一个计算因素,但是速度实在是很慢,没跑完,我就关机了。效果应该比6 template要好,可以尝试以下。

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

有某位同学问我要crfeval.py文件,特放出如下:

[python] view plaincopy
 
  1. #!/usr/bin/python  
  2. # -*- coding: utf-8 -*-  
  3.   
  4. import sys  
  5.   
  6. if __name__=="__main__":  
  7.     try:  
  8.         file = open(sys.argv[1], "r")  
  9.     except:  
  10.         print "result file is not specified, or open failed!"  
  11.         sys.exit()  
  12.       
  13.     wc_of_test = 0  
  14.     wc_of_gold = 0  
  15.     wc_of_correct = 0  
  16.     flag = True  
  17.       
  18.     for l in file:  
  19.         if l=='/n': continue  
  20.       
  21.         _, _, g, r = l.strip().split()  
  22.        
  23.         if r != g:  
  24.             flag = False  
  25.       
  26.         if r in ('E', 'S'):  
  27.             wc_of_test += 1  
  28.             if flag:  
  29.                 wc_of_correct +=1  
  30.             flag = True  
  31.       
  32.         if g in ('E', 'S'):  
  33.             wc_of_gold += 1  
  34.   
  35.     print "WordCount from test result:", wc_of_test  
  36.     print "WordCount from golden data:", wc_of_gold  
  37.     print "WordCount of correct segs :", wc_of_correct  
  38.               
  39.     #查全率  
  40.     P = wc_of_correct/float(wc_of_test)  
  41.     #查准率,召回率  
  42.     R = wc_of_correct/float(wc_of_gold)  
  43.       
  44.     print "P = %f, R = %f, F-score = %f" % (P, R, (2*P*R)/(P+R))  
原文地址:https://www.cnblogs.com/DjangoBlog/p/4201507.html