Kaggle宠物收养比赛亚军复盘

写在前面

这个比赛是在 19 年年中进行的,已经过去一段时间。完赛时我是季军,但后面由于冠军大哥作弊被发现并除名,我在排行榜上的位置也变成了亚军。这个比赛很有特点,是难得一见的“多模态数据”比赛,也是我唯一的 Solo 金牌,初学者应该能从中学到不少东西。

正文的内容其实也是去年写的,但原来是放在自己的博客里,一共也没几个人看过。后面我会把之前的一些复盘都发出来,希望可以启发到有需要的人。

赛题概况

这是一次很有意思的比赛,主办方是马来西亚的动物慈善组织 PetFinder。这次比赛的数据种类非常的丰富,基础数据集包含了了图像数据文本数据和结构化数据,通过不同的数据类型的组合,可以探索很多有意思的算法。而且这次比赛允许使用外部数据,只要在官方的论坛里把你使用到的数据公开给所有的参赛者,你就可以进行使用了。我一开始以为这种方式会让这个比赛变得比较蛋疼,但到最后几天我发现,通过观察排在前面队伍使用的外部数据,可以对我们自己的模型产生一些帮助:P。

petfinder.png

这道题也是一个只能用 Kagge 提供的 kernel 执行代码的比赛,GPU 版 kernel 规定时间是两小时以内,所以对编程实现的效率也有一定的要求。

这道题的评价指标是 qwk(quadratic weighted kappa,基本介绍可以看这里,详细介绍可以看这里)。这种指标是没有办法直接优化的,参考前面的 crowdflower 的比赛,大家通常把这种题目转换成回归问题来做,然后使用一个额外的模块去获得切分的阈值点。

公开的 kernel 里大多数使用了一种优化器去获得一个比较好的切分点。但在我阅读了 crowdflower 第一名的解题方案之后(Chenglong 大神的解题报告)我发现,这种优化器可能没有办法去超越直接通过训练集的分布来获得阈值点的方式。所以我在最后并没有使用那些优化器而是直接,把训练集的分布搬到了测试集上。

数据处理和特征工程

图像特征

首先来说一下我对图像数据的处理,我的处理方法和公开的 kernel 差不太多。公开 kernel 的做法是用预训练的模型,提取特征。由于目前常用的模型在分类器前得到的 feature map 维度一般很高(比如 resnet50 达到了 2048 维),比较难放进树模型;kernel 里大多使用 SVD 对这种高维特征进行的降维,然后把降维后的数据当作特征,放到模型里训练。

我在这个基础上做了两个比较有意思的操作。一是我用一个全连接神经网络直接训练了一个基于这种高维图像特征的回归模型,直接回归得到一个 y,然后进行 stacking。二是我用 kmeans 对高维特征进行了聚类,获得了一列类别 label,这组特征也对后面的建模提供了一些帮助。

曾经想过在训练这个回归器的同时也 finetune backbone 网络,但一方面是实在无法在规定时间里完成,另一方面发现效果其实并没有不 finetune 好。

文本特征

对于文本数据,公开的 kernel 里大多只采用了词频 tfidf 变换的方式,再结合 svd 获得特征。熟悉 nlp 的朋友应该都知道,词频特征虽然很强,但这只能获得浅层文本特征。稍微更进一步可以做一些主题模型,比如 lda。但对于深层的语义信息必须用神经网络来进行提取。这道题比较有趣的是 description 字段文本的长度范围比较大,有的文本可以到几百个词。这对编程实现有一些技巧上的要求。

meta 特征

Meta 特征指数据集中用 googleAPI 获取的对图像和文本的分析结果,例如有图片分类的结果、描述文本的 NER 结果等等。在处理的时候我感觉有两点需要注意,一是尽可能全面的去读取结果 JSON 文件,然后进行特征构建,公开的 kernel 都只读取了小部分的内容。二是需要对处理代码做一些优化,公开 kernel 的实现比较缓慢。假设用 kernel 的写法去读取我最终使用的信息,可能这部分就要花费 30 分钟,我最终的代码在 4 分钟左右就可以完成所有操作。

统计特征和未解之谜

这道题还有一个比较有意思的地方,是一个叫 rescuerID 的字段,测试集合训练集中没有重叠的 rescuerID。如果我们在训练模型的时候采用普通的 kfold,那么验证集和训练及之间可能会有重合的 ID。这样的后果就是,验证集的分数特别的高,和 LB 成绩有一个明显的 gap。

为了避免这种情况,我在这个比赛中是全程使用了 Groupkford,这种做法可以得到和测试集划分同样的不带重叠 rescuerID 的验证集和训练集。而且这这种方式训练时收敛的轮数会比普通 kfold 的少很多。而且获得的线下验证成绩跟 LB 成绩差的比较小。我在公开排行榜的后期,线下成绩和线上成绩基本上是完全一致的。

但我在最后提交的时候,选择了一个 stratifiedkfold 和一个 groupkfold,结果普通 kfold 的成绩在 B 榜是略好于 groupkfold 的。这里不得不称赞一下 Kaggle 可以选两个 submission 的设计,可以让参赛者发挥尽可能多的实力,少留下遗憾。

对于提供的表格数据,我只进行一些非常常规的统计,没有什么特别的。在特征工程的时候,完全参考 local CV 来进行,只要这个特征能够提高 CV 成绩我就会使用它。在做 CV 时使用的指标主要是 RMSE,QWK 成绩由于不太稳定,只是作为一个辅助信息来参考。

模型

我的模型架构如下图所示,是一个比较复杂 stacking 的架构。第一层及以下是特征提取层,第二层是模型,第三层是融合。图像部分我用了四种 ImageNet 预训练分类模型来提取图片特征(图中 image feature),并传入 DNN 进行处理获得对目标预测结果,然后将预测结果作为其他模型的特征。第二层使用了 lightgbm,xgboost,catboost 三种树模型,以及一个融合了文本、图像、统计特征的 RNN+DeepFM 网络。

model.png

比较有意思的模型是那个 NLPDeepFM 模型,处理 NLP 信息的神经网络是一个双向 GRU+Attention。由于表格数据里有大量的 categorical 列,所以我构造了一个 FM 来处理类别数据。对于统计特征等等其他的浮点特征则将他们直接放到网络里的 DNN 部分。最后将各部分的中间结果进行拼接,传入一个回归器得到最终的结果。

nn_model.png

从线下成绩来看神经网络模型虽然很复杂,但是他的能力还是和树模型有较大的差距。但是两者的差异性是比较明显的,在最后的融合过程中能带来很好的收益。最后将所有模型的结果通过一个岭回归器进行 stacking。

写在最后

不得不说的是这次比赛的运气还是挺好的,因为 qwk metric 其实是很不稳定的。最后我的 B 榜排名相比 A 榜成绩有了非常大的提升,这个是我没有预料到的。但我在 B 榜的线下成绩和线上成绩是比较吻合的,这说明我原来采用的线下验证策略应该比较靠谱的。其实做比赛真的还是要尽量合理地去构建验证集,然后提高验证集的成绩,强行拟合 LB 没有任何意义,也学不到东西。心态也要放平,尽人事,听天命,不要有太多杂念。

最后,感谢论坛里的大神写了这么多优秀的公开 kernel 给我们学习。我的 kernel 也已经开源,也算是回馈一下社区,感兴趣的朋友可以看这里

【欢迎关注公众号:朴素人工智能】