資料競賽入門-金融風控(貸款違約預測)四、建模與調參

越前浩波發表於2020-09-24

前言

本次活動為datawhale與天池聯合舉辦,為金融風控之貸款違約預測挑戰賽(入門)
比賽地址:https://tianchi.aliyun.com/competition/entrance/531830/introduction

模型對比與效能評估

邏輯迴歸

推薦部落格:
機器學習筆記I: 基於邏輯迴歸的分類預測
機器學習系列(1)_邏輯迴歸初步

  • 優點

    • 訓練速度較快,分類的時候,計算量僅僅只和特徵的數目相關;
    • 簡單易理解,模型的可解釋性非常好,從特徵的權重可以看到不同的特徵對最後結果的影響;
    • 適合二分類問題,不需要縮放輸入特徵;
    • 記憶體資源佔用小,只需要儲存各個維度的特徵值;
  • 缺點

    • 邏輯迴歸需要預先處理缺失值和異常值,可以檢視此部落格

    • 不能用Logistic迴歸去解決非線性問題,因為Logistic的決策面是線性的;

    • 對多重共線性資料較為敏感,且很難處理資料不平衡的問題;

    • 準確率並不是很高,因為形式非常簡單,很難去擬合資料的真實分佈;

決策樹模型

推薦部落格:
機器學習筆記II: 決策樹
Python3《機器學習實戰》學習筆記(三):決策樹實戰篇之為自己配個隱形眼鏡

  • 優點
    • 簡單直觀,生成的決策樹可以視覺化展示
    • 資料不需要預處理,不需要歸一化,不需要處理缺失資料
    • 既可以處理離散值,也可以處理連續值
  • 缺點
    • 決策樹演算法非常容易過擬合,導致泛化能力不強(可進行適當的剪枝)
    • 採用的是貪心演算法,容易得到區域性最優解

整合模型整合方法(ensemble method)

通過組合多個學習器來完成學習任務,通過整合方法,可以將多個弱學習器組合成一個強分類器,因此整合學習的泛化能力一般比單一分類器要好。

整合方法主要包括Bagging和Boosting,Bagging和Boosting都是將已有的分類或迴歸演算法通過一定方式組合起來,形成一個更加強大的分類。兩種方法都是把若干個分類器整合為一個分類器的方法,只是整合的方式不一樣,最終得到不一樣的效果。常見的基於Baggin思想的整合模型有:隨機森林、基於Boosting思想的整合模型有:Adaboost、GBDT、XgBoost、LightGBM等。

Baggin和Boosting的區別總結如下:

  • 樣本選擇上: Bagging方法的訓練集是從原始集中有放回的選取,所以從原始集中選出的各輪訓練集之間是獨立的;而Boosting方法需要每一輪的訓練集不變,只是訓練集中每個樣本在分類器中的權重發生變化。而權值是根據上一輪的分類結果進行調整
  • 樣例權重上: Bagging方法使用均勻取樣,所以每個樣本的權重相等;而Boosting方法根據錯誤率不斷調整樣本的權值,錯誤率越大則權重越大
  • 預測函式上: Bagging方法中所有預測函式的權重相等;而Boosting方法中每個弱分類器都有相應的權重,對於分類誤差小的分類器會有更大的權重
  • 平行計算上: Bagging方法中各個預測函式可以並行生成;而Boosting方法各個預測函式只能順序生成,因為後一個模型引數需要前一輪模型的結果。

模型評估方法

對於模型來說,其在訓練集上面的誤差我們稱之為訓練誤差或者經驗誤差,而在測試集上的誤差稱之為測試誤差。

對於我們來說,我們更關心的是模型對於新樣本的學習能力,即我們希望通過對已有樣本的學習,儘可能的將所有潛在樣本的普遍規律學到手,而如果模型對訓練樣本學的太好,則有可能把訓練樣本自身所具有的一些特點當做所有潛在樣本的普遍特點,這時候我們就會出現過擬合的問題。

因此我們通常將已有的資料集劃分為訓練集和測試集兩部分,其中訓練集用來訓練模型,而測試集則是用來評估模型對於新樣本的判別能力。

對於資料集的劃分,我們通常要保證滿足以下兩個條件:

  • 訓練集和測試集的分佈要與樣本真實分佈一致,即訓練集和測試集都要保證是從樣本真實分佈中獨立同分布取樣而得;
  • 訓練集和測試集要互斥

對於資料集的劃分有三種方法:留出法,交叉驗證法和自助法,下面挨個介紹:

  • ①留出法

    留出法是直接將資料集D劃分為兩個互斥的集合,其中一個集合作為訓練集S,另一個作為測試集T。需要注意的是在劃分的時候要儘可能保證資料分佈的一致性,即避免因資料劃分過程引入額外的偏差而對最終結果產生影響。為了保證資料分佈的一致性,通常我們採用分層取樣的方式來對資料進行取樣。

    Tips: 通常,會將資料集D中大約2/3~4/5的樣本作為訓練集,其餘的作為測試集。

  • ②交叉驗證法

    k折交叉驗證通常將資料集D分為k份,其中k-1份作為訓練集,剩餘的一份作為測試集,這樣就可以獲得k組訓練/測試集,可以進行k次訓練與測試,最終返回的是k個測試結果的均值。交叉驗證中資料集的劃分依然是依據分層取樣的方式來進行。

    對於交叉驗證法,其k值的選取往往決定了評估結果的穩定性和保真性,通常k值選取10。

    當k=1的時候,我們稱之為留一法

  • ③自助法

    我們每次從資料集D中取一個樣本作為訓練集中的元素,然後把該樣本放回,重複該行為m次,這樣我們就可以得到大小為m的訓練集,在這裡面有的樣本重複出現,有的樣本則沒有出現過,我們把那些沒有出現過的樣本作為測試集。

    進行這樣取樣的原因是因為在D中約有36.8%的資料沒有在訓練集中出現過。留出法與交叉驗證法都是使用分層取樣的方式進行資料取樣與劃分,而自助法則是使用有放回重複取樣的方式進行資料取樣

