GPLinker:基于GlobalPointer的事件联合抽取
By 苏剑林 | 2022-02-21 | 75809位读者 |大约两年前,笔者在百度的“2020语言与智能技术竞赛”中首次接触到了事件抽取任务,并在文章《bert4keras在手,baseline我有:百度LIC2020》中分享了一个转化为BERT+CRF做NER的简单baseline。不过,当时的baseline更像是一个用来凑数的半成品,算不上一个完整的事件抽取模型。而这两年来,关系抽取的模型层见迭出,SOTA一个接一个,但事件抽取似乎没有多亮眼的设计。
最近笔者重新尝试了事件抽取任务,在之前的关系抽取模型GPLinker的基础上,结合完全子图搜索,设计一个比较简单但相对完备的事件联合抽取模型,依然称之为GPLinker,在此请大家点评一番。
任务简介 #
事件抽取是一个比较综合的任务。一个标准的事件抽取样本如下:
每个事件会有一个事件类型以及相应的触发词,并且配有不同角色的论元。事件类型和论元角色是在约定的有限集合(schema)中选择,而触发词和论元一般情况下都是输入句子的片段,少数情况下也是可枚举的分类对象(百度的DuEE-fin出现过)。原则上来说,事件抽取模型的设计取决于评价指标,在LIC2020中,我们之所以可以将事件抽取转化为一个NER问题,是因为当时的评测指标只考察(事件类型, 论元角色, 论元)构成的三元组,因此我们可以将(事件类型, 论元角色)组合成一个大类,然后就跟NER对应上了。
当然,这只是针对该指标的一种取巧方式。对于真实的事件抽取场景,我们自然是希望把标准格式的事件抽取出来,也就是设计一个尽量完备的模型。下面将介绍我们用于事件抽取的GPLinker模型,它基本达到了简单而完备的要求。
统一论元 #
我们多次提到了“完备”这个词,它是什么含义呢?具体来说,我们希望最终设计出来的模型,至少在理论上能适用于尽可能多情形的事件抽取任务。传统的事件抽取模型一般分为“触发词检测”、“事件/触发词类型识别”、“事件论元检测”和“论元角色识别”四个子任务,也就是说要先检测触发词然后基于触发词做进一步的处理,所以如果训练集没有标注出触发词,它就没法做了,这说明传统思路是不够完备的。
为了统一有无触发词的场景,我们这里就触发词也当作事件的一个论元角色,这样有无触发词就是增减一个论元而已,所以主要还是在于论元识别和事件划分。对于论元识别,我们还是将(事件类型, 论元角色)组合成一个大类,然后转化为NER问题,但要注意的是,不同的实体是可能嵌套的,所以之前基于CRF的NER其实是不够用的,这里用能识别嵌套实体的GlobalPointer来完成。
前面还提到,像DuEE-fin这样的任务还出现了分类式的论元类型,其论元不是输入文本的一个片段,而是有限集合中选1个,考虑到这种论元类型不多,所以这里也将其转化为抽取式论元。以DuEE-fin为例,其“公司上市”事件类型的“环节”论元,四个候选值为筹备上市、暂停上市、正式上市、终止上市,那么我们不将“环节”视为论元类型,而分别将筹备上市、暂停上市、正式上市、终止上市视为四种论元类型,而对应的实体则为触发词,这样我们要抽取的论元不是分类式的(公司上市, 环节, XX上市),而是抽取式的(公司上市, XX上市, 触发词),最后我们在模型后处理阶段再将它们转回来就好。
完全子图 #
至于事件划分,一个很自然的想法就是直接把所有具有同一事件类型的论元聚合起来作为一个事件,但这也是不够完备的,因为同一个输入可能有多个同一类型的事件。如果加上触发词呢?还是不够,多个同一类型的事件还可能公用同一个触发词,比如DuEE有一个样本是“主要成员程杰、王绍伟被法院一审判处有期徒刑22年和20年。”,分别有两个事件“程杰判处有期徒刑22年”和“王绍伟判处有期徒刑20年”,触发词都是“有期徒刑”,事件类型都是“入狱”。
所以,我们需要设计一个额外的模块来做事件划分。我们认为,同一事件的各个论元是有联系的,这种联系可以用无向图来描述。也就是说,我们将每个论元看成是图上的一个节点,而同一事件任意两个论元的节点可以连上一条边而成为相邻节点,而如果两个论元从未出现在同一事件中,那么对应的节点则没有边(不相邻)。这样一来,同一事件的任意两个节点都是相邻的,这我们称为“完全图(Complete Graph)”,也称为“团(Clique)”,事件划分转化为图上的完全子图搜索。
那么如何构建这个无向图呢?我们沿用TPLinker的做法,如果两个论元实体有关系,那么它们的(首, 首)和(尾, 尾)都能匹配上,我们可以像关系抽取的GPLinker一样,用GlobalPointer来预测它们的匹配关系。特别地,由于我们只需要构建一个无向图,所以我们可以mask掉下三角部分,所有的边都只用上三角部分描述。
搜索算法 #
假定我们已经有了描述论元关系的有向图,那么我们要怎么搜索出所有的完全子图呢?看上去跟图分割有点相似,但不完全一样,因为在我们的场景下,节点是可以重复使用的,这意味着同一个实体同时是多个不同事件的论元。比如上图中的8个节点可以搜索出两个完全子图,其中节点$D$同时出现在两个子图中,这代表我们可以划分出两个事件,它们有共同的论元$D$。
经过分析,笔者构思了如下的递归搜索算法:
1、枚举图上的所有节点对,如果所有节点对都相邻,那么该图本身就是完全图,直接返回;如果有不相邻的节点对,那么执行步骤2;
2、对于每一对不相邻的节点,分别找出与之相邻的所有节点集(包含自身)构成子图,然后对每个子图集分别执行步骤1。
还是以上图为例,我们可以找出$B$、$E$是一对不相邻节点,那么我们可以分别找出其相邻集为$\{A,B,C,D\}$和$\{D,E,F,G,H\}$,然后继续找$\{A,B,C,D\}$和$\{D,E,F,G,H\}$的不相邻节点对,发现找不到,所以$\{A,B,C,D\}$和$\{D,E,F,G,H\}$都是完全子图。注意这个不依赖于不相邻节点对的顺序,因为对于“所有”不相邻节点对我们都要进行同样的操作,比如我们又找到$A$、$F$是一对不相邻节点,那我们同样要找其相邻集$\{A,B,C,D\}$和$\{D,E,F,G,H\}$并递归执行。所以,在整个过程中,我们可能会得到很多重复结果,但不会漏了某个结果,也不会搜识别顺序影响,最后再去重即可。
此外,每次搜索的时候,我们只需要对同一事件类型的节点进行搜索,而多数情况下同一事件类型的论元只有个位数,所以上述算法看似复杂,实际运行速度还是很快的。
实验结果 #
现在我们的事件抽取设计已经介绍完了。总的来说,我们需要一个嵌套实体识别模型来识别论元,然后分别需要一个“首-首”匹配和“尾-尾”匹配模型来构建论元之间的关系,这些模块跟关系抽取的GPLinker模型出奇一致,并且也都可以用GlobalPointer来完成,所以我们依然将它命名为“GPLinker”。代码统一整理在:
Github地址:https://github.com/bojone/GPLinker
笔者简单在DuEE和DuEE-fin上做了实验,并提交到了千言排行榜,结果分别如下:
\begin{array}{c|ccc}
\hline
& \text{precision} & \text{recall} & \text{f1} \\
\hline
\text{DuEE} & 82.65 & 80.31 & 81.47 \\
\hline
\text{DuEE-fin} & 50.35 & 65.19 & 56.82 \\
\hline
\end{array}
单看这两个成绩,在排行榜上排第5,不算出彩,距离榜1还有点远。不过这篇文章也不是专门去打榜,所以也没有进一步针对比赛数据来优化,目前这个成绩,也算是可圈可点了。至于没有对比其他事件抽取模型,是因为笔者对这一领域实在不熟,简单看了几篇关系抽取的论文,感觉里边的模型都异常复杂,提不起兴趣去复现。
最后,代码用的GlobalPointer,全部都是Efficient GlobalPointer,笔者也比较过使用标准版的GlobalPointer,发现它前期收敛更快但最终效果会更差。这也再次肯定了Efficient GlobalPointer的有效性。
模型反思 #
GPLinker做事件抽取,追求的是简单和完备,实际情况下可能并不是最有效的方案。一个很明显的问题是,它对数据的依赖比较明显,在小样本的情况下效果可能欠佳。这本质上也是GPLinker足够完备导致的,因为完备意味着自由度相当大(可以容纳足够多的场景),模型没有多少先验信息,所以学习难度也会增加。
但是,GPLinker确实有着简单高效的特点,而且理论上也不存在Exposure Bias问题,所以实际情况下,如果GPLinker效果欠佳,那么一种可行的方案是先用其他能把效果做上去的方案(很可能效果好但效率差)做一版模型,然后蒸馏到GPLinker上去,这样就可以兼顾效果与效率了。
文章小结 #
本文介绍了将GPLinker用于事件抽取的思路,先用嵌套实体抽取的方式来抽取论元,然后将事件的划分转化为完全子图搜索问题,整个模型相对简洁和完备,并且理论上不存在Exposure Bias问题。
转载到请包括本文地址:https://kexue.fm/archives/8926
更详细的转载事宜请参考:《科学空间FAQ》
如果您还有什么疑惑或建议,欢迎在下方评论区继续讨论。
如果您觉得本文还不错,欢迎分享/打赏本文。打赏并非要从中获得收益,而是希望知道科学空间获得了多少读者的真心关注。当然,如果你无视它,也不会影响你的阅读。再次表示欢迎和感谢!
如果您需要引用本文,请参考:
苏剑林. (Feb. 21, 2022). 《GPLinker:基于GlobalPointer的事件联合抽取 》[Blog post]. Retrieved from https://kexue.fm/archives/8926
@online{kexuefm-8926,
title={GPLinker:基于GlobalPointer的事件联合抽取},
author={苏剑林},
year={2022},
month={Feb},
url={\url{https://kexue.fm/archives/8926}},
}
February 21st, 2022
hh,这种完全图的方法和我们的工作不谋而合。我们把完全图拓展了一下,尝试使用这种范式适配了有无触发词标注的事件抽取。另外我们还尝试了对完全图进行剪枝,只把核心实体当作“伪触发词”,获得了比完全图还好的效果。不过这种完全图的方案也不完美,理论上限并不是100%正确,且预测边的连接效果并不是很好,我们尝试了多种相似度度量方案,感觉还有很大的提升空间。
Efficient Document-level Event Extraction via Pseudo-Trigger-aware Pruned Complete Graph
论文:https://arxiv.org/pdf/2112.06013.pdf
代码:https://github.com/Spico197/DocEE
幸会幸会,与大佬异曲同工也是一种荣幸。看来正确的方向都有其相似之处,不过我只是浅尝辄止,期待大佬有更多进展。
请教大佬,论文中计算实体的边是用实体语义相关性做的吗?(arguments in the same combination are close to each other in the semantic space)没太明白,同一个事件的各个角色之间,语义上会相近吗?
1、实体的边用本文说的方法做,本文已经说了怎么做,也有代码,不要凭空猜测;
2、同一事件的各个角色是有关联,不是语义相近。
苏神,我是看了楼上朱桐的文章有一些疑问,不是针对本文。。。
朱桐大佬可能没留意到,如果让我来回答,那其实答案是类似的,“语义相关”不代表“语义相近”,我们可以认为同一个事件的角色之间存相关性,所以可以用一定的函数关系进行建模边,但它们不一定语义相近(比如Embedding的cos值不一定接近于1)。
March 2nd, 2022
苏神你好,请问代码里sparse_multilabel_categorical_crossentropy这个函数,在最新bert4keras包里没有啊,只有multilabel_categorical_crossentropy这个函数
我自己找到了..打扰了
March 2nd, 2022
“一种可行的方案是先用其他能把效果做上去的方案(很可能效果好但效率差)做一版模型,然后蒸馏到GPLinker上去,这样就可以兼顾效果与效率了” 苏神您好,能问一下关于这个蒸馏方案一般要怎么实现?在训练的时候直接让GPLinker最后一层的结果逼近效果好的模型最后一层嘛?
最简单的方式就是用其他模型预测无标注数据的结果,将预测结果当作标注数据(伪标签)用来训练GPLinker
March 16th, 2022
苏神,提取事件的递归搜索算法效率很慢,可有其他改进方案?
苏神,效率慢的问题解决了,我自己的问题,不好意思,打扰。
不过我发现一个新问题,解码建立links时只考虑索引,不考虑type,当同一entity在不同事件中有不同type时,会多析出事件,例如:
真实事件为:
[(('竞赛行为-胜负', '胜者', '常规赛打出73胜9负的历史第一战绩的勇士', 92), ('竞赛行为-胜负', '触发词', '胜', 99), ('竞赛行为-胜负', '赛事名称', '常规赛', 92)), (('竞赛行为-胜负', '触发词', '负', 101), ('竞赛行为-胜负', '败者', '常规赛打出73胜9负的历史第一战绩的勇士', 92), ('竞赛行为-胜负', '赛事名称', '常规赛', 92))]
实际析出事件为:
[(('竞赛行为-胜负', '胜者', '常规赛打出73胜9负的历史第一战绩的勇士', 92), ('竞赛行为-胜负', '触发词', '胜', 99), ('竞赛行为-胜负', '赛事名称', '常规赛', 92)), (('竞赛行为-胜负', '触发词', '负', 101), ('竞赛行为-胜负', '败者', '常规赛打出73胜9负的历史第一战绩的勇士', 92), ('竞赛行为-胜负', '赛事名称', '常规赛', 92)), (('竞赛行为-胜负', '触发词', '胜', 99), ('竞赛行为-胜负', '败者', '常规赛打出73胜9负的历史第一战绩的勇士', 92), ('竞赛行为-胜负', '赛事名称', '常规赛', 92)), (('竞赛行为-胜负', '胜者', '常规赛打出73胜9负的历史第一战绩的勇士', 92), ('竞赛行为-胜负', '触发词', '负', 101), ('竞赛行为-胜负', '赛事名称', '常规赛', 92))]
有一种解决办法是建立关系时也加入type,GlobalPointer(heads=1, head_size=64, RoPE=False)就变成GlobalPointer(heads=len(relation_types), head_size=64, RoPE=False),这样会不会太稀疏了?
但是不排除某些情况下,这种多事件析出是合理的,所以感觉不算是模型设计的问题,而是模型本身的泛化能力以及数据的问题。
March 29th, 2022
最后为啥要排除event_list中被final_event包含的event,或者event_list中的有event包含final_event就不添加的操作。可以不要这操作吗。在之前不是已经全部遍历找到所有互相相关的论元并构成了一个集合,应该不存在这种一个样本只找了部分的相关论元构成的事件。
这主要是根据比赛数据集的特点进行处理的。
原则上来说,extract_events就可以作为最终结果输出,但问题是线上评测的时候,不需要抽取触发词,所以预测比赛测试集的时候去掉了触发词,而去掉了测试机之后,就可能存在一个事件是另一个事件的子集的可能性了,而实测显示去掉这些子集能提高最终分数。
哦哦好滴,谢谢
May 26th, 2022
作为一名不了解事件抽取的小白,我对于事件抽取和实体关系抽取有一些迷惑,想请教一下:
1. 事件抽取和添加后处理规则的实体关系抽取有什么区别呢?
2. 如果可以添加后处理规则使实体关系抽取模型的输出转化为事件格式,其效果会比事件抽取差一些么?主要的原因是什么呢?
谢谢
1、事件抽取和关系抽取的格式不一样,事件抽取相对复杂点;
2、正如1所说,事件抽取复杂点,所以用事件抽取模型做关系抽取,通常是简单问题复杂化,确实有可能变差。
可是就实体关系抽取和事件抽取数据集的格式来说,很大程度上它们能做到相互转化,关系抽取经过简单后处理也可以转化为事件抽取,事件抽取也可以处理关系抽取的问题;
那么提出事件抽取的意义是什么呢?为什么不能完全使用实体关系抽取模型来进行代替呢?
谢谢
“关系抽取经过简单后处理也可以转化为事件抽取,事件抽取也可以处理关系抽取的问题”,你这句话说的都是“事件抽取模型可以处理关系抽取任务”,并不代表反过来可以。
另外,事件抽取是一种任务,它提出的意义就是现实中有这个需求,它并没有不允许你用关系抽取模型做(假如能做到的话)。
June 10th, 2022
所以这个思路是用做ner的核心做的,然后关系靠子图搜索,我理解的对吗
是的。
July 19th, 2022
苏老师您好,请问该模型适用于一个论元角色下有多个论元实体的情况吗
适用
August 26th, 2022
苏老师您好,请问该模型使用英文事件论元抽取吗?
我没规定用什么语言,所以模型本身肯定是可以用于任意语言的。如果你问的是直接套用代码,那我就不知道了。
September 18th, 2022
您好,关于这句(特别地,由于我们只需要构建一个无向图,所以我们可以mask掉下三角部分)不是很理解,为什么(首, 首)和(尾, 尾)关系抽取的时候不mask,在事件抽取的时候mask。
是因为关系抽取是有方向性的吗
对。A是B的学生,B是A的老师。