參考:

論文:《Practical Lessons from Predicting Clicks on Ads at Facebook》

1、背景

本文主要介紹Facebook提出的CTR預估模型LR(Logistic Regression)+GBDT。當時深度學習還沒有應用到計算廣告領域,Facebook提出利用GBDT的葉節點編號作為非線性特徵的表示,或者說是組合特徵的一種方式。

LR+GBDT相比於單純的LR或者GBDT帶來了較大的性能提升,論文中給出數據為3%,這在CTR預估領域確實非常不錯。除此之外,Facebook還在在線學習、Data freshness、學習速率、樹模型參數、特徵重要度等方面進行了探索。

相比於搜索廣告領域,根據用戶query來給出候選廣告,然後利用Rank模型對候選廣告進行排序。這些廣告要麼顯式要麼隱式的和用戶query相關聯。但是在Facebook這樣的社交場閤中,廣告並沒有和用戶query相關聯,但是用戶看到的廣告一定程度上反映了用戶的人口統計特性和興趣特性。基於這個原因,在Facebook上展示的廣告相比於搜索廣告中的要多一些。

在實際的生產環境中,Facebook做了多個分類器,並把他們級聯起來。但是論文中分析的是最後的那一個prediction模型。它直接給出最後的CTR概率。

在介紹這個模型之前,我們先來介紹兩個問題:

1)為什麼要使用集成的決策樹模型,而不是單棵的決策樹模型:一棵樹的表達能力很弱,不足以表達多個有區分性的特徵組合,多棵樹的表達能力更強一些。可以更好的發現有效的特徵和特徵組合

2)為什麼建樹採用GBDT而非RF:RF也是多棵樹,但從效果上有實踐證明不如GBDT。且GBDT前面的樹,特徵分裂主要體現對多數樣本有區分度的特徵;後面的樹,主要體現的是經過前N顆樹,殘差仍然較大的少數樣本。優先選用在整體上有區分度的特徵,再選用針對少數樣本有區分度的特徵,思路更加合理,這應該也是用GBDT的原因。

瞭解了為什麼要用GBDT,我們就來看看到底二者是怎麼融合的吧!

2、評估函數

論文目的是分析機器學習模型的影響因素,所以沒有使用實際利益相關的評測函數。而是主要從以下兩方面進行:

  • Normalized Cross-Entropy 或者叫做 Normalized Entropy, 縮寫NE
  • Calibration 校準

2.1、Normalized Cross-Entropy(NE)

NE的公式如下:

  • NE等於預測的log loss除以background CTR的熵
  • NE越小模型性能越好
  • 除以了background CTR的熵,使得NE對background CTR不敏感
  • p代表平均經驗CTR

2.2、 Calibration

  • Calibration校準是期待或預測的點擊數除以實際的點擊數。它是一個比例。
  • Calibration越接近1,模型性能越好

AUC也是一個非常不錯的評價指標,但是它有個問題。比如當我們的模型預測的CTR概率都偏高了2倍,我們可以通過Calibration校準,使用一個全局的0.5的係數來修正。修正之後NE也會提高,而AUC卻保持不變。

在實際工作中,我們希望得到的是儘可能準確的預測每個廣告被點擊的概率,而不是僅僅得到相對的概率排序。所以AUC不如上面的NE、Calibration合適。

3、GBDT和LR的融合方案

GBDT和LR的融合方案,FaceBook的paper中有個例子:

圖中共有兩棵樹,x為一條輸入樣本,遍歷兩棵樹後,x樣本分別落到兩顆樹的葉子節點上,每個葉子節點對應LR一維特徵,那麼通過遍歷樹,就得到了該樣本對應的所有LR特徵。構造的新特徵向量是取值0/1的。舉例來說:上圖有兩棵樹,左樹有三個葉子節點,右樹有兩個葉子節點,最終的特徵即為五維的向量。對於輸入x,假設他落在左樹第一個節點,編碼[1,0,0],落在右樹第二個節點則編碼[0,1],所以整體的編碼為[1,0,0,0,1],這類編碼作為特徵,輸入到LR中進行分類。

4、代碼

訓練GBDT模型

本文使用lightgbm包來訓練我們的GBDT模型,訓練共100棵樹,每棵樹有64個葉子結點。

import lightgbm as lgb

import pandas as pd
import numpy as np

from sklearn.metrics import mean_squared_error
from sklearn.linear_model import LogisticRegression

print(Load data...)
df_train = pd.read_csv(data/train.csv)
df_test = pd.read_csv(data/test.csv)

NUMERIC_COLS = [
"ps_reg_01", "ps_reg_02", "ps_reg_03",
"ps_car_12", "ps_car_13", "ps_car_14", "ps_car_15",
]

