446. CommonLit Readability Prize | commonlitreadabilityprize
首先,我要非常感谢我优秀的队友 @horsek 和 @lftuwujie,在这次比赛中我从他们身上学到了很多。其次,感谢所有参赛者、竞赛主办方以及 Kaggle 组织了这场精彩的比赛。此外,祝贺所有位于金牌区的队伍。
在比赛开始时,我告诉 @horsek,让我们把它当作学习 NLP 的机会,因为我之前没有这方面的知识,这是我的第一次 NLP 比赛。
在这篇文章中,我想分享的不仅是我的模型,还有我在这次比赛中的旅程,从起点到像我这样的新手如何形成思路。
我的旅程始于这个精彩的笔记本 (CommonLit Readability Prize - RoBERTa Torch Fit),请给他/她点赞。
然而,我没有直接 fork 它,而是阅读并重写了自己的笔记本。这帮助我进行调试,并形成了一些想法来进一步定制和优化流程。
经过这一步,我最好的单模型是 RoBERTa-large,在公榜上得分 0.468,在私榜上得分 0.469。我的推理笔记本在这里。
该模型的设置如下:
在这次比赛(以及我所有的比赛)中,我学习了很多相关文献。这一步帮助我理解了比赛的概念并找到了新的思路。我得到的第一个想法是二次加权 Kappa 损失,第二个是自定义 Bradley-Terry 损失。
这个损失的想法来自这篇论文。其主要思想是将节选的目标值分箱到类别中,并训练模型将它们分类到这些类别中。在我看来,这一步相当于标签平滑,因为它将具有相似目标的节选归入同一个箱。QWK 损失不是必须的,你可以用交叉熵损失来实现它。这是 QWK 损失的实现:
class QuadraticWeightedKappaLoss(nn.Module):
def __init__(self, num_cat = 7, device = 'cpu'):
super(QuadraticWeightedKappaLoss, self).__init__()
self.num_cat = num_cat
cats = torch.arange(num_cat).to(device)
self.weights = (cats.view(-1,1) - cats.view(1,-1)).pow(2) / (num_cat - 1)**2
def _confusion_matrix(self, pred_cat, true_cat):
confusion_matrix = torch.zeros((self.num_cat, self.num_cat)).to(pred_cat.device)
for t, p in zip(true_cat.view(-1), pred_cat.view(-1)):
confusion_matrix[t.long(), p.long()] += 1
return confusion_matrix
def forward(self, pred_cat, true_cat):
# 混淆矩阵
O = self._confusion_matrix(pred_cat, true_cat)
# 统计每个类别的元素数量
true_hist = torch.bincount(true_cat, minlength = self.num_cat)
pred_hist = torch.bincount(pred_cat, minlength = self.num_cat)
# 期望值
E = torch.outer(true_hist, pred_hist)
# 归一化
O = O / torch.sum(O)
E = E / torch.sum(E)
# 加权 Kappa
numerator = torch.sum(self.weights * O)