准备参加一个算法比赛,想把自己所学的知识拿来用一用,在比赛初始自己没一点思路,突然看到知乎上有一个大神写了一篇博文,非常适合我这种刚入门的小白。
本文转载来自:https://zhuanlan.zhihu.com/p/27424282
不知道你有没有这样的感受,在刚刚入门机器学习的时候,我们一般都是从MNIST、CIFAR-10这一类知名公开数据集开始快速上手,复现别人的结果,但总觉得过于简单,给人的感觉太不真实。因为这些数据太“完美”了(干净的输入,均衡的类别,分布基本一致的测试集,还有大量现成的参考模型),要成为真正的数据科学家,光在这些数据集上跑模型却是远远不够的。而现实中你几乎不可能遇到这样的数据(现实数据往往有着残缺的输入,类别严重不均衡,分布不一致甚至随时变动的测试集,几乎没有可以参考的论文),这往往让刚进入工作的同学手忙脚乱,无所适从。
Kaggle则提供了一个介于“完美”与真实之间的过渡,问题的定义基本良好,却夹着或多或少的难点,一般没有完全成熟的解决方案。在参赛过程中与论坛上的其他参赛者互动,能不断地获得启发,受益良多。即使对于一些学有所成的高手乃至大牛,参加Kaggle也常常会获得很多启发,与来着世界各地的队伍进行厮杀的刺激更让人欲罢不能。更重要的是,Kaggle是业界普遍承认的竞赛平台,能从Kaggle上的一些高质量竞赛获取好名次,是对自己实力极好的证明,还能给自己的履历添上光辉的一笔。如果能获得金牌,杀入奖金池,那更是名利兼收,再好不过。
为了方便,我们先定义几个名词:
拿到赛题以后,第一步就是要破题,我们需要将问题转化为相应的机器学习问题。其中,Kaggle最常见的机器学习问题类型有:
比如Quora的比赛就是二分类问题,因为只需要判断两个问句的语义是否相似。
所谓数据挖掘,当然是要从数据中去挖掘我们想要的东西,我们需要通过人为地去分析数据,才可以发现数据中存在的问题和特征。我们需要在观察数据的过程中思考以下几个问题:
对于数值类变量(Numerical Variable),我们可以得到min,max,mean,meduim,std等统计量,用pandas可以方便地完成,结果如下:
从上图中可以观察Label是否均衡,如果不均衡则需要进行over sample少数类,或者down sample多数类。我们还可以统计Numerical Variable之间的相关系数,用pandas就可以轻松获得相关系数矩阵:
观察相关系数矩阵可以让你找到高相关的特征,以及特征之间的冗余度。而对于文本变量,可以统计词频(TF),TF-IDF,文本长度等等,更详细的内容可以参考这里。
人是视觉动物,更容易接受图形化的表示,因此可以将一些统计信息通过图表的形式展示出来,方便我们观察和发现。比如用直方图展示问句的频数:
或者绘制相关系数矩阵:
常用的可视化工具有 matplotlib 和 seaborn。当然,你也可以跳过这一步,因为可视化不是解决问题的重点。
刚拿到手的数据会出现噪声,缺失,脏乱等现象,我们需要对数据进行清洗与加工,从而方便进行后续的工作。针对不同类型的变量,会有不同的清洗和处理方法:
都说 特征为王,特征是决定效果最关键的一环。我们需要通过探索数据,利用人为先验知识,从数据中总结出特征。
我们应该尽可能多地抽取特征,只要你认为某个特征对解决问题有帮助,它就可以成为一个特征。特征抽取需要不断迭代,是最为烧脑的环节,它会在整个比赛周期折磨你,但这是比赛取胜的关键,它值得你耗费大量的时间。
那问题来了,怎么去发现特征呢?光盯着数据集肯定是不行的。如果你是新手,可以先耗费一些时间在Forum上,看看别人是怎么做Feature Extraction的,并且多思考。虽然 Feature Extraction 特别讲究经验,但其实还是有章可循的:
在做特征抽取的时候,我们是尽可能地抽取更多的Feature,但过多的 Feature 会造成 冗余(部分特征的相关度太高了,消耗计算性能),噪声(部分特征是对预测结果有负影响),容易过拟合等问题,因此我们需要进行 特征筛选。特征选择可以加快模型的训练速度,甚至还可以提升效果。
特征选择的方法多种多样,最简单的是相关度系数(Correlation coefficient),它主要是衡量两个变量之间的线性关系,数值在[-1.0, 1.0]区间中。数值越是接近0,两个变量越是线性不相关。但是数值为0,并不能说明两个变量不相关,只是线性不相关而已;也可用 互信息、距离相关度来计算。
我们通过一个例子来学习一下怎么分析相关系数矩阵:
相关系数矩阵是一个对称矩阵,所以只需要关注矩阵的左下角或者右上角。我们可以拆成两点来看:
除此之外,还可以训练模型来筛选特征,比如带L1或L2惩罚项的Linear Model、Random Forest、GBDT等,它们都可以输出特征的重要度。在这次比赛中,我们对上述方法都进行了尝试,将不同方法的 平均重要度 作为最终参考指标,筛选掉得分低的特征。
如何进行特征选择,可点击这
终于来到机器学习了,在这一章,我们需要开始炼丹了。
机器学习模型有很多,建议均作尝试,不仅可以测试效果,还可以学习各种模型的使用技巧。其实,几乎每一种模型都有回归和分类两种版本,常用模型有:
幸运的是,这些模型都已经有现成的工具(如scikit-learn、XGBoost、LightGBM等)可以使用,不用自己重复造轮子。但是我们应该要知道各个模型的原理,这样在调参的时候才会游刃有余。当然,你也使用PyTorch/Tensorflow/Keras等深度学习工具来定制自己的Deep Learning模型,玩出自己的花样。
人无完人,每个模型不可能都是完美的,它总会犯一些错误。为了解某个模型在犯什么错误,我们可以观察被模型误判的样本,总结它们的共同特征,我们就可以再训练一个效果更好的模型。这种做法有点像后面 Ensemble 时提到的 Boosting,但是我们是人为地观察错误样本,而Boosting是交给了机器。通过 错误分析->发现新特征->训练新模型->错误分析,可以不断地迭代出更好的效果,并且这种方式还可以培养我们对数据的嗅觉。
举个例子,这次比赛中,我们在错误分析时发现,某些样本的两个问句表面上很相似,但是句子最后提到的地点不一样,所以其实它们是语义不相似的,但我们的模型却把它误判为相似的。比如这个样本:
为了让模型可以处理这种样本,我们将两个问句的 最长公共子串(Longest Common Sequence)去掉,用剩余部分训练一个新的深度学习模型,相当于告诉模型看到这种情况的时候就不要判断为相似的了。因此,在加入这个特征后,我们的效果得到了一些提升。
在训练模型前,我们需要预设一些参数来确定模型结构(比如树的深度)和优化过程(比如学习率),这种参数被称为超参(Hyper-parameter),不同的参数会得到的模型效果也会不同。总是说调参就像是在“炼丹”,像一门“玄学”,但是根据经验,还是可以找到一些章法的:
在Test Data的标签未知的情况下,我们需要自己构造测试数据来验证模型的泛化能力,因此把Train Data分割成Train Set和Valid Set两部分,Train Set用于训练,Valid Set用于验证。
将Train Data按一定方法分成两份,比如随机取其中70%的数据作为Train Set,剩下30%作为Valid Set,每次都固定地用这两份数据分别训练模型和验证模型。这种做法的缺点很明显,它没有用到整个训练数据,所以验证效果会有偏差。通常只会在训练数据很多,模型训练速度较慢的时候使用。
交叉验证是将整个训练数据随机分成K份,训练K个模型,每次取其中的K-1份作为Train Set,留出1份作为Valid Set,因此也叫做K-fold。至于这个K,你想取多少都可以,但一般选在3~10之间。我们可以用 K 个模型得分的 mean 和 std,来评判模型得好坏(mean体现模型的能力,std体现模型是否容易过拟合),并且用K-fold的验证结果通常会比较可靠。
如果数据出现 Label 不均衡情况,可以使用 Stratified K-fold,分层采样,确保训练集,测试集中各类别样本的比例与原始数据集中相同),这样得到的 Train Set 和 Test Set 的 Label 比例是大致相同。
曾经听过一句话,”Feature为主,Ensemble为后”。Feature决定了模型效果的上限,而Ensemble就是让你更接近这个上限。Ensemble讲究“好而不同”,不同是指模型的学习到的侧重面不一样。举个直观的例子,比如数学考试,A的函数题做的比B好,B的几何题做的比A好,那么他们合作完成的分数通常比他们各自单独完成的要高。
常见的Ensemble方法有Bagging、Boosting、Stacking、Blending。
Bagging是将多个模型(基学习器)的预测结果简单地 加权平均 或者 投票。Bagging的好处在于可以 并行 地训练基学习器,其中 Random Forest 就用到了Bagging的思想。举个通俗的例子,如下图:
老师出了两道加法题,A同学和B同学答案的加权要比A和B各自回答的要精确。
Bagging通常是没有一个明确的优化目标的,但是有一种叫Bagging Ensemble Selection的方法,它通过 贪婪算法 来Bagging多个模型来优化目标值。在这次比赛中,我们也使用了这种方法。
Boosting的思想有点像知错能改,每训练一个基学习器,是为了弥补上一个基学习器所犯的错误。其中著名的算法有 AdaBoost,Gradient Boost。Gradient Boost Tree 就用到了这种思想。
我在1.2.3节(错误分析)中提到 Boosting,错误分析->抽取特征->训练模型->错误分析,这个过程就跟Boosting很相似。
Stacking是用 新的模型(次学习器)去学习怎么组合那些基学习器,它的思想源自于Stacked Generalization这篇论文。如果把Bagging看作是多个基分类器的线性组合,那么Stacking就是多个基分类器的非线性组合。Stacking可以很灵活,它可以将学习器一层一层地堆砌起来,形成一个网状的结构,如下图:
举个更直观的例子,还是那两道加法题:
这里A和B可以看作是基学习器,C、D、E都是次学习器。
在实现 Stacking 时,要注意的一点是,避免标签泄漏(Label Leak)。在训练次学习器时,需要上一层学习器对 Train Data 的测试结果作为特征,如果我们在Train Data上训练,然后在Train Data上预测,就会造成Label Leak。为了避免Label Leak,需要对每个学习器使用K-fold,将K个模型对Valid Set的预测结果拼起来,作为下一层学习器的输入。如下图:
由图可知,我们还需要对Test Data做预测。这里有两种选择,可以将K个模型对Test Data的预测结果求平均,也可以用所有的Train Data重新训练一个新模型来预测Test Data。所以在实现过程中,我们最好把每个学习器对Train Data和对Test Data的测试结果都保存下来,方便训练和预测。
对于Stacking还要注意一点,固定 K-fold 可以尽量避免Valid Set过拟合,也就是全局共用一份K-fold,如果是团队合作,组员之间也是共用一份K-fold。如果想具体了解 为什么需要固定K-fold,请看这里。
Blending 与 Stacking 很类似,它们的区别可以参考这里
有些时候在确认没有过拟合的情况下,验证集上做校验时效果挺好,但是将测试结果提交后的分数却不如人意,这时候就有可能是训练集的分布与测试集的分布不一样而导致的。这时候为了提高LeaderBoard的分数,还需要对测试结果进行分布调整。
比如这次比赛,训练数据中正类的占比为0.37,那么预测结果中正类的比例也在0.37左右,然后Kernel上有人通过测试知道了测试数据中正类的占比为0.165,所以我们也对预测结果进行了调整,得到了更好的分数。具体可以看这里。
深度学习具有很好的模型拟合能力,使用深度学习可以较快得获取一个不错的Baseline,对这个问题整体的难度有一个初始的认识。虽然使用深度学习可以免去繁琐的手工特征,但是它也有能力上限,所以提取传统手工特征还是很有必要的。我们尝试Forum上别人提供的方法,也尝试自己思考去抽取特征。总结一下,我们抽取的手工特征可以分为以下4种:
FNN,CNN,RNN的区别
比赛中发现的一些深度学习的局限:
通过对深度学习产生的结果进行错误分析,并且参考论坛上别人的想法,我们发现深度学习没办法学到的特征大概可以分为两类:
传统的机器学习模型和深度学习模型之间也存在表达形式上的不同。虽然传统模型的表现未必比深度学习好,但它们学到的Pattern可能不同,通过Ensemble来取长补短,也能带来性能上的提升。因此,同时使用传统模型也是很有必要的。
比赛结束不久,第一名也放出了他们的解决方案,我们来看看他们的做法。他们的特征总结为三个类别:
并且他们也使用了 Stacking 的框架,并且使用 固定的k-fold:
我们模型存在不足:
工欲善其事,必先利其器。
Kaggle 的上常工具除了大家耳熟能详的XGBoost之外, 这里要着重推荐的是一款由微软推出的LightGBM,这次比赛中我们就用到了。LightGBM的用法与XGBoost相似,两者使用的区别是XGBoost调整的一个重要参数是树的高度,而LightGBM调整的则是叶子的数目。与XGBoost 相比, 在模型训练时速度快, 单模型的效果也略胜一筹。
调参也是一项重要工作,调参的工具主要是Hyperopt,它是一个使用搜索算法来优化目标的通用框架,目前实现了Random Search和Tree of Parzen Estimators (TPE)两个算法。
对于 Stacking,Kaggle 的一位名为Μαριος Μιχαηλιδης的GrandMaster使用Java开发了一款集成了各种机器学习算法的工具包StackNet,据说在使用了它以后你的效果一定会比原来有所提升,值得一试。
以下总结了一些常用的工具:
1. 请问在Stacking中,你们如何得知可以这样组合? 以及如何验证其效果跟正确度呢?
对于这个问题我们第一时间想到的是用deep learning,然后再抽手工特征,这就组成了stage1,在stage2的时候想要发挥stack的能力当然是要上非线性模型,然后在进入stage3,而stack层数越深,所用的模型需要更简单,不然很容易过拟合,因此用了bagging ensemble selection,当然你也可以像第一名的做法,去训练带惩罚项的线性模型。至于magic feature为什么要放在deeplearning的输入,那是因为我们发现deep learning会容易过拟合到某些特征,加入magic feature可以显式地告诉它“你已经有这些特征了,你可以去挖掘其他的语义特征”,其实你甚至可以把所有的 手工特征 也加入deeplearning的输入,这样效果可能会更好,第一名就是这么做的。
如何验证stacking的效果,很简单啊,用那份固定的kfold做交叉验证。但是要注意,stacking有过拟合到 valid set 的风险,所以最好的评判当然是leaderborad啦。
2. stage2中的非线性模型,是任何的非线性模型皆可吗? 还是需要数学验证呢?
使用 Ensemble 的时候秘诀就是多尝试,不能吊死在一棵树上,如果你认为xgboost是里面表现最好的,不需要尝试其他模型了,那就太可惜了。我们不能保证一种模型一定能够学习到所有的方面,所以需要去尝试其他模型,尽量让学出来的模型好而不同,这样可以让各种模型发挥自己的长处。所以,不仅仅是非线性模型可以,就算是线性模型也可以,当然我们还要考虑时间、资源等因素进行一些取舍。
如果我们追求的只是实用性和准确性,只要实验的效果出色即可,如果要严谨地用数学证明再去使用,说不定等你证明完比赛已经结束了。
每次学习感觉都醍醐灌顶,在此感谢知乎大神分享
参考文献:
1. Paper: Ensemble Selection from Libraries of Models
2. Kaggle 数据挖掘比赛经验分享
3. Kaggle Ensembling Guide