机器学习算法 05 —— 集成学习(Bagging、随机森林、Boosting、AdaBost、GBDT)

本文深入探讨了集成学习中的随机森林和Boosting方法,包括Bagging原理、随机森林的构造、包外估计的应用,以及Boosting(如AdaBoost和GBDT)的实现过程和区别。通过案例研究,展示了如何在实际项目中优化随机森林参数并应用到奥拓产品分类问题上。
摘要由CSDN通过智能技术生成


系列文章

机器学习算法 01 —— K-近邻算法(数据集划分、归一化、标准化)

机器学习算法 02 —— 线性回归算法(正规方程、梯度下降、模型保存)

机器学习算法 03 —— 逻辑回归算法(精确率和召回率、ROC曲线和AUC指标、过采样和欠采样)

机器学习算法 04 —— 决策树(ID3、C4.5、CART,剪枝,特征提取,回归决策树)

机器学习算法 05 —— 集成学习(Bagging、随机森林、Boosting、AdaBost、GBDT)

机器学习算法 06 —— 聚类算法(k-means、算法优化、特征降维、主成分分析PCA)

机器学习算法 07 —— 朴素贝叶斯算法(拉普拉斯平滑系数、商品评论情感分析案例)

机器学习算法 08 —— 支持向量机SVM算法(核函数、手写数字识别案例)

机器学习算法 09 —— EM算法(马尔科夫算法HMM前置学习,EM用硬币案例进行说明)

机器学习算法 10 —— HMM模型(马尔科夫链、前向后向算法、维特比算法解码、hmmlearn)


1 集成学习算法介绍

集成学习通过建立几个模型来解决单一预测问题。它的工作原理是生成多个模型(分类器),各自独立地学习并作出预测,这些预测结果最后结合在一起成为组合预测,因此最终结果会优于任何一个单独模型作出的预测。

这其实有点像三个臭皮匠赛过诸葛亮的道理。

 

在机器学习中有两个核心任务:

  • 如何优化训练数据——>主要用于解决欠拟合问题
  • 如何提升泛华性能——>主要用于解决过拟合问题

在集成学习中可以通过Boosting和Bagging来解决。只要单分类器的表现不是太差,集成学习的结果总会好于单分类器。

 

 

2 Bagging和随机森林

我们知道Bagging是集成学习方法中的一种,其实就是Bagging+决策树/线性回归/逻辑回归/深度学习…=Bagging集成学习方法,将其他算法与之组合,经过这种方式组合而成的学习方法:

  • 均可在原有算法基础上提高2%左右的泛华准确率
  • 组合方式简单、方便且通用

 

2.1 Bagging集成原理

Bagging的集成过程:采样(从所以样本里取一部分)、学习(对取出的部分进行训练,得到弱学习器)、集成(使用平权投票)

现在,我们想把下图中的圆和方块进行分类。(中间肯定还是有误差的)

实现过程如下:

  1. 采样不同的数据集。如下图马赛克就是采取的数据,右边每个坐标系都是一个数据集。

  1. 对每个数据集分别训练分类器(模型),从而得到多个训练好的模型。

  1. 平权投票,获取最终结果。平权投票在各个算法可能不同,随机森林(Bagging+决策树)里平权投票是取众数。

 

2.2 随机森林

在集成学习中,随机森林是比较常用的。随机森林就是一个包含多个决策树的分类器,并且它的输出多个决策树的输出的众数决定的,也就是说随机森林里平权投票就是取众数。例如,我们训练出5个决策树,其中4个树的结果是True,1个树结果是False,最终投票结果就是True。

 

随机森林构造过程中的关键步骤(M表示特征数目):

  1. 一次随机选出一个样本,是有放回的抽样,重复N次,所以是有可能出现重复样本的。
  2. 随机选出m个特征,m<<M,建立决策树。

为什么是随机抽样?如果不随机,那么每棵树的训练集都一样,最终训练出来的树分类结果也会完全一样。

为什么是有放回抽样?如果不放回,那么每棵树的训练样本都完全不同,一点交集都没有,这样每棵树都是“有偏的”、“片面的”,也就是说每棵树训练出来都存在很大差异。而随机森林最后的结果要取决于众数,所以每棵树都存在差异显然是不合理的。

 

2.3 包外估计

在随机森林的构造过程中,因为进行的是有放回抽样,所以有一部分样本我们可能始终抽不到。如下图随机森林Baggin过程时,对每一棵训练出的决策树 g t g_t gt和训练集D,其中星号部分是没被抽取的。

这部分数据我们称之为包外数据(Out-of-bag,oob),那么这部分样本数据有什么用呢?会占整体多大比重呢?

当数据足够多时,对于任意一组数据 ( x N , y N ) (x_N,y_N) (xN,yN)是包外数据的概率为:

由于基分类器是构建在训练样本的⾃助抽样集上的,只有约 63.2% 原样本集出现在中,⽽剩余的 36.8% 的数据作为包外数据。包外数据可以⽤于基分类器的测试集,也就是说其他算法中,我们通常评价score,而在随机森林中是用oob_score_,这就代表计算score是用的包外数据。 在随机森林算法中数据集属性的重要性、分类器集强度和分类器间相关性计算都依赖于包外数据。

包外估计是对集成分类器泛化误差的⽆偏估计(大学概率论里的),其作用:

  • 当基学习器是决策树时,可使⽤包外数据来辅助剪枝 ,或⽤于估计决策树中各结点的后验概率以辅助对零训练样本结点的处理;
  • 当基学习器是神经⽹络时,可使⽤包外样本来辅助早期停⽌以减⼩过拟合 。

 

