IJCAI-18 阿里媽媽搜索廣告轉化預測總結( 29 / 0.13939 )

IJCAI-18 阿里媽媽搜索廣告轉化預測總結( 29 / 0.13939 )

來自專欄 ML理論&實踐

代碼稍後開源

隊名「去網吧里偷耳機」

前後兩個半月的奮戰告一段落,這次也是我第一個從始至終參與的比賽.中間不僅經歷了排名跌宕起伏的刺激,而且結識很多大佬,從中學習比賽的經驗.更重要的是有隊友的團隊協作,不再是單打獨鬥,思維局限。感謝隊友的騷套路。

接下來奉上此次比賽完全方案,乾貨滿滿!!

本文內容:

  1. 賽題分析
  2. 特徵構造
  3. 特徵選擇
  4. 模型選擇與評估
  5. 最終融合
  6. 不足與總結

1.賽題分析

比賽題目是"搜索廣告轉化預測",需要通過人工智慧技術構建預測模型預估用戶的購買意向,即給定歷史廣告點擊相關的用戶(user)、廣告商品(ad)、檢索詞(query)、上下文內容(context)、商店(shop)等五類信息的條件下預測接下來日期廣告產生購買行為的概率(pCVR).

結合淘寶平台的業務場景和不同的流量特點,官方定義了以下兩類挑戰:

(1)日常的轉化率預估

(2)特殊日期的轉化率預

來自plantsgo

從圖中可以看出7號的轉化率高出平日一大截,據此推斷7號為雙十一。所以複賽較初賽而言得改變策略。

1.1 評估指標

通過logarithmic loss(記為logloss)評估模型效果(越小越好), 公式如下:

其中N表示測試集樣本數量, y_i 表示測試集中第i個樣本的真實標籤, p_i 表示第i個樣本的預估轉化率。這類評估函數常用logloss和AUC,簡單的說logloss更關注和觀察數據的吻合程度,AUC更關注rank order.

1.2 相關比賽

在此之前有過多場類似比賽,都可以幫助選手迅速了解比賽,同時學到更多的思路方案.

kaggle:outbrain click prediction

kaggle:Display Advertising Challenge

kaggle Click-ThroughRate Prediction

騰訊社交廣告大賽

天池優惠券使用預測

TalkingData AdTracking Fraud Detection Challenge

2.特徵構造

特徵類型分類

2.1基礎特徵

官方提供的原始特徵(基礎數據、廣告商品信息、用戶信息、上下文信息和店鋪信息),其中context_tiemstamp取值是以秒為單位的Unix時間戳,需要對其進行轉化.三個list特徵也是需要進行預處理的.

# timedata[time] = pd.to_datetime(data.context_timestamp, unit=s)# listdata_item_category = data.item_category_list.str.split(;, expand=True).add_prefix(item_category_)data_item_property = data.item_property_list.str.split(;, expand=True).add_prefix(item_property_)

2.2統計特徵

統計特徵主要用到三種度量方式:count,unique,mean,分別從全局,天,小時三種時間粒度來構造.

2.3時間差特徵

時間差特徵在這次比賽中也算是trick的存在,從用戶點擊商品的時間差來反映用戶購買商品的可能性,短時間內點擊相同商品購買的可能性會比較大.我們分別:

  • 從單特徵,多特徵進行組合構造
  • 從全局,天統計首次點擊與當前點擊的時間差,最後次點擊與當前點擊的時間差
  • 上一次點擊和下一次點擊與當前點擊的時間差.

根據這種時間差特徵,我們還能構造了先後點擊標記特徵,用戶點擊商品無非三個位置,首次點擊,中間點擊和末次點擊,可以設想我們瀏覽電商網站,往往最後一次瀏覽購買可能性最大.

subset = [user_id, day]# 標記點擊位置特徵data[click_user_lab] = 0pos = data.duplicated(subset=subset, keep=False)data.loc[pos, click_user_lab] = 1pos = (~data.duplicated(subset=subset, keep=first)) & data.duplicated(subset=subset, keep=False)data.loc[pos, click_user_lab] = 2pos = (~data.duplicated(subset=subset, keep=last)) & data.duplicated(subset=subset, keep=False)data.loc[pos, click_user_lab] = 3# 構造時間差特徵subset = [user_id, day]temp = data.loc[:,[context_timestamp, user_id, day]].drop_duplicates(subset=subset, keep=first)temp.rename(columns={context_timestamp: u_day_diffTime_first}, inplace=True)data = pd.merge(data, temp, how=left, on=subset)data[u_day_diffTime_first] = data[context_timestamp] - data[u_day_diffTime_first]temp = data.loc[:,[context_timestamp, user_id, day]].drop_duplicates(subset=subset, keep=last)temp.rename(columns={context_timestamp: u_day_diffTime_last}, inplace=True)data = pd.merge(data, temp, how=left, on=subset)data[u_day_diffTime_last] = data[u_day_diffTime_last] - data[context_timestamp]data.loc[~data.duplicated(subset=subset, keep=False), [u_day_diffTime_first, u_day_diffTime_last]] = -1

上面代碼我們構造了同一天內,用戶點擊位置的標記,以及首次末次與當前時刻的時間差。

# 幫助方便的獲取相同樣本的首條data.duplicated(subset=subset, keep=first)

2.4分段特徵

分段特徵主要是對小時,score,rate進行構造,這類離散化後的特徵對異常數據有很強的魯棒性,刻畫出用戶習慣操作和偏好。

2.5概率特徵

概率特徵我們主要構造了轉化率特徵和比例特徵,其中轉化率特徵主要統計的是歷史轉化率,一是刻畫轉化率的變化情況,二是反應某個特徵大概轉化情況,特徵我們進行了單特徵和多特徵組合構造轉化率特徵。比例特徵用來刻畫某類用戶的偏好。

grouped_df = train.groupby(["day", "hour"])["is_trade"].aggregate("mean").reset_index()grouped_df = grouped_df.pivot(day, hour, is_trade)plt.figure(figsize=(12,6))sns.heatmap(grouped_df)plt.title("CVR of Day Vs Hour")plt.show()

2.6其他特徵

另外還有一些特徵有很大表現能力,在數據集中發現存在很多用戶在同一時間下進行多次點擊,我們統計其出現次數(same_time_expo_cnt)。用戶對相同商品點擊次數對於次數大於2的進行標記(user_large2)。深入業務我們對predict_category_property進行了挖局,由於用戶搜索某商品時顯示預測的類別屬性影響著用戶點擊情況,所以我們構造了預測類別屬性與用戶點擊商品類別屬性的交集個數與相似度。

def split_category(x,index): tmp = [] for a in x: tmp.append(a.split(:,1)[index]) return tmpdef split_property(x): tmp = [] for a in x: tmp.extend(a.split(,,a.count(","))) return tmp def check_same(a,b): set_same = a & b return list(set_same)def do_list_feat(data): # 逐步切分預測類別特徵 data[predict_category_list] = data[predict_category_property].apply(lambda x:x.split(;,x.count(;))) data[predict_category] = data[predict_category_list].apply(lambda x:split_category(x,0)) data[predict_category] = data[predict_category].apply(lambda x:set(x)) ## 切分廣告商品類別特徵 data[item_category] = data[item_category_list].apply(lambda x:x.split(;,x.count(;))) data[item_category] = data[item_category].apply(lambda x:set(x)) ## 篩出商品類別和預測類別的交集 data[same_category_list] = list(map(check_same,data[predict_category], data[item_category])) ## 計算預測類別和廣告商品類別的相似程度 data[same_category_num] = data[same_category_list].apply(lambda x:len(x)) data[predict_category_num] = data[predict_category].apply(lambda x:len(x)) data[same_category_prob] = round(data[same_category_num]/data[predict_category_num], 5) ## 逐步切分預測屬性特徵 data[predict_property] = data[predict_category_list].apply(lambda x:split_category(x,-1)) data[predict_property] = data[predict_property].apply(lambda x:split_property(x)) data[predict_property]= data[predict_property].apply(lambda x:set(x)) ## 切分廣告商品屬性特徵 data[item_property] = data[item_property_list].apply(lambda x:x.split(;,x.count(;))) data[item_property] = data[item_property].apply(lambda x:set(x)) ## 篩出商品屬類別和預測類別的交集 data[same_property_list] = list(map(check_same,data[predict_property], data[item_property])) ## 計算預測屬性和廣告商品屬性的相似程度 data[same_property_num] = data[same_property_list].apply(lambda x:len(x)) data[predict_property_num] = data[predict_property].apply(lambda x:len(x)) data[same_property_prob] = round(data[same_property_num]/data[predict_property_num], 5) return data

3.特徵選擇

首先推薦一篇有關特徵選擇的文章

zhuanlan.zhihu.com/p/32

可以想像經過上述構造特徵的過程能夠上到數百個特徵,但是我們又不可能對所有特徵進行訓練,因為裡面可能包含很多冗餘特徵,同時我們需要在少特徵的情況下達到多特徵的效果(奧卡姆剃刀原理)。最常用的方法是相關係數法以及模型輸出特徵重要性的方法。

# lgbm輸出特徵重要性lgb_clf = lgb.LGBMClassifier(objective=binary,num_leaves=35,max_depth=6,learning_rate=0.05,seed=2018, colsample_bytree=0.8,subsample=0.9,n_estimators=20000)lgb_model = lgb_clf.fit(train_x[features], train_x[target], eval_set=[(test_x[features], test_x[target])], early_stopping_rounds=200)lgb_predictors = [i for i in train_x[features].columns]lgb_feat_imp = pd.Series(lgb_model.feature_importances_, lgb_predictors).sort_values(ascending=False)lgb_feat_imp.to_csv(lgb_feat_imp.csv)

然而特徵重要性的結果並不是很可靠,也不能反應特徵相互組合對logloss的影響。故我們使用warpper的方式來進行特徵選擇。將前向搜索、後向搜索和隨機搜索進行組合篩選出最終特徵。

  • 前向搜索

