基于用户画像进行广告投放,是优化投放效果、实现精准营销的基础;而人口属性中的性别、年龄等标签,又是用户画像中的基础信息。那该如何尽量准确的为数据打上这些标签?
这时候机器学习就派上用场了。 本文将以性别标签为例,介绍人口属性标签预测的机器学习模型构建与优化。
通常情况下,无监督学习不仅很难学习到有用信息,而且对于学习到的效果较难评估。所以,如果可以,我们会尽可能地把问题转化成有监督学习。
对于性别标签也是如此,我们可以使用可信的性别样本数据,加上从TalkingData收集的原始数据中提取出来的有用信息,将性别标签的生产任务转化成有监督机器学习任务。更具体来说,男/女分别作为1/0标签(Label,也就是常说的Y值,为了方便表达,我们标记男/女分别为1/0标签),这样性别标签的任务就转化成了二分类任务。
性别标签的生产流程图如下:
另外,面对TalkingData十几亿的数据体量,在标签生产的过程中,为了加速运算,除了必须用单机的情况下,我们都会优先采用Spark分布式来加速运算。
为了优化模型的效果,我们又对该性别标签预测模型进行了多次迭代。
模型最初使用的特征包括4个维度: 设备应用信息、嵌入SDK的应用包名、嵌入SDK的应用内自定义事件日志以及设备机型信息。
模型采用Xgboost (版本为0.5),基于每个维度的特征分别训练模型,得到4个子模型。每个子模型会输出基于该特征维度的设备男/女倾向的打分,分值区间从0到1,分值高代表设备为男性倾向,反之则为女性倾向。模型代码示例如下:
import com.talkingdata.utils.LibSVM
import ml.dmlc.xgboost4j.scala.DMatrix
import ml.dmlc.xgboost4j.scala.spark.XGBoost//version 0.5
//train stage
val trainRDD = LibSVM.loadLibSVMFile(sc, trainPath)// sc为SparkContext
val model = XGBoost.train(trainRDD, paramMap, numRound, nWorkers = workers)
//predict stage
val testSet = LibSVM.loadLibSVMFilePred(sc,testPath,-1,sc.defaultMinPartitions)
val pred = testSet.map(_._2).mapPartitions{ iter =>
model.value.predict(new DMatrix(iter)).map(_.head).toIterator
}.zip(testSet).map{case(pred, (tdid, feauture)) =>
s"$tdid\t$pred"
}
缺点及优化方向:
对模型使用特征的4个维度进行了调整 ,改为:嵌入SDK的应用包名、嵌入SDK的应用AppKey、设备机型信息以及设备名称。
其中,对嵌入SDK的应用包名和设备名称做分词处理。再使用CountVectorizer将以上4类特征处理成稀疏向量(Vector),同时用ChiSqSelector进行特征筛选。
模型采用LR (Logistic Regression),代码示例如下:
import org.apache.spark.ml.feature.VectorAssembler
import org.apache.spark.ml.PipelineModel
import org.apache.spark.ml.classification.LogisticRegression
val transformedDF = spark.read.parquet("/traindata/path")//分词、CountVectorizer、ChiSqSelector操作之后的特征,为vector列
val featureCols = Array("packageName","appKey", "model", "deviceName")
val vectorizer = new VectorAssembler().
setInputCols(featureCols).
setOutputCol("features")
val lr = new LogisticRegression()
val pipeline = new Pipeline().setStages(Array(vectorizer, lr))
val model = pipeline.fit(transformedDF)
//predict stage
val transformedPredictionDF = spark.read.parquet("/predictData/path")//同train一致,为分词、CountVectorizer、ChiSqSelector处理之后的特征,为vector列
val predictions = model.transform(transformedPredictionDF)
优点及提升效果:
缺点及优化方向:
模型所使用的特征,除了上个版本包括的4个维度:嵌入SDK的应用包名、嵌入SDK的应用AppKey、设备机型信息以及设备名称, 又增加了近期的聚合后的设备应用信息 ,处理方式与上个版本类似,不再赘述。
模型从LR更换成Xgboost (版本为0.82),代码示例如下:
import org.apache.spark.ml.feature.VectorAssembler
import ml.dmlc.xgboost4j.scala.spark.XGBoostClassifier//version 为0.82
val transformedDF = spark.read.parquet("/trainData/path")//分词、CountVectorizer操作之后的特征,为vector列
val featureCols = Array("packageName","appKey", "model", "deviceName")
val vectorizer = new VectorAssembler().
setInputCols(featureCols).
setOutputCol("features")
val assembledDF = vectorizer.transform(transformedDF)
//traiin stage
//xgboost parameters setting
val xgbParam = Map("eta" -> xxx,
"max_depth" -> xxx,
"objective" -> "binary:logistic",
"num_round" -> xxx,
"num_workers" -> xxx)
val xgbClassifier = new XGBoostClassifier(xgbParam).
setFeaturesCol("features").
setLabelCol("labelColname")
model = xgbClassifier.fit(assembledDF)
//predict stage
val transformedPredictionDF = spark.read.parquet("/predictData/path")//同train一致,为分词、CountVectorizer操作之后的特征,为vector列
val assembledpredicDF = vectorizer.transform(transformedPredictionDF)
val predictions = model.transform(assembledpredicDF)
优点及提升效果:
除了上个版本包括的5个特征维度,还 添加了TalkingData自有的三个广告类别维度的特征 ,虽然广告类别特征覆盖率仅占20%,但对最终标签的召回率的提升也有着很大的影响。
模型由Xgboost替换成DNN ,设置最大训练轮数(Epoch)为40,同时设置了early stopping参数。考虑到神经网络能工作是基于大数据的,因此我们将用于训练的样本量扩充了一倍,保证神经网络的学习。
DNN的结构如下:
python
GenderNet_VLen(
(embeddings_appKey): Embedding(xxx, 64, padding_idx=0)
(embeddings_packageName): Embedding(xxx, 32, padding_idx=0)
(embeddings_model): Embedding(xxx, 32, padding_idx=0)
(embeddings_app): Embedding(xxx, 512, padding_idx=0)
(embeddings_deviceName): Embedding(xxx, 32, padding_idx=0)
(embeddings_adt1): Embedding(xxx, 16, padding_idx=0)
(embeddings_adt2): Embedding(xxx, 16, padding_idx=0)
(embeddings_adt3): Embedding(xxx, 16, padding_idx=0)
(fc): Sequential(
(0): Linear(in_features=720, out_features=64, bias=True)
(1): BatchNorm1d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(2): ReLU()
(3): Dropout(p=0.6)
(4): Linear(in_features=64, out_features=32, bias=True)
(5): BatchNorm1d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(6): ReLU()
(7): Dropout(p=0.6)
(8): Linear(in_features=32, out_features=16, bias=True)
(9): BatchNorm1d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(10): ReLU()
(11): Dropout(p=0.6)
(12): Linear(in_features=16, out_features=2, bias=True)
)
)
优点及提升效果:
从原始日志当中抽取字段聚合成信息,需要经过很多步ETL,也会涉及很多优化方式,这部分有专门的ETL团队负责,在这里不做过多介绍。
模型团队可以直接使用按时间聚合之后的字段进行建模任务,尽管如此,ETL和特征生成所花费的时间,也占据了模型优化和迭代的大部分时间。
下面总结两个优化方面的坑和解决经验,希望能给大家一些参考。
对于性别标签预测,输入的特征大部分为Array类型,比如近期采集到的设备应用信息。对于这种类型的字段,在训练模型之前,我们一般会调用CountVectorizer将Array转成Vector,然后再作为模型的输入,但是CountVectorizer这一步非常耗时,这导致我们在版本迭代时不能快速实验。
针对该问题,我们 可以事先完成这一步转换,然后将生成的Vector列也存储下来 ,这样在每次实验时,就可以节省CountVectorizer消耗的时间。
在实际生产中,因为有很多标签的生产都会用到同样的字段,事先将Array转成Vector存储下来,后续不同任务即可直接调用Vector列,节省了很多时间。
虽然第一条能够节省不少时间,但Spark还是更多用于生产。其实在模型前期的探索当中,我们也 可以先用Spark生成训练集 ——因为真实样本通常不会很多,生成的训练集往往不是很大,这时我们就可以用单机来进行快速实验了。
在单机上,我们可以使用Python更方便的画图来更直观的认识数据,更快的进行特征筛选,更快的验证想法。在对数据、对模型有了深入的了解之后,我们就可以把实验所得的结论快速应用到生产当中。
作者介绍:
张小艳,TalkingData数据科学家,目前负责企业级用户画像平台的搭建以及高效营销投放算法的研发,长期关注互联网广告、用户画像、欺诈检测等领域。
本文转载自公众号 TalkingData(ID:Talkingdata)。
原文链接:
领取专属 10元无门槛券
私享最新 技术干货