通过追加-t, –textmodel参数可以输出文本格式的CRF模型文件,通过该模型文本,可以加深对条件随机场的理解或为其他应用所利用。本文旨在介绍CRF++的文本模型格式,具体读取与解码将集成到HanLP中一并开源。
训练
语料
以BMES标注语料为例:
那 S 音 B 韵 E 如 S 轻 B 柔 E 的 S 夜 B 风 E , S 惊 S 溅 S 起 S 不 B 可 M 言 M 传 E 的 S 天 B 籁 E 。 S
注意字与标签之间的分隔符为制表符\t,否则会导致feature_index.cpp(86) [max_size == size] inconsistent column size错误。
特征模板
# 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
T**:%x[#,#]中的T表示模板类型,两个"#"分别表示相对的行偏移与列偏移。
一共有两种模板:
第一种是Unigram template:第一个字符是U,这是用于描述unigram feature的模板。每一行%x[#,#]生成一个CRFs中的点(state)函数: f(s, o), 其中s为t时刻的的标签(output),o为t时刻的上下文.如CRF++说明文件中的示例函数:
func1 = if (output = B and feature="U02:那") return 1 else return 0
它是由U02:%x[0,0]在输入文件的第一行生成的点函数.将输入文件的第一行"代入"到函数中,函数返回1,同时,如果输入文件的某一行在第1列也是“那”,并且它的output(第2列)同样也为B,那么这个函数在这一行也返回1。
第二种是Bigram template:第一个字符是B,每一行%x[#,#]生成一个CRFs中的边(Edge)函数:f(s', s, o), 其中s'为t – 1时刻的标签.也就是说,Bigram类型与Unigram大致机同,只是还要考虑到t – 1时刻的标签.如果只写一个B的话,默认生成f(s', s),这意味着前一个output token和current token将组合成bigram features。
命令行
使用下列命令可以得到一个model文件和一个model.txt文件,后者是本文的主要研究对象。
\..\crf_learn -f 3 -c 4.0 template pku_training.bmes.txt model -t
参数解释如下:
可选参数 -f, –freq=INT使用属性的出现次数不少于INT(默认为1) -m, –maxiter=INT设置INT为LBFGS的最大迭代次数 (默认10k) -c, –cost=FLOAT 设置FLOAT为代价参数,过大会过度拟合 (默认1.0) -e, –eta=FLOAT设置终止标准FLOAT(默认0.0001) -C, –convert将文本模式转为二进制模式 -t, –textmodel为调试建立文本模型文件 -a, –algorithm=(CRF|MIRA) 选择训练算法,默认为CRF-L2 -p, –thread=INT线程数(默认1),利用多个CPU减少训练时间 -H, –shrinking-size=INT 设置INT为最适宜的跌代变量次数 (默认20) -v, –version显示版本号并退出 -h, –help显示帮助并退出
输出
训练过程中会输出一些信息,其意义如下:
iter:迭代次数。当迭代次数达到maxiter时,迭代终止 terr:标记错误率 serr:句子错误率 obj:当前对象的值。当这个值收敛到一个确定值的时候,训练完成 diff:与上一个对象值之间的相对差。当此值低于eta时,训练完成
可见,如果希望训练快速结束,可以在命令行中适当减小maxiter值,增大eta值。
CRF模型格式
骨架
打开model.txt,其基本内容骨架如下:
version: 100 cost-factor: 1 maxid: 2159868 xsize: 1 B E M S 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] B 0 B 16 U00:- 20 U00:0 24 U00:1 28 U00:2 32 U00:3 36 U00:4 40 U00:5 44 U00:6 48 U00:7 52 U00:8 56 U00:9 60 U00:_B-1 64 U00:_B-2 …… 17404 U01:厨 17408 U01:去 17412 U01:县 17416 U01:参 17420 U01:又 17424 U01:叉 17428 U01:及 17432 U01:友 17436 U01:双 17440 U01:反 17444 U01:发 17448 U01:叔 17452 U01:取 17456 U01:受 …… 77800 U05:_B-1/一/个 107540 U05:一/方/面 107544 U05:一/无/所 107548 U05:一/日/三 107552 U05:一/日/为 107556 U05:一/日/之 …… 566536 U06:万/吨/_B+1 …… 2159864 U09:v/e -8.5354017525999719 9.0491453814148901 7.0388286231971700 -7.2545558164093009 5.2799470769112835 -8.5333633546653758 -5.3549190735606933 5.2575182675282477 -5.4259109736696054
接下来我会把上述骨架分解说明——
文件头
version: 100 cost-factor: 1 maxid: 2159868 xsize: 1
说明了模型的版本,通过-c参数指定的cost-factor,特征函数的最大id,xsize是特征维数,也就是训练语料列数-1。
值得注意的是maxid比我们从文本中观察到的最大id大了4,这是为什么呢?且听下文分解。
标签
B E M S
也就是最终的输出。
模板
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] B
训练时用到的模板。
特征函数
0 B 16 U00:- 20 U00:0 24 U00:1 28 U00:2 32 U00:3 36 U00:4 40 U00:5 44 U00:6 48 U00:7 52 U00:8 56 U00:9 60 U00:_B-1 64 U00:_B-2 …… 17404 U01:厨 17408 U01:去 17412 U01:县 17416 U01:参 17420 U01:又 17424 U01:叉 17428 U01:及 17432 U01:友 17436 U01:双 17440 U01:反 17444 U01:发 17448 U01:叔 17452 U01:取 17456 U01:受 …… 77800 U05:_B-1/一/个 107540 U05:一/方/面 107544 U05:一/无/所 107548 U05:一/日/三 107552 U05:一/日/为 107556 U05:一/日/之 …… 566536 U06:万/吨/_B+1 …… 2159864 U09:v/e
按照[id] [参数o]的格式排列,你可能会奇怪,f(s, o)应该接受两个参数才对。其实s隐藏起来了,注意到id不是连续的,而是隔了四个,这表示这四个标签(s=b|m|e|s)和公共的参数o组合成了四个特征函数。特别的,0-15为BEMS转移到BEMS的转移函数,也就是f(s', s, o=null)。
值得注意的是,_B-1表示句子第一个单词前面的一个单词,_B+1表示末尾后面的一个单词,你可以在最大熵的模型中找到类似的逻辑处理,依次类推。
特征函数权值
后面的小数依id顺序对应每个特征函数的权值。
9.0491453814148901 7.0388286231971700 -7.2545558164093009 5.2799470769112835 -8.5333633546653758 -5.3549190735606933 5.2575182675282477 -5.4259109736696054
关于解码
严格来讲,解码并不属于本文的范围,但是不说说解码的话,对特征函数权值的理解就仅仅限于“浮点数”这一表面。所以简要地说说解码,比如说我们有一个句子“商品和服务”,对于每个字都按照上述模板生成一系列U特征函数的参数代入,得到一些类似010101的函数返回值,乘上这些函数的权值求和,就得到了各个标签的分数,由大到小代表输出这些标签的可能性。
至于B特征函数(这里特指简单的f(s', s)),在Viterbi后向解码的时候,前一个标签确定了后就可以代入当前的B特征函数,计算出每个输出标签的分数,再次求和排序即可。
Reference
原文 http://www.hankcs.com/nlp/the-crf-model-format-description.html