2.4 随机森林API

sklearn.ensemble.RandomForestClassifier(n_estimators=10, criterion=’gini’, max_depth=None, bootstrap=True, random_state=None, min_samples_split=2)

  • n_estimators:随机森林里决策树的数量,默认10。

  • criterion:划分特征的依据,默认"gini"基尼指数,其他还有像“entropy"信息增益。

  • max_depth:树的最大深度。

  • max_features:每个决策树的最大特征数量。可选"auto"即等价于特征数开根号、"sqrt"即特征数开根号、"log2"特征数取对数、None等于特征数。

  • boostrap:是否在构建树时放回抽样。

  • min_samples_split:内部节点再划分所需的最小样本数。默认2,如果样本量不⼤就不需要管这个值,如果样本量数量级⾮常⼤,则推荐增⼤这个值。

  • min_samples_leaf:叶子节点的最小样本数。如果某叶⼦节点数⽬⼩于样本数,则会和兄弟节点⼀起被剪枝,

    默认是1。 较⼩的叶⼦使模型更容易捕捉训练数据中的噪声,一般来说可以设置为大于50。

  • min_impurity_split:节点划分最小不纯度。这个值限制了决策树的增⻓,如果某节点的不纯度(基于基尼系数,均⽅差)⼩于这个阈值,则该节点不再⽣成⼦节点。即为叶⼦节点 。一般不推荐改动,默认1e-7。

 

这里我们把上次决策树算法里的泰坦尼克生存预测的案例修改一下,用随机森林,同时加上交叉验证和网格搜索。

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split,GridSearchCV
from sklearn.feature_extraction import DictVectorizer
from sklearn.ensemble import RandomForestClassifier

# 1. 获取数据
titanic =  pd.read_csv("./data/titanic_train.csv")

# 2. 数据基本处理
# 2.1 确定特征值和目标值
x = titanic[["Pclass", "Age", "Sex"]] 
y = titanic["Survived"] # 是否生还
# 2.2 缺失值处理
x["Age"].fillna(x["Age"].mean(), inplace=True)
# 2.3 数据集划分
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=22)

# 3. 特征工程 - 特征提取 字典提取
transfer = DictVectorizer(sparse=False) 
x_train = transfer.fit_transform(x_train.to_dict(orient="records"))
x_test = transfer.fit_transform(x_test.to_dict(orient="records"))

# 4. 机器学习 - 随机森林 交叉验证 网格搜索
rf = RandomForestClassifier()
param = {"n_estimators": [120,200,300,500,800,1200], "max_depth": [5, 8, 15, 25, 30]}
gc = GridSearchCV(rf, param_grid=param, cv=2) 
gc.fit(x_train, y_train) 
print("随机森林预测的准确率为:", gc.score(x_test, y_test))

image-20210817104440127

 

 

3 案例:奥拓产品分类

3.1 背景介绍

奥托集团是世界上最⼤的电⼦商务公司之⼀,在20多个国家设有⼦公司。该公司每天都在世界各地销售数百万种产品,所以对其产品根据性能合理的分类⾮常重要。不过,在实际⼯作中,⼯作⼈员发现,许多相同的产品却得到了不同的分类。本案例要求,你对奥拓集团的产品进⾏正确的分类。尽可能的提供分类的准确性。

案例链接:https://www.kaggle.com/c/otto-group-product-classification-challenge/overview

 

3.2 数据集介绍

  • 本案例中,数据集包含⼤约200,000种产品的93个特征。

  • 其⽬的是建⽴⼀个能够区分otto公司主要产品类别的预测模型。

  • 所有产品共被分成九个类别(例如时装,电⼦产品等)。

  • id - 产品id

  • feat_1, feat_2, …, feat_93 - 产品的各个特征(显然进行了脱敏处理)

  • target - 产品被划分的类别

 

3.3 评分标准

本案例中,最后的结果使⽤多分类对数损失进⾏评估。 【sklearn中有logloss的API】

具体公式:
l o g l o s s = − 1 N ∑ i = 1 N ∑ j = 1 N y i j l o g ( p i j ) logloss=-\frac{1}{N} \sum\limits_{i=1}^N \sum\limits_{j=1}^N y_{ij} log(p_{ij}) logloss=N1i=1Nj=1Nyijlog(pij)

  • i i i表示样本, j j j表示类别, p i j p_{ij} pij代表第 i i i个样本属于类别 j j j的概率,

  • 如果第 i i i个样本真的属于类别 j j j,则 y i j y_{ij} yij等于1,否则为0。

  • 根据上公式,假如你将所有的测试样本都正确分类,所有 p i j p_{ij} pij都是1,那每个 l o g ( p i j ) log(p_{ij}) log(pij)都是0,最终的 l o g L o s s logLoss logLoss也是0。

  • 假如第1个样本本来是属于1类别的,但是你给它的类别概率 p i j = 0.1 p_{ij}=0.1 pij=0.1,那 l o g L o s s logLoss logLoss就会累加上 l o g ( 0.1 ) log(0.1) log(0.1)这⼀项。我们知道这⼀项是负数,⽽且 p i j p_{ij} pij⼩,负得越多,如果 p i j = 0 p_{ij}=0 pij=0,将是⽆穷。

  • 这会导致:你分错了⼀个, l o g L o s s logLoss logLoss就是⽆穷。这当然不合理,为了避免这⼀情况,所以我们对⾮常⼩的值进行了如下处理:
    m a x ( m i n ( p , 1 − 1 0 − 15 ) , 1 0 − 15 ) max(min(p,1-10^{-15}),10^{-15}) max(min(p,11015),1015)

  • 也就是说最小的值都不会小于 1 0 − 15 10^{-15} 1015

