
邱震宇
华泰证券算法工程师
NLP方向
熟悉我的读者应该读过我之前写的一篇关于中文的文章 NER 任务实践文章(邱振宇:中文 NER 任务实验总结报告-深模型实现细节 [1]在那篇文章中,我介绍了一个 NER 任务的新范式:将实体抽取任务转化为抽取式阅读理解任务。
其核心思想是描述待提取的物理标签 query 与原始文本拼接,然后基于 BERT 做对应 span 的抽取。这样,模型就可以学习实体标签本身的语义信息,引入先验知识,从而提高模型的能力。这种方法的有效性在我后来的实现中得到了进一步的验证。但当我试图将这种方法应用到实际的场景任务中时,我遇到了很多限制。
影响最大的是在线预测的效率。在实际场景中,我们的实体或其他序列标记元素通常有很多类型的标签(假设有 |C| ),这意味着我们需要预测每个文本和 |C| 个标签 query 模板拼接得到模型输入,需要调用 |C| 模型的前向计算可以完成样本的提取。假设 query 最长文本长度为 n,原始文本的文本长度为 m,由于进行了 self-attention 整个计算的复杂性是 ,这种在线预测效率不能满足在线需求。
针对这个问题,我尝试了一些优化方法,但都不理想。最近,我终于在一篇文章中 EMNLP2021 在论文中找到了更好的方案,论文如下:Enhanced Language Representation with Label Knowledge forSpan Extraction [2]。这篇论文提出了一模型架构,尝试优化 BERT-MRC 对于一些缺陷,我将详细描述这种方法,并验证其有效性。
https://github.com/qiufengyuyi/lear_ner_extraction
首先,论文提到了以前的使用 BERT-MRC 虽然与传统的序列标记方法相比,虽然 BERT-CRF 该方法有一定的效果,但仍有两个缺陷。其中一个是前言中提到的。另一个是 BERT-MRC 并没有充。前言中提到,BERT-MRC 然而,标签的先验知识被引入, LEAR 在论文中通过对 attention 部分可视化分析发现模型可能不太用 query 的信息。如图 1 所示,实体 judge 对应的高分 attention 它不像预期的那样集中在问题的核心部分,而是分散在一些无关的信息中(如 [CLS] 等 token)。
▲图1 attention 可视化
通过以上分析,可以发现 BERT-MRC 先验知识的利用率不如预期。LEAR 设计了一种新的标签知识集成方法,使模型能够有效地利用标签先验知识,同时解决问题 BERT-MRC 的效率和知识利用率问题。
LEAR 如图所示 2 所示。
▲图2 LEAR 模型
模型的输入分为两部分:原始待提取文本和所有标签对应的描述文本(先验知识)。 BERT-MRC 不同的是,我们不会将标签描述文本与原始文本拼接,而是使用它 BERT 编码器分别编码以获得不同的文本表示。
值得注意的是,为了提高训练效率,降低模型尺寸,原始文本和标签描述文本;之后我们引入一个标签知识融合模块,将所有标签描述文本的表示融合到原始文本中;最后我们使用引入分类器来识别待抽取 span 的 start 和 end 位置的概率分布,并使用一些 decoding 提取实体的策略。下面,我将详细描述集成模块和分类模块的具体内容。
假设我们通过 BERT 编码器得到了原始文本的表示 以及所有标签的描述文本 ,其中 n 表示原始文本的长度,m 标签文本的统一长度(通过 padding 之后),|C| 表示标签类别的数量,d 表示 hidden_size。标签信息集成模块本质上是一个注意机制模块,其目的是让模型在原始文本中学习每一个 token 关注标签描述文本中的内容。
对每个标签 c,我们将 视作 attention 计算中的 key 和 value 元素,将 作为 attention 中的 query 元素(关于 attention 中的Q、K、V 可以参考我以前的 attention 文章介绍 Attention 简单总结机制 [3],点乘模式 attention 计算并得到 attention 分数。在做点乘计算之前,我们参考一下 attention 典型的做法是线性映射原始文本表示和标签描述文本表示:
然后我们就这样做了 attention 计算:
接下来是对的 value 元素应用所得 attention 加权要求和操作分数,获得原始文本 token 的 context 信息(标签描述文本应在这里实现) mask 信息,把 padding 位置的值 mask 掉):
我们接下来要做的就是 context 将信息整合到原始文本的向量表示中。直接使用论文 ADD 操作,将 与 context 相加:
我尝试过 concat 拼接融合最终发现效果不如直接 add。在此架构下,add 操作的融合有更高的信息利用率。
之后,我们介绍一个 dense layer,输出最终文本表达,使用激活函数 tanh,相比 sigmoid,其值域更广,防止向量表示的绝对值过大:
最后,我们对每一篇文本进行了评论 token、标签类别进行上述流程,最终得到完整的集成向量表示:。
当模型实现时,所有标签类别和所有标签类别 token 在一次矩阵计算中可以完成计算。
与 BERT-MRC 类似,LEAR 最后,预测一个 span 的 start 和 end 的位置 token。但由于 LEAR 所有标签类别的预测都放在前向计算中,所以最终的分类层不同于传统的方式。对于原文 token ,我们计算它作为某种实体 start 概率分布:
其中, 和 分别是可训练的权重,而 表示 element-wise而矩阵乘法 表示输入矩阵 hidden_size 求和的维度,最后得到一个 |C| 维的向量。
虽然上面的公式看起来还是有点绕,但实现起来并不是很复杂 TensorFlow 可以在框架下实现:
defclassifier_layers(self,input_features,type="start"): #inputfeatures #batch_size,input_seq_len,num_labels,hidden_size #input_shape=modeling.get_shape_list(input_features) #batch_size=input_shape[0] &nbs; # seq_length = input_shape[1]
classifer_weight = tf.get_variable(
name="classifier_weight"+"_"+type,
shape=[self.num_labels, self.attention_size],
initializer=modeling.create_initializer(self.bert_config.initializer_range))
classifer_bias = tf.get_variable(name="classifier_bias"+"_"+type,shape=[self.attention_size],initializer=tf.constant_initializer(0))
output = tf.multiply(input_features,classifer_weight)
output += classifer_bias
#[batch_size, input_seq_len, num_labels]
output = tf.reduce_sum(output,axis=-1)
return output
其中 tf.multiply 是一个 element-wise 矩阵乘法,且支持 broadcasting 机制。
对于某类实体的 end 概率计算,与 start 的流程是一样的。最后每个样本将得到两个概率输出:, 。根据这两个输出与预先设计的概率阈值(一般是 0.5),我们就能一次性得到所有标签类别的起始位置列表,但是要得到具体的 span 实体,还需要设计一些 decoding 策略。
论文中提出了两种 decoding 策略,分别为和。最近距离策略就是先定位所有的 start 位置,然后找距离 start 位置最近的 token 作为 end。而启发式策略则稍微复杂一些,若定位到一个 start1 位置,不会马上决定它作为一个 span 的起始位置,若后面的 token 如果也是一个候选的 start2,且其概率还要大于 start1,则会选择新的 start2 作为 start 候选。具体大家可以参考论文后面的附录,有详细的算法流程。
说实话,我感觉论文中附录的算法伪代码有些问题,对于 end 位置的判断应该不需要像 start 一样选择概率最高的。另外,我自己的训练数据预处理时,对于单字成实体的情况,我的 end 位置是置为 0 的,而论文和开源代码中的设置却不同,因此不好做直接对比。
我自己实现时,使用的是变种的最近距离策略。因为选择与 start 最近的 end 时,有可能会越过下一个新的 start 位置,这有可能是算法本身预测有问题,或者有实体嵌套的情况(目前还未考虑嵌套实体的 case),如图 3 所示。
▲ 图3 最近距离decoding策略
我这边采取了比较简单的策略,就是在 start1 和 start2 的区间中,来定位与 start1 最近的 end 位置。那么上图中的情况下,就不会选择 s1-e1 作为结果,而是只选择 s1 位置处的单个 token 作为结果输出:
▲ 图4 自定义的最近距离decoding策略
大家可以自行尝试不同的 decoding 策略,我感觉每种策略的适用场景都不一样,需要根据实际情况来判断。
LEAR 相对于 BERT-MRC 的最大优势在于其较高的 inference 效率。由于 LEAR 不需要为每个原始文本构造 |C| 个新样本,理论上 LEAR 的计算复杂度为 。可以看到,这个复杂度是远小于 BERT-MRC 的,在后面的验证中,我也得到了预期的效果。
我最近分享的一些方法大部分都经过实验并验证有效的,这次介绍的 LEAR 也不例外。论文有放出 pytorch 的源码:https://github.com/ Akeepers/LEAR。我照例还是使用 tensorflow 来实现,最近重新看了下之前的 NER 框架代码,感觉有很多地方写的不太合理,因此我又重新开了个 repo。LEAR 的实现也不是太复杂,需要注意的地方主要有以下几个方面:
1. 构造模型输入时,要专门针对实体标签的描述文本进行预处理。在做 input_fn 的时候,使用 tf.data.Dataset.from_generator 来进行 batch 数据流的构建,但是我们的标签文本本来是没有 batch 概念的,所有训练样本都只用一份标签文本,因此在 model 方法定义中,要人为将 tf 添加的 batch 维度去掉:
if label_token_ids.shape.ndims == 3:
label_token_ids = label_token_ids[0,:,:]
label_token_type_ids = label_token_type_ids[0,:,:]
label_token_masks = label_token_masks[0,:,:]
label_token_length = label_token_length[0,:]
label_lexicons = label_lexicons[0,:,:,:]
2. 原始文本和标签文本共享 encoder 参数,这里要对 google原始的 BERT 的 modeling.py 进行修改,在模型定义的时候,添加 auto_reuse 配置,同时在调用 bert 的时候,传入 scope="bert":
with tf.variable_scope(scope, default_name="bert",reuse=tf.AUTO_REUSE):
3. 论文和官方开源的代码都没有对 attention 计算后的分数进行 scaling。我觉得可能的原因是融合模块中的 attention 操作只有一层, 且注意力的分数并没有直接用于词 softmax 的计算,而是融合到了原始文本的向量表示中,另外在最后输出的时候使用了 tanh 进行了激活,输出的值不会太大,因此即使不做 scale,也不会产生很严重的梯度消失问题,并影响后面的分类器的计算。我这边也验证了,加入了 scaling 之后,效果相差不多。
最后,我将 LEAR 在中文的 MSRA 的 NER 数据集上进行了验证,同时与之前已经实现过的 BERT-MRC 进行了对比。验证指标则是考虑了实体类型后的 micro-level 的 f1 分数,具体来说我会将实体类型字符串与实体内容拼接,做去重后,进行 exact match 匹配。最终,得到的结果大体如下:
可以看到,LEAR 的效果相比 BERT-MRC 来说有一定提升,但是不够明显,但是 inference 的效率却是显著提升了,这也是我最关注的地方,这意味着这个方法可以应用在实际的业务场景中!
因为很长时间没写文章了,所以写一次就尽量多包含点内容。为了奖励读到这边的同学,我再奉送一段额外的 NER 优化技巧。
读过我上一篇中文 NER 总结的同学应该记得我在那篇文章中提到尝试在 BERT 中引入词汇信息,当时尝试的办法很依赖分词的质量,且融合词汇信息的方式也比较简单。因此,这次我参考了最近比较火的一篇论文来做词汇增强:Simplify the Usage of Lexicon in Chinese NER [4]。
这篇论文解读我就不做了,大家可以参考这篇:JayJay:中文 NER 的正确打开方式: 词汇增强方法总结(从 Lattice LSTM 到 FLAT)[5]。这个方法的核心思想是先准备一份词向量和词表,然后对当前所有语料中的文本字符统计其分别属于 BMES 的信息,B 代表词的开头,M 代表词的中间,E 代表词的结束,S 代表单独成词。
假设对于某个 token x,若某个样本 A 中存在片段 span,其开头为 x,则将 span 对应的词向量添加到 B 对应的列表中,其他类型同样操作。最后,我们将每个类型中的词向量做加权平均,得到增加的词汇信息与原始的 token 向量表示拼接。
▲ 图5 词汇增强方法
这个方法实现的最大难点在于如何快速找到包含某个字符,且符合特定位置关系的 span 词。原论文开放了代码:
https://github.com/v-mipeng/LexiconAugmentedNER
其使用了 trie 数来构建词典树,这也是海量字符串匹配场景中会使用的一种方法。我在实现的时候,没有用这种方式,为了快速得到效果,使用了一种比较基础的方式,就是在数据预处理的时候,将每个字符对应的 BMES 的词列表信息都预先存储起来,在模型输入的时候直接读取信息,这样做速度不慢,但是对内存的要求就比价高了,类似于空间换时间。
另外,在使用 tensorflow 实现完整的功能时,我发现坑有点多。因为静态图中,你要将每个字符的 BMES 词表序列作为输入传到模型图中,然后用 embedding_lookup 分别找到词对应的词向量,再做加权平均,而每个字符实际的 BMES 词表 size 都不一样,这意味着更多 padding 和 mask 的处理,想想都头大。。。所以我偷了个懒,,,看其是否仍有增益的效果,结果居然也有一定的增益,结合 LEAR 架构,我得到了如下的结果:
TIPS:我在构建词表的时候,为了提高速度,将出现频率小于 5 的词都过滤掉了。
可以看到增加了词汇增强信息后,LEAR 的效果有一定的提升。如果对这个方法感兴趣,可以尝试实现完整功能的增强,让增强信息也参与到模型学习中,最后应该会有很大的提升。
本文主要介绍了一种优化 BERT-MRC 的方法,针对其预测效率低、没有充分利用标签信息的两个缺陷,设计了一种专门针对标签文本的注意力机制融合方法,在提升模型效果的同时,大大提升了基于 MRC 做 NER 的效率,使其能够应用在实际的业务场景中。另外,本文也额外介绍了一种不依赖于分词工具的词汇增强方法,经过验证,证明其与其他 BERT 类的方法结合能够提升模型的抽取效果。
[1] https://zhuanlan.zhihu.com/p/103779616
[2] https://aclanthology.org/2021.emnlp-main.379.pdf
[3] https://zhuanlan.zhihu.com/p/46313756
[4] https://aclanthology.org/2020.acl-main.528.pdf
[5] https://zhuanlan.zhihu.com/p/142615620
感谢 TCCI 天桥脑科学研究院对于 PaperWeekly 的支持。TCCI 关注大脑探知、大脑功能和大脑健康。
如何才能让更多的优质内容以更短路径到达读者群体,缩短读者寻找优质内容的成本呢?
总有一些你不认识的人,知道你想知道的东西。PaperWeekly 或许可以成为一座桥梁,促使不同背景、不同方向的学者和学术灵感相互碰撞,迸发出更多的可能性。
PaperWeekly 鼓励高校实验室或个人,在我们的平台上分享各类优质内容,可以是,也可以是、或等。我们的目的只有一个,让知识真正流动起来。
📝
• 文章确系个人,未曾在公开渠道发表,如为其他平台已发表或待发表的文章,请明确标注
• 稿件建议以 格式撰写,文中配图以附件形式发送,要求图片清晰,无版权问题
• PaperWeekly 尊重原作者署名权,并将为每篇被采纳的原创首发稿件,提供,具体依据文章阅读量和文章质量阶梯制结算
📬
• 投稿邮箱:hr@paperweekly.site
• 来稿请备注即时联系方式(微信),以便我们在稿件选用的第一时间联系作者
• 您也可以直接添加小编微信()快速投稿,备注:姓名-投稿
🔍
现在,在也能找到我们了
进入知乎首页搜索
点击订阅我们的专栏吧
·