資料集劃分總結

  • 對於資料量充足的時候,通常採用留出法或者k折交叉驗證法來進行訓練/測試集的劃分;
  • 對於資料集小且難以有效劃分訓練/測試集時使用自助法;
  • 對於資料集小且可有效劃分的時候最好使用留一法來進行劃分,因為這種方法最為準確

模型評價標準

對於本次比賽,我們選用auc作為模型評價標準,類似的評價標準還有ks、f1-score等,具體介紹與實現大家可以回顧下task1中的內容。

一起來看一下auc到底是什麼?

在邏輯迴歸裡面,對於正負例的界定,通常會設一個閾值,大於閾值的為正類,小於閾值為負類。如果我們減小這個閥值,更多的樣本會被識別為正類,提高正類的識別率,但同時也會使得更多的負類被錯誤識別為正類。為了直觀表示這一現象,引入ROC。

根據分類結果計算得到ROC空間中相應的點,連線這些點就形成ROC curve,橫座標為False Positive Rate(FPR:假正率),縱座標為True Positive Rate(TPR:真正率)。 一般情況下,這個曲線都應該處於(0,0)和(1,1)連線的上方,如圖:
在這裡插入圖片描述

ROC曲線中的四個點:

  • 點(0,1):即FPR=0, TPR=1,意味著FN=0且FP=0,將所有的樣本都正確分類;
  • 點(1,0):即FPR=1,TPR=0,最差分類器,避開了所有正確答案;
  • 點(0,0):即FPR=TPR=0,FP=TP=0,分類器把每個例項都預測為負類;
  • 點(1,1):分類器把每個例項都預測為正類

總之:ROC曲線越接近左上角,該分類器的效能越好,其泛化效能就越好。而且一般來說,如果ROC是光滑的,那麼基本可以判斷沒有太大的overfitting。

但是對於兩個模型,我們如何判斷哪個模型的泛化效能更優呢?這裡我們有主要以下兩種方法:

如果模型A的ROC曲線完全包住了模型B的ROC曲線,那麼我們就認為模型A要優於模型B;

如果兩條曲線有交叉的話,我們就通過比較ROC與X,Y軸所圍得曲線的面積來判斷,面積越大,模型的效能就越優,這個面積我們稱之為AUC(area under ROC curve)

程式碼示例

匯入相關關和相關設定

import pandas as pd
import numpy as np
import warnings
import os
import seaborn as sns
import matplotlib.pyplot as plt
"""
sns 相關設定
@return:
"""
# 宣告使用 Seaborn 樣式
sns.set()
# 有五種seaborn的繪圖風格,它們分別是:darkgrid, whitegrid, dark, white, ticks。預設的主題是darkgrid。
sns.set_style("whitegrid")
# 有四個預置的環境,按大小從小到大排列分別為:paper, notebook, talk, poster。其中,notebook是預設的。
sns.set_context('talk')
# 中文字型設定-黑體
plt.rcParams['font.sans-serif'] = ['SimHei']
# 解決儲存影像是負號'-'顯示為方塊的問題
plt.rcParams['axes.unicode_minus'] = False
# 解決Seaborn中文顯示問題並調整字型大小
sns.set(font='SimHei')

讀取資料

reduce_mem_usage 函式通過調整資料型別,幫助我們減少資料在記憶體中佔用的空間

def reduce_mem_usage(df):
    start_mem = df.memory_usage().sum() 
    print('Memory usage of dataframe is {:.2f} MB'.format(start_mem))
    
    for col in df.columns:
        col_type = df[col].dtype
        
        if col_type != object:
            c_min = df[col].min()
            c_max = df[col].max()
            if str(col_type)[:3] == 'int':
                if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
                    df[col] = df[col].astype(np.int8)
                elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
                    df[col] = df[col].astype(np.int16)
                elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
                    df[col] = df[col].astype(np.int32)
                elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:
                    df[col] = df[col].astype(np.int64)  
            else:
                if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:
                    df[col] = df[col].astype(np.float16)
                elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
                    df[col] = df[col].astype(np.float32)
                else:
                    df[col] = df[col].astype(np.float64)
        else:
            df[col] = df[col].astype('category')

    end_mem = df.memory_usage().sum() 
    print('Memory usage after optimization is: {:.2f} MB'.format(end_mem))
    print('Decreased by {:.1f}%'.format(100 * (start_mem - end_mem) / start_mem))
    
    return df
# 讀取資料
data = pd.read_csv('dataset/data_for_model.csv')
data = reduce_mem_usage(data)

Memory usage of dataframe is 928000128.00 MB
Memory usage after optimization is: 165006456.00 MB
Decreased by 82.2%

