【机器学习】天池O2O优惠券使用预测_系统性总结与心得

  • Post author:
  • Post category:其他



Preface

:上半年参加天池的O2O优惠券预测赛排名第二,同时参加了学校数据仓库老师的课程改革建设团队,于是把参赛经验总结成文,准备分享给该课程的学弟学妹。现在我把总结的参赛教程文章也发到CSDN上来,供参赛的同学们参考,也希望能得到更多的指教。


1 赛题背景


1.1 题目介绍

  • 比赛背景:赛题的发布网址如下:

    https://tianchi.aliyun.com/competition/gameList.htm#tab%3D%E5%85%A5%E9%97%A8%E8%B5%9B%26pageIndex%3D1

    。大家可以在这里提交自己的结果数据集,每天的10点和22点服务器会刷新成绩评测新提交的结果。
  • 赛题目标:提供用户在2016年1月1日至2016年6月30日之间真实线上线下消费行为数据,需要预测用户在2016年7月领取优惠券后15天以内的使用情况。
  • 赛题数据:包括三个数据表。TABLE1(用户线下优惠券消费记录),TABLE2(用户线上优惠券消费记录),TABLE3(待预测的样本数据,是用户线下消费记录)。在本教程中,我们只使用TABLE1来构造训练集,因为如果把TABLE1、TABLE2同TABLE3数据比较,会发现TABLE3的格式与TABLE1相同但与TABLE2有差异,从而能够推断TABLE1与TABLE3为同源数据,使用TABLE1的数据构造训练集更合适。比赛结果也证明只使用TABLE1的数据来训练效果良好。当然,TABLE2也可以作为辅助预测的数据使用。
  • 赛题模型:预测优惠券是否会在15天内核销可以看作一个数据挖掘中的分类问题。决策树是解决此类问题的常用模型,在本教程中我们将使用Xgboost(改进梯度提升决策树)模型。


1.2 整体思路

教程给出了两个完全独立的赛题解决方式:单指标方法和机器学习方法。

  • 单指标方法是为了给读者一个简单的初步参赛体验,这也是笔者第一次提交结果时所用的方法,即只用一个基础统计指标来拟合优惠券的预期核销率,十分容易上手。
  • 机器学习方法是完整的使用机器学习模型参赛的流程教学,涵盖了特征工程和XGboost模型使用两部分。建议读者将本教程的解析和源代码结合起来阅读,更方便理解。源代码已经添加了较为详细的注释。


1.3 开发环境

  • 推荐Anaconda3+Python3+Pycharm的开发环境,这种搭配下撰写代码以及python包的管理都方便,省去了很多不必要的麻烦。


2 数据集情况及处理


2.1 数据集预处理(datapre.py)

首先明确 O2O优惠券使用预测是二分类问题。要应用机器学习模型来分类,则数据集中必须有目标变量,这样才能把训练集“喂给”机器学习模型让它寻找特征属性到目标变量的映射规则。

因此教程的第一项就是在训练集中构造目标变量。本次竞赛的目标是:“预测用户在2016年7月领取优惠券后15天以内的使用情况”,所以我们需要为训练集构造一个“flag”属性,“flag”标识某条记录是否为领券后15天内核销了优惠券的记录。对于正向数据我们标记为1,负向数据标记为0。这一操作在datapre.py文件中完成,该操作最后生成了文件“off_flag.csv“,这一数据集是2.2节数据集划分的基础。


2.2 数据集划分(datapre.py)

这部分操作我们会划分出4个数据集(dataset1,dataset2,feature1,feature2),dataset与feature两两对应,它们是之后特征工程以及构造训练集、测试集的原料数据集。

  • dataset1将会是训练集的主键部分,它是截取的20160515-20160615一个月区间内的数据。因为这段时间已经是过去时,所以每条记录我们都有“flag”目标属性,这就构成了训练集的基础部分。训练集以后还会merge上特征属性部分,这些特征属性提取自20160101-20160514这段时间的历史数据集,也就是feature1。不过要注意,feature1是特征属性的来源,但不是特征属性本身。
  • dataset2将会是测试集的主键部分,其实它就是竞赛提供的TABLE3,是20160701-20160731一个月区间内的数据。这段时间是未来时,所以“flag”目标属性未知,这也正是我们需要预测的东西。测试集也需要merge上特征属性部分,这是为了与训练集保持一致。我们使用训练集投喂了机器学习模型之后,它习得了这种特定属性模式下的映射规则,所以测试集也需要构造成相同的属性模式,机器才能“照葫芦画瓢”式地做出预测。训练集的特征属性部分将提取自20160201-20160630这个时段的历史数据集,也就是feature2。跟前一段同理,feature2是特征属性的来源,但不是特征属性本身。
  • 值得再次指出的是,构造完成后的训练集与测试集在字段种类上是一模一样的,唯一的不同之处在于:训练集是20160515-20160615的历史数据因此包含目标变量,而测试集是20160701-20160731的未来数据因此不包含目标变量。机器学习模型要做的,正是通过学习训练集上特征集合到目标变量的映射关系,生成特定的判别规则,达到根据测试集上的特征集合来预测出目标变量取值的能力。


