CRF是一种常用的序列标注算法,可用于词性标注、分词、命名实体识别等任务。BiLSTM+CRF是目前比较流行的序列标注算法。它结合了BiLSTM和CRF,使得模型可以像CRF一样考虑序列前后序列之间的相关性,也可以具有LSTM的特征提取和拟合能力。1.前言上篇文章《CRF 条件随机场》介绍了条件随机场CRF,并描述了CRF与LSTM的区别。我们以分词为例。每个词对应的标签可以有四种类型:s、b、m、e。给定一句话“什么是地摊经济”,其正确的分词方式是“什么/是/地摊/经济”,每个词对应的分词标签是“be/s/be/be”。从下图可以看出LSTM在做序列标注时的问题。BiLSTM分词BiLSTM可以预测每个词属于不同标签的概率,然后利用Softmax得到概率最大的标签作为该位置的预测值。这样,在预测时会忽略标签之间的相关性。如上图所示,BiLSTM预测第一个词为s,第二个词为e。但实际上,分词时e并没有出现在s之后,所以BiLSTM没有考虑标签之间的关系。因此,BiLSTM+CRF在BiLSTM的输出层增加了一个CRF,使得模型可以考虑类标签之间的相关性。标签之间的相关性就是CRF中的转移矩阵,表示从一种状态转移到另一种状态的概率。.假设CRF的转移矩阵如下图所示。CRF状态转移矩阵是对于前两个词“what”,标签为“se”的概率=0.8×0×0.7=0,标签为“be”的概率=0.6×0.5×0.7=0.21。因此,BiLSTM+CRF考虑的是整个类标签路径的概率,而不仅仅是单个类标签的概率,在BiLSTM输出层加入CRF后,如下图。BiLSTM+CRF分词最后计算所有路径中besbebe的概率,所以预测结果是besbebe。2.BiLSTM+CRF模型CRF包括两种特征函数。不熟悉的童鞋可以看看之前的文章。第一类特征函数是状态特征函数,也称为发射概率,表示词x对应标签y的概率。CRF状态特征函数在BiLSTM+CRF中,这个特征函数(发射概率)是直接使用LSTM的输出计算出来的。如第一节图中所示,LSTM可以计算每个位置对应不同标签的概率。CRF的第二个特征函数是状态转移特征函数,表示从一个状态y1转移到另一个状态y2的概率。CRF状态转移特征函数CRF的状态转移特征函数可以用状态转移矩阵表示,训练时需要调整状态转移矩阵的元素值。因此,BiLSTM+CRF需要在BiLSTM模型中加入状态转移矩阵。在如下代码中。classBiLSTM_CRF(nn.Module):def__init__(self,vocab_size,tag2idx,embedding_dim,hidden_??dim):self.word_embeds=nn.Embedding(vocab_size,embedding_dim)self.lstm=nn.LSTM(embedding_dim,hidden_??dim//2,num_layers=1,bidirectional=True)#对应CRF的发射概率,即每个位置对应不同类型label的概率self.hidden2tag=nn.Linear(hidden_??dim,self.tagset_size)#传递矩阵,维度为等于标签个数,表示从一个标签转移到另一个标签的概率self.transitions=nn.Parameter(torch.randn(len(tag2idx),len(tag2idx))tagsequenceisy的计算公式如下p(y|x)公式中的score通过以下公式计算,其中Emit对应emission概率(即LSTM输出的概率),Trans对应到转移概率(即CRF转移矩阵对应的值X)。score的计算公式BiLSTM+CRF采用的是最大似然,但是对应的损失函数如下:损失函数中,score(x,y)比较容易计算,Z(x)是所有标签序列(y)。如果序列长度为l,标签个数为k,则序列个数为(k^l)。不能直接计算,所以采用前向算法计算。利用目前主流的深度学习框架,loss的推导和梯度下降可以优化BiLSTM+CRF。模型训练好后,可以使用维特比算法(动态规划)寻找最优路径。3.损失函数计算BiLSTM+CRF损失函数的计算难点在于计算logZ(x),用F来表示logZ(x),如下式所示。我们将score拆分为emissionprobabilityp和transitionprobabilityT之和。为了简化问题,我们假设序列的长度为3,那么当长度为分别为1、2、3,如下图。上式中,p代表发射概率,T代表转移概率,Start代表开始,End代表句子结束。F(3)是生成的对数Z(x)值。通过对上式进行变换,F(3)可以转化为递归形式,如下所示。可以看出,上述公式中每一步的操作都是相同的,操作中包括log_sum_exp,如F(1):首先需要计算exp。对于所有的y1,计算exp(p(y1)+T(Start,y1)),并且,将上一步得到的exp值求log求和,计算求和结果的log。因此,前向算法计算logZ的代码可以这样写,如下:defforward_algorithm(self,probs):defforward_algorithm(probs):"""probs:LSTM输出的概率值,大小为[seq_len,num_tags],num_tags是标签的个数"""#forward_var(可以理解为文章中的F)保存的是上一时刻的值,是一个向量,维度等于num_tags#一开始,只有Start为0,其他取小值(-10000.)forward_var=torch.full((1,num_tags),-10000.0)#[1,num_tags]forward_var[0][Start]=0.0forpinprobs:#probs[seq_len,num_tags],遍历序列alphas_t=[]#alphas_t保存的是下一时刻取不同标签的累积概率值Probabilityemit_score=p[next_tag].view(1,-1).expand(1,num_tags)#Probability从所有标签转移到next_tag,transitions是一个矩阵,长宽为num_tagstrans_score=transitions[next_tag].view(1,-1)#next_tag_ver=F(i-1)+p+Tnext_tag_var=forward_var+trans_score+emit_scorealphas_t.append(log_sum_exp(next_tag_var).view(1))forward_var=torch.cat(alphas_t).view(1,-1)terminal_var=forward_var+self.transitions[Stop]#最后转为Stop表示句子结束alpha=log_sum_exp(terminal_var)returnalpha4.viterbi算法解码模型训练好后,预测过程需要使用viterbi算法对序列进行解码。有兴趣的童鞋可以参考下面的介绍《统计学习方法》我们来看一下维特比的公式,首先是一些符号的含义,如下:然后就可以得到维特比算法的递归公式,最后可以求根据维特比计算的值选择最合适的序列。最后推荐大家阅读pytorch官网的BiLSTM+CRF代码,通过代码更容易理解。5.高级参考:制定动态决策和BI-LSTMCRF
