如何用機器學(xué)習(xí)模型,為十幾億數(shù)據(jù)預(yù)測性別
基于用戶畫像進行廣告投放,是優(yōu)化投放效果、實現(xiàn)精準營銷的基礎(chǔ);而人口屬性中的性別、年齡等標簽,又是用戶畫像中的基礎(chǔ)信息。那該如何盡量準確的為數(shù)據(jù)打上這些標簽?
這時候機器學(xué)習(xí)就派上用場了。本文將以性別標簽為例,介紹人口屬性標簽預(yù)測的機器學(xué)習(xí)模型構(gòu)建與優(yōu)化。
性別標簽預(yù)測流程
通常情況下,無監(jiān)督學(xué)習(xí)不僅很難學(xué)習(xí)到有用信息,而且對于學(xué)習(xí)到的效果較難評估。所以,如果可以,我們會盡可能地把問題轉(zhuǎn)化成有監(jiān)督學(xué)習(xí)。
對于性別標簽也是如此,我們可以使用可信的性別樣本數(shù)據(jù),加上從TalkingData收集的原始數(shù)據(jù)中提取出來的有用信息,將性別標簽的生產(chǎn)任務(wù)轉(zhuǎn)化成有監(jiān)督機器學(xué)習(xí)任務(wù)。更具體來說,男/女分別作為1/0標簽(Label,也就是常說的Y值,為了方便表達,我們標記男/女分別為1/0標簽),這樣性別標簽的任務(wù)就轉(zhuǎn)化成了二分類任務(wù)。
性別標簽的生產(chǎn)流程圖如下:

- 簡單來說,輸入為具有可信性別信息的樣本數(shù)據(jù),以及從近期活躍的原始數(shù)據(jù)中提取出有用特征;
 - 將兩者join之后,得到可以直接用于建模的數(shù)據(jù)集;
 - 基于該數(shù)據(jù)集進行建模,學(xué)習(xí)出性別預(yù)測模型;
 - 再用該模型對全部樣本進行預(yù)測,從而得到所有樣本的性別打分。至此,模型部分的工作基本完成;
 - 最后一步是確定閾值,輸出男/女標簽。這里我們不依賴模型確定閾值,而是借助比較可信的第三方工具,保證在期望準確度(precision)下,召回盡可能多的樣本。
 
另外,面對TalkingData十幾億的數(shù)據(jù)體量,在標簽生產(chǎn)的過程中,為了加速運算,除了必須用單機的情況下,我們都會優(yōu)先采用Spark分布式來加速運算。
特征與模型方法的版本迭代
為了優(yōu)化模型的效果,我們又對該性別標簽預(yù)測模型進行了多次迭代。
01性別預(yù)測模型V1
模型最初使用的特征包括4個維度:設(shè)備應(yīng)用信息、嵌入SDK的應(yīng)用包名、嵌入SDK的應(yīng)用內(nèi)自定義事件日志以及設(shè)備機型信息。
模型采用Xgboost(版本為0.5),基于每個維度的特征分別訓(xùn)練模型,得到4個子模型。每個子模型會輸出基于該特征維度的設(shè)備男/女傾向的打分,分值區(qū)間從0到1,分值高代表設(shè)備為男性傾向,反之則為女性傾向。模型代碼示例如下:
<左右滑動查看完整代碼>
- 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"
 - }
 
缺點及優(yōu)化方向:
- 模型為四個子模型的融合,結(jié)構(gòu)較復(fù)雜,運行效率較低,考慮改為使用單一模型;
 - 嵌入SDK的應(yīng)用內(nèi)自定義事件日志特征覆蓋率低,且ETL處理資源消耗大,需重新評估該字段對模型的貢獻程度;
 - 發(fā)現(xiàn)設(shè)備名稱字段看上去有男/女區(qū)分度——部分用戶群體會以名字或者昵稱命名設(shè)備名(例如帶有“哥”“軍”等字段的傾向為男性,帶有“妹”“蘭” 等字段的傾向為女性),驗證效果并考慮是否加入該字段。
 
02性別預(yù)測模型V2
對模型使用特征的4個維度進行了調(diào)整,改為:嵌入SDK的應(yīng)用包名、嵌入SDK的應(yīng)用AppKey、設(shè)備機型信息以及設(shè)備名稱。
其中,對嵌入SDK的應(yīng)用包名和設(shè)備名稱做分詞處理。再使用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)
 
