While a Hidden Markov Model is a sequential extension to the Nave Bayes Model, Conditional Random Fields can be understood as a sequential extension to the Maximum Entropy Model.
不過我們還是要從安裝CRF++工具包說起,在Linux或者Mac OS系統(tǒng)下,下載C++源代碼安裝包(這里用的是 CRF++-0.58.tar.gz )之后,依然是 “configure & make & (sudo) make install",安裝完畢之后,可以cd python進入到其同樣用SWIG生成的Python工具包下,安裝python包:python setup.py build & (sudo) python setup.py install。安裝完畢之后,可以在python解釋器下測試,是否能成功import CRFPP,如果ok,則準(zhǔn)備工作就緒。
上一節(jié)我們利用最大熵模型工具包里自帶的詞性標(biāo)注工具進行的中文分詞,稍微有些曲折,這一節(jié)我們依然利用CRF++ example里的樣例進行測試,不過好處是,CRF++ example里有個seg目錄,這個seg目錄對應(yīng)的是一個日文分詞的樣例,正好可以套用到我們的中文分詞中來。在安裝包目錄下,cd example, cd seg目錄后,有4個文件:
有了這4個文件,我們可以做得事情就比較簡單,只要按測試集,訓(xùn)練集的格式準(zhǔn)備數(shù)據(jù)就可以了,特征模板和執(zhí)行腳本可以套用,不過這里簡單解讀一下這幾個CRF++文件。首先來看訓(xùn)練集:
毎 k B
日 k I
新 k I
聞 k I
社 k I
特 k B
別 k I
顧 k B
問 k I
4 n B
關(guān)于CRF++中特征模板的說明和舉例,請大家參考官方文檔上的“Preparing feature templates”這一節(jié),而以下部分的說明拿上述日文分詞數(shù)據(jù)舉例。在特征模板文件中,每一行(如U00:%x[-2,0])代表一個特征,而宏“%x[行位置,列位置]”則代表了相對于當(dāng)前指向的token的行偏移和列的絕對位置,以上述訓(xùn)練集為例,如果當(dāng)前掃描到“新 k I”這一行,
毎 k B
日 k I
新 k I <== 掃描到這一行,代表當(dāng)前位置
聞 k I
社 k I
特 k B
別 k I
顧 k B
問 k I
4 n B
CRF++里將特征分成兩種類型,一種是Unigram的,“U”起頭,另外一種是Bigram的,“B”起頭。對于Unigram的特征,假如一個特征模板是"U01:%x[-1,0]", CRF++會自動的生成一組特征函數(shù)(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
最后我們再來看一下執(zhí)行腳本:
#!/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
執(zhí)行腳本告訴了我們?nèi)绾斡?xùn)練一個CRF模型,以及如何利用這個模型來進行測試,執(zhí)行這個腳本之后,對于輸入的測試集,輸出結(jié)果多了一列:
よ 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
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()
只需要執(zhí)行“python make_crf_train_data.py ./icwb2-data/training/msr_training.utf8 msr_training.tagging4crf.utf8” 即可得到CRF++要求的格式的訓(xùn)練文件msr_training.tagging4crf.utf8,樣例如下:
“ S
人 B
們 E
常 S
說 S
生 B
活 E
是 S
一 S
部 S
...
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")
output_data.write("\n")
input_data.close()
output_data.close()
執(zhí)行“python make_crf_test_data.py ./icwb2-data/testing/msr_test.utf8 msr_test4crf.utf8”即可得到可用于CRF++測試的測試語料msr_test4crf.utf8,樣例如下:
揚 B
帆 B
遠 B
東 B
做 B
與 B
中 B
國 B
合 B
作 B
...
=== 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
這次我們獲得了一個準(zhǔn)確率,召回率以及F值都在96%以上的結(jié)果,相對于前面幾節(jié)的測試結(jié)果,這個CRF字標(biāo)注分詞結(jié)果還相對不錯。不過是不是感覺上面的步驟有些繁瑣,有沒有一次到位的CRF分詞器,這里我們同樣提供一個CRF分詞腳本 crf_segmenter.py ,利用CRF++的python工具包,做到一次輸入,一次輸出:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: 52nlpcn@gmail.com
# Copyright 2014 @ YuZhen Technology
#
# 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: # tag == 'S'
output_data.write(' ' + char + ' ')
output_data.write('\n')
input_data.close()
output_data.close()