416. Riiid Answer Correctness Prediction | riiid-test-answer-prediction
感谢各位Kaggler的讨论和支持。我在这次比赛中学到了很多,也尝试了很多东西。这里列出了一些我认为效果不错的做法。我会尽量覆盖整个模型,但后续可能会进行更新以澄清和补充说明。
我的架构仅由3个编码器(Encoder)模块堆叠而成,使用512作为d_model,每个模块包含4个编码器层。
我的问题、交互和回答序列都在左侧通过一个由用户历史统计数据生成的唯一向量进行了填充。这确保了所有3个序列在序列号上是对齐的。
将Q-Block和QRI-Block的输出拼接,输入到3个线性层中。
我将较长的序列分割为长度为256、重叠为128的窗口(0-256, 128-384, 256-512)。
我将它们保存在tf.records中,在推理过程中,我从每个256长度的序列中均匀随机选取一个128长度的窗口。这确保了任何事件都有相等的概率被放置在我训练序列的0-127位置(不包括用户最后一个窗口中的最后128个事件)。对于序列长度较短的用户,我用0填充至长度128,并每次使用这128个数据。
最初我只使用Kaggle GPU来训练模型。在比赛的最后3周,我开始建立TPU流水线,最终也成功使用了TPU。我想我已经用完了最后3周的全部TPU配额。
我要感谢 @yihdarshieh (TPU Guru) 提供的 TPU notebook。这对建立TPU流水线帮助很大。
我使用了2种编码来捕捉事件的序列。我减去了每个序列中的第一个值,以确保所有编码都以0开始。
为此,我将时间戳转换为分钟,并使用了与位置编码相同的方案,幂次为 (60 x 24 x 365 = 1年)。我相信这使模型能够知道两个问题/交互在时间上相隔多远。我认为这是SAINT+中使用的滞后时间变量的更好实现,因为它同时捕捉所有事件之间的时间差,而不仅仅是两个相邻事件之间的差。
为此,我使用task_container_id作为位置,幂次为10,000。
除了使用回答中的0/1之外,我还添加了一个新特征,作为回答错误时的知识启发式代理。如果用户在正确答案是1的情况下选择了选项2。我会计算(该问题选择2的总次数)/(该问题被回答错误的总次数)。这个比率在推理期间也会维护和更新。我检查了错误回答的proxy_knowledge平均值与整体用户准确率之间的相关性,大约是0.4。
这是一个简单的特征,计算方式为(该问题被回答正确的总次数)/(该问题被回答的总次数)。这个比率在推理期间也会维护和更新。
我使用了不对同一捆绑包中的事件进行关注的自注意力掩码。对于QRI模块的最后一层,我移除了对角线上的掩码条目,以确保它只关注先前的值。
对于起始向量,我使用了在问题和讲座中看到的每个标签被看到/回答正确的次数计数。只是使用了一个全连接层将其编码为序列的第一个向量。
我将内容ID、部分ID、标签ID的加权平均和类型(来自讲座)的嵌入相加,然后将其与两种序列编码拼接