100天搞定機(jī)器學(xué)習(xí)|Day61 手算+可視化,終于徹底理解了 XGBoost
↑↑↑點(diǎn)擊上方藍(lán)字,回復(fù)資料,10個(gè)G的驚喜
我們已經(jīng)學(xué)習(xí)了XGBoost淵源及優(yōu)點(diǎn)、模型原理及優(yōu)化推導(dǎo)、模型參數(shù)解析:100天搞定機(jī)器學(xué)習(xí)|Day60 遇事不決,XGBoost,文章有點(diǎn)太枯燥,大家可能對其中的公式推導(dǎo)和參數(shù)還不甚理解。
今天我們以西瓜數(shù)據(jù)集為例,配合手算,拆開揉碎,深入理解公式與代碼之間的內(nèi)在聯(lián)系,然后用可視化的方式更形象地看透 XGBoost 的原理!
本文又硬又干,歡迎同學(xué)們來個(gè)素質(zhì)三連:在看、收藏、轉(zhuǎn)發(fā),沒有關(guān)注的同學(xué)也來個(gè)關(guān)注下哈↓↓↓↓↓↓
#本文用到的庫
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn import preprocessing
from xgboost.sklearn import XGBClassifier
from xgboost import plot_tree
import matplotlib.pyplot as plt
from xgboost import plot_importance
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import classification_report, precision_recall_curve
西瓜數(shù)據(jù)集及預(yù)處理
def getDataSet():
dataSet = [
['青綠', '蜷縮', '濁響', '清晰', '凹陷', '硬滑', 0.697, 0.460, 1],
['烏黑', '蜷縮', '沉悶', '清晰', '凹陷', '硬滑', 0.774, 0.376, 1],
['烏黑', '蜷縮', '濁響', '清晰', '凹陷', '硬滑', 0.634, 0.264, 1],
['青綠', '蜷縮', '沉悶', '清晰', '凹陷', '硬滑', 0.608, 0.318, 1],
['淺白', '蜷縮', '濁響', '清晰', '凹陷', '硬滑', 0.556, 0.215, 1],
['青綠', '稍蜷', '濁響', '清晰', '稍凹', '軟粘', 0.403, 0.237, 1],
['烏黑', '稍蜷', '濁響', '稍糊', '稍凹', '軟粘', 0.481, 0.149, 1],
['烏黑', '稍蜷', '濁響', '清晰', '稍凹', '硬滑', 0.437, 0.211, 1],
['烏黑', '稍蜷', '沉悶', '稍糊', '稍凹', '硬滑', 0.666, 0.091, 0],
['青綠', '硬挺', '清脆', '清晰', '平坦', '軟粘', 0.243, 0.267, 0],
['淺白', '硬挺', '清脆', '模糊', '平坦', '硬滑', 0.245, 0.057, 0],
['淺白', '蜷縮', '濁響', '模糊', '平坦', '軟粘', 0.343, 0.099, 0],
['青綠', '稍蜷', '濁響', '稍糊', '凹陷', '硬滑', 0.639, 0.161, 0],
['淺白', '稍蜷', '沉悶', '稍糊', '凹陷', '硬滑', 0.657, 0.198, 0],
['烏黑', '稍蜷', '濁響', '清晰', '稍凹', '軟粘', 0.360, 0.370, 0],
['淺白', '蜷縮', '濁響', '模糊', '平坦', '硬滑', 0.593, 0.042, 0],
['青綠', '蜷縮', '沉悶', '稍糊', '稍凹', '硬滑', 0.719, 0.103, 0]
]
features = ['color', 'root', 'knocks', 'texture', 'navel', 'touch', 'density', 'sugar','good']
dataSet = np.array(dataSet)
df = pd.DataFrame(dataSet,columns=features)
for feature in features[0:6]:
le = preprocessing.LabelEncoder()
le = le.fit(df[feature])
df[feature] = le.transform(df[feature])
df.iloc[:,6:8]=df.iloc[:,6:8].astype(float)
df['good']=df['good'].astype(int)
return df
本文中,我們使用sklearn風(fēng)格的接口,并使用sklearn風(fēng)格的參數(shù)。xgboost.XGBClassifier實(shí)現(xiàn)了scikit-learn 的分類模型API:
xgboost.XGBClassifier(max_depth=3, learning_rate=0.1, n_estimators=100,
silent=True, objective='binary:logistic', booster='gbtree', n_jobs=1,
nthread=None, gamma=0, min_child_weight=1, max_delta_step=0, subsample=1,
colsample_bytree=1, colsample_bylevel=1, reg_alpha=0, reg_lambda=1,
scale_pos_weight=1, base_score=0.5, random_state=0, seed=None,
missing=None, **kwargs)
為方便手算,我們設(shè)n_estimators=2,即XGBoost僅2棵樹,正則項(xiàng),系數(shù)=1,gamma=0。
#訓(xùn)練模型
df = getDataSet()
X, y = df[df.columns[:-1]],df['good']
X_train, X_test, y_train, y_test = train_test_split(X,y, random_state=0)
sklearn_model_new = XGBClassifier(n_estimators=2,max_depth=5,learning_rate= 0.1, verbosity=1, objective='binary:logistic',random_state=1)
sklearn_model_new.fit(X_train, y_train)
model.fit(X_train, y_train)
XGBoost可視化
現(xiàn)在看一下XGBoost的內(nèi)部結(jié)構(gòu),看看樹的形狀。XGBoost可視化可使用xgboost.plot_tree方法:
xgboost.plot_tree(booster, fmap='', num_trees=0, rankdir='UT', ax=None, **kwargs)
參數(shù):
booster:一個(gè)Booster對象, 一個(gè) XGBModel 對象
fmap:一個(gè)字符串,給出了feature map 文件的文件名
num_trees:一個(gè)整數(shù),制定了要繪制的子數(shù)的編號。默認(rèn)為 0
rankdir:一個(gè)字符串,它傳遞給graphviz的graph_attr
ax:一個(gè)matplotlib Axes 對象。特征重要性將繪制在它上面。如果為None,則新建一個(gè)Axes
kwargs:關(guān)鍵字參數(shù),用于傳遞給graphviz 的graph_attr
XGBoost很多函數(shù)會用的一個(gè)參數(shù)fmap (也就是feature map),但是文檔里面基本沒解釋這個(gè)fmap是怎么產(chǎn)生的,Kaggle上有好心人提供了解決方案: https://www.kaggle.com/mmueller/xgb-feature-importance-python
def ceate_feature_map(features):
outfile = open('xgb.fmap', 'w')
i = 0
for feat in features:
outfile.write('{0}\t{1}\tq\n'.format(i, feat))
i = i + 1
outfile.close()
ceate_feature_map(df.columns)
這個(gè)函數(shù)就是根據(jù)給定的特征名字(直接使用數(shù)據(jù)的列名稱), 按照特定格式生成一個(gè)xgb.fmap文件, 這個(gè)文件就是XGBoost文檔里面多次提到的fmap, 注意使用的時(shí)候, 直接提供文件名, 比如fmap='xgb.fmap'.
有了fmap, 在調(diào)用plot_tree函數(shù)的時(shí)候, 直接指定fmap文件即可:
plot_tree(fmap='xgb.fmap')
調(diào)整清晰度需要使用plt.gcf()方法
plot_tree(sklearn_model_new,fmap='xgb.fmap',num_trees=0)
fig = plt.gcf()
fig.set_size_inches(150, 100)
plt.show()
plot_tree(sklearn_model_new,fmap='xgb.fmap',num_trees=1)
fig = plt.gcf()
fig.set_size_inches(150, 100)
plt.show()
重頭戲分割線————手算
先看看X_train
第一棵樹僅以含糖率為分割點(diǎn),對其排序,分割點(diǎn)為兩點(diǎn)間的均值

