放牧代码和思想
专注自然语言处理、机器学习算法
    正处于一个非常忙的阶段,抱歉不会经常回应任何联络

CRF++模型格式说明

目录

通过追加-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

CRF++工具包使用介绍.ppt

http://www.zhihu.com/question/20279019

知识共享许可协议 知识共享署名-非商业性使用-相同方式共享码农场 » CRF++模型格式说明

分享到:更多 ()

评论 43

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
  1. #22

    博主好!为什么我用CRF++-0.58版本,训练输出的模型不是按id排序呢?而如果非id排序,则不能用于您的hanlp!看您源码是默认认为是排序了的!

    leo2个月前 (04-18)回复
    • 看到有人问了,不好意思,忽略掉我的问题吧!

      leo2个月前 (04-18)回复
  2. #21

    我今天也遇到了同样的问题,我看代码里写先从bin 读模型文件,然道还是要以txt的方式去读取。

    陈军9个月前 (09-29)回复
  3. #20

    func1 = if (output = B and feature="U02:那") return 1 else return 0
    它是由U02:%x[0,0]在输入文件的第一行生成的点函数.将输入文件的第一行"代入"到函数中,函数返回1,同时,如果输入文件的某一行在第1列也是“那”,并且它的output(第2列)同样也为B,那么这个函数在这一行也返回1。
    博主 上面给的语料库里“那”不是对应的S么 怎么这里是B? 不太理解

    高玮立12个月前 (07-12)回复
    • 应该是生成output数量的func 。本例中还生成了func2= if (output = M and feature="U02:那") return 1 else return 0; func3= if (output = E and feature="U02:那") return 1 else return 0; func4= if (output = S and feature="U02:那") return 1 else return 0; 个人理解,博主省略了

      落叶无忧Jt10个月前 (08-17)回复
  4. #19

    您好 为什么我这儿生成的model里,下面特征函数的排列的ID号是乱的呢,长成这样:

    B

    300 B
    6264 U00:10
    426 U00:2
    5235 U00:30
    1992 U00:5
    7140 U00:A205门口
    3519 U00:B座
    8400 U00:D栋405
    60 U00:_B-1

    李二2年前 (2015-12-06)回复
    • 版本不同,自己按id排序就行了

      hankcs2年前 (2015-12-06)回复
  5. #18

    博主,打扰一下! 我想向您确认一件事情,就是您在1.2.7中的CRF分词这一块,如果用户enableCustomDictionary(trued)的话,才会去执行 猜词性 。反之的话,如果不做修改都是 null 是吧? 

    _彩虹之上的幻城_2年前 (2015-11-27)回复
    • enableCustomDictionary(trued)的话,选择词频最高的词性,com.hankcs.hanlp.seg.Segment#enablePartOfSpeechTagging后用HMM标注词性

      hankcs2年前 (2015-11-28)回复
  6. #17

    博主博主–我又双叒叕来了: 关于CRF模型的,我是先用CRF++ 按照BEMS 的标签训练好了模型,然后也可以成功使用CRF++的接口来分词,现在想用您的CRF来分词,主要识别地点新词, 结果替换模型之后, 报如下异常:
    Exception in thread “main” java.lang.OutOfMemoryError: Requested array size exceeds VM limit
    at com.hankcs.hanlp.model.crf.CRFModel.load(CRFModel.java:334), 但是模型只有几M的大小。

    博主能不能指点一下,是模型的问题,还是hanlp的CRF 对模型有特殊要求。

    _彩虹之上的幻城_2年前 (2015-11-26)回复
  7. #16

    对,分词模型直接替换配置中的CRFSegmentModelPath,其他模型调用com.hankcs.hanlp.model.crf.CRFModel#load(java.lang.String)

    hankcs2年前 (2015-11-10)回复
  8. #15

    您是怎么使用自己训练的模型呢?能不能指点一下?

    _彩虹之上的幻城_2年前 (2015-11-10)回复
  9. #14

    博主您好!问一个入门的问题:就是您这里面crf模型文件model.txt , 是位与data/model/CRFSegment.txt.bin的吗?那我想用自己的CRF训练好的model,应该怎么使用crf++操作呢?

    _彩虹之上的幻城_2年前 (2015-11-10)回复
  10. #13

    问一下博主,双精度trie树是受CRF++的启发吗?

    落鸿先生_kvr2年前 (2015-11-10)回复
  11. #12

    [吃惊]

    hankcs2年前 (2015-11-10)回复
  12. #11

    博主好坏,我以为你写的CRFModel是直接匹配CRF++的,原来你先做了一次转化。

    落鸿先生_kvr2年前 (2015-11-10)回复
  13. #10

    的确,这是最精确的解释。

    hankcs2年前 (2015-11-10)回复
  14. #9

    我说错了,应该就是直接等于特征维数。这里用CRF做分词,只使用了字符本身,所以xsize=1;最后一列是标签(SBME),所以总列数是2.

    落鸿先生_kvr2年前 (2015-11-10)回复
  15. #8

    这么说也是对的

    hankcs2年前 (2015-11-08)回复
  16. #7

    应该是训练语料列数-1,特征就多了

    hankcs2年前 (2015-11-06)回复
  17. #6

    xsize=特征个数-1;

    落鸿先生_kvr2年前 (2015-11-06)回复
  18. #5

    请问在HanLP的model中怎么没有CRFSegmentModel.txt呢?可不可以提供一份呢?我想通过通过CRFSegment的调用学习一下CRF的使用。

    starkingout2年前 (2015-08-23)回复
    • 这是CRF++的原始模型文件,转了格式就删了。你可以自己训练一个。

      hankcs2年前 (2015-08-24)回复
  19. #4

    博主你好,请问你在训练的时候-c参数指定的不是4.0吗,为什么模型文件里的cost-factor还是1

    RavenChaser2年前 (2015-08-12)回复
    • 好问题,我要去源码里看看才能知道为什么。

      hankcs2年前 (2015-08-13)回复
  20. #3

    xsize 是训练语料列数-1

    南昌小猴子2年前 (2015-06-07)回复
  21. #2

    可以告诉我怎么用条件随机场自己添加特征吗?

    刘璟2年前 (2015-05-26)回复
  22. #1

    博主好,能给我几份CRF标注好的语料库吗,谢谢。82725411@qq.com

    刚子2年前 (2015-04-13)回复

我的开源项目

HanLP自然语言处理包基于DoubleArrayTrie的Aho Corasick自动机