Kaggle比赛记录(二)Don't overfit!Ⅱ

       前天终于出了比赛结果,拿到了19/2330的好成绩,第一次参加Kaggle能进前1%我已经十分满意了。比赛前期读Kernel区代码所做的一些记录在这里,接下来写一下本人比赛用的具体方法以及心路历程吧。
       首先简要介绍一下这个赛题,题目名字是"Don’t overfit!Ⅱ",训练集有250个样本,测试集有19750个样本,输入是300维的连续值,即 x ∈ R 300 x\in R^{300} xR300,输出是二分类,即 y ∈ { 0 , 1 } y\in\{0,1\} y{0,1}。这个题目没有对应任何现实问题,貌似是题目投稿人自己按一定规律生成的各个样本点。
       一开始拿到这道题,首先做简单的EDA,发现300个特征大致是以0为均值的正态分布。然后用各种模型都试一遍,把pipeline做好后,直接把数据丢进去,用5重10折交叉验证检验每个模型的效果,发现Lasso回归和逻辑回归的效果是最好的,提交后的LB分数也佐证了这一结论。因此,可以判断这共计20000个数据点大致是线性可分的。
       那么,在简单的逻辑回归与Lasso回归效果已经可以完爆Xgboost、随机森林时,我们还能做什么改进呢?注意到一个现象,Lasso回归得分比岭回归高,而 L 1 L_1 L1正则化的逻辑回归也要比 L 2 L_2 L2正则化得分高。考虑到 L 1 L_1 L1正则化相比于 L 2 L_2 L2,能够让系数矩阵更加稀疏,也就是更多的特征被设为无用特征,这说明生成这些数据点的原始模型里应该有大量的无用特征!
       这一结论是很重要的,它表明了一种趋势。普通未筛选特征的逻辑回归效果最差, L 2 L_2 L2正则化的模型次之, L 1 L_1 L1正则化的模型最好,那有没有比 L 1 L_1 L1正则化更强有力的筛选特征模型呢?那就是概率模型了,这也是我最终融合的三种模型中的其中一种。
       我们可以设每个特征前面有两个系数 α \alpha α β \beta β,其中 α \alpha α是0与1的伯努利分布,用来控制特征是否有效, β \beta β是正态分布,代表特征的权重。再增加一个正态分布偏移 b b b,进一步假设出下面的概率模型 y = s i g m o i d ( α 1 β 1 x 1 + α 2 β 2 x 2 + ⋯ + α 300 β 300 x 300 + b ) y=sigmoid(\alpha_1\beta_1x_1+\alpha_2\beta_2x_2+\cdots +\alpha_{300}\beta_{300}x_{300}+b) y=sigmoid(α1β1x1+α2β2x2++α300β300x300+b)       通过pymc3对上面的概率模型进行建模,用MCMC采样求解系数 α 1 、 β 1 、 ⋯ 、 α 300 、 β 300 、 b \alpha_1、\beta_1、\cdots、\alpha_{300}、\beta_{300}、b α1β1α300β300b,将得到的结果提交,public LB分数已经有0.860了。这里吐槽一句,我在自己本机上用pymc3的MCMC采样是真的慢,真要训练一遍得花六七个小时,但在Kaggle自带的IDE下就很快,不知道为啥。
       接下来再看看别的方法,也就是我融合的第二个模型。在public LB分数上,Lasso回归的分数其实是最高的,那我们可以开始考虑用这个模型来筛选特征了,毕竟这可是有大量的无用特征呢。递归特征消除(RFE)是一种常用的筛选特征方法,普通的RFE我们必须指定一个筛选后的特征数量 n n n,最终通过多次使用模型判断得分来消除其余特征,剩下 n n n个特征。RFECV在此基础上更进了一步,我们只需指定一个最小特征 n n n,RFECV会自动帮我们确定一个合适的最终特征数目,这样,我们就又有活可以干了!
       我们可以对250个训练样本点按0.65:0.35的比例做多次分割,然后对65%的样本点做RFECV,这个RFECV是以Lasso回归为基模型的。这样,每对样本点分割一次,我们都能得到一组筛选后的特征。每组特征的大小不一,相互之间也没关联,我们可以把它当作是RFECV观察不同的65%样本点做出的特征筛选决定,接下来就是判断这些筛选中哪些是真正有效的。
       方法也很简单,不是有35%的样本没有使用嘛!在对样本点分割后,观察65%的样本筛选出特征,再把Lasso模型用GridSearchCV进行包装,用这65%的样本训练模型,在35%的样本上测试,可以得到一个roc_auc分数。我们进行多次分割,取其中roc_auc分数高于某一数值的模型对19750的测试集进行预测,最终将多个预测结果做平均,得到最终的预测值,将其提交,public LB得分是0.868,比普通的Lasso回归不知道高到哪里去了。
       这个方法有bagging的思想,不过bagging是多次使用自助采样法改变样本的分布,而这个方法直接对样本进行多次分割,同时也对特征进行多次筛选,这个方法很好的防止了过拟合。
       下一个方法是支持向量机SVM。这里要说明一下,我融合的三种模型都借鉴于Kernel区,毕竟这是我第一次参加Kaggle比赛。由于源代码是R语言写的,而我只熟悉Python,所以第三个模型我是直接使用了Kernel区的源程序,没有进行修改。很奇怪的是,在刚拿到题目时我便试过了SVM,效果并不好,而这篇Kernel的public LB得分是0.861分,可能是我当时没有尝试根据LB分数调参吧。
       以上三个模型对应的原始Kernel在这里,概率模型、RFECV+Lasso多次筛选特征、支持向量机。
       接下来说一下我后来是怎么做的。当有了三个还不错的模型后,就考虑开始模型融合了。关于模型融合,一个很自然的想法便是stacking,比赛前期我天真的认为,只要基模型够多够好,stacking之后肯定能得到一个不错的分数。因此前期我花了大量的时间在创建基模型上,包括各种特征筛选方法与模型的组合,但我想出来的单个基模型public LB分数最高也只有0.863,其它大部分在0.820~0.840。当我把这些基模型做ensemble后,发现各种组合的stacking能达到的最好的分数也只有0.863,也就是说,stacking的最好结果与基模型中最好的那个相当,没有任何提升。这个发现在当时让我很挫败,转而开始认真研究起特征的筛选了。
       虽然在前期构建stacking的基模型时,我用过不同的特征筛选方式,但都只是初步使用,后面我开始尝试去除无用特征、添加新特征,整个过程我都是以概率模型为基础的。首先考虑筛选特征,主成分分析PCA我试过,但发现没有哪个特征是最主要的,大家都很一样,这条路便走不通了。再来考察特征的相关性,打算把相互之间相关度高的特征删除,但发现每个特征之间都是独立的。再后来,只能尝试用RFE给定筛选后的特征数量 n n n,把这剩余的 n n n个数量丢进概率模型里计算CV得分,提交后记录LB得分。通过这次尝试,我发现了一个规律, n n n越小,CV得分越高,LB得分越低。这个现象的原因,我认为是过拟合了!毕竟只有250个样本,但特征却有300个之多,可能本来系数为0的特征,在250个样本里可能恰好跟target特别相关。总之,针对250个样本,随意给定300个特征,再随意产生250个target,用特征筛选去训练,都能得到不错的CV得分,但它是不符合实际模型的,很明显overfit了。
       看来特征筛选是行不通了,开始尝试添加特征。我试过用各种方法找出重要特征,再将它们平方、两两相乘、代入sigmoid函数中,其中sigmoid特征的效果最好,添加了sigmoid特征后,LB得分是0.860,与不添加得分相当,而平方、两两相乘的CV和LB得分均下降了。按理说,特征越多,模型越难训练,如果添加的都是无用特征,得分必然下降。但添加sigmoid特征后,LB得分没有下降,说明了我添加的特征中必然有部分是有用的,但我已经没有继续尝试把它们筛选出来了,因为这时候我对题目的认知加深了。
       还记得之前失败的stacking吗?后来我便想明白了,stacking的基础还是在用这250个样本进行训练,可能0.863的LB得分已经是用250个样本训练的最佳结果了。讨论区的大神也佐证了我的这一猜想,后来排名第二的Chris说过,单纯用250个训练样本进行训练,最好的得分大约在0.860左右。因此,我开始尝试借鉴active learning的思想,先用逻辑回归训练250个样本,然后再19750的测试样本中找出概率最高的、概率最低的样本,分别作为正样本和负样本添加进训练集中,这样我们就有了252个训练样本,继续训练,继续添加样本。上述过程循环多次,到最后我们可以得到远不止250个样本,是不是模型训练得会更准确一些呢?
       很遗憾不是的,我是这么思考的,每个模型都有它自己的偏见,就像不同人对一件事情的认知是不同的,一千个读者就有一千个哈姆雷特。对于逻辑回归,模型是想找出一个超平面将正负样本分离开,而我们选取、添加进样本集的概率最高和最低样本,相当于距离超平面最远的样本点,它们对更精确地确定超平面没有什么帮助。反而,它们会扩大模型的偏见,可能概率最高的样本点实际上不是正样本,这是模型的偏见产生的错误认知,而上述循环会让模型对这个错误认知更加深信不疑。因此,实际可以看到的是,循环次数越多,LB得分越低!
       上述方法其实叫伪标签法pseudo-labels,比赛后期在讨论区里被人提出来了,这时候我才发现,之前不止我一个人想到了用这种方法,但他们都没做成功。如果说对同一个模型使用伪标签法,会让模型的偏见更加严重,但如果是用A模型得到的结果来修正B模型呢?这是可行的,我用目前LB分数最高的第二个模型(RFECV+Lasso)对测试集进行预测后,按训练集正负样本的比例,把测试集中概率最高和最低的共2000个样本取出,添加到训练集中,这样我们就有了2250个训练样本。在用概率模型对这2250个样本进行训练,得到的LB分数提升了(0.866)。
       同样的,我也可以用SVM训练这2250个样本,但我没有这么尝试。因为SVM的分离超平面完全取决于支持向量点,而其他大部分样本点对分离超平面并没有作用,所以我便没有花时间在这上面。
       经过了这么多的尝试,我对这场比赛的认知也加深了,知道光靠250个训练集,是很难更进一步的了,目前仅剩的一个方法便是LB probing。训练集只有250个数据点,但测试集有19750个,我们每次提交,反馈的public LB分数都来源于测试集的10%,即1975个样本,这远远比250个样本更多。所以,我们应该更看重LB分数(基于1975个样本)带来的信息,而非CV分数(基于250个样本)。很多人可能觉得,public LB只有总测试样本的10%,LB probing不会导致过度拟合这10%的样本吗?确实是会的,但在比赛中,对手只用250个样本训练,相当于去拟合250个样本,而我选择去拟合public LB(1975个样本),这能给我带来更多的信息。
       那么,在模型融合方面,既然依靠250个样本的stacking没有用,我们尝试更简单的blending吧,直接把各模型对测试集的预测归一化后做加权平均。
       这个方法很简单,就是需要不断尝试。由于比赛限制每天只能提交5次,所以我在选择用哪种模型融合时会慎之又慎。具体的,把当前最高分的第二个模型作为主要模型,我给了0.8的权重。接着我把各模型对测试集的预测结果归一化后放进了一个dataframe里,作出热力图,找出分数高、与第二个模型相关度低的模型。在比赛的最后一周,我都在做这样的尝试,原则是无脑相信public LB得分。这时候我发现,除了Kernel区的高分答案外,之前自己提交的150多个预测结果都是做blending的材料。最后,我找到了四个模型线性组合的LB分数最高(0.877),其中三个便是上面讲的三个模型,最后一个来自于我之前提交的某一版stacking。之前说的伪标签法在这一步让我的分数提高了0.003分左右,因为它不仅让基模型更优,还降低了彼此的相关性(虽然这不符合我原本的预期)。这个分数在当时已经排到25名左右,已经很接近前1%了,最终结果出来,名次从28名提高到了19名,进入前1%。
       比赛结束后,发现排行榜前几名的大佬都早早意识到LB probing的重要性了,而我当时还在傻乎乎的对着250个训练样本抓耳挠腮。前两名的方法其实很像,这甚至不算是机器学习方法。第一名的Zach花了60天的时间,提交了300个预测结果,他每次提交的只有一个特征,通过public LB来得知这一个特征与target的相关度,最后筛选出有用的特征进行加权。比如这次提交了一号特征,public LB得分是0.65,就把一号特征的权重设为 0.65 − 0.5 = 0.15 0.65-0.5=0.15 0.650.5=0.15,如果二号特征的public LB得分是0.4,就把二号特征的权重设为 0.4 − 0.5 = − 0.1 0.4-0.5=-0.1 0.40.5=0.1,如果三号特征的public LB得分是0.505,由于跟0.5相差太小,就认为三号特征是无效特征,权重设为0。
       真是天才的思路!方法虽然简单,但不是很多人能想到的。尤其是第一名的Zach,据他所说他一开始就知道该怎么做了。他在8年前第一次打Kaggle比赛,正是"Don’t overfit!Ⅰ ",8年前的比赛把他带上了机器学习工程师的职业之路。
       这便是我第一次参加Kaggle的经历,在这里做一下记录以免遗忘!

你可能感兴趣的:(Kaggle比赛之路,机器学习,人工智能,算法工程师,kaggle)