最终成绩:A, B榜第三。
比赛地址:https://www.turingtopia.com/competitionnew/detail/e4880352b6ef4f9f8f28e8f98498dbc4/sketch
代码地址:https://github.com/LogicJake/tuling-video-click-top3

赛题背景

移动互联网的快速发展,催生了海量视频数据的产生,也为用户提供了类型丰富的视频数据类型。面对如何从海量视频数据类型中选择用户喜欢的类型的这一难题,作为一家拥有海量视频素材和用户行为的数据公司,希望通过用户行为数据,用户特征,以及视频特征,可以在充足数据基础上精准的推荐给用户喜欢的视频类型。

本次竞赛的目的是以用户的视频行为数据为基础,构建推荐模型,参赛队伍则需要搭建个性化推荐模型。希望参赛队伍能够挖掘数据背后丰富的内涵,为移动用户在合适的时间、合适的地点精准推荐用户感兴趣的内容,提高用户在数据集上的点击行为。

参赛者通过构建推荐模型,预测待测试数据中用户在对应的视频上是否会产生点击行为。

数据说明

第一部分是用户在资讯全集上的移动端行为数据(D)。

训练数据包含了抽样出来的一定量用户在三天之内的移动端行为数据(D),评分数据是这些用户在之后一天对子集(P)的点击数据。参赛者要使用训练数据建立推荐模型,并输出用户在接下来一天点击行为的预测结果。

解决方案

数据探索

通过数据来源,我们能够更清楚地了解数据,了解业务背景。比赛数据采集于一款叫“亿刻看点”的 APP,此 APP 安卓独占,因为其某些流氓操作在苹果那边应该过不了审。这款 APP 以阅读赚钱(看应用市场评论提现很困难)为吸引点吸引用户使用,但有些迷之操作,在使用过程中会出现广告和下载弹窗,甚至退出这款 APP 后也能给我弹(如图1左)。APP 推荐的视频大多是影视剪辑,没有任何时效性,推荐策略也很单一:推荐与上次点击相似的视频。从图1右可以看到,当我点击了“那年花开”的视频片段后,刷新之后再推荐的就是该影视剧相关。

图1 APP 使用展示

从一个用户的角度,这款 APP 的用户体验不佳,首先广告弹出,误触下载让我无法忍受,所以不可能持续使用。即使有些人为了赚钱使用,也有极大几率为了奖励去点击视频,这样会产生大量不可信的操作数据。当一个人的行为数据不可信,对其未来的点击预估也就无从谈起了。在比赛中也有类似的体会,许多 ctr 预估中使用的常规特征收益很低甚至无效。

回归到数据本身,训练集总共给出了从2019.11.08 ~ 2019.11.10的三天数据,需要预测2019.11.11的用户点击行为。首先观察一下新老用户分布,下表列出了9,10,11三天昨日老用户占比。可以看出昨日老用户占比还是挺高,所以在后面的特征工程做了大量针对用户昨日行为的特征。按道理说这种体验感不好的 APP,在用户手机上存活时间不会太长,应该不会存在这么高的老用户比例,也不知道是奖励起到了作用还是数据提供方特意筛选出一部分活跃用户。

day 昨日老用户比例
9 0.6501773380623483
10 0.6388528497721992
11 0.7410879161463921

特征工程

这一题数据量比较大,模型训练起来也比较慢,所以针对 lgb 模型准备了两套参数,一套学习率比较大用于快速迭代验证特征效果,训练一次大概在半小时左右,线上分数0.825。另外一套学习率为0.01的参数用于正式提交,运行一次大概需要15小时,线上分数0.8377。

穿越特征

前面说过,这题常规特征收益很小,但由于数据给出了视频曝光时间(ts),所以可以借助其构造大量穿越特征,或者称作为视频点击后的模式特征。基本构造方法就是计算距离下一次视频曝光的时间差。这么做的原因也很好理解,假如一个人点击了某个视频,那么必然会观看一段时间,那么距离下一次视频的曝光就会久一点,ts 差值也较大。相反,连续两次视频的曝光时间间隔应该很小。距离上次视频的曝光时间差也是有效的,根据 APP 的推荐规则,在点击视频后下次推荐的也是相关视频,从而再次点击的可能性较大。我们构造了大量组合下的视频曝光时间间隔,曝光跨度也从1往后扩展,主要构造如下特征:

  • deviceid 前x次曝光到当前的时间差
  • deviceid netmodel 前x次曝光到当前的时间差
  • deviceid 后x次曝光到当前的时间差
  • deviceid pos 后x次曝光到当前的时间差
  • deviceid netmodel 后x次曝光到当前的时间差
  • deviceid netmodel pos 后x次曝光到当前的时间差
  • deviceid lng_lat 后x次曝光到当前的时间差
  • deviceid lng_lat pos 后x次曝光到当前的时间差

