Home Credit 2024 总结
用时三个月打了人生中第一场 Kaggle 比赛,虽然结果一般,但还是学到了一点知识,而且比赛的过程也有些“跌宕起伏”(见下图),于是在此记录一下。
猜猜为啥掉了1000多名
起
这事要从四个月前说起。研一正是找实习的关键时期(雾),需要一个有含金量的比赛来给简历增色,于是就在舍友(好队友!)的推荐下在 Kaggle 组队参加了这个比赛。
比赛名为 Home Credit - Credit Risk Model Stability,是一个时间序列预测问题,要求参赛者预测未来几个月的客户违约概率。比赛的数据集是好几家公司的数据,包括了客户的信用卡账单、信用卡申请、信用卡还款等信息,数据量也没有很多,也就 30 多个文件、总大小 10 GB 起步。
一开始我是很拒绝的,因为信贷相关的内容从来都没接触过,特征列有 400 来条,描述全是英文,针对不同的数据类型,处理方式也不一样。因为数据的来源有亿点多,还有不同的 depth
和 num_group
用于聚合。所以第一个困难就是如何处理多个不同来源的大量数据。
办法总比困难多,在比赛开始的一段时间后,就有大佬分享了一些便捷的聚合函数代码,自此数据的聚合再不成问题,后面大家在数据预处理这部分的代码也就都差不多了。
比赛开始前两周,有人已经发现 evaluation metrics 可以通过一定的手段进行 hack,让 Public LB 上的得分到 0.62x。举办方专门为此事还修改过测试集。
承
光是理解数据集就花了不少时间,主要还是没有经验。三月底四月初的时候 Public LB 上最高分一直都是 0.57x 左右,看到有人只用 LinearRegression 加手工的特征筛选就能拿到 0.5 的分数,顿时感觉特征工程真的很重要 那位兄弟的代码当时没看懂但是说
那个时候我只知道个随机森林、决策树啥的,压根不知道 LightGBM、XGBoost 这些东西。也正是在这个比赛中,我才开始接触和使用 sklearn
系列相关的机器学习库。有人开源了 LightGBM 的 notebook,我就 fork 下来,调调参交一下,结果也能上到 0.56,0.57 左右。
后面舍友提出可以做一做模型融合,比如 Stacking、Blending 之类的。我对模型融合的第一个想法就是 stacking,即把不同基模型的输出作为新的特征输入到一个元模型中。第一个尝试的元模型居然是 MLP,结果直接过拟合,Public LB 上得分直接掉到 -0.003,简直离谱。于是乎尝试了 LinearRegression
、LogisticRegression
,发现 LogisticRegression 的效果还不错,最高能到 0.58x。于是乎之后的操作就是不断加模型不断调参,最后最高的结果上到 0.592,排名最高也上到过 23 名。
这个时间我和舍友都在做融合,因为他的那个 notebook 冗余的代码太多 (不同模型对数据的预处理不一样),于是我就参考开源的 notebook 写了几个常见模型的训练本子,然后再将训练好的模型导入到一个推理本子里进行融合,最后的结果比较一般,LightGBM + CatBoost 的 Public LB 上有 0.589,再加个 XGBoost 反倒还降了一点,顿时觉得 XGBoost 没用,在评论区也看到有人出现了这种情况。
其实这个时候就发现本地的 CV 分数和 Public LB 上的分数差距很大,本地 CV 可以很高,但在 Public LB 上就不一定了。所以那时候就有一个问题,到底是该相信本地 CV 还是 Public LB 上的分数?
转
"What a terrible competition!!!"
我们的排名一直比较稳定,从 20 多掉到 30 多,再掉到 40 多。虽然一直在掉,但看着这个速度,感觉最后拿个银牌还是有希望的。但是天不遂人愿,有一天起床看了眼 LB,我超,掉到 300 多了?
原来是又有人发现了一个 hack,原本的 evaluation metrics 是这样的:
$$ \begin{aligned} \text{stability metric} = mean(gini) +88.0 \cdot min(0, a) -0.5 \cdot std(\text{residuals}) \end{aligned} $$
通过对比测试集和训练集的相似度,将那些没那么高的结果手动减去一个固定值,这样就能减小最后的那个惩罚项,从而提高分数。下面是一个简单的代码片段
y_pred = pd.Series(model.predict_proba(df_test)[:,1], index=df_test.index)
condition=y_pred<0.96
df_subm = pd.read_csv("/kaggle/working/sub.csv")
df_subm = df_subm.set_index("case_id")
df_subm.loc[condition, 'score'] = (df_subm.loc[condition, 'score'] - 0.05).clip(0)
df_subm.to_csv("submission.csv")
就很抽象,通过这个方法,Public LB 至少能提 0.03,原先 0.6 以上的就两三个人,现在最高分已经跳到 0.639 了,我们这些用了正经做法的全部都掉了好多名次。有人在 Discussion 里给 Host 反馈,结果人家回了几句后直接冷处理(想想为什么)。于是乎我们也找猫画虎地开始了 hack,最后的最后,调参调到了 0.659,挤到了银牌区。
截止日的前 12 个小时,该选择提交哪两个 notebook 了,这个时候我们队有三个选择:
- 不用 Hack 的 Public LB 最高的
- 用 Hack 的 Public LB 最高的
- 本地 CV 分最高的
最后我们选了前两个,毕竟能看到排名,直观的排名最能让人感到心安。
合
第二天早上 8 点多起来一看,我去,狂掉 1000 名,有点绷不住 😂。看了看 LB 和 Submission,发现我们再提个 0.003 到 0.519 就能从 1200 提升到 150 名,拿到银牌,而且本地 CV 分最高的那个本子(正好是那版 LightGBM + CatBoost + XGBoost)正好在 Private 数据集上是 0.519,就很,,,
通过这个结果也就知道 Host 为啥到后面鸟都不鸟那些 Hack 了,因为对最终结果没啥影响,真的高呀,只用了 Hack 的基本连牌都拿不到。
结
这次比赛最终的排名就如过山车一般,曾经到过山峰,又跌回谷底,跟我们的最终的决策有很大关系,同时也因为第一次在 Kaggle 上打比赛,没啥经验,一直在关注那个 Public LB,我现在总算知道了,这个榜仅供参考。
这次比赛其实没用什么特征工程,最多是用相关性做了个筛选,这点是一个小小的遗憾。然后简单总结一下这次比赛的收获:
polars
和pandas
怎么处理数据,处理数据的思路- 一些常见的机器学习库的使用,比如
sklearn
、LightGBM
、CatBoost
、XGBoost
- 模型融合的一些方法,比如 Stacking、Blending
要相信本地 CV