最后将结果存储到csv文件中,按照规定格式上传提交。

 

3.4 实现过程

  1. 模型基本训练
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
from imblearn.under_sampling import RandomUnderSampler
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import log_loss

# 1. 获取数据
otto = pd.read_csv("./data/otto_train.csv")

# 2. 数据基本处理

"""
数据已经经过脱敏,所以不再需要特殊处。
ps:数据脱敏指对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护。
    所以现在并不知道哪些特征的是什么意思,因此所有特征都需要保留。
"""
# 2.1 获取特征值和目标值
x = otto.drop(["id", "target"], axis=1) # 特征值(不需要id和target)
y = otto["target"]

# 2.2 截取部分数据
# 考虑到数据太大,后期训练花费时间太长,因此只截取部分数据进行训练。
# 如果只是简单截取 前一万行数据是不行的,它们类别会不均衡,所以需要用【欠采样】。上一篇博客 逻辑回归里有说明

# 错误截取数据 查看分布情况
# new_otto = otto[:10000]
# sns.countplot(new_otto.target)
# plt.show()

# 随机欠采样
rus = RandomUnderSampler(random_state=0)
x_resampled, y_resampled = rus.fit_sample(x, y)
# 2.3 把目标值Class_1这种标签 转换为 数字
le = LabelEncoder()
y_resampled = le.fit_transform(y_resampled.reshape([-1, 1]))
# 2.4 分割训练集和测试集
x_train, x_test, y_train, y_test = train_test_split(x_resampled, y_resampled, test_size=0.2)

# 3. 模型训练 - 随机森林
rf = RandomForestClassifier(oob_score=True) # oob_score 外包估计
rf.fit(x_train, y_train)

# 4. 模型评估
# 4.1 自主评估
y_pre = rf.predict(x_test)
print("预测值:", y_pre)
print("准确度:", rf.score(x_test, y_test))
print("外包数据精确度:", rf.oob_score_)

# 5. 模型评估 - logLoss 评估
# 5.1 需要先用One-Hot编码,才能使用log_loss(注意:不是之前决策树里特征工程的DictVectorizer,它是用于字典提取的)
one_hot = OneHotEncoder(sparse=False)
y_test1 = one_hot.fit_transform(y_test.reshape([-1, 1]))
y_pre1 = one_hot.fit_transform(y_pre.reshape([-1, 1]))
# 5.2 logLoss评估
log_loss(y_test1, y_pre1, eps=1e-15, normalize=True)
# 5.3 改变预测值的输出模式,让y_pre1是百分比形式,而不只是0和1
y_pre_proba = rf.predict_proba(x_test)
print("百分比形式的预测值:\n",y_pre_proba)

 

  1. 模型调优,获得最佳超参数(n_estimators, max_features, max_depth, min_samples_leaf)

    确定最优n_estimators

"""
说明:定义一个循环,多次进行训练,每次训练的超参数(估计器数量,也就是决策树数量)不同,且每次训练完的评估都存储到列表中。
     最后用图像可视化。
"""
# 调优参数 - 每次循环时n_estimators的取值,从10到200,间隔10。
tuned_parameters = range(10, 200, 10)

# 结果存储 - 精确度存储,生成一个0填充的ndarray
accuracy_t = np.zeros(len(tuned_parameters))
# 结果存储 - 损失值存储
error_t = np.zeros(len(tuned_parameters))

# 通过enumerate(tuned_parameters) 可以获得两个参数,下标和tuned_parameters的每个值
# 其中,i是下标,从0到18,one_parameter是tuned_parameters的每次取值,10到190,每次间隔10。
for i, one_parameter in enumerate(tuned_parameters):
    rf2 = RandomForestClassifier(n_estimators=one_parameter,
                                 max_depth=10,
                                 max_features=10,
                                 oob_score=True,
                                 random_state=0,
                                 n_jobs=-1)
    rf2.fit(x_train, y_train)
    accuracy_t[i] = rf2.oob_score_

    
    y_pre_proba = rf2.predict_proba(x_test)
    error_t[i] = log_loss(y_test, y_pre_proba, eps=1e-15, normalize=True)
print("损失函数:\n", error_t)
print("精确度:\n", accuracy_t)

# 评估可视化
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(20, 4), dpi=100) # 两个图
axes[0].plot(tuned_parameters, error_t) # 损失值-估计器 折线图
axes[1].plot(tuned_parameters, accuracy_t) # 精确度-估计器 折线图

axes[0].set_xlabel("n_estimators") # 设置坐标轴标签
axes[0].set_ylabel("error_t")
axes[1].set_xlabel("n_estimators")
axes[1].set_ylabel("accuracy_t")

axes[0].grid(True) # 打开网格
axes[1].grid(True)

plt.show()

 

同理,确定最优max_features

"""
说明:同上。max_features在随机森林API中说了,通常要么是log计算,要么开根号。数据集中大概90多个特征。
"""
# 调优参数 - 每次循环时max_features的取值,从5到40,间隔5。
tuned_parameters = range(5 , 50, 5)

# 结果存储 - 精确度存储,生成一个0填充的ndarray
accuracy_t = np.zeros(len(tuned_parameters))
# 结果存储 - 损失值存储
error_t = np.zeros(len(tuned_parameters))

