1. 程式人生 > 實用技巧 >推薦系統實踐 0x0d GBDT+LR

推薦系統實踐 0x0d GBDT+LR

前一篇文章我們介紹了LR->FM->FFM的整個演化過程,我們也知道,效果最好的FFM,它的計算複雜度已經達到了令人髮指的\(n^2k\)。其實就是這樣,希望提高特徵交叉的維度來彌補稀疏特徵,不可避免的帶來組合爆炸和計算複雜度過高的問題。這一篇,我們介紹一下Facebook提出的GBDT+LR的組合來解決特徵組合和篩選的問題。

結構

整體的思路就是用GBDT構建特徵工程,使用LR預估CTR這兩步。由於這兩步是獨立的,所以不存在將LR的梯度回傳到GBDT這類複雜問題。關於GBDT,就需要另外開一篇文章來講解,這一部分網上也有很多小夥伴寫了原理和實現,這裡我推薦這篇文章,比較直觀的介紹了GBDT的原理以及相應的程式碼實現,這裡也簡單介紹一下。

下圖是Facebook論文中的模型示意圖:

GBDT

GBDT是由多棵迴歸樹組成的森林,後一棵樹以前面森林的結果與真實結果的殘差做為擬合目標。每棵樹的生成都是一棵標準迴歸樹的生成過程,因此迴歸樹的節點分裂是自然的特徵選擇過程,多層節點的結構對特徵進行了有效的組合,也高效的解決了特徵選擇以及特徵組合的過程。通過GBDT可以完成從原始特徵向量到離散型特徵向量的轉化。一般來說,訓練樣本進入某個子樹之後,根據規則落入某個葉子節點將被設為1,其他葉子節點將被設為0.

舉例來說,一棵典型的GBDT如下圖所示:

這顆GBDT由兩棵子樹構成,第一棵子樹有三個葉子節點,第二棵子樹有兩個葉子節點。那麼圖中的第一棵子樹的特徵向量為[0,1,0]

,第二棵子樹特徵向量為[0,1],最終連線所有的特徵向量得到的最終特徵向量為[0,1,0,0,1]

決策樹的深度決定了特徵交叉的階數。這種特徵組合能力要比FM系列的模型強得多,但是由於轉換成離散特徵向量導致丟失了大量特徵的數值資訊。

LR就不再介紹了,在上一篇文章中已經介紹過。GBDT的特徵工程之後,資料不僅變得稀疏,而且由於弱分類器個數,葉子結點個數的影響,可能會導致新的訓練資料特徵維度過大的問題,因此,在LR這一層中,可使用正則化來減少過擬合的風險,在Facebook的論文中採用的是L1正則化。

GBDT+LR 程式碼

這裡我們用的是Kaggle比賽資料集,Porto Seguro’s Safe Driver Prediction

,也就是預測汽車保險保單持有人提出索賠的可能性。

以下程式碼就是我們用了lightgbm+LR的形式進行預測。

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('../dataset/DriverPrediction/train.csv')
df_test = pd.read_csv('../dataset/DriverPrediction/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)
lgb_eval = lgb.Dataset(X_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
y_pred = gbm.predict(X_train, pred_leaf=True)

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

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

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

lm = LogisticRegression(penalty='l2', C=0.05)  # logestic model construction
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)

# 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))

小結

GBDT+LR的組合模型,意味著特徵工程可以完全交給一個獨立的模型來完成,模型的輸入可以是原始的特徵向量,而不必投入過多的人工篩選和模型設計能力,實現端到端訓練。

參考

機器學習-一文理解GBDT的原理-20171001
GBDT分類的原理及Python實現
GBDT+LR演算法解析及Python實現
推薦系統遇上深度學習(十)--GBDT+LR融合方案實戰