CRF原理和代码实现

        CRF常用在序列标注任务中,是找出一个隐藏状态序列,使得在该隐藏状态(简称状态)序列下对应的观测序列出现的概率最大,本质上是一个token分类问题。以常见的中文NER任务为例,需要找出每一个中文字符对应的状态标签(BIOS标签体系),即隐藏在每一个观测字符之后的状态,也即给每一个字符做分类。

        既然是给字符(token)做分类,很自然地会想到LSTM,BERT等特征提取器。

假定表征一个中文序列的tensor的形状为(batch_size, sequence_length, hidden_size),

经过一个特征提取器之后,形状变为(batch_size, sequence_lenght, d_model),

再经过一步矩阵变换,可以将形状变为(batch_size, sequence_length, label_size),

最后经过一步softmax函数,可以将每个位置的token对应的类别标签的概率计算出来。

        到此为止,已经实现了token的分类,可以看到即使不用CRF,也可以完成token的分类任务。事实上,只用BERT模型的NER任务也得到了很好的结果。加上CRF可以为模型再加上一层限制条件,避免出现一些不合理的情况,比如I-LOC出现在了B-PER之后。

        下面我们就来看一下CRF是如何来实现这些限制的。

        我们还是从易于理解的HMM模型说起,CRF结构和参数可以与HMM进行类比。在HMM中有2个重要的假设,齐次马尔科夫假设和观测独立假设,可解释为当前隐藏状态只与前一时刻的隐藏状态相关,当前时刻的观测只与当前时刻的隐藏状态相关。模型涉及的3个参数可以表示为(π, A, B), 分别为初始时刻状态π,状态之间的转移矩阵A,状态到观测之间的发射矩阵B。模型解决的主要问题是,已知观测序列,求隐藏状态。(HMM另外两个问题是求序列概率和求模型参数)。

        CRF没有采用HMM的两个假设,而是采用了另外计算(假设)方式,马尔科夫性和无向图最大团概率计算模型。马尔科夫性可以表示为,节点(随机变量)之间没有边连接则概率无关(成对马尔科夫性)。最大团概率模型可表示为,多个随机变量取值的概率,等于概率图上所有最大团随机变量势函数的乘积。

        我们这里用的条件随机场是线性链条件随机场,由此可以将模型进一步简化,将最大团限定在两个节点之间,即线性链条件随机场中的一个最大团只包括两个节点,可以是和状态转移相关(yi-1, yi)的节点,或者是发射相关(yi,xi)的节点,由此,线性链条件随机场的参数化模型可以表示为

        Z(x)是归一化函数,t和s是特征函数,取值范围为 {0, 1}两个元素的集合,代表连个节点之间有没有边相连(没有边连接时,取值为0,可以限制不合理结果的出现)。λ和μ为权重值。

        另外两种表示形式为向量表示(也可以称为简化表示),和矩阵表示。其中向量表示是将参数和特征分别统一起来,用w(包含λ和μ)和f(包含t和s)来表示。理解矩阵表示时,可以只考虑状态之间的转移矩阵来方便理解。

        理解了马尔科夫性,最大团概率计算,线性链等概念之后,CRF模型应该也可以理解了。现在我们来看一下代码的实现。在BERT-NER-pytorch的代码中有这样几行代码,(参考BERT-NER-Pytorch/crf.py at master · lonePatient/BERT-NER-Pytorch · GitHub)

def _compute_score(self, emissions, tags, mask)

         …

         score = self.start_transitions[tags[0]]  # score.shape = (batch_size)

         score += emission[0, torch.arange(batch_size), tags[0]]

         for i in range(1, seq_length):

                   score += self.transitions[tags[i-1], tags[i]] * mask[i]

                   score += emission[i, torch.arange(batch_size), tags[i]]* mask[i]

         …

        到这里计算训练模式下标签状态未归一化概率(log prob)已经结束了,代码实现如此简洁。我们来看一下函数中各个参数及其形状,看能不能和我们之前提到的CRF参数化表示形式对应上(mask和一个batch内的长度相关,为简化讨论先不考虑了)。

        tags: (seq_len, batch_size)  # seq_first, 训练时的状态标签

        start_transitions: (num_tags)

        transitions: (num_tags, num_tags)

        emissions: (seq_len, batch_size, num_tags)  # seq_first

        这里start_transitions 和transions矩阵相对容易理解。start_transition和初始状态相关,

transitions矩阵里面的值代表了状态之间的转移分数。但是观测值在哪里?通过CRF模型得到的观测值不应该是最后一个维度为词表大小的tensor吗?这里需要注意的是,观测值也是tag(或者说并没有教科书意义上的观测值),tag代表token的类别,而不是token本身。这和我们通常认为的,发射矩阵的形状应当为(num_tags, vocab_size)是不一样的。

        这里的emissions不再是一个固定的矩阵,而是由BERT模型产生的,所以CRF层只需要学习start_transitions和transitions。

你可能感兴趣的:(自然语言处理,人工智能,nlp)