返回列表

4th Place Solution

562. Predict Student Performance from Game Play | predict-student-performance-from-game-play

开始: 2023-02-06 结束: 2023-06-28 学习效果预测 数据算法赛
Joel Erikanders - 第四名解决方案
作者: Joel Erikanders(Kaggle Expert)
排名: 第4名
发布时间: 2023年6月30日
最后更新: 2023年7月19日

致谢

我要感谢主办方提供了这个非常有趣且具有挑战性的项目。同时我也非常感谢Kaggle上所有的公开分享,这是一次疯狂的学习经历。如果没有所有公开的笔记本、讨论帖和以往的竞赛解决方案,我不可能在这次竞赛中取得成功。

概述

  • 使用大部分原始数据进行训练,仅在Kaggle数据上进行验证
  • 集成Transformer、XGBoost和Catboost模型,每个模型使用3个随机种子和5折交叉验证
  • 使用基于时间、索引和屏幕坐标差异的通用特征集
  • 使用线性回归作为元模型
  • 阈值对LB分数有重大影响

数据处理

我使用大部分原始数据进行训练,包括仅完成0-4级和5-12级会话的数据。总共约38,000个完整会话和约58,000个会话。使用原始数据使CV分数提高了0.001以上。我只使用Kaggle数据进行验证。

我的初始数据预处理只是按级别组和索引排序,与推理过程中的处理方式相同。此外,我的实验表明使用悬停持续时间没有帮助,因此在排序后我删除了悬停行,并将会话从0到len(session)重新索引。

Transformer模型

我花费了大量时间实验Transformer,最终得到了一个轻量级模型,在公开和私有LB上达到0.698,CV分数为0.702。

class NN(nn.Module):
    def __init__(self, num_cont_cols, embed_dim, num_layers, num_heads, max_seq_len):
        super(NN, self).__init__()
        self.emb_cont = nn.Sequential(
            nn.Linear(num_cont_cols, embed_dim//2),
            nn.LayerNorm(embed_dim//2)
        )
        self.emb_cats = nn.Sequential(
            nn.Embedding(max_seq_len + 1, embed_dim//2),
            nn.LayerNorm(embed_dim//2)
        )
        encoder_layer = nn.TransformerEncoderLayer(
            d_model=embed_dim,
            nhead=num_heads,
            dim_feedforward=embed_dim,
            dropout=0.1,
            batch_first=True,
            activation="relu",
        )
        self.encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)
        self.clf_heads = nn.ModuleList([
            nn.Linear(embed_dim, out_dim) for out_dim in [3, 10, 5]
        ])

    def forward(self, x, grp):
        emb_conts = self.emb_cont(x[:, :, :-1])
        emb_cats = self.emb_cats(x[:, :, -1].type(torch.int32))
        x = torch.cat([emb_conts, emb_cats], dim=2)
        x = self.encoder(x)
        x = x.mean(dim=1)
        x = self.clf_heads[["0-4", "5-12", "13-22"].index(grp)](x)
        return x.unsqueeze(2)
  • embed_dim: 64
  • num_layers: 1
  • num_heads: 8
  • max_seq_len: 452(下面会解释),但序列会被裁剪到256
  • 我对所有问题使用同一个模型

我发现Transformer很容易过拟合数据,因此为了提高信噪比,我执行了以下操作:

  1. 通过连接event_name、level、name、page、fqid、room_fqid、text_fqid来识别游戏中的不同点
  2. 其中有些点在会话中出现多次,通过枚举并添加到名称中来将它们视为不同的点
  3. 过滤掉在超过0.999会话中都存在的点,这使得每个会话最长452步
  4. 创建6个特征列:时间差、索引差、距离差(从屏幕坐标计算的累计移动距离)、room_coor_x、room_coor_y和分类点列的嵌入

XGBoost模型

这是我最强的单模型,公开LB 0.701,私有LB 0.702,CV 0.7029。值得注意的是,我展平了5个Transformer输入列(不包括分类列),并将所有值用作单独的特征。

其他特征主要来自公开内核中的统计信息,如分类变量的时间和索引差值的均值和最大值。这些统计在应用Transformer输入过滤之前计算。

从早期开始,我为每个级别组训练一个模型,将问题编号作为特征。我发现与为每个问题使用模型相比,CV提高了约0.0002。这可能是随机性导致的,但我选择使用它,因为我认为3个模型而不是18个会让我的实验过程更简单。对Transformer也采用了类似的推理。

Catboost模型

本质上与XGBoost相同。CV 0.7022。

集成方法

我为每个问题训练了一个线性回归元模型,使用上述模型的输出概率作为输入来生成最终预测。我还包含了过去问题以及一些未来问题的概率!例如,训练预测问题2的回归模型使用了问题1-3的概率作为输入;预测问题7时使用了问题1-13的概率;预测问题16时使用了问题1-18的概率。在输入线性回归之前,我先对3个种子取平均以提高鲁棒性。

最终结果为公开LB 0.702,私有LB 0.703,CV 0.7044。

关于阈值和提交选择

我尽可能尝试相信CV,但我的CV和LB之间持续的差距直到最后几天都让我感到可疑。然后我意识到一个原因可能是我的选定阈值在测试数据上不是最优的。我用最高CV解决方案提交了几次,只改变阈值,发现它确实不是最优的,并且导致的LB分数变化比最近大多数实验都要大。因此最后我选择了3个相同的解决方案,使用不同的阈值:0.60(在LB上表现最佳)、0.62(在CV期间最佳)和0.64。结果是0.61本可以得到0.704的私有分数,但我不后悔 ;)

感谢您的阅读!

同比赛其他方案