©PaperWeekly 原创 · 作者|苏剑林

单位|追一科技

研究方向|NLP、神经网络

本文将介绍一个称为 GlobalPointer 的设计,它利用全局归一化的思路来进行命名实体识别(NER),可以无差别地识别嵌套实体和非嵌套实体,在非嵌套(Flat NER)的情形下它能取得媲美 CRF 的效果,而在嵌套(Nested NER)情形它也有不错的效果。

还有,在理论上,GlobalPointer 的设计思想就比 CRF 更合理;而在实践上,它训练的时候不需要像 CRF 那样递归计算分母,预测的时候也不需要动态规划,是完全并行的,理想情况下时间复杂度是









简单来说,就是更漂亮、更快速、更强大!真有那么好的设计吗?不妨继续看看。

▲ GlobalPoniter多头识别嵌套实体示意图

GlobalPointer

常规的 Pointer Network 的设计在做实体识别或者阅读理解时,一般是用两个模块分别识别实体的首和尾,这会带来训练和预测时的不一致。而 GlobalPointer 就是针对这个不一致而设计的,它将首尾视为一个整体去进行判别,所以它更有“全局观”(更 Global)。

1.1 基本思路

具体来说,假设要识别文本序列长度为 n,简单起见先假定只有一种实体要识别,并且假定每个待识别实体是该序列的一个连续片段,长度不限,并且可以相互嵌套(两个实体之间有交集),那么该序列有多少个“候选实体”呢?不难得出,答案是













个,即长度为 n 的序列有













个不同的连续子序列,这些子序列包含了所有可能的实体,而我们要做的就是从这













个“候选实体”里边挑出真正的实体,其实就是一个“













选 k”的多标签分类问题。

如果有 m 种实体类型需要识别,那么就做成 m 个“













选 k”的多标签分类问题。这就是 GlobalPointer 的基本思想,以实体为基本单位进行判别,如本文开头的图片所示。

可能有读者会问:这种设计的复杂度明明就是












呀,不会特别慢吗?如果现在还是 RNN/CNN 的时代,那么它可能就显得很慢了,但如今是 Transformer 遍布 NLP 的时代,Transformer 的每一层都是












的复杂度,多 GlobalPointer 一层不多,少 GlobalPointer 一层也不少,关键是












的复杂度仅仅是空间复杂度,如果并行性能好的话,时间复杂度甚至可以降到









,所以不会有明显感知。

1.2 数学形式

设长度为 n 的输入 t 经过编码后得到向量序列





















,通过变换 我们可以得到序列向量序列 ,它们是识别第




种类型实体所用的向量序列。此时我们可以定义:

作为从 i 到 j 的连续片段是一个实体的打分。也就是说,用























的内积,作为片段













是类型为




的实体的打分(logits),这里的













指的是序列 t 的第 i 个到第 j 个元素组成的连续子串。

在这样的设计下,GlobalPointer 事实上就是 Multi-Head Attention 的一个简化版而已,有多少种实体就对应多少个 head,相比 Multi-Head Attention 去掉了




相关的运算。

1.3 相对位置

理论上来说,式(1)这样的设计就足够了,但实际上训练语料比较有限的情况下,它的表现往往欠佳,因为它没有显式地包含相对位置信息。在后面的实验中我们将会看到,加不加相对位置信息,效果可以相差 30 个百分点以上!

比如,我们要识别出地名,输入是天气预报的内容“北京:21 度;上海:22 度;杭州:23 度;广州:24 度;...”,这时候要识别出来的实体有很多,如果没有相对位置信息输入的话,GlobalPointer 对实体的长度和跨度都不是特别敏感,因此很容易把任意两个实体的首尾组合都当成目标预测出来(即预测出“北京:21 度;上海”这样的实体)。相反,有了相对位置信息之后,GlobalPointer就会对实体的长度和跨度比较敏感,因此能更好地分辨出真正的实体出来。

用哪种相对位置编码呢?理论上来说,Transformer 里边所有的相对位置编码都可以考虑用(参考让研究人员绞尽脑汁的 Transformer 位置编码),但真的要去落实就会发现一个问题,大多数相对位置编码都对相对位置进行了一个截断,虽然这个截断范围对我们要识别的实体来说基本都够用了,但未免有点不优雅,不截断又会面临可学参数太多的问题。想来想去,还是觉得笔者之前构思的旋转式位置编码(RoPE)比较适合。

RoPE 的介绍可见 Transformer 升级之路:博采众长的旋转式位置编码,它其实就是一个变换矩阵









,满足关系





























,这样一来我们分别应用到






中,就有:

从而就显式地往打分














