公衆號
更多精彩內容請移步公衆號:推薦算法工程師
公衆號後臺回覆”進羣“,加入學習交流羣,和小夥伴們一起學習,一起進步~
前言
介紹GBDT+LR背景
點擊率預估模型涉及的訓練樣本一般是上億級別,樣本量大,模型常採用速度較快的LR(logistic regression)。LR雖然是線性模型線性模型,但是在業界廣泛使用。爲什麼呢?雖然模型本身表達能力差,但是可以通過特徵工程不斷減少問題的非線性結構。又由於模型計算複雜度低,可以吞吐超大規模的特徵空間和樣本集合,這樣就爲效果優化打開了空間。同時,他可以學習id化特徵,從而減少了特徵工程的環節,可以提高特徵的實時性[1]。但正因爲LR學習能力有限,此時特徵工程尤其重要。
在深度學習大行其道之前,一般採用人工或一些一些傳統的方法,人工成本高就不說了,傳統的方法像FM,FFM,只能挖掘兩個特徵間的特徵交互關係,作用有限。GBDT是解決這個問題的一種不錯方案。回顧我們上篇文章所講的,GBDT有以下優點:
- 弱分類器要求不高,樹的層數一般較小,小數據可用,擴展到大數據也能方便處理。
- 需要更少的特徵工程,比如不用做特徵標準化
- 可以處理字段缺失的數據
- 可以自動組合多個特徵並且不用關心特徵間是否依賴,可以自動處理特徵間的交互,不用擔心數據是否線性可分
- 可以靈活處理多種類型的異構數據,這是決策樹的天然特性
- 損失函數選擇靈活,可以選擇具有魯棒性的損失函數,對異常值有一定的魯棒性
顯而易見,GBDT對於處理特徵有很多優點。而LR雖然是線性模型,但是Facebook探索出一種將GBDT和LR結合的方案,來預測廣告的點擊通過率(Click Trough Rate,CTR)的預測問題。結果顯示融合方案比單個的GBDT或LR的性能高3%左右。
論文地址:
http://quinonero.net/Publications/predicting-clicks-facebook.pdf
代碼地址:
https://github.com/wyl6/Recommender-Systems-Samples/tree/master/RecSys%20Traditional/DecisionTree/LRGBDT
GBDT+LR模型
首先要說明的是,對點擊通過問題,要麼點擊要麼不點擊,因此y∈{1,_1},是個二分類的問題。因此LR+GBDT中的GBDT是上篇文章中所說的L2-Treeboost方案,上篇文章介紹過了這裏不多說。
那麼,GBDT與LR是如何結合的呢?看懂論文上的一張圖就夠了:
如上圖所示,這個強學習器由兩個弱學習器前向加和組成。假設x輸入GBDT後,落到左邊迴歸樹的第一個節點,落到右邊迴歸樹的第一個節點。則GBDT對樣本x的特徵進行工程處理得到的轉換特徵,就可以表示爲:(1,0,0)串聯(1,0)==》(1,0,0,1,0)。然後將特徵輸入LR即可進行分類。
反思與總結
GBDT侷限性
GBDT+LR這個模型還是有一些侷限性的。首先看GBDT的缺點,上篇文章中我們提到過:GBDT有一些缺點:
- GBDT在高維稀疏的數據集上,表現不如支持向量機或者神經網絡[2]。
- 訓練過程基學習器需要串行訓練,只能通過局部並行提高速度。
[3]中凱菜大佬指出,對於高維稀疏的特徵,GBDT容易過擬合,表現不理想,甚至LR都比GBDT好,並給了一個例子:
對於高維稀疏的特徵,GBDT容易過擬合,表現不理想,甚至LR都比GBDT好,[3]中凱菜大佬給了一個很好的例子:
GBDT+LR缺點
關於GBDT+LR的缺點,[4]中屈偉大佬給出了一些看法:
我們分析模型的優缺點,是爲了從中借鑑,也是爲了更好地使用模型。沒有哪個模型可以解決所有問題,不同模型都有自己的優點和缺點。GBDT+LR這種組合,有侷限性,但也提供了一種不錯的思路。實際業務中還是根據不同情況選擇最合適的模型,揚長補短。
幾十行代碼的小例子
我們實戰一個GBDT對Iris數據集做分類的小例子。改自[4]。參數的使用可以參考官網說明:https://github.com/microsoft/LightGBM/blob/master/docs/Parameters.rst。
首先,加載數據:
from sklearn.datasets import load_iris
import numpy as np
import lightgbm as lgb
import pandas as pd
import warnings
warnings.filterwarnings('ignore')
## build data
iris = pd.DataFrame(load_iris().data)
iris.columns = ['SepalLengthCm','SepalWidthCm','PetalLengthCm','PetalWidthCm']
iris['Species'] = load_iris().target%2
由於Iris的labels有三類setosa,versicolor和virginica,而GBDT+LR做CTR是做二分類,因此爲了一致,第四行將第三類和第一類合爲一類。然後做訓練和測試數據劃分:
## train test split
train=iris[0:130]
test=iris[130:]
X_train=train.filter(items=['SepalLengthCm','SepalWidthCm','PetalLengthCm','PetalWidthCm'])
X_test=test.filter(items=['SepalLengthCm','SepalWidthCm','PetalLengthCm','PetalWidthCm'])
y_train=train[[train.Species.name]]
y_test=test[[test.Species.name]]
樣本總共有150個,每類50個,簡單劃分下得了。然後構建和訓練GBDT模型:
## build lgb model
lgb_train = lgb.Dataset(X_train.as_matrix(),
y_train.values.reshape(y_train.shape[0],))
lgb_eval = lgb.Dataset(X_test.as_matrix(),
y_test.values.reshape(y_test.shape[0],),
reference=lgb_train)
params = {
'task': 'train',
'boosting_type': 'gbdt',
'objective': 'binary',
'metric': {'binary_logloss'},
'num_leaves': 16,
'num_trees': 10,
'learning_rate': 0.1,
'feature_fraction': 0.9,
'bagging_fraction': 0.8,
'bagging_freq': 5,
'verbose': 0
}
gbm = lgb.train(params=params,
train_set=lgb_train,
num_boost_round=3000,
valid_sets=None)
使用GBDT做二分類,因此這裏我們指定’num_trees’參數.而如果做k(k>2)分類,就要使用’num_class’參數,此時num_trees=num_class*k。下面GBDT輸出轉換特徵,構建LR訓練和測試數據,注意細節:
# build train matrix
num_leaf = 16
y_pred = gbm.predict(X_train,raw_score=False,pred_leaf=True)
transformed_training_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_training_matrix[i][temp] += 1
由於我們需要知道每棵樹中輸出到了那個點上,因此設置參數pred_leaf=True
,打印一下看看print(y_pred[0], y_pred.shape)
:
[0 0 0 0 0 0 0 0 0 0 0 4 0 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 3 0
3 0 0 0 0 0 0 0 3 4 3 3 3 0 0 0 0 2 2 3 3 2 3 3 3 1 1 3 3 2 3 1 3 0 3 3 3
0 2 3 2 3 2 2 2 2 1 1 3 3 0 1 1 3 2 3 0 3 2 0 1 3 3] (130, 100)
如果想知道強學習器的預測值y,則需要設置raw_score=True',打印一下看看
print(y_pred[0], y_pred.shape)`:
-9.0297726841214 (130,)
預測數據同理:
# build test matrix
y_pred = gbm.predict(X_test,pred_leaf=True)
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
然後輸入到邏輯迴歸中分類:
# logistic regression
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
label_train = y_train.values.reshape(y_train.shape[0],)
label_test = y_test.values.reshape(y_test.shape[0],)
c = np.array([1,0.5,0.1,0.05,0.01,0.005,0.001])
for t in range(0,len(c)):
lm = LogisticRegression(penalty='l2',C=c[t]) # logestic model construction
lm.fit(transformed_training_matrix,y_train.values.reshape(y_train.shape[0],)) # fitting the data
y_pred_est = lm.predict(transformed_testing_matrix) # Give the probabilty on each label
acc =accuracy_score(label_test, y_pred_est)
print('Acc of test', acc)
100行代碼不到,有興趣可以調調代碼:
https://github.com/wyl6/Recommender-Systems-Samples/tree/master/RecSys%20Traditional/DecisionTree/LRGBDT
參考
[1] https://www.zhihu.com/question/62109451
[2] http://f.dataguru.cn/thread-935853-1-1.html
[3] https://www.zhihu.com/question/35821566
[4] https://github.com/NearXdu/gbdt_lr/blob/master/gbdt_lr.py