# 通过enumerate(tuned_parameters) 可以获得两个参数,下标和tuned_parameters的每个值
# 其中,i是下标,从0到18,one_parameter是tuned_parameters的每次取值,10到190,每次间隔10。
for i, one_parameter in enumerate(tuned_parameters):
    rf2 = RandomForestClassifier(n_estimators=175,
                                 max_depth=10,
                                 max_features=one_parameter,
                                 oob_score=True,
                                 random_state=0,
                                 n_jobs=-1)
    rf2.fit(x_train, y_train)
    accuracy_t[i] = rf2.oob_score_

    
    y_pre_proba = rf2.predict_proba(x_test)
    error_t[i] = log_loss(y_test, y_pre_proba, eps=1e-15, normalize=True)
print("损失函数:\n", error_t)
print("精确度:\n", accuracy_t)

# 评估可视化
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(20, 4), dpi=100) # 两个图
axes[0].plot(tuned_parameters, error_t) # 损失值-估计器 折线图
axes[1].plot(tuned_parameters, accuracy_t) # 精确度-估计器 折线图

axes[0].set_xlabel("max_features") # 设置坐标轴标签
axes[0].set_ylabel("error_t")
axes[1].set_xlabel("max_features")
axes[1].set_ylabel("accuracy_t")

axes[0].grid(True) # 打开网格
axes[1].grid(True)

plt.show()

 

最终获得的最优超参数如下:

rf3 = RandomForestClassifier(n_estimators=175,max_depth=30,min_samples_leaf=1,max_features=15, oob_score=True,random_state=0)
rf3.fit(x_train, y_train)
y_pre_proba = rf2.predict_proba(x_test)
print("精确度:\n", rf3.oob_score_)
print("损失值:\n", log_loss(y_test, y_pre_proba, eps=1e-15, normalize=True))
  1. 上传数据。将结果转换为Kaggle需要的形式,并上传。
# 读取测试集数据
test_data = pd.read_csv("./data/otto_test.csv")
# 删除 id 列。(因为刚刚训练的模型里面是不需要ID的)
test_data_drop_id = test_data.drop(['id'], axis=1)
# 用模型进行预测
y_pre_test = rf.predict_proba(test_data_drop_id)
# 按照要求 加上列索引 每一列是Class_1、Class_2 ...
result_data = pd.DataFrame(y_pre_test, columns=["Class_"+str(i) for i in range(1, 10)])
# 按照要求 添加上 ID 列
result_data.insert(loc=0, column="id", value=test_data.id)
# 存储到CSV文件
result_data.to_csv("./data/otto_submission.csv",index=False) # 不写入行索引

最后的效果如下:

image-20210819155354589

 

 

4 Boosting介绍

4.1 什么是Boosting

Boosting是随着学习的积累从弱到强,简⽽⾔之:每新加⼊⼀个弱学习器,整体能⼒就会得到提升。正好和Bagging相反。

代表算法:Adaboost,GBDT,XGBoost,LightGBM

 

4.2 实现过程

  1. 训练第⼀个学习器。用红线划分出正确和错误区域,左侧正确,右侧错误。

  1. 调整数据分布。左侧两个圆圈划分到正确区域,所以是正确数据。右侧三个圆圈划分到错误区域,所以是错误数据。

  1. 接着,训练第二个学习器。第二根红线,继续划分区域。

  1. 再次调整数据分布。

  1. 依次训练学习器,调整数据分布。

  1. 整体过程实现。

 

4.3 Bagging集成和Boosting集成的区别

区别⼀:数据⽅⾯

  • Bagging:对数据进⾏采样训练。

  • Boosting:根据前⼀轮学习结果调整数据的重要性(增大缩小数据 )。

区别⼆:投票⽅⾯

  • Bagging:所有学习器平权投票。每个数据权重相同。

  • Boosting:对学习器进⾏加权投票。 正确率越高的数据,权重越大。

区别三:学习顺序

  • Bagging的学习是并⾏的,每个学习器没有依赖关系。

  • Boosting学习是串⾏,学习有先后顺序。

区别四:主要作⽤

  • Bagging主要⽤于提⾼泛化性能(解决过拟合,也可以说降低⽅差)

  • Boosting主要⽤于提⾼训练精度 (解决⽋拟合,也可以说降低偏差)

 

5 AdaBost

  • 步骤⼀:初始化训练数据权重相等,训练第⼀个学习器;

  • 步骤⼆:AdaBoost反复学习基本分类器;

  • 步骤三:对m个学习器进⾏加权投票

5.1 构成过程细节

步骤一:初始化训练数据权重相等,训练第⼀个学习器。

该假设每个训练样本在基分类器的学习中作⽤相同,这⼀假设可以保证第⼀步能够在原始数据上学习基本分类器H (x)

步骤二:AdaBoost反复学习基本分类器,在每⼀轮m = 1, 2, …, M顺次的执⾏下列操作。

  • 在权值分布为D 的训练数据上,确定基分类器

  • 计算该学习器在训练数据中的错误率 ϵ t = P ( h t ( x t ) ≠ y t ) \epsilon_t=P(h_t(x_t) \ne y_t) ϵt=P(ht(xt)=yt)

  • 计算该学习器的投票权重 α t = 1 2 l n ( 1 − ϵ t ϵ t ) \alpha_t = \frac{1}{2}ln(\frac{1- \epsilon_t}{\epsilon_t}) αt=21ln(ϵt1ϵt)

  • 根据投票权重,对训练数据重新赋权

将下⼀轮学习器的注意⼒集中在错误数据上

  • 重复执⾏上面步骤二,m次;

步骤三:对m个学习器进⾏加权投票
H ( x ) = s i g n ( ∑ i = 1 m α i ⋅ h i ( x ) ) H(x)=sign ( \sum\limits_{i=1}^m \alpha_i \cdot h_i(x)) H(x)=sign(i=1mαihi(x))


下面通过一个案例来进行说明。