注入了相对位置信息。

优化细节

在这部分内容中,我们会讨论关于 GlobalPointer 在训练过程中的一些细节问题,包括损失函数的选择以及评价指标的计算和优化等,从中我们可以看到,GlobalPointer 以实体为单位的设计有着诸多优雅和便利之处。

2.1 损失函数

到目前为止,我们已经设计好了打分














,识别特定的类




的实体,则变成了共有













类的多标签分类问题。接下来的关键是损失函数的设计。最朴素的思路是变成













个二分类,然而实际使用时 n 往往并不小,那么













更大,而每个句子的实体数不会很多(每一类的实体数目往往只是个位数),所以如果是













个二分类的话,会带来极其严重的类别不均衡问题。

这时候我们之前研究的将“softmax+交叉熵”推广到多标签分类问题就可以派上用场了。简单来说,这是一个用于多标签分类的损失函数,它是单目标多分类交叉熵的推广,特别适合总类别数很大、目标类别数较小的多标签分类问题。其形式也不复杂,在 GlobalPointer 的场景,它为:

其中









是该样本的所有类型为




的实体的首尾集合,









是该样本的所有非实体或者类型非




的实体的首尾集合,注意我们只需要考虑






的组合,即:

而在解码阶段,所有满足
















的片段













都被视为类型为




的实体输出。可见,解码过程是及其简单的,并且在充分并行下解码效率就是









2.2 评价指标

对于 NER 来说,常见的评价指标就是 F1,注意是实体级别的 F1,并非标注标签级别的 F1。在传统的 Pointer Network 或者 CRF 的设计下,我们并不容易在训练过程中直接计算实体级别的 F1,但是在 GlobalPointer 的设计下,不管是计算实体级别的 F1 还是 accuracy 都是很容易的,比如 F1 的计算如下:

def global_pointer_f1_score(y_true, y_pred):"""给GlobalPointer设计的F1"""y_pred = K.cast(K.greater(y_pred, 0), K.floatx())return 2 * K.sum(y_true * y_pred) / K.sum(y_true + y_pred)

能有这么简单,主要就是因为 GlobalPointer 的“Global”,它的 y_true 和  y_pred 本身就已经是实体级别了,通过 y_pred > 0 我们就可以知道哪些实体被抽取出来的,然后做个匹配就可以算出各种(实体级别的)指标,达到了训练、评估、预测的一致性。

2.3 优化F1值

GlobalPointer 的“Global”还有一个好处,就是如果我们用它来做阅读理解的话,它可以直接优化阅读理解的 F1 指标!阅读理解的 F1 跟 NER 的 F1 有所不同,它是答案的一个模糊匹配程度,直接优化 F1 可能更有利于提高阅读理解的最终得分。将 GlobalPointer 用于阅读理解,相当于就只有一种实体类型的 NER,此时我们定义:

而有了 p(i,j) 之后,用强化学习的思想(参考殊途同归的策略梯度与零阶优化),优化 F1 就是以下述函数为损失:

这里的












就是提前算好的片段













与标准答案之间的 F1 相似度,




是一个超参数。当然,算出所有的












成本可能会有点大,但它是一次性的,而且可以在计算时做些策略(比如首尾差别过大就直接置零),总的来说,可以控制在能接受的范围。如果为了提高阅读理解最终的 F1,这是一种比较直接的可以尝试的方案。(笔者在今年的百度 lic2021 阅读理解赛道上尝试过,确实能有一定的效果。)

实验结果

现在一切准备就绪,马上就能够开始实验了,实验代码整理如下:

开源地址:https://github.com/bojone/GlobalPointer

目前 GlobalPointer 已经内置在 bert4keras>=0.10.6 中,bert4keras 的用户可以直接升级 bert4keras 使用。实验的三个任务均为中文 NER 任务,前两个为非嵌套 NER,第三个为嵌套 NER,它们的训练集文本长度统计信息为:

3.1 人民日报

首先,我们验证一下在非嵌套场景 GlobalPointer 能否取代 CRF,语料是经典的人民日报语料,baseline 是 BERT+CRF 的组合,而对比的是  BERT+GlobalPointer 的组合,实验结果如下:

首先,表格中带来最大视觉冲击力的无疑是 GlobalPointer 有无 RoPE 的差距,达到了 30 个点以上!这说明了给 GlobalPointer 显式加入相对位置信息的重要性,后面的实验中我们将不再验证去掉 RoPE 的版本,默认都加上 RoPE。

从表格中还可以看出,在经典的非嵌套 NER 任务中,效果上 GlobalPointer 可以跟 CRF 相媲美,速度上 GlobalPointer 还更胜一筹,称得上是又快又好了。