簡單建模

  • Tips1:金融風控的實際專案多涉及到信用評分,因此需要模型特徵具有較好的可解釋性,所以目前在實際專案中多還是以邏輯迴歸作為基礎模型。但是在比賽中以得分高低為準,不需要嚴謹的可解釋性,所以大多基於整合演算法進行建模。

  • Tips2:因為邏輯迴歸的演算法特性,需要提前對異常值、缺失值資料進行處理【參考task3部分】

  • Tips3:基於樹模型的演算法特性,異常值、缺失值處理可以跳過,但是對於業務較為了解的同學也可以自己對缺失異常值進行處理,效果可能會更優於模型處理的結果。

注:以下建模的源資料參考baseline進行了相應的特徵工程,對於異常缺失值未進行相應的處理操作。

建模之前的預操作

from sklearn.model_selection import KFold
# 分離資料集,方便進行交叉驗證
X_train = data.loc[data['sample']=='train', :].drop(['id','issueDate','isDefault', 'sample'], axis=1)
X_test = data.loc[data['sample']=='test', :].drop(['id','issueDate','isDefault', 'sample'], axis=1)
y_train = data.loc[data['sample']=='train', 'isDefault']

# 5折交叉驗證
folds = 5
seed = 2020
kf = KFold(n_splits=folds, shuffle=True, random_state=seed)

使用Lightgbm進行建模

"""對訓練集資料進行劃分,分成訓練集和驗證集,並進行相應的操作"""
from sklearn.model_selection import train_test_split
import lightgbm as lgb
# 資料集劃分
X_train_split, X_val, y_train_split, y_val = train_test_split(X_train, y_train, test_size=0.2)
train_matrix = lgb.Dataset(X_train_split, label=y_train_split)
valid_matrix = lgb.Dataset(X_val, label=y_val)

params = {
            'boosting_type': 'gbdt',
            'objective': 'binary',
            'learning_rate': 0.1,
            'metric': 'auc',
            'min_child_weight': 1e-3,
            'num_leaves': 31,
            'max_depth': -1,
            'reg_lambda': 0,
            'reg_alpha': 0,
            'feature_fraction': 1,
            'bagging_fraction': 1,
            'bagging_freq': 0,
            'seed': 2020,
            'nthread': 8,
            'silent': True,
            'verbose': -1,
}

"""使用訓練集資料進行模型訓練"""
model = lgb.train(params, train_set=train_matrix, valid_sets=valid_matrix, num_boost_round=20000, verbose_eval=1000, early_stopping_rounds=200)

'''
Training until validation scores don't improve for 200 rounds
Early stopping, best iteration is:
[427]	valid_0's auc: 0.724947
'''

對驗證集進行預測

from sklearn import metrics
from sklearn.metrics import roc_auc_score

"""預測並計算roc的相關指標"""
val_pre_lgb = model.predict(X_val, num_iteration=model.best_iteration)
fpr, tpr, threshold = metrics.roc_curve(y_val, val_pre_lgb)
roc_auc = metrics.auc(fpr, tpr)
print('未調參前lightgbm單模型在驗證集上的AUC:{}'.format(roc_auc))
"""畫出roc曲線圖"""
plt.figure(figsize=(8, 8))
plt.title('Validation ROC')
plt.plot(fpr, tpr, 'b', label = 'Val AUC = %0.4f' % roc_auc)
plt.ylim(0,1)
plt.xlim(0,1)
plt.legend(loc='best')
plt.title('ROC')
plt.ylabel('True Positive Rate')
plt.xlabel('False Positive Rate')
# 畫出對角線
plt.plot([0,1],[0,1],'r--')
plt.show()

#未調參前lightgbm單模型在驗證集上的AUC:0.7249469360631181

在這裡插入圖片描述
更進一步的,使用5折交叉驗證進行模型效能評估

import lightgbm as lgb
"""使用lightgbm 5折交叉驗證進行建模預測"""
cv_scores = []
for i, (train_index, valid_index) in enumerate(kf.split(X_train, y_train)):
    print('************************************ {} ************************************'.format(str(i+1)))
    X_train_split, y_train_split, X_val, y_val = X_train.iloc[train_index], y_train[train_index], X_train.iloc[valid_index], y_train[valid_index]
    
    train_matrix = lgb.Dataset(X_train_split, label=y_train_split)
    valid_matrix = lgb.Dataset(X_val, label=y_val)

    params = {
                'boosting_type': 'gbdt',
                'objective': 'binary',
                'learning_rate': 0.1,
                'metric': 'auc',
        
                'min_child_weight': 1e-3,
                'num_leaves': 31,
                'max_depth': -1,
                'reg_lambda': 0,
                'reg_alpha': 0,
                'feature_fraction': 1,
                'bagging_fraction': 1,
                'bagging_freq': 0,
                'seed': 2020,
                'nthread': 8,
                'silent': True,
                'verbose': -1,
    }
    
    model = lgb.train(params, train_set=train_matrix, num_boost_round=20000, valid_sets=valid_matrix, verbose_eval=1000, early_stopping_rounds=200)
    val_pred = model.predict(X_val, num_iteration=model.best_iteration)
    
    cv_scores.append(roc_auc_score(y_val, val_pred))
    print(cv_scores)

print("lgb_scotrainre_list:{}".format(cv_scores))
print("lgb_score_mean:{}".format(np.mean(cv_scores)))
print("lgb_score_std:{}".format(np.std(cv_scores)))

'''
...
lgb_scotrainre_list:[0.7303837315833632, 0.7258692125145638, 0.7305149209921737, 0.7296117869375041, 0.7294438695369077]
lgb_score_mean:0.7291647043129024
lgb_score_std:0.0016998349834934656
'''