3 单指标方法(weightModel.py)

  • 在进入“特征工程+训练机器学习模型”的正规数据分析模式前,我们可以先尝试用一个极其容易上手的单指标方法来参赛。代码不到20行,提交结果在0.67的auc左右。代码见weightModel.py,步骤已经在代码中给出详细注释。
  • 这里解释一下该方法的逻辑。从业务的角度考虑,某一用户领取优惠券后是否核销,应该同该用户的活跃度正相关。通过构造“probability”属性,使用“某个用户在该数据集中出现的次数/所有用户中出现次数最多的那个用户的出现次数”,我们得到了一个标识某个用户活跃度的指标。我们直接以该活跃度指标作为对优惠券是否会被核销的预测概率值,构造符合要求的提交数据集后上传提交结果。


4 机器学习方法


4.1 特征工程概述

“特征工程”里所谓的工程跟“软件工程”里的工程是差不多的意思,学者觉得它本质是一项工程活动,所以取名为“特征工程”。其实说的通俗一点,特征工程就是利用表中已有的字段构造新的字段,它的目的是最大限度地从原始数据中提取特征,以供算法模型学习出映射规则。

例如TABLE1最开始只有7个字段,经过特征工程之后包含了41个字段,这多出来的34个字段就是特征属性。更详细地看,“某一用户领取优惠券的次数”,“某一用户购买过的商店总数”这些属性都是原表中没有的,我们需要通过python的pandas库等工具去统计、构造出这些属性。

特征工程的意义在于让每条记录的特征更明晰,从而使机器学习模型更容易找出特征字段到目标变量的映射关系。我们当然可以直接把原始数据表喂给机器学习模型,但那是没有什么意义的,分类结果会很差。


