前面對GBDT的算法原理進行了描述,通過前文了解到GBDT是以回歸樹為基分類器的集成學習模型,既可以做分類,也可以做回歸,由於GBDT設計很多CART決策樹相關內容,就暫不對其算法流程進行實現,本節就根據具體數據,直接利用Python自帶的Sklearn工具包對GBDT進行實現。
數據集采用之前決策樹中的紅酒數據集,之前的數據集我們做了類別的處理(將連續的數據刪除了,且小批量數據進行了合並),這里做同樣的處理,將其看為一個多分類問題。
首先依舊是讀取數據,並對數據進行檢查和預處理,這里就不再贅述,所得數據情況如下:
wine_df = pd.read_csv('./winequality-red.csv', delimiter=';', encoding='utf-8') columns_name = list(wine_df.columns) for name in columns_name: q1, q2, q3 = wine_df[name].quantile([0.25, 0.5, 0.75]) IQR = q3 - q1 lower_cap = q1 - 1.5 * IQR upper_cap = q3 + 1.5 * IQR wine_df[name] = wine_df[name].apply(lambda x: upper_cap if x > upper_cap else (lower_cap if (x < lower_cap) else x))
sns.countplot(wine_df['quality']) wine_df.describe()
接下來就是先導入使用GBDT所需要用到的工具包:
# 這里采用的是分類,因此是GradientBoostingClassifier,如果是回歸則使用GradientBoostingRegressor from sklearn.ensemble import GradientBoostingClassifier from sklearn.metrics import mean_squared_error from sklearn import metrics from sklearn.model_selection import train_test_split import matplotlib.pyplot as plt
然后依舊是對數據進行切分,將數據分為訓練集和測試集:
trainX, testX, trainY, testY = train_test_split(wine_df.drop(['quality']), wine_df['quality'], test_size=0.3, random_state=22)
然后就是建立模型:
model = GradientBoostingClssifier()
這里模型就有很多可選參數,用於調整模型,下面進行具體介紹:
首先是Boosting的框架的參數,這里在使用GradientBoostingRegressor和GradientBoostingClassifier是一樣的,具體包括:
- n_estimators:弱分類器的最大迭代次數,也就是多少個弱分類器組成,默認值為100。當該值越小時容易欠擬合,太大又會過擬合;
- learning_rate:學習率,這在前面原理部分有進行介紹,默認值為1,較小的learning_rate意味着步長較小,需要更多的分類器才能夠達到效果,通常需要與上面n_eatimators結合共同調參;
- subsample:這也是一種正則化的方法,在正則化中有提到過,取值為(0,1],默認值為1,即不進行子采樣;
- init:初始化的弱分類器,即f0(x),默認為采用訓練樣本初始化的分類回歸預測,若賦予值需要根據一定的先驗知識或者預擬合;
- loss:即損失函數,在原理篇介紹過相關損失函數,對於分類和回歸中損失函數是不相同的:
- 在分類模型中,有對數似然損失函數“deviance”和指數損失函數“exponential”,默認為對數似然損失函數,一般選擇默認,因為選擇指數損失函數“exponential”又退回到AdaBoost了;
- 在回歸模型中,有方差損失“ls”、絕對損失“lad”、Huber損失“huber”和分位數損失“quantile”,默認為均方差損失“ls”,一般來說,數據的噪音不多,采用均方差損失即可,噪音點較多,則推薦使用抗噪能力較強的Huber損失,如果需要對訓練集進行分段預測時則采用分位數損失“quantile”;
- alpha:這個參數只存在於GradientBoostingRegressor中,當使用Huber損失和分位數損失時,需要指定分位數的值,默認為0.9,如果噪音數據較多,可以適當降低這個值。
然后就是弱分類器有關的參數值,弱分類器采用的CART回歸樹,決策樹中的相關參數在決策樹實現部分已經進行介紹,這里主要對其中一些重要的參數再進行解釋:
- max_features:划分樹時所用到的最大特征數,默認為None,即使用全部的特征,當取值為“log2”時,划分時使用log2N個特征,如果是“sqrt”和“auto”則分別對應着最多考慮√N¯個特征,如果是整數,則代表考慮特征的絕對數,如果是浮點數,則代表考慮總特征數的百分比。一般來說樣本總特征數小於50,直接采用50即可,當樣本特征數量較大時,再考慮其他特征數;
- max_depth:每個弱分類器的最大深度,默認為不輸入,樹的深度為3,一般對於數據較少或者特征較少,該值不需要輸入,當樣本數量和特征數量過於龐大,推薦使用最大深度限制,一般選擇10~100;
- min_samples_split:內部節點再划分所需最小的樣本數,它限制了子樹進一步划分的條件,如果節點的樣本數小於min_samples_split則不再進行分裂。默認值為2,若樣本數量較大,則推薦增大該值;
- min_samples_leaf:葉子節點最小樣本數,該值限定了葉子節點的最小樣本數,默認值為1,如果葉子節點樣本數量小於該值,則會和兄弟節點一起被剪枝,如果樣本量巨大,則推薦增加該值;
- min_weight_fraction_leaf:葉子節點最小樣本權重和,該值限制了葉子節點所有樣本權重和的最小值,若小於該值,則會和兄弟節點被剪枝,默認值為0,即不考慮權重。當樣本分類問題中類別分布偏差較大,則會引入樣本權重,需要調整該值;
- max_leaf_nodes:最大葉子節點數,通過限制最大葉子結點數防止過擬合,默認為None。如果加入了限制,則算法會建立在最大葉子節點數內最優的決策樹,當樣本特征數量過多的話,可以限制該值;
- min_impurity_split:節點划分最小不純度,這個值限定了決策樹的生長,若節點的不純度(即基尼系數)小於這個值,則該節點不再生長,即為葉子結點,默認值為1e-7,一般不推薦修改。
上述即為模型的主要參數,這里首先全部使用默認值,對樣本進行訓練:
model.fit(trainX, trainY)
print("模型在訓練集上分數為%s"%model.score(trainX, trainY))
pred_prob = model.predict_proba(trainX)
print('AUC:', metrics.roc_auc_score(np.array(trainY), pred_prob, multi_class='ovo'))
模型在訓練集上分數為0.8817106460418562
AUC: 0.9757763363472337
可以看到在訓練集上AUC表現還不錯,模型的分數但並不高,嘗試調整訓練參數,首先對於迭代次數和學習率共同進行調整:
param_test1 = {'n_estimators': range(10, 501, 10), 'learning_rate': np.linspace(0.1, 1, 10)} gsearch = GridSearchCV(estimator=GradientBoostingClassifier(learning_rate=1, min_samples_split=2, min_samples_leaf=1, max_depth=3, max_features=None, subsample=0.8, ), param_grid=param_test1, cv=5) gsearch.fit(trainX, trainY) means = gsearch.cv_results_['mean_test_score'] params = gsearch.cv_results_['params'] for i in range(len(means)): print(params[i], means[i]) print(gsearch.best_params_) print(gsearch.best_score_) # {'learning_rate': 0.2, 'n_estimators': 100}
找出最好的n_estimators=100和learning_rate=0.2,將其定下來,帶回模型,再次驗證:
model = GradientBoostingClassifier(n_estimators=100, learning_rate=0.2,subsample=0.8) model.fit(trainX, trainY) print("模型在訓練集上分數為%s"%model.score(trainX, trainY)) pred_prob = model.predict_proba(trainX) print('AUC:', metrics.roc_auc_score(np.array(trainY), pred_prob, multi_class='ovo'))
模型在訓練集上分數為0.9663330300272975
AUC: 0.9977791940084874
可以看到擬合效果已經很好了,再次調整參數,接下來調整弱分類器中的參數,max_depth和min_samples_split:
param_test1 = {'max_depth': range(1, 6, 1), 'min_samples_split': range(1, 101, 10)} gsearch2 = GridSearchCV(estimator=GradientBoostingClassifier(n_estimators=100, learning_rate=0.2, max_features=None, min_samples_leaf=1, subsample=0.8, ), param_grid=param_test1, cv=5) gsearch2.fit(trainX, trainY) means = gsearch2.cv_results_['mean_test_score'] params = gsearch2.cv_results_['params'] for i in range(len(means)): print(params[i], means[i]) print(gsearch2.best_params_) print(gsearch2.best_score_)
找出了樹的最大深度為5,由於最小樣本划分數量同葉子節點最小樣本數量有一定關系,暫時不能定下min_samples_split,將其同min_samples_leaf共同調整:
param_test1 = {'min_samples_leaf': range(1, 101, 10), 'min_samples_split': range(1, 101, 10)} gsearch3 = GridSearchCV(estimator=GradientBoostingClassifier(n_estimators=100, learning_rate=0.2, max_features=None, max_depth=5, subsample=0.8, ), param_grid=param_test1, cv=5) gsearch3.fit(trainX, trainY) means = gsearch3.cv_results_['mean_test_score'] params = gsearch3.cv_results_['params'] for i in range(len(means)): print(params[i], means[i]) print(gsearch3.best_params_) print(gsearch3.best_score_) # {'min_samples_leaf': 21, 'min_samples_split': 41}
可以找出最小樣本划分數量21和葉子節點最小數量,我們將這些參數再帶回模型:
model = GradientBoostingClassifier(n_estimators=100, learning_rate=0.2, max_depth=5, min_samples_leaf=21, min_samples_split=41, subsample=0.8) model.fit(trainX, trainY) print("模型在訓練集上分數為%s"%model.score(trainX, trainY)) pred_prob = model.predict_proba(trainX) print('AUC:', metrics.roc_auc_score(np.array(trainY), pred_prob, multi_class='ovo'))
模型在訓練集上分數為1.0
AUC: 1.0
可以看到在訓練集上已經完美擬合了,但為了驗證模型,我們需要再分離出一部分用於驗證模型的數據集:
validX, tX, validY, tY = train_test_split(testX, testY, test_size=0.2)
然后使用驗證集,驗證模型:
print("模型在測試集上分數為%s"%metrics.accuracy_score(validY, model.predict(validX))) pred_prob = model.predict_proba(validX) print('AUC test:', metrics.roc_auc_score(np.array(validY), pred_prob, multi_class='ovo'))
模型在測試集上分數為0.726790450928382
AUC test: 0.8413890948027345
可以看到模型在驗證集上表現並不是很好,上面模型存在一定的過擬合問題,繼續調整參數,通過調整max_features來提高模型的泛華能力:
param_test1 = {'max_features': range(3, 12, 1)} gsearch4 = GridSearchCV(estimator=GradientBoostingClassifier(n_estimators=100, learning_rate=0.2, min_samples_leaf=21, min_samples_split=41, max_depth=5, subsample=0.8, ), param_grid=param_test1, cv=5) gsearch4.fit(trainX, trainY) means = gsearch4.cv_results_['mean_test_score'] params = gsearch4.cv_results_['params'] for i in range(len(means)): print(params[i], means[i]) print(gsearch4.best_params_) print(gsearch4.best_score_) # {'max_features': 5}
進一步調整subsamples:
param_test1 = {'subsample': np.linspace(0.1, 1, 10)} gsearch5 = GridSearchCV(estimator=GradientBoostingClassifier(n_estimators=100, learning_rate=0.2, min_samples_leaf=21, min_samples_split=41, max_depth=5, max_features=5 ), param_grid=param_test1, cv=5) gsearch5.fit(trainX, trainY) means = gsearch5.cv_results_['mean_test_score'] params = gsearch5.cv_results_['params'] for i in range(len(means)): print(params[i], means[i]) print(gsearch5.best_params_) print(gsearch5.best_score_) # {'subsample': 0.7}
到這里基本主要參數都進行了調整,帶回到模型中:
model = GradientBoostingClassifier(n_estimators=100, learning_rate=0.2, max_depth=5, min_samples_leaf=21, min_samples_split=41, max_features=5, subsample=0.7) model.fit(trainX, trainY) print("模型在訓練集上分數為%s"%model.score(trainX, trainY)) pred_prob = model.predict_proba(trainX) print('AUC:', metrics.roc_auc_score(np.array(trainY), pred_prob, multi_class='ovo'))
模型在訓練集上分數為0.9990900818926297
AUC: 0.9999992641648271
有略微下降,因為通過提高模型的泛華能力,會增大模型的偏差,然后利用驗證集驗證模型:
print("模型在測試集上分數為%s"%metrics.accuracy_score(validY, model.predict(validX))) pred_prob = model.predict_proba(validX) print('AUC test:', metrics.roc_auc_score(np.array(validY), pred_prob, multi_class='ovo')) 模型在測試集上分數為0.7161803713527851 AUC test: 0.8429467644071055
進一步將模型的迭代次數增加一倍,學習率減半:
model = GradientBoostingClassifier(n_estimators=200, learning_rate=0.1, max_depth=5, min_samples_leaf=21, min_samples_split=41, max_features=5, subsample=0.7) model.fit(trainX, trainY) print("模型在訓練集上分數為%s"%model.score(trainX, trainY)) pred_prob = model.predict_proba(trainX) print('AUC:', metrics.roc_auc_score(np.array(trainY), pred_prob, multi_class='ovo')) # validX, tX, validY, tY = train_test_split(testX, testY, test_size=0.2) print("模型在測試集上分數為%s"%metrics.accuracy_score(validY, model.predict(validX))) pred_prob = model.predict_proba(validX) print('AUC test:', metrics.roc_auc_score(np.array(validY), pred_prob, multi_class='ovo'))
模型在訓練集上分數為0.9990900818926297
AUC: 1.0
模型在測試集上分數為0.7427055702917772
AUC test: 0.851199242237048
可以看到模型泛化能力有略微增強,可以嘗試進一步上述步驟,當迭代次數增加到一定程度,學習率減小到一定程度,模型泛化能力下降,可能是由於步長過小導致擬合和泛化能力下降。
以上就是GBDT的一個實例和參數調整過程,這里使用的是CvGrid網格遍歷搜索調參的方法,從結果來看並不理想,可能是由於樣本分布的問題,還有就是在進行數據處理的時候采用了replace直接更改了樣本的標簽,在測試集中這部分數據可能會預測錯誤。 后面會繼續查找原因,調整數據集再次進行訓練。
本例僅作為一個調參的學習過程,主要對GBDT中的參數有一個初步的了解,也是剛開始學習調參的方法,參數設置的也比較粗糙,后續會找一些新的數據集進一步對調參進行學習。