除 ts 是个强特征之外,pos 也是强特。pos 取值范围为0 ~ 8,取值分布如图2左所示。抓包分析可知0 ~ 3这四个值对应首页推荐四个位置,但从图2右可以看到界面大小最多只够显示2个半,有曝光才有流量,所以才会导致 pos 取0,1,2的数据量相较于3较大。pos中的其他取值并没有找到在何处产生,猜想可能来自于相关推荐视频点击或者消息栏推送点击。进一步分析在不同 pos 下的点击率,如图3所示。可以看出同样是首页推荐位置,位置3虽然总数据不多,但点击率远高于其他首页位置。初步猜想大部分用户可能只打开看了一下,只看到首页屏幕大小展示的三个视频,并不会下滑查看更多推荐,所以才会出现0 ~ 2的数据量大,但点击率非常低的情况。对于深度使用用户,其要么出于完成任务拿奖励的目的还是正常使用,点击欲望相对更高,这部分用户在使用探索中会更多地看到 pos 3以上的视频。

图2 左:pos 取值分布;右:app 视频展示位
图3 各pos是否点击分布
图3 各pos是否点击分布

效仿 ts 的穿越特征构造,我们又尝试构造了后x次视频曝光位置,同样效果也十分显著。仔细研究 APP 发现,假如你不是直接在首页点击播放视频,而是点击进入该视频的详情页,同样会触发视频观看,同时在视频详情页下会出现相关推荐视频,如图4红框所示。综上猜测,pos 中某些取值对应于相关推荐位,所以当下次视频曝光位置为上述相关推荐位,则表示当次视频一定是被用户点击观看的。

图4 相关推荐
图4 相关推荐

通过学习上述两大类穿越特征,模型的效果已然十分明显,在快速迭代模型上,已经能够达到0.80114的分数。相较于最高分0.825,说明常规特征带来的收益只能提高0.024。穿越特征只有30+,但常规特征却有140左右,收益对比一目了然。工程实践中穿越特征自然无法使用,但作为一项数据挖掘比赛,当然是追求已有数据的最大化利用。

除了上述两大类穿越特征,还构造了下一次视频曝光的网络环境和基于经纬度的位置变化。

历史特征

历史特征主要用过去一个时间单位的数据进行统计,然后作为当前时刻的特征。由于数据中昨日老用户占比较多,所以本方案大量构造了昨日数据统计特征。在这部分特征中大量涉及到各种点击率的构造,点击率使用到了标签数据,因此相较于全局统计点击率,构造昨日点击率避免了标签泄露和数据穿越问题。主要构造的点击统计特征如下:

  • deviceid 点击次数,点击率
  • deviceid, hour 点击率
  • deviceid, netmodel 点击率
  • newsid 点击次数,点击率
  • next_pos 点击率

原数据给出了两个时间特征,一个是视频曝光时间 ts 和视频假如被点击时的点击时间 timestamp。二者的差值可以表示用户的反应时间,反应时间越短说明用户越喜欢该类视频。针对反应时间分别以 deviceid 和 newsid 为单位构造统计特征,构造方式包括:max,min,mean,std,median,kurt 和 quantile。反应时间从用户侧提取,所以针对 newsid 做统计误差较大,效果不明显,所以只保留了 std 统计。

除了以天为单位构造昨日数据特征,还以小时为单位构造上一小时的统计特征,尝试下来只有上一小时的 deviceid 点击次数和点击率有点效果。

count 统计

这部分特征以不同时间单位进行 count 统计,时间单位包括:10分钟,小时,天,全局。不同于历史特征,这部分 count 统计往往会出现数据穿越问题。count 特征主要反应偏好属性,比如今天哪个视频曝光量大,用户倾向于在哪个时间,哪个网络环境下使用 APP。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cat_list = [['deviceid'], ['guid'], ['newsid'], ['deviceid', 'pos'], ['newsid', 'pos'], 
['deviceid', 'guid', 'newsid'], ['deviceid', 'next_pos']]
for f in tqdm(cat_list):
df_feature['{}_day_count'.format('_'.join(f))] = df_feature.groupby(['day'] + f)['id'].transform('count')