前向搜索說白了就是每次增量地從剩餘未選中的特徵選出一個加入特徵集中,待達到閾值或者 n 時,從所有的 F 中選出錯誤率最小的。過程如下:

  1. 初始化特徵集 F 為空。
  2. 掃描 i1n

    如果第 i 個特徵不在 F 中,那麼特徵 iF 放在一起作為 F_i (即 F_i=Fcup{i} )。

    在只使用 F_i 中特徵的情況下,利用交叉驗證來得到 F_i 的錯誤率。
  3. 從上步中得到的 nF_i 中選出錯誤率最小的 F_i ,更新 FF_i
  4. 如果 F 中的特徵數達到了 n 或者預定的閾值(如果有的話),

    那麼輸出整個搜索過程中最好的 ;若沒達到,則轉到 2,繼續掃描。

  • 後向搜索

既然有增量加,那麼也會有增量減,後者稱為後向搜索。先將 F 設置為 {1,2,...,n} ,然後每次刪除一個特徵,並評價,直到達到閾值或者為空,然後選擇最佳的 F

我們在此基礎上還可以進行擴展,因為這種方式會導致局部最優,所以加入了模擬退火方式進行改善,不會因為新加入特徵不能降低logloss而捨棄它,而是對此特徵添加一個權重放入已選特徵集。當然還有更加精細的方法,不過會耗費大量時間,下面給出更為精細的方法。

github.com/duxuhao/Feat

4.模型訓練與評估

4.1模型選擇

此次比賽中我們嘗試了多種模型,其中包含lgb,xgboost,deepfm,wide&deep,ffm等模型。但最後只用了lgb和xgb

4.2交叉驗證

由於比賽每天只有一次評測機會,所以線下驗證由為重要,另外初賽複賽場景不同。初賽我們用最後一天作為驗證集,其餘作為訓練集。複賽需要預測特殊日期的轉化率,由於7號和之前差異比較大,所以用七號上半天進行訓練,選用最後倆個小時作為驗證集。

5.最終融合

在此階段我們嘗試了多種方案,普通加權,Stacking,加權平均結合sigmoid反函數

5.1普通加權

這種方法更適合模型結果差異性較大,線上效果好的權重較大些,線上效果好的權重相對大些,反之權重相對小些。

5.2Stacking

該圖是一個基模型得到P1和T1的過程,採用的是5折交叉驗證,所以循環了5次,拼接得到P1,測試集預測了5次,取平均得到T1。而這僅僅只是第二層輸入的一列/一個特徵,並不是整個訓練集。再分析作者的代碼也就很清楚了。也就是剛剛提到的兩層循環。

5.3加權平均結合sigmoid反函數

首先將各個模型的結果代入到sigmoid反函數中,然後得到其均值,對其結果使用sigmoid函數。相較於普通的加權平均,這種方法更適合於結果具有較小差異性的。

def f(x): res=1/(1+np.e**(-x)) return resdef f_ver(x): res=np.log(x/(1-x)) return res

經過隊內討論後,使用stacking結合sigmoid反函數的方法進行融合。用多個模型的結果作為新的特徵進行訓練,然後利用不同折數加參數,特徵,樣本(隨機數種子)擾動,再使用加權平均結合sigmoid反函數得到最終成績。

skf=list(StratifiedKFold(y_loc_train, n_folds=10, shuffle=True, random_state=1024))for i, (train, test) in enumerate(skf): print("Fold", i) model.fit(X_loc_train[train], y_loc_train[train], eval_metric=logloss,/ eval_set=[(X_loc_train[train], y_loc_train[train]), (X_loc_train[test], / y_loc_train[test])],early_stopping_rounds=100) test_pred= model.predict_proba(X_loc_test, num_iteration=-1)[:, 1] print(test mean:, test_pred.mean()) res[prob_%s % str(i)] = test_predfor i in range(10): res[predicted_score] += res[prob_%s % str(i)].apply(lambda x: math.log(x/(1-x)))res[predicted_score] = (res[predicted_score]/10).apply(lambda x: 1/(1+math.exp(-x)))

6.不足與總結

比賽中,最讓我頭疼的就是數據太大,沒有伺服器跑代碼,多虧一位外校的師兄借伺服器使用,不然很難達到這個成績,雖然只能12點後使用。

模型最後階段能用的不多,尤其是nn方面一直沒有達到滿意的效果,最後只能lgb和xgb融合。

當熱這次比賽也學到很多東西,嘗試了各種特徵工程,模型調參,模型融合的方法,從隊友那裡總能獲得新思路。相信下次比賽能夠學到更多知識。

參考文獻:

csie.ntu.edu.tw/~r01922

csie.ntu.edu.tw/~cjlin/

arxiv.org/abs/1701.0409

推薦閱讀:

第二十章 KNN演算法(上)
2017 CCF ADL會議總結
巧用指標拆解和連環替代,一招定位指標的最大影響因子
大數據:Hadoop入門
《利用Python進行數據分析·第2版》第6章 數據載入、存儲與文件格式

TAG:阿里天池 | 機器學習 | 數據挖掘 |