最近参加了两场Kaggle比赛,收获颇多,一直想写篇文章总结一下。接触Kaggle到现在不到一年,比赛成绩一个银牌(5%)一个铜牌(9%),勉强算入门了,跟大神们还有很大的距离。新学期准备找实习面试,整理一下项目经验,于是动笔总结总结参加Kaggle比赛的个人心得体会和技巧。
先放一张成绩图和Kaggle个人主页的链接
我参加的这两场比赛,一个是Zillow Prize:Zillow公司有一个房价预测算法Zestimate,根据房产的特征、历史成交数据和Zestimate预测值之间的logerror(这里有点绕),来预测实际成交价和Zestimate预测值之间的logerror。另一个是TensorFlow Speech Recoginition Challenge:识别长度为1秒的英文语音指令,并且分为12个类,包括10个指令(yes, no, up, down, left, right, on, off, stop, go)以及未知(unknown)和静默(silence)。
这两个比赛,刚好属于Kaggle社区中两个不同类别。Zillow Prize给定了60个房产的特征,数据量不是特别大并且有明确的特征,适合xgboost、lgb这样的树模型,对机器的要求不高。而语音识别这个比赛则相反,原始数据是wav文件,实际上相当于对波形图进行分类,不可能手动选特征,因此这个比赛适用深度学习的各种模型。并且想要取得好成绩,需要使用复杂的模型,CPU就不够用了,需要一块不错的显卡(我用的是队友的GTX 1070ti)。
两场比赛下来,自己总结了一些kaggle比赛的基本流程:
- 特征工程
- 模型选择
- 调参
- 模型融合
特征工程
特征工程是Kaggle比赛的重中之重,尤其是对于房价预测这类使用树模型的比赛。模型大同小异,基本是由GBDT模型演化而来,而且主要用XGBoost、LightGBM等几种开源框架。所以,模型大家都差不多,特征就是关键了。
每个比赛都有独特的背景,想要发现甚至是自己创造出重要的特征,往往需要专业的领域知识,比如Zillow这个比赛要预测美国的房价,原始特征有卧室数量、面积,税收等等。想要自己通过原始特征组合,创造出一个“magic feature”就需要了解美国的房地产业。所以,选择一个自己熟悉领域的比赛,会比较有优势。
比赛背景千变万化,从数据科学的角度,还有许多通用的方法来做特征工程。这里列举一些这个比赛里用到的方法:
- 基础预处理:对category类型的数据OneHot编码;数值类型的数据归一化(但是这里用到的大多数模型都是基于决策树的,所以不需要)
- 缺失值处理:实际数据集中有许多数据是缺失的,考虑列出每个特征的缺失比例,比例过大的直接舍弃,否则想办法填充。这个比例没有什么定式,舍弃特征会丢掉有用信息,填充会引入噪声,具体怎么操作要看模型实际的表现。填充的话,基础的是用均值、中位数等填充,更准确的方法是用算法拟合,还可以直接把缺失视为一种特殊的值(这个比赛中的许多模型就是用-1填充)。对于树模型来说,数据缺失并不影响树的生成,所以xgboost会在生成树的同时,根据training loss自动学会将遇到缺失值时分裂到左子树还是右子树。作者Tianqi Chen的原话:
Internally, XGBoost will automatically learn what is the best direction to go when a value is missing. Equivalently, this can be viewed as automatically "learn" what is the best imputation value for missing values based on reduction on training loss.
-
outlier: 由于各种原因,往往有一些样本的误差特别大,把这些样本加入模型会引入很大的噪声,就像很多打分的比赛会去掉最高分和最低分之后再取平均值。这个比赛中去掉这样大误差的outlier能带来很大的提升。
下图是所有样本的logerror值,可以看到绝大部分都在0附近,去掉两端logerror急剧上升的样本,能让模型更稳定。
相关性分析:特征之间并不是完全独立的,这时候可以计算correlation coefficient来确定特征之间的两两关系。还可以计算每个特征和目标值(这里是logerror)之间的相关性,绝对值越大说明这个特征的作用越大。
模型权重分析:现在大多数模型在训练完成后都会给出获取特征权重的接口,这时的权重是直接反映了一个特征在这个模型中的作用。这是判断一个特征是否有用的重要方法。例如,原始特征有卧室数量和卫生间数量,我们自己创造了一个特征房间总数(卧室数量+卫生间数量)。这时可以用这种方法判断这个新特征是否有效。
特征工程在实际应用中非常有挑战性,还有许多方法,上述只是一些皮毛。Zillow比赛里我尝试了许多新特征,但最终都没有采用。
而对于语音识别比赛来说,特征工程就非常有限了。在语音识别领域广泛使用的特征是log-mel频谱和mfcc特征,没有必要自己再做特征工程。而现在火热的端到端(end-to-end)语音识别优点就是省去特征提取,直接使用原始波形作为神经网络的输入。
模型选择
Kaggle比赛很重要的一点是,不可能只使用一个单一模型。在许多比赛的第一名公布的方案里,往往有十几个甚至几十个不同的模型。模型融合(ensemble)实在是太重要了,模型融合的方法下文再讲,但是在选择模型的时候就要考虑到如何让这些模型在融合后效果更好。
不管用什么方法融合,想要模型融合之后效果好,模型之间要有多样性。换句话说,模型之间越不相似,模型融合的效果越好。
对于Zillow这样给定特征,数据不是图像音频的比赛,主要选用树模型。这类Kaggle比赛,首选肯定是XGBoost和LightGBM。这两个模型都是由梯度提升树(GBDT)演化而来的。简而言之,就是通过梯度提升(Gradient Boost)算法训练许多决策树及其对应的权重,然后投票得到最终的结果。详细的数学证明可以看林轩田老师的台大机器学习技法课程
xgboost模型在生成决策树时是level-wise的,即每一层上的所有节点都会一起分裂,通过max_depth来控制树的高度从而控制模型的拟合程度。lightgbm模型则是leaf-wise的,每一次分裂会从所有叶子节点中找增益最大的节点来分裂,所以主要通过num-leaves来控制模型的拟合程度。
只用这两个模型显然不够,可以调整不同的参数来获得许多个侧重点不同的xgboost(lgb)模型:不同的深度(叶子数)、不同的损失函数等等。另外,在这个比赛里还用到了CatBoost、sklearn里的一些模型:随机森林、ExtraTree等。
对于语音识别这类用深度学习的比赛而言,模型选择主要在于神经网络的结构和不同的输入。首先可以尝试不同种类的网络,比如CNN、LSTM。这里很难说有什么通用的技巧,在这个比赛中,CNN的效果较好,我用的所有6个模型都是以CNN为基础的。主要用到的结构是VGGNet和SE-ResNet。对于VGGNet,实际上并没有完全按照论文上的模型,只是参考了思路,整个网络都使用了同样大小的卷积核尺寸(33)和最大池化尺寸(22)。SE(Sequeeze-and-Excitation)Block是一个挺新的结构,在2017年提出,核心思想是学习特征权重。主要是通过global average pool以及全连接层来学习feature map的权重,然后作为scale乘到原始feature map上。然后,如下图所示,将SE Block和ResNet结合。
在深度学习中,很多时候难以说清为什么这个网络结构效果好,只能根据结果来证明。这次的语音识别比赛中,一个比较有用的trick是:为了增加模型的多样性,在每个模型卷积操作完成后分别对feature map用全局最大池化和全局平均池化,相当于抓住了不同的特征,把一个模型变成了两个。另外,上文提到的三种特征:原始波形、log-mel频谱、mfcc特征分别作为输入,这样就形成了六个模型。
调参
调参这事很复杂,有很多经验、方法,实际的比赛中往往还是需要一些玄学。调参最常用的方法就是GridSearch和RandomSearch。GridSearch是给定每个待调参数的几个选择,然后排列组合出所有可能性(就像网格一样),做Cross Validation,然后挑选出最好的那组参数组合。RandomSerach很类似,只是不直接给定参数的有限个取值可能,而是给出一个参数分布,从这个分布中随机采样一定个数的取值。
调参的方法理解了,那具体调什么参数呢?Zillow Prize比赛里,主要用的模型是XGBoost和LightGBM。下面列出一些主要用到的参数,更多的还是直接看文档。
XGBoost:
- booster: gblinear/gbtree 基分类器用线性分类器还是决策树
- max_depth: 树最大深度
- learning_rate:学习率
- alpha:L1正则化系数
- lambda:L2正则化系数
- subsample: 训练时使用样本的比例
LightGBM:
- num_leaves: 叶子节点的个数
- max_depth:最大深度,控制分裂的深度
- learning_rate: 学习率
- objective: 损失函数(mse, huber loss, fair loss等)
- min_data_in_leaf: 叶子节点必须包含的最少样本数
- feature_fraction: 训练时使用feature的比例
- bagging_fraction: 训练时使用样本的比例
调参的时候需要理解这些参数到底是什么意思,如果过拟合了应该增大还是减小某个参数,这样才能有目的而不是盲目地调参。当然,想要找到最佳的参数很多时候需要一些经验和运气。也不需要极致追求最佳参数,大多数情况下找到一组相对不错的参数就可以了,往往还有别的方法来提升总成绩。
Zillow Prize比赛中这些单个模型的训练时间是基本上是分钟级别的,这样可以有足够的时间来进行调参。而语音识别这个比赛里的模型需要在GPU上训练,一个模型要训练几个小时,其实没有什么时间来仔细的调参,更多的是靠经验来估计。
值得一提的是,语音识别比赛中遇到一个问题,所有模型在训练集和验证集上表现都很好,但是在提交的测试集上有15%-20%左右的差距。最后发现是测试集和给出的训练验证集样本分布不同:这个比赛是12个类的分类问题,最终的验证集上基本是均匀分布的,而给出的训练集和验证集上,未知(unknown)类样本明显多于其他类,而静默(silence)类则只有6个音频。最终构建喂给神经网络的batch时,需要重新对训练集采样。对于unknown和silence这两个特殊类,他们的比例也可以算是超参数,最后根据Kaggle社区里大神的分享,确定了10%是最佳的。
模型融合
模型融合在Kaggle比赛中非常重要,同时也是一个很大的话题。这里只记录一下我应用的两种比较有效的方式。
- Averaging
Averaging是最简单粗暴也是最好理解的模型融合方式,而且效果还挺好的。实际上就是加权平均,小学生也会计算。虽然简单,但是非常有效。如果模型的多样性足够,比如有的模型擅长从税收角度预测房价,有的模型擅长从房间数量来预测房价,把这些模型平均后,取长补短,就能获得一个更准确泛化能力更强的模型。
每个模型的权重怎么算?一般是根据单个模型的表现好坏来决定,可以看测试集上的表现,Kaggle比赛里可以看LB Score,但是也不能完全看Public LB,这样就过拟合了。Zillow Prize比赛里我们的Public LB排到了前2%,但是最终所有测试集公布后还是回到了5%。 -
Stacking
Stacking应该是目前各类竞赛中最好用的模型融合方法了。看下面这张流传很广的图,其实Stacking并不难理解。
Stacking的核心思想是把第一层模型的结果作为第二层模型的特征,然后训练第二层模型得到最终结果。以5-fold stacking为例,将训练集随机分成5份,分别用其中4份作训练,来预测剩下的1份,同时也预测所有的测试集。这样,一个模型训练了五次,对训练集的预测拼起来,正好每一个训练集的样本都有一个预测值。对测试集的每个样本,则有5个预测值,求平均值作为测试集的预测值。这样,训练集和测试集都有一个预测值,作为第二层模型的特征。
另外,在语音识别比赛中还学到了一种类似Stacking的巧妙模型融合方法。CNN模型在所有卷积池化操作完成得到feature后,先不做全连接,把各个模型的feature全部拼接在一起,然后作为一个全连接神经网络的输入,相当于stacking中的第二层模型。
总结
Kaggle比赛对于关注数据科学的人来说肯定不陌生,参加一次比赛可以在实践中学习,远胜于看书看视频。初学者可以参考这篇文章,用经典的泰坦尼克号之灾先练练手。很多知识只有实践过才能真正理解。Kaggle社区非常活跃,有许多大神会分享自己的思路甚至是代码,一起讨论一起学习会有巨大的进步。比赛成绩很有用,但更重要的是通过比赛学到东西!