print(df_test.head(10))

y_train = df_train[target] # training label
y_test = df_test[target] # testing label
X_train = df_train[NUMERIC_COLS] # training dataset
X_test = df_test[NUMERIC_COLS] # testing dataset

# create dataset for lightgbm
lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)

params = {
task: train,
boosting_type: gbdt,
objective: binary,
metric: {binary_logloss},
num_leaves: 64,
num_trees: 100,
learning_rate: 0.01,
feature_fraction: 0.9,
bagging_fraction: 0.8,
bagging_freq: 5,
verbose: 0
}

# number of leaves,will be used in feature transformation
num_leaf = 64

print(Start training...)
# train
gbm = lgb.train(params,
lgb_train,
num_boost_round=100,
valid_sets=lgb_train)

print(Save model...)
# save model to file
gbm.save_model(model.txt)

print(Start predicting...)
# predict and get data on leaves, training data

# 特徵轉換

# 在訓練得到100棵樹之後,我們需要得到的不是GBDT的預測結果,而是每一條訓練數據落
#在了每棵樹的哪個葉子結點上,因此需要使用下面的語句:

y_pred = gbm.predict(X_train, pred_leaf=True)# 8001*100 查看每個樣本落在每棵樹的第幾個葉子上

print(np.array(y_pred).shape)
print(y_pred[:10])

#然後我們需要將每棵樹的特徵進行one-hot處理,如前面所說,假設第一棵樹落在43號葉子結點上,
#那我們需要建立一個64維的向量,除43維之外全部都是0。
#因此用於LR訓練的特徵維數共num_trees * num_leaves。
print(Writing transformed training data)
transformed_training_matrix = np.zeros([len(y_pred), len(y_pred[0]) * num_leaf],
dtype=np.int64) # N * num_tress * num_leafs 8001*(100*64) 對每棵樹的特徵進行onehot處理,所以在y_pred的基礎上每一列擴展成64列(每棵樹有64個葉子)
for i in range(0, len(y_pred)):
temp = np.arange(len(y_pred[0])) * num_leaf + np.array(y_pred[i])#計算onehot在100*64列當中的位置 arange(100)*64 + y_pred[i]
transformed_training_matrix[i][temp] += 1

# 對於測試集也要進行同樣的處理
y_pred = gbm.predict(X_test, pred_leaf=True)
print(Writing transformed testing data)
transformed_testing_matrix = np.zeros([len(y_pred), len(y_pred[0]) * num_leaf], dtype=np.int64)
for i in range(0, len(y_pred)):
temp = np.arange(len(y_pred[0])) * num_leaf + np.array(y_pred[i])
transformed_testing_matrix[i][temp] += 1

#然後我們可以用轉換後的訓練集特徵和label訓練我們的LR模型,並對測試集進行測試:
lm = LogisticRegression(penalty=l2,C=0.05) # logestic model construction L2懲罰 C為正則化係數λ的倒數,通常默認為1
lm.fit(transformed_training_matrix,y_train) # fitting the data
#我們這裡得到的不是簡單的類別,而是每個類別的概率。
y_pred_test = lm.predict_proba(transformed_testing_matrix) # Give the probabilty on each label

print(y_pred_test)

效果評價

在Facebook的paper中,模型使用NE(Normalized Cross-Entropy),進行評價,計算公式如下:

NE = (-1) / len(y_pred_test) * sum(((1+y_test)/2 * np.log(y_pred_test[:,1]) + (1-y_test)/2 * np.log(1 - y_pred_test[:,1])))
print("Normalized Cross Entropy " + str(NE))

5、總結

現在的GBDT和LR的融合方案真的適合現在的大多數業務數據麼?現在的業務數據是什麼?是大量離散特徵導致的高維度離散數據。而樹模型對這樣的離散特徵,是不能很好處理的,要說為什麼,因為這容易導致過擬合。下面的一段話來自知乎:

用蓋坤的話說,GBDT只是對歷史的一個記憶罷了,沒有推廣性,或者說泛化能力。 但這並不是說對於大規模的離散特徵,GBDT和LR的方案不再適用,感興趣的話大家可以看一下參考文獻2和3,這裡就不再介紹了。 剛才提到了阿里的蓋坤大神,他的團隊在2017年提出了兩個重要的用於CTR預估的模型,MLR和DIN,之後的系列中,我們會講解這兩種模型的理論和實戰!歡迎大家繼續關注!參考:推薦系統遇上深度學習(十)--GBDT+LR融合方案實戰?

www.jianshu.com
圖標

推薦閱讀:
相關文章