给定下⾯这张训练数据表所示的数据,假设弱分类器由xv产⽣,其阈值v使该分类器在训练数据集上的分类误差率最低,试⽤Adaboost算法学习⼀个强分类器。

image-20210819162803792

步骤⼀:初始化训练数据权重相等,训练第⼀个学习器。

  • D 1 = ( w 11 , w 12 , . . . , w 110 ) D_1=(w_{11},w_{12},...,w_{110}) D1=w11,w12,...,w110,其中 w 1 i = 0.1 , i = 1 , 2 , . . . 10 w_{1i}=0.1,i=1,2,...10 w1i=0.1i=1,2,...10

 

步骤二:AdaBoost反复学习基本分类器,在每⼀轮m = 1, 2, …, M顺次的执⾏下列操作。

1、当m=1时。

在权值分布为 D 1 D_1 D1的训练数据上,阈值v取2.5时分类误差率最低,故基本分类器为:

6、7、8被分错

计算该学习器在训练数据中的错误率 ϵ 1 = P ( h 1 ( x 1 ) ≠ y 1 ) = 0.3 \epsilon_1 = P(h_1(x_1) \ne y_1 ) = 0.3 ϵ1=P(h1(x1)=y1)=0.3

计算该学习器的投票权重 α 1 = 1 2 l n ( 1 − ϵ 1 ϵ 1 ) = 0.4236 \alpha_1 = \frac{1}{2}ln(\frac{1- \epsilon_1}{\epsilon_1})=0.4236 α1=21ln(ϵ11ϵ1)=0.4236

根据投票权重,对训练数据重新赋权: D 2 = ( w 21 , w 22 , . . . , w 210 ) D_2=(w_{21},w_{22},...,w_{210}) D2=(w21,w22,...,w210),根据下面公式,计算各个权重值:

经计算得,D 的值为: D 3 = ( 0.07143 , 0.07143 , 0.07143 , 0.07143 , 0.07143 , 0.07143 , 0.16667 , 0.16667 , 0.16667 , 0.07143 ) D_3 = (0.07143, 0.07143, 0.07143, 0.07143, 0.07143, 0.07143, 0.16667, 0.16667, 0.16667, 0.07143) D3=(0.07143,0.07143,0.07143,0.07143,0.07143,0.07143,0.16667,0.16667,0.16667,0.07143)

计算过程:

故, H 1 ( x ) = s i g n [ 0.4236 ⋅ h 1 ( x ) ] H_1(x) = sign[0.4236 \cdot h_1(x)] H1(x)=sign[0.4236h1(x)],且分类器 H 1 ( x ) H_1 (x) H1(x)在训练数据集上有3个误分类点。

2、当m=2时。

在权值分布为 D 2 D_2 D2的训练数据上,阈值v取8.5时分类误差率最低,故基本分类器为:

3、4、5被分错

计算该学习器在训练数据中的错误率 ϵ 2 = P ( h 2 ( x 2 ) ≠ y 2 ) = 0.2143 \epsilon_2 = P(h_2(x_2) \ne y_2 ) = 0.2143 ϵ2=P(h2(x2)=y2)=0.2143

计算该学习器的投票权重 α 2 = 1 2 l n ( 1 − ϵ 2 ϵ 2 ) = 0.6496 \alpha_2 = \frac{1}{2}ln(\frac{1- \epsilon_2}{\epsilon_2})=0.6496 α2=21ln(ϵ21ϵ2)=0.6496

根据投票权重,对训练数据重新赋权: D 3 = ( w 31 , w 32 , . . . , w 310 ) D_3=(w_{31},w_{32},...,w_{310}) D3=(w31,w32,...,w310),计算各个权重值。

经计算得,D 的值为:$D_3 = (0.0455, 0.0455, 0.0455, 0.1667, 0.1667, 0.1667, 0.1060, 0.1060, 0.1060, 0.0455) $

H 2 ( x ) = s i g n [ 0.4236 ⋅ h 1 ( x ) + 0.6496 h 2 ( x ) ] H_2(x) = sign[0.4236 \cdot h_1(x)+0.6496h_2(x)] H2(x)=sign[0.4236h1(x)+0.6496h2(x)],分类器 H 2 ( x ) H_2 (x) H2(x)在训练数据集上有3个误分类点。

3、当m=3时。

在权值分布为$ D 3 D_3 D3的训练数据上,阈值v取5.5时分类误差率最低,故基本分类器为:

计算该学习器在训练数据中的错误率 ϵ 3 = 0.1820 \epsilon_3 = 0.1820 ϵ3=0.1820

计算该学习器的投票权重 α 3 = 0.7514 \alpha_3 = 0.7514 α3=0.7514

根据投票权重,对训练数据重新赋权: D 4 = ( w 41 , w 42 , . . . , w 410 ) D_4=(w_{41},w_{42},...,w_{410}) D4=(w41,w42,...,w410),计算各个权重值。

经计算得,D 的值为:$D_4 = (0.125, 0.125, 0.125, 0.102, 0.102, 0.102, 0.065, 0.065, 0.065, 0.125) $

H 3 ( x ) = s i g n [ 0.4236 ⋅ h 1 ( x ) + 0.6496 h 2 ( x ) + 0.7514 h 3 ( x ) ] H_3(x) = sign[0.4236 \cdot h_1(x)+0.6496h_2(x)+0.7514h_3(x)] H3(x)=sign[0.4236h1(x)+0.6496h2(x)+0.7514h3(x)],分类器 H 2 ( x ) H_2 (x) H2(x)在训练数据集上有0个误分类点。

 

