IJCAI

Wesley13
• 阅读 280

前言

从去年11月下旬开始入坑机器学习,到现在也已经有半年了,一开始就是学习机器学习理论方面的知识,但是学到两三个月的时候总觉得没点实际操作,所以感到知识点很空洞。自己是自学机器学习的,周围又没有实际项目可以做,能拿来练手的恐怕只有各种数据算法竞赛了。今年二月到三月第一次参加了kaggle上的Toxic文本分类比赛,但是由于自身经验严重不足,和理论知识的浅薄,导致这次比赛几乎可以以惨败收场,虽然最后的排名也是在20%左右,这对于一个机器学习的小白来说也并不是非常差,但是总觉得心里的落差还是挺大的。kaggle的比赛结束不久,还想继续参加,可是kaggle后来的比赛的数据量实在是太大,动辄几十个G,还是未解压的,自己的渣渣笔记本实在是玩不起。这时候发现天池大数据有个阿里妈妈广告转化预估的比赛,看到初赛的数据量比kaggle小多了,并且比赛的背景也非常符合实际的应用,我非常感兴趣,就马上参加了,并积极准备起来。在接近两个月的比赛的过程中,数据不断更换,自己的排名也不断地跌宕起伏,每天除了看书就是想着怎么把排名提上去以及怎么才能进复赛,还好最终也是顺利进复赛了,虽然排名比较落后,初赛第346名,不过能进复赛已经是非常满意了,毕竟比赛总共有5200多支队伍参与,自己还是个初学者,嗯,也不算很差吧,反正就这么给自己打气,希望复赛能够杀进前一百。复赛的数据要比初赛大很多,解压后数据集有10多个G,这时我的渣渣笔记本又有了巨大的压力,最后是仅使用了七分之一的训练数据,要是整个数据都跑恐怕跑个baseline便Memory Error了。当然最后复赛的结果还是没能达到预期的前一百名,只有187名,这让我知道了天池的比赛也是高手如云,挺进前一百名那是相当不易啊。不过也好在事情是朝着好的方向发展的,毕竟复赛排187也比初赛的346名强不是?还是在数据难度加大的情况下,嗯,又来自我安慰了哈哈。闲话不多说,现在稍微总结一下我在天池上的这一场处女赛。代码地址附在文末。

比赛简介

这次的比赛的目标是预测商品的转化率,就是用户看到这个商品的各种广告信息来估计这个用户买这个商品的概率。原始的特征给出了用户的id,商品的id,商品所属的类别和属性以及上下文等信息,既有类别型特征又有数值型特征。评估预测结果的方法是log-loss,具体详情见比赛网址

数据

比赛给出的数据是用前几天的数据情况来预测最后一天的情况,比如给出1到7号的历史数据,里面有各种用户商品的信息作为特征,以及给出用户是否购买的情况,1为成交,0为未成交,所以这就是一个二分类问题。初赛的数据比较小,所以我用了全部的数据进行了训练,但是复赛数据实在是太大了(对我的机器来说),完全没办法使用全部的数据,仅仅加载数据进去就耗尽了70%多的内存了。这时候有两种选择,一种是进行样本采样,从原始的样本数据中采样出五分之一到八分之一的数据进行训练,其余的抛弃;另一种是仅使用最后一天的样本进行训练,之前天数的数据全部抛弃。我也不清楚哪一种更好,当然,全部数据都保留,一块训练那是最好,可是硬件条件不允许。后来对复赛的训练数据进行了简单的分析,发现前面几天的转化率都差不多,但是最后几天的转化率明显有异常,典型的是倒数最后三天的情况,倒数第二和第三天的转化率仅是开始那几天的一半,而最后一天的转化率竟然是开始那几天的4倍左右,而让你预测的那天数据和训练集最后一天的数据是同一天的,所以我就想这训练集的最后一天可能是类似于双十一狂欢节这类的日子,只有在双十一或双十二的时候用户会买很多商品,毕竟有活动,看到个优惠广告就点进去,然后发现价格也不贵,嗯就买了吧,所以造成了那天的转化率对比平时异常地变高。那么为什么前两天转化率变低了呢,应该是人们早就知道狂欢节有优惠,故而在前两天看好自己想买的东西,但是不急着买,等打折了再买,只看不买的行为多了,不就造成了转化率低了么。由于要你预测的日期和训练集最后一天是同一天的,同时这一天的转化率和其他天还不一样,某种程度上就是数据的‘分布’不同,我的直觉认为仅用最后一天进行训练会比用采样后的样本训练要好,所以就仅使用了训练集最后一天来作为训练集。当然这是不是真的就比使用采样样本来训练要好,就有待于对比检验了。

