返回列表

Single Model - 1.5 Transformers - 31st place

360. 2019 Data Science Bowl | data-science-bowl-2019

开始: 2019-10-24 结束: 2020-01-22 学习效果预测 数据算法赛
单模型 - 1.5个Transformer - 第31名
作者:Zac Warren | 排名:第31名

单模型 - 1.5个Transformer - 第31名

这是一次极具挑战性且非常有趣的比赛。我要感谢赞助商、Kaggle,当然还有所有优秀的参赛者!

我还要向Kaggle/Google的工程师们致敬。我对网站和Kernels印象深刻。我目前是亚马逊的全栈工程师,所以我深知要做到这一点有多难,感谢你们打造了如此出色的产品!

数据准备

我对事件代码(event codes)做了一些细微的修改。我将4020、4010、4025根据正确与否进行了拆分,最终得到了40201、40200、41001、41000、40251、40250。

我将数据转换为“历史记录”。基本上,一段历史记录是指目标评估之前的所有数据。然后我将这些历史记录处理成一个大型numpy数组。

由于设备共享导致某些历史记录非常庞大,我决定针对每个目标评估取最后X个游戏会话,以及每个游戏会话取最后Y个事件。这对我来说很有意义,因为最近的数据应该更重要,而且这是处理共享设备问题的一种简单方法。

我发现 x = 80 和 y = 100 的效果最好,因此最终得到了一个稀疏的numpy数组:(历史记录数, 80, 100, 特征数)。

我在标题(title)、事件代码(event_code)和准确度组(accuracy group)的嵌入中添加了“空白”类别。这告知模型这些内容不存在。(历史记录少于100个事件或少于80个游戏会话)。我尝试对Transformer的输入进行掩码处理,但这严重损害了性能,分数反而下降了。

特征工程

你可以在下方的模型图中看到这些特征。评估目标标题和评估目标时间被输入到每一个事件中。我确实尝试过在模型末尾输入一次这些信息,但性能略有下降。我还尝试将游戏会话特征插入到游戏会话嵌入中,但我尝试的特征都没有起到帮助作用。

在比赛接近尾声时,我尝试加入OOF(Out-of-Fold)模型的预测结果和模型的预测组。这在本地CV(交叉验证)上似乎有很大帮助,但在测试集上效果没那么明显。我想可能我在为测试评估创建这些值的方式上有些问题。

模型

我最初的想法是使用双重Transformer网络。一个Transformer用于处理游戏会话的事件,然后利用这些输出让另一个Transformer接收每个游戏会话的嵌入。这确实有效,但我发现如果第一个Transformer是一个“零头”,效果会更好(也更快得多),这意味着我只是移除了注意力部分,保留了共享的全连接层:

events2 = self.linear2(self.dropout1(F.relu(self.linear1(events))))
events = events + self.dropout2(events2)
events = self.norm2(events)

一个关键的想法来自NLP领域,他们将输入和输出中的词嵌入绑定在一起以提高泛化能力。我在准确度组上应用了这一方法,发现QWK(二次加权Kappa系数)有了不错的提升。

模型架构图

噪声标签

这是我尝试过的比较有趣的想法之一。考虑到3-5岁的孩子通常非常不可预测,你可以认为标签是相当嘈杂的。我阅读了大量关于处理噪声标签的论文,它们基本上都是让模型(或第二个模型)学习噪声。我决定保存OOF预测,然后在训练新模型时将这些预测与实际目标混合,希望单模型能够学习到一些噪声模式。结果证明这很难正确调整,因为它似乎泄漏到了我的5折CV中,并且使得训练损失难以分析。基本上,这很容易导致对训练数据的过拟合。

我也不确定我做OOF预测的方式是否最佳。我会在每次5折运行后保存它,然后取过去所有预测的平均值。我现在认为这可能增加了确认偏差,如果我只是从一些未与新目标混合的模型中获取初始预测并坚持使用这些预测,效果可能会更好。

最终,它确实将我的私榜分数从0.550提高到了0.554。我最好的混合方案是进行85个epoch,从所有实际目标开始,到第50个epoch时变为50/50混合,然后再逐渐增加实际目标的比例。混合比例每个epoch线性变化1%,即旧/新:1/0 -> .5/.5 -> .85/.15。

同比赛其他方案