机器学习(九):Xgboost原理与案例分析

一、简介

Xgboost(extreme Gradient Boosting,极端梯度提升)经常被用在一些比赛中,效果显著。它是大规模并行的boosted tree的工具,是目前最快最好的开源boosted tree工具包。XGBoost所应用的算法就是GBDT(gradient boosting decision tree)的改进,使之更强大,适用于更大的范围,既可以用于分类也可以用于回归问题。Boosting算法的思想是将许多弱分类器集成在一起,形成一个强分类器。因为Xgboost是一种提升树,所以它是将许多树模型集成在一起,形成一个很强的分类器。Xgboost一般和sklearn一起使用,但是由于sklearn中没有集成Xgboost,所以需要单独下载安装。

  • 主要创新点
    • 设计和构建高度可扩展的端到端提升树系统
    • 提出了一个理论上合理的加权分位数略图来计算候选集
    • 引入了一种新颖的稀疏感知算法用于并行树学习,令缺失值有默认方向
    • 提出了一个有效的用于核外树形学习的缓存感知块结构。用缓存加速寻找排序后被打乱的索引的列数据的过程。

二、原理

1、Xgboost预测

Xgboost算法是和决策树算法联系到一起的,所以我们对不同叶子结点分配不同的权重项。此时我们的预测值,\widehat{y}_{i}=\sum_{j}w_{j}x_{ij},其中w_{j}是权重项,x_{ij}是叶子结点我们的目标函数,l(y_{i},\widehat{y}_{i})=(y_{i}-\widehat{y}_{i})^2最优函数解,F^*{(\vec{x})}=arg min E_{(x,y)}[L(y,F((\vec x))],对损失函数在整个样本上计算平均值集成算法的表示,\widehat{y}_{i}=\sum_{k=1}^{K}f_{k}(x_{i}),f_{k} \in \tau我们展开来表示一下,

一开始树是0,然后往里面加数,相当于多了一个函数,再加第二棵树,相当于又多了一个函数...,这里需要保证加入新的函数能够提升整体的表达效果。提升表达效果的意思就是加上新的树之后,目标函数(就是损失)的值会下降。

2、树的复杂度

接下来定义树的复杂度,对于f_{t}的定义做一下细化,把树拆分成结构函数q(输入x输出叶子结点索引)和叶子权重部分w(输入叶子结点索引输出叶子结点分数),结构函数q把输入映射到叶子的索引号上面去,而w给定了每个索引号对应的叶子分数是什么。


如果叶子结点的个数太多,那么过拟合的风险会越大,所以要限制叶子结点的个数,所以在原来目标函数里加上一个正则化惩罚项,我们来看一下如何计算,
此时的目标函数就变成了,

3、泰勒展开求解目标函数

为了求解这个方程式,我们采用泰勒展开近似来定义一个近似的目标函数,

我们发现真实值与上一轮的预测值之间的差值是一个常数项,这个常数项可以移除,就变成了
我们将q与j合并继续进行计算,
其中被定义为每个叶子上面样本集合(每个叶子结点里面样本集合);等价于求出的值(每一个样本所在叶子索引的分数);为叶子结点数量。定义(每个叶子结点里面一阶梯度的和)(每个叶子结点里面二阶梯度的和),
目标函数相应改变为,
求偏导,
我们将求出的带回到目标函数得到,
Obj代表了当我们指定一个树的结构的时候,我们在目标上面最多减少多少,可叫做结构分数,可以认为这个就是类似基尼系数一样更加一般的对于树结构进行打分的函数。Obj计算示例,

4、定义树的结构

基于目标函数Obj,对结点进行分割,分别对左右子树求目标值,然后再对未分割之前的树结构进行求目标值,最后在所有特征离选择分割后,取Gain最高的那个特征。

三、案例分析

使用到的数据集最初来自国家糖尿病/消化/肾脏疾病研究所。数据集的目标是基于数据集中包含的某些诊断测量来诊断性的预测患者是否患有糖尿病。这里的所有患者都是Pima印第安至少21岁的女性,数据集由多个医学预测变量和一个目标变量组成Outcome。预测变量包括患者的怀孕次数、BMI、胰岛素水平、年龄等。
我们先来看一下sklearn包中关于Xgboost的API:
xgboost.XGBClassifier(max_depth=3, learning_rate=0.1, n_estimators=100, verbosity=1, silent=None, 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, colsample_bynode=1, reg_alpha=0, reg_lambda=1, scale_pos_weight=1, base_score=0.5, random_state=0, seed=None, missing=None, **kwargs)

  • 常规参数
booster
    gbtree 树模型做为基分类器(默认)
    gbliner 线性模型做为基分类器
silent
    silent=0时,输出中间过程(默认)
    silent=1时,不输出中间过程
nthread
    nthread=-1时,使用全部CPU进行并行运算(默认)
    nthread=1时,使用1个CPU进行运算。
scale_pos_weight
    正样本的权重,在二分类任务中,当正负样本比例失衡时,设置正样本的权重,模型效果更好。例如,当正负样本比例为1:10时,scale_pos_weight=10。
  • 模型参数
n_estimatores
    含义:总共迭代的次数,即决策树的个数
    调参:
early_stopping_rounds
    含义:在验证集上,当连续n次迭代,分数没有提高后,提前终止训练。
    调参:防止overfitting。
max_depth
    含义:树的深度,默认值为6,典型值3-10。
    调参:值越大,越容易过拟合;值越小,越容易欠拟合。
min_child_weight
    含义:默认值为1,。
    调参:值越大,越容易欠拟合;值越小,越容易过拟合(值较大时,避免模型学习到局部的特殊样本)。
subsample
    含义:训练每棵树时,使用的数据占全部训练集的比例。默认值为1,典型值为0.5-1。
    调参:防止overfitting。
colsample_bytree
    含义:训练每棵树时,使用的特征占全部特征的比例。默认值为1,典型值为0.5-1。
    调参:防止overfitting。
  • 学习任务参数
learning_rate
    含义:学习率,控制每次迭代更新权重时的步长,默认0.3。
    调参:值越小,训练越慢。
    典型值为0.01-0.2。
objective 目标函数
    回归任务
        reg:linear (默认)
        reg:logistic 
    二分类
        binary:logistic     概率 
        binary:logitraw   类别
    多分类
        multi:softmax  num_class=n   返回类别
        multi:softprob   num_class=n  返回概率
    rank:pairwise 
eval_metric
    回归任务(默认rmse)
        rmse--均方根误差
        mae--平均绝对误差
    分类任务(默认error)
        auc--roc曲线下面积
        error--错误率(二分类)
        merror--错误率(多分类)
        logloss--负对数似然函数(二分类)
        mlogloss--负对数似然函数(多分类)

gamma
    惩罚项系数,指定节点分裂所需的最小损失函数下降值。
    调参:
alpha
    L1正则化系数,默认为1
lambda
    L2正则化系数,默认为1

接下来我们开始使用里面的参数进行分类,首先先把需要的包写出来,

from xgboost import XGBClassifier
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from xgboost import plot_importance
import matplotlib.pyplot as plt
from sklearn.model_selection import GridSearchCV,StratifiedKFold

1、首先看一下数据集,并进行简单的处理

def dataset():
    """
    获取数据集并进行处理
    :return:
    """
    # 获取数据集
    data = pd.read_csv('../../数据集/机器学习/集成学习/糖尿病患者/PimaIndiansdiabetes.csv')
    print(data)
    # 将特征值与目标值分开
    y = data.iloc[:, 8]
    x = data.drop(columns='Outcome', axis=1)
    # 分割成训练集与测试集
    seed = 7
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.33, random_state=seed)
    return x,y,x_train, x_test, y_train, y_test