模型調參

1. 貪心調參

先使用當前對模型影響最大的引數進行調優,達到當前引數下的模型最優化,再使用對模型影響次之的引數進行調優,如此下去,直到所有的引數調整完畢。

這個方法的缺點就是可能會調到區域性最優而不是全域性最優,但是隻需要一步一步的進行引數最優化除錯即可,容易理解。

需要注意的是在樹模型中引數調整的順序,也就是各個引數對模型的影響程度,這裡列舉一下日常調參過程中常用的引數和調參順序:

  • ①:max_depth、num_leaves
  • ②:min_data_in_leaf、min_child_weight
  • ③:bagging_fraction、 feature_fraction、bagging_freq
  • ④:reg_lambda、reg_alpha
  • ⑤:min_split_gain
from sklearn.model_selection import cross_val_score

# 調objective
best_obj = dict()
for obj in objective:
    model = LGBMRegressor(objective=obj)
    """預測並計算roc的相關指標"""
    score = cross_val_score(model, X_train, y_train, cv=5, scoring='roc_auc').mean()
    best_obj[obj] = score
    
# num_leaves
best_leaves = dict()
for leaves in num_leaves:
    model = LGBMRegressor(objective=min(best_obj.items(), key=lambda x:x[1])[0], num_leaves=leaves)
    """預測並計算roc的相關指標"""
    score = cross_val_score(model, X_train, y_train, cv=5, scoring='roc_auc').mean()
    best_leaves[leaves] = score
    
# max_depth
best_depth = dict()
for depth in max_depth:
    model = LGBMRegressor(objective=min(best_obj.items(), key=lambda x:x[1])[0],
                          num_leaves=min(best_leaves.items(), key=lambda x:x[1])[0],
                          max_depth=depth)
    """預測並計算roc的相關指標"""
    score = cross_val_score(model, X_train, y_train, cv=5, scoring='roc_auc').mean()
    best_depth[depth] = score

"""
可依次將模型的引數通過上面的方式進行調整優化,並且通過視覺化觀察在每一個最優引數下模型的得分情況
"""

可依次將模型的引數通過上面的方式進行調整優化,並且通過視覺化觀察在每一個最優引數下模型的得分情況

2. 網格搜尋

sklearn 提供GridSearchCV用於進行網格搜尋,只需要把模型的引數輸進去,就能給出最優化的結果和引數。相比起貪心調參,網格搜尋的結果會更優,但是網格搜尋只適合於小資料集,一旦資料的量級上去了,很難得出結果。

同樣以Lightgbm演算法為例,進行網格搜尋調參:

"""通過網格搜尋確定最優引數"""
from sklearn.model_selection import GridSearchCV

def get_best_cv_params(learning_rate=0.1, n_estimators=581, num_leaves=31, max_depth=-1, bagging_fraction=1.0, 
                       feature_fraction=1.0, bagging_freq=0, min_data_in_leaf=20, min_child_weight=0.001, 
                       min_split_gain=0, reg_lambda=0, reg_alpha=0, param_grid=None):
    # 設定5折交叉驗證
    cv_fold = StratifiedKFold(n_splits=5, random_state=0, shuffle=True, )
    
    model_lgb = lgb.LGBMClassifier(learning_rate=learning_rate,
                                   n_estimators=n_estimators,
                                   num_leaves=num_leaves,
                                   max_depth=max_depth,
                                   bagging_fraction=bagging_fraction,
                                   feature_fraction=feature_fraction,
                                   bagging_freq=bagging_freq,
                                   min_data_in_leaf=min_data_in_leaf,
                                   min_child_weight=min_child_weight,
                                   min_split_gain=min_split_gain,
                                   reg_lambda=reg_lambda,
                                   reg_alpha=reg_alpha,
                                   n_jobs= 8
                                  )
    grid_search = GridSearchCV(estimator=model_lgb, 
                               cv=cv_fold,
                               param_grid=param_grid,
                               scoring='roc_auc'
                              )
    grid_search.fit(X_train, y_train)

    print('模型當前最優引數為:{}'.format(grid_search.best_params_))
    print('模型當前最優得分為:{}'.format(grid_search.best_score_))
"""以下程式碼未執行,耗時較長,請謹慎執行,且每一步的最優引數需要在下一步進行手動更新,請注意"""

"""
需要注意一下的是,除了獲取上面的獲取num_boost_round時候用的是原生的lightgbm(因為要用自帶的cv)
下面配合GridSearchCV時必須使用sklearn介面的lightgbm。
"""
"""設定n_estimators 為581,調整num_leaves和max_depth,這裡選擇先粗調再細調"""
lgb_params = {'num_leaves': range(10, 80, 5), 'max_depth': range(3,10,2)}
get_best_cv_params(learning_rate=0.1, n_estimators=581, num_leaves=None, max_depth=None, min_data_in_leaf=20, 
                   min_child_weight=0.001,bagging_fraction=1.0, feature_fraction=1.0, bagging_freq=0, 
                   min_split_gain=0, reg_lambda=0, reg_alpha=0, param_grid=lgb_params)

"""num_leaves為30,max_depth為7,進一步細調num_leaves和max_depth"""
lgb_params = {'num_leaves': range(25, 35, 1), 'max_depth': range(5,9,1)}
get_best_cv_params(learning_rate=0.1, n_estimators=85, num_leaves=None, max_depth=None, min_data_in_leaf=20, 
                   min_child_weight=0.001,bagging_fraction=1.0, feature_fraction=1.0, bagging_freq=0, 
                   min_split_gain=0, reg_lambda=0, reg_alpha=0, param_grid=lgb_params)

