阿里数据挖掘竞赛之自我总结——秋田君

我是怎么开始做这个比赛的

忘了具体是哪天,反正三月的某个傍晚,我正在往github上传MLAPP中python部分的代码,邱泽宇打电话问我,阿里有个数据挖掘的比赛,有兴趣参加不?思考了3秒钟,我答应说,行。随后看了下比赛细则,感觉进个前五十应该不是太大的问题(要知道我们报名的时候才一百多队,这点自信还是有滴哈)。所以一开始的目的,不过是希望能够借助比赛这个形式,在第一季熟悉下scikit-learn机器学习的包,然后将上学期学过的一些机器学习算法在这个数据集上实践一下,把基础打扎实。至于第二季,就是希望能借助阿里的分布式平台,进一步熟悉下用怎么用mapreduce写一些常见算法,由于第一学期云计算的课只是在虚拟机下跑了跑,感觉不够尽兴,一想到有这么个免费平台跑大数据,这感觉应该很爽~

得承认,之前毫无做这类比赛的经验,我低估了比赛的实际难度。抱着打基础的目的而去,结果比赛的整个过程异常激烈,所需要的知识也远不止基础算法,而是涵盖了模型、特征等等更多外围的东西。而且进入第二季后才发现,自己动手在odps上写算法是一个异常艰巨的过程,很大程度上只能依赖odps上已有的几个算法。以至于做到后面有种骑虎难下的感觉,接着做下去吧,感觉花大量时间在上面收获不是特别大(我不是个急性子的人,总想着先把自己的基础打好了再在实践中一点点巩固);就这么放弃吧,心有不甘,连top50都进不了这实在说不过去,而且有点坑队友啊。就这样的状态一直到了6月份某天。突然发现了个bug,成绩才算有了点起色。在此之前的那段时间,真的觉得挺难熬的,每天无论怎么改代码换算法,成绩就在那里,纹丝不动,只能仰望首页的大神们。月底去青岛玩了一周,回来后调整了下状态,决心好好搏一把,至少得混个top50,然后和邱同学一起对特征做了重大更新,终于突破了top50的baseline。再后来,好不容易进首页后,其实没有太多兴奋了,只是觉得,唉,来得太晚了......因为排行榜的名次直接影响自己的思考方式。排名靠后的时候,更多的是考虑我的特征是不是不够好?而排名靠前时考虑的则是,是模型的融合方法不对,还是没有找准Boost的点?,然而这时候已经没有太多机会来验证这许许多多的想法了。有些可惜,以至于后面换数据集后,有点后力不足。

有时候会想,这段时间,如果花在打基础上,收获是不是会更大?呵呵,只能怪自己参赛的时候,2young2simple。

不管怎么说,参加这个比赛,还是挺有收获的,所以决定在这里都记录下来。我会尽量跳出实现细节,聊聊感悟。不对之处,恳请指正。

先上图(原谅我字写得很怂......):

Motiation

下决定之前,想清楚自己的动机是什么。我一直相信,只要自己对于某件事的motivation足够强大,总可以找到办法去实现的。

一开始给自己设定的目标是巩固机器学习基础算法,所以在s1阶段,选择尽量多地去尝试各种不同算法,并对其效果进行评判。整个过程中队友用matlab,我用python,差不多把sklearn中的各个算法跑了个遍,除此之外还有协同过滤方面的一些包。虽然s1阶段,自己对成绩的贡献不是特别大,但是玩得很happy,因为我的目的达到了,我很享受整个比赛的过程。最后在队友的帮助下将s1成绩提升到了二三十名。进入s2后,给自己设定的目标是熟练使用mapreduce并在此之上实现一下神经网络的算法。并没有特别制定个目标要到多少多少名。自我感觉上手还算快的,没花几天就大致把平台上的工具熟悉了一遍。可是云平台的各种限制,使得自己实现算法异常困难,而且由于缺少一些测试数据,即使自己写出像神经网络和协同过滤方面的算法后,验证起来很费劲。与此同时,队友套用s1算法的效果并不理想,成绩一直上不去。不得不把我拉回来从头开始一点点实现。郁闷的是,自己一直用的是删除没有购买记录用户后的数据集,无论怎么更换算法,一点起色都没有。这是最煎熬的阶段,一方面,自己想要实现的算法没完善好,另一方面排行榜上的成绩很难看而自己又帮不上忙。认真思考和权衡之后,决定放弃最初的“实验性参赛”的想法。既然是比赛,那就注定是残酷的。意识到这点时,多少让人觉得有些忧伤,本想着有这么个机会相互交流算法,巩固基础,无奈不得不匆忙上阵和大神们赤手相搏,结果,逆袭不成功,当然是趴到在地......

