您的位置  > 互联网

如何利用MRC(阅读理解)方式做事件抽取任务?

文章链接:

【前言】

本文旨在介绍如何使用MRC(阅读理解)方法来执行事件提取任务。 文章第二部分详细介绍了模型训练步骤以及训练过程中出现的BUG问题及解决方案。

本文基于百度2020语言与智能技术大赛:事件抽取任务。 事件提取的原理、本任务的数据介绍、获取方法、评价标准等内容请参见文章:

大赛官方网站:

理解难度:★★★★★

【这篇文章内容很多,但是介绍的很详细。 只要你耐心阅读,你将会有所收获。 代码或数据可私信作者获取】

1.如何使用MRC来执行这个事件提取任务?

整个工作主要分为三个步骤:

步骤1:事件类型提取

这个内容其实是简化了的。 您无需提取事件类型的触发词,只需知道包含哪些事件类型即可。 (当然,有些同学会使用事件触发词来帮助进行论元提取,所以触发词提取还是需要的。)因此,我可以将这个子任务转化为多标签分类任务。 目前总共有65种事件类型,可以转化为65种多标签分类任务。

借鉴了预测SQL语句中包含什么内容的方法,将这65个事件分别用标签包围起来,然后拼接到原文的后面,如下:

【CLS】今晨泰安发生2.9级地震! 靠近这个国家森林公园[9月][]地震[][]死亡[]....

然后使用BERT类型的模型处理整合后的文本,同时提取所有[]标签位置并拼接成[*65*]矩阵,最后连接一个分类层并使用相同的损失函数进行训练。 模型结构图见图1。

【需要注意的是,事件文本的拼接顺序必须与我们预测的事件索引一致。 】

这种方法与传统方法有些不同:

第 2 步:提取事件参数

使用 MRC 方法执行事件参数提取任务。 在本模块中,我们将访问预测的事件类型(在训练期间直接使用-truth类型)并根据不同事件类型中包含的参数类型构建MRC样本。

【插曲】

[1] MRC问题构建

整合事件类型和参数类型后,可以获得217种不同的标签形式。 我们可以把这217个不同的标签形式看成是217个不同的标签来进行序列标注,然后为每个标签构建一个适合该标签的问题。 分析完这217个标签后,我大致将这些标签分为四类:

设计问题模板后,您可以为每个文本生成 MRC 样本。 代码如下:

for event_type in event_type_list:        complete_slot_str = event_type + "-" + role_type        slot_id = self.labels_map.get(complete_slot_str)        query_str = self.query_map.get(slot_id)        event_type_str = event_type.split("-")[-1]        if query_str.__contains__("?"):            query_str_final = query_str        if query_str == role_type:            query_str_final = "找到{}事件中的{}".format(event_type_str, role_type)        elif role_type == "时间":            query_str_final = "找到{}{}".format(event_type_str, query_str)        else:            query_str_final = "找到{}事件中的{},包括{}".format(event_type_str, role_type, query_str)        return query_str_final

MRC方法进行参数提取可以间接增加数据量。 假设一段文本包含n个不同的事件类型,每个事件类型平均包含m个可以提取内容的参数,那么一段文本可以扩展n*m次,相当于数据增强。

[2] MRC模型构建及跨度预测

使用bert类模型执行跨度提取类型的MRC任务通常可以取得可观的结果。 一些传统的sota方法通常需要分别进行查询和查询,然后设计各种方法(qp和p-等)来充分捕获查询与查询之间的语义关系,并获得最终的答案位置。 Bert类方法通常将query和class拼接在一起,然后统一使用Bert类模型。 最后,整个输出被预测为每个位置的结果。 根据预测的标签类别,可以分为两种方法:

