基于BiLSTM-CRF的命名实体识别
推荐在日间模式下阅读
命名实体识别(NER)
命名实体识别属于自然语言处理中的序列标注任务,是指从文本中识别出特定命名指向的词,比如人名、地名和组织机构名等。具体而言,输入自然语言序列 ,给出对应标签序列 ,见下图。
序列标注里标记法有很多,包括BIO、BIOSE、IOB、BILOU、BMEWO、BMEWO+等,最常见的是 BIO 与 BIOES 这两种。不同标注方法会对模型效果有些许影响,例如有些时候用BIOES会比BIO有些许优势。
在BIO和BIOSE中,Beginning 表示某个实体词的开始,Inside表示某个实体词的中间,Outside表示非实体词,End表示某个实体词的结尾,Single表示这个实体词仅包含当前这一个字。IOB与BIO字母对应的含义相同,其不同点是IOB中,标签B仅用于两个连续的同类型命名实体的边界区分。BILOU 等价于 BIOES,Last等同于End,Unit等同于Single。BMEWO 等价于 BIOES,Middle等同于Inside,Whole等同于Single。BMEWO+是在命名实体边界外的标注,即‘ O plus ‘。
命名实体识别的常用方法是BiLSTM-CRF和BERT-CRF,可以完美的匹配该任务。
BiLSTM-CRF模型
下文,我们使用BIO标注进行解析,同时加入START和END来使转移矩阵更加健壮,其中,START表示句子的开始,END表示句子的结束。这样,标注标签共有5个:[B, I, O, START, END]。
BiLSTM-CRF模型主体由双向长短时记忆网络(Bi-LSTM)和条件随机场(CRF)组成,模型输入是字符特征,输出是每个字符对应的预测标签。
模型输入
对于输入的自然语言序列,可通过特征工程的方法定义序列字符特征,如词性特征、前后词等,将其输入模型。但现在多数情况下,可以直接选择句中每个字符的字嵌入或词嵌入向量,可以是事先训练好的或是随机初始化。对于中文,一般倾向于将字符向量和其所属的词向量进行拼接,词嵌入使用预训练好的,字嵌入随机初始化。
LSTM
BiLSTM接收每个字符的embedding,并预测每个字符的对5个标注标签的概率。
LSTM是一种特殊的循环神经网络,可以解决RNN的长期依赖问题,其关键就是细胞状态,见下图中贯穿单元结构上方的水平线。细胞状态在整个链上运行,只有一些少量的线性交互,从而保存长距离的信息流。具体而言,LSTM一共有三个门来维持和调整细胞状态,包括遗忘门,输入门,输出门。
遗忘门接受$h_{t-1}$和$x_t$,通过公式1输出一个0到1之间的数值$f_t$,这个数值会作用于上一个细胞状态$C_{t-1}$,1表示“完全保留”,0表示”完全忘记”;输入们接受$h_{t-1}$和$x_t$,通过公式2输出一个0到1之间的数值,以控制当前候选状态$\hat{C_t}$又多少信息需要保留,至于候选状态$\hat{C_t}$,则通过公式2由tanh层创建一个新的候选值向量,然后根据上一个细胞状态$C_{t-1}$和遗忘值$f_t$、新的细胞状态$C_the$输入值$i_t$,由公式4更新细胞状态;输出门接受$h_{t-1}$和$x_t$,通过公式5输出一个在0到1之间的数值$o_t$,最后公式6决定了当前状态$C_t$有多少信息需要输出。
题外话:LSTM的理解
一般来说,循环神经网络是MLP增加了一个时间维度,那么该怎样理解这个时间维度呢?可以参考以下两张图可视化LSTM。
在$torch.nn.LSTM()$中,有以下几个重要的参数
- $input$_$size$:是每个字符嵌入的维度。
- $hidden$_$size$:经过一个LSTM单元后输入h的维度。
- $num$_$layers$:即上图中depth的深度,若干个LSTMcell的堆叠
- $bidirectional$:默认False
LSTM的输入包括$input$和$(h_0,c_0)$两部分,其中$input$大小为$(seq_len, batch, input_size)$,$h_0$和$c_0$大小均为$(num_layersnum_directions, batch, hidden_size)$。输出包括$output$和$(h_n, c_n)$两部分,其中$output$大小为$(seq_len, batch, num_direction hidden_size)$,$h_n$和$c_n$大小均为$(num_layers*num_direction, batch, hidden_size)$。
我们知道,CNN在空间上共享参数,而RNN是在时间上共享参数。也就是在每一层depth中只有一个LSTMcell,即上面第二张图中每一层只有一个LSTM单元。
继续…
在BiLSTM-CRF中,一般使用一层的双向LSTM是足够的。因此,BiLSTM对输入embeddings的特征提取过程如下图。
本部分一开始就说过,BiLSTM接收每个字符的embedding,并预测每个字符的对5个标注标签的概率。但是,我们也知道上图得到的拼接向量维度大小为[num_directions, hidden_size]。为将输入表示为字符对应各个类别的分数,需要在BiLSTM层加入一个全连接层,通过softmax将向量映射为一个5数值的分布概率。(注:此处也可不加softmax激活函数,直接使用全连接层映射为一个5数值分布即可,区别仅在于得到的emission score值大小及该维加和是否等于1。下图示表示使用了softmax,可见各列加和为1)
到了这一步,似乎通过BiLSTM已经找到每个单词对应的最大标签类别,但实际上,直接选择该步骤最大概率的标签类别得到的结果并不理想。原因在于,尽管LSTM能够通过双向的设置学习到观测序列之间的依赖,但softmax层的输出是相互独立的,输出相互之间并没有影响,只是在每一步挑选一个最大概率值的label输出,这样的模型无法学习到输出的标注之间的转移依赖关系(标签的概率转移矩阵)以及序列标注的约束条件,如句子的开头应该是“B”或“O”,而不是“I”等。为此,引入CRF层学习序列标注的约束条件,通过转移特征考虑输出label之间的顺序性,确保预测结果的有效性。
CRF
CRF层将BiLSTM的Emission_score作为输入,输出符合标注转移约束条件的、最大可能的预测标注序列。
在开始之前…
先了解一下线性链条件随机场。见李航老师的《统计学习方法》第11章。
可以看到,NER问题就是条件随机场,即给定自然语言序列X,最大概率的标注序列Y用来表示NER标注结果,至于$P(y|x)$,由公式(11.10)和(11.11)求得。
上图中标出、公式(11.10)有三个值得注意的部分:$t_k$,$s_l$和$Z(X)$,理解这三个部分是理解BiLSTM-CRF模型中CRF的关键,以下图说明。
在例子中,输入$x$为$c_0,c_1,c_2,c_3,c_4$,理想输出$y$为$B,I,O,O,B$,上图中红的线路。
$Z(x)$,称为规范化因子或配分函数。在公式(11.10)中,$Z(x)$是规范化因子,求和是在所有可能的输出序列上进行的。对应到我们的图中,其实就是途中所有可能的路径的组合,由于输入序列长度为5,标注类型也为5,英雌上图中共有$5^5$条不同的路径。每条路径是根据$exp(*)$计算该路径的个扽,加和得到$Z(x)$。
$s_t$是节点上的状态特征,取决于当前节点;$t_k$是边上的转移特征,取决于当前和前一个节点。根据他们的定义,可以很自然的将他们于BiLSTM-CRF中的Emission Score和Transition Score匹配:Emission Score是由BiLSTM生成的的、对当前字符标注的概率分布;Transition Score是加入CRF约束条件的、字符标注之间的概率转移矩阵。**从这个意义上讲,BiLSTM-CRF其实就是个CRF模型,只不过用BiLSTM得到状态特征值$s_t$,用反向传播算法更新转移特征值$t_k$**。
在模型训练过程中,模型损失函数定义如下:
$$P(\bar{y}|x)=\frac{exp(\operatorname{score}(x,\bar{y}))}{\sum_y{exp(\operatorname{score}(x,y))}} $$
$$ \operatorname{score}(x,y)=\sum_{i=1}^{n} P_{i, y_{i}}+\sum_{i=0}^{n} A_{y_{i-1}, y_{i}}$$
其中,**$P_{i, y_{i}}$和$A_{y_{i-1}, y_{i}}$分别表示标注序列$y$中$y_i$的Emission Score和Transition Score**,通过查找上图中的”BiLSTM的Emission Score“和”序列标注转移矩阵“可以得到每个字符位置的得分,整个序列相加得到$\operatorname{score}(x,y)$。
模型训练过程中最大化对数似然函数:
$$\log P\left(\bar{y}\mid x\right)=\operatorname{score}\left(x, \bar{y}\right)-\log \left(\sum_{y} \exp \left(\operatorname{score}\left(x, y\right)\right)\right)$$
真实路径得分
在我们的例子中,真实路径$\bar{y}=(B,I,O,O,B)$,$$\operatorname{score}(x,\bar{y})=\sum\operatorname{EmissionScores}+\sum\operatorname{TransitionScores}$$,其中:$$\sum\operatorname{EmissionScores}=P_{0,START}+P_{1, B}+P_{2, I}+P_{3,O}+P_{4, O}+P_{5,B}+P_{6,END}$$
$$ \sum\operatorname{TransitionScores}=A_{START,B}+A_{B,I}+A_{I,O}+A_{O,O}+A_{O,B}+A_{B,END}$$
EmissionScores来自BiLSTM层的输出,至于$P_{0,START}$和 $P_{6,END}$,则设为0;TransitionScores来自于CRF层;将真实路径中这两类分数加和,即可得到真实路径得分,上图中红色路线。
所有路径得分
对于所有路径的总分 $\sum_{y} \exp \left(\operatorname{score}\left(x, y\right)\right)$ ,一种直接的想法是列举出所有可能的路径,然后查找每条路径的Emission Score和Transition Score,计算出每条路径的得分,之后加和。
这种方法显然效率低下,在我们的例子中,仅有5个字符和5个标注序列,就已经有了约$5^5$种路径组合,在实际工作中,我们显然会有更长的序列和更多的标注标签,提高计算效率是必要的。于是,我们以分数累积的方法计算所有路径得分,即先计算出到达$c_0$的所有路径的总得分,然后,计算$c_0{->}c_1$的所有路径的得分,依次类推,直到计算出$c_0->c_1\dots->c_n$的所有路径的得分,这就是我们需要的结果。通过下图可以看出这两种计算方式的不同。
上面的图是直观的、计算所有路径得分加和的示意图,这种方法思想上是深度优先的;下面的图是改进的、分数累计求和的示意图,这种方法思想上是广度优先的。
在第一种方法中,图示以$[(S,S,S,S,S),(S,S,S,S,B),(S,S,S,S,I),(S,S,S,S,O),(S,S,S,S,E)] $五条可能的标注路径为例,每一条路径得分由该路径上节点得分(EmissionScore)和边得分(TransitionScore)相加得到,可以发现,如果计算分别计算每一条路径的话,存在大量的冗余,如对于上述五条路径,前四个标注是相同的,理论上只需分别计算最后一项不同标注即可。
分数累积的方法更为激进。要知道,在计算$Z(x) $时我们并不是要排列组合出所有可能路径,只是要一个All值,即图中所有边和节点的总值。因此,我们可以先计算出到达某一字符的路径得分之和 $(s_S^i,s_B^i,s_I^i,s_O^i,s_E^i)$,然后依次计算下一字符的路径得分之和$ (s_S^{i+1},s_B^{i+1},s_I^{i+1},s_O^{i+1},s_E^{i+1}) $,这类似于动态规划的思想,其中 $s$表示score,$$S/E$$表示标注START/END。如在上图示例中,我们在得知到达 $y_1$的路径得分之和 $(s_S^1,s_B^1,s_I^1,s_O^1,s_E^1)$后,根据 $$ s_{tag’}^2=\sum_{tags}{s_{tag}^1+t_{(tag,tag’)}}$$可分别计算出 $y_2$的路径得分之和$(s_S^2,s_B^2,s_I^2,s_O^2,s_E^2)$,图中红/绿/蓝/黄/紫分别根据$ y_1$各标注的得分计算到达 $y_2$的 $(S,B,I,O,E)$各标注的得分之和。依次类推,计算出整个图中所有路径得分之和。
在图中可以直观看到上述两种方法是等价的,其数学上的等价性见下: |
---|
$$\log \left(\sum e^{\log \left(\Sigma e^{x}\right)+y}\right)=\log \left(\sum \sum e^{x+y}\right) $$
证明过程:
$$ \begin{aligned} \log \left(\sum e^{\log \left(\Sigma e^{x}\right)+y}\right) &=\log (\sum e^{\log (\Sigma e^{x})+\log e^y}) \ &=\log (\sum e^{\log (\Sigma e^{x})*e^y})\ &=\log (\sum e^{\log (\Sigma e^{x+y})})\ &=\log \left(\sum \sum e^{x+y}\right) \end{aligned} $$
第二种方法有效减少了计算冗余。第一种方法的计算复杂度为$O(nm^n)$,其中,$m$为标注标签类别数,第二种方法的计算复杂度为$O(nm^2) $。当然,第二种方法还可以以下图这种方式计算,下文Pytorch Tutorial中的实现_forward_alg()
就是如此,但本质上就是一回事。
最终BiLSTM-CRF模型如下:
Pytorch Tutorial NER代码解析
网上多数Pytorch NER解析来自官方示例,见ADVANCED: MAKING DYNAMIC DECISIONS AND THE BI-LSTM CRF,以下代码添加有备注解析。以下几点需要注意:
- 前文提到,模型目标是最大化对数似然函数$ \log P\left(\bar{y}\mid x\right)=\operatorname{score}\left(x, \bar{y}\right)-\log \left(\sum_{y} \exp \left(\operatorname{score}\left(x, y\right)\right)\right) $,但在深度学习中,我们一般通过最小化损失函数优化模型参数。因此,取负得到损失函数$Loss=\log (\sum_{y} \exp (\operatorname{score}(x, y)))-\operatorname{score}(x, \bar{y})$。
- 在NER问题中,训练过程与测试过程是不同的。在训练中,我们需要计算所有路径的得分以正确更新转移矩阵,但在测试过程中,我们以然得到合理的标签概率转移矩阵,因此只需要结合Emission Score选择一条概率最大的路径。该过程为解码过程,采用Vertibe算法,参见如何通俗地讲解 viterbi 算法?。
- Pytorch Tutorial仅提供最基本的代码帮助理解BiLSTM-CRF,具体的到实践,该代码还有很大的优化空间。如批次训练、GPU训练、维特比解码和配分函数计算优化等。
1 | import torch |
参考
- 流水的NLP铁打的NER:命名实体识别实践与探索
- 序列标注方法BIO、BIOSE、IOB、BILOU、BMEWO、BMEWO+的异同
- BiLSTM-CRF模型理解
- Understanding LSTM Networks
- pytorch中LSTM的细节分析理解
- ADVANCED: MAKING DYNAMIC DECISIONS AND THE BI-LSTM CRF
- 如何通俗地讲解 viterbi 算法?
- 命名实体识别(NER):BiLSTM-CRF原理介绍+Pytorch_Tutorial代码解析_人工智能_misite_J-DevPress官方社区 (csdn.net)
- 《统计学习方法(第二版)》