LightGBM算法總結 2018年08月21日 18:39:47 Ghost_Hzp 閱讀數:2360 版權聲明:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/weixin_39807102/article/details/81912566 1 LightGBM原理 1.1 GBDT和 LightGBM對比 1.2 LightGBM 的動機 1.3 Xgboost 原理 1.4 LightGBM 優化 1.4.1 Histogram 算法 1.4.2 帶深度限制的 Leaf-wise 的葉子生長策略 1.4.3 直方圖加速 1.4.4 直接支持類別特征 1.4.5 LightGBM並行優化 1.5 其他注意 2 lightGBM代碼 2.1 基礎代碼 2.2 模板代碼 2.2.1 二分類 2.2.2 多分類 2.3 lightGBM 和 xgboost 的代碼比較 2.3.1 划分訓練集測試集 2.3.2 設置參數 2.3.3 模型訓練 2.3.4 模型執行時間 2.3.5 模型測試 2.3.6 分類轉換 2.3.7 准確率計算 2.3.8 roc_auc_score計算 3 lightGBM調參 3.1 參數 3.1 控制參數 3.2 核心參數 3.3 IO參數 3.2 調參 4 lightGBM案例 4.1 回歸案例 4.1.1 代碼 4.1.2 運行結果 4.2 [ICC競賽] 精品旅行服務成單預測 4.2.1 業務需求 4.2.2 數據表格 4.2.3 lightGBM模型 5 lightGBM的坑 5.1 設置提前停止 5.2 自動處理類別特征 5.3 自動處理缺失值 1 LightGBM原理 1.1 GBDT和 LightGBM對比 GBDT (Gradient Boosting Decision Tree) 是機器學習中一個長盛不衰的模型,其主要思想是利用弱分類器(決策樹)迭代訓練以得到最優模型,該模型具有訓練效果好、不易過擬合等優點。GBDT 在工業界應用廣泛,通常被用於點擊率預測,搜索排序等任務。GBDT 也是各種數據挖掘競賽的致命武器,據統計 Kaggle 上的比賽有一半以上的冠軍方案都是基於 GBDT。 LightGBM (Light Gradient Boosting Machine)是一個實現 GBDT 算法的框架,支持高效率的並行訓練,並且具有以下優點: 更快的訓練速度 更低的內存消耗 更好的准確率 分布式支持,可以快速處理海量數據 如下圖,在 Higgs 數據集上 LightGBM 比 XGBoost 快將近 10 倍,內存占用率大約為 XGBoost 的1/6,並且准確率也有提升。 img 1.2 LightGBM 的動機 常用的機器學習算法,例如神經網絡等算法,都可以以 mini-batch 的方式訓練,訓練數據的大小不會受到內存限制。 而 GBDT 在每一次迭代的時候,都需要遍歷整個訓練數據多次。如果把整個訓練數據裝進內存則會限制訓練數據的大小;如果不裝進內存,反復地讀寫訓練數據又會消耗非常大的時間。尤其面對工業級海量的數據,普通的 GBDT 算法是不能滿足其需求的。 LightGBM 提出的主要原因就是為了解決 GBDT 在海量數據遇到的問題,讓 GBDT 可以更好更快地用於工業實踐。 1 1.3 Xgboost 原理 目前已有的 GBDT 工具基本都是基於預排序的方法(pre-sorted)的決策樹算法(如 xgboost)。這種構建決策樹的算法基本思想是: 首先,對所有特征都按照特征的數值進行預排序。 其次,在遍歷分割點的時候用O(#data)的代價找到一個特征上的最好分割點。 最后,找到一個特征的分割點后,將數據分裂成左右子節點。 這樣的預排序算法的優點是:能精確地找到分割點。 缺點也很明顯: 首先,空間消耗大。這樣的算法需要保存數據的特征值,還保存了特征排序的結果(例如排序后的索引,為了后續快速的計算分割點),這里需要消耗訓練數據兩倍的內存。 其次,時間上也有較大的開銷,在遍歷每一個分割點的時候,都需要進行分裂增益的計算,消耗的代價大。 最后,對 cache 優化不友好。在預排序后,特征對梯度的訪問是一種隨機訪問,並且不同的特征訪問的順序不一樣,無法對 cache 進行優化。同時,在每一層長樹的時候,需要隨機訪問一個行索引到葉子索引的數組,並且不同特征訪問的順序也不一樣,也會造成較大的 cache miss。 1.4 LightGBM 優化 LightGBM 優化部分包含以下: 基於 Histogram 的決策樹算法 帶深度限制的 Leaf-wise 的葉子生長策略 直方圖做差加速 直接支持類別特征(Categorical Feature) Cache 命中率優化 基於直方圖的稀疏特征優化 多線程優化。 下面主要介紹 Histogram 算法、帶深度限制的 Leaf-wise 的葉子生長策略和直方圖做差加速。 1.4.1 Histogram 算法 直方圖算法的基本思想是先把連續的浮點特征值離散化成k個整數,同時構造一個寬度為k的直方圖。在遍歷數據的時候,根據離散化后的值作為索引在直方圖中累積統計量,當遍歷一次數據后,直方圖累積了需要的統計量,然后根據直方圖的離散值,遍歷尋找最優的分割點。 這里寫圖片描述 使用直方圖算法有很多優點。首先,最明顯就是內存消耗的降低,直方圖算法不僅不需要額外存儲預排序的結果,而且可以只保存特征離散化后的值,而這個值一般用 8 位整型存儲就足夠了,內存消耗可以降低為原來的1/8。 img 然后在計算上的代價也大幅降低,預排序算法每遍歷一個特征值就需要計算一次分裂的增益,而直方圖算法只需要計算k次(k可以認為是常數),時間復雜度從O(#data*#feature)優化到O(k*#features)。 當然,Histogram 算法並不是完美的。由於特征被離散化后,找到的並不是很精確的分割點,所以會對結果產生影響。但在不同的數據集上的結果表明,離散化的分割點對最終的精度影響並不是很大,甚至有時候會更好一點。 原因是決策樹本來就是弱模型,分割點是不是精確並不是太重要;較粗的分割點也有正則化的效果,可以有效地防止過擬合;即使單棵樹的訓練誤差比精確分割的算法稍大,但在梯度提升(Gradient Boosting)的框架下沒有太大的影響。 1.4.2 帶深度限制的 Leaf-wise 的葉子生長策略 在 Histogram 算法之上,LightGBM 進行進一步的優化。首先它拋棄了大多數 GBDT 工具使用的按層生長 (level-wise) 的決策樹生長策略,而使用了帶有深度限制的按葉子生長 (leaf-wise) 算法。Level-wise 過一次數據可以同時分裂同一層的葉子,容易進行多線程優化,也好控制模型復雜度,不容易過擬合。但實際上 Level-wise 是一種低效的算法,因為它不加區分的對待同一層的葉子,帶來了很多沒必要的開銷,因為實際上很多葉子的分裂增益較低,沒必要進行搜索和分裂。 img Leaf-wise 則是一種更為高效的策略,每次從當前所有葉子中,找到分裂增益最大的一個葉子,然后分裂,如此循環。因此同 Level-wise 相比,在分裂次數相同的情況下,Leaf-wise 可以降低更多的誤差,得到更好的精度。Leaf-wise 的缺點是可能會長出比較深的決策樹,產生過擬合。因此 LightGBM 在 Leaf-wise 之上增加了一個最大深度的限制,在保證高效率的同時防止過擬合。 這里寫圖片描述 1.4.3 直方圖加速 LightGBM 另一個優化是 Histogram(直方圖)做差加速。一個容易觀察到的現象:一個葉子的直方圖可以由它的父親節點的直方圖與它兄弟的直方圖做差得到。通常構造直方圖,需要遍歷該葉子上的所有數據,但直方圖做差僅需遍歷直方圖的k個桶。利用這個方法,LightGBM 可以在構造一個葉子的直方圖后,可以用非常微小的代價得到它兄弟葉子的直方圖,在速度上可以提升一倍。 img 1.4.4 直接支持類別特征 實際上大多數機器學習工具都無法直接支持類別特征,一般需要把類別特征,轉化到多維的0/1 特征,降低了空間和時間的效率。而類別特征的使用是在實踐中很常用的。基於這個考慮,LightGBM 優化了對類別特征的支持,可以直接輸入類別特征,不需要額外的0/1 展開。並在決策樹算法上增加了類別特征的決策規則。在 Expo 數據集上的實驗,相比0/1 展開的方法,訓練速度可以加速 8 倍,並且精度一致。據我們所知,LightGBM 是第一個直接支持類別特征的 GBDT 工具。 LightGBM 的單機版本還有很多其他細節上的優化,比如 cache 訪問優化,多線程優化,稀疏特征優化等等。優化匯總如下: 類目 預排序算法預排序算法 lightGBM 內存占用 2*#feature*#data*4Bytes *#feature*#data*1Bytes 統計量累積 O(*#feature*#data) O(*#feature*#data) 分割增益計算 O(*#feature*#data) O(*#feature*#k) 直方圖做差 N/A 加速一倍 直接支持類別特征 N/A 在Expo數據上加速8倍 Cache優化 N/A 在Higgs數據上加速40% 帶深度限制的Leaf-wise的決策樹算法 N/A 精度更 1.4.5 LightGBM並行優化 LightGBM 還具有支持高效並行的優點。LightGBM 原生支持並行學習,目前支持特征並行和數據並行的兩種。 特征並行的主要思想是在不同機器在不同的特征集合上分別尋找最優的分割點,然后在機器間同步最優的分割點。 數據並行則是讓不同的機器先在本地構造直方圖,然后進行全局的合並,最后在合並的直方圖上面尋找最優分割點。 LightGBM 針對這兩種並行方法都做了優化: 在特征並行算法中,通過在本地保存全部數據避免對數據切分結果的通信; 在數據並行中使用分散規約 (Reduce scatter) 把直方圖合並的任務分攤到不同的機器,降低通信和計算,並利用直方圖做差,進一步減少了一半的通信量。基於投票的數據並行則進一步優化數據並行中的通信代價,使通信代價變成常數級別。在數據量很大的時候,使用投票並行可以得到非常好的加速效果。 img img 這里寫圖片描述 1.5 其他注意 當生長相同的葉子時,Leaf-wise 比 level-wise 減少更多的損失。 高速,高效處理大數據,運行時需要更低的內存,支持 GPU 不要在少量數據上使用,會過擬合,建議 10,000+ 行記錄時使用。 2 lightGBM代碼 2.1 基礎代碼 # 01. train set and test set 划分訓練集和測試集 train_data = lgb.Dataset(dtrain[predictors],label=dtrain[target],feature_name=list(dtrain[predictors].columns), categorical_feature=dummies) test_data = lgb.Dataset(dtest[predictors],label=dtest[target],feature_name=list(dtest[predictors].columns), categorical_feature=dummies) # 02. parameters 參數設置 param = { 'max_depth':6, 'num_leaves':64, 'learning_rate':0.03, 'scale_pos_weight':1, 'num_threads':40, 'objective':'binary', 'bagging_fraction':0.7, 'bagging_freq':1, 'min_sum_hessian_in_leaf':100 } param['is_unbalance']='true' param['metric'] = 'auc' #03. cv and train 自定義cv函數和模型訓練 bst=lgb.cv(param,train_data, num_boost_round=1000, nfold=3, early_stopping_rounds=30) estimators = lgb.train(param,train_data,num_boost_round=len(bst['auc-mean'])) #04. test predict 測試集結果 ypred = estimators.predict(dtest[predictors]) 2.2 模板代碼 2.2.1 二分類 import lightgbm as lgb import pandas as pd import numpy as np import pickle from sklearn.metrics import roc_auc_score from sklearn.model_selection import train_test_split print("Loading Data ... ") # 導入數據 train_x, train_y, test_x = load_data() # 用sklearn.cross_validation進行訓練數據集划分,這里訓練集和交叉驗證集比例為7:3,可以自己根據需要設置 X, val_X, y, val_y = train_test_split( train_x, train_y, test_size=0.05, random_state=1, stratify=train_y # 這里保證分割后y的比例分布與原數據一致 ) X_train = X y_train = y X_test = val_X y_test = val_y # create dataset for lightgbm lgb_train = lgb.Dataset(X_train, y_train) lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train) # specify your configurations as a dict params = { 'boosting_type': 'gbdt', 'objective': 'binary', 'metric': {'binary_logloss', 'auc'}, #二進制對數損失 'num_leaves': 5, 'max_depth': 6, 'min_data_in_leaf': 450, 'learning_rate': 0.1, 'feature_fraction': 0.9, 'bagging_fraction': 0.95, 'bagging_freq': 5, 'lambda_l1': 1, 'lambda_l2': 0.001, # 越小l2正則程度越高 'min_gain_to_split': 0.2, 'verbose': 5, 'is_unbalance': True } # train print('Start training...') gbm = lgb.train(params, lgb_train, num_boost_round=10000, valid_sets=lgb_eval, early_stopping_rounds=500) print('Start predicting...') preds = gbm.predict(test_x, num_iteration=gbm.best_iteration) # 輸出的是概率結果 # 導出結果 threshold = 0.5 for pred in preds: result = 1 if pred > threshold else 0 # 導出特征重要性 importance = gbm.feature_importance() names = gbm.feature_name() with open('./feature_importance.txt', 'w+') as file: for index, im in enumerate(importance): string = names[index] + ', ' + str(im) + '\n' file.write(string) 2.2.2 多分類 import lightgbm as lgb import pandas as pd import numpy as np import pickle from sklearn.metrics import roc_auc_score from sklearn.model_selection import train_test_split print("Loading Data ... ") # 導入數據 train_x, train_y, test_x = load_data() # 用sklearn.cross_validation進行訓練數據集划分,這里訓練集和交叉驗證集比例為7:3,可以自己根據需要設置 X, val_X, y, val_y = train_test_split( train_x, train_y, test_size=0.05, random_state=1, stratify=train_y ## 這里保證分割后y的比例分布與原數據一致 ) X_train = X y_train = y X_test = val_X y_test = val_y # create dataset for lightgbm lgb_train = lgb.Dataset(X_train, y_train) lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train) # specify your configurations as a dict params = { 'boosting_type': 'gbdt', 'objective': 'multiclass', 'num_class': 9, 'metric': 'multi_error', 'num_leaves': 300, 'min_data_in_leaf': 100, 'learning_rate': 0.01, 'feature_fraction': 0.8, 'bagging_fraction': 0.8, 'bagging_freq': 5, 'lambda_l1': 0.4, 'lambda_l2': 0.5, 'min_gain_to_split': 0.2, 'verbose': 5, 'is_unbalance': True } # train print('Start training...') gbm = lgb.train(params, lgb_train, num_boost_round=10000, valid_sets=lgb_eval, early_stopping_rounds=500) print('Start predicting...') preds = gbm.predict(test_x, num_iteration=gbm.best_iteration) # 輸出的是概率結果 # 導出結果 for pred in preds: result = prediction = int(np.argmax(pred)) # 導出特征重要性 importance = gbm.feature_importance() names = gbm.feature_name() with open('./feature_importance.txt', 'w+') as file: for index, im in enumerate(importance): string = names[index] + ', ' + str(im) + '\n' file.write(string) 2.3 lightGBM 和 xgboost 的代碼比較 2.3.1 划分訓練集測試集 #xgboost dtrain = xgb.DMatrix(x_train,label=y_train) dtest = xgb.DMatrix(x_test) # lightgbm train_data = lgb.Dataset(x_train,label=y_train) 2.3.2 設置參數 #xgboost parameters = { 'max_depth':7, 'eta':1, 'silent':1, 'objective':'binary:logistic', 'eval_metric':'auc', 'learning_rate':.05} # lightgbm param = { 'num_leaves':150, 'objective':'binary', 'max_depth':7, 'learning_rate':.05, 'max_bin':200} param['metric'] = ['auc', 'binary_logloss'] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 2.3.3 模型訓練 #xgboost num_round = 50 from datetime import datetime start = datetime.now() xg = xgb.train(parameters,dtrain,num_round) stop = datetime.now() # lightgbm num_round = 50 start = datetime.now() lgbm = lgb.train(param,train_data,num_round) stop = datetime.now() 1 2 3 4 5 6 7 8 9 10 11 12 2.3.4 模型執行時間 #xgboost execution_time_xgb = stop - start execution_time_xgb # lightgbm execution_time_lgbm = stop - start execution_time_lgbm 1 2 3 4 5 6 7 2.3.5 模型測試 #xgboost ypred = xg.predict(dtest) ypred # lightgbm ypred2 = lgbm.predict(x_test) ypred2[0:5] 1 2 3 4 5 6 7 2.3.6 分類轉換 #xgboost for i in range(0,9769): if ypred[i] >= .5: # setting threshold to .5 ypred[i] = 1 else: ypred[i] = 0 # lightgbm for i in range(0,9769): if ypred2[i] >= .5: # setting threshold to .5 ypred2[i] = 1 else: ypred2[i] = 0 1 2 3 4 5 6 7 8 9 10 11 12 13 2.3.7 准確率計算 #xgboost from sklearn.metrics import accuracy_score accuracy_xgb = accuracy_score(y_test,ypred) accuracy_xgb # lightgbm accuracy_lgbm = accuracy_score(ypred2,y_test) accuracy_lgbm y_test.value_counts() from sklearn.metrics import roc_auc_score 1 2 3 4 5 6 7 8 9 10 2.3.8 roc_auc_score計算 #xgboost auc_xgb = roc_auc_score(y_test,ypred) # lightgbm auc_lgbm = roc_auc_score(y_test,ypred2) 1 2 3 4 5 最后可以建立一個 dataframe 來比較 Lightgbm 和 xgb: auc_lgbm comparison_dict = { 'accuracy score':(accuracy_lgbm,accuracy_xgb), 'auc score':(auc_lgbm,auc_xgb), 'execution time':(execution_time_lgbm,execution_time_xgb)} comparison_df = DataFrame(comparison_dict) comparison_df.index= ['LightGBM','xgboost'] comparison_df 1 2 3 4 5 6 7 8 3 lightGBM調參 3.1 參數 3.1 控制參數 Control Parameters 含義 用法 max_depth 樹的最大深度 當模型過擬合時,可以考慮首先降低 max_depth min_data_in_leaf 葉子可能具有的最小記錄數 默認20,過擬合時用 feature_fraction 例如 為0.8時,意味着在每次迭代中隨機選擇80%的參數來建樹 boosting 為 random forest 時用 bagging_fraction 每次迭代時用的數據比例 用於加快訓練速度和減小過擬合 early_stopping_round 如果一次驗證數據的一個度量在最近的early_stopping_round 回合中沒有提高,模型將停止訓練 加速分析,減少過多迭代 lambda 指定正則化 0~1 min_gain_to_split 描述分裂的最小 gain 控制樹的有用的分裂 max_cat_group 在 group 邊界上找到分割點 當類別數量很多時,找分割點很容易過擬合時 3.2 核心參數 CoreParameters 含義 用法 Task 數據的用途 選擇 train 或者 predict application 模型的用途 選擇 regression: 回歸時,binary: 二分類時,multiclass: 多分類時 boosting 要用的算法 gbdt, rf: random forest, dart: Dropouts meet Multiple Additive Regression Trees, goss: Gradient-based One-Side Sampling num_boost_round 迭代次數 通常 100+ learning_rate 如果一次驗證數據的一個度量在最近的 early_stopping_round 回合中沒有提高,模型將停止訓練 常用 0.1, 0.001, 0.003… num_leaves 默認 31 device cpu 或者 gpu metric mae: mean absolute error , mse: mean squared error , binary_logloss: loss for binary classification , multi_logloss: loss for multi classification 3.3 IO參數 IO parameter 含義 max_bin 表示 feature 將存入的 bin 的最大數量 categorical_feature 如果 categorical_features = 0,1,2, 則列 0,1,2是 categorical 變量 ignore_column 與 categorical_features 類似,只不過不是將特定的列視為categorical,而是完全忽略 save_binary 這個參數為 true 時,則數據集被保存為二進制文件,下次讀數據時速度會變快 3.2 調參 IO parameter 含義 num_leaves 取值應 <= 2 ^(max_depth), 超過此值會導致過擬合 min_data_in_leaf 將它設置為較大的值可以避免生長太深的樹,但可能會導致 underfitting,在大型數據集時就設置為數百或數千 max_depth 這個也是可以限制樹的深度 下表對應了 Faster Speed ,better accuracy ,over-fitting 三種目的時,可以調的參數 Faster Speed better accuracy over-fitting 將 max_bin 設置小一些 用較大的 max_bin max_bin 小一些 num_leaves 大一些 num_leaves 小一些 用 feature_fraction 來做 sub-sampling 用 feature_fraction 用 bagging_fraction 和 bagging_freq 設定 bagging_fraction 和 bagging_freq training data 多一些 training data 多一些 用 save_binary 來加速數據加載 直接用 categorical feature 用 gmin_data_in_leaf 和 min_sum_hessian_in_leaf 用 parallel learning 用 dart 用 lambda_l1, lambda_l2 ,min_gain_to_split 做正則化 num_iterations 大一些,learning_rate 小一些 用 max_depth 控制樹的深度 4 lightGBM案例 4.1 回歸案例 data來源:LightGBM包自帶data 4.1.1 代碼 import json import lightgbm as lgb import pandas as pd from sklearn.metrics import roc_auc_score path="D:/data/" print("load data") df_train=pd.read_csv(path+"regression.train.csv",header=None,sep='\t') df_test=pd.read_csv(path+"regression.train.csv",header=None,sep='\t') y_train = df_train[0].values y_test = df_test[0].values X_train = df_train.drop(0, axis=1).values X_test = df_test.drop(0, axis=1).values # create dataset for lightgbm lgb_train = lgb.Dataset(X_train, y_train) lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train) # specify your configurations as a dict params = { 'task': 'train', 'boosting_type': 'gbdt', 'objective': 'binary', 'metric': {'l2', 'auc'}, 'num_leaves': 31, 'learning_rate': 0.05, 'feature_fraction': 0.9, 'bagging_fraction': 0.8, 'bagging_freq': 5, 'verbose': 0 } print('Start training...') # train gbm = lgb.train(params, lgb_train, num_boost_round=20, valid_sets=lgb_eval, early_stopping_rounds=5) print('Save model...') # save model to file gbm.save_model(path+'lightgbm/model.txt') print('Start predicting...') # predict y_pred = gbm.predict(X_test, num_iteration=gbm.best_iteration) # eval print(y_pred) print('The roc of prediction is:', roc_auc_score(y_test, y_pred) ) print('Dump model to JSON...') # dump model to json (and save to file) model_json = gbm.dump_model() with open(path+'lightgbm/model.json', 'w+') as f: json.dump(model_json, f, indent=4) print('Feature names:', gbm.feature_name()) print('Calculate feature importances...') # feature importances print('Feature importances:', list(gbm.feature_importance())) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 4.1.2 運行結果 load data Start training... [1] valid_0's auc: 0.76138 valid_0's l2: 0.243849 Training until validation scores don't improve for 5 rounds. [2] valid_0's auc: 0.776568 valid_0's l2: 0.239689 [3] valid_0's auc: 0.797394 valid_0's l2: 0.235903 [4] valid_0's auc: 0.804646 valid_0's l2: 0.231545 [5] valid_0's auc: 0.807803 valid_0's l2: 0.22744 [6] valid_0's auc: 0.811241 valid_0's l2: 0.224042 [7] valid_0's auc: 0.817447 valid_0's l2: 0.221105 [8] valid_0's auc: 0.819344 valid_0's l2: 0.217747 [9] valid_0's auc: 0.82034 valid_0's l2: 0.214645 [10] valid_0's auc: 0.821408 valid_0's l2: 0.211794 [11] valid_0's auc: 0.823175 valid_0's l2: 0.209131 [12] valid_0's auc: 0.824161 valid_0's l2: 0.206662 [13] valid_0's auc: 0.824834 valid_0's l2: 0.204433 [14] valid_0's auc: 0.825996 valid_0's l2: 0.20245 [15] valid_0's auc: 0.826775 valid_0's l2: 0.200595 [16] valid_0's auc: 0.827877 valid_0's l2: 0.198727 [17] valid_0's auc: 0.830383 valid_0's l2: 0.196703 [18] valid_0's auc: 0.833477 valid_0's l2: 0.195037 [19] valid_0's auc: 0.834914 valid_0's l2: 0.193249 [20] valid_0's auc: 0.836136 valid_0's l2: 0.191544 Did not meet early stopping. Best iteration is: [20] valid_0's auc: 0.836136 valid_0's l2: 0.191544 Save model... Start predicting... [ 0.63918719 0.74876927 0.7446886 ..., 0.27801888 0.47378265 0.49893381] The roc of prediction is: 0.836136144322 Dump model to JSON... Feature names: ['Column_0', 'Column_1', 'Column_2', 'Column_3', 'Column_4', 'Column_5', 'Column_6', 'Column_7', 'Column_8', 'Column_9', 'Column_10', 'Column_11', 'Column_12', 'Column_13', 'Column_14', 'Column_15', 'Column_16', 'Column_17', 'Column_18', 'Column_19', 'Column_20', 'Column_21', 'Column_22', 'Column_23', 'Column_24', 'Column_25', 'Column_26', 'Column_27'] Calculate feature importances... Feature importances: [25, 4, 4, 41, 7, 56, 4, 1, 4, 29, 5, 4, 1, 20, 8, 10, 0, 7, 3, 10, 1, 21, 59, 7, 66, 77, 55, 71] 4.2 [ICC競賽] 精品旅行服務成單預測 比賽說明:精品旅行服務成單預測 4.2.1 業務需求 提供了5萬多名用戶在旅游app中的瀏覽行為記錄,其中有些用戶在瀏覽之后完成了訂單,且享受了精品旅游服務,而有些用戶則沒有下單。參賽者需要分析用戶的個人信息和瀏覽行為,從而預測用戶是否會在短期內購買精品旅游服務。預測用戶是否會在短期內購買精品旅游服務。 4.2.2 數據表格 (1)數據整體描述: 數據包含5萬多名用戶的個人信息,以及他們上百萬條的瀏覽記錄和相應的歷史訂單記錄,還包含有用戶對歷史訂單的評論信息。 這些用戶被隨機分為2組,80%作為訓練集,20%作為測試集。 兩組數據的處理方式和內容類型是一致的,唯一不同的就是測試集中不提供需要預測的訂單類型(即是否有購買精品旅游服務)。 (2)數據詳細描述: (a)用戶個人信息:userProfile_\***.csv** (*表示train或者test,下同) 數據共有四列,分別是用戶id、性別、省份、年齡段。注:信息會有缺失。 例如: userid,gender,province,age 100000000127,,上海, 100000000231,男,北京,70后 (b)用戶行為信息:action_*.csv** 數據共有三列,分別是用戶id,行為類型,發生時間。 例如: userid,actionType,actionTime 100000000111,1,1490971433 100000000111,5,1490971446 100000000111,6,1490971479 100000000127,1,1490695669 100000000127,5,1490695821 行為類型一共有9個,其中1是喚醒app;2~4是瀏覽產品,無先后關系;5~9則是有先后關系的,從填寫表單到提交訂單再到最后支付。 注意:數據存在一定的缺失! (c)用戶歷史訂單數據:orderHistory_*.csv** 該數據描述了用戶的歷史訂單信息。數據共有7列,分別是用戶id,訂單id,訂單時間,訂單類型,旅游城市,國家,大陸。其中1表示購買了精品旅游服務,0表示普通旅游服務。 例如: userid,orderid,orderTime,orderType,city,country,continent 100000000371, 1000709,1503443585,0,東京,日本,亞洲 100000000393, 1000952,1499440296,0,巴黎,法國,歐洲 注意:一個用戶可能會有多個訂單,需要預測的是用戶最近一次訂單的類型;此文件給到的訂單記錄都是在“被預測訂單”之前的記錄信息!同一時刻可能有多個訂單,屬於父訂單和子訂單的關系。 (d)待預測訂單的數據:orderFuture_*.csv** 對於train,有兩列,分別是用戶id和訂單類型。供參賽者訓練模型使用。其中1表示購買了精品旅游服務,0表示未購買精品旅游服務(包括普通旅游服務和未下訂單)。 例如: userid,orderType 102040050111,0 103020010127,1 100002030231,0 對於test,只有一列用戶id,是待預測的用戶列表。 (e)評論數據:userComment_*.csv** 共有5個字段,分別是用戶id,訂單id,評分,標簽,評論內容。 其中受數據保密性約束,評論內容僅顯示一些關鍵詞。 userid,orderid,rating,tags,commentsKeyWords 100000550471, 1001899,5.0,, 10044000637, 1001930,5.0,主動熱情|提前聯系|景點介紹詳盡|耐心等候, 111333446057, 1001960,5.0,主動熱情|耐心等候,[‘平穩’, ‘很好’] 4.2.3 lightGBM模型 # -*- coding:utf-8 -*- from __future__ import print_function from __future__ import division from data_helper import * import lightgbm as lgb from sklearn.model_selection import train_test_split import time import logging.handlers """Train the lightGBM model.""" LOG_FILE = 'log/lgb_train.log' check_path(LOG_FILE) handler = logging.handlers.RotatingFileHandler(LOG_FILE, maxBytes=1024 * 1024, backupCount=1) # 實例化handler fmt = '%(asctime)s - %(filename)s:%(lineno)s - %(name)s - %(message)s' formatter = logging.Formatter(fmt) handler.setFormatter(formatter) logger = logging.getLogger('train') logger.addHandler(handler) logger.setLevel(logging.DEBUG) def lgb_fit(config, X_train, y_train): """模型(交叉驗證)訓練,並返回最優迭代次數和最優的結果。 Args: config: xgb 模型參數 {params, max_round, cv_folds, early_stop_round, seed, save_model_path} X_train:array like, shape = n_sample * n_feature y_train: shape = n_sample * 1 Returns: best_model: 訓練好的最優模型 best_auc: float, 在測試集上面的 AUC 值。 best_round: int, 最優迭代次數。 """ params = config.params max_round = config.max_round cv_folds = config.cv_folds early_stop_round = config.early_stop_round seed = config.seed # seed = np.random.randint(0, 10000) save_model_path = config.save_model_path if cv_folds is not None: dtrain = lgb.Dataset(X_train, label=y_train) cv_result = lgb.cv(params, dtrain, max_round, nfold=cv_folds, seed=seed, verbose_eval=True, metrics='auc', early_stopping_rounds=early_stop_round, show_stdv=False) # 最優模型,最優迭代次數 best_round = len(cv_result['auc-mean']) best_auc = cv_result['auc-mean'][-1] # 最好的 auc 值 best_model = lgb.train(params, dtrain, best_round) else: X_train, X_valid, y_train, y_valid = train_test_split(X_train, y_train, test_size=0.2, random_state=100) dtrain = lgb.Dataset(X_train, label=y_train) dvalid = lgb.Dataset(X_valid, label=y_valid) watchlist = [dtrain, dvalid] best_model = lgb.train(params, dtrain, max_round, valid_sets=watchlist, early_stopping_rounds=early_stop_round) best_round = best_model.best_iteration best_auc = best_model.best_score cv_result = None if save_model_path: check_path(save_model_path) best_model.save_model(save_model_path) return best_model, best_auc, best_round, cv_result def lgb_predict(model, X_test, save_result_path=None): y_pred_prob = model.predict(X_test) if save_result_path: df_result = df_future_test df_result['orderType'] = y_pred_prob df_result.to_csv(save_result_path, index=False) print('Save the result to {}'.format(save_result_path)) return y_pred_prob class Config(object): def __init__(self): self.params = { 'objective': 'binary', 'metric': {'auc'}, 'learning_rate': 0.05, 'num_leaves': 30, # 葉子設置為 50 線下過擬合嚴重 'min_sum_hessian_in_leaf': 0.1, 'feature_fraction': 0.3, # 相當於 colsample_bytree 'bagging_fraction': 0.5, # 相當於 subsample 'lambda_l1': 0, 'lambda_l2': 5, 'num_thread': 6 # 線程數設置為真實的 CPU 數,一般12線程的機器有6個物理核 } self.max_round = 3000 self.cv_folds = 5 self.early_stop_round = 30 self.seed = 3 self.save_model_path = 'model/lgb.txt' def run_feat_search(X_train, X_test, y_train, feature_names): """根據特征重要度,逐個刪除特征進行訓練,獲取最好的特征結果。 同時,將每次迭代的結果求平均作為預測結果""" config = Config() # train model tic = time.time() y_pred_list = list() aucs = list() for i in range(1, 250, 3): drop_cols = feature_names[-i:] X_train_ = X_train.drop(drop_cols, axis=1) X_test_ = X_test.drop(drop_cols, axis=1) data_message = 'X_train.shape={}, X_test.shape={}'.format(X_train_.shape, X_test_.shape) print(data_message) logger.info(data_message) lgb_model, best_auc, best_round, cv_result = lgb_fit(config, X_train_, y_train) print('Time cost {}s'.format(time.time() - tic)) result_message = 'best_round={}, best_auc={}'.format(best_round, best_auc) logger.info(result_message) print(result_message) # predict # lgb_model = lgb.Booster(model_file=config.save_model_path) now = time.strftime("%m%d-%H%M%S") result_path = 'result/result_lgb_{}-{:.4f}.csv'.format(now, best_auc) check_path(result_path) y_pred = lgb_predict(lgb_model, X_test_, result_path) y_pred_list.append(y_pred) aucs.append(best_auc) y_preds_path = 'stack_preds/lgb_feat_search_pred_{}.npz'.format(i) check_path(y_preds_path) np.savez(y_preds_path, y_pred_list=y_pred_list, aucs=aucs) message = 'Saved y_preds to {}. Best auc is {}'.format(y_preds_path, np.max(aucs)) logger.info(message) print(message) def run_cv(X_train, X_test, y_train): config = Config() # train model tic = time.time() data_message = 'X_train.shape={}, X_test.shape={}'.format(X_train.shape, X_test.shape) print(data_message) logger.info(data_message) lgb_model, best_auc, best_round, cv_result = lgb_fit(config, X_train, y_train) print('Time cost {}s'.format(time.time() - tic)) result_message = 'best_round={}, best_auc={}'.format(best_round, best_auc) logger.info(result_message) print(result_message) # predict # lgb_model = lgb.Booster(model_file=config.save_model_path) now = time.strftime("%m%d-%H%M%S") result_path = 'result/result_lgb_{}-{:.4f}.csv'.format(now, best_auc) check_path(result_path) lgb_predict(lgb_model, X_test, result_path) if __name__ == '__main__': # get feature feature_path = 'features/' train_data, test_data = load_feat(re_get=True, feature_path=feature_path) train_feats = train_data.columns.values test_feats = test_data.columns.values drop_columns = list(filter(lambda x: x not in test_feats, train_feats)) X_train = train_data.drop(drop_columns, axis=1) y_train = train_data['label'] X_test = test_data data_message = 'X_train.shape={}, X_test.shape={}'.format(X_train.shape, X_test.shape) print(data_message) logger.info(data_message) # 根據特征搜索中最好的結果丟棄部分特征 # n_drop_col = 141 # drop_cols = feature_names[-n_drop_col:] # X_train = X_train.drop(drop_cols, axis=1) # X_test = X_test.drop(drop_cols, axis=1) # 直接訓練 run_cv(X_train, X_test, y_train) # 特征搜索 # get feature scores # try: # df_lgb_feat_score = pd.read_csv('features/lgb_features.csv') # feature_names = df_lgb_feat_score.feature.values # except Exception as e: # print('You should run the get_no_used_features.py first.') # run_feat_search(X_train, X_test, y_train, feature_names) 注意:該案例還使用了XGboost和catBoost模型,以及其他特征提取方法,在此不詳述。數據+模型見github 5 lightGBM的坑 5.1 設置提前停止 如果在訓練過程中啟用了提前停止,可以用 bst.best_iteration從最佳迭代中獲得預測結果: ypred = bst.predict(data,num_iteration = bst.best_iteration ) 1 5.2 自動處理類別特征 當使用本地分類特征,LightGBM能提供良好的精確度。不像簡單的one-hot編碼,lightGBM可以找到分類特征的最優分割。 用categorical_feature指定分類特征 首先需要轉換為int類型,並且只支持非負數。轉換為連續范圍更好。 使用min_data_per_group,cat_smooth去處理過擬合(當#data比較小,或者#category比較大) 對於具有高基數的分類特征(#category比較大),最好轉換為數字特征。 5.3 自動處理缺失值 lightGBM通過默認方式處理缺失值,可以通過設置use_missing = false 來使其無效。 lightGBM通過默認的方式用NA(NaN)去表示缺失值,可以通過設置zero_as_missing = true 將其變為0 當設置zero_as_missing = false(默認)時,在稀疏矩陣里(和lightSVM),沒有顯示的值視為0 當設置zero_as_missing = true時,NA和0(包括在稀疏矩陣里,沒有顯示的值)視為缺失。
