/ NLP, DEEPLEARNING

事件抽取实战

一. 事件抽取

1.1 事件抽取定义

在NLP中,我们经常听到NER实体抽取,但是对于事件抽取却相对陌生。这里简单介绍下什么是事件抽取。

  • 定义:对非结构化的文本抽取感兴趣的事件信息,并用结构化(论元)进行展示,即事件要素提取。
  • 应用:抽取的事件可以广泛应用在舆情发现领域;同时事件抽取其实也是知识图谱的构建中的重要一环。

下面用百度的LIC2021事件抽取任务来举例,事件抽取到底做了什么事?

如上图所示,给定了一个句子,通过事件抽取我们可以得到:

  • 2个目标事件:竞赛行为-胜负,竞赛行为-夺冠
  • 各自事件的论元:比如事件的发生时间/地点,以及胜者、败者、冠军分别是谁。

1.2 事件抽取拆分

通过上面的例子,我们可以看出其实所谓的事件其实就是将论元(实体)、触发词(实体)在文本中抽取出来;同时,也需要将论元和触发词进行关系匹配。因此,我们可以将事件抽取任务进行拆分,即:

  • 实体抽取(NER):抽取触发词(动作实体)、抽取论元(名次实体)
  • 关系抽取(RE):判定俩俩实体之间是否存在关系(什么类型关系)

二. 相关模型实战

目前,事件抽取模型一般分为pipeline模型(流水线)和联合抽取模型。

  • pipeline流水线式模型:先训练一个实体抽取模型,然后再训练一个关系抽取模型,两个模型相互不影响。
  • joint联合抽取式模型:将实体抽取和关系抽取放在同一个模型上,这里的loss = 实体抽取loss + 关系抽取loss。

由于BERT的预训练模型在样本较少的情况下,文本特征抽取上有很大优势,因此下面所有实战的模型基本都是基于BERT的。

本人更喜欢torch,因此下面所有实战的代码都是基于pytorch框架的。

2.1 Pipeline模型

我这边使用的方案是:

2.1.1 NER模型

推荐的git仓库:https://github.com/cooscao/Bert-BiLSTM-CRF-pytorch

NER模型这边选用的文本标注模式为BIOES这种模式,其原因是对比BIO这种标注模式而言,对于实体最尾端的“E”标识能更好的帮助我们在预测时找到实体的尾端,对于错误实体的修正更加有帮助。比如预测的结果为”I-I-I-O-I-I”(BIO)可能识别为2个实体,然是如果是BIOES预测结果为“I-I-I-O-I-E”则其实就是一个实体。

这边主要介绍下选用的层的各自作用:

  • BiLSTM层:能解决文本实体的顺序问题,比如抽取的时间中要区分“开始时间”和“结束时间”。
  • CRF层:给最终预测的标签添加一些约束来保证预测的标签合法性(其实效果不是很显著)
2.1.2 RE模型

推荐的git仓库:https://github.com/monologg/R-BERT

选用该模型的原因:它的模型结构和我起初看的snow ball(雪球算法)很像,在抽取的关系规则上很近似,都是开始词向量 + 实体1 + 中间词向量 + 实体2 + 尾词向量

这里再推荐一篇基于上面稍加改造的文章(很秀,就加上了“开始词向量” + “中间词向量” + “尾词向量”就能发文章了。。。):基于信息增强BERT的关系分类

模型结构:

  • BERT层:文本特征抽取
  • 表征关系:将[cls]表征的全句特征 + 实体1的特征 + 实体2的特征
  • 分类层:输出关系分类

这里需要说下关于这个关系抽取模型的弊端:当一条文本中包含大量的实体对需要预测其关系时,哪怕是用batch预测,消耗的时间也是很长的。

因此如果真的是用这个模型的话,还是推荐自己改下dataset格式和模型结构,具体可以参考我下面使用的Spert模型结构中的关系抽取部分。

2.2 Joint联合抽取模型

我这边选用的模型是:Span-based Entity and Relation Transformer

原因其实是上面的NER模型用的是BIOES这种数据模式,联合模型我这边就想用span这种标注方式。span最大的优势就是能够解决实体嵌套的问题。比如”武汉长江大桥”这个实体,其实本身是一个“桥”类实体,但是“武汉”两个字也是“市”类的实体,即实体嵌套。

官方的git仓库:https://github.com/lavis-nlp/spert

在官方的代码中,模型的训练阶段和推理阶段其实模型结构是不同的:

  • 训练阶段:
    • 数据加载:创建实体的负样本和关系的负样本。实体的负样本可以理解为图像中的预选框,只不过这里是随机创建并抽样比指定最长实体短些的实体;关系的负样本则是基于俩俩实体是真正实体却没有关联。
    • 模型部分:对输入的不同长度的span实体利用嵌入层表示,通过实体的分类器输出实体的logits;在关系分类中,这里是定义了max_pairs作为步长,预选的关系对作为最大长度,滑块的形式来预测关系对,最终将其拼接。
  • 推理阶段:
    • 数据加载:创建所有的实体候选
    • 模型部分:同训练部分 + 实体过滤器(过滤为None的实体)

看了这么多的代码,还是觉得spert作者的这篇repo的代码最棒!

三. 训练的小tricks

  1. 选用的BERT预训练模型(中文)我这边选用了一大一小两个模型来测试:chinese-roberta-wwm-extalbert-chinese-tiny
  2. 在训练大模型的时候,这里推荐freeze住bert层;而训练小模型的时候,不冻结bert层。
  3. 在训练的时候发现loss变为nan的时候,在模型和数据都没啥问题的情况下,推荐使用模型剪枝(clip_grad)来处理。
  4. 设置不同的学习率:在模型的训练阶段对于不同的层我们可能会需要设置不同的学习率,尤其是当我们在预训练模型的基础上对下游任务进行微调,对于预训练模型需要设置较小的学习率,而其他一些我们自定义的层则需要较大的学习率。
  5. 学习率预热:当设置较大的学习率的时候,会导致模型loss震荡;warm up则可以使得在在最初的几个epoch中lr较小,之后模型趋于稳定后,lr变大到和自己设置的lr一致。