注:本篇文章假设你已经看过CRF(条件随机场)与Viterbi(维特比)算法原理详解(侵权则删),但是对Pytorch的Tutorials中BiLSTM-CRF中关于CRF的代码还有些许的疑惑。
代码分析
假设有句子 “ 我爱饭 ”
- 句子经过分词,得到 “我”、“爱”、“饭” 这三个词(意思是句子的长度为3)
- 标签集合={‘START’ 、'N 、 ‘V’、‘END’},START、END表示开始结束标签,N表示名词,V表示动词。
在上述前提下执行一遍代码。
一、train代码
loss的计算方法为neg_log_likelihood(),我们跳转到该方法。
二、neg_log_likelihood()
- 第一行代码:在输入句子 “我爱饭” 的情况下,经过_get_lstm_features()方法可以得到一个(3*4)的tensor,分别表示“我”、“爱”、“饭” 对于{START,N,V,END}标签的可能性。
如feats[‘爱’][N]=0.2,表示词语 ‘爱’ 的标签是N的分数为0.2,然后将feats传给_forward_alg,接下来我们看farward_alg是如何计算所有路径的分数的。
三、 _forward_alg()
首先看一下CRF求解所有路径分数之和的动态规划的算法
- score [ x ] [ y ] :句子从起始位置到位置 x ,且位置x是标签 y 的分数。
如score [“爱”][N]就是说句子 “我爱” 且“爱”是标签N的所有路径的分数和,这就包括了四条路径的分数,如下
(START,N)(N,N)(V,N)(END,N) - emit [ x ] [ i ]:位置x是标签 i 的分数,emit [“爱”] [N],表示“爱”是标签N的分数
- trans [ i ] [ y ]:从标签 i 到标签y 的分数,即当前标签为y而上一标签是i 的分数。
如trans [N][START] = -10000,表示当前标签为START,而上一标签为N的分数为-10000,也就是说START上一个标签几乎不可能是N。
回到代码,逐步说明:
- 第一层循环,for feat in feats:
得到的feat是某个位置的所有标签的可能性。
第一次进入该循环时,feat就是 “我” 对应的标签可能性,即feat=[0.1,0.5,0.3,0.1],表示 ‘我’ 是START、N、V、END的分数分别为0.1、0.5、0.3、0.1,分数越大,则对应标签的可能性越大。 - 第二层循环,for next_tag in feats
第一次进入该循环时,该层的目的是得到 ‘我’ 是标签next_tag的所有分数,刚进入第二层循环时,next_tag是0,也就是START标签,表明我们在这次循环中求到的是“我” 是 START的分数。 - 回到代码,首先求的是“我” 是标签 START的分数。
- forward_var 初始化为[ 0,-10000,-10000,-10000,-10000 ],因为我们的每个句子的开头有一个独立的START标签,所以句首 “我” 的上一个位置是START、N、V、END的分数分别为 0,-10000,-10000,-10000,相当于式子中的score [x-1] [ i ],i∈tagset。
- emit_score得到的是"我"是标签START的分数,也就是式子中的emit[ “我” ] [ START ]
- trans_score 得到的是其他标签转化为START的分数,也就是式子中的trans[ START ][ i ],i∈tagset
最后得到的next_tag_var 就是[a, b, c, d ],简单的理解可以是a+b+c+d就是当前路径的分数,但是代码中使用了log_sum_exp对[a, b, c, d]进行了处理。