返回列表

#8 solution: Ensemble of 15 same NN models

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

开始: 2020-10-05 结束: 2021-01-07 学习效果预测 数据算法赛
#8 解决方案:15个相同NN模型的集成

#8 解决方案:15个相同NN模型的集成

作者:chlxyd (Grandmaster) | 比赛排名:第8名

大家好,
从其他人的解决方案中学到了很多!在这篇文章中,我想分享一些关于我的解决方案的见解。

总而言之,我最好的单一NN模型可以达到813/815的公开/私有分数,通过5折集成和每折3个快照,最终15个nn模型达到了814/816的公开/私有分数。

使用15个nn模型,在线推理成本不到4小时,因此该流程至少可以集成30个模型。

数据集划分

我通过以下几个步骤划分训练集和验证集:

  1. 计算数据集中的所有唯一用户。
  2. 在验证集中完全选择5%的用户,在训练集中完全选择45%的用户。
  3. 对于剩下的50%用户,按时间将用户数据随机分为训练集和验证集。

这样我们在训练集和验证集中都有独立的用户,也有许多同时出现在训练集和验证集中的用户。这种划分方式与LB(排行榜)的分数差异可以小于0.001。

通过更改不同的随机种子,我们可以获得不同的折。

特征工程

由于其他帖子中已经有很多详细的FE(特征工程),我只分享一些重点。

基础特征

  1. 评估用户能力

    • 我们可以通过用户的历史行为来评估用户能力,包括正确性、耗时、滞后时间等。
    • 这些特征不仅可以在内容级别计算,还可以在相同的部分、相同的标签、相同的内容等维度上计算。
    • 这些特征也可以基于时间进行扩展,例如,我们可以制作一个特征,表示用户在过去60秒内的正确率。
  2. 评估内容难度

    • 我们可以通过计算其全局准确率、标准差、平均耗时等来评估内容难度。
  3. 评估用户 x 内容特征

    即使内容很难,用户可能仍然足够熟练并能正确解决。因此我们还需要描述用户在此内容上的表现。

    • user_acc_diff:对于某个内容,如果该内容的全局准确率较低,但用户回答正确,则该用户可能高于所有用户的平均水平。我们可以使用 $logloss(content\_global\_average\_acc, user\_answer)$ 来评估差异。

    • user_elapsed_diff:类似于user_acc_diff,我们也可以评估用户历史内容中的用户耗时差异。

我们可以在上述三个领域挖掘许多特征,例如,用户在历史内容/历史相同部分内容/历史相同标签内容上的平均滞后时间……也可能是有用的特征。

其他特征/技巧

  1. 时间相关特征:last_content_timestamp_diff、last_lag_time及其历史统计信息。
  2. 异常用户:如果一个用户在4秒内回答了每个内容,并且他选择的所有答案都相同(例如C),那么如果下一个内容的正确答案是C,我们可以相信他会正确回答下一个内容。
  3. 针对特定内容的学习讲座:如果用户历史记录中存在“内容-讲座-内容”的模式,并且这两个内容是相同的内容。这可能意味着用户针对此内容学习了特定的讲座,这意味着第二次回答正确的概率很高。
  4. 错误答案比率:可能存在一种模式,如“对困难内容默认选择C”。因此,计算用户在其错误内容上的选择比率可以告诉我们用户是否可能幸运地猜中当前内容,即使他不知道正确答案。

此外,还使用了诸如当前内容ID/当前时间戳/当前部分等特征。最后,我得到了120维特征,单折的单个lgb模型可以获得公开分数0.806。

P.S. lightgbm的分类特征(在content id上设置)可以使我的分数提高约0.003。

还有许多有用的提示可以提高速度并节省内存:

  1. 不要使用pandas计算特征,将其转换为numpy或仅使用python。
  2. 使用一个类来存储用户信息,并将每个用户信息保存在单独的文件中。
  3. 在推理阶段,我们只能读取出现在测试集中的用户信息,测试集中的用户总数少于10,000。这是减少内存的关键。如果内存仍然不足,可以使用LRU缓存来移除未使用的用户。
  4. 在我的实验中,使用numpy数组存储信息比python变量占用更多的磁盘空间。

NN(神经网络)

因为当我注意到顶尖选手的关键是NN模型时已经太晚了,我没有太多时间分析NN模型,特别是设计特定的特征或结构。为了节省时间,我使用lightgbm特征作为NN输入(添加了时间轴,序列长度为128)。

鲁棒标准化

lightgbm的特征中有许多异常值,因此简单地利用标准化很难获得理想的结果。我利用鲁棒标准化来标准化所有特征:

def robust_normalization(column):
    cur_mean = np.nanmedian(column)
    cur_qmin, cur_qmax = np.nanpercentile(cur,[2.5, 97.5])
    cur_std = np.nanstd(column[(column>=cur_qmin) & (column<=cur_qmax)])
    column = np.clip(column, a_min = cur_qmin, a_max = cur_qmax)
    column = (column-cur_mean)/cur_std
    return column