步骤三:对m个学习器进⾏加权投票,获取最终分类器。

H 3 ( x ) = s i g n [ 0.4236 ⋅ h 1 ( x ) + 0.6496 h 2 ( x ) + 0.7514 h 3 ( x ) ] H_3(x) = sign[0.4236 \cdot h_1(x)+0.6496h_2(x)+0.7514h_3(x)] H3(x)=sign[0.4236h1(x)+0.6496h2(x)+0.7514h3(x)]

 

 

5.2 AdaBost API介绍

from sklearn.ensemble import AdaBoostClassifier

由于这个用的比较少,详细参考:https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.AdaBoostClassifier.html#sklearn.ensemble.AdaBoostClassifier

 

 

6 GBDT介绍

GBDT 的全称是 Gradient Boosting Decision Tree,梯度提升决策树,在传统机器学习算法中,GBDT算的上TOP3的算法。想要理解GBDT的真正意义,那就必须理解GBDT中的Gradient BoostingDecision Tree分别是什么。

 

6.1 Decision Tree:CART回归树

⾸先,GBDT使⽤的决策树是CART回归树,⽆论是处理回归问题还是⼆分类以及多分类,GBDT使⽤的决策树通通都是都是CART回归树。

为什么不⽤CART分类树呢? 因为GBDT每次迭代要拟合的是梯度值,是连续值所以要⽤回归树。

对于回归树算法来说最重要的是寻找最佳的划分点,那么回归树中的可划分点包含了所有特征的所有可取的值。

在分类树中最佳划分点的判别标准是熵或者基尼指数,都是⽤纯度来衡量的,但是在回归树中的样本标签是连续数值,所以再使⽤熵之类的指标不再合适,取⽽代之的是平⽅误差,它能很好的评判拟合程度。


回归树生成算法复习 ,可以看我另一篇博客:回归决策树

  • 输入:训练集D

  • 输出:回归决策树 f ( x ) f(x) f(x)

在训练数据集所在的输入空间中,递归的将每个区域划分为两个子区域并决定每个子区域上的输出值,构建二叉决策树。

  1. 选择最优切分特征j与切分点s,求解:
    m i n j , s [ m i n c 1 ∑ x i ∈ R 1 ( j , s ) ( y i − c 1 ) 2 + m i n c 2 ∑ x i ∈ R 2 ( j , s ) ( y i − c 2 ) 2 ] min_{j,s}[min_{c_1} \sum\limits_{x_i \in R_1(j, s)}(y_i-c_1)^2 + min_{c_2} \sum\limits_{x_i \in R_2(j, s)}(y_i - c_2)^2] minj,s[minc1xiR1(j,s)(yic1)2+minc2xiR2(j,s)(yic2)2]
    遍历特征j,对固定的切分特征j扫描切分点,选择能使上式达到最小的一对 ( j , s ) (j, s) (j,s)

  2. 根据选定的 ( j , s ) (j,s) (j,s)划分区域,并决定相应的输出值:
    R 1 ( j , s ) = x ∣ x ( j ) ≤ s , R 2 ( j , s ) = x ∣ x ( j ) > c m = 1 N ∑ x 1 ∈ R m ( j , s ) y i , x ∈ R m , m = 1 , 2 R_1(j, s)=x|x^{(j)} \le s, R_2(j,s)=x|x^{(j)} \gt \\ c_m=\frac{1}{N} \sum\limits_{x_1 \in R_m(j,s)} y_i, x \in R_m, m=1,2 R1(j,s)=xx(j)s,R2(j,s)=xx(j)>cm=N1x1Rm(j,s)yi,xRm,m=1,2

  3. 继续对两个子区域调用 1和2,直到满足条件。

  4. 将输入空间划分为M个区域 R 1 , R 2 , . . . R M R_1, R_2, ... R_M R1,R2,...RM,生成决策树:
    f ( x ) = ∑ m = 1 M c m I ( x ∈ R m ) f(x)=\sum\limits_{m=1}^M c_mI(x \in R_m) f(x)=m=1McmI(xRm)


 

6.2 Gradient Boosting: 拟合负梯度

梯度提升树(Grandient Boosting)是提升树(Boosting Tree)的⼀种改进算法,所以在讲梯度提升树之前先来说⼀下提升树。

先来个通俗理解:假如有个⼈30岁,我们⾸先⽤20岁去拟合,发现损失有10岁,这时我们⽤6岁去拟合剩下的损失,发现差距还有4岁,第三轮我们⽤3岁拟合剩下的差距,差距就只有⼀岁了。如果我们的迭代轮数还没有完,可以继续迭代下⾯,每⼀轮迭代,拟合的岁数误差都会减⼩。最后将每次拟合的岁数加起来便是模型输出的结果。

提升树算法:

  • (1)初始化f0(x) = 0

  • (2)对m=1,2,…,M

    • (a)计算残差 r m i = y i − f m − 1 ( x ) , i = 1 , 2 , , , , , , , N r_{mi} = y_i − f_{m−1}(x),i = 1, 2, , , , , , , N rmi=yifm1(x),i=1,2,,,,,,,N

    • (b)拟合残差 r m i r_{mi} rmi 学习⼀个回归树,得到 h m ( x ) h_m(x) hm(x)

    • (c)更新 f m ( x ) = f m − 1 + h m ( x ) f_m(x) = f_{m−1} + h_m(x) fm(x)=fm1+hm(x)

  • (3)得到回归问题提升树 f M ( x ) = ∑ m = 1 M h m ( x ) f_M(x)= \sum\limits_{m=1}^M h_m(x) fM(x)=m=1Mhm(x)

上⾯伪代码中的残差是什么?