if __name__ == "__main__":
    x,y,x_train, x_test, y_train, y_test = dataset()

2、使用Xgboost进行分类预测

def xgboost(x_train, x_test, y_train, y_test):
    """
    利用xgboost对癌症数据进行分类
    :return:
    """
    #xgboost进行训练
    xgb = XGBClassifier()
    xgb.fit(x_train,y_train)
    y_predict = xgb.predict(x_test)
    accuracy = accuracy_score(y_test, y_predict)
    print("准确率:%.2f%%" % (accuracy*100))
    return None
if __name__ == "__main__":
    x,y,x_train, x_test, y_train, y_test = dataset()
    xgboost(x_train, x_test, y_train, y_test)

3、将Xgboost损失函数的变化过程输出

def xgboot_steps(x_train, x_test, y_train, y_test):
    """
    将xgboost选择树的过程分步展示
    :return:
    """
    xgb = XGBClassifier()
    eval_set = [(x_test,y_test)]
    xgb.fit(x_train, y_train,early_stopping_rounds=10,eval_metric="logloss",eval_set=eval_set,verbose=True)
    y_predict = xgb.predict(x_test)
    accuracy = accuracy_score(y_test, y_predict)
    print("准确率:%.2f%%" % (accuracy*100))
if __name__ == "__main__":
    x,y,x_train, x_test, y_train, y_test = dataset()
    xgboot_steps(x_train, x_test, y_train, y_test)

