由于最近想实现CRF,学完了理论后就开始怎么想怎么实现,想参照CRF++的开源实现,但首先要解决的怎么理解特征模板,所以写了此文,主要参考了2篇文章,在此感谢。
2020.3.27 更正一些错误理解。
1.CRF++要求的训练数据格式
对于训练数据,首先需要多列,但不能不一致,既在一个文件里有的行是两列,有的行是三列;其次第一列代表的是需要标注的“字或词”,最后一列是输出位"标记tag",如果有额外的特征,例如词性什么的,可以加到中间列里,所以训练集或者测试集的文件最少要有两列。
以分词举例,标签为BMES:
2.特征模板
CRF++的特征模板通常长下面这个样子:
“%x[行位置,列位置]”代表了相对于当前指向的token的行偏移和列的绝对位置。
(1)unigram特征模板
先来解释以下Unigram特征模板是什么意思。
举例:U00:%x[-2,0]
行-2代表是当前行向前偏移2个位置
0代表第2列,如果遍历到当前行为“网 E”:
图3.10.1
将产生以下的特征函数:(注:下列伪码的x代表观测,y代表状态,也就是贴的标签)
If(y=='E'&&x=='人') return 1 else return 0;
每个特征函数会有一个权重参数W,需要训练得到这个参数。
同理U00:%x[-1,0],将产生:
If(y=='E'&&x=='民') return 1 else return 0;
至于下面这种特征模板:
则是同时包含了当前x,前一个x,和后一个x:
令当前行x的下标为2,则前一x=x1,后一个x=x3,则特征函数为:
if(x1==’民’&&x2==’网’&x3==’1’&y2==’E’) return 1 else return 0;
这种特征模板表示了前一个x,后一个x和当前x构成的上下文对当前标签y的影响。
(2)bigram特征模板
何为bigram,即考虑前一个状态转移到当前状态以及和自由组合构成的特征,这里代表当前行所在的索引。
设i = 2,那么前一状态为y1,当前状态为y2。
(2.1)在crf++中如果bigram只给出一个B,代表特征函数中不包括x这个特征,
还是以图3.10.1举例,将产生特征函数:
If(y1==’M’&&y2==’E’) return 1 else return 0;
我需要指出有的博客列举除了所有特征函数,比如:
If(y1==’B’&&y2==’B’) return 1 else return 0;
If(y1==’B’&&y2==’M’) return 1 else return 0;
If(y1==’B’&&y2==’E’) return 1 else return 0;
If(y1==’B’&&y2==’S’) return 1 else return 0;
If(y1==’M’&&y2==’B’) return 1 else return 0;
If(y1==’M’&&y2==’M’) return 1 else return 0;
If(y1==’M’&&y2==’E’) return 1 else return 0;
If(y1==’M’&&y2==’S’) return 1 else return 0;
...............
而事实是,特征函数的产生由样本而来,事实而来的,比如分词中不会出现B转移到B这种特征,但是代码实现中我们可以给出这样一个特征函数,因为预测时需要遍历所有可能的y,但通常这样的特征函数权重初始化为一个很大的负数。
这里的特征函数仅仅return 1或者0,这实际是符号公式的体现,在预测时,遍历一个序列到某个位置i时,需要知道其产生的特征函数有哪些?公式中就暴力的遍历所有特征函数,If判断为true,那么被检查的特征函数就被认为属于当前位置,其权重用于计算,,实际代码中我们并不这么做,这样做效率非常低,实际中用一个数据结构来进行索引,这就是后话了,想了解crf++的实现可以去看看,内部使用DoubleArrayTrie存储,每个特征函数拥有唯一的id:模板id+自身特征组成。
(2.2)bigram组合x
如果下面这样的特征模板,则会组合x
以B02举例,当前位置处于“网 行”,i=2,
则产生特征函数:
If(y1=='M'&&y2=='E'&&x2='网'&&x1='民') return 1 else return 0;
(2)trigram
trigam理论上也是可行的,考虑前对的影响,不过crf++并没有实现,这实际和二阶HMM模型类似的,有论文详细描述过其求解,我也自己推导过,比较复杂,但是其效果提升并未多大,所以一般不使用。
3.产生特征函数
假如这是训练数据,训练阶段将遍历数据逐一产生特征函数,写成伪码过程为:
m:样本个数
k:模板个数
templates:一组特征模板
genFeatureFunc:根据模板和序列x,y和位置i产生特征函数
for(i = 0; i < m; i++) {
for(j = 0; j < k; j++){
genFeatureFunc(templates[j],x,y,i);
}
}
如果你还不够明白请按照这段伪码和前面给出的模板和示例在脑子里或者纸上运行下。