在提升树算法中

  • 假设我们前⼀轮迭代得到的强学习器是: f t − 1 ( x ) f_{t−1}(x) ft1(x)

  • 损失函数是: L ( y , f t − 1 ( x ) ) L(y, f_{t−1}(x)) L(y,ft1(x))

  • 我们本轮迭代的⽬标是找到⼀个弱学习器: h t ( x ) h_t(x) ht(x)

  • 最⼩化让本轮的损失: L ( y , f t ( x ) ) = L ( y , f t − 1 ( x ) + h t ( x ) ) L(y, f_t(x)) = L(y, f_{t−1}(x) + h_t(x)) L(y,ft(x))=L(y,ft1(x)+ht(x))

  • 当采⽤平⽅损失函数时:

  • 这⾥, r = y − f t − 1 ( x ) r = y − f_{t−1}(x) r=yft1(x)是当前模型拟合数据的残差(residual)。

  • 所以,对于提升树来说只需要简单地拟合当前模型的残差。

回到我们上⾯讲的那个通俗易懂的例⼦中,第⼀次迭代的残差是10岁,第⼆ 次残差4岁,

 

当损失函数是平⽅损失和指数损失函数时,梯度提升树每⼀步优化是很简单的,但是对于⼀般损失函数⽽⾔,往往每⼀步优化起来不那么容易。

针对这⼀问题,Friedman提出了梯度提升树算法,这是利⽤最速下降的近似⽅法,其关键是利⽤损失函数的负梯度作为提升树算法中的残差的近似值。

 

那么负梯度⻓什么样呢?

  • 第t轮的第i个样本的损失函数的负梯度为:

  • 此时不同的损失函数将会得到不同的负梯度,如果选择平⽅损失:
    L ( y , f ( x i ) ) = 1 2 ( y − f ( x i ) ) 2 L(y, f(x_i))=\frac{1}{2}(y-f(x_i))^2 L(y,f(xi))=21(yf(xi))2

  • 负梯度为:

此时我们发现GBDT的负梯度就是残差,所以说对于回归问题,我们要拟合的就是残差。

那么对于分类问题呢? ⼆分类和多分类的损失函数都是logloss。

 

6.3 GBDT算法原理

上⾯两节分别将Decision Tree和Gradient Boosting介绍完了,下⾯将这两部分组合在⼀起就是我们的GBDT了。

1、初始化弱学习器
f 0 ( x ) = a r g   m i n c ∑ i = 1 N L ( y i , c ) f_0(x)=arg\ min_c \sum\limits_{i=1}^N L(y_i, c) f0(x)=arg minci=1NL(yi,c)
2、对m=1,2,…M有

  • 对每个样本i=1,2,…,N,计算负梯度,即残差 γ i m \gamma_{im} γim

    在这里插入图片描述

  • 将上步得到的残差作为样本新的真实值,并将数据 ( x i , γ i m ) , i = 1 , 2 , . . N (x_i, \gamma_{im}), i = 1, 2, ..N (xi,γim),i=1,2,..N作为下棵树的训练数据,得到⼀颗新的回归树 f m ( x ) f_m(x) fm(x)其对应的叶⼦节点区域为 R j m , j = 1 , 2 , . . . , J R_{jm}, j = 1, 2, ..., J Rjm,j=1,2,...,J。其中J为回归树t的叶⼦节点的个数。

  • 对叶⼦区域j=1,2,…J计算最佳拟合值

  • 更新强学习器
    f m ( x ) = f m − 1 ( x ) + ∑ j = 1 J γ j m I ( x ∈ R j m ) f_m(x)=f_{m-1}(x)+\sum\limits_{j=1}^J \gamma_{jm}I(x \in R_{jm}) fm(x)=fm1(x)+j=1JγjmI(xRjm)

3、得到最终学习器
f ( x ) = f M ( x ) = f 0 ( x ) + ∑ m = 1 M ∑ j = 1 J γ j m I ( x ∈ R j m ) f(x)=f_M(x)=f_0(x)+\sum\limits_{m=1}^M \sum\limits_{j=1}^J \gamma_{jm}I(x \in R_{jm}) f(x)=fM(x)=f0(x)+m=1Mj=1JγjmI(xRjm)


下面用一个案例来说明GBDT算法。

算法说明实例

根据如下数据,预测最后⼀个样本的身⾼。

设置参数

  • 学习率:learning_rate=0.1

  • 迭代次数:n_trees=5

  • 树的深度:max_depth=3

 

开始训练

1、初始化弱学习器:
f 0 ( x ) = a r g   m i n c ∑ i = 1 N L ( y i , c ) f_0(x)=arg\ min_c \sum\limits_{i=1}^N L(y_i, c) f0(x)=arg minci=1NL(yi,c)
损失函数为平⽅损失,因为平⽅损失函数是⼀个凸函数,直接求导,倒数等于零,得到c。

令导数等于0

所以初始化时,c取值为所有训练样本标签值的均值。c = (1.1 + 1.3 + 1.7 + 1.8)/4 = 1.475,此时得到初始学习器 f 0 ( x ) = c = 1.475 f_0(x)=c=1.475 f0(x)=c=1.475

 

2、对迭代轮数m=1,2,…,M:

由于我们设置了迭代次数:n_trees=5,这⾥的M = 5。

计算负梯度,根据上⽂损失函数为平⽅损失时,负梯度就是残差,再直⽩⼀点就是 y与上⼀轮得到的学习器f 的差值:

残差在下表列出:

此时将残差作为样本的真实值来训练弱学习器f (x),即下表数据

接着,寻找回归树的最佳划分节点,遍历每个特征的每个可能取值。