優(yōu)點及提升效果:
采用單一的模型,能夠用常見的模型評估指標(比如ROC-AUC, Precision-Recall 等)衡量模型,并在后續(xù)的版本迭代中作為baseline,方便從模型角度進行版本提升的比較。
缺點及優(yōu)化方向:
LR模型較簡單,學(xué)習(xí)能力有限,后續(xù)還是替換成更強大的模型,比如Xgboost模型。
03性別預(yù)測模型V3
模型所使用的特征,除了上個版本包括的4個維度:嵌入SDK的應(yīng)用包名、嵌入SDK的應(yīng)用AppKey、設(shè)備機型信息以及設(shè)備名稱,又增加了近期的聚合后的設(shè)備應(yīng)用信息,處理方式與上個版本類似,不再贅述。
模型從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)
 
優(yōu)點及提升效果:
- 相比上個版本,AUC提升了6.5%,在最終的性別標簽生產(chǎn)中召回率提升了26%??紤]到TalkingData的十幾億的數(shù)據(jù)體量,這個數(shù)值還是很可觀的。
 
04性別預(yù)測模型V4
除了上個版本包括的5個特征維度,還添加了TalkingData自有的三個廣告類別維度的特征,雖然廣告類別特征覆蓋率僅占20%,但對最終標簽的召回率的提升也有著很大的影響。
模型由Xgboost替換成DNN,設(shè)置最大訓(xùn)練輪數(shù)(Epoch)為40,同時設(shè)置了early stopping參數(shù)。考慮到神經(jīng)網(wǎng)絡(luò)能工作是基于大數(shù)據(jù)的,因此我們將用于訓(xùn)練的樣本量擴充了一倍,保證神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)。
DNN的結(jié)構(gòu)如下:
<左右滑動查看完整代碼>
- 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)
 - )
 - )
 
優(yōu)點及提升效果:
- 與上個版本對比,AUC僅提升了1.5%,但在最終性別標簽生產(chǎn)中的召回率提升了13%,考慮數(shù)據(jù)體量以及現(xiàn)有的標簽體量,這個提升還是不錯的。由此可以看出,在驗證版本迭代效果的時候,我們不應(yīng)該僅僅從模型的AUC這單一指標來衡量,因為這對版本迭代的效果提升程度衡量不夠準確。我們應(yīng)該驗證最終的、真正的指標提升情況——在性別標簽預(yù)測中,是期望準確度(precision)下召回的樣本數(shù)量。但我們?nèi)匀豢梢栽诎姹緝?yōu)化時使用AUC等模型相關(guān)指標,來快速驗證控制變量的實驗效果,畢竟這些指標容易計算。
 
模型探索小建議
從原始日志當中抽取字段聚合成信息,需要經(jīng)過很多步ETL,也會涉及很多優(yōu)化方式,這部分有專門的ETL團隊負責(zé),在這里不做過多介紹。
模型團隊可以直接使用按時間聚合之后的字段進行建模任務(wù),盡管如此,ETL和特征生成所花費的時間,也占據(jù)了模型優(yōu)化和迭代的大部分時間。
下面總結(jié)兩個優(yōu)化方面的坑和解決經(jīng)驗,希望能給大家一些參考。
1.對于性別標簽預(yù)測,輸入的特征大部分為Array類型,比如近期采集到的設(shè)備應(yīng)用信息。對于這種類型的字段,在訓(xùn)練模型之前,我們一般會調(diào)用CountVectorizer將Array轉(zhuǎn)成Vector,然后再作為模型的輸入,但是CountVectorizer這一步非常耗時,這導(dǎo)致我們在版本迭代時不能快速實驗。
針對該問題,我們可以事先完成這一步轉(zhuǎn)換,然后將生成的Vector列也存儲下來,這樣在每次實驗時,就可以節(jié)省CountVectorizer消耗的時間。
在實際生產(chǎn)中,因為有很多標簽的生產(chǎn)都會用到同樣的字段,事先將Array轉(zhuǎn)成Vector存儲下來,后續(xù)不同任務(wù)即可直接調(diào)用Vector列,節(jié)省了很多時間。
2.雖然第一條能夠節(jié)省不少時間,但Spark還是更多用于生產(chǎn)。其實在模型前期的探索當中,我們也可以先用Spark生成訓(xùn)練集——因為真實樣本通常不會很多,生成的訓(xùn)練集往往不是很大,這時我們就可以用單機來進行快速實驗了。
在單機上,我們可以使用Python更方便的畫圖來更直觀的認識數(shù)據(jù),更快的進行特征篩選,更快的驗證想法。在對數(shù)據(jù)、對模型有了深入的了解之后,我們就可以把實驗所得的結(jié)論快速應(yīng)用到生產(chǎn)當中。
作者簡介:張小艷,TalkingData數(shù)據(jù)科學(xué)家,目前負責(zé)企業(yè)級用戶畫像平臺的搭建以及高效營銷投放算法的研發(fā),長期關(guān)注互聯(lián)網(wǎng)廣告、用戶畫像、欺詐檢測等領(lǐng)域。















 
 
 







 
 
 
 