算法或模型

先讲下使用的算法吧,本来这应该是比赛流程中的最后一步,但是我在这方面花的时间不多,使用的也都是普遍的东西,参数也没怎么调,就先说下吧。初赛的时候使用过两三种算法,对于这种CTR预估的场景,最经典或者说最常用的大概就是逻辑回归(Logistic Regression, LR)了吧,确实逻辑回归算法简单易用,训练速度还比较快,得到的结果精度也能满足大多数应用场合了。但是后面主要使用的算法是LightGBM,这种树算法的训练速度非常快,每次大概几十秒就可以出结果,保证了训练的时间效率,关键得出的结果精度还挺高,所以当仁不让地成为我在这次比赛中的主力模型。随机森林(Random Forest, RF)在初赛时候也使用了,相对来说它的结果精度不如前两个模型,log-loss值差的有点多,训练时间也多了几倍。总之,初赛时候由于数据集小,可以使用多个模型进行尝试,也花不了很多时间,就把三个模型都套用了一遍,最后的提交结果是三个预测的结果取的加权平均,按LightGBM,LR,RF依次取0.6,0.3,0.1来的。初赛的结果不如人意我想是自己特征工程做的不行,复赛时候要注意了(虽然最后也还是没找到强特征)。复赛仅使用了LightGBM这一种模型,没有和其他模型进行stack或blend,因为测试LR和RF模型的时候发现结果特别差,再加上时间和机器等杂七杂八的原因,就没有尝试其他模型了。其他可用在CTR预估的算法诸如FM,FFM,DeepFM,GBDT+LR等,需要对数据进行必要的处理或者训练时要GPU加速,自己由于精力和硬件上的不足,没有应用,下次要尝试一下。GBDT+LR的模型其实也是在实际中用的比较多的,主要思想就是利用GBDT利用原始特征生成一些叶子结点作为新的特征再输入到LR里,嗯,也算是一种特征选择吧,其实这是一种非常好的思想,但是不知为何我在实验的时候发现效果并不好,可能还是使用方法上有些问题,当时并未仔细思考追究。下面说下特征工程。

特征工程

我使用的特征主要分为三大类,分别是组合统计特征,时间差特征以及转化率特征,这部分是自己想得比较多且用的比较多的地方。

  • 组合统计特征

就是把各种属性类别类特征进行组合,然后统计它们在某一时间段内的行为。举个例子,比如user_id和item_id这两个特征,分别代表用户名和商品名,但是同一个用户可能看过好几个商品(反过来也成立,即一个商品可能会被多个用户看过),所以可以统计该用户在同一天内看过的商品个数,以及在这一天内,该用户在看过了某个商品之前已经看过了多少个商品,或者在看某个商品之后还会看多少个商品,这就对这个用户的行为进行了一定的统计。当然,统计了同一天内,还可以再统计一个小时内,一分钟内,甚至是一秒钟内用户看商品的行为。一秒钟内一个用户看好几个商品的情况是有可能出现的,原因可能是这个用户同一秒钟疯狂地点击好多个广告,或者是因为网络原因这个用户总是点击同一个广告,这些都是有可能的。另外,除了统计总数,还可以统计这个用户看到的商品有多少是唯一的,例如用户一天内看了代号为1,2,2,3,3这一共5个商品,按照上文叙述,这里会统计为5,但是唯一的商品只有1,2,3这三种,所以唯一商品的总数为3,这也可以作为一个特征,再者,二者的比例同样也可以作为一个特征,即3/5。同时,这里也可以整理出商品的一些特征,比如代号为3的商品出现了两次,那么这个商品就可以构造出2/5这样的特征,即某个用户在某个时间段内,他看过的某个商品占他所有看过的商品的比例。诸如此类的组合统计特征可以构造出相当多数量,关键点就在于怎样才能构造出有利于预测转化率的特征组合,然后对其进行行为统计。

  • 时间差特征

