最近在搞信息抽取任务,用到了LSTM+CRF模型,之前没有深入了解过,就趁这次好好总结一下。把所有的代码,文章看完一遍后发现,这个LSTM+CRF和一般的CRF还是有点区别的,以及具体的代码实现还是有些细节需要注意的。本文打算对原理,数据的构造,模型的搭建进行详细叙述,不过由于不同人之间的前置知识不同,所以理解起来可能还会有些差别,如果描述的不是很清晰的话,可以在下方评论指出,模型的实现参考李航《统计学习方法第二版》中有关CRF的内容,以及代码具体实现时根据
https://github.com/SkyAndCloud/bilstm_crf_sequence_labeling_pytorch/blob/master/main.pygithub.com
进行了check,保证代码准确性,代码使用了Pytorch框架。
解析分为原理篇和代码篇,此篇为原理篇。
原理解析:
首先我们要知道为什么使用LSTM+CRF,序列标注问题本质上是分类问题,因为其具有序列特征,所以LSTM就很合适进行序列标注,确实,我们可以直接利用LSTM进行序列标注。但是这样的做法有一个问题:每个时刻的输出没有考虑上一时刻的输出。我们在利用LSTM进行序列建模的时候只考虑了输入序列的信息,即单词信息,但是没有考虑标签信息,即输出标签信息。这样会导致一个问题,以“我 喜欢 跑步”为例,LSTM输出“喜欢”的标签是“动词”,而“跑步”的标签可能也是“动词”。但是实际上,“名词”标签更为合适,因为“跑步”这里是一项运动。也就是“动词”+“名词”这个规则并没有被LSTM模型捕捉到。也就是说这样使用LSTM无法对标签转移关系进行建模。而标签转移关系对序列标注任务来说是很重要的,所以就在LSTM的基础上引入一个标签转移矩阵对标签转移关系进行建模。这就和CRF很像了。我们知道,CRF有两类特征函数,一类是针对观测序列与状态的对应关系(如“我”一般是“名词”),一类是针对状态间关系(如“动词”后一般跟“名词”)。在LSTM+CRF模型中,前一类特征函数的输出由LSTM的输出替代,后一类特征函数就变成了标签转移矩阵。
如下图所示,对于一个输入序列
,经过Embedding后得到输入到LSTM中,经过线性层作用后得到每个词对应到每个label(这里有5个label)上的分数。这里label的集合包括起始标签S,结束标签E,以及一般标签L1,L2,L3。
同样的,根据标签转移矩阵
,我们可以得到上一个时刻的标签 为
,下一个时刻标签为
的得分,即
。
LSTM输出每个词在各个标签上的得分
标签转移矩阵T,表示标签之间的转移得分
一般来说,对于一个序列
,如果序列
的长度为
,有
个可能的标签,那么共有
个可能的标记结果,即
个
。我们可以利用LSTM+CRF模型计算出每个可能的标注结果的得分
,然后利用softmax进行归一化求出某个标注结果的概率
,选择概率最大的作为标注结果,这里我们用
表示所有可能路径对应分数的指数和。
这样我们就需要关注几个问题:
1.给定输入
,如何计算某个输出序列
的概率?
2.给定训练数据
,如何对LSTM+CRF模型进行训练?
3.对于训练好的LSTM+CRF模型,给定输入
,如何求得最可能的结果
。
这三个问题其实也就对应CRF的三个基本问题。
首先是第一个问题
根据前面概率的定义,我们可以知道
,一般我们会对其取对数,然后再求解,即求
。所以首先我们讨论给定输入
,如何计算某个输出序列
的分数。
输入LSTM中得到每个词
对应的标签得分分布
。而
的结果是一个矩阵,我们称之为发射矩阵
。对于词
,其
,是一个m维向量,其标签
的得分为
。(
为int型数值,表示标签索引)
而
是一条链,我们将所有的
加起来只是得到了节点的分数,我们还需要求边的分数。即
到
的转移分数,根据前面的定义标签转移矩阵为
,即
到
的转移分数为
。
最后,把所有的分数加起来即可得
。
然后我们需要下一步工作,计算所有可能的路径的分数指数和的对数
。但是我们知道这种情况下共有
个可能的标记结果,如果列举所有的路径再求总分,那么计算量就很大。我们可以考虑采用动态规划的方法,动态规划之所以比列举法高效,是因为它可以复用前面的计算信息,不必在后面重新计算。
我们知道对于
时刻的输出
来说,其有
种可能,如果我们记录下从开始时刻到
时刻时,
取某一个值
时经过的所有路径的分数的指数和的对数
,那么我们就可以利用此信息获得
时刻,
的所有路径的得分的指数和的对数。
我们知道对于
到
,路径得分变化是在原来的基础上加上
。所以
计算图解如下:
这样就需要我们维护一个
维向量
,
表示t时刻,
时的所有路径指数和的对数
。即
如果我们想根据
得到
,则按照前面的公式来说,
当得到最后一个时刻
的
时,我们只需要用
就可以得到最终所有路径的分数的指数和的对数,即
。
这样,我们就能最终得到
。
然后是第二个问题,如何训练模型。
在CRF里面,模型训练方法有点麻烦,但是也还行,不过还是不如LSTM+CRF简单,因为所有的参数都是以神经网络的形式定义,我们只需要表示出损失函数,然后让Pytorch框架自动求导即可。
这里我们使用负对数似然函数作为损失函数,即
,
为
对应的真实标签数据,至于为什么,请参考:
visionshao:极大似然角度下的神经网络分类损失函数zhuanlan.zhihu.com
之后,我们就可以愉快地训练了。
第三个问题,当模型训练好后,如何求得最好的结果。
首先,我们要明白我们要寻找的最优路径即为得分最高的路径,下图是一个demo示例,不同颜色的箭头指向不同的节点,任意时刻,带有颜色的箭头表示的路径是指向该节点的得分最高的路径。
在最后一个时刻T=3,我们只需要找出得分最高的路径(某种颜色表示的路径)指向的节点,然后不断回溯即可找到整体的最优路径。
从上面的叙述中,我们可以发现,要想实现这种方法,我们需要保存每个节点对应的得分最高的路径及其分数。
我们扩展到第一个问题定义的情况下,即
,经过LSTM后得到的发射矩阵为
,标签转移矩阵为
。
首先,t=0时刻,我们获取各个节点对应的最大路径的得分
,即节点的发射得分
。
然后到t=1时刻我们要求各个节点对应的最大得分路径及其得分。
这时我们对
和
使用wise加法,这样,我们就得到一个
的矩阵
,
表示t=0的节点
的最大得分路径到t=1时刻的节点
形成的路径得分。如果我们想求得t=1时刻到节点
的最大得分路径,只需要对
求最大值。同样的,如果我们想得到t=1时刻的各个节点对应的最大路径得分
,我们只需要对
的每一列求最大值。求得最大值的同时将最值路径的来源即箭头的尾部节点记下来,便于遍历查找。这样,我们在得到
的同时,也得到了t=1时刻各个节点对应的最大路径在t=0时刻对应的节点向量
。
然后,不断重复上面的步骤,直到得到t=n-1时刻的
和
。这时
对应的是最后时刻的各个节点对应的最大路径得分。我们求得
中最大值对应的索引
。
这个索引表示最优路径的最后一个节点。然后我们将
带入
,得到
,表示最优路径倒数第二个节点,然后依次类推,知道得到
,这时,最优路径为
。
至此,上述三个问题全部解决。
后面会针对具体实现过程中的代码进行解析,指出一些需要注意的地方。代码地址为