3.2 CLUENER

当然,可能因为人民日报这个经典任务的起点已经很高了,所以拉不开差距。为此,我们在测一下比较新的 CLUENER [1] 数据集,这个数据集也是非嵌套的,当前 SOTA 的 F1 是 81% 左右。BERT+CRF 与 BERT+GlobalPointer 的对比如下:

这个实验结果说明了,当 NER 难度增加之后,哪怕只是非嵌套的场景,GlobalPointer 的效果能优于 CRF,这说明对于 NER 场景,GlobalPointer 其实比 CRF 更加好用。后面我们将对此做个简单的理论分析,进一步说明 GlobalPointer 相比 CRF 在理论上就更加合理。

至于速度方面,由于这个任务的文本长度普遍较短,因此GlobalPointer的速度增幅也没有那么明显。

3.3 CMeEE

最后,我们来测一个嵌套的任务(CMeEE),它是去年 biendata 上的“中文医学文本命名实体识别”[2] 比赛,也是今年的“中文医疗信息处理挑战榜 CBLUE”[3] 的任务 1,简单来说就是医学方面的 NER,带有一定的嵌套实体。同样比较 CRF 和 GlobalPointer 的效果:

可以看到效果上 GlobalPointer 明显地优于 CRF;速度方面,综合三个任务的结果,总的来说文本越长的任务,GlobalPointer 的训练加速就越明显,而预测速度通常也略有提升,但幅度没有训练阶段大。随后笔者以 RoBERTa large 为 encoder 继续捣鼓了一下,发现线上测试集就可以(不是太难地)达到 67% 以上,这说明 GlobalPointer 是一个“称职”的设计了。

当然,可能有读者会诟病:你拿非嵌套的CRF去做嵌套的 NER,这样跟 GlobalPointer 比肯定不公平呀。确实会有点,但是问题不大,一方面 CMeEE 目前的 F1 还比较低,嵌套的实体本来就不多,哪怕去掉嵌套部分当成非嵌套的来做,影响也不会太大;另一方面就是在嵌套 NER 方面,笔者还没发现比较简单明快的设计可以作为 baseline 跑跑的,所以就还是先跑个 CRF 看看了。欢迎读者报告其他设计的对比结果。

思考拓展

在本节中,我们将进一步对 CRF 和 GlobalPointer 做一个理论上的对比,并且介绍一些与 GlobalPointer 相关的工作,以方便读者更好地理解和定位 GlobalPointer。

4.1 相比CRF

CRF(条件随机场,Conditional Random Field)是序列标注的经典设计,由于大多数 NER 也能转化为序列标注问题,所以 CRF 也算是 NER 的经典方法,笔者也曾撰写过简明条件随机场 CRF 介绍(附带纯 Keras 实现)和你的 CRF 层的学习率可能不够大等文章来介绍 CRF。在之前的介绍中,我们介绍过,如果序列标注的标签数为 k,那么逐帧 softmax 和 CRF 的区别在于:

前者将序列标注看成是 n 个 k 分类问题,后者将序列标注看成是 1 个







分类问题。

这句话事实上也说明了逐帧 softmax 和 CRF 用于 NER 时的理论上的缺点。怎么理解呢?逐帧 softmax 将序列标注看成是 n 个 k 分类问题,那是过于宽松了,因为某个位置上的标注标签预测对了,不代表实体就能正确抽取出来了,起码有一个片段的标签都对了才算对;

相反,CRF 将序列标注看成是 1 个







分类问题,则又过于严格了,因为这意味着它要求所有实体都预测正确才算对,只对部分实体也不给分。虽然实际使用中我们用 CRF 也能出现部分正确的预测结果,但那只能说明模型本身的泛化能力好,CRF 本身的设计确实包含了“全对才给分”的意思。

所以,CRF 在理论上确实都存在不大合理的地方,而相比之下,GlobalPointer 则更加贴近使用和评测场景:它本身就是以实体为单位的,并且它设计为一个“多标签分类”问题,这样它的损失函数和评价指标都是实体颗粒度的,哪怕只对一部分也得到了合理的打分。因此,哪怕在非嵌套 NER 场景,GlobalPointer 能取得比 CRF 好也是“情理之中”的。

4.2 相关工作

如果读者比较关注实体识别、信息抽取的进展,那么应该可以发现, GlobalPointer 与前段时间的关系抽取新设计 TPLinker [4] 很相似。但事实上,这种全局归一化的思想,还可以追溯到更远。