cat_list = [['deviceid'], ['guid'], ['deviceid', 'pos'], ['deviceid', 'netmodel']]
for f in tqdm(cat_list):
df_feature['{}_minute10_count'.format('_'.join(f))] = df_feature.groupby(['day', 'hour', 'minute10'] + f)['id'].transform('count')

cat_list = [['deviceid', 'netmodel']]
for f in tqdm(cat_list):
df_feature['{}_hour_count'.format('_'.join(f))] = df_feature.groupby(['hourl'] + f)['id'].transform('count')

cat_list = [['deviceid', 'group', 'pos']]
for f in tqdm(cat_list):
df_feature['{}_count'.format('_'.join(f))] = df_feature.groupby(f)['id'].transform('count')

embedding 特征

作为推荐中重要的一方,数据集给出的视频信息十分少,只有一列简单的视频 id,并没有视频分类之类的对推荐重要的属性信息,所以我们只能从视频被推荐给哪些用户下手。以视频为单位,找出被推荐的用户列表,每个列表作为句子喂到 Word2Vec 模型得到每个用户的 embedding 向量,用视频所有被推荐的用户的 embedding 向量平均值表示视频。得到 embedding 之后,就可以度量两个视频之间的相似度,所以也就产生了另外一种思路,当一个新视频被推荐给用户后,计算新视频与之前用户被推荐过(或者看过)的视频的平均相似度,平均相似度越大,用户点击的可能性越大。但在实际试验中效果不好,也不知道是不是 embedding 效果达不到的原因。

user 表的打开方式

这一题特征构造主要还是围绕 train 表做的,user 表打开过多次都没啥效果。用户画像属性 tag 和 outertag 维度特别高,尝试过 PCA 降维和各种 embedding 方式都不行,最后还是采用的开源方法,对 tag 的分数做一些简单的统计。app 表的 applist 维度更高,也是各种尝试无效。

其他特征

我们根据连续使用将用户的使用时间分段,曝光时间间隔少于3分钟的视为同一个使用时段。然后统计每个使用时段中上一次曝光时间差的 mean 和 std 特征。还统计了每个用户下一次曝光时间差的 mean,std,median,skew。针对曝光时间差的统计可以反应用户当前使用时段或者整体上的视频观看时长信息。

此外还构造了用户平均每小时被曝光多少视频,该视频是一小时内被曝光给用户的第几个视频,今日曝光给用户的视频量与昨日曝光给用户的视频量的差值,未来一小时用户在该网络环境下的视频曝光数量。

当然也有许多尝试后效果不好的操作,比如数据集给出了用户的设备信息:设备厂商,设备版本,可以效仿科大讯飞反欺诈中的操作,对其进行数据处理,但可能由于自身重要性就不大,数据处理也没啥用。借助百度地图 api 可以将数据集中的经纬度转化成具体的城市,也没啥效果。

模型

这题主要有两种建模方式,一种是常规的五折交叉验证,在划分每折数据时,按 day 所占比例划分比以是否点击比例划分要好,可以让每折模型学习到三天的数据,做到时间上同分布。另一种按时间划分,线下用8,9两天的数据做训练集,10号数据做验证集,保留模型的最优迭代次数,然后线上模型用完整的三天数据预测测试集。对比来看,第二种验证集划分方式和线上更为相似,所以与线上分数 gap 较小,训练时间也更短,所以我们主要使用第二种建模方式。但第一种可以得到全数据集上的点击概率,可以作为新的特征加到数据集中,可以提升0.002-0.003。需要注意的是假如采用这种方式集成,五折模型的学习率不能太小,否则模型效果足够好导致概率值特征过强,使得时间划分模型直接拟合概率值,过早早停,无法学习其他特征。

总结

个人感觉,受限于 APP 的用户体验,单纯的用户属性对预测作用不大,这也是为什么 app 表和 user 无法正确打开的原因。所以我们需要从用户的行为数据(也就是 train 表)构造特征,但行为数据中也有大量不可信操作。有些用户为了完成 APP 的任务拿奖励,会随意点击观看视频,这部分行为完全随机,无法从中正确提炼出用户的兴趣所在,也就很难去预测未来的点击行为。但用户观看视频后的模式是固定的,视频的播放时间是必定有的,所以 ts 的时间差是可信的。点击视频详情页出现的相关视频推荐也是必然存在的(这是我猜测的,可能不正确),所以下一次视频曝光的 pos 也是可信的。所以这一题常规特征干不过穿越特征,无法过于信任用户行为,只能信任机器行为。

参考资料

比赛初期0.65开源
天才儿童0.81高分开源
user 表处理方式开源
麻婆豆腐视频讲解