以上两种方法对于这个任务基本上是有效的。 由于某些事件类型的参数提取,一种参数类型可能有两个不同的参数内容。 例如:在离婚事件类型中,离婚当事人的论点必须包含两个不同的人名。 转换成MRC任务后,相当于一个多问题。 此时,第一种标注方法无需修改任何内容,可以直接作为多类别序列标注任务; 但是,对于第二个任务,您需要注意多个开始位置、多个结束位置和开始。 以及端相应的映射处理。 同时还需要注意的是,起始位置和结束位置是相同的。 (详细信息请参见代码中的.py)。

第三步:优化事件参数提取模块

追溯改善问题效果

1、对前述模型预测的结果进行误差后,发现模型存在一些问题。 也就是说,之前构建MRC任务数据时,除了构建文本中包含的参数类型查询样本之外,还人为添加了一些包含的参数类型。 例如,地震类型事件包含的参数类型包括:

{"role": "时间"}, {"role": "死亡计数"}, {"role": "震级"}, {"role": "聚焦深度"}, {"role": "震中" }, {"role": "受伤人数"}

2. 但并非所有文本都会包含所有论点。 例如测试集中的第一个例子:一个7岁的男孩在地震中丧生。 当他的父亲听到这个坏消息时,痛哭起来。 网友:希望不要再有人员伤亡了。 在这个例子中,关于地震的争论似乎都不存在。 在训练集中构造这样的例子时,这样的no query会被添加到训练集中,并且它的标签全部为0(表示非跨度)。

3. 然而,以这种方式构建样本会带来一些问题。 当构建的样本过多时,在测试集上的得分会很低,但得分会相对较高; 而当所有样本被删除时,测试集上的分数会比较高,但分数会急剧下降。 当然,两者的F1成绩提升非常有限。 对于上面测试集中的示例,模型将提取诸如死亡人数:7 岁、死亡程度:7 岁等结果。

4、显然,上述问题不能简单地通过增加或减少数量来解决。 因此,经过长时间的研究和尝试,我们终于找到了一种易于实现且效果良好的方法:Retro-。 这是上海交大大佬的文章,在Squad 2.0的排名还是很高的。 其中,该方法在该问题上表现非常好。 论文的具体解读这里不再解释。 可以参考博文:。

2.模型训练步骤

关于GPU环境配置,如果服务器环境配置程序失败,可以自行安装环境。 以下是我配置环境时的版本控制情况,仅供参考:

bert4keras             0.8.4Keras                  2.3.1scipy                  1.0.0tensorflow-gpu         1.13.1      tensorflow-probability 0.6.0    【如果这个出现错误,则需要看下你的版本是否过高】   termcolor              1.1.0       torch                  1.4.0       tornado                5.1.1

1.生成k折训练数据(根据不同阶段生成两个阶段的k折训练数据):.py

【注意】

2.事件类型提取:.sh

输出得分【代码中使用AUC作为评价指标】:

Saving dict for global step 28036: 0 = 0.5000005, 1 = 0.9444444, 10 = 0.92857146, 11 = 1.0, 12 = 0.99874055, 13 = 1.0, 14 = 1.0, 15 = 1.0, 16 = 1.0, 17 = 1.0, 18 = 1.0, 19 = 0.99715096, 2 = 0.8333334, 20 = 1.0, 21 = 1.0, 22 = 0.99874055, 23 = 1.0, 24 = 1.0, 25 = 1.0, 26 = 0.99874055, 27 = 1.0, 28 = 1.0, 29 = 1.0, 3 = 1.0, 30 = 1.0, 31 = 0.9987437, 32 = 1.0, 33 = 0.9987437, 34 = 0.9583334, 35 = 1.0, 36 = 0.9748712, 37 = 1.0, 38 = 0.99873096, 39 = 0.875, 4 = 1.0, 40 = 1.0, 41 = 0.8888889, 42 = 0.7500001, 43 = 1.0, 44 = 1.0, 45 = 0.7500001, 46 = 1.0, 47 = 0.78444207, 48 = 1.0, 49 = 1.0, 5 = 1.0, 50 = 1.0, 51 = 1.0, 52 = 1.0, 53 = 0.99872774, 54 = 0.8333334, 55 = 1.0, 56 = 0.75000006, 57 = 0.9948718, 58 = 1.0, 59 = 1.0, 6 = 1.0, 60 = 1.0, 61 = 0.99864495, 62 = 0.9907162, 63 = 1.0, 64 = 1.0, 7 = 1.0, 8 = 0.9583334, 9 = 1.0, eval_loss = 0.09869139, global_step = 28036, loss = 0.098691374

