前言
從去年11月下旬開始入坑機器學習,到現在也已經有半年了,一開始就是學習機器學習理論方面的知識,但是學到兩三個月的時候總覺得沒點實際操作,所以感到知識點很空洞。自己是自學機器學習的,周圍又沒有實際項目可以做,能拿來練手的恐怕只有各種數據算法競賽了。今年二月到三月第一次參加了kaggle上的Toxic文本分類比賽,但是由於自身經驗嚴重不足,和理論知識的淺薄,導致這次比賽幾乎可以以慘敗收場,雖然最后的排名也是在20%左右,這對於一個機器學習的小白來說也並不是非常差,但是總覺得心里的落差還是挺大的。kaggle的比賽結束不久,還想繼續參加,可是kaggle后來的比賽的數據量實在是太大,動輒幾十個G,還是未解壓的,自己的渣渣筆記本實在是玩不起。這時候發現天池大數據有個阿里媽媽廣告轉化預估的比賽,看到初賽的數據量比kaggle小多了,並且比賽的背景也非常符合實際的應用,我非常感興趣,就馬上參加了,並積極准備起來。在接近兩個月的比賽的過程中,數據不斷更換,自己的排名也不斷地跌宕起伏,每天除了看書就是想着怎么把排名提上去以及怎么才能進復賽,還好最終也是順利進復賽了,雖然排名比較落后,初賽第346名,不過能進復賽已經是非常滿意了,畢竟比賽總共有5200多支隊伍參與,自己還是個初學者,嗯,也不算很差吧,反正就這么給自己打氣,希望復賽能夠殺進前一百。復賽的數據要比初賽大很多,解壓后數據集有10多個G,這時我的渣渣筆記本又有了巨大的壓力,最后是僅使用了七分之一的訓練數據,要是整個數據都跑恐怕跑個baseline便Memory Error了。當然最后復賽的結果還是沒能達到預期的前一百名,只有187名,這讓我知道了天池的比賽也是高手如雲,挺進前一百名那是相當不易啊。不過也好在事情是朝着好的方向發展的,畢竟復賽排187也比初賽的346名強不是?還是在數據難度加大的情況下,嗯,又來自我安慰了哈哈。閑話不多說,現在稍微總結一下我在天池上的這一場處女賽。代碼地址附在文末。
比賽簡介
這次的比賽的目標是預測商品的轉化率,就是用戶看到這個商品的各種廣告信息來估計這個用戶買這個商品的概率。原始的特征給出了用戶的id,商品的id,商品所屬的類別和屬性以及上下文等信息,既有類別型特征又有數值型特征。評估預測結果的方法是log-loss,具體詳情見比賽網址。
數據
比賽給出的數據是用前幾天的數據情況來預測最后一天的情況,比如給出1到7號的歷史數據,里面有各種用戶商品的信息作為特征,以及給出用戶是否購買的情況,1為成交,0為未成交,所以這就是一個二分類問題。初賽的數據比較小,所以我用了全部的數據進行了訓練,但是復賽數據實在是太大了(對我的機器來說),完全沒辦法使用全部的數據,僅僅加載數據進去就耗盡了70%多的內存了。這時候有兩種選擇,一種是進行樣本采樣,從原始的樣本數據中采樣出五分之一到八分之一的數據進行訓練,其余的拋棄;另一種是僅使用最后一天的樣本進行訓練,之前天數的數據全部拋棄。我也不清楚哪一種更好,當然,全部數據都保留,一塊訓練那是最好,可是硬件條件不允許。后來對復賽的訓練數據進行了簡單的分析,發現前面幾天的轉化率都差不多,但是最后幾天的轉化率明顯有異常,典型的是倒數最后三天的情況,倒數第二和第三天的轉化率僅是開始那幾天的一半,而最后一天的轉化率竟然是開始那幾天的4倍左右,而讓你預測的那天數據和訓練集最后一天的數據是同一天的,所以我就想這訓練集的最后一天可能是類似於雙十一狂歡節這類的日子,只有在雙十一或雙十二的時候用戶會買很多商品,畢竟有活動,看到個優惠廣告就點進去,然后發現價格也不貴,嗯就買了吧,所以造成了那天的轉化率對比平時異常地變高。那么為什么前兩天轉化率變低了呢,應該是人們早就知道狂歡節有優惠,故而在前兩天看好自己想買的東西,但是不急着買,等打折了再買,只看不買的行為多了,不就造成了轉化率低了么。由於要你預測的日期和訓練集最后一天是同一天的,同時這一天的轉化率和其他天還不一樣,某種程度上就是數據的‘分布’不同,我的直覺認為僅用最后一天進行訓練會比用采樣后的樣本訓練要好,所以就僅使用了訓練集最后一天來作為訓練集。當然這是不是真的就比使用采樣樣本來訓練要好,就有待於對比檢驗了。
算法或模型
先講下使用的算法吧,本來這應該是比賽流程中的最后一步,但是我在這方面花的時間不多,使用的也都是普遍的東西,參數也沒怎么調,就先說下吧。初賽的時候使用過兩三種算法,對於這種CTR預估的場景,最經典或者說最常用的大概就是邏輯回歸(Logistic Regression, LR)了吧,確實邏輯回歸算法簡單易用,訓練速度還比較快,得到的結果精度也能滿足大多數應用場合了。但是后面主要使用的算法是LightGBM,這種樹算法的訓練速度非常快,每次大概幾十秒就可以出結果,保證了訓練的時間效率,關鍵得出的結果精度還挺高,所以當仁不讓地成為我在這次比賽中的主力模型。隨機森林(Random Forest, RF)在初賽時候也使用了,相對來說它的結果精度不如前兩個模型,log-loss值差的有點多,訓練時間也多了幾倍。總之,初賽時候由於數據集小,可以使用多個模型進行嘗試,也花不了很多時間,就把三個模型都套用了一遍,最后的提交結果是三個預測的結果取的加權平均,按LightGBM,LR,RF依次取0.6,0.3,0.1來的。初賽的結果不如人意我想是自己特征工程做的不行,復賽時候要注意了(雖然最后也還是沒找到強特征)。復賽僅使用了LightGBM這一種模型,沒有和其他模型進行stack或blend,因為測試LR和RF模型的時候發現結果特別差,再加上時間和機器等雜七雜八的原因,就沒有嘗試其他模型了。其他可用在CTR預估的算法諸如FM,FFM,DeepFM,GBDT+LR等,需要對數據進行必要的處理或者訓練時要GPU加速,自己由於精力和硬件上的不足,沒有應用,下次要嘗試一下。GBDT+LR的模型其實也是在實際中用的比較多的,主要思想就是利用GBDT利用原始特征生成一些葉子結點作為新的特征再輸入到LR里,嗯,也算是一種特征選擇吧,其實這是一種非常好的思想,但是不知為何我在實驗的時候發現效果並不好,可能還是使用方法上有些問題,當時並未仔細思考追究。下面說下特征工程。
特征工程
我使用的特征主要分為三大類,分別是組合統計特征,時間差特征以及轉化率特征,這部分是自己想得比較多且用的比較多的地方。
- 組合統計特征
就是把各種屬性類別類特征進行組合,然后統計它們在某一時間段內的行為。舉個例子,比如user_id和item_id這兩個特征,分別代表用戶名和商品名,但是同一個用戶可能看過好幾個商品(反過來也成立,即一個商品可能會被多個用戶看過),所以可以統計該用戶在同一天內看過的商品個數,以及在這一天內,該用戶在看過了某個商品之前已經看過了多少個商品,或者在看某個商品之后還會看多少個商品,這就對這個用戶的行為進行了一定的統計。當然,統計了同一天內,還可以再統計一個小時內,一分鍾內,甚至是一秒鍾內用戶看商品的行為。一秒鍾內一個用戶看好幾個商品的情況是有可能出現的,原因可能是這個用戶同一秒鍾瘋狂地點擊好多個廣告,或者是因為網絡原因這個用戶總是點擊同一個廣告,這些都是有可能的。另外,除了統計總數,還可以統計這個用戶看到的商品有多少是唯一的,例如用戶一天內看了代號為1,2,2,3,3這一共5個商品,按照上文敘述,這里會統計為5,但是唯一的商品只有1,2,3這三種,所以唯一商品的總數為3,這也可以作為一個特征,再者,二者的比例同樣也可以作為一個特征,即3/5。同時,這里也可以整理出商品的一些特征,比如代號為3的商品出現了兩次,那么這個商品就可以構造出2/5這樣的特征,即某個用戶在某個時間段內,他看過的某個商品占他所有看過的商品的比例。諸如此類的組合統計特征可以構造出相當多數量,關鍵點就在於怎樣才能構造出有利於預測轉化率的特征組合,然后對其進行行為統計。
- 時間差特征
這種特征構造開始也需要像組合統計特征一樣,要先對需要進行時間差計算的特征進行組合。還是以user_id和item_id特征為例,一個用戶看完當前商品之后,他可能還會再看另外一個商品,即點擊另外一個廣告,那么統計這二者的時間差或許會有所幫助。時間差有兩類,一類是當前點擊距離下次點擊的時間,另一類是當前點擊距離上一次點擊的時間。
- 轉化率特征
就是利用某種特征的歷史轉化率來構造特征。舉個例子,某個item_id,發現它在過去幾天中被人們點擊數是10,購買數是8,那么這個商品就有8/10的轉化率,而另外一個item_id點擊和購買數為10和2,那么轉化率就是2/10,大大低於前者,所以就可以給前者設定一個比較大的數值而給后者設定一個比較小的數值,這便構造出了轉化率特征。那么倘若你要預測的這天有的商品沒在前幾天出現怎么辦,不是沒有轉化率特征了么?設為0認為其歷史轉化率為0?這顯然不符合實際,這時候就需要貝葉斯平滑算法了,來利用先驗給其設定一個初值。而貝葉斯平滑的具體原理可能需要單獨學習一下。這里就不說了。需要注意的是轉化率特征一些事項,在我的個人實驗中,是這個類別屬性的多樣性越大越好,比如用戶的性別id,最多就三種情況,男或女或用戶沒有設置,對這三種情況進行貝葉斯平滑效果不見得有多好,而item_id,會有成千上萬種,這類屬性進行轉化率特征構造可能會好得多。另外,千萬不要構造當天的轉化率特征來預測當天的情況,比如我7號的訓練集,我構造了7號的item_id的轉化率,然后再拿7號訓練集的一部分作為訓練集,一部分作為驗證集,當然驗證結果會表現的很好,但是要是用在預測你的預測集數據上,提交結果之后會發現線上效果很差,因為,已經過擬合了。這里我用了除最后一天的數據來計算各種屬性的轉化率,這也是復賽中唯一一次用了除最后一天的訓練數據。
- 其他特征
其中有三個字符串特征,分別是item_category_list,item_property_list和predict_category_property,意思是商品的類別,商品的屬性和商品的預測類別和屬性。這三個特征是字符串型的,無法直接輸入到模型里,但直接扔了不用有點可惜。我在這里沒進行多少處理,就是把預測類別屬性的字符串給拆開,分成預測類別和預測屬性,然后對比商品的真實類別和屬性,計算它的預測命中率,即它的預測屬性有多少在真實屬性中出現過,再除以它的預測屬性總數便得到了命中率。這個命中率我想也能反映出一些問題吧,畢竟如果能夠精確地猜出用戶輸入的搜索關鍵詞對應的類別和屬性,那么用戶的使用體驗也會提高,購買商品的欲望也會強一些。我之前在初賽的時候有參考過天池官方論壇上的這篇文章,作者提出可以用文本分類中的TF-IDF的方法去處理predict_category_property這個屬性,即把這個屬性經TF-IDF處理后變成一個稀疏矩陣,再把這個矩陣輸入到一個模型里預測結果,再將結果作為一個特征。我嘗試了一下發現對預測結果影響不大就沒使用了,或許是自己的操作不是很正確,就沒有再去管它了,但是這個想法我覺得很好。
其他
有幾個問題未能很好解決
-
類別屬性直接和數值屬性一樣,直接輸入到LightGBM模型中了,雖然感覺不影響結果,LightGBM有比較好的處理類別特征的能力,但是應該有更好的表示類別屬性的方法,曾經用過one-hot方法,可是對於
LightGBM,輸出的結果並不好,或許是因為LightGBM不能很好地處理大型稀疏矩陣。 -
特征總數最后達到了三四百,這么多的特征對於我這渣渣筆記本來訓練十分夠嗆,包括構造特征的時候都是用了很多繁瑣的方法才把這些特征組合到一起,時常會有Memory Error,內存不足是硬傷。最后也是勉強用這所有特征預測出結果了,但是我認為可以進行特征選擇,這么多的特征其實大多數是不起作用的,需要篩選出真正有作用的特征,可是篩選的成本實在太高,時間和機器都不足,就沒又做了。關於特征篩選,可參考這個帖子。
-
強特不足,渣特來湊,最后特征篩選的方法其實很耗時間和資源的,關鍵點還是在於對業務的深刻理解上,以便構造出強有力的特征,我在這方面還是涉獵太少,找不出問題的關鍵所在,所以學習任重道遠。
最后感謝那些分享想法和代碼的朋友們,我從你們那里收獲了很多,再次感謝!也希望自己下次比賽再接再厲。
- 查看代碼,請點擊這里
求個星星!多謝!:)
再附上幾個前排大佬的解決方案: