历时98天的阿里移动推荐算法终于结束了,有种终于下了贼船的感觉。总的来说,整个比赛的体验并不好:时间太长,资源不够,运气成分大,学到的干货少。虽然学到的干货不多,但这毕竟是在Data Science道路上进军的第一场实战,还是有必要好好总结一下。
比赛统计
初赛名次:21
复赛名次:22
Python代码行数:2320
SQL代码行数:6656
天池平台数据表数:1006
线下结果数:490
初识比赛
虽然比赛的名字叫做移动推荐算法,但本质上也是一个机器学习的问题。比赛给出了一个月(2014.11.18~2014.12.18)的用户对商品操作的行为数据,需要预测12.19号这天的购买行为,评分采用经典的F1值计算。行为数据中包括了时间、地点、用户、商品、行为类型、商品类别六个要素,特征提取的思路就是围绕这六个要素进行。题目还给出了一个商品子集,只需要提交对商品子集购买行为的预测结果。
大致看了去年前十选手的比赛总结,比赛流程大致分为4个模块:
- 特征提取
- 训练集构造
- 模型学习调参
- 模型融合
这4个模块并不是相互独立的,比如特征提取和训练集构造通常是一起完成,构造训练集也会用到简单模型学习来平衡正负样本数。
比赛分为两个赛季:
- 初赛3.20-4.25,仅提供1万用户数据,采用本地调试提交。
- 复赛4.30-7.1,有500万用户数据,使用阿里天池平台提交。
初赛和复赛是需要区别对待的:
- 工具:初赛本地调试,学习模型和特征提取并不拘泥,可以用任意熟悉的工具。然而复赛需要使用天池平台,工具受限制。
- 模型or规则?初赛数据量很小,很可能模型效果并不好,事实证明,规则实际上是简单粗暴效果佳的。复赛则不可能寄希望于规则,老老实实搞模型吧。
- 时间分配:初赛只用进前500名即可,不用投入太多的精力。到复赛前期越快熟悉环境越好,尽量前期多做,比赛资源并不充足,后期资源会非常有限,速度很慢。
初赛
初赛的环境使用了MySQL+Python,选择Python的原因是数据挖掘的包很齐全也容易上手:numpy、scipy、pandas,还有机器学习包scikit-learn,对付比赛足够用了。
特征提取
根据行为数据的各个字段,很容易可以将特征分为以下5类:
- 用户-商品UI特征
- 用户特征
- 商品特征
- 类别特征
- 地理位置特征
特征类别主要是计数和4行为转化率,比如用户1行为计数,4行为计数/1行为计数等等。
第一赛季并没有想到时间特征,只是根据时间划分训练集,第二赛季才发现时间特征非常重要。
为了便于特征提取,我用两种方法存放数据,一种是存入SQL表中:
建立表logUser,user_id为主键,
字段名:
user_id,item_start,item_end
建立表logItem,item_idx为主键,
字段名:
item_idx,item_id,bhv_start,bhv_end
建立表logBhv,bhv_idx为主键,
字段名:
bhv_idx, behavior_type,user_geohash,item_category,time
这样存放可以方便地提取UI特征,但事实证明这种方法实际上也没有很快,因为需要大量的SQL查询操作。幸而第一赛季数据量并不大,提取训练集并不是瓶颈。
第二种方法是将数据按天分开,便于提取用户、商品、地理、类别的全局特征,这部分特征我全部使用了awk脚本提取。
构造训练集
线下训练集使用12.18号购买行为标注样本,严格使用18号之前的数据提取特征(包括全局特征),样本为18号之前n天有交互行为的UI对。 线上预测集则使用19号之前n天所有的交互行为的UI对。
由于正例负例不平衡,需要对训练集进行抽样,抽样比例是在1:5到1:20这个区间里找最优结果。
模型训练
主要使用了Adaboost,Random forrest,Logistic Regression三种模型,直接调用Scikit-lSearn提供的模型训练。其中树型模型的测试结果最好。
然而第一赛季受到数据量的限制,实际上规则比模型好用多了,比如购物车+时间规则:前一天晚上8点后加入购入车的UI,就可以做到比单个模型结果好很多。
模型融合
第一赛季比赛前期一直死磕模型,成绩一直提不上去,改成规则后才大呼坑爹。
初赛的最优成绩使用了规则+模型混合的方法,使用规则得到候选集后,再从候选集中去掉多个模型预测按概率排序后的top k负例交集。
复赛
第二赛季要使用天池平台,第一赛季的代码都用不上,得全部推翻重写。天池平台的工具有SQL、Map Reduce、UDF,算法平台也提供了经典的机器学习模型。
SQL容易学习,但写起来复杂容易出错,没有可读性,不提供参数设置,复用比较麻烦。Map Reduce和UDF学习成本高,可以实现复杂逻辑,但天池对这两个工具的限制很多,文档对用户非常不友好。
大部分特征、训练集和模型基本需求如计算f1等等都是通过SQL实现,少部分特征和其他复杂功能使用了UDF和Map Reduce。比较有用的是Map Reduce实现了特征Information Gain的计算,用来进行特征筛选,这要感谢会Java的队友/男票xge。
特征提取
整个复赛的特征工程一共进行了10次更改,更改大多是增加新特征,从最初的22维增加到最后的380维。特征构建还是一赛季的思路,不过根据一赛季的结果,筛选掉了一部分无用特征。除了简单计数和4行为转化特征,在第一赛季基础上增加的特征有:
- 时间特征
- UI行为距离标注日的小时数
- 用时间衰减来计算加权后的行为计数
- 多次行为操作之间的时间间隔(Map Reduce实现)
- 不同时间粒度的UI特征
- 按照标注日前1天,3天,7天,30天为粒度提取
- 按照标注日前4小时,8小时,16小时为粒度提取
- 增加地理特征
- 用户商品的距离
- 用户地理用最后操作位置填充
- 商品地理用购买该商品的用户位置填充
- 计数特征去重
- 商品特征计数对用户去重
- 类别特征计数对商品去重
- 增加交叉特征
- 用户&UI特征交叉
- 商品&UI特征交叉
- 类别&UI特征交叉
- 用户&商品特征交叉
特征增加对分数提升是最显著的,在特征选择中也需要注意:
- 不要盲目添加大量特征,最好一部分一部分添加,这样会对添加的特征效果有个大体的认识。
- 添加的特征要能从现实逻辑上解释得通,最好是直观上影响购买行为的特征。
- 特征筛选或许有用,但最好添加特征时就尽量加入有用的特征,不要妄图从一大堆特征中再筛选。
训练集构造
构造训练集前对数据进行了一个初步的统计,第二天的购买行为中仅有32%的UI是有过交互的,剩下64%的购买行为都是当天的偶发购买。前1天有过交互的占16%,前2天21%,前2天23%,前4天25%。也就是说,有一半的正样本是前1天的,因此仅仅使用前1天的交互UI构造训练集。
考虑到仅仅需要对商品子集进行预测,为了使训练集和预测集保持一致,又可以减少训练集的规模,所以只使用了商品子集内的商品构造训练集。
在比赛前期,这样构造训练集是有效的,缩短了模型训练时间,加快了特征迭代的工作。但是当特征增加不能再提升分数时,应该想到,这样构造训练集存在很大的问题,丢掉了很多正例样本。和其他队伍交流来看,这实际上是比赛后期的一个瓶颈,限制了特征的发挥。
正确的做法是:在特征工程比较完善之后,最好是比赛中期,使用不止1天的交互和商品全集构造训练集,采用简单模型过滤掉大量负样本。这点我到比赛后期才开始改,平台资源已经不够了,所以并没有做好,也是比赛的一大遗憾。
可以通过不同的标注日来得到不同的训练集,注意要预留出相同规模的验证集和测试集,到模型融合时会有用。
模型学习+调参
- 逻辑回归 逻辑回归(Logistic Regression)训练速度预测速度很快,预测效果较差,可以用来进行负样本筛选,模型融合也有用。
- 随机森林 随机森林学习速度一般,预测速度很慢,主要用于模型融合。
- GBDT二分类 GBDT训练速度慢,预测速度一般,单模型的效果最优。唯一的调参经验是:树越多越好,但速度也越慢。并且特征不一样,最优的参数也不一样,调参时注意每次最好只改变一个参数值,否则不容易看出参数的影响。不要使用枚举参数的办法调参,太浪费时间。
模型融合
总结一下道听途说模型融合的方法: 1. 取不同模型top k并集 2. 取不同模型top k交集 3. 直接将预测概率相加 4. 将预测概率作为特征用lr训练 5. 将预测概率作为特征加入验证集,再将验证集当做训练集训练。
对我来说以上5种方法都是然并卵,不论使用哪种融合方式,总没有单模型的结果好。具体的原因还没有找到,或许是因为没有对训练集进行抽样,GBDT模型对正负样本比例并不敏感,所以我直接用了所有的负样本,也算是这次比赛的第二大遗憾吧。
其他收获
与其说是收获,不如说是一些犯过的错误,总结出来自勉:
不要过早优化
主要是模型调参和模型融合,这两部分应当留到比赛中后期来做,过早优化耽误时间也没有意义。
不要短视
比赛每天都在更新名次,但不要只关注短期的分数,更要为长期做打算。比如虽然调参、获得更多的训练集会提升暂时的分数,但是也失去了快速迭代特征的机会。要时刻记住这是一场长时间的比赛,终点才是胜利,中间领跑并不说明问题。
合理安排时间精力
初赛不需要投入全部精力,复赛前期尽量多做,否则后期没有资源。
不要妄下结论
对于不确定的猜想,一定要用数据验证,比如训练集用商品全集还是子集的区别,是否需要抽样等等。尽量多试多做,要去尝试各种可能性。
不要懒,不要懒,不要懒
重要的事情说三遍。有时会觉得麻烦,于是采用了写起来简单但效率低的方法。有时明明应该改成另一种方法,又觉得不改也没影响就算了吧。有时觉得今天太晚了就不做了,马虎总是不检查提交结果,于是又浪费了很多次机会。其实这种时候就会感觉,有个队友还是挺好的。
后记
比赛最初的期望是进复赛,进入复赛后又期望进前50,进入前50后又期望进首页,进入首页又期望进前十,最好的时候也排到过第二名。但是比赛后期确实陷入了瓶颈,主要是模型融合和训练集的构造,还是挺遗憾的。虽说如此,但还是超预期完成了目标,也是值得开心一下。貌似赢的积分可以拿去换个杯子T恤和音响,为了身心健康,也决定不再参加阿里天池的其他比赛了,第二战打算去搞搞Kaggle。
最后,衷心感谢拉我参赛从头到尾互为小号没有放弃的Libaier战友,长时间占据榜首传授高招的江神,以闺蜜幸福为代价出售特征的闺蜜男票Xuhuan童鞋,以及男票兼打酱油队友xge。
完。