返回列表

22nd Place Solution: Just Encoder Modules

416. Riiid Answer Correctness Prediction | riiid-test-answer-prediction

开始: 2020-10-05 结束: 2021-01-07 学习效果预测 数据算法赛
第22名方案:仅使用编码器模块

第22名方案:仅使用编码器模块

作者: AbdurRafae | 比赛排名: 第22名

感谢各位Kaggler的讨论和支持。我在这次比赛中学到了很多,也尝试了很多东西。这里列出了一些我认为效果不错的做法。我会尽量覆盖整个模型,但后续可能会进行更新以澄清和补充说明。

架构

我的架构仅由3个编码器(Encoder)模块堆叠而成,使用512作为d_model,每个模块包含4个编码器层。

我的问题、交互和回答序列都在左侧通过一个由用户历史统计数据生成的唯一向量进行了填充。这确保了所有3个序列在序列号上是对齐的。

  1. 仅问题模块: 该模块仅对问题进行自注意力计算。
  2. 仅交互模块: 该模块仅对交互进行自注意力计算。
  3. 问题、回答、交互模块: 我使用Q-Block的输出作为查询,I-Block作为键,回答/(QRI Block)作为值。注意力模块后的残差连接使用的是值向量(而不是默认的查询向量)。前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模块的最后一层,我移除了对角线上的掩码条目,以确保它只关注先前的值。

起始向量

对于起始向量,我使用了在问题和讲座中看到的每个标签被看到/回答正确的次数计数。只是使用了一个全连接层将其编码为序列的第一个向量。

其他细节

  • 使用了TF文档中Transformers页面提供的Noam学习率。
  • 批次大小1024(在TPU上训练 - 1个Epoch耗时4-6分钟)。
  • 对于验证,我最初只是分离了大约3.4%的用户并使用他们的序列。
  • 模型在大约20-30个Epoch收敛。(最长训练时间4小时)。
  • 使用的序列长度为128。

问题嵌入

我将内容ID、部分ID、标签ID的加权平均和类型(来自讲座)的嵌入相加,然后将其与两种序列编码拼接

同比赛其他方案