前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Kaggle TensorFlow 2.0 Question Answering 16名复盘

Kaggle TensorFlow 2.0 Question Answering 16名复盘

作者头像
朴素人工智能
发布2020-04-21 18:08:09
7360
发布2020-04-21 18:08:09
举报
文章被收录于专栏:朴素人工智能朴素人工智能

比赛海报

这是Kaggle春节前结束的一个阅读理解的比赛[1],我和管老师曹老师最终获得16/1233的成绩。成绩来自于管老师的提交,我自己的最好成绩大概排在23名的样子,不好不坏,略低于我们的预期。

数据集

这次比赛的数据集来自于Google,名为Natural Questions,简称NQ。数据集早在19年初就已经公布,在官网[2]上还有排行榜。

这个数据集和SQuAD挺像的,关于SQuAD的介绍大家可以在这篇文章中找到。NQ的训练集包含30多万个样例,每个样例包含一篇来源于维基百科的文章和一个问题。每篇文章可以被分为多个“候选长答案”,所谓候选长答案,可能是一个段落、一张表格、一个列表等等。候选长答案有可能有包含关系,但大部分的标注出来的长答案(95%)都是顶层候选长答案。在所有样例中,有大约一半样例的问题可以用候选长答案来回答。对于有的问题,还可以用更加简短的文章区间来回答,这种区间称为短答案。大约有三分之一的样例可以用短答案来回答。短答案并不一定是一个连续区间,有可能是多个离散的区间。

从上面的描述可以看出这个数据集比SQuAD复杂不少,大家可以到这个页面[3]看一些官方提供的可视化样例。由于复杂,NQ的难度也比SQuAD大不少,我认为主要体现在两点:

  1. 文章来源于维基百科原文,并不是像SQuAD一样已经筛选好了段落。这就导致NQ的文章通常很长也很乱。长比较好理解,乱主要体现在数据中会有大量表格、列表以及用来标示这些的HTML保留字。这些内容不能舍弃,因为问题的答案有可能出现在这些部件中。
  2. 答案的种类更加多样。SQuAD1.x只需要预测答案区间,2.0增加了不可回答问题。NQ更进一步,多了找长答案这个任务,短答案除了普通的区间还增加了两种特殊的形式:Yes或No

Kaggle这次的比赛形式是Kernel赛,允许参赛者线下训练,但必须在线上完成测试集的推理,推理时间限制为2小时。

Baseline

谷歌针对这一数据集提供了一个开源的Baseline模型[4],并且有一篇简短的论文[5]介绍这个模型。这里需要介绍一下它,因为它真的是一个很强的基线模型。

模型叫做BERTjoint,joint的含义是用单个模型完成长答案和短答案的寻找。这个模型的基础是BERT,输入进BERT的数据由问题和文章正文用[sep]拼接而成。因为文章正文很长,所以使用了滑窗的方式截取正文,默认的步长是128个token。由于问题长度几乎都很小,所以如果模型的输入长度是512个token的话,大概每个token会出现在四个滑窗样本(chunk)里。

具体的数据集构造过程大概如下:

  1. 取出所有的顶层候选长答案,在其头部增加特殊token,表明该候选长答案的类型(段落、表格、列表)和位置,即是全文的第几个该种类型候选长答案;
  2. 将处理过的候选长答案拼接起来,进行滑窗处理。将每个窗口的正文与问题拼接,组成一个输入样本;
  3. 样本的标签包含两部分:类型和起止位置。若样本中包含短答案,则该样本的答案类型为"short",若没有短答案而有长答案则标注为"long",其他情况均为"unknown",short类型有可能分化为"yes"和"no"两种特殊类型。有短答案时,起止位置是第一个短答案的起止位置,当只有长答案没有短答案的时候,起止位置标为整个长答案的头和尾,当不包含答案时,起止位置都标为0,即指向[CLS];
  4. 如果该样本的类型为"unknown",则有98%的概率将这个样本丢弃,98%这个比例是为了使最终制作出来的训练集里面不含答案的样本大概占50%

训练的方法比较简单,对于找开头和结尾这个标准阅读理解任务,直接在BERT后面加了一个全连接,然后对概率在句子维度上做softmax;对于答案类别分类则是在[CLS]的输出后面跟一个全连接。论文中指出,训练1轮的效果是最好的,我们在训练时也使用了这个设定。

baseline使用的模式和这张图非常像,只是扩展了可回答性的部分

推理过程如下:

  1. 对于每个长度为512的文本窗口,得到512个位置作为开始和结束位置的概率,以及该样本属于5个答案类型的概率;
  2. 分别找出概率最高的k个开始位置和结束位置;将这些开始和结束位置两两组合,留下符合要求的区间。这里的要求有两个,一是开始位置必须小于等于结束位置(区间必须合法),二是区间长度小于某个设定的值(是一个可调节的超参数);
  3. 一篇文章对应的所有符合要求的区间按概率进行排序,找到最好的那个区间作为短答案,并记录区间概率,找到包含这个区间的候选长答案作为长答案,用短答案区间概率作为长答案概率,并记录答案类型概率;
  4. 通过阈值进行筛选,保留大于阈值的长答案和短答案。如果被保留长答案的YES或NO答案类型概率大于0.5,则将短答案改为YES或NO。

可以看出,由于输入输出明确,模型其实是整个pipeline里最简单的部分。而对于长文本、长短答案、特殊答案类型的处理大多是有数据预处理和后处理来完成的。其实这也说明一个道理,模型能力不是一个算法工程师最重要的能力,它们已经被封装得越来越好,使用门槛越来越低。

我的做法

当时看了Baseline之后我感觉这个解法有几个比较明显的不足:

  1. 答案只有在长度小于一个chunk长度时才会被认可,这样会导致长度较长的长答案label丢失;
  2. 只取了第一个短答案,也会丢失信息;
  3. 将所有候选长答案拼接之后再滑窗增大了寻找长答案的难度。