"""
確定min_data_in_leaf為45,min_child_weight為0.001 ,下面進行bagging_fraction、feature_fraction和bagging_freq的調參
"""
lgb_params = {'bagging_fraction': [i/10 for i in range(5,10,1)], 
              'feature_fraction': [i/10 for i in range(5,10,1)],
              'bagging_freq': range(0,81,10)
             }
get_best_cv_params(learning_rate=0.1, n_estimators=85, num_leaves=29, max_depth=7, min_data_in_leaf=45, 
                   min_child_weight=0.001,bagging_fraction=None, feature_fraction=None, bagging_freq=None, 
                   min_split_gain=0, reg_lambda=0, reg_alpha=0, param_grid=lgb_params)

"""
確定bagging_fraction為0.4、feature_fraction為0.6、bagging_freq為 ,下面進行reg_lambda、reg_alpha的調參
"""
lgb_params = {'reg_lambda': [0,0.001,0.01,0.03,0.08,0.3,0.5], 'reg_alpha': [0,0.001,0.01,0.03,0.08,0.3,0.5]}
get_best_cv_params(learning_rate=0.1, n_estimators=85, num_leaves=29, max_depth=7, min_data_in_leaf=45, 
                   min_child_weight=0.001,bagging_fraction=0.9, feature_fraction=0.9, bagging_freq=40, 
                   min_split_gain=0, reg_lambda=None, reg_alpha=None, param_grid=lgb_params)

"""
確定reg_lambda、reg_alpha都為0,下面進行min_split_gain的調參
"""
lgb_params = {'min_split_gain': [i/10 for i in range(0,11,1)]}
get_best_cv_params(learning_rate=0.1, n_estimators=85, num_leaves=29, max_depth=7, min_data_in_leaf=45, 
                   min_child_weight=0.001,bagging_fraction=0.9, feature_fraction=0.9, bagging_freq=40, 
                   min_split_gain=None, reg_lambda=0, reg_alpha=0, param_grid=lgb_params)
"""
引數確定好了以後,我們設定一個比較小的learning_rate 0.005,來確定最終的num_boost_round
"""
# 設定5折交叉驗證
# cv_fold = StratifiedKFold(n_splits=5, random_state=0, shuffle=True, )
final_params = {
                'boosting_type': 'gbdt',
                'learning_rate': 0.01,
                'num_leaves': 29,
                'max_depth': 7,
                'min_data_in_leaf':45,
                'min_child_weight':0.001,
                'bagging_fraction': 0.9,
                'feature_fraction': 0.9,
                'bagging_freq': 40,
                'min_split_gain': 0,
                'reg_lambda':0,
                'reg_alpha':0,
                'nthread': 6
               }

cv_result = lgb.cv(train_set=lgb_train,
                   early_stopping_rounds=20,
                   num_boost_round=5000,
                   nfold=5,
                   stratified=True,
                   shuffle=True,
                   params=final_params,
                   metrics='auc',
                   seed=0,
                  )

print('迭代次數{}'.format(len(cv_result['auc-mean'])))
print('交叉驗證的AUC為{}'.format(max(cv_result['auc-mean'])))

在實際調整過程中,可先設定一個較大的學習率(上面的例子中0.1),通過Lgb原生的cv函式進行樹個數的確定,之後再通過上面的例項程式碼進行引數的調整優化。

最後針對最優的引數設定一個較小的學習率(例如0.05),同樣通過cv函式確定樹的個數,確定最終的引數。

需要注意的是,針對大資料集,上面每一層引數的調整都需要耗費較長時間,

貝葉斯調參

在使用之前需要先安裝包bayesian-optimization,執行如下命令即可:

pip install bayesian-optimization

貝葉斯調參的主要思想是:給定優化的目標函式(廣義的函式,只需指定輸入和輸出即可,無需知道內部結構以及數學性質),通過不斷地新增樣本點來更新目標函式的後驗分佈(高斯過程,直到後驗分佈基本貼合於真實分佈)。簡單的說,就是考慮了上一次引數的資訊,從而更好的調整當前的引數。

貝葉斯調參的步驟如下:

  • 定義優化函式(rf_cv)
  • 建立模型
  • 定義待優化的引數
  • 得到優化結果,並返回要優化的分數指標
from sklearn.model_selection import cross_val_score

"""定義優化函式"""
def rf_cv_lgb(num_leaves, max_depth, bagging_fraction, feature_fraction, bagging_freq, min_data_in_leaf, 
              min_child_weight, min_split_gain, reg_lambda, reg_alpha):
    # 建立模型
    model_lgb = lgb.LGBMClassifier(boosting_type='gbdt', bjective='binary', metric='auc',
                                   learning_rate=0.1, n_estimators=5000,
                                   num_leaves=int(num_leaves), max_depth=int(max_depth), 
                                   bagging_fraction=round(bagging_fraction, 2), feature_fraction=round(feature_fraction, 2),
                                   bagging_freq=int(bagging_freq), min_data_in_leaf=int(min_data_in_leaf),
                                   min_child_weight=min_child_weight, min_split_gain=min_split_gain,
                                   reg_lambda=reg_lambda, reg_alpha=reg_alpha,
                                   n_jobs= 8
                                  )
    
    val = cross_val_score(model_lgb, X_train_split, y_train_split, cv=5, scoring='roc_auc').mean()
    
    return val
