361. TensorFlow 2.0 Question Answering | tensorflow2-question-answering
首先,恭喜所有排名在我们之上的团队——既然我们获得了第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有两个不同之处:
开始/结束模块的实现如下:
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。