上一節對XGBoost算法的原理和過程進行了描述,XGBoost在算法優化方面主要在原損失函數中加入了正則項,同時將損失函數的二階泰勒展開近似展開代替殘差(事實上在GBDT中葉子結點的最優值求解也是使用的二階泰勒展開(詳細上面Tips有講解),但XGBoost在求解決策樹和最優值都用到了),同時在求解過程中將兩步優化(求解最優決策樹和葉子節點最優輸出值)合并成為一步。本節主要對XGBoot進行實現并調參。
XGBoost框架及參數XGBoost原生框架與sklearn風格框架
XGBoost有兩個框架,一個是原生的XGBoost框架,另一個是sklearn所帶的XGBoost框架。二者實現基本一致,但在API的使用方法和參數名稱不同,在數據集的初始化方面也有不同。
XGBoost原生庫的使用過程如下:
其中主要是在DMatrix讀取數據和train訓練數據的類,其中DMatrix在原生XGBoost庫中的需要先把數據集按輸入特征部分、輸出特征部分分開,然后放到DMatrix中,即:
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=1) dtrain = xgb.DMatrix(X_train, y_train) dtest = xgb.DMatrix(X_test, y_test)
sklearn中參數都是寫在train中(不過也有使用原生XGBoost參數風格的sklearn用法),而原生庫中必須將參數寫入參數param的字典中,再輸入到train中,之所以這么做是因為XGBoost所涉及的參數實在過多,都放在一起太長也容易出錯,比如(參數含義后面再說):
param = {\'max_depth\':5, \'eta\':0.5, \'verbosity\':1, \'objective\':\'binary:logistic\'}
在模型訓練上,一種是使用原生XGBoost接口:
import xgboost as xgb model = xgb.train(param, dtrain,num_boost_round=10,evals=(),obj=None,feval=None,maximize=False,early_stopping_rounds=None)
第二種是使用sklearn風格的接口,sklearn風格的xgb有兩種,一種是分類用的,還有一個回歸用的:
model = xgb.XGBClassifier(\'max_depth\':5, \'eta\':0.5, \'verbosity\':1, \'objective\':\'binary:logistic\') model.fit(X_train, y_train, early_stopping_rounds=10, eval_metric="error",)
同時也可以sklearn風格下的原生參數param,只需傳入**param即可:
model = xgb.XGBClassifier(**param) model.fit(X_train, y_train, early_stopping_rounds=10, eval_metric="error")
上面就是XGBoost的大致使用過程,下面主要對模型中的參數進行說明。
XGBoost框架下的參數
由于XGBoost原生庫與sklearn庫的參數在名字上有一定的差異,既然有sklearn風格的接口,為了與其他算法保持一致,這里主要對sklearn風格的參數進行說明,并盡量與原生庫與GBDT中進行對應。
首先參數主要包括三個方面:XGBoost框架參數、弱學習器參數以及其他學習參數。
XGBoost框架參數:
這方面參數主要包括3個:booster、、。
弱學習器參數:
由于默認學習器為gbtree,其效果較好,這里只介紹gbtree相關參數,其包含參數較多,大多與GBDT中的一樣,下面一一說明:
該參數與GBDT中的類似,但又不完全一樣,XGBoost中是最小樣本權重和,GBDT中限定的是樣本的數量,在原生庫中該參數一致;
上面除了,其他基本都是需要進行調參的參數,一般先調,,,和gamma,如果還是過擬合,繼續調節后面的參數。
其他參數
其他參數主要用于控制XGBoost性能以及結果的相關參數,主要有以下這些:
以上就是XGBoost的基本參數,通常先通過網格搜索找出比較合適的和的組合,然后調整和gamma,查看模型的處于什么樣的狀態(過擬合還是欠擬合)然后再決定是否進行剪枝調整其他參數,通常來說是需要進行剪枝的,為了增強模型的泛化能力,因為XGB屬于天然過擬合模型。
XGBoost的實例及調參
下面我們就來使用XGBoost進行分類,數據集采用Kaggle入門比賽中的Titanic數據集來根據乘客特征,預測是否生存,數據集的下載可以在官網網站中:,數據集包含train、test和gender ,首先來導入數據并對數據進行有個初步的認知:
import pandas as pd import numpy as np import seaborn as sns import missingno as msno_plot import matplotlib.pyplot as plt train_data = pd.read_csv(\'./train.csv\') test_data = pd.read_csv(\'./test.csv\') train_data.describe() test_data.describe()
可以看到訓練數據有891條,測試集有418條數據。由于我們是要利用乘客特征預測是否生還“Survive”,測試集上沒有直接給出結果,因此我們就拿訓練集上的數據進行訓練和測試。(由于目前Kaggle需要爬梯子提交,暫時先不提交驗證了)。
然后是對數據進行一個初步的分析,首先查看是否存在缺失值:
plt.figure(figsize=(10, 8))
msno_plot.bar(train_data)
可以看到Age屬性和Cabin屬性還是存在很多缺失值的,同時由于生存與否與姓名、、Ticket無關(主觀認知),這里先對這幾個屬性特征進行刪除,由于Cabin缺失值較多,且為非數值型數據,這里也暫且刪除,然后將Age屬性缺失值填補Age的均值(為簡單起見,還有其他很多方式),Embark屬性填補出現最多的值:
# 刪除屬性 train_data.drop([\'Name\', \'PassengerId\', \'Ticket\', \'Cabin\'], axis=1, inplace=True) test_data.drop([\'Name\', \'PassengerId\', \'Ticket\', \'Cabin\'], axis=1, inplace=True) # 填補缺失值 train_data[\'Age\'].fillna(30, inplace=True) train_data[\'Embarked\'].fillna(\'S\', inplace=True) test_data[\'Age\'].fillna(30, inplace=True) test_data[\'Embarked\'].fillna(\'S\', inplace=True) for i in range(8): plt.subplot(241+i) sns.countplot(x=train_data.iloc[:, i])
填補后的數據分布如上圖中,然后就是對一些屬性進行數值化處理,主要有Sex和Embark:
train_data[\'Sex\'].replace(\'male\', 0, inplace=True) train_data[\'Sex\'].replace(\'female\', 1, inplace=True) train_data[\'Embarked\'] = [0 if example == \'S\' else 1 if example==\'Q\' else 2 for example in train_data[\'Embarked\'].values.tolist()]
這里數據就初步處理完成了,處理后的數據包含7個特征和1個類別“Survive”,然后就要開始利用XGBoost對數據進行分類了,首先導入所需要的包:
from sklearn.model_selection import train_test_split import xgboost as xgb from sklearn.model_selection import GridSearchCV from sklearn import metrics
將數據進一步分為訓練集和測試集兩部分:
trainX, testX, trainy, testy = train_test_split(train_data.drop([\'Survived\'], axis=1), train_data[\'Survived\'], test_size=0.2, random_state=10)
然后我們初始隨便給定一組參數建立一個模型:
model = xgb.XGBClassifier(learning_rate=0.1, n_estimators=100, max_depth=6, min_child_weight=1, gamma=0, subsample=1, objective=\'binary:logistic\') model.fit(trainX, trainy) print(model.score(trainX, trainy))
#0.9269662921348315
可以看到在訓練集上的分數已經很高了,然后利用進行調參,首先調整和兩個參數,調參方法跟GBDT中一樣:
gsearch = GridSearchCV(estimator=model, param_grid={\'n_estimators\': range(10, 301, 10), \'max_depth\': range(2, 7, 1)}) 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_score_) print(gsearch.best_params_)
# 0.8244262779474048
# {\'max_depth\': 5, \'n_estimators\': 30}
可以看到分數降低了很多,說明原先模型確實存在過擬合,接下來繼續調整gamma和參數:
model2 = xgb.XGBClassifier(learning_rate=0.1, n_estimators=30, max_depth=5, min_child_weight=1, gamma=0, subsample=1, objective=\'binary:logistic\', random_state=1) gsearch = GridSearchCV(estimator=model2, param_grid={\'gamma\': np.linspace(0, 1, 11), \'subsample\': np.linspace(0.1, 1, 10)}) 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_score_) print(gsearch.best_params_)
# 0.825824879346006
# {\'gamma\': 0.7, \'subsample\': 0.7}
分數略微提升了一些,繼續調整參數:
model3 = xgb.XGBClassifier(learning_rate=0.1, n_estimators=30, max_depth=5, min_child_weight=1, gamma=0.7, subsample=0.7, objective=\'binary:logistic\', random_state=1, silent=False) gsearch = GridSearchCV(estimator=model3, param_grid={\'min_child_weight\': range(1, 11)}) gsearch.fit(trainX, trainy)
# 0.825824879346006
# {\'min_child_weight\': 1}
分數已經不再提升了,應該是已經達到極限了,再進行正則化也沒有意義了,這里嘗試了一下:
model4 = xgb.XGBClassifier(learning_rate=0.1, n_estimators=30, max_depth=5, min_child_weight=1, gamma=0.7, subsample=0.7, objective=\'binary:logistic\', random_state=1) gsearch = GridSearchCV(estimator=model4, param_grid={\'reg_lambda\': [1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 1, 10, 100, 1000]}) gsearch.fit(trainX, trainy)
# 0.825824879346006
# {\'reg_lambda\': 1}
已經不能夠再提升了,然后再次嘗試改變和的值,成倍的放大和縮小相應的值,縮小10倍,放大10倍:
odel3 = xgb.XGBClassifier(learning_rate=0.01, n_estimators=300, max_depth=5, min_child_weight=1, gamma=0.7, subsample=0.7, objective=\'binary:logistic\', random_state=1, silent=False) model3.fit(trainX, trainy) model3.score(trainX, trainy)
# 0.848314606741573
再進一步提升發現分數已經不再增長,然后確定最終模型即為上述的model3,然后最后進行訓練:
model3.fit(trainX, trainy, early_stopping_rounds=10, eval_metric=\'error\', eval_set=[(testX, testy)]) print(model3.score(trainX, trainy)) print(model3.score(testX, testy))
# 0.84831460674157
# 0.875
利用前面GBDT算法,找到的一組參數,所帶來的表現跟XGBoost差不多,略微低于XGBoost一點:
model3_2 = GradientBoostingClassifier(n_estimators=80, learning_rate=0.1, subsample=0.7, max_depth=5, max_features=7, min_samples_leaf=31, min_samples_split=17)
model3_2.fit(trainX, trainy)
print(model3_2.score(trainX, trainy))
print(model3_2.score(testX, testy))
# 0.875
# 0.
但總體來說準確率并不是很高,這個可能數據處理不當等問題吧,后面會找一下原因,該數據集只作為算法的訓練和熟悉用,在某乎上搜到一些原因:“一般來說姓名對于預測能否生還沒有太大的價值,但在這個賽題的設置下,適當的考慮姓名可以發揮意想不到的作用。如訓練集中頭等艙一個姓Abel(隨便起的,但是ms確實有這樣的實例)的男性生還了,那么測試集中頭等艙同樣姓Abel的女子和小孩則很可能也能夠生還,因為一家子基本上男的活下來了老婆孩子也問題不大”。所以這里就不深究了,后面會找其他一些數據集再進行測試和訓練。
到這里集成學習內容已基本完了,后面還是要利用一些有價值的數據集進行實戰,此外還有一個算法,但在算法本身的優化的內容不多。更多的還是運行速度提升和內存占用降低。唯一值得討論的是它的決策樹深度分裂方式,相比之下XGBoost使用的決策樹廣度分裂方式。后面有時間會對這一算法進行了解,之后可能在進行算法學習的同時對機器學習的一些基礎知識進行整理和回顧。