前段时间写了中文分词的一些记录​里面提到了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] 

  1. #!/usr/bin/python  

  2. # -*- coding: utf-8 -*-  



  3. import sys  



  4. #home_dir = "D:/source/NLP/people_daily//"  

  5. home_dir = "/home/lhb/workspace/CRF_data/"  
  6. def splitWord(words):  
  7.     uni = words.decode('utf-8')  
  8.     li = list()      
  9.     for u in uni:  
  10.     li.append(u.encode('utf-8'))  
  11.     return li     


  12. #4 tag  

  13. #S/B/E/M  
  14. def get4Tag(li):  
  15.     length = len(li)  
  16.     #print length  
  17.     if length   == 1:  
  18.     return ['S']  
  19.     elif length == 2:  
  20.     return ['B','E']  
  21.     elif length > 2:  
  22.     li = list()  
  23.     li.append('B')  
  24.     for i in range(0,length-2):  
  25.         li.append('M')  
  26.     li.append('E')  
  27.     return li  
  28. #6 tag  
  29. #S/B/E/M/M1/M2  
  30. def get6Tag(li):  
  31.     length = len(li)  
  32.     #print length  
  33.     if length   == 1:  
  34.     return ['S']  
  35.     elif length == 2:  
  36.     return ['B','E']  
  37.     elif length == 3:  
  38.     return ['B','M','E']  
  39.     elif length == 4:  
  40.     return ['B','M1','M','E']  
  41.     elif length == 5:  
  42.     return ['B','M1','M2','M','E']  
  43.     elif length > 5:  
  44.     li = list()  
  45.     li.append('B')  
  46.     li.append('M1')  
  47.     li.append('M2')  
  48.     for i in range(0,length-4):  
  49.         li.append('M')  
  50.     li.append('E')  
  51.     return li  

  52. def saveDataFile(trainobj,testobj,isTest,word,handle,tag):  
  53.     if isTest:  
  54.     saveTrainFile(testobj,word,handle,tag)  
  55.     else:  
  56.     saveTrainFile(trainobj,word,handle,tag)  

  57. def saveTrainFile(fiobj,word,handle,tag):   
  58.     if len(word) > 0:  
  59.     wordli = splitWord(word)  
  60.     if tag == '4':  
  61.         tagli = get4Tag(wordli)  
  62.     if tag == '6':  
  63.         tagli = get6Tag(wordli)  
  64.     for i in range(0,len(wordli)):  
  65.         w = wordli[i]  
  66.         h = handle  
  67.         t = tagli[i]  
  68.         fiobj.write(w + '/t' + h + '/t' + t + '/n')  
  69.     else:  
  70.     #print 'New line'  
  71.     fiobj.write('/n')  

  72. #B,M,M1,M2,M3,E,S  
  73. def convertTag(tag):      
  74.     fiobj    = open( home_dir + 'people-daily.txt','r')  
  75.     trainobj = open( home_dir + tag + '.train.data','w' )  
  76.     testobj  = open( home_dir + tag + '.test.data','w')  

  77.     arr = fiobj.readlines()  
  78.     i = 0  
  79.     for a in arr:  
  80.     i += 1  
  81.     a = a.strip('/r/n/t ')  
  82.     words = a.split(' ')  
  83.     test = False  
  84.     if i % 10 == 0:  
  85.         test = True  
  86.     for word in words:  
  87.         word = word.strip('/t ')  
  88.         if len(word) > 0:          
  89.         i1 = word.find('[')  
  90.         if i1 >= 0:  
  91.             word = word[i1+1:]  
  92.         i2 = word.find(']')  
  93.         if i2 > 0:  
  94.             word = word[:i2]  
  95.         word_hand = word.split('/')  
  96.         w,h = word_hand  
  97.         #print w,h  
  98.         if h == 'nr':    #ren min  
  99.             #print 'NR',w  
  100.             if w.find('·') >= 0:  
  101.             tmpArr = w.split('·')  
  102.             for tmp in tmpArr:  
  103.                 saveDataFile(trainobj,testobj,test,tmp,h,tag)  
  104.             continue  
  105.         if h != 'm':  
  106.             saveDataFile(trainobj,testobj,test,w,h,tag)  

  107.         if h == 'w':  
  108.             saveDataFile(trainobj,testobj,test,"","",tag) #split  

  109.     trainobj.flush()  
  110.     testobj.flush()  

  111. if __name__ == '__main__':      
  112.     if len(sys.argv) < 2:  
  113.     print 'tag[6,4] convert raw data to train.data and tag.test.data'  
  114.     else:  
  115.     tag = sys.argv[1]  
  116.     convertTag(tag)  



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

python ./peopledata.py 6


90%数据作为训练数据,10%的数据作为测试数据,生成的文件如:

6.test.data

6.train.data


模板文件的写法如下

template:

[python] ​

  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. # Bigram  
  9. B  




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


4 执行和结果查看 

6exec.sh文件

[python] ​​​


  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. #./crf_learn -a MIRA -f 3 template train.data model  
  6. #./crf_test -m model test.data  
  7. #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]

  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] 


  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. # Bigram  
  15. B  




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



[python] 


  1. #!/usr/bin/python  
  2. # -*- coding: utf-8 -*-  

  3. import sys  

  4. if __name__=="__main__":  
  5.     try:  
  6.         file = open(sys.argv[1], "r")  
  7.     except:  
  8.         print "result file is not specified, or open failed!"  
  9.         sys.exit()  

  10.     wc_of_test = 0  
  11.     wc_of_gold = 0  
  12.     wc_of_correct = 0  
  13.     flag = True  

  14.     for l in file:  
  15.         if l=='/n': continue  

  16.         _, _, g, r = l.strip().split()  

  17.         if r != g:  
  18.             flag = False  

  19.         if r in ('E', 'S'):  
  20.             wc_of_test += 1  
  21.             if flag:  
  22.                 wc_of_correct +=1  
  23.             flag = True  

  24.         if g in ('E', 'S'):  
  25.             wc_of_gold += 1  

  26.     print "WordCount from test result:", wc_of_test  
  27.     print "WordCount from golden data:", wc_of_gold  
  28.     print "WordCount of correct segs :", wc_of_correct  

  29.     #查全率  
  30.     P = wc_of_correct/float(wc_of_test)  
  31.     #查准率,召回率  
  32.     R = wc_of_correct/float(wc_of_gold)  

  33.     print "P = %f, R = %f, F-score = %f" % (P, R, (2*P*R)/(P+R))