0.186為何可以成為根節(jié)點(diǎn)呢?這個(gè)咱們待會兒再算。
那我們就算一下,不過在此之前還要再復(fù)習(xí)一下?lián)p失函數(shù) logloss:
由于后面需要用到logloss的一階導(dǎo)數(shù)以及二階導(dǎo)數(shù),這里先簡單推導(dǎo)一下。
其中
在一階導(dǎo)的基礎(chǔ)上再求一次有(其實(shí)就是sigmod函數(shù)求導(dǎo))
base_score初始值 0.5 ,我們計(jì)算每個(gè)樣本的一階導(dǎo)數(shù)值和二階導(dǎo)數(shù)值
樣本的一階導(dǎo)數(shù)值:
樣本的二階導(dǎo)數(shù)值:
=2.5
=-2.5
=1.25
=1.75
計(jì)算最優(yōu)的權(quán)重:
所以:
=-0.111111111
=0.090909091
tips:上述結(jié)果直接計(jì)算與圖中不符,這里要記得乘以學(xué)習(xí)率:0.1
第一棵樹即為:
回答開頭的問題,第一棵樹為何以含糖率的0.1856為根節(jié)點(diǎn)?必然是所有特征中此處分割的 gain 最大!看一下這個(gè)公式
計(jì)算結(jié)果為5.0505,其他特征的gain小于它,原理類似這里就不挨個(gè)算了。
另一個(gè)問題,為何分裂到此怎么就停了呢?
XGBClassifier參數(shù)min_child_weight默認(rèn)值 1 ,是葉子節(jié)點(diǎn)包含樣本的所有二階偏導(dǎo)數(shù)之和,代表子節(jié)點(diǎn)的權(quán)重閾值。它刻畫的是:對于一個(gè)葉子節(jié)點(diǎn),當(dāng)對它采取劃分之后,它的所有子節(jié)點(diǎn)的權(quán)重之和的閾值。如果它的所有子節(jié)點(diǎn)的權(quán)重之和小于該閾值,則該葉子節(jié)點(diǎn)不值得繼續(xù)分裂。
本例中如再以0.344處或其他特征某處分裂則必有>1,所以就不分裂了。min_child_weight的值較大時(shí),可以避免模型學(xué)習(xí)到局部的特殊樣本。本例如將其值改為0.2,就會發(fā)現(xiàn)還會分裂(如下圖):
還以min_child_weight默認(rèn)值為 1,然后,生成第二棵樹,此處僅需按第一棵樹的預(yù)測結(jié)果更新base_score的值,注意:預(yù)測結(jié)果要經(jīng)過sigmod映射,即當(dāng)預(yù)測為0時(shí),base_score更新為
當(dāng)預(yù)測為0時(shí),base_score更新為
進(jìn)一步計(jì)算,和,大家可以試一下,結(jié)果如圖。
因?yàn)槲覀冎挥袃煽脴洌卣鬟x擇僅使用了含糖率,分裂了兩次,so,特征重要性為2
plot_importance(sklearn_model_new)
XGBoost網(wǎng)格搜索調(diào)參
參數(shù)調(diào)整是機(jī)器學(xué)習(xí)中的一門暗藝術(shù),模型的最優(yōu)參數(shù)可以依賴于很多場景。所以要創(chuàng)建一個(gè)全面的指導(dǎo)是不可能的。XGBoost使用sklearn風(fēng)格的接口,并使用網(wǎng)格搜索類GridSeachCV來調(diào)參,非常方便。gsCv.best_params_獲取最優(yōu)參數(shù),添加新的參數(shù)進(jìn)來,然后再次GridSearchCV。
也可以一把梭哈,把重要參數(shù)一次懟進(jìn)去。
gsCv = GridSearchCV(sklearn_model_new,
{'max_depth': [4,5,6],
'n_estimators': [5,10,20],
'learning_rate ': [0.05,0.1,0.3,0.5,0.7],
'min_child_weight':[0.1,0.2,0.5,1]
})
gsCv.fit(X_train,y_train)
print(gsCv.best_params_)
{'learning_rate ': 0.05, 'max_depth': 4, 'min_child_weight': 0.1, 'n_estimators': 5} 以此參數(shù)重新訓(xùn)練即可。
也可以加一下老胡的微信 圍觀朋友圈~~~
推薦閱讀
(點(diǎn)擊標(biāo)題可跳轉(zhuǎn)閱讀)
所以,機(jī)器學(xué)習(xí)和深度學(xué)習(xí)的區(qū)別是什么? 機(jī)器學(xué)習(xí)避坑指南:訓(xùn)練集/測試集分布一致性檢查 機(jī)器學(xué)習(xí)深度研究:特征選擇中幾個(gè)重要的統(tǒng)計(jì)學(xué)概念 老鐵,三連支持一下,好嗎?↓↓↓