通过上图我们发现,随着树的增加,损失函数在下降,在32时达到最低,之后开始上升,说明在32的时候效果最好。

4、画图观察8个特征的权重

def show_importance(x,y):
    """
    画图展示出特征的重要程度
    :return:
    """
    xgb = XGBClassifier()
    xgb.fit(x,y)
    plot_importance(xgb)
    plt.show()
    return None
if __name__ == "__main__":
    x,y,x_train, x_test, y_train, y_test = dataset()
    show_importance(x,y)

5、使用网格搜索进行调优

def gridsearchCV(x_train, y_train):
    """
    使用网格搜索进行超参数调优
    :return:
    """
    #1、learning rate 学习率
    #2、tree(max_depth、min_child_weight、subsample、colsample_bytree、gamma
    #3、gamma
    #4、正则化参数(lambda、alpha)
    learning_rate = [0.0001,0.001,0.01,0.1,0.2,0.3]
    param_rate = dict(learning_rate=learning_rate)  #必须是字典格式

    #StratifiedKFold是一种将数据集中每一类样本数据按均等方式拆分的方法。
    kfold = StratifiedKFold(n_splits=10,shuffle=True,random_state=7)

    xgb = XGBClassifier()
    grid_search = GridSearchCV(xgb,param_grid=param_rate,scoring="neg_log_loss",n_jobs=-1,cv=kfold)
    grid_result = grid_search.fit(x_train,y_train)

    print("Best:%f using %s" % (grid_result.best_score_,grid_result.best_params_))

    means =grid_result.cv_results_['mean_test_score']
    params = grid_result.cv_results_['params']
    for mean,param in zip(means,params):
        print("%f with: %r" % (mean,param))
if __name__ == "__main__":
    x,y,x_train, x_test, y_train, y_test = dataset()
    gridsearchCV(x_train, y_train)

四、补充:Adaboost算法

1、简介

AdaBoost,是英文"Adaptive Boosting"(自适应增强)的缩写,它的自适应在于:前一个基本分类器分错的样本会得到加强,加权后的全体样本再次被用来训练下一个基本分类器。同时,在每一轮中加入一个新的弱分类器,直到达到某个预定的足够小的错误率或达到预先指定的最大迭代次数。

2、算法流程

给定一个训练数据集T={\lbrace (x_{1},y_{1}),({x_{1},y_{2}),...(x_{N},y_{N})} \rbrace}其中实例x \in \chi,而实例空间\chi \subset \mathbb{R}^{n}y_{i}属于标记集合{-1,1},Adaboost的目的就是从训练数据中学习一系列弱分类器或基本分类器,然后将这些弱分类器组合成一个强分类器。

  • 第一步
    初始化训练数据的权值分布,如果有N个样本,则每一个训练样本最开始时都被赋予相同的权重:\frac{1}{N}
    D_{1}={(w_{11},w_{12},...w_{1i}...,w_{1N})},w_{1i}=\frac{1}{N},i=1,2,...,N
  • 第二步
    训练弱分类器。具体训练过程中,如果某个样本点已经被准确地分类,那么在构造下一个训练集中,它的权重就被降低;相反,如果某个样本点没有被准确得分类,那么它的权重就得到提高。然后,权重更新过的样本集被用于训练下一个分类器,整个训练过程如此迭代地进行下去。
    用m=1,2,...,M表示迭代的第多少轮
    • a.使用具有权值分布Dm的训练数据集学习,得到基本分类器:
      G_{m}(x):\chi \rightarrow {\lbrace -1,1 \rbrace}
    • b.计算G_{m}(x)在训练集上的分类误差率
      e_{m}=P(G_{m}(x_{i}) \neq y_{i})=\sum_{i=1}^{N}w_{mi}I(G_{m}(x_{i})) \neq y_{i}由上述式子可知,G_{m}(x)在训练数据集上的误差率e_{m}就是被G_{m}(x)误分样本的权值之和。
    • c.计算G_{m}(x)的系数,\alpha_{m}表示G_{m}(x)在最终分类器中的重要程度(目的:得到基本分类器在最终分类器中所占的权重)
      \alpha_{m} =\frac{1}{2}\log \frac{1-e_{m}}{e_{m}}由上述式子可知,e_{m}<1/2时,\alpha_{m} \geq 0,且\alpha_{m}随着e_{m}的减少而增大,意味着分类误差率越小的基本分类器在最终分类器中的作用越大。
    • d.更新训练数据集的权值分布(目的:得到样本的新的权值分布),用于下一轮迭代D_{m+1}=(w_{m+1,1},w_{m+1,2},...w_{m+1,i},...w_{m+1,N})
      w_{m+1,i}=\frac{w_{mi}}{Z_{m}}exp^{-\alpha_{m}y_{i}G_{m}(x_{i})},i=1,2,...,N
      使得被基本分类器G_{m}(x)误分类样本的权值增大,而被正确分类样本的权值减小。就这样通过这样的方式,AdaBoost方法能"聚焦于"那些较难分的样本上。其中,Z_{m}是规范化因子,使得D_{m+1}成为一个概率分布:z_{m}=\sum_{i=1}^{N}w_{mi}exp^{-\alpha_{m}y_{i}G_{m}(x_{i})}
  • 第三步
    将各个训练得到的弱分类器组合成强分类器。各个分类器的训练过程结束后,加大分类误差率小的弱分类器的权重,使其在最终的分类函数中起着较大的决定作用,而降低分类误差率大的弱分类器的权重,使其在最终的分类函数中起着较小的决定作用。换言之,误差率低的弱分类器在最终分类器中占的权重较大,否则较小。
    组合各个弱分类器:f(x)=\sum_{m=1}^{M}\alpha_{m}G_{m}(x)从而得到最终分类器,如下:G(x)=sign(f(x))=sign(\sum_{m=1}^{M}\alpha_{m}G_{m}(x))
    举个例子,看一下是如何计算的(偷个懒,直接把图贴上来了),

