原文:http://quweiprotoss.blog.163.com/blog/static/4088288320144810567471/
廣告點擊率預測
屈偉 / Koala++
先聲明一下,本文所提到的所有的點擊率預測的技術不是我在的團隊使用的,因為我們團隊使用的技術是保密的,所以我也不知道他們是怎么做的。事實上我不知道廣告點擊率怎么預測的,認識我的人都知道,我就是最喜歡舞那開始三板斧的人,然后我就想扔了板斧投降了。也希望各位能指正我所寫的內容中的錯誤之處,給我一下學習第四斧的機會。
強調一下,按本文的方法來一天就能實現pCTR的功能。
Introduction
我所寫的這一篇,從技術和算法上講,比不上網上幾個公司的pCTR的PPT,那幾篇比較偏重理論,而且一般講幾種算法,讓新接觸pCTR的人很茫然,不知道如何開始實現。這就是我寫這一篇文章的原因。如果你在一兩天之內完成pCTR最粗糙的版本,這是真的可以做到的。Andrew Ng說過:你應該最短的時候,比如一天的時候,完全一個粗糙的版本,看它有什么問題,再去解決。不要擔心太粗糙太快速。
廣告點擊率預測(pCTR Predict Click-Through Rate)是廣告算法中最核心的技術了。pCTR要解決的問題是預測特定用戶在特定廣告位對特定廣告當特定環境下的點擊概率。為什么pCTR如此重要,因為廣告排序的核心是eCPM = pCTR * CPC,CPC是廣告主對點擊的出價,是個已知量,所以只有一個pCTR變量。當然在實際中不可能是如此簡單的排序公式,比如還有質量得分(Quality Score),比如Google的質量得分因素。
pCTR一般是從離線數據中學習得到的,離線數據是保存到類似Hive的分布式數據庫中,通過機器學習的算法將Hive中的數據進行分析,得到一個pCTR模型,這個模型就可以預測pCTR了,大致流程就是這樣。
![廣告點擊率預測 [離線部分] - quweiprotoss - Koala++s blog 廣告點擊率預測 [離線部分] - quweiprotoss - Koala++s blog](/image/aHR0cDovL2ltZzAucGguMTI2Lm5ldC9kOEhoY3d0aEZxemtqT1dRYzdpSnhnPT0vNjYwODI2ODI5MjU4NTcwNDIxOS5qcGc=.png)
下圖中有淡綠色和綠色兩塊背景,分別表示離線部分和在線部分。看起來離線部分的工作很多,在線部分似乎沒什么工作。其實要實現一個通過配置把模型加載進去的在線部分,的確沒什么工作量,幾行代碼就完了。但想實現一個比這強一點的在線部分,都要用一周以上的時候來完成。
離線部分,真正能用的當然全要用MapReduce來寫,粗糙版本就用python單機運行就行了,看起來有Join,Norm,Binarize,Train四個步驟,其實都是比較簡單的。
Join步驟就是將多個數據源的數據通過Key進行Join,和Sql里的Join是一個意思。
Norm和Binarize是對數據進行一定的變換,這是由我們將要使用的Logistic Regression算法決定的,其實很多算法都逃不了這兩步的,所以不用擔心會做無用功。
Train這一步就是真正訓練模型的工作了。我介紹的時候會用liblinear。
![廣告點擊率預測 [離線部分] - quweiprotoss - Koala++s blog 廣告點擊率預測 [離線部分] - quweiprotoss - Koala++s blog](/image/aHR0cDovL2ltZzEucGguMTI2Lm5ldC9SQS1sZlgwaUVkT2NwcDJvX1Iwa3d3PT0vMzY3NDkzNzI5NjAzNDU5MzU0OC5wbmc=.png)
Offline
Join
![廣告點擊率預測 [離線部分] - quweiprotoss - Koala++s blog 廣告點擊率預測 [離線部分] - quweiprotoss - Koala++s blog](/image/aHR0cDovL2ltZzEucGguMTI2Lm5ldC9hOS1rVTFwZzA3Z2g1NVNzMGhWUHR3PT0vMTMzNjE2MTcxNDU0NTc5NDg1Mi5wbmc=.png)
這一步是將多個數據源的數據,通過類似SQL中的Left Join將多個數據源合並起來,要合並起來是因為我們的訓練數據是最終要是一個向量,所以離線的時候一定要先將數據合並成一行。
上圖的例子解釋一下:左圖是Hive中的Log,左圖是User Info。左圖中有一些字段,比如:廣告ID,用戶ID,廣告位ID,時間,等等,右圖中有用戶ID,性別,年齡等等。通過UserID將兩個數據Join后,就得到了下面的數據。
Note:1. 上圖只是舉個例子,實現的時候,最好不要把User Info中的User ID在合並的時候去掉,否則在你的字段配置文件會有困難。2. 用Hadoop實現的時候,一定要考慮key skew的問題,否則會出現out of memory的問題。3. 要考慮Join的時候有多個Key的情況。4. 數據格式最好要求嚴一些,因為處理數據一些就是腳本來寫,而如果把工作都放到了Join里,那就是Hadoop Java了。5. 如果是要實現粗糙版本,這一步應該是可以跳過的,因為一般來講,對pCTR重要的特征都是已經上報了的。6. Left Join的時候,要設置Null值,設置的時候注意點,不要設置0之類的,Norm的時候又忘了。
另一個問題:為什么有Join這個問題呢?直接讓工程組的人把我要的字段都寫到Log里,我一句select就完了呀!其實這個問題有很多答案,比較合理的答案是:有多少pCTR模型,比如有電商的廣告的pCTR,游戲廣告的pCTR等等,每個pCTR都用不同的特征,如果都上報,會很浪費,問題又來了:那你不能pb上報?答:在線部分我還沒想出來如何能完全不修改在線部分的代碼,完成特征的增減。如果你不停的讓工程組的人添加特征相關代碼,刪除特征相關代碼,我相信他們會找你拼命的。
![廣告點擊率預測 [離線部分] - quweiprotoss - Koala++s blog 廣告點擊率預測 [離線部分] - quweiprotoss - Koala++s blog](/image/aHR0cDovL2ltZzIucGguMTI2Lm5ldC9zZks0OUtmenhNVVY1WXdpbGRtWHdRPT0vMzc3MDA3NTgzODE2MjYyNjgwNC5wbmc=.png)
整個Join過程的示意圖如上,我畫的還是比較仔細的,在Join Ad Info的時候,用的是多個Key Join,而下面的幾個矩形的大小是不同的,因為它們Key的個數肯定是不同的。
Cross Feature
有時候和別人交流的時候說LR模型是線性模型,別人很疑惑的說sigmoid函數明顯不是線性函數呀?我給一下圖就明白了,圖中的decision boundary是一條直線。為什么是直線?因為weight向量和特征向量x線性關系。
那如果我就這兩個特征,我想得到不是直線的decision boundary怎么搞呢?兩個方法:1. 讓算法支持,比如用神經網絡,2. 自己把高維特征給造出來。說一下神經網絡算法,神經網絡上次實踐時發現也不是完全不靠交叉特征就能hold住的。再下面一張圖就是引入高維特征后的decision boundary(另一個數據集,我懶得找圖了):
交叉特征幾乎沒什么工作,就是兩個字段值拼到一起。舉個例子:比如User Info里有個性別字段,Ad Info里有個字段是廣告ID,現在我想產生一個性別和廣告ID的交叉字段,再假設有個樣本里性別為男,廣告ID為1234,交叉特征就是男_1234。
注:1. 產生交叉特征的時候一定要搞分隔符,不然12和1交叉和1和21交叉出來結果一樣。2. 交叉哪些特征呢?自己把握了,當然不能一下子交叉五六個特征,這樣特征特別稀疏,而且詞典很大,給線上代碼編寫再來不必要的壓力。3.如果實現這粗糙版本,這一步跳過,畢竟你不想搞了半天就得到一個過擬合的效果。
![廣告點擊率預測 [離線部分] - quweiprotoss - Koala++s blog 廣告點擊率預測 [離線部分] - quweiprotoss - Koala++s blog](/image/aHR0cDovL2ltZzEucGguMTI2Lm5ldC82TlVqRmduYm5Ccm9YcWg5MG1tbGJnPT0vMjY3ODIzNDQwMzUwMjE3OTY1Mi5wbmc=.png)
![廣告點擊率預測 [離線部分] - quweiprotoss - Koala++s blog 廣告點擊率預測 [離線部分] - quweiprotoss - Koala++s blog](/image/aHR0cDovL2ltZzAucGguMTI2Lm5ldC9QODRWY2VQcjc2ZFpRdTNaYmFLazJnPT0vMzY2NTA4NTY3MTg0OTcyMTE5NS5wbmc=.png)
Norm
需要Norm的原因是因為我們要用LR算法,相信大家也知道不是所有的算法都有這個過程,比如Normal Equation。大概解釋一下要Norm的原因,可能大家都見過下面的圖:
![廣告點擊率預測 [離線部分] - quweiprotoss - Koala++s blog 廣告點擊率預測 [離線部分] - quweiprotoss - Koala++s blog](/image/aHR0cDovL2ltZzIucGguMTI2Lm5ldC84UzMwbEl1Wi1WLTE5WG9mc3hWalZnPT0vNjYwODIwMzQyMTM5OTY2Njc4MC5wbmc=.png)
上圖是梯度下降的軌跡。為什么是個橢圓,而不是圓,是因為特征的值域不同,如果值域相差,這個橢圓會變的非常扁。
![廣告點擊率預測 [離線部分] - quweiprotoss - Koala++s blog 廣告點擊率預測 [離線部分] - quweiprotoss - Koala++s blog](/image/aHR0cDovL2ltZzIucGguMTI2Lm5ldC9QSUcycEZNYXNSaTlWUHZneW0zRDFnPT0vNjYwODIwNzgxOTQ0NjE3Nzg4OS5wbmc=.png)
![廣告點擊率預測 [離線部分] - quweiprotoss - Koala++s blog 廣告點擊率預測 [離線部分] - quweiprotoss - Koala++s blog](/image/aHR0cDovL2ltZzEucGguMTI2Lm5ldC9LOTNMek13ZDVYb05hTlJiSW1MM2xBPT0vNjYwODI0OTYwMDg4ODAzMTU4OS5qcGc=.png)
舉個例子,左圖是兩個特征值域在[-10, 10],右邊的兩個特征分別是[-10, 10], [-30, 30],可以看出下面的函數等高線一下圓,一個扁。那么圓和扁的后果是什么呢?想象爬山的時候,有兩座山,一座山是類似半球形的,另一座山是把前一座山沿一個方向拉長幾十倍,爬這兩座山的時間上的區別。
雖然背后的道理是需要點時間去理解,但Norm的過程卻是異常簡單,最常用的兩種方法,max-min和standard-score,推薦standard-score,因為max-min可能一兩個孤異點,把特征的作用給抹殺了。
Max-Min方法:
Standard-Score方法:
為什么我計算方差要用上面的公式,不是常用的方差公式,是因為這種計算廣告只用一次Map/Reduce,而標准計算公式需要兩次Map/Reduce,一次計算均值,一次計算方差。
注:1. Norm的時候注意缺失值的處理,特別是把缺失值設置成0或-1這種問題。2. Norm和Binarize顯然只會走其中一個邏輯的。
Binarize
二值化一樣是因為要用LR算法的原因,如果用決策樹那就沒這問題了。LR需要二值化是因為一些離散值是不可比的。比如性別的男和女,無法比較大小。而一些看起來可比的值,其實邏輯上也不可比了,比如年齡,29歲並不比23歲大。可比的例子,比如:已經曝光的次數,出價。
我下面舉個例子來說明Binarize的過程:
性別 [男,女],學歷 [小學,初中,高中,本科,碩士,博士]
性別詞典 [男, 女],學歷詞典 [小學,初中,高中,本科,碩士,博士]
全局詞典 [男, 女,小學,初中,高中,本科,碩士,博士]
用戶A:(男, 本科),二值化結果 [1, 0, 0, 0, 0, 1, 0, 0]
用戶B:(女, 碩士),二值化結果 [0, 1, 0, 0, 0, 0, 1, 0]
我發現我給別人解釋邏輯的時候,最常被問到的就是如果是我事先不知道詞典是什么怎么辦?事實上,實現的時候和文本分類一樣,是要掃兩次數據的,第一次產生詞典,第二次將樣本轉化1, 0向量,當然一般用稀疏矩陣的表示方法,0就不寫了。
Training
已經說了是要一天做完pCTR,那顯然自己寫就有點不現實了,下載一下liblinear,然后就可以開始訓練了,當然一般點擊率都很低的,Feeds,搜索廣告高一些x%,展示廣告一般就0.x%,我做的產品點擊率就低的沒臉提了。按一切從簡一切從暴的原則,正例全保留,負例按正例的倍數抽吧。
我一般是按Andrew Ng說的,先把Bias-Variance的圖畫出來,看一下指標不好的可能是什么原因。
![廣告點擊率預測 [離線部分] - quweiprotoss - Koala++s blog 廣告點擊率預測 [離線部分] - quweiprotoss - Koala++s blog](/image/aHR0cDovL2ltZzIucGguMTI2Lm5ldC9kODluX3cwYVAtQ2MwT3ZwMHVvT1d3PT0vMjY3MDM1MzEwNDE1NDI4MDcwNy5wbmc=.png)
![廣告點擊率預測 [離線部分] - quweiprotoss - Koala++s blog 廣告點擊率預測 [離線部分] - quweiprotoss - Koala++s blog](/image/aHR0cDovL2ltZzEucGguMTI2Lm5ldC83T1VYb3FLRGZmZFAwUXU2OEt2WTRRPT0vMjY3MzczMDgwMzg3NDgwOTI2OC5wbmc=.png)
我還喜歡的一個圖是把特征一個個加入后,指標的變化曲線,還是因為公司保密的原因,我就不帖圖了,可能有人會問為什么不直接用特征選擇看一下就行了,或是L1看,是因為Binarize之后,特征和原始特征對應是可以對應,但這樣還是不行我的方法來的直觀。
還有一個問題,抽樣了要還原吧,哎,就負例抽了多少比例,再除回去吧。你是不是已經憤怒了,到了最后一步你就這樣耍我的?其實我真的認為除回去也是勉強能自圓其說的。想知道下不那么山寨的做法?看下google的論文:Ad Click Prediction: a View from the Trenches,里面的Subsampling Training Data,CALIBRATING PREDICTIONS。