所以啊,有个明确的参赛动机,真的很重要。如果是抱着学习的目的,那就充分利用参赛资源,自己动手实现各种算法,并在大数据上验证自己的想法。如果是奔着大赛的奖励机制而去的,那也不用太在意实现的方法上是不是很奇葩,只要瞄准榜首的f1,反复迭代输出即可,我一想起康同学前面全用sql,直到最后一个月才开始用MapReduce提取特征就心生佩服,不过最后进了top50也就无所谓了,哈哈。最好别像我这样,前面想着打基础,后面想着进首页,最后坑了自己也坑了队友。

Data

啰嗦了这么多,来点实际的。

数据是不是越多越好?

机器学习的理论告诉我们,数据越多,往往测试集上的效果越好。这一点很容易解释。不过,看前面有参赛队友的经验分享里提到这么一个观点,数据也并不是越多越好,因为数据量太大导致计算资源的消耗太大,以至于某些算法跑不起来或者是耗时太长。不过我倒是觉得,问题不在于数据量太大导致计算量太大,而是,数据的使用不合理。为了降低计算量,大家普遍采用的一个办法就是抽样。抽样的方法无外乎随机抽,加权采样抽这两类。由于正负样本不均衡,一种简单的做法是将负样本抽样至合适的比例后训练并预测。好处当然是数据的规模降下来了,但不足之处是抽样的随机性导致训练结果会出现小幅波动,本来这应该是在可接受的误差范围内,不过,这毕竟是比赛,当你与首页相差0.0x%的时候,这个小幅波动就显得尤为重要了。于是呢,诞生了各种神奇的做法。

  1. 本地测试好合适的样本比,确定下来后。每次线上测试时抽不同的负样本,并对负样本标注。然后选线上结果最好的那组负样本。

  2. 按照法1的做法,给每次随机抽样训练后得到的模型赋予相同权值,然后再融合结果,降低随机误差的影响。

  3. 正负样本比不变,在可接受的计算量范围内,复制正样本,同时抽样负样本。这样扩充了负样本的数量,调整了正负样本比,同时又可以一定程度上降低计算量。相比1的好处是模型输入的负样本数量更多了,实际效果略有所提升。

抽象的来说,以上做法都是为了调整合适的正负样本比,而对负样本采取的不同策略。不过,其实我们可以对正样本部分作进一步挖掘。联想到数字图像处理中,为了丰富样本库,增加模型的鲁棒性,我们会对图像做一些列的仿射变换,对应到我们这里,其实就是对正样本增加一些合理的噪声,从而在增加模型的鲁棒性同时,扩充正样本数量。最简单的做法,就是对训练样本的时间尺度做一个平移,比如一般选取1~92天作为基准,2~93天等等的正样本作为补充,样本区间越小,可平移的范围也就越大。据Marvel队的李森栋同学说,后期采用过这种做法,虽然不清楚具体的实现形式,但我估计时间跨度应该比这窄。

数据预处理

应该说,我自己就是栽在这。说起来都是一把辛酸泪啊......

按照s1的做法,先上来就把几乎不可能购买的用户从原始数据集中删除了。然后前两个月一直在这个数据集上做测试,坑爹的线下永远比线上高许多。偶然有一天和李森栋同学交流后才想到这个问题(再次感谢李同学,哈哈),由于这个预处理做得太早了,我一直在这个数据集上操作,甚至没有意识到预处理的这个过程。更正这个之后,瞬间跳跃至top50内。唉,总之发现这个问题后,真的很是惭愧,相当于前面两个月做的所有事情都是在瞎掰。这直接导致我后期不敢再对数据做预处理了......

根据其他同学的经验来看,对一些噪声数据适当的平滑处理,是可以提高算法的性能的。这毕竟是比赛,更加特殊地,可以把这部分异常用户的数据单独抽出来建模和训练,然后叠加到结果上,会有惊喜哟,呵呵。我想说的其实就是,异常点的处理并不是简单删掉不予考虑就好。其实在大数据集下,异常点也有规律可循。比赛到最后,考验的就是谁的心思更缜密更完备了。

数据集怎么划分