针对这几个方面,我决定走一条和baseline不同的道路,主要操作如下:

  1. 我不再拼接候选长答案,而是直接对每个候选长答案进行滑窗。也就是说,我的每个样本里不会含有来自多个候选长答案的文本,这样我就可以用[CLS]来做针对某条具体长答案的分类任务。
  2. 候选长答案里不包含任何标题,而我发现有的表格或列表如果不看标题根本不知道它们的主题。所以我单独写了一段处理代码来引入标题。
  3. 在标签中加入所有的短答案开头结尾,将找开头结尾的loss改为BCE loss。

由于我不会用TensorFlow,所以我花了很长时间把相关代码写成pytorch;整个调模型的过程也并不顺利,一方面是训练所需时间特别长,另一方面是因为我很晚才建立起比较稳健的线下验证体系。我的LB成绩一直比曹老师的baseline模型低很多,这让我有点慌了阵脚。赛后回看,曹老师的模型很严重地过拟合了LB,这也是比较诡异的一件事情。当时我们就发现了这种可能性,所以我也很乐观地相信我们前面有队伍肯定过拟合了,并在结束前把队名改成了“Shake up过新年”。虽然没有在B榜上升到金牌区,但确实上升了10名。

最终我最好的成绩来自一个BERT Large WWM SQuAD和Roberta SQuAD的融合,线下F1 是0.66,线上LB 0.638,private score 0.670。两个单模的线下CV都是0.65左右,融合带给我大概0.01的boost。

这次比赛里我踩的一个大坑是我前期为了加快训练,一直使用的是基于序列长度的Bucket Sampler。这个trick使我的训练速度提高了一倍,但模型的性能却大打折扣。一开始我并不知道有这个情况,直到最后几天我为了使用多卡训练的distributedSampler而无法使用这个技巧之后成绩突然猛增,我才意识到这个问题。后来回想,由于我是对单个长答案进行直接滑窗,不同训练样本的非padding长度差异比较大,这么训练很可能带来bias。但这个技巧在提升推理速度方面是很有成效的,后面有机会的话给大家介绍这个技巧。

这次比赛的另一大遗憾是队伍里的模型没有很好地融合起来。这是因为我们一开始没有考虑到这一题的特殊性带来的融合难度。首先我们都没有做检索用的快速模型,这导致我们推理的时间都比较长,即使融合也只能塞2-3个模型。第二是由于大家的预处理不太一样,输入token层面就产生了差异,需要提前将预测结果映射回word空间再进行融合,但我们一开始没有想到这点,到后面已经来不及了。

做的比较好的地方也有一些,例如在比赛最后几天Huggingface放出了他们Rust实现的新版Tokenizer,比原来快了好几倍,我在第一时间使用了这个库;预处理的代码经过精心考虑,做了很多缓存,大大加快了速度等等。

前排方案

总体来看大家都没有超出baseline的模式,但还是有很多值得借鉴的地方。曹老师在讨论区开了一个帖子[6],收集了所有的金牌方案

第一名

很开心地看到Guanshuo老师使用了和我一样的数据生成模式,但他做了很重要的一步就是hard negative sampling。他先用一个模型得到了训练集所有候选长答案的预测概率,然后根据这个概率来对负样本进行采样。每个模型训练了3-4个epochs。相比于我们直接随机采样负样本(baseline只保留了2%的负样本)这种方式更能保留有训练价值的数据。这个技巧应该会在后面的比赛中被越来越多地使用。

大佬的另一个牛逼之处就是由于做了上面的操作,他可以在推理的时候很自然地也引入这个机制。他使用一个bert base来事先筛选出可能性较高的候选长答案,然后再用大模型求短答案。因此他在最后融合了惊人的5个模型!而且推理时间只有1小时。这个方式我们也想到了,但是在线下实验的时候可能由于代码写错了没有成功。

在训练方面,预测开始和结束位置的loss只有在训练样本为正样本时才会被计算。

第二名

老师的方法很朴素,几乎跟baseline一样。他说他的关键点也是采样,他调高了负样本的保留概率。

第三名

融合了3个模型,3个模型在训练时的主要差别是使用了不同的滑窗步长生成的数据以及略有差异的训练目标。

第四名

一方面提高了负样本比例,另一方面使用了知识蒸馏。知识蒸馏之后的学生模型在验证集上相比教师模型有0.01的boost。

后记

可能是由于运算时间的限制或者评测数据集不一样,我发现Kaggle竞赛冠军的成绩也没有达到官网Leaderboard最顶尖的水平。但很神奇的是这个task几乎没人发paper,所以无从知晓排行榜上大佬们做了什么操作。如果大家对阅读理解有兴趣,可以尝试一下这个任务。不像SQuAD里机器已经大幅超越人类,在这里目前机器的水平离人类还有较大差距,巨大的空间等着大家探索。

参考资料

[1]

比赛链接: "https://www.kaggle.com/c/tensorflow2-question-answering/overview"

[2]

Natural Questions官网: https://ai.google.com/research/NaturalQuestions

[3]

数据可视化页面: https://ai.google.com/research/NaturalQuestions/visualization

[4]

Baseline模型: https://github.com/google-research/language/tree/master/language/question_answering

[5]

Baseline论文: https://arxiv.org/abs/1901.08634

[6]

已公开金牌方案: https://www.kaggle.com/c/tensorflow2-question-answering/discussion/127409

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-03-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 朴素人工智能 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 数据集
  • Baseline
  • 我的做法
  • 前排方案
    • 第一名
      • 第二名
        • 第三名
          • 第四名
          • 后记
          • 参考资料
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档