CRF++进行中文分词实例

工具包:https://taku910.github.io/crfpp/#tips

语料http://sighan.cs.uchicago.edu/bakeoff2005/

安装:

1)下载linux版本CRF++包-----CRF++-0.58.tar.gz,并解压。

2)cd CRF++-0.58

3)./configure

4)sudo make 

5)sudo make install

若出现ImportError: libcrfpp.so.0: cannot open shared object file: No such file or directory 。
解决方法: ln -s /usr/local/lib/libcrfpp.so.0 /usr/lib/

第一步:准备训练语料

将backoff2005里的训练数据转化为CRF++所需的训练数据格式,采用4-tag( B(Begin,词首), E(End,词尾), M(Middle,词中), S(Single,单字词))标记集,处理utf-8编码文本。 原始训练集/icwb2-data/training/msr_training.utf8的形式是人工分好词的中文句子形式。如下:

“ 人们 常 说 生活 是 一 部 教科书 , 而 血 与 火 的 战争 > 更 是 不可多得 的 教科书 , 她 确实 是 名副其实 的 ‘ 我 的 > 大学 ’ 。
“ 心 静 渐 知 春 似 海 , 花 深 每 觉 影 生 香 。
“ 吃 屎 的 东西 , 连 一 捆 麦 也 铡 不 动 呀 ?
他 “ 严格要求 自己 , 从 一个 科举 出身 的 进士 成为 一个 伟> 大 的 民主主义 者 , 进而 成为 一 位 杰出 的 党外 共产主义 战 士 , 献身 于 崇高 的 共产主义 事业 。
“ 征 而 未 用 的 耕地 和 有 收益 的 土地 , 不准 荒芜 。
“ 这 首先 是 个 民族 问题 , 民族 的 感情 问题 。
’ 我 扔 了 两颗 手榴弹 , 他 一下子 出 溜 下去 。
“ 废除 先前 存在 的 所有制 关系 , 并不是 共产主义 所 独具 的 特征 。
“ 这个 案子 从 始 至今 我们 都 没有 跟 法官 接触 过 , 也 > 没有 跟 原告 、 被告 接触 过 。
“ 你 只有 把 事情 做好 , 大伙 才 服 你

 

根据如下的脚本 make_crf_train.py,将这个训练语料转换为CRF++训练用的语料格式(2列,4-tag):

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()  

 转化后如下:

“ S
人 B
们 E
常 S
说 S
生 B
活 E
是 S
一 S
部 S
教 B
科 M
书 E

 

第二步:训练模型

准备好训练语料,就可以利用crf的训练工具crf_learn来训练模型了,假设上述准备好的语料文件为:msr_training.tagging4crf.utf8

执行如下命令即可:
crf_learn -f 3 -c 4.0 ./template ./msr_training.tagging4crf.utf8 model   #执行此命令可以在安装文件外面新建一个文件夹进行,template是模板文件,model是训练完成后的model文件,只需要将模板训练数据放到新建的文件夹里面,执行此命令就在当前文件夹下训练并生成了model文件

有四个主要的参数可以调整:
    -a CRF-L2 or CRF-L1     
    规范化算法选择。默认是CRF-L2。一般来说L2算法效果要比L1算法稍微好一点,虽然L1算法中非零特征的数值要比L2中大幅度的小。
    -c float
    这个参数设置CRF的hyper-parameter。c的数值越大,CRF拟合训练数据的程度越高。这个参数可以调整过度拟合和不拟合之间的平衡度。这个参数可以通过交叉验证等方法寻找较优的参数。
    -f NUM
    这个参数设置特征的cut-off threshold。CRF++使用训练数据中至少NUM次出现的特征。默认值为1。当使用CRF++到大规模数据时,只出现一次的特征可能会有几百万,这个选项就会在这样的情况下起到作用。
    -p NUM
    如果电脑有多个CPU,那么那么可以通过多线程提升训练速度。NUM是线程数量。

 

模板文件如下:

# 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++用的测试语料,然后利用CRF++的测试工具crf_test进行字标注。原始的测试语料是icwb2-data/testing/msr_test.utf8 ,样例如下:

扬帆远东做与中国合作的先行
希腊的经济结构较特殊。
海运业雄踞全球之首,按吨位计占世界总数的17%。
另外旅游、侨汇也是经济收入的重要组成部分,制造业规模相对较小。
多年来,中希贸易始终处于较低的水平,希腊几乎没有在中国投资。
十几年来,改革开放的中国经济高速发展,远东在崛起。
瓦西里斯的船只中有40%驶向远东,每个月几乎都有两三条船停靠中国港口。
他感受到了中国经济发展的大潮。
他要与中国人合作。
他来到中国,成为第一个访华的大船主。

 

 这里我们同样提供一个python脚本 make_crf_test.py 对测试语料进行处理,将其转换为CRF++要求的格式(2列,B作为最后一列的占位符)

 