从年龄特征的5开始,到体重特征的70结束,分别计算分裂后两组数据的平⽅损失(Square Error)。其中, S E l SE_l SEl为左节点平⽅损失, S E r SE_r SEr为右节点平⽅损失,找到使平⽅损失和 S E s u m = S E l + S E r SE_{sum} = SE_l + SE_r SEsum=SEl+SEr 最⼩的那个划分节点,即为最佳划分节点。

例如,以年龄21为划分节点,将⼩于21的样本划分为到左节点,⼤于等于21的样本划分为右节点。左节点包括 x 0 x_0 x0 , x 1 x_1 x1 ,右节点包括样本 x 2 x_2 x2 , x 3 x_3 x3,那么
S E l = [ − 0.375 − ( − 0.275 ) ] 2 + [ − 0.175 − ( − 0.275 ) ] 2 = 0.02 S E r = [ 0.225 − 0.275 ] 2 + [ 0.325 − 0.275 ] 2 = 0.005 S E s u m = S E l + S E r = 0.025 SE_l=[-0.375-(-0.275)]^2+[-0.175-(-0.275)]^2=0.02\\ SE_r=[0.225-0.275]^2+[0.325-0.275]^2=0.005\\ SE_{sum}=SE_l+SE_r=0.025 SEl=[0.375(0.275)]2+[0.175(0.275)]2=0.02SEr=[0.2250.275]2+[0.3250.275]2=0.005SEsum=SEl+SEr=0.025

所有可能划分的情况如下表所示:

划分点小于划分点的样本大于等于划分点的样本 S E l SE_l SEl S E r SE_r SEr S E s u m SE_{sum} SEsum
年龄5/0,1,2,300.3270.327
年龄701,2,300.140.13
年龄210,12,30.020.0050.025
年龄300,1,230.18700.187
体重20/0,1,2,300.3270.327
体重3001,2,300.140.14
体重600,12,30.020.0050.025
体重700,1,320.2600.26

 

以上划分点是的总平⽅损失最⼩为0.025有两个划分点:年龄21和体重60,所以随机选⼀个作为划分点,这⾥我们选年龄21 现在我们的第⼀棵树⻓这个样⼦:

在这里插入图片描述

 

由于我们设置的参数中树的深度max_depth=3,现在树的深度只有2,需要再进⾏⼀次划分,这次划分要对左右两个节点分别进⾏划分:

  • 对于左节点,只含有0,1两个样本,根据下表我们选择年龄7划分

    划分点小于划分点的样本大于等于划分点的样本 S E l SE_l SEl S E r SE_r SEr S E s u m SE_{sum} SEsum
    年龄5/0,100.020.02
    年龄701000
    体重20/0,100.020.02
    体重3001000
  • 对于右节点,只含有2,3两个样本,根据下表我们选择年龄30划分(也可以选体重70

    划分点小于划分点的样本大于等于划分点的样本 S E l SE_l SEl S E r SE_r SEr S E s u m SE_{sum} SEsum
    年龄21/2,300.0050.005
    年龄3023000
    体重60/2,300.0050.005
    体重7032000

 

现在我们的第⼀棵树⻓这个样⼦:

此时我们的树深度满⾜了设置,还需要做⼀件事情,给这每个叶⼦节点分别赋⼀个参数Υ,来拟合残差。

这⾥其实和上⾯初始化学习器是⼀个道理,平⽅损失,求导,令导数等于零,化简之后得到每个叶⼦节点的参数Υ,其实就是标签值的均值。这个地⽅的标签值不是原始的 y,⽽是本轮要拟合的标残差 y − f ( x ) y − f (x) yf(x)

根据上述划分结果,为了⽅便表示,规定从左到右为第1,2,3,4个叶⼦结点

此时的树⻓这个样⼦:

此时可更新强学习器,需要⽤到参数学习率:learning_rate=0.1,⽤ l r lr lr表示。

为什么要⽤学习率呢?这是Shrinkage的思想,如果每次都全部加上(学习率为1)很容易⼀步学到位导致过拟合。重复此步骤,直到 m>5 结束,最后⽣成5棵树。

image-20210819183621201

结果中,0.9倍这个现象,和其学习率有关。这是因为数据简单每棵树⻓得⼀样,导致每⼀颗树的拟合效果⼀样,⽽每棵树都只学上⼀棵树残差的0.1倍,导致这颗树只能拟合剩余0.9了。

 

3、得到最后的强学习器:

4、预测样本:

  • $f_0(x) = 1.475 $

  • f 1 ( x ) f_1(x) f1(x)中,样本4的年龄为25,⼤于划分节点21岁,⼜⼩于30岁,所以被预测为0.2250;

  • f 2 ( x ) f_2(x) f2(x)中,样本4的…此处省略…所以被预测为0.2025;

  • f 3 ( f_3( f3(x)$中,样本4的…此处省略…所以被预测为0.1823;

  • f 4 ( x ) f_4(x) f4(x)中,样本4的…此处省略…所以被预测为0.1640;

  • f 5 ( x ) f_5(x) f5(x)中,样本4的…此处省略…所以被预测为0.1476.

最终预测结果: f ( x ) = 1.475 + 0.1 ∗ ( 0.225 + 0.2025 + 0.1823 + 0.164 + 0.1476 ) = 1.56714 f(x) = 1.475 + 0.1 ∗ (0.225 + 0.2025 + 0.1823 + 0.164 + 0.1476) = 1.56714 f(x)=1.475+0.1(0.225+0.2025+0.1823+0.164+0.1476)=1.56714

  • 8
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老板来碗小面加蛋~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值