这种特征构造开始也需要像组合统计特征一样,要先对需要进行时间差计算的特征进行组合。还是以user_id和item_id特征为例,一个用户看完当前商品之后,他可能还会再看另外一个商品,即点击另外一个广告,那么统计这二者的时间差或许会有所帮助。时间差有两类,一类是当前点击距离下次点击的时间,另一类是当前点击距离上一次点击的时间。

  • 转化率特征

就是利用某种特征的历史转化率来构造特征。举个例子,某个item_id,发现它在过去几天中被人们点击数是10,购买数是8,那么这个商品就有8/10的转化率,而另外一个item_id点击和购买数为10和2,那么转化率就是2/10,大大低于前者,所以就可以给前者设定一个比较大的数值而给后者设定一个比较小的数值,这便构造出了转化率特征。那么倘若你要预测的这天有的商品没在前几天出现怎么办,不是没有转化率特征了么?设为0认为其历史转化率为0?这显然不符合实际,这时候就需要贝叶斯平滑算法了,来利用先验给其设定一个初值。而贝叶斯平滑的具体原理可能需要单独学习一下。这里就不说了。需要注意的是转化率特征一些事项,在我的个人实验中,是这个类别属性的多样性越大越好,比如用户的性别id,最多就三种情况,男或女或用户没有设置,对这三种情况进行贝叶斯平滑效果不见得有多好,而item_id,会有成千上万种,这类属性进行转化率特征构造可能会好得多。另外,千万不要构造当天的转化率特征来预测当天的情况,比如我7号的训练集,我构造了7号的item_id的转化率,然后再拿7号训练集的一部分作为训练集,一部分作为验证集,当然验证结果会表现的很好,但是要是用在预测你的预测集数据上,提交结果之后会发现线上效果很差,因为,已经过拟合了。这里我用了除最后一天的数据来计算各种属性的转化率,这也是复赛中唯一一次用了除最后一天的训练数据。

  • 其他特征

其中有三个字符串特征,分别是item_category_list,item_property_list和predict_category_property,意思是商品的类别,商品的属性和商品的预测类别和属性。这三个特征是字符串型的,无法直接输入到模型里,但直接扔了不用有点可惜。我在这里没进行多少处理,就是把预测类别属性的字符串给拆开,分成预测类别和预测属性,然后对比商品的真实类别和属性,计算它的预测命中率,即它的预测属性有多少在真实属性中出现过,再除以它的预测属性总数便得到了命中率。这个命中率我想也能反映出一些问题吧,毕竟如果能够精确地猜出用户输入的搜索关键词对应的类别和属性,那么用户的使用体验也会提高,购买商品的欲望也会强一些。我之前在初赛的时候有参考过天池官方论坛上的这篇文章,作者提出可以用文本分类中的TF-IDF的方法去处理predict_category_property这个属性,即把这个属性经TF-IDF处理后变成一个稀疏矩阵,再把这个矩阵输入到一个模型里预测结果,再将结果作为一个特征。我尝试了一下发现对预测结果影响不大就没使用了,或许是自己的操作不是很正确,就没有再去管它了,但是这个想法我觉得很好。

其他

