CRF学习完了,但是理论不结合实践总是无法出真知的。使用CRF进行命名实体识别有很多方法,比如CRF++工具包、sklearn包中的crfsuite套装、keras框架、tensorflow框架、pytorch框架等等,下面总结借助CRF++工具包实现的几点心得。
一、CRF++工具包的安装与配置
1.CRF++:Yet Another CRF toolkit
CRF++是基于C++开发、可自定义特征集、基于LBFGS快速训练等等高效特征的CRF开源工具包。用于对序列数据进行分割和标记,主要用于NLP任务,例如命名实体识别、信息提取和序列标注等任务。
2.安装
截至目前更新的最新版为2013-02-13的CRF0.58版本。Windows下的安装很简单,只需要下载下来解压即可。解压包中的文件如图1所示。实际上使用的文件主要有CRF++的训练程序(crf_learn.exe)文件、CRF++的测试程序(crf_test.exe)文件和训练程序和测试程序需要使用的静态链接库(libcrfpp.dll)三个文件。
图表 1 CRF++0.58解压后文件目录
以上图中各个文件夹内容:
- doc文件夹:官方主页的html内容。
- example文件夹:分别有四个任务:基础任务英文命名实体识别basenp、英文分词chunking、日文分词seg、日文实体识别JapaneseNE,每个人物对应的训练数据(train.data)、测试数据(test.data)、模板文件(template)和执行脚本文件(exec.sh)。
- sdk文件夹:CRF++的头文件和静态链接库。
3.命令行使用
(1)训练(encoding):crf_learn template_file train_file model_file >> train_info_file。
即训练需要准备template_file、train_file两个文件,输出model_file文件。训练有以下几个参数:
- -a CRF-L2/CRF-L1/MIRA:规范化算法选择,默认是CRF-L2。一般情况L2算法要比L1算法稍微好一点,虽然L1算法中非零特征的数值要比L2中大幅度的小。在0.45版本后的CRF++,还支持single-best MIRA训练算法。
- -c float:设置CRF的hyper-parameter。默认为1.0,c的数值越大,CRF拟合训练数据的程度越高。这个参数可以调整过度拟合和不拟合之间的平衡度,通过交叉验证等方法寻找较优的参数。
- -f NUM:设置特征的cut-off threshold。CRF++使用训练数据中至少NUM次出现的特征。默认值为1,当使用CRF++到大规模数据时,只出现一次的特征可能会有几百万,这个选项就会在这样的情况下起到作用。
- -p NUM:多核CPU,通过多线程提升训练速度。NUM是线程数量。
- -t:同时生成文本格式的模型,用于调试。
- -e float:设置停止条件的阈值,float是一个大于0的浮点数。
- -v:显示版本并退出程序。
- -m NUM:设置LBFGS的最大迭代次数,NUM是一个整数,默认为10K。
训练时输出内容的参数:
- iter:表示迭代次数
- terr:表示标记的训练错误率,等于标记的训练错误数量/标记的总数。
- serr:表示sentence的训练错误率,等于sentence的训练错误数量/sentence的总数。
- obj:当前的目标函数值。当目标函数值收敛到某个固定值时,CRF++停止迭代。
- diff:目标函数值的相对变化。它等于当前的目标函数值减去上一个目标函数值。
(2)测试:crf_test -m model_file test_files >> result_file。
此处有两个参数:-v和-n。-v用来预测标签概率值,默认值为0,级别越高输出的内容越多;-n NUM显示不同可能序列的概率值。eg. crf_test -v0 -n 20 -m model test.data
二、实验
1.准备训练文件
(1)训练测试文件格式
- 文件由很多token组成,每个token占据一行,包含固定的字段。
- 所有token的字段数量相等,字段的数量没有限制,字段之间用空白分割(空格符或者tab符)。
- 每个字段通常表示某种含义。如:第一列表示单词,第二列表示词性,第三列表示属性等等……h
- 一个sentence由多个token表述,sentence之间通过空行来区分边界。
- 训练文件中,最后一个字段必须是标记,将作为CRF++训练的目标。
(2)模板文件
CRF++使用模板文件来生成特征。模板文件需要用户编写,从而指定需要生成哪些特征。文件中的每一行定义了一个特征模板,以#开头的行是注释行,空行也被认为是注释行而被剔除。有两种类型的特征模板,通过特征模板的第一个字符区分。
- 宏语句:特征模板中使用的宏语句%x[row,col],%x是固定的,宏语句的引导字符。row是一个整数,指定了相对于当前的数据行的行数,col是一个整数,指定了采用第几个字段(从0开始编号)。
- Unigram特征模板:模板的第一个字符串为U,这种特征模板用于描述unigram特征。
给定一个Unigram特征模板U01:%x[0,1],它会生成M个特征函数,其中M为训练数据的行数(剔除空行,因为空行是sentence的分隔符)。每个特征函数为:
“““
func1 = if (output = LABEL1 and feature = “U01:xx1”) return 1 else return 0
func2 = if (output = LABEL2 and feature = “U01:xx2”) return 1 else return 0
func3 = if (output = LABEL3 and feature = “U01:xx3”) return 1 else return 0
……
funcM = if (output = LABELM and feature = “U01:xxM”) return 1 else return 0
”””
其中LABEL1,……,LABELM是训练文件中每一行的标记,feature=“U01:xx1”,……,feature="U01:xxM"是训练文件中,每一行由U01:%x[0,1]指定的,从该行提取到的特征。
需要注意的是,上述生成的特征函数会有大量重复,假设标记的种类一共有L个,由U01:%x[0,1]指定,从该行提取到的特征的种类一共有N个,则特征函数的种类一共有L×N个。CRF++会按照L中标记,N中特征来自动生成L×N个特征函数。
- Bigram特征模型:模板的第一个字符串为B,这种特征模板用于描述bigram特征。
给定一个Bigram特征模板B01:%x[0,1],它会生成M个特征函数,其中M为训练数据的行数(剔除空白行,因为空白行是sentence的分隔符)。每个特征函数为
“““
func2 = if (output = LABEL2/LABEL1 and feature="B01:xx2") return 1 else return 0
func3 = if (output = LABEL3/LABEL2 and feature="B01:xx3") return 1 else return 0
func4 = if (output = LABEL4/LABEL3 and feature="B01:xx4") return 1 else return 0
....
funcM = if (output = LABELM/LABELM_1 and feature="B01:xxM") return 1 else return 0
”””
在Bigram中,特征函数中的output是当前的输出标记和前一个输出标记的联合,即联合的是标记而不是特征,特征的联合由宏语句来实现。其他的意义和Unigram中的相同。
此时生成的特征函数也会有大量重复。假设标记的种类一共有L个,由B01:%x[0,1]指定的,从该行提取到的特征的种类一共有N个,则CRF++会按照L种标记,N种特征自动生成L×L×N个特征函数,训练后函数的权值反映了上一个节点的标签对当前节点的影响。
当标记的种类L较大时,Bigram会生成非常多的特征函数,其中非常多的特征函数在样本中的返回值只有少量的1,此时,模型的训练和测试将会非常低效。
如果某一行的内容只有一个字符B,表示:由当前的输出标记和前一个输出标记的联合生成的特征函数。
三、实践
(1)用CRF进行命名实体识别
博客:https://www.jianshu.com/p/12f2cdd86679
参考文献
[1] http://www.huaxiaozhuan.com/%E5%B7%A5%E5%85%B7/CRF/chapters/crfpp.html,CRF++使用。
[2] http://taku910.github.io/crfpp/,CRF