一、特征組合
廣告點擊率預估、推薦系統等業務場景涉及到的特征通常都是高維、稀疏的,並且樣本量巨大,模型通常采用速度較快的LR,然而LR算法學習能力有限,因此要想得到好的預測結果,需要前期做大量的特征工程,工程師通常需要花費大量精力去篩選特征、做特征與處理,即便這樣,最終的效果提升可能非常有限。
樹模型算法天然具有特征篩選的功能,其通過熵、信息增益、基尼指數等方法,在每次分裂時選取最優的分裂節點。因此,當樹模型訓練完畢后,從樹的根節點到葉子節點都是篩選出來的局部最優特征。
於是很自然的想法就是,通過樹模型的特征篩選功能篩選一些局部最優的特征組合,然后將組合特征輸入到LR算法,這樣就可以提升LR算法的擬合能力。
二、樹模型+LR
2014年Facebook 發表論文 Practical Lessons from Predicting Clicks on Ads at Facebook,這篇文章介紹了通過GBDT構造組合特征,然后通過LR對組合特征進行訓練,從而達到對預測樣本進行分類的目的。隨后在Kaggle競賽中該方法得到驗證 ,GBDT與LR也引起了人們的廣泛關注。
圖1 GBDT+LR組合方法圖
圖1所示為FaceBook的paper中GBDT與LR融合的方法,圖中x為輸入特征,經過兩棵樹后走到葉子節點。圖中左邊的樹有三個葉子節點,右邊的樹有兩個葉子節點,因此對於輸入x,假設其在左子樹落在第一個節點,在右子樹落在第二個節點,那么在左子樹的one-hot編碼為[1,0,0],在右子樹的one-hot編碼為[0,1],最終的特征為兩個one-hot編碼的組合[1,0,0,0,1]。最后將轉化后的特征輸入到線性分類器,即可訓練的到一個基於組合特征的線性模型。
在進行特征轉化的時候,GBDT模型中所包含的樹的棵樹即為后面組合特征的數量,每一個組合特征的向量長度不等,該長度取決於所在樹的葉子節點數量。舉例來說,假設訓練得到100棵樹之后,就可以得到100個組合特征。
三、XGBoost+LR
XGBoost是一個高效的梯度提升樹的實現框架,並且廣泛用於工業界及各種比賽。XGBoost提供了一個借口,如圖2所示,其中輸入X為樣本特征,ntree_limit為用於預測的樹的棵樹。其返回值為[n_sample, n_trees]的矩陣,每一行代表一個樣本,每一列代表一棵樹,矩陣的值代表相應樣本在相應樹的所在節點的編號,舉例來說,假設模型共有四棵樹,返回值第一列為[1,0,3,2],則“1”代表該樣本在第一顆樹的第一個葉子節點,在第三棵樹的的第三個葉子節點,在第四棵樹的第二個葉子節點。
圖2 XGBoost apply函數借口
代碼:
import numpy as np import pandas as pd from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import OneHotEncoder from sklearn.model_selection import train_test_split from sklearn import metrics import logging import xgboost as xgb import time from sklearn.datasets import load_iris logging.basicConfig(format='%(asctime)s : %(levelname)s: %(message)s', level=logging.INFO) def XGBoost_LR(df_train): X_train = df_train.values[:, :-1] y_train = df_train.values[:, -1] X_train_xgb, X_train_lr, y_train_xgb, y_train_lr = train_test_split(X_train, y_train, test_size=0.75) XGB = xgb.XGBClassifier(n_estimators = 6) XGB.fit(X_train_xgb, y_train_xgb) logging.info("訓練集特征數據為: \n%s" % X_train_xgb) logging.info("訓練集標簽數據為: \n%s" % y_train_xgb) logging.info("轉化為葉子節點后的特征為:\n%s" % XGB.apply(X_train_xgb, ntree_limit=0)) XGB_enc = OneHotEncoder() XGB_enc.fit(XGB.apply(X_train_xgb, ntree_limit=0)) # ntree_limit 預測時使用的樹的數量 XGB_LR = LogisticRegression() XGB_LR.fit(XGB_enc.transform(XGB.apply(X_train_lr)), y_train_lr.astype('int')) X_predict = XGB_LR.predict_proba(XGB_enc.transform(XGB.apply(X_train)))[:, 1] AUC_train = metrics.roc_auc_score(y_train.astype('int'), X_predict) logging.info("AUC of train data is %f" % AUC_train) if __name__ == "__main__": start = time.clock() #加載數據集 iris=load_iris() df_data = pd.concat([pd.DataFrame(iris.data), pd.DataFrame(iris.target)], axis=1) df_data.columns = ["x1","x2", "x3","x4", "y"] df_new = df_data[df_data["y"]<2] logging.info("Train model begine...") XGBoost_LR(df_new) end = time.clock() logging.info("Program over, time cost is %s" % (end-start))
這里使用iris數據集,特征及標簽如下圖所示,這里選取兩類數據用於訓練模型,因此標簽只有0和1。
圖3 訓練樣本示例
調用apply函數后,打印出轉化為葉子節點的特征:
圖4 apply函數結果示例
四、項目案例
這里是我在一個營銷項目中的案例,背景是重疾險潛客識別,利用客戶的多維度特征預測客戶是否會購買重疾險。項目中嘗試了多種算法,包括XGBoost、XGBoost+LR、GBDT、RF+LR,不同算法在數據的表現如下圖所示。圖中橫坐標代表樣本的得分分布,共有10個分段區間,縱坐標代表正樣本(已購重疾險的客戶)在相應區間的數量。該項目本身對樣本的區分度並不高,但是從圖中可以看出,使用XGBoost+LR模型的結果表現明顯優於其他三種方法,其在得分較高的區間的樣本數量較其他方法多,例如[0.9,1.0]這個區間;而在得分較低的區間的樣本數量較少,例如[0.0,0.1]這個區間。直觀上看,XGBoost+LR具有將正樣本向得分高的方向“移動”的作用。
圖4 不同算法的表現
五、寫在最后
我個人在工作中的感受是樹模型+LR的方法最適用於高緯稀疏數據。樹模型對這樣的稀疏數據容易導致過擬合,舉例來說,假設預測用戶是否會購買蘋果手機,那么“近一周瀏覽蘋果手機的次數”這個特征對於模型的貢獻度極高,大量的正樣本都有瀏覽或者瀏覽次數很多,而未購買的用戶在這個特征上的表現則可能數值極小甚至為0。此時,樹模型只需要將該特征作為一個分叉就可以得到很好的效果,然而當上線使用時發現可能效果並不是那么好,因為有些客戶可能在這個特征表現不明顯,但依然購買了商品。
原因是什么呢?因為樹模型的正則項是樹的深度和葉子節點的數量,上述情況樹模型的深度非常淺,葉子節點相應少,因此樹模型認為自身很完美,但其實已經過擬合了。對於這樣的特征,如果輸入到LR這樣的線性模型,正則項會對貢獻度高的特征的權重進行懲罰,以此來避免過擬合,但是LR的學習能力較差,可能難以達到期望的效果。
綜上,可以結合樹模型的特征組合能力和LR的正則項來處理高維稀疏數據,既可以提高模型的擬合能力,又可以防止過擬合。