from bayes_opt import BayesianOptimization
"""定義優化引數"""
bayes_lgb = BayesianOptimization(
    rf_cv_lgb, 
    {
        'num_leaves':(10, 200),
        'max_depth':(3, 20),
        'bagging_fraction':(0.5, 1.0),
        'feature_fraction':(0.5, 1.0),
        'bagging_freq':(0, 100),
        'min_data_in_leaf':(10,100),
        'min_child_weight':(0, 10),
        'min_split_gain':(0.0, 1.0),
        'reg_alpha':(0.0, 10),
        'reg_lambda':(0.0, 10),
    }
)

"""開始優化"""
bayes_lgb.maximize(n_iter=10)
'''
|   iter    |  target   | baggin... | baggin... | featur... | max_depth | min_ch... | min_da... | min_sp... | num_le... | reg_alpha | reg_la... |
-------------------------------------------------------------------------------------------------------------------------------------------------
| �[0m 1       �[0m | �[0m 0.7263  �[0m | �[0m 0.7196  �[0m | �[0m 80.73   �[0m | �[0m 0.7988  �[0m | �[0m 19.17   �[0m | �[0m 5.751   �[0m | �[0m 40.71   �[0m | �[0m 0.9548  �[0m | �[0m 176.2   �[0m | �[0m 2.939   �[0m | �[0m 7.212   �[0m |
| �[95m 2       �[0m | �[95m 0.7279  �[0m | �[95m 0.8997  �[0m | �[95m 74.72   �[0m | �[95m 0.5904  �[0m | �[95m 7.259   �[0m | �[95m 6.175   �[0m | �[95m 92.03   �[0m | �[95m 0.4027  �[0m | �[95m 51.65   �[0m | �[95m 6.404   �[0m | �[95m 4.781   �[0m |
| �[0m 3       �[0m | �[0m 0.7207  �[0m | �[0m 0.5133  �[0m | �[0m 16.53   �[0m | �[0m 0.9536  �[0m | �[0m 4.974   �[0m | �[0m 2.37    �[0m | �[0m 98.08   �[0m | �[0m 0.7909  �[0m | �[0m 52.12   �[0m | �[0m 4.443   �[0m | �[0m 4.429   �[0m |
| �[0m 4       �[0m | �[0m 0.7276  �[0m | �[0m 0.6265  �[0m | �[0m 53.12   �[0m | �[0m 0.7307  �[0m | �[0m 10.67   �[0m | �[0m 1.824   �[0m | �[0m 18.98   �[0m | �[0m 0.954   �[0m | �[0m 60.47   �[0m | �[0m 6.963   �[0m | �[0m 1.999   �[0m |
| �[0m 5       �[0m | �[0m 0.6963  �[0m | �[0m 0.6509  �[0m | �[0m 11.58   �[0m | �[0m 0.5386  �[0m | �[0m 11.21   �[0m | �[0m 7.85    �[0m | �[0m 11.4    �[0m | �[0m 0.4269  �[0m | �[0m 153.0   �[0m | �[0m 0.5227  �[0m | �[0m 2.257   �[0m |
| �[0m 6       �[0m | �[0m 0.7276  �[0m | �[0m 0.6241  �[0m | �[0m 49.76   �[0m | �[0m 0.6057  �[0m | �[0m 10.34   �[0m | �[0m 1.718   �[0m | �[0m 22.43   �[0m | �[0m 0.8294  �[0m | �[0m 55.68   �[0m | �[0m 6.759   �[0m | �[0m 2.6     �[0m |
| �[95m 7       �[0m | �[95m 0.7283  �[0m | �[95m 0.9815  �[0m | �[95m 96.15   �[0m | �[95m 0.6961  �[0m | �[95m 19.45   �[0m | �[95m 1.627   �[0m | �[95m 37.7    �[0m | �[95m 0.4185  �[0m | �[95m 14.22   �[0m | �[95m 7.057   �[0m | �[95m 9.924   �[0m |
| �[0m 8       �[0m | �[0m 0.7278  �[0m | �[0m 0.7139  �[0m | �[0m 96.83   �[0m | �[0m 0.5063  �[0m | �[0m 3.941   �[0m | �[0m 1.469   �[0m | �[0m 97.28   �[0m | �[0m 0.07553 �[0m | �[0m 196.9   �[0m | �[0m 7.988   �[0m | �[0m 2.159   �[0m |
| �[0m 9       �[0m | �[0m 0.7195  �[0m | �[0m 0.5352  �[0m | �[0m 98.72   �[0m | �[0m 0.9699  �[0m | �[0m 4.445   �[0m | �[0m 1.767   �[0m | �[0m 13.91   �[0m | �[0m 0.1647  �[0m | �[0m 191.5   �[0m | �[0m 4.003   �[0m | �[0m 2.027   �[0m |
| �[0m 10      �[0m | �[0m 0.7281  �[0m | �[0m 0.7281  �[0m | �[0m 73.63   �[0m | �[0m 0.5598  �[0m | �[0m 19.29   �[0m | �[0m 0.5344  �[0m | �[0m 99.66   �[0m | �[0m 0.933   �[0m | �[0m 101.4   �[0m | �[0m 8.836   �[0m | �[0m 0.9222  �[0m |
| �[0m 11      �[0m | �[0m 0.7279  �[0m | �[0m 0.8213  �[0m | �[0m 0.05856 �[0m | �[0m 0.7626  �[0m | �[0m 17.49   �[0m | �[0m 8.447   �[0m | �[0m 10.71   �[0m | �[0m 0.3252  �[0m | �[0m 13.64   �[0m | �[0m 9.319   �[0m | �[0m 0.4747  �[0m |
| �[0m 12      �[0m | �[0m 0.7281  �[0m | �[0m 0.8372  �[0m | �[0m 95.71   �[0m | �[0m 0.9598  �[0m | �[0m 10.32   �[0m | �[0m 8.394   �[0m | �[0m 15.23   �[0m | �[0m 0.4909  �[0m | �[0m 94.48   �[0m | �[0m 9.486   �[0m | �[0m 9.044   �[0m |
| �[0m 13      �[0m | �[0m 0.6993  �[0m | �[0m 0.5183  �[0m | �[0m 99.02   �[0m | �[0m 0.542   �[0m | �[0m 15.5    �[0m | �[0m 8.35    �[0m | �[0m 38.15   �[0m | �[0m 0.4079  �[0m | �[0m 58.01   �[0m | �[0m 0.2668  �[0m | �[0m 1.652   �[0m |
| �[0m 14      �[0m | �[0m 0.7267  �[0m | �[0m 0.7933  �[0m | �[0m 4.459   �[0m | �[0m 0.79    �[0m | �[0m 7.557   �[0m | �[0m 2.43    �[0m | �[0m 27.91   �[0m | �[0m 0.8725  �[0m | �[0m 28.32   �[0m | �[0m 9.967   �[0m | �[0m 9.885   �[0m |
| �[0m 15      �[0m | �[0m 0.6979  �[0m | �[0m 0.9419  �[0m | �[0m 1.22    �[0m | �[0m 0.835   �[0m | �[0m 11.56   �[0m | �[0m 9.962   �[0m | �[0m 93.79   �[0m | �[0m 0.018   �[0m | �[0m 197.6   �[0m | �[0m 9.711   �[0m | �[0m 3.78    �[0m |
=================================================================================================================================================
'''
"""顯示優化結果"""
bayes_lgb.max
'''
{'target': 0.7282530196283977,
 'params': {'bagging_fraction': 0.9815471914843896,
  'bagging_freq': 96.14757648686668,
  'feature_fraction': 0.6961281791730929,
  'max_depth': 19.45450235568963,
  'min_child_weight': 1.6266132496156782,
  'min_data_in_leaf': 37.697878831472295,
  'min_split_gain': 0.4184947943942168,
  'num_leaves': 14.221122487200399,
  'reg_alpha': 7.056502173310882,
  'reg_lambda': 9.924023764203156}}
'''

