傳統推薦算法(六)Facebook的GBDT+LR模型(2)理論淺析+實戰

公衆號

更多精彩內容請移步公衆號:推薦算法工程師
公衆號後臺回覆”進羣“,加入學習交流羣,和小夥伴們一起學習,一起進步~

前言

介紹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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章