对于笔者来说,第一次了解到这种思想,是在百度 2017 年发表的一篇《Globally Normalized Reader》[5] ,里边提出了一种用于阅读理解的全局归一化设计(GNR),里边不单单将(首, 尾)视为一个整体了,而是(句子, 首, 尾)视为一个整体(它是按照先选句子,然后在句子中选首尾的流程,所以多了一个句子维度),这样一来组合数就非常多了,因此它还用了《Sequence-to-Sequence Learning as Beam-Search Optimization》[6] 里边的思路来降低计算量。

有了 GNR 作铺垫,其实 GlobalPointer 就很容易可以想到的,事实上早在前年笔者在做 LIC2019 的关系抽取赛道的时候,类似的想法就已经有了,但是当时还有几个问题没有解决。

第一,当时 Transformer 还没有流行起来,总觉得












的复杂度很可怕;第二,当时将“softmax+交叉熵”推广到多标签分类问题也还没想出来,所以多标签分类的不均衡问题没有很好的解决方案;第三,当时笔者对 NLP 各方面的理解也还浅,bert4keras 也没开发,一旦实验起来束手束脚的,出现问题也不知道往哪里调(比如开始没加上 RoPE,降低了 30 个点以上,如果是两年前,我肯定没啥调优方案了)。

所以,GlobalPointer 算是这两年来笔者经过各方面积累后的一个有点“巧合”但又有点“水到渠成”的工作。至于 TPLinker,它还真跟 GlobalPointer 起源没什么直接联系。当然,在形式上 GlobalPointer 确实跟 TPLinker 很相似,事实上 TPLinker 还可以追溯到更早的《Joint entity recognition and relation extraction as a multi-head selection problem》[7] ,只不过这系列文章都主要是把这种 Global 的思想用于关系抽取了,没有专门针对 NER 优化。

4.3 加性乘性

在具体实现上,TPLinker 与 GlobalPointer 的一个主要区别是在 Multi-Head 上 TPLinker 用的是加性 Attention:

目前尚不清楚该选择与式(1)的效果差异有多大,但是相比式(1)的乘性 Attention,虽然它们的理论复杂度相似,但实际实现上这种加性 Attention 的计算成本会大很多,尤其是空间成本(显存)会大很多~

所以笔者认为,就算加性效果确实比乘性好一些,也应该选择在乘性的基础上继续优化才行,因为加性的效率确实不行啊。此外,TPLinker 等文章也没有像本文一样报告过相对位置信息的重要性,难道在加性 Attention 中相对位置不那么重要了?这些暂时还不得而知。

本文小结

本文介绍了一种 NER 的新设计 GlobalPointer,它基于全局指针的思想,融合了笔者之前的一些研究结果,实现了用统一的方式处理嵌套和非嵌套 NER 的“理想设计”。实验结果显示,在非嵌套的情形下它能取得媲美 CRF 的效果,而在嵌套情形它也有不错的效果。

参考文献

[1] https://github.com/CLUEbenchmark/CLUENER2020

[2] https://www.biendata.xyz/competition/chip_2020_1/

[3] https://tianchi.aliyun.com/dataset/dataDetail?dataId=95414

[4] https://arxiv.org/abs/2010.13415

[5] https://arxiv.org/abs/1709.02828

[6] https://arxiv.org/abs/1606.02960

[7] https://www.sciencedirect.com/science/article/abs/pii/S095741741830455X

更多阅读

#投 稿 通 道#

 让你的论文被更多人看到 

如何才能让更多的优质内容以更短路径到达读者群体,缩短读者寻找优质内容的成本呢?答案就是:你不认识的人。

总有一些你不认识的人,知道你想知道的东西。PaperWeekly 或许可以成为一座桥梁,促使不同背景、不同方向的学者和学术灵感相互碰撞,迸发出更多的可能性。

PaperWeekly 鼓励高校实验室或个人,在我们的平台上分享各类优质内容,可以是最新论文解读,也可以是学习心得技术干货。我们的目的只有一个,让知识真正流动起来。

???? 来稿标准:

• 稿件确系个人原创作品,来稿需注明作者个人信息(姓名+学校/工作单位+学历/职位+研究方向)

• 如果文章并非首发,请在投稿时提醒并附上所有已发布链接

• PaperWeekly 默认每篇文章都是首发,均会添加“原创”标志

???? 投稿邮箱:

• 投稿邮箱:hr@paperweekly.site

• 所有文章配图,请单独在附件中发送

• 请留下即时联系方式(微信或手机),以便我们在编辑发布时和作者沟通

????

现在,在「知乎」也能找到我们了

进入知乎首页搜索「PaperWeekly」

点击「关注」订阅我们的专栏吧