import codecs  
import sys  
  
def character_split(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():
        for word in line.strip():
            word = word.strip()
            if word:
                output_data.write(word + "\tB\n")
    input_data.close()
    output_data.close()

 转化后如下(注意中间不要有空行,否则标注结果全部为S):

扬 B
帆 B
远 B
东 B
做 B
与 B
中 B
国 B
 
假设上述测试语料为msr_test4crf.utf8,执行crf_test即可得到字标注结果:
crf_test -m ./crf_model  ./msr_test4crf.utf8 > msr_test4crf.tag.utf8
标注后样例如下:
扬 B B
帆 B E
远 B B
东 B E
做 B S
与 B S
中 B B
国 B E
合 B B
作 B E
 
第四步:将标注的词位信息转化为分词结果
import codecs
import sys
def character_2_word(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():
        if line == "\n":
            output_data.write("\n")
        else:
            char_tag_pair = line.strip().split('\t')
            char = char_tag_pair[0]
            tag = char_tag_pair[2]
            if tag == 'B':
                output_data.write(' ' + char)
            elif tag == 'M':
                output_data.write(char)
            elif tag == 'E':
                output_data.write(char + ' ')
            else: # tag == 'S'
                output_data.write(' ' + char + ' ')
    input_data.close()
    output_data.close()

 转化后如下:

扬帆 远东 做 与 中国 合作 的 先行
希腊 的 经济 结构 较 特殊 。
海运 业 雄踞 全球 之 首 , 按 吨 位 计 占 世界 总数 的 17% 。
另外 旅游 、 侨汇 也是 经济 收入 的 重要 组成部分 , 制造业 规模 相对 较小 。
多年来 , 中 希 贸易 始终 处于 较低 的 水平 , 希腊 几乎 没有 在 中国 投资 。
十几年 来 , 改革开放 的 中国 经济 高速 发展 , 远东 在 崛起 。
瓦西里斯 的 船只 中 有 40% 驶 向 远东 , 每个 月 几乎 都 有 两三条 船 停靠 中国 港口 。
他 感受 到 了 中国 经济 发展 的 大潮 。
他 要 与 中国人 合作 。
他 来到 中国 , 成为 第一个 访 华 的 大船 主

最后:评估一下分词效果

有了这个CRF字标注分词结果,我们就可以利用backoff2005的测试脚本来测一下这次分词的效果了:
./icwb2-data/scripts/score ./icwb2-data/gold/msr_training_words.utf8 ./icwb2-data/gold/msr_test_gold.utf8 msr_test4crf.tag2word.utf8 > msr_crf_segment.score
结果如下:
=== SUMMARY:
=== TOTAL INSERTIONS: 1412
=== TOTAL DELETIONS: 1305
=== TOTAL SUBSTITUTIONS: 2449
=== TOTAL NCHANGE: 5166
=== TOTAL TRUE WORD COUNT: 106873
=== TOTAL TEST WORD COUNT: 106980
=== TOTAL TRUE WORDS RECALL: 0.965
=== TOTAL TEST WORDS PRECISION: 0.964
=== F MEASURE: 0.964
=== OOV Rate: 0.026
=== OOV Recall Rate: 0.647
=== IV Recall Rate: 0.974
### msr_test4crf.tag2word.utf8 1412 1305 2449 5166 106873 106980 0.965 0.964 0.964 0.026 0.647 0.974
这次我们获得了一个准确率,召回率以及F值都在96%以上的结果,相对于前面几节的测试结果,这个CRF字标注分词结果还相对不错。

 

上面测试阶段略微繁琐一些,下面程序直接输入测试语料然后直接输出分词结果:

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)  

 只需执行“python crf_segmenter.py crf_model  ./icwb2-data/testing/msr_test.utf8  msr_test.seg.utf8”即可得到与前面几步得到的分词结果完全一致的CRF分词结果:msr_test.seg.utf8 。

参考连接:http://www.52nlp.cn/中文分词入门之字标注法4#comments

模板格式说明参考http://www.hankcs.com/nlp/the-crf-model-format-description.html

posted @ 2018-12-02 15:52  光彩照人  阅读(5386)  评论(1编辑  收藏  举报