4.2 特征工程(getfeatures_new.py)

  • 特征工程分为5个部分:用户相关特征、商家相关特征、优惠券相关特征、用户-商家联合特征以及数据泄露相关特征。每部分特征工程都需要为训练集和测试集各做一次,这样才能为训练集和测试集构造出相同的特征属性,保证训练集和测试集最后的字段种类是完全相同的(除了目标变量字段)。
  • 每部分特征工程处理得出的结果数据集都有相应的主键。例如用户相关特征这一部分的特征工程,其结果即是一个以“user_id”为主键的附有9个用户特征字段的数据表;而用户-商家联合特征这一部分的特征工程结果,即是一个以“user_id, merchant_id” 联合段为主键的附有用户商家交互特征字段的数据表。其他部分的特征工程同理。
  • 值得注意的是,用户相关特征、商家相关特征、用户-商家联合特征这三个属性我们从历史数据中提取,而优惠券相关特征、数据泄露相关特征我们则直接从两个“dataset”所在的月提取。因为用户、商家这两个主体的特征是基本保持不变的,一个用户的行为有其长期习惯性,而一个商家的经营状况也有长期特征,所以这两个主体的特征我们可以从历史数据中提取。但是优惠券是变动的,7月份的优惠券与5月份的优惠券完全没有重叠,因此我们在提取优惠券的特征时不可能使用5月份的优惠券历史特征,只能从7月份本身的数据出发提取优惠券的特征。
  • 在做完所有特征工程后,我们就为训练集和测试集各生成好了一套(分5个特征工程部分)的结果数据集,这五个特征集都有自己的主键。最后我们需要把这些特征都合并到各自的“dataset”上,从而构造出完整的训练集和测试集,这一操作对应代码最后的“merge”部分
  • 下面解释一下每个部分的特征工程代码结构方便大家阅读。每一部分的代码书写结构都是先列出这部分需要提取的特征属性清单,然后列出这部分需要用到的自定义函数(如果有的话),之后是测试集部分的特征工程,接着是训练集部分的特征工程。每部分特征工程之间用空白和#号标题隔开。提取特征属性的代码我都写了简要注释,阅读代码和查阅资料的过程也是提升自己能力的过程,所以一定要认真且耐心。


  1. 用户相关特征

  • 用户相关特征包括9个特征属性,但是在这一部分只提取完成7个,剩余的2个在最后merge完成各个数据集时才可以提取。avg_dis和max_dis就是为了构造那两个属性而在此处预先构造出的原料属性。

  1. 商家相关特征

  • 商家相关特征包括10个特征属性。跟用户相关特征一样,在这一部分只提取完成9个,剩下的1个属性也需要在最后merge完成各个数据集时才可以提取。max_distance就是构造剩余属性的原料属性。

  1. 优惠券相关特征

  • 优惠券相关特征包括5个特征属性。这一部分最重要的地方就是涉及了4个自定义函数,使用map()方法来调用执行。map()方法可供我们调用自定义函数并使其作用于Series每一个元素,这是Pandas处理中“用一列数据构造新列”时的常用方法,更详细的使用方式请大家参考代码以及CSDN等博客的教程。

  1. 用户-商家交互特征

  • 用户-商家交互相关特征包括4个特征属性。这一部分特征工程与其他部分最大的区别在于,其结果数据集的主键是两个字段的组合:[user_id, merchant_id]。

  1. leakage相关特征

  • 图示即leakage相关特征清单,包含6个特征属性。这一部分最难的地方在leak4和leak5的提取,需要用到比较高级的聚合行值函数以及自定义函数。在看详细的代码之前,有必要指出一个数据集处理中的常用思路:需要进行行与行比较的问题(通常无法直接操作)通常转化为列与列比较的问题。实现转化的方法就是行错位,有点类似于数列错位相消的操作。

  1. 合并dataset与feature

  • 至此我们已经从历史数据和两个“dataset”本身出发,提取出了所有特征属性。不过目前这些特征属性散开分成了独立的数据集,因此我们需要将它们按主键合并到各自对应的“dataset”上,这样才构成完整的训练集和测试集。
  • 在完成各数据集合并之后,我们还需要构造了4个特征,它们都是基于各个部分已构造的特征提取的。这四个特征都关于距离,最后的机器学习模型阶段证明,这4个关于距离的特征对于预测用户是否核销优惠券是非常有指示意义的。


4.3 处理有偏数据集(datapre.py 最后一部分)

实际上对有偏数据集的处理是笔者在做比赛的最后阶段才意识到的问题,处理之后大概能提升0.5%左右的AUC,这在数据分析类竞赛的后期阶段已经算是很大的提升了。

  • 在4.2节中构造出的训练集,如果留心探查一下,会发现极其严重的数据集偏倚问题。flag属性为1的记录条数在20000+,而flag属性为0的记录条数在230000+,正负样本的数量严重不平衡。直接把这样的训练集喂给机器学习模型并不能发挥出最好的训练效果,会一定程度上把模型“带偏”。
  • 所以教程采用了将正负样本分开,在负样本中随机抽样10%构造出新的负样本,再将其与正样本合并的方法,把有偏数据集很简易的调整为了平衡数据集。这个思路并不很严谨,大家可以继续考虑一下怎么做更好。


4.4 XGboost模型介绍

这里笔者贴上自己总结的树模型相关算法的XGboost部分,不涉及多少公式,大都是描述性介绍。这个总结主要为XGboost而写,而Boosting方法是集成学习的一种,所以在最开始有必要记录一下集成学习的概念。

集成学习的目的是通过结合多个基学习器的预测结果来改善单个学习器的泛化能力和鲁棒性。目前的集成学习方法大致分为两大类:个体学习器之间存在强依赖关系、必须串行生成的序列化方法,代表就是Boosting;以及个体学习器间不存在强依赖关系、可同时生成的并行化方法,代表是Bagging和Random Forest。


  • Boosting:

    不同的分类器是通过串行训练而获得的,也即通过关注被已有分类器错分的那些数据,来获得新的分类器。


    Boosting分类的结果是基于所有分类器的加权求和结果的,但分类器权重并不相等,每个权重代表对应的分类器在上一轮迭代中的成功度。

  • Bagging:

    可以简单的理解为:放回抽样,多数表决(分类)或简单平均(回归),同时Bagging的基学习器之间属于并列生成,不存在强依赖关系。