关于PaperWeekly

PaperWeekly 是一个推荐、解读、讨论、报道人工智能前沿论文成果的学术平台。如果你研究或从事 AI 领域,欢迎在公众号后台点击「交流群」,小助手将把你带入 PaperWeekly 的交流群里。

GlobalPointer:用统一的方式处理嵌套和非嵌套NER相关推荐

  1. spring mvc 异常统一处理方式

    spring mvc 异常统一处理方式 参考文章: (1)spring mvc 异常统一处理方式 (2)https://www.cnblogs.com/xd502djj/archive/2012/09 ...

  2. 支付宝开放平台api接口封装统一调用方式

    在对接支付宝过程中,发现支付宝每个接口例子,都定义了AlipayClient和xxxRequest.xxxResponse. 老的写法就是每次都去写new一个AlipayClient package ...

  3. Android中RecyclerView嵌套RecyclerView或嵌套ListView

    Android中RecyclerView嵌套RecyclerView或嵌套ListView

  4. oracle无法打开表的行集,Oracle错误ORA-22905:无法访问非嵌套表项中的行

    这是我写入的存储过程.在此proc中,"p_subjectid"是从前端传递的一组数字.Oracle错误ORA-22905:无法访问非嵌套表项中的行 PROCEDURE getsu ...

  5. lingo 嵌套@for或嵌套@sum

    嵌套@for或嵌套@sum 对集合中的i单独求和,例如集合Xij,求Xi1+Xi2+Xi3-+Xin sets: r/1..5:y; c/1..5/:; link(r,c):x; endsets @f ...

  6. AJPFX实例集合嵌套之ArrayList嵌套ArrayList

    案例: package com.heima.list; import java.util.ArrayList; import java.util.Iterator; import com.heima. ...

  7. python多个if_Python之条件判断/if嵌套/如何写嵌套代码

    一.条件判断 条件判断是计算机沟通的逻辑,其作用就是明确地让计算机知道:在什么条件下,该去做什么.对于Python也是同样.Python之所以能完成自动化任务,比如自动抓取网页关键词,自动下载小电影等 ...

  8. 关于书籍“Composing Programs”1.6.3自定义函数的嵌套定义与非嵌套定义的比较

    最近开始刷ucb的cs61-2018年课程,配套书籍是John DeNero的"Composing Programs".书中1.6.3部分讲到user defined functi ...

  9. ElasticSearch使用(嵌套查询、嵌套高亮)

    ElasticSearch使用(嵌套查询.嵌套高亮) 嵌套查询 bool 查询 must.should关系 1.只有must 2.只有should 3.must和should同时存在 4.怎样设置sh ...

最新文章

  1. 使用OpenCV进行对象检测
  2. 如何在Keras中检查深度学习模型(翻译)
  3. HTML5 LocalStorage 本地存储JSON数据
  4. excel 多项式拟合数据
  5. 检索COM类工厂中CLSID为{000209FF-0000-0000-C000-000000000046}的组件时失败,原因是出现以下错误: 80070005...
  6. gin context和官方context_Go语言gin框架从入门到精通(3)
  7. 数据流技术在GPU和大数据处理中的应用
  8. mysqldump文件中有SET @@SESSION.SQL_LOG_BIN= 0;解决方法
  9. 动态规划思想-----求数组中出现最多数
  10. python调用word_Python操作Word
  11. [转]使用T4模板批量生成代码
  12. 概率论与数理统计图解
  13. PHP non-thread-safe和thread-safe这两个版本的区别
  14. python 爬虫基础——淘宝评论
  15. 计算机操作系统第四章测试题及答案
  16. JAVA面向对象之对象和类
  17. 关于下载安装pycharm专业版的步骤
  18. 计算机无法设置ip地址,如何解决Windows8无法设置静态IP地址的问题
  19. tushare 获取复权数据
  20. 虚拟机服务器扩容,vmware虚拟机 ubuntu根目录磁盘扩容

热门文章

  1. java编写github监控_【原创工具】github监控工具
  2. c语言怎么编辑错题本,嵌入式错题本
  3. 如何用express+node+ejs 搭建一个简单的页面
  4. HDU [P3605] Escape
  5. ThinkPHP5路由图解
  6. 在写移动端时,a标签或者input标签等 在手机上点击背后会有阴影的解决办法
  7. ibatis.net:第五天,QueryForObject
  8. Javascript中的对象查找【转】
  9. java 将图片转成二进制文件bin_java 问题:怎样把一个bin二进制图片文件用java代码打开?求解!...
  10. amd一键超频怎么用_头条信息流“一键起量”工具怎么用?经验分享