一、 赛题说明
1. 竞赛题目
在真实的业务场景下,我们往往需要对所有商品的一个子集构建个性化推荐模型。在完成这件任务的过程中,我们不仅需要利用用户在这个商品子集上的行为数据,往往还需要利用更丰富的用户行为数据。定义如下的符号:
- U:用户集合
- I :商品全集
- P:商品子集,P ⊆ I
- D:用户对商品全集的行为数据集合
那么我们的目标是利用D来构造U中用户对P中商品的推荐模型。
2. 数据说明
数据主要包含两个部分。第一部分是1000万用户在商品全集上的移动端行为数据(D),包含如下字段:
字段 | 字段说明 | 提取说明 |
---|---|---|
user_id | 用户标识 | 抽样&字段脱敏 |
item_id | 商品标识 | 字段脱敏 |
behavior_type | 用户对商品的行为类型 | 包括浏览、收藏、加购物车、购买,对应取值分别为1,2,3,4 |
user_geohash | 用户位置的空间标识,可以为空 | 由经纬度通过保密的算法生成 |
item_category | 商品分类标识 | 字段脱敏 |
time | 行为时间 | 精确到小时级别 |
实例如:
141278390,282725298,1,95jnuqm,5027,2014-11-18 08
这些字段中behavior_type字段和time字段包含的信息量最大,user_geohash字段由于缺失值太多,基本没法使用。
第二个部分是商品子集(P),包含如下字段:
字段 | 字段说明 | 提取说明 |
---|---|---|
item_id | 商品标识 | 抽样&字段脱敏 |
item_geohash | 商品位置的空间信息,可以为空 | 由经纬度通过保密算法生成 |
item_category | 商品分类标识 | 字段脱敏 |
实例如:
117151719,96ulbnj,7350
训练数据包含了抽样出来的一定量用户在一个月时间(11.18~12.18)之内的移动端行为数据(D),评分数据是这些用户在这个一个月之后的一天(12.19)对商品子集(P)的购买数据。参赛者要使用训练数据建立推荐模型,并输出用户在接下来一天对商品子集购买行为的预测结果。
3. 评估指标
比赛采用经典的精确度(precision)、召回率(recall)和F1值作为评估指标。具体计算公式如下:
其中PredictionSet为算法预测的购买数据集合,每条记录包含用户id和商品id两个字段,即表示预测某用户会在12月19日购买某商品;ReferenceSet为真实的答案购买数据集合。
二、 数据集分割
1. 日期分割
我们总共获得了1000万用户在一个月的行为数据(11.18~12.18),需要预测的是在12月19日用户的购买情况,因而我们采用滑窗的形式来构造训练集和预测集。
线下
Train Feature Span :11.18~12.15
Train Target Span: 12.16
测试:12.17 —> 从11.18~12.16提取特征
验证:12.18 —> 从11.18~12.17提取特征线上
Train Feature Span:11.18~12.17
Train Target Span: 12.18
预测:12.19 —> 从11.18~12.18提取特征
对于线下模拟,我们用从11月18日到12月15日行为数据作为知识集,用来提取相关特征,然后利用12月16日真实的情况来给特征集打上label,如对于某个用户商品对,如果用户在12月16日真实购买了该商品,则在特征表的该UI对最后一个字段赋值1,这样我们就得到了一个训练集。然后我们再从11月18日到12月16日的行为数据中提取相关特征,得到一个12月17日的测试集。为了保证训练的模型具有稳定性,我们还需要一个验证集作对比,即从11月18日到12月17日提取特征,得到一个12月18日的验证集。如果用12月16日训练集得到模型,在测试集和验证集上得到的F1值差不多(相差在0.02%左右),则说明这个模型是可靠的,同时也表明这些特征构建也是有效的。然后我们可以利用11月18日到12月17日的数据来构建特征,以12月18日的真实购买情况来打上label,得到一个线上的训练集,用该训练集训练出来的模型来预测12月19日的购买情况(以11.18~12.18作知识集提取特征)。
从比赛的实际情况来看,数据集的划分对结果是有较大影响的。我们一开始一直以12月16日这天的真实情况作为训练集,然后用训练集的模型直接去预测17号、18号、19号三天的情况,在特征维度只有200的时候,其结果并没有太大的影响。但当后面我们加到300维特征时,用12月16日训练的模型去预测17、18、19号这三天的情况则产生了明显的偏差,最优与最差的F1值能相差0.2%,直接影响到我们最后的成绩。后面分析原因,极有可能是特征提取的时间跨度不同,而后面增加的特征又受时间跨度的影响较大,比如统计用户的总浏览数,我们训练集统计的是11月18日到12月15日共28天的数据,而预测17、18、19号的该特征提取的时间跨度为29、30、31天,因而用12月16日训练出来的模型去预测明显是有偏差的。即我们的模型是基于28天的真实情况训练出来的,而我们后面预测时知识集跨度却是大于28天的,且随着时间的推移偏差越来越大,以致模型并不能做出合理的预测。
2. 全集与子集
全集由于包含的数据量很大,对于训练模型发现用户的购物规律很有帮助,同时也能更准确的评价用户的购物能力。但是用全集去训练对<用户,商品子集>这个层面的帮助是否还有那么大意义?本次的主题是淘宝移动推荐,其商品主要是服务类商品,特点在于线上购买线下消费(O2O),这与平时意义的网上购物还是有一定的区别的,这一类的消费是否有其区别于其他消费的特点?因而训练集有可以分为以下两种情况:
- 直接用商品全集训练
- 直接用商品子集训练
在给定1000万用户的行为数据情况,全集每天发生购买的UI对在20万条左右,而子集则在3万条左右。从实际情况对比来看,用全集训练的结果还是要明显好于子集训练,训练结果相差在1%左右,原因就在于子集中的正例太少,虽然子集训练也许更有针对性,但是由于数据量太小,并不能充分利用大数据量发现统计规律。
三、 特征构建
关于特征的构建,我们必须要明确一点:我们需要预测什么?只有清楚的明白预测的主体,我们才能去构建合适的特征。官方需要我们给出的是所有<用户,子集商品>的购买情况,我们可以从任意一天的<用户,子集商品>对的购买情况看出,其中绝大部分购买是用户当天交互当天购买(一般有2/3左右),这些用户商品对没有任何历史情况。对于这部分购买,我们实际上直接选择了放弃预测,即只考虑预测这一个月中有历史记录的用户商品对,这主要是基于以下两个原因:
- 不能确定用户是否会购买该商品
- 不能确定用户会在什么时候购买
我们没法通过学习历史数据去评估这两个因素的影响,因而直接凭空预测的代价太高,会严重影响整体预测的准确度,这也决定了我们召回率的极限。所以,我们的预测主体是有交互历史用户商品对,预测的内容是用户是否会购买该商品、用户是否会在预测日当天购买。因此,我将特征归为以下四类:
1. 用户特征
用户特征就只是针对用户来说的,反映的是用户整个购物习惯与购物规律,而与具体哪件商品无关,比如用户是不是喜欢浏览购物网站、用户的购物频率等。
此外还包括以下:
- 用户评分(结合时间)
- 最大购买量距离预测日时长
- 用户购买量与浏览量的比值
- 用户发生二次购买商品数占总购买商品数的比值
- 浏览过的商品中发生购买的比值
- 收藏商品中发生购买的比值
- 加购商品中发生购买的比值
- 双12期间的浏览量、购买量以及其占用户总浏览、总购买的比值
- 双12期间的活跃小时数占总购买的比值
- 用户近三天四种行为加权
2. 商品特征
商品特征反映的是商品本身的品质或者受欢迎程度如何,而与具体哪一个用户没有关系,同时商品特征也体现了商品的活动规律,即被用户购买的频率、最后有人购买的时间等。
此外还包括以下:
- 商品评分
- 商品购买人数占总浏览人数的比值
- 收藏商品的人中最后发生购买的比例
- 加购商品的人中最后发生购买的比例
- 购买商品的人中发生二次购买的比例
- 商品最大购买量距离预测日时长
- 商品双12期间的浏览量、购买占总浏览量、购买量的比值
- 该商品的交互量占该类商品的交互量比值
- 该商品的购买量占该类商品购买量的比值
3. 协同特征
协同特征则是以<用户,商品>作为统计对象,是用来表现某个用户对某件商品的喜爱程度或是购买的可能性,这一部分特征直接与测试集数据对接,对预测结果起着决定性的作用。
此外还包括:
- 用户商品对评分
- 最大交互量距离预测日时长
- 用户对该商品的浏览量占用户总浏览量的比值
- 用户对该商品的购买量占总购买量的比值
- 用户对该商品的活跃小时数占用户总活跃小时数的比值
- 用户双12期间对该商品的活跃量、购买量占总活跃量、购买量比值
4. 类别特征
类别特征对预测的影响主要体现在两个方面:第一,通过对用户对某一类商品的浏览、收藏、加购、购买情况可以看出用户是否有可能购买该商品(不是误点),以及用户对这一类商品的偏爱程度;第二,主要是体现在物品竞争上,当我们需要判断用户是否会购买某个商品,可以看用户在看该商品是还关注了多少同类商品,并借此来评判用户购买该商品的可能性。
此外还包括:
- 对该类别的交互量占用户总交互量的比值
- 对该类别的购买量占用户总购买量的比值
- 用户对该类商品的最大交互量距离预测日时长
- 对某一UI对,用户最后接触该商品的当天浏览、收藏、加购、购买了多少同类商品
- 用户双12期间对该类商品的交互量、活跃量、购买量占总交互量、活跃量、购买量的比值
特征的构建对于模型预测是至关重要的,它决定着我们成绩的上限。在比赛的时候我们过早的进入了模型调参阶段,花费了大量的时间和计算资源(被阿里警告),然而不管怎么调整,其F1值的提升最多都不过0.2%(峰值在6.8%左右),原因就在于我们前期构造的特征过于简单,能够给模型提供的信息量有限,因此这也决定了模型预测的极限。同时我们对于时间的处理也不够合理,前期我们对时间直接是做的截断处理,直接丢弃了日期的小时字段,只关注用户行为发生在哪一天,这就影响了到了相关特征的准确性(如用户对商品的最后接触时间)。在师兄的提醒下我们把相关特征换成小时后,F1直接提升了0.5%。之后我们又新增了将近100维的新特征,线下测试F1的峰值到了8.3%,由此可见特征对于整个预测的重要性。
四、 数据预处理
1. 异常点剔除
我们在得到完整的特征表后,发现里面有一些用户的浏览量的值特别大(最大超过200万),已经超出了合理水平,我们分析其原因可能是,这些用户是爬虫用户,因此这些用户对商品的行为并不能作为预测用户购买商品的依据。同时,我们预测的是所有历史记录中出现过的用户商品对,这些用户的存在无疑会增大我们的预测量,同时也会对我们正常模型训练产生干扰,因而我们选择将这些用户过滤掉,过滤规则如下:
2. 缺失值填充
由于系统只能记录用户的发生点击的行为,这就可能造成用户对于某商品浏览量为0,而收藏、加购、购买量不为0的情况。然而现实却是无论你对商品进行收藏、加购、购买哪种行为,你必定会先浏览它们,因此我们对于浏览量为0的UI对进行了补值操作,具体补值规则如下:
对于整个数据预处理,由于没有做对比,我不能清楚的知道我所做的这些操作是有利于提升预测的准确率,还是会因为破坏了数据的真实性而对预测产生干扰。而且严格意义上来讲,我对数据的预处理都是在已获取的特征表上进行操作,而不是对官方提供的原始数据进行预处理。
五、 模型与调参
1. 模型选择
随机森林(Random Forest)
随机森林,顾名思义,是用随机的方式建立一个森林,森林里面有很多的决策树组成,随机森林的每一棵决策树之间是没有关联的。在得到森林之后,当有一个新的输入样本进入的时候,就让森林中的每一棵决策树分别进行一下判断,看看这个样本应该属于哪一类(对于分类算法),然后看看哪一类被选择最多,就预测这个样本为那一类。
在建立每一棵决策树的过程中,有两点需要注意:采样与完全分裂。首先是两个随机采样的过程,random forest对输入的数据要进行、列的采样。对于行采样,采用有放回的方式,也就是在采样得到的样本集合中,可能有重复的样本。假设输入样本为N个,那么采样的样本也为N个。这样使得在训练的时候,每一棵树的输入样本都不是全部的样本,使得相对不容易出现over-fitting。然后进行列采样,从M个feature中,选择m个(m << M)。之后就是对采样之后的数据使用完全分裂的方式建立出决策树,这样决策树的某一个叶子节点要么是无法继续分裂的,要么里面的所有样本的都是指向的同一个分类。
GBDT(Gradient Boosting Decision Tree)
迭代决策树在我理解更像是一种数据的串行处理,对于每一个训练样本,一棵决策树在进行处理后将其交给下一个决策树,其核心就在于每一棵树学的是之前所有树的结论和的残差,这个残差就是一个加预测值后能得真实值的累加量。以预测年龄为例,A的真实年龄是18岁,但第一棵树的预测年龄是12岁,差了6岁,即残差为6岁。那么在第二棵树里我们把A的年龄设为6岁去学习,如果第二棵树真的能把A分到6岁的叶子节点,那累加两棵树的结论就是A的真实年龄;如果第二棵树的结论是5岁,则A仍然存在1岁的残差,第三棵树里A的年龄就变成1岁,继续学。
两种算法其实都是对决策树进行组合,RF采用的是Bagging的思想,即每棵树共同投票决定,类似于一种并行决策机制;GBDT采用的是boosting的思想,每棵树的决策都是基于上一棵树的结果,类似于一种串行决策机制。从我们的实际经验来看,在使用相同特征的情况下,我们使用RF模型的F1峰值在(7.0%),而GBDT的峰值在8.3%。当然这里面也有我们没有将RF调整到最优的原因,而GBDT在选择相对较大参数的情况与最优性能差异不大,可以理解为该特征集在该模型下的预测极限。
2. 参数调整
对这两个模型的参数调整必须要对模型有充分的了解,并非简单的理解其基本的原理即可。我们在比赛的时候,正是由于不理解每个参数对预测的影响以及每个参数之间的相互作用,花了费许多不必要的时间去遍历这些参数,其主要需要考虑的参数如下:
- 属性度量选择(信息增益、增益率、基尼指数)
- 每个节点使用的特征数量
- 每棵树的训练样本量(越多越好,但训练时间是个问题)
- 树的深度
- 叶子节点最小记录数(与训练样本量有关)
- 最小叶子节点数目(跟树的深度、属性度量以及叶子节点最小记录数都有关)
- 迭代速率(GBDT)
属性度量选择实际上决定着你采用哪种决策树算法,因此对ID3、C4.5、Cart算法都需要有所了解,同时一定要注意你的特征是连续的还是离散的,这可能会导致较大的训练偏差;每个节点使用的特征数量我们选择的是总特征数开平方,当然如果特征维度特别大也可以取总特征数的对数;对决策树而言如果训练样本的数量越多,预测的稳定性就越强,这很好理解,样本数据越大越接近真实分布,但是过多的训练样本可能需要很长的训练时间,同时随机森林和GBDT对训练量的变化也有不同,GBDT训练量的增加对预测结果的提升要比随机森林明显,这或许是迭代的强大之处吧。
至于树的深度、叶子节点的最小记录数、最小叶子节点数目这些参数实际上是对树的前剪枝,避免出现过拟合,一般来说GBDT树的深度要偏小一些,RF的深度相对要大一些,叶子节点数同样会限制树的生长,至于叶子节点最小记录数则与训练数据量有关,都需要凭经验来设定。迭代速率类仅GBDT包含,设的过大一般容易越过最优值,太小的又可能学习过慢,个人感觉一般设置在0.05~0.1之间都是可以的。
由于要调整的参数很多,我们按照如下的思路去一步步优化寻找最优的F1值:
六、 自己的尝试
1. 分类训练
不同的用户商品交互情况对于预测内容是不同的,如用户购买过该商品,那么通过用户特征、商品特征去评价用户是否会购买该商品就没什么太大意义了,我们预测的重点就变成了用户会不会二次购买以及什么时候发生二次购买。因此,我们可以将原始的二分类问题转换为基于用户历史行为的多分类问题,具体分类如下:
- 用户只浏览过该商品
- 用户只收藏过该商品(有记录就一定有浏览)
- 用户只加购过该商品(收藏也并入加购)
- 用户购买过该商品
考虑这四种情况的分类实际体现的用户的一个行为转移,即用户看了就买、用户看了然后收藏再购买、用户加购再购买、用户买过再购买。我在小数据集上以1216为样本进行相关的数据统计,结果如下:
从统计结果可以看出,要预测的用户商品对中,绝大部分是只浏览过,而在1216日当天,发生购买的用户商品对中有历史加购行为的最多,其次是只浏览的(毕竟基数比较大)。但是从预测难度角度来看(发生购买与要预测的比值),预测只浏览过的用户商品对的难度最高,其发生购买的概率不到万分之一,而预测有购买和加购行为的难度较低,都在千分之一以内。
将二分类问题转换为多分类问题,一方面模型更加专一,有利于提升预测的准确性;另一方面特征的维度也能降低,因为四种模型所需特征都不尽相同。因此,接下来我们需要做的工作就是:
- 对于不同模型提取不同特征
- 将不同模型的输出进行合理的组合
关于特征提取,我是直接基于用户、商品、用户商品对、用户类别构建四张通用的特征表,四种模型从这张总表中选取各自所需的特征,如果模型还需要一些特有的特征,再进行单独提取。
对四个模型输出的组合,我考虑的是单独去评估每个模型,看每个模型的预测代价的最小值,即最少预测多少个用户商品对就能预测准确一个,然后根据这个预测代价来进行预测数据融合。如果某一模型的代价特别大,如用户只浏览过该商品,我们是否应该选择放弃这部分呢?我的想法是再弄一个全特征的模型,然后将其输出与用户只浏览的模型取交集,大致流程如下:
不知道是不是我执行的问题还是怎么回事,最终得到的效果很差,花了很长时间去尝试结果很不理想还是挺让人懊恼的。后来师兄给了我一个我觉得相对合理的解释,我所做的这种预分类其实意义不是很大,因为决策树最大的优点就在于帮我们找到最合理的分割,我这种分割UI对的方式不一定利于模型的预测,更何况后续的融合方式问题也很大,分割后每个集合正负样本比例如何确定、训练量如何确定等都值得商榷。
2. 模型融合
由于我们只使用了RF和GBDT两个模型,因此我们将RF和GBDT输出的label为1的概率值结合起来作为一个特征向量,以真实的UI对label作为该特征向量的label,然后利用LR模型对两个模型的输出进行融合,这实际是在利用LR来决定两个模型输出的权重,由于我们的GBDT跑出来的效果要比RF好太多,融合之后实际上与原GBDT的输出差别并不大,但提交到线上确实能够提升一点点,可以说还是相对提升了整个模型的稳定性吧。
七、 总结
由于是第一次参加数据挖掘的比赛,抱着一种半学习半竞赛的态度,因此能做到这样自己还是挺满足了,毕竟学习不是一蹴而就的事情,虽然最后被挤出前50还是挺让人不爽的。通过这次比赛,我对整个数据挖掘的流程更加明了,对我所使用过的模型也有了深一步的理解,同时也有如下体会:
- 数据挖掘需要极大的耐心和细心,数据预处理、特征提取连接、线下训练预测、线上提交任何一步出问题都可能导致意想不到的结果,这一方面需要我们提前确定好一个框架,做到有条不紊,另一方面也需要我们对一个步骤都做到认真细致
- 不要过早的陷入到模型参数的调整,更不要盲目调参,要去理解模型的学习和预测,不然不停的试参数只是浪费时间和精力;要多在特征上做文章,这决定着你成绩的理论上界,模型调参的过程只是帮助你逼近这个上界
- 在构建特征时一定要主要保证训练集和预测集上的特征分布一致,不然可能会造成线上线下偏差很大
- 要多跟人沟通,一个人很容易跳进坑里,可能最后比赛完了你都还没从坑里爬出来,集思广益才能有所提升