361. TensorFlow 2.0 Question Answering | tensorflow2-question-answering
感谢 Kaggle 和 Tensorflow 团队举办这次比赛。我是问答(QA)领域的新手,花了 5 周多的时间才完成第一次正式提交,在这个过程中我学到了很多东西。参加比赛前我的初步计划是学习 QA 和 TF2.0,但最后我没有时间接触 TF2.0,所以我的解决方案完全使用 PyTorch 实现。感谢 @sakami 提供的优秀内核 https://www.kaggle.com/sakami/tfqa-pytorch-baseline。你的内核是我旅程的起点。当然也要感谢 huggingface (https://github.com/huggingface/transformers),让 NLP 微调变得更加容易。
我的解决方案描述如下。
我是在提供的候选答案上进行训练的,而不是像基线论文 (https://arxiv.org/abs/1901.08634) 中那样从原始文档(示例)中采样。由于训练数据中总共有 4000 万个候选答案,对于每个 epoch,我只从每个文档中采样一个负样本候选答案。为了更高效的训练,我使用了难负样本采样来代替均匀随机采样。最终的提交是五个模型的集成。
最初,我尝试对负样本候选进行均匀采样,但结果不尽如人意。原因可能是大多数负样本候选“太容易”了,模型可能只需要学习一些“基本”模式就能获得良好的候选级别分类性能。但在测试阶段,我们的实际目标是从每个文档中预测最可能的正样本候选,这个文档级别的分类是一个更困难的任务。因此,我用难负样本采样代替了均匀采样,以增加候选级别训练的难度,正如预期的那样,性能得到了大幅提升。为了在后续模型中进行难负样本采样,我首先训练了一个使用均匀采样的模型,并在整个训练数据上进行预测,存储每个负样本候选的答案概率。最后一步是将文档内负样本候选的概率归一化以形成分布。对于接下来的模型训练,负样本候选可以从该概率分布中采样。
根据基线论文,我添加了 HTML 标签作为新标记以获得更好的模型性能。我添加了 https://github.com/google-research-datasets/natural-questions 数据统计部分的所有 9 个标签。对于不在 9 个添加标记中的 HTML 标签,我用标记字典中的唯一标记替换它们,或者简单地添加另一个新标记来表示它们。我没有时间尝试像基线论文那样添加段落或表格编号。
总体而言,模型架构与基线论文相同(一个 5 类分类分支 + 2 个跨度分类分支)。这五个类别是 "no_answer"(无答案)、"long_answer_only"(仅长答案)、"short_answer"(短答案)、"yes"(是)、"no"(否)。在我的情况下,没有短答案跨度的答案没有跨度预测,因为我直接使用了候选答案。在训练期间,如果不存在短答案跨度,则简单地忽略跨度预测分支的损失更新。在测试阶段,对于每个文档,我使用 1.0-prob(no_answer) 作为每个候选的长答案得分(置信度),并选择置信度最高的候选来代表该文档。短答案跨度被强制限制在得分最高的长答案候选内(不确定这是否必要)。我使用 prob(short_answer)+prob(yes)+prob(no) 作为短答案得分。短答案的确切类别由这三个概率值的最大值决定。对于跨度预测,输出标记级别的概率被映射到单词级别(空白分词)概率,以便于不同分词器模型的集成。
我的最终提交是一个 Bert-base、两个 Bert-large (WWM) 和两个 Albert-xxl (v2) 模型的集成,全部为不区分大小写版本。Bert large 和 Albert 模型在训练前已在 SQUAD 数据上进行了微调。下面列出了使用代码 https://github.com/google-research-datasets/natural-questions/blob/master/nq_eval.py 在开发集上的验证性能。我没有尝试实现比赛指标。
| 模型 | 长答案最佳阈值 F1 (long-best-threshold-f1) | 短答案最佳阈值 F1 (short-best-threshold-f1) |
|---|---|---|
| Bert-base | 0.618 | 0.457 |
| Bert-large | 0.679 | 0.541 |
| Albert-xxl | 0. |