返回列表

Brief summary of 13th place solution (hide the pain Harold)

361. TensorFlow 2.0 Question Answering | tensorflow2-question-answering

开始: 2019-10-28 结束: 2020-01-22 自然语言处理 数据算法赛
第13名方案简报

第13名方案简报

作者: Dmytro Danevskyi, Yury Kashnitsky, Oleg Yaroshevskiy
比赛排名: 第13名(金牌区)

首先,恭喜所有排名在我们之上的团队——既然我们获得了第13名,这意味着你们所有人都进入了金牌区。

我们最好的解决方案由4个基于BERT的模型组成:1个DistilBERT,2个ALBERT-large,以及1个BERT-large WWM。

@kashnitsky 训练了我们最好的单模型,在本地验证集上得分约为0.64(我们使用官方NQ验证集进行验证)。他使用了原始的bert-joint代码库,并添加了论文中描述的技巧。他还花了很多时间尝试让ALBERT模型工作,但没有一个(甚至是xxlarge版本)比bert-large更好。

@yaroshevskiy 在PyTorch中实现了他自己的bert-joint版本,包括所有的预处理和后处理工作。他最好的模型基于在SQuAD 2.0上预训练的ALBERT large。Oleg还提出了一个可以称为“窗口平滑”的技巧。该技巧解决了窗口边缘开始/结束概率预测不佳的问题。其思想是,对于那些靠近窗口边缘的开始/结束logits,我们使用当前窗口logits和相邻窗口logits的线性组合。这将分数提高了大约0.01-0.02。

我实现了自己的PyTorch模型,该模型与bert-joint有两个不同之处:

  • 不是在任意分块的文本上工作,而是在长答案候选上工作
  • 开始/结束logits是通过类似注意力的层联合预测的,不切实际的开始/结束位置(如填充或问题标记)被填充为-inf

开始/结束模块的实现如下:

class StartEndModule(nn.Module):

    def __init__(self, input_dim, hidden_dim):
        super().__init__()

        self.start = nn.Conv1d(input_dim, hidden_dim, kernel_size=1)
        self.end = nn.Conv1d(input_dim, hidden_dim, kernel_size=1)

    def forward(self, hidden, text_mask):

        start = self.start(hidden).unsqueeze(3)
        end = self.end(hidden).unsqueeze(2)

        logits = (start * end).sum(dim=1)
        triu_mask = torch.triu(logits, diagonal=1) == 0
        text_mask = ((text_mask.unsqueeze(2) * text_mask.unsqueeze(1)) < 0.5)
        mask = text_mask | triu_mask
        mask[:, 0, 0] = False
        logits.masked_fill_(mask, float("-inf"))

        return logits, mask.float()

因此,logits是一个方阵,其中每个条目是特定开始/结束对的分数。为了找到得分最高的跨度,只需要计算这些分数的argmax。

我还对两个模型应用了SWA,并获得了不错的分数提升(约0.015),而Oleg和Yury报告说SWA没有带来改进或只有微小的改进。

为了加速推理,我们使用DistilBERT模型进行候选预评分。其思想是,对于除DistilBERT以外的所有其他模型,我们忽略那些从DistilBERT模型获得低分的窗口/候选。

为了融合我们的模型,我们使用了LightGBM提升树。对于每个候选,我们收集所有模型的相应分数以及一些元特征(例如答案长度或该候选在文档中的相对位置),目标是预测该候选是否包含答案。

我们最好的融合在本地验证集上达到了约0.67,在排行榜(LB)上达到了0.68。

同比赛其他方案