对于一般的分类预测问题,数据集都是标注好了的,训练集、测试集、验证集不过是一个简单的拆分便能搞定了。但这次所给数据的奇葩之处在于,数据本身是“自标注”的。这就使得数据更具有可玩性了。比赛一开始便有人提到了两种划分方式,并且说这两种划分方式对结果的影响不大,其实,这里边还是有许多细节值得探索。

  1. 1~63天提取训练集特征,64~93提取训练集标注信息;31~93提取测试集特征,94~123提取测试集标注信息,线上采用61~123提取的特征。(这种划分方式的明显优点在于本地测试和线上测试几乎保持一致,除此之外,还有个隐藏的优点,也就是前面提到的可以通过对时间段平滑处理丰富正样本;当然,缺点也很明显,模型接触的数据的时间范围只有两个月)

  2. 1~93提取特征,94~123提取标注信息。然后将特征拆分成训练集和测试集两部分用于本地测试。(这样做的优势是模型接触的时间范围有3个月,模型的鲁棒性更好,正样本也会更丰富,缺点是有些特征没法用于本地测试,比如用户或者商品的历史排名信息,采用这种方式会导致本地训练集和测试集上的用户和商品的排名信息是同一时间段的,具有强关联性质,而前一种划分方式中,测试集和训练集属于不同时间段的,从而本地测试更具有预见性)

相信大多数队伍会在这两种划分方式里,结合自己的特征体系有着自己的考虑和抉择。但我当时有这么几个问题:

1.既然两种划分方式各有利弊,那能不能结合这两者的优点来预测呢? 答:完全可以,实际上这就涉及到怎么融合两种不同划分体系的预测结果了。

2.为什么一定要是2选1呢? 答:其实还可以有更多选择,比如,64~93提取特征,94~123提取标注。只用一个月的信息去预测下个月的信息。虽然这样子只用一个月做训练集的效果肯定不如用3个月的效果好,但是更有时效性,其预测的对象更有针对性。另外,1~93天就一定是最好的了么?为什么不能用1~108天作为训练集,109~123作为标注,我只预测未来半个月的购买情况呢?时间上这些都是可以用于融合的部分。

数据可视化

平台上工具有限,没法做太多可视化分析。但是我发现了几个有意思的地方。

  • 在s1阶段,大家纷纷猜测这是哪一年数据的时候,有个很有意思的分析,就是把连续7天的购买量,点击量统计出来,然后根据周末购买量的差异性结合每一年具体时间对应的星期来发现数据具体是哪一年的。(看来那哥们跟我们想到一起去了)

  • 在s2阶段,有人画了个很有意思的图,我没保存下来,很可惜。就是统计模型预测的结果对应到下个月具体是哪一天,从那个图上很容易看到,模型对于时间越近的命中次数越高。这也很有指导意义。

FEATURE

我们对在特征上做得的确没有其他队好,一开始便走在了错误的方向上。前面两个月一直用的30多维基础特征,并在此基础上做了太多无用功。后来和队友交流,为啥我们不像其它队一样直接想特征往上堆就是了,人家都成百上千的特征了我们还在几十维特征上死磕。后来得出结论,我们都认为想特征这活,big太低......也就是说,我们完全走的一个反方向,企图先在基础特征上巩固好算法(我承认这其中多少是因为自己抱着学习算法的目的而来),然后再不断加特征持续提升。无奈由于前面提到的bug,前面两个月一直卡在第一步。所以,关于特征提取这部分实在没有太多经验可分享,不过某队提到的将NLP中的概念迁移到这里的想法挺有意思的。之前我也有想过借助文档相似度的计算方法来解决用户和商品聚类的问题。前面耽搁的时间太多,后期都没机会实现了。

特征的筛选

虽然特征的提取没有太多经验,但我们队在特征筛选上还算是有所体会。前面提到我们对前期坚持只用三十多维特征,是因为经验告诉我们高维度特征中,真正关键的也就那么几十维而已,太多的特征意义不是特别大(当然提升那么0.0x%还是有用的)。后来迫不得已跟风上了300多维特征后,面临一个问题,如何从中找到最有用的特征。

选特征的方法很多,大家常用的一个就是通过随机森林的模型表查看哪些特征更有区分度。很遗憾的是,这只能做一个粗略筛选,并不是总能work。而且我们的目的是将维度降到100以下,按照这个方法来筛选,显然难以满足需求。