引數優化完成後,我們可以根據優化後的引數建立新的模型,降低學習率並尋找最優模型迭代次數

"""調整一個較小的學習率,並通過cv函式確定當前最優的迭代次數"""
base_params_lgb = {
                    'boosting_type': 'gbdt',
                    'objective': 'binary',
                    'metric': 'auc',
                    'learning_rate': 0.01,
                    'num_leaves': 14,
                    'max_depth': 19,
                    'min_data_in_leaf': 37,
                    'min_child_weight':1.6,
                    'bagging_fraction': 0.98,
                    'feature_fraction': 0.69,
                    'bagging_freq': 96,
                    'reg_lambda': 9,
                    'reg_alpha': 7,
                    'min_split_gain': 0.4,
                    'nthread': 8,
                    'seed': 2020,
                    'silent': True,
                    'verbose': -1,
}

cv_result_lgb = lgb.cv(
    train_set=train_matrix,
    early_stopping_rounds=1000, 
    num_boost_round=20000,
    nfold=5,
    stratified=True,
    shuffle=True,
    params=base_params_lgb,
    metrics='auc',
    seed=0
)

print('迭代次數{}'.format(len(cv_result_lgb['auc-mean'])))
print('最終模型的AUC為{}'.format(max(cv_result_lgb['auc-mean'])))
'''
迭代次數14269
最終模型的AUC為0.7315032037635779
'''

模型引數已經確定,建立最終模型並對驗證集進行驗證

import lightgbm as lgb
"""使用lightgbm 5折交叉驗證進行建模預測"""
cv_scores = []
for i, (train_index, valid_index) in enumerate(kf.split(X_train, y_train)):
    print('************************************ {} ************************************'.format(str(i+1)))
    X_train_split, y_train_split, X_val, y_val = X_train.iloc[train_index], y_train[train_index], X_train.iloc[valid_index], y_train[valid_index]
    
    train_matrix = lgb.Dataset(X_train_split, label=y_train_split)
    valid_matrix = lgb.Dataset(X_val, label=y_val)

    params = {
                'boosting_type': 'gbdt',
                'objective': 'binary',
                'metric': 'auc',
                'learning_rate': 0.01,
                'num_leaves': 14,
                'max_depth': 19,
                'min_data_in_leaf': 37,
                'min_child_weight':1.6,
                'bagging_fraction': 0.98,
                'feature_fraction': 0.69,
                'bagging_freq': 96,
                'reg_lambda': 9,
                'reg_alpha': 7,
                'min_split_gain': 0.4,
                'nthread': 8,
                'seed': 2020,
                'silent': True,
    }
    
    model = lgb.train(params, train_set=train_matrix, num_boost_round=14269, valid_sets=valid_matrix, verbose_eval=1000, early_stopping_rounds=200)
    val_pred = model.predict(X_val, num_iteration=model.best_iteration)
    
    cv_scores.append(roc_auc_score(y_val, val_pred))
    print(cv_scores)