下面开始简单介绍CART树以及XGboost模型。


1. CART树( Classification And Regression Tree)

CART树是最典型的二叉决策树,是我们学校数据挖掘课上老师一定会推导的模型。CART的C和R代表它有分类和回归两种类型。

CART构建的核心过程一是分裂点依据什么来划分。CART分类树划分的依据是叶子节点的基尼系数;CART回归树划分的依据是分裂后的GINI_Gain,GINI_Gain应该是一种衡量预测误差的指标。剩下还有过拟合、剪枝等等细节,也是老师在课上会推导的东西。

核心过程二是分类后的节点预测值是多少。分类树与回归树有区别:作分类决策树时,待预测样本落至某一叶子节点,则输出该叶子节点中所有样本所属类别最多的那一类(这也是老师讲课时的那一种);作回归决策树时,待预测样本落至某一叶子节点,则输出该叶子节点中所有样本的均值(虽然这个是很粗糙的方法)。


2. Xgboost

Xgboost是很多CART回归树的集成(集成学习)。这种集成不是Random Forest那种各个决策树独立运作的模式,Xgboost算法中下一棵决策树输入样本会与前面决策树的训练和预测相关,是集成学习下Boosting方式的一种。

首先明确目标:希望建立K个回归树,使得树群的预测值尽量接近真实值(准确率)而且有尽量大的泛化能力。它的目标函数如下:

不求甚解的讲,函数前一部分衡量预测误差,后一部分衡量树的复杂度,整个目标函数要求构建的树尽可能分类准确同时不复杂能泛化。这个看起来很美好的目标函数在具体实现时,使用了

贪心策略+二次最优化

的方式,具体怎么应用这些方法的参见

https://blog.csdn.net/github_38414650/article/details/76061893

这篇文章,讲解的很通俗。我在这边竞赛教程里就不再多讲了。


4.5 使用XGboost进行预测(xgb_new.py)

这一部分代码很简单,主要就是调用API。以下是一点思路解释。

  • 调参是使用模型进行预测的关键步骤。调参很像多元函数的优化,参数有很多,它们都会影响模型的拟合结果,调参的过程就是固定所有参数而只调整一个参数的控制变量法的过程。教程沿用了网络上比较通行的XGboost的参数设置,我们只调整max_depth, boost_round这两个参数。
  • 笔者调参的时间跨度从4月份开始到6月结束,这里强烈建议一定对调参过程做详尽记录,这样时间久了翻看日志记录才能有一个清楚的调参思路。教程调参的依据是每天两次的提交刷新成绩机会。


5 模型融合方法(gbdt.py+mergeModel.py)

前一章的机器学习方法,归根到底只是使用了XGboost这一个机器学习模型,但机器学习模型数量众多,性能优秀的模型也不止XGboost这一个,所以我们可以考虑使用不同的机器学习模型进行预测,而后将它们的预测结果融合到一起,这就是模型融合方法。值得提醒的是,训练这些机器学习模型的训练集可以是同一套特征工程产出的,也可以是不同特征工程产出的。根据经验来讲,不同特征工程能产出不同倾向特质的训练集,从而能训练出不同特质的机器学习模型,将拥有不同特质的机器学习模型预测的结果进行融合,效果会很好。但是通常参赛选手一个人思路有限,只会有一套特征工程,并不能训练出具有不同特质的模型,所以我们鼓励大家多多和其他人交流,相互取长补短。

笔者也跟其他参赛选手有过一些交流,发现在这个O2O比赛中,单模型的成绩上限应该在AUC0.806左右,而使用模型融合后,效果好的话能跃升上0.81的台阶。产生这种情况的背后道理,我们可以类比集成学习:集成学习通常将多学习器进行结合,常可获得比单一学习器显著优越的泛化性能,而我们将不同模型的预测结果进行线性融合的做法,其实和集成学习中的Bagging方法类似。

教程中所融合的两个模型是XGboost和GBDT,它们都是机器学习竞赛中的常用模型。GBDT的模型调用与XGboost的调用十分相似,具体的代码参考gbdt.py文件。而所谓的模型线性融合,其实很简单,就是把两个模型的预测结果按一定比例融在一起,具体的代码参考mergeModel.py文件。两份代码均添加了步骤注释。



版权声明:本文为orsonV原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。