在这介绍下汽车垂直领域,高意向用户的抽取计算过程。app的日活用户大约在百万级别,每天的pv量大约在1-2亿之间,有一些用户只是简单的浏览一下内容,还有一些会有更加深入的内容浏览,留下深层次线索,本节的目的是在这些用户中找到其中的高意向用户,这些高意向用户可以用作后续的广告投放人群包,特别是针对特定的汽车品牌,意义更大。
在这只说下大致的处理思路以及一点代码帮助理解。
用户特征提取
用户特征也属于用户画像的一部分,因为缺少强账号,所以拿不到用户的很多基本信息,比如性别、年龄、手机号等,像性别这些可以对用户的行为通过模型进行预测,用户特征主要使用的是用户的行为特征:
- 反作弊对用户虑除;
- 页面浏览次数,200维左右,处理单位是用户一天时间内(session时间有时候比较短);
- 用户页面跳转矩阵,灵感来自于HMM中的状态转移概率矩阵(注意归一化, 如下图所示[1]);
- 用户的其他统计信息;
现在充斥着互联网的假量信息相信很多人都有所体会,假量之大可能超乎你的想象。所以对每天的百万活跃用户进行反作弊虑除是很有必要的。反作弊方法,花了好长时间做了一个反作弊引擎,通过用户的一些统计特征以及行为特征给出用户的可信度分值,最终去除掉可信度极低的部分用户。
//scala
//dvid 使用户设备号的唯一标示 1 to 200表示app端 pageid
val pvFeatureDF = pvDataFrame
.groupBy($"dvid")
.agg(
sum("pcount").cast(DoubleType).alias(s"${flag}_total_pv"),
(1 to 200).map(pid => sum(when($"pid" === pid, $"pcount").otherwise(0)).alias(s"${flag}_pid_$pid")): _*
)
.na.fill(0)
//将特征dataframe 转为dvid, label, feature 三列,方便转化为labeledpoint结构作为mllib模型的输入
val featureTrainDF = pvFeatureDF.map(
row => (
row.getAs[String]("dvid"),
row.getAs[Double]("label"),
featureNameList.map(column => row.getAs[Double](column)).toArray
)
)
//用户页面跳转特征提取, 因为pv表中只有记录的相对关系,没有具体到每条记录的时间戳(简直是无语),只能通过窗函数平移一位得到页面跳转对(还要主要保持记录的顺序,防止分区打乱,实验结果也确实会打乱),
var userFeatureTransform = pvDataFrame
.select($"dvid", $"pid", $"rowid")
.withColumn("pid_next", lag("pid", 1).over(Window.partitionBy("dvid").orderBy("rowid")))
.groupBy($"dvid", $"pid", $"pid_next")
.agg(count("*").alias("skip_count"))
.groupBy($"dvid")
.agg(
sum($"skip_count").cast(DoubleType).alias(s"${flag}_total_skip"),
pageIdTransformList.map{case (pid, pid_next) => // 统计页面跳转对概率
sum(when($"pid" === pid && $"pid_next" === pid_next, $"skip_count").otherwise(0)).alias(s"${flag}_trans_${pid}_$pid_next")}: _*
)
模型部分
本文主要通过四个不同的维度评价用户是否是一个高意向用户,并给出意向分值(方便抽取人群包时候选择不同包的大小)。
- 提交线索的用户(反作弊引擎过滤后)赋予一定的意向权重,该部分用户通过向服务端提交真实的线索数据,已经表明具备较高的意向度;
- 进行过产品比价行为的用户,进行比较行为不管是本品还是竞品,都属于高意向用户;
- 通过用户的行为特征进行建模分析训练分类模型区分该用户是否是高意向用户,通过真实的广告投放也验证了,模型识别的意向用户要比前两者意向度更高;
- 对于某一个固定的品牌,其竞品的高意向用户,也是本品的潜在高意向用户。
建模部分代码比较简洁,通过spark的mllib包完成,部分代码如下:
//将特征转化为LabeledPoint结构,spark中的dataframe确实是一个非常好用的接口,做各类的数值运算都很方便,并且速度要比RDD块一些,所以前期的数据预处理部分(包括归一化)都放在了dataframe中完成
val labeledRDD = trainDataFrame.rdd.map(row => LabeledPoint(
row.getAs[Double]("label"),
Vectors.dense(row.getAs[Seq[Double]]("feature").toArray)
)).randomSplit(Array(0.8, 0.2), seed = 11L)
// 在这通过真实用户到店数据标注了部分样本,想通过模型来对用户是否是高意向用户进行识别
val trainRDD = labeledRDD(0)
val valRDD = labeledRDD(1)
val model = GradientBoostedTrees.train(trainRDD, boostingStrategy)
val labelAndPredict = valRDD.map(point => {
val predictLabel = model.predict(point.features)
(point.label, predictLabel)
})
结果
对最终得到的某品牌的意向用户,抽取人群包进行广告投放进行对比,发现CPL要远低于通投(40 VS 140)。也从侧面验证了这条思路还是有效果的,当然还可以做很多很多的优化,比如用户画像的完善,模型的调优等。
【1】Wang, Gang, et al. "You Are How You Click: Clickstream Analysis for Sybil Detection." USENIX Security Symposium. Vol. 9. 2013
欢迎一块交流讨论,文中有错误的地方,还请指正,谢谢~
email: bidai541@foxmail.com
网友评论