print("lgb_scotrainre_list:{}".format(cv_scores))
print("lgb_score_mean:{}".format(np.mean(cv_scores)))
print("lgb_score_std:{}".format(np.std(cv_scores)))
'''
...
lgb_scotrainre_list:[0.7329726464187137, 0.7294292852806246, 0.7341505801564857, 0.7328331383185244, 0.7317405262608612]
lgb_score_mean:0.732225235287042
lgb_score_std:0.0015929470575114753
'''

通過5折交叉驗證可以發現,模型迭代次數在13000次的時候會停之,那麼我們在建立新模型時直接設定最大迭代次數,並使用驗證集進行模型預測

""""""
base_params_lgb = {
                    'boosting_type': 'gbdt',
                    'objective': 'binary',
                    'metric': 'auc',
                    'learning_rate': 0.01,
                    'num_leaves': 14,
                    'max_depth': 19,
                    'min_data_in_leaf': 37,
                    'min_child_weight':1.6,
                    'bagging_fraction': 0.98,
                    'feature_fraction': 0.69,
                    'bagging_freq': 96,
                    'reg_lambda': 9,
                    'reg_alpha': 7,
                    'min_split_gain': 0.4,
                    'nthread': 8,
                    'seed': 2020,
                    'silent': True,
}

"""使用訓練集資料進行模型訓練"""
final_model_lgb = lgb.train(base_params_lgb, train_set=train_matrix, valid_sets=valid_matrix, num_boost_round=13000, verbose_eval=1000, early_stopping_rounds=200)

"""預測並計算roc的相關指標"""
val_pre_lgb = final_model_lgb.predict(X_val)
fpr, tpr, threshold = metrics.roc_curve(y_val, val_pre_lgb)
roc_auc = metrics.auc(fpr, tpr)
print('調參後lightgbm單模型在驗證集上的AUC:{}'.format(roc_auc))
"""畫出roc曲線圖"""
plt.figure(figsize=(8, 8))
plt.title('Validation ROC')
plt.plot(fpr, tpr, 'b', label = 'Val AUC = %0.4f' % roc_auc)
plt.ylim(0,1)
plt.xlim(0,1)
plt.legend(loc='best')
plt.title('ROC')
plt.ylabel('True Positive Rate')
plt.xlabel('False Positive Rate')
# 畫出對角線
plt.plot([0,1],[0,1],'r--')
plt.show()
'''
Training until validation scores don't improve for 200 rounds
[1000]	valid_0's auc: 0.723676
[2000]	valid_0's auc: 0.727282
[3000]	valid_0's auc: 0.728593
[4000]	valid_0's auc: 0.729493
[5000]	valid_0's auc: 0.730087
[6000]	valid_0's auc: 0.730515
[7000]	valid_0's auc: 0.730872
[8000]	valid_0's auc: 0.731121
[9000]	valid_0's auc: 0.731351
[10000]	valid_0's auc: 0.731502
[11000]	valid_0's auc: 0.731707
Early stopping, best iteration is:
[11192]	valid_0's auc: 0.731741
調參後lightgbm單模型在驗證集上的AUC:0.7317405262608612
'''

在這裡插入圖片描述
可以看到相比最早的原始引數,模型的效能還是有提升的

"""儲存模型到本地"""
# 儲存模型
import pickle
pickle.dump(final_model_lgb, open('dataset/model_lgb_best.pkl', 'wb'))

模型調參小總結

  • 整合模型內建的cv函式可以較快的進行單一引數的調節,一般可以用來優先確定樹模型的迭代次數

  • 資料量較大的時候(例如本次專案的資料),網格搜尋調參會特別特別慢,不建議嘗試

  • 整合模型中原生庫和sklearn下的庫部分引數不一致,需要注意,具體可以參考xgb和lgb的官方API

    xgb原生庫APIsklearn庫下xgbAPI

    lgb原生庫APIsklearn庫下lgbAPI

經驗總結

在部落格中,我們主要完成了建模與調參的工作,首先在建模的過程中通過劃分資料集、交叉驗證等方式對模型的效能進行評估驗證,並通過視覺化方式繪製模型ROC曲線。

最後我們對模型進行調參,這部分介紹了貪心調參、網格搜尋調參、貝葉斯調參共三種調參手段,重點使用貝葉斯調參對本次專案進行簡單優化,大家在實際操作的過程中可以參考調參思路進行優化,不必拘泥於以上所寫的具體例項。

推薦部落格:

  • GBDT模型
    https://zhuanlan.zhihu.com/p/45145899
  • XGBoost模型
    https://blog.csdn.net/wuzhongqiang/article/details/104854890
  • LightGBM模型
    https://blog.csdn.net/wuzhongqiang/article/details/105350579
  • Catboost模型
    https://mp.weixin.qq.com/s/xloTLr5NJBgBspMQtxPoFA
  • 時間序列模型
    RNN:https://zhuanlan.zhihu.com/p/45289691
    LSTM:https://zhuanlan.zhihu.com/p/83496936

推薦教材:

  • 《機器學習》 https://book.douban.com/subject/26708119/

  • 《統計學習方法》 https://book.douban.com/subject/10590856/

  • 《面向機器學習的特徵工程》 https://book.douban.com/subject/26826639/

  • 《信用評分模型技術與應用》https://book.douban.com/subject/1488075/

  • 《資料化風控》https://book.douban.com/subject/30282558/

相關文章