3.事件参数提取:bash .sh

输出分数:

Saving dict for global step 64326:f1_end_micro=0.91180056,f1_start_micro=0.90675104,loss=1.1188397

【注意】这部分代码有很多bug。 以下是反复出现的错误和解决方案。 (其他小问题不再重现)

[BUG-1] not such file or directory : neg_fold_data_{}...解决方案:这部分运行前需要将.../event_extraction/train_helper.py中第117-125行的 neg_fold_data_{} 改成 verify_neg_fold_data_{}(行号可能会不一致,我这边修改了代码)

[BUG-2] TypeError: Tensor objects are only iterable when eager execution is enabled. To iterate over this tensor use tf.map_fn.解决方案:作者试了两种label标注,一种是多分类形式,一种是两个类型label,分别预测起始位置,这个问题应该是模型用的是预测起始位置,但是实际上加载的label是多分类,建议看一下代码,换成加载预测起始位置的数据。^_^ train_helper.py——run_event_role_mrc(args)修改处如下:(1)118行train_labels替换成2个labels:train_labels_start & train_labels_end(dev_labels也一样替换掉)(2)173行train_input_fn中的 event_input_bert_mrc_mul_fn 替换成 event_input_bert_mrc_fn 。里面的train_labels也要替换掉。(dev_input_fn同样处理方式)

[BUG-3] NameError: name 'event_input_bert_mrc_fn' is not defined解决方案:在train_helper.py中导入包:from data_processing.event_prepare_data import event_input_bert_mrc_fn

[BUG-4] TypeError: create_optimizer() missing 1 required positional argument: ‘use_tpu'解决方案:正确:将./models/bert_mrc.py中第101行train_op的最后一个参数设置为False。train_op = optimization.create_optimizer(loss,args.lr, params["decay_steps"],args.clip_norm,False)错误:将optimization.py——create_optimizer(…)中的参数use_tpu去掉。会出现TypeError:create_optimizer() takes 4 positional arguments but 5 were given。

4. - EAV 问题可以回答吗? 模块:bash.sh

输出分数:

Saving dict for global step 53606: eval_loss = 0.25366816, f1_score_micro = 0.9475, global_step = 53606, loss = 0.25366816

5.-精读模块:bash.sh

输出分数:

Saving dict for global step 85768: eval_loss = 0.44854522, f1_end_macro = 0.9141987, f1_has_answer_macro = 0.935, f1_start_macro = 0.9093747, global_step = 85768, loss = 0.44854522

三、总结

这些方法仅在当前测试集1上产生了上述效果。由于测试集2数据量较大,模型在其上的泛化性能仍是未知的。 但一定程度上已经达到了验证目的,可以初步得出以下结论:

附:一些标签映射文件介绍

1..txt

比赛中给出了样本中包含的65种事件类型,每种事件类型都有不同数量的参数类型。 将事件类型和参数类型合并为一种标签类型后,可以获得 217 种类型。 例如,将销售/收购与卖家进行整合,得到销售/收购-卖家的标签形式。

例子:

金融/交易-降价-降价商品0

金融/交易-降价-时间21

金融/交易-降价-降价36

金融/交易-降价-降价派对204

类别-事件类型-事件角色1/事件角色2/…

2. .txt(本来没有提供这个文件,需要自己生成一个[示例要求见代码])

例子:

金融/贸易-销售/收购0

金融/交易-跌幅限制1

金融/交易-加息2

金融/交易-降价3

金融/交易-降息4

金融/贸易融资 5

金融/交易-上市 6

金融/交易-涨价7

金融/交易-限额 8