既然没有好的方法降维,那就只好来硬的,邱同学提出直接采用贪心算法,先选出3维点击特征作为基础(本地F1接近5%),将特征分组(每组特征的粒度很细,<=3维)后叠加上去,根据本地F1的升降(给定一个阈值)决定保留与否。3个进程同时跑,跑之前把特征组随机shuffle一下。我们采用的是随机森林,跑起来很快(50棵树一次也就17min)。由于随机森林的随机性,再加上shuffle的过程,最后得到的特征组会有较大的不同。调整阈值后会进一步放大这种差异性。整个过程耗时三四天。得到的特征组分别有(39维,43维,59维,117维等)这些特征单独预测的结果稍低于使用300多维特征的结果(其实也就差0.02~0.03%)但融合后却可以比单独使用300多特征的效果高出(0.03%)左右。

ALGORITHM

在s2阶段,大家的工具都一样。我个人感觉特征只要还行,用好gbrt后还是相比rf高出一些。反正我们没用到太奇葩要想破脑袋才想出来的特征,无非是转化率增长率之类的。所以,在工具和特征的差别都不大的时候,决定差别的就是——参数、融合这二者了。

调参

一看到这俩字,我队友估计就会吐槽,这活太特么无趣了。其实,无论是rf还是gbrt,调参都是相对容易的事。对于rf来说,当然是树越多越好,每棵树的数据量也是越多越好。剪枝的策略无非那3种,测试一下就好。对于随机采样的特征数,只要前面特征找好了,选择每次分裂使用全部特征应该是最优策略,至于树的深度和叶子结点的数目,由于每颗树都会随机采样,控制起来很复杂,这里不加限制就行了。对于gbrt而言,先固定树的数量后,根据3倍步长的经验遍历一下其它参数就很容易找到最优解,再根据线上的实际效果增加数的数量。

融合

我只说我们队的做法,对于同模型,一般采取概率归一化后加权相加。Rf和gbrt的融合,相信很多队都尝试过,只能说,不理想。李森栋同学也提到这个融合起来没有那么理想。 不过,似乎级联的效果要比较好,很可惜最后才尝试,没来得及线上测试。

对rf的预测结果计算偏差,再对偏差进行回归预测,然后进一步矫正rf的预测结果。

Adaboost,这个实际上是很有用的,在这里最关键的地方是要重采样的过程要控制好,简单的增加错分样本的权重,效果不明显。不过采用adaboost有个很蛋疼的问题,计算量!根据以往的经验,一般要迭代数十次后才收敛,然而计算一次rf或者gbrt就要几个小时,这显然难以忍受。所以我们的做法是将adaboost放在了第二级,也就是前面一级是多组特征的rf和gbrt输出结果,然后把结果输入lr再boost(lr算起来快)。很可惜,几乎没有提升,实在找不到原因。有机会可以去看看大神们都怎么用的adaboost。

未交互部分预测

没做。其实准确的说,应该是,做了,但效果不明显(别笑,哈哈)。

但是,经验还是有滴。那就是,处理未交互部分的最好办法是,采用贝叶斯的理论来部分解决。当时重点考虑了以下几点:

  • 用户购买热门商品的可能性

  • 用户下周的购买欲

  • 用户的迁移概率

  • 商品的下周的热门程度

  • 热门商品与用户已接触商品的关联程度

通过这些来挖掘最可能购买热门商品的那一部分用户并给其推荐近期热门商品。

不知Marvel队有何神奇滴算法呢,呵呵,拭目以待吧。

总结

最后上张图,其实,整个过程中,我们队侧重的都是ML的部分,而反观那些充分挖掘用户商品特征的首页队伍,他们才是真正在做DM。这也提醒自己,真实的数据,往往比实验中所测试的数据复杂得多。不同的问题,不同的数据适合不同的算法。算法的复杂性和效果好坏相关性不大,但是同样的特征不同的算法效果是有差异的,而且很明显。(这话是算者说滴哈)

最最后,贴个笑话。(bigdata是北航滴李森栋同学,其实我们都忘了在F1后面加一个百分号,结果还都以为对方真的做到了那么多,哈哈)

对了,忘了说我们队的成绩了。憋了好久,我居然没想起来S1具体排多少名!!!回头问队长,汗......答复是,“我也不记得了......应该跟S2差不多吧”。看来我们队真是重在参与啊......

其实,这个过程里要感谢许多人,没有刘强他们的帮忙,估计s1都比较悬。很久以后,我会忘了这比赛的许多细节,甚至忘了比赛最后取得的成绩,可是,这些真的没那么重要。只要还记得,一群人曾经一起这么努力地参加过一个比赛,就足够了,满满的都是回忆......

我们队是秋田君