有几个问题未能很好解决

  • 类别属性直接和数值属性一样,直接输入到LightGBM模型中了,虽然感觉不影响结果,LightGBM有比较好的处理类别特征的能力,但是应该有更好的表示类别属性的方法,曾经用过one-hot方法,可是对于 LightGBM,输出的结果并不好,或许是因为LightGBM不能很好地处理大型稀疏矩阵。

  • 特征总数最后达到了三四百,这么多的特征对于我这渣渣笔记本来训练十分够呛,包括构造特征的时候都是用了很多繁琐的方法才把这些特征组合到一起,时常会有Memory Error,内存不足是硬伤。最后也是勉强用这所有特征预测出结果了,但是我认为可以进行特征选择,这么多的特征其实大多数是不起作用的,需要筛选出真正有作用的特征,可是筛选的成本实在太高,时间和机器都不足,就没又做了。关于特征筛选,可参考这个帖子

  • 强特不足,渣特来凑,最后特征筛选的方法其实很耗时间和资源的,关键点还是在于对业务的深刻理解上,以便构造出强有力的特征,我在这方面还是涉猎太少,找不出问题的关键所在,所以学习任重道远。

最后感谢那些分享想法和代码的朋友们,我从你们那里收获了很多,再次感谢!也希望自己下次比赛再接再厉。

求个星星!多谢!:)

再附上几个前排大佬的解决方案:

点赞
收藏
评论区
推荐文章
光头强的博客 光头强的博客
2个月前
Java面向对象试题
1、 请创建一个Animal动物类,要求有方法eat()方法,方法输出一条语句“吃东西”。 创建一个接口A,接口里有一个抽象方法fly()。创建一个Bird类继承Animal类并实现 接口A里的方法输出一条有语句“鸟儿飞翔”,重写eat()方法输出一条语句“鸟儿 吃虫”。在Test类中向上转型创建b对象,调用eat方法。然后向下转型调用eat()方
Jacquelyn38 Jacquelyn38
1年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。 1、使用解构获取json数据let jsonData   id: 1, status: "OK", data: ['a', 'b'] ; let  id, status, data: number   jsonData; console.log(id, status, number )
blmius blmius
1年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录 问题 用navicat导入数据时,报错: 原因这是因为当前的MySQL不支持datetime为0的情况。 解决修改sql\mode: sql\mode:SQL Mode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。 全局s
Karen110 Karen110
1年前
一篇文章带你了解JavaScript日期
日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:Thu Feb 02 2019 09:59:51 GMT+0800 (中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。 1\. 显示日期使用
Easter79 Easter79
1年前
Twitter的分布式自增ID算法snowflake (Java版)
概述 == 分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。 有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。 而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
1年前
MySQL查询按照指定规则排序
1.按照指定(单个)字段排序 select * from table_name order id desc; 2.按照指定(多个)字段排序 select * from table_name order id desc,status desc; 3.按照指定字段和规则排序 selec
Stella981 Stella981
1年前
Django中Admin中的一些参数配置
### **设置在列表中显示的字段,id为django模型默认的主键** list_display = ('id', 'name', 'sex', 'profession', 'email', 'qq', 'phone', 'status', 'create_time') ### **设置在列表可编辑字段** list_editable
Stella981 Stella981
1年前
Angular material mat
Icon Icon Name mat-icon code _add\_comment_ add comment icon <mat-icon> add\_comment</mat-icon> _attach\_file_ attach file icon <mat-icon> attach\_file</mat-icon> _attach\
Wesley13 Wesley13
1年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
#### 背景描述 # Time: 2019-01-24T00:08:14.705724+08:00 # User@Host: **[**] @ [**] Id: ** # Schema: sentrymeta Last_errno: 0 Killed: 0 # Query_time: 0.315758 Lock_
helloworld_34035044 helloworld_34035044
4个月前
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。 uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid() 或 uuid(sep)参数说明:sep 布尔值,生成的uuid中是否包含分隔符'',缺省为