3、案例

我们还是使用上面的数据集,用AdaBoost算法跑一遍看一下准确率,

  • 首先我们先用网格搜索确定一下学习率的参数
def gridsearchCV(x_train, y_train):
    """
    使用网格搜索进行超参数调优
    :return:
    """
    #1、learning rate 学习率
    #2、tree(max_depth、min_child_weight、subsample、colsample_bytree、gamma
    #3、gamma
    #4、正则化参数(lambda、alpha)
    learning_rate = [0.0001,0.001,0.01,0.1,0.2,0.3]
    param_rate = dict(learning_rate=learning_rate)  #必须是字典格式

    #StratifiedKFold是一种将数据集中每一类样本数据按均等方式拆分的方法。
    kfold = StratifiedKFold(n_splits=10,shuffle=True,random_state=7)

    xgb = XGBClassifier()
    grid_search = GridSearchCV(xgb,param_grid=param_rate,scoring="neg_log_loss",n_jobs=-1,cv=kfold)
    grid_result = grid_search.fit(x_train,y_train)
    print("Best:%f using %s" % (grid_result.best_score_, grid_result.best_params_))

    ada = AdaBoostClassifier()
    grid_search_ada = GridSearchCV(ada, param_grid=param_rate, scoring="neg_log_loss", n_jobs=-1, cv=kfold)
    grid_result_ada = grid_search_ada.fit(x_train, y_train)
    print("Best:%f using %s" % (grid_result_ada.best_score_, grid_result_ada.best_params_))
if __name__ == "__main__":
    x,y,x_train, x_test, y_train, y_test = dataset()
    gridsearchCV(x_train, y_train)

这是Xgboost和Adaboost算法learning_rate参数的最优值,然后我们分别看一下在最优参数下两个算法的准确率,

  • 对比
def adaboost(x_train, x_test, y_train, y_test):
    """
    使用adaboost算法进行分类预测
    :return:
    """
    ada = AdaBoostClassifier(learning_rate=0.01)
    ada.fit(x_train,y_train)
    y_predict = ada.predict(x_test)
    accuracy = accuracy_score(y_test, y_predict)
    print("准确率:%.2f%%" % (accuracy * 100))
    return None
if __name__ == "__main__":
    x,y,x_train, x_test, y_train, y_test = dataset()
    xgboost(x_train, x_test, y_train, y_test)
    adaboost(x_train, x_test, y_train, y_test)

上面是xgboost的准确率,下面是adaboost的准确率,虽然在这里可以看到adaboost的准确率稍微低一点,但是因为数据集和参数的原因(多个参数调优)结果不一定。有兴趣的同学可以去试一下!
Xgboost算法的学习到这里就结束了,有不明白的同学可以在下方留言。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342