学习目标:
集成学习通过建立几个模型来解决单一预测问题。它的工作原理是生成多个分类器(模型),各自独立地学习和作出预测。这些预测最后结合成组合预测,因此优于任何一个单分类的做出预测。
注意:
只要单分类器的表现不太差,集成学习的结果总是要好于单分类器的。
小结:
注意:
学习目标:
Bagging 是一种集成学习方法,它的全称是 Bootstrap aggregating。它的核心思想是通过并行地训练一系列各自独立的同类模型,然后再将各个模型的输出结果按照某种策略进行聚合(例如分类中可采用投票策略,回归中可采用平均策略)。
Bootstrap aggregating的中文翻译是“自助聚集算法”,也称为“装袋算法”。
Bootstrap:英['bu:tstræp] 美[ˈbutˌstræp]
v. 独自创立; 靠一己之力做成; 附属于; 与…相联系;
n. 拔靴带;aggregating:英[ˈæɡrɪɡeɪtɪŋ] 美[ˈæɡrɪɡeɪtɪŋ]
v. 总计; 合计;
Bagging 算法的实现过程是将训练数据随机抽取形成 k k k 份数据,然后完成学习。随机采样(bootstrap sample)是从 n n n 个数据点中有放回地重复随机抽取一个样本(即同一个样本可被多次抽取),共抽取 n n n 次。创建一个与原数据大小相同得数据集,但有些数据点会缺失(大约 1/3),因为有些会重复。
Bagging 适合对偏差低、方差高的模型进行融合。它通过降低方差来降低过拟合。因为 Bagging 是并行的生成一堆决策树,这些决策树可以不做剪枝。它其实是生成了一堆弱学习器,从而实现降低过拟合的效果。
Q1:偏差低、方差高的模型指的是什么?
A1:在机器学习中,偏差(bias)和方差(variance)是用来衡量模型的性能的两个重要指标。偏差度量了模型的预测值与真实值之间的差异,即模型的准确性。方差度量了模型对于不同训练数据的敏感性,即模型的稳定性。
偏差低、方差高的模型指的是一个在训练数据上拟合得很好,但对于不同训练数据的泛化能力较差的模型。这种模型通常过度拟合训练数据,导致在新数据上表现不佳。Bagging算法通过降低方差来降低过拟合,从而提高模型的泛化能力。
Q2:偏差越低越好,方差越低越好,对吗?
A2:理论上,偏差和方差都越低越好。然而,在实际应用中,偏差和方差之间通常存在权衡关系。降低偏差可能会增加方差,反之亦然。因此,我们需要在偏差和方差之间找到一个平衡点,使得模型在训练数据上拟合得较好,同时具有较好的泛化能力。
这种权衡关系被称为偏差-方差权衡(bias-variance tradeoff)。在实际应用中,我们通常会通过调整模型的复杂度、正则化等方法来平衡偏差和方差。
Q3:强学习器是指过拟合吗?弱学习器是欠拟合吗?
A3:强学习器和弱学习器并不直接与过拟合和欠拟合相关。
因此,强学习器和弱学习器并不直接与过拟合和欠拟合相关。一个强学习器可能会过拟合,也可能不会;同样,一个弱学习器也可能会欠拟合,也可能不会。
目标:把下面的圈和方块进行分类。
实现过程:
一、采样不同数据集
二、训练分类器
三、平权投票,获取最终结果
四、主要实现过程小结
在机器学习中,随机森林是一个包含多个决策树的分类器,并且其输出的类别是由个别树输出的类别的众数而定。
随机森林 = B a g g i n g + 决策树 随机森林 = \mathrm{Bagging} + 决策树 随机森林=Bagging+决策树
例如,如果你训练了 5 棵树,其中有 4 个树的结果是 True
,1 棵树的结果是 False
,那么最终投票结果就是 True
。
随机森林够造过程中的关键步骤( M M M 表示特征数目):
Step.1 一次随机选出一个样本,有放回的抽样,重复 N N N 次(有可能出现重复的样本)。
Step.2 随机选出 m m m 个特征( m ≪ M m \ll M m≪M),建立决策树。
思考:
在随机森林的构造过程中,如果进行有放回的抽样,我们会发现:总是有一部分样本我们选不到的。那么就带来一些问题:
在随机森林的 Bagging 过程中,对于每一棵训练出的决策树 g t gt gt,与数据集 D D D 有如下关系:
数据点 | 决策树 g 1 g_1 g1 | 决策树 g 2 g_2 g2 | 决策树 g 3 g_3 g3 | … | 决策树 g T g_T gT |
---|---|---|---|---|---|
( x 1 , y 1 ) (x_1, y_1) (x1,y1) | D 1 D_1 D1 | * | D 3 D_3 D3 | … | D T D_T DT |
( x 2 , y 2 ) (x_2, y_2) (x2,y2) | * | * | D 3 D_3 D3 | … | D T D_T DT |
( x 3 , y 3 ) (x_3, y_3) (x3,y3) | * | D 2 D_2 D2 | * | … | D T D_T DT |
… | … | … | … | … | … |
( x N , y N ) (x_N, y_N) (xN,yN) | D 1 D_1 D1 | D 2 D_2 D2 | * | … | * |
这张表格展示了随机森林中每一棵决策树 g t g_t gt 与数据集 D D D 的关系。表格中的每一行代表一个数据点 ( x n , y n ) (x_n, y_n) (xn,yn),每一列代表一棵决策树 g t g_t gt。如果一个数据点 ( x n , y n ) (x_n, y_n) (xn,yn) 被用于训练某棵决策树 g t g_t gt,则对应的单元格中会显示该决策树的训练数据集 D t D_t Dt。如果一个数据点 ( x n , y n ) (x_n, y_n) (xn,yn) 没有被用于训练某棵决策树 g t g_t gt,则对应的单元格中会显示一个星号 *
,表示这个数据点是包外数据(Out-of-bag,OOB)。
当数据足够多,对于任意一组数据 ( x n , y n ) (x_n, y_n) (xn,yn) 是包外数据的概率为:
( 1 − 1 N ) N = 1 ( N N − 1 ) N = 1 ( 1 + 1 N − 1 ) N ≈ 1 e ≈ 36.8 % (1 - \frac{1}{N})^N = \frac{1}{(\frac{N}{N-1})^N} = \frac{1}{(1+\frac{1}{N-1})^N} \approx \frac{1}{e} \approx 36.8\% (1−N1)N=(N−1N)N1=(1+N−11)N1≈e1≈36.8%
由于基分类器是构建在训练样本的自助抽样集上的,只有约 63.2% 原样本集出现在中,而剩余的 36.8% 的数据作为包外数据,可以用于基分类器的验证集。
经验证,包外估计是对集成分类器泛化误差的无偏估计。
在随机森林算法中数据集属性的重要性、分类器集强度和分类器间相关性计算都依赖于包外数据。
无偏估计就是我认为所有样本出现的概率一样。
假如有 N N N 种样本我们认为所有样本出现概率都是 1 / N 1/N 1/N。然后根据这个来计算数学期望。此时的数学期望就是我们平常讲的平均值。
数学期望本质就是平均值
首先回答第一个问题:它要“估计”什么?
第二个问题:那为何叫做无偏?有偏是什么?
假设这个是一些样本的集合 X = x 1 , x 2 , x 3 , . . . , x N X = {x_1, x_2, x_3, ..., x_N} X=x1,x2,x3,...,xN。我们根据样本估计整体的数学期望(平均值)。
因为正常求期望是加权和,什么叫加权和? E ( X ) = E ( P i × x i ) E(X)=E(P_i \times x_i) E(X)=E(Pi×xi) 这个就叫加权和。因为每个样本出现概率不一样,概率大的加起来就大,这就产生偏重了(有偏估计)。但是,我们不知道某个样本出现的概率啊。比如你从别人口袋里面随机拿了 3 张钞票。两张是 10 块钱,一张 100 元,然后你想估计下他口袋里的剩下的钱平均下来每张多少钱(估计平均值)。
然后呢?
无偏估计计算数学期望就是认为所有样本出现概率一样大,没有看不起哪个样本。
回到求钱的平均值的问题。无偏估计我们认为每张钞票出现概率都是 1 / 2 1/2 1/2(因为只出现了 10 和 100 这两种情况,所以是 1 / 2 1/2 1/2。如果是出现 1,10,100三种情况,每种情况概率则是 1 / 3 1/3 1/3。
哪怕拿到了两张 10 块钱,我还是认为 10 块钱出现的概率和 100 元的概率一样。不偏心。所以无偏估计所估计的别人口袋每张钱的数学期望(平均值)= 10 × 1 / 2 + 100 × 1 / 2 10 \times 1/2 + 100 \times 1/2 10×1/2+100×1/2。
有偏估计那就是偏重那些出现次数多的样本,认为样本的概率是不一样的。
比如我出现了两次 10 块钱,那么我认为 10 块钱的概率是 2 / 3 2/3 2/3,100 块钱概率只有 1 / 3 1/3 1/3。有偏所估计的别人口袋每张钱的数学期望(平均值)= 10 × 2 / 3 + 100 × 1 / 3 10 \times 2/3 + 100 \times 1/3 10×2/3+100×1/3。
当基学习器是决策树时,可使用包外样本来辅助剪枝,或用于估计决策树中各结点的后验概率以辅助对零训练样本结点的处理。
当基学习器是神经网络时,可使用包外样本来辅助早期(Early Stop)停止以减小过拟合。
sklearn.ensemble.RandomForestClassifier(n_estimators=100, criterion='gini',
max_depth=None, min_samples_split=2,
min_samples_leaf=1, min_weight_fraction_leaf=0.0,
max_features='sqrt',max_leaf_nodes=None,
min_impurity_decrease=0.0, bootstrap=True,
oob_score=False, n_jobs=None, random_state=None,
verbose=0, warm_start=False, class_weight=None,
ccp_alpha=0.0, max_samples=None)
ensemble:英[ɒnˈsɒmbl] 美[ɑːnˈsɑːmbl]
n.(经常在一起演出的小型)乐团,剧团,舞剧团; 全体,整体; 合奏,合唱; 成套的东西;
adv. 一起,同时;
作用:sklearn.ensemble.RandomForestClassifier
是 scikit-learn 库中的一个随机森林分类器。它是一个元估计器,它适用于数据集的各个子样本上的多个决策树分类器,并使用平均值来提高预测准确性并控制过拟合。如果 bootstrap=True
(默认),则使用 max_samples
参数控制子样本大小,否则将使用整个数据集构建每棵树。
参数:
n_estimators
:森林中树的数量,默认值为 100。criterion
:衡量拆分质量的函数,默认为 “gini”,支持的标准是 “gini” 用于 Gini 不纯度和 “entropy” 用于香农信息增益。max_depth
:树的最大深度,如果为 None,则节点将展开,直到所有叶子都是纯净的或直到所有叶子都包含少于 min_samples_split
个样本。bootstrap
:是否对样本集进行有放回抽样来构建树,默认值为 True
。random_state
:控制构建树时使用的样本的随机性(如果 bootstrap=True
)和在每个节点上寻找最佳分割时要考虑的要素采样(如果 max_features < n_features
)。min_samples_split
:拆分内部节点所需的最小样本数,默认值为 2。max_features
:在寻找最佳拆分时要考虑的特征数量。它可以是整数、浮点数或字符串,其默认值为 “sqrt”。
max_features
个特征。max_features
是一个分数,每次拆分时都会考虑 max(1, int(max_features * n_features_in_))
个特征。sqrt(n_features)
个特征。sqrt(n_features)
个特征。log2(n_features)
个特征。min_samples_leaf
:在叶节点处需要的最小样本数。仅在任何深度的拆分点都只会被考虑,如果它在左右分支中都留下至少 min_samples_leaf
个训练样本。这可能会使模型平滑,特别是在回归中。如果为整数,则将 min_samples_leaf
视为最小值。如果为浮点数,则 min_samples_leaf
是一个分数,而 ceil(min_samples_leaf * n_samples)
是每个节点的最小样本数。min_impurity_decrease
:一个节点将被拆分的最小不纯度减少量。如果拆分导致的不纯度减少量大于或等于这个值,则该节点将被拆分,否则不拆分。返回值:返回一个随机森林分类器对象,具有多种方法,如:
fit()
predict()
predict_proba()
上面决策树参数中最重要的包括:
max_features
max_depth
min_samples_split
min_samples _leaf
零:数据读取及其处理
这里仍然使用 Titanic 数据集。
import pandas as pd
import numpy as np
from sklearn.feature_extraction import DictVectorizer
from sklearn.model_selection import train_test_split
# 1. 获取数据
titanic = pd.read_csv("../data/titanic.txt")
## 2.1 确定特征值和目标值
x = titanic[["pclass", "sex", "age"]]
y = titanic["survived"]
## 2.2 缺失值处理
# 缺失值需要处理,将特征中有类别的特征进行字典特征抽取
x.loc[x['age'].isnull(), 'age'] = x['age'].mean()
## 2.3 数据集划分
x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=22)
# 3. 特征工程(字典特征抽取)
## 3.1 实例化一个字典转换器类
transfer = DictVectorizer(sparse=False) # 不用输出稀疏矩阵
## 3.2 将DataFrame转换为字典数据
x_train = x_train.to_dict(orient="records")
x_test = x_test.to_dict(orient="records")
## 3.3 特征转换
x_train = transfer.fit_transform(x_train)
# 注意:在测试数据上,应该使用与训练数据相同的转换方式,因此应该使用 `transform` 方法,
# 而不是 `fit_transform` 方法。`transform` 方法只进行转换,不会改变转换器的拟合结果。
x_test = transfer.transform(x_test)
一、实例化随机森林
## 4.0 导入必要的库
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
## 4.1 实例化随机森林
rf = RandomForestClassifier()
二、定义超参数的选择列表
## 4.2 定义超参数的选择列表
hyper_params = {"n_estimators": [120, 200, 300, 500, 800, 1200], "max_depth": [5, 8, 15, 25, 30]}
三、使用 GridSearchCV
进行网格搜索交叉验证
## 4.3 超参数调优
gc = GridSearchCV(estimator=rf, param_grid=hyper_params, cv=2) # 2-fold
四、模型训练及评估
## 5.1 模型训练
gc.fit(x_train, y_train)
## 5.2 模型评估
print("随机森林预测的准确率为:", gc.score(x_test, y_test))
运行结果:
随机森林预测的准确率为: 0.7713414634146342
之前我们仅使用决策树取得的准确率为:0.7560975609756098
注意:
Bagging(Bootstrap aggregating)是一种集成学习算法,它通过并行地训练一系列各自独立的同类模型,然后再将各个模型的输出结果按照某种策略进行聚合(例如分类中可采用投票策略,回归中可采用平均策略)。Bagging 算法可与其他分类、回归算法结合,提高其准确率、稳定性的同时,通过降低结果的方差,避免过拟合的发生。
B a g g i n g + 决策树 / 线性回归 / 逻辑回归 / 深度学习 . . . = B a g g i n g 集成学习方法 \mathrm{Bagging} + 决策树/线性回归/逻辑回归/深度学习... = \mathrm{Bagging}集成学习方法 Bagging+决策树/线性回归/逻辑回归/深度学习...=Bagging集成学习方法
Bagging 算法的特点如下:
Bagging 集成的优点包括:
Bagging 集成的缺点包括:
小结:
sklearn.ensemble.RandomForestClassifier()
奥托集团是世界上最大的电子商务公司之一,在 20 多个国家设有子公司。该公司每天都在世界各地销售数百万种产品,所以对其产品根据性能合理的分类非常重要。
不过,在实际工作中,工作人员发现许多相同的产品得到了不同的分类。本案例要求你对奥拓集团的产品进行正确的分分类。尽可能的提供分类的准确性。
链接:Otto Group Product Classification Challenge
对于这次比赛,我们提供了一个包含超过200,000种产品的93个特征的数据集。目标是建立一个预测模型,能够区分我们的主要产品类别。
本案例中,数据集包含大约 200,000 种产品的 93 个特征。其目的是建立一个能够区分 otto 公司主要产品类别的预测模型。所有产品共被分成九个类别(例如时装,电子产品等)。
其中:
id
:产品IDfeat_1, feat_2, ..., feat_93
:产品的各个特征target
:产品被划分的类别本案例中,最后结果使用多分类对数损失进行评估。具体公式为:
l o g l o s s = − 1 N ∑ i = 1 N ∑ j = 1 M y i j log ( p i j ) \mathrm{log \ loss} = -\frac{1}{N} \sum_{i=1}^N \sum_{j=1}^M y_{ij}\log{(p_{ij})} log loss=−N1i=1∑Nj=1∑Myijlog(pij)
其中:
根据上公式,假如模型将所有的测试样本都正确分类,即所有 p i j p_{ij} pij 都是1,那每个 log ( p i j ) \log{(p_{ij})} log(pij) 都是0,最终的 l o g l o s s ( p i j ) \mathrm{log \ loss}{(p_{ij})} log loss(pij) 也是0。
假如第 1 个样本本来是属于 1 类别的,但模型给它的类别概率 p i j = 0.1 p_{ij} = 0.1 pij=0.1,那 l o g l o s s \mathrm{log \ loss} log loss 就会累加上 log ( 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 \mathrm{log \ loss} log loss 就是无穷。这当然不合理,为了避免这一情况,我们对非常小的值做如下处理:
max ( min ( p , 1 − 1 0 − 15 ) , 1 0 − 15 ) \max(\min(p, 1-10^{-15}), 10^{-15}) max(min(p,1−10−15),10−15)
也就是说,最小不会小于 1 0 − 15 10^{-15} 10−15。
导入库:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
一、读取数据
data = pd.read_csv("./data/otto-group-product-classification-challenge/train.csv")
图像可视化,查看数据分布
# 图像可视化,查看数据分布
import seaborn as sns
fig = plt.figure(dpi=80)
sns.countplot(x=data.target)
plt.show()
由上图可以看出,该数据不同类别的样本数量不均衡,所以需要后期处理。
二、数据基本处理
因为数据已经脱敏,不需要再进行特殊处理。
二·一 数据量比较大,尝试是否可以进行数据分割
new_data = data[:10000]
new_data.shape
fig = plt.figure(dpi=80)
sns.countplot(x=new_data.target)
plt.show()
说明使用上述的方式截取数据是不可行的,需要使用 随机欠采样 截取得到部分数据集。
# 随机欠采样获取部分数据集
## 首先需要确定特征值和目标值
y = data["target"] # 目标值
x = data.drop(["id", "target"], axis=1) # 特征值
y.head()
0 Class_1
1 Class_1
2 Class_1
3 Class_1
4 Class_1
Name: target, dtype: object
x.head()
## 欠采样获取数据
from imblearn.under_sampling import RandomUnderSampler
rus = RandomUnderSampler(random_state=0)
x_resampled, y_resampled = rus.fit_resample(x, y)
fig = plt.figure(dpi=80)
sns.countplot(x=y_resampled)
plt.show()
所有类别都有了,且样本不均衡问题也被解决了。
二·二 转换目标值表示方式
此时的目标值是Class_*
,这样是不行的,需要将目标值转换为数字。
# 把目标值转换为数字
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
y_resampled = le.fit_transform(y_resampled)
y_resampled
array([0, 0, 0, ..., 8, 8, 8])
分割数据:分为训练集和测试集
# 分割数据
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(x_resampled, y_resampled, test_size=0.2)
x_train.shape, x_test.shape, y_train.shape, y_test.shape
# ((13888, 93), (3473, 93), (13888,), (3473,))
三、模型训练
三·一 基本模型训练
## 基本模型训练
from sklearn.ensemble import RandomForestClassifier
rfc = RandomForestClassifier(oob_score=True) # 使用袋外估计
rfc.fit(x_train, y_train)
四、模型评估
y_pred = rfc.predict(x_test)
print("模型预测结果为:\r\n", y_pred)
score = rfc.score(x_test, y_test)
print(f"测试集上,模型准确率为:{score * 100:.2f}%")
print(f"模型袋外估计准确率为:{rfc.oob_score_ * 100:.2f}%")
# 绘图展示模型预测结果
fig = plt.figure(dpi=80)
sns.countplot(x=y_pred)
plt.show()
虽然我们进行了模型准确率以及袋外估计,但案例要求我们使用 log loss 进行评估,所以我们还需要使用 log loss 对模型性能进行评估。
# log loss模型评估
from sklearn.metrics import log_loss
log_loss(y_test, y_pred, eps=1e-15, normalize=True)
报错了:
ValueError: y_true and y_pred contain different number of classes 9, 2. Please provide the true labels explicitly through the labels argument. Classes found in y_true: [0 1 2 3 4 5 6 7 8]
y_test, y_pred
(array([3, 0, 8, ..., 7, 1, 3]), array([3, 7, 8, ..., 7, 1, 3]))
上面报错的原因是:log loss 在使用的时候要求输出用 one-hot 表示。
from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder(sparse_output=False)
y_test = ohe.fit_transform(y_test.reshape(-1, 1))
y_pred = ohe.fit_transform(y_pred.reshape(-1, 1))
y_test
array([[1., 0.],
[1., 0.],
[1., 0.],
...,
[1., 0.],
[1., 0.],
[1., 0.]])
y_pred
array([[1., 0.],
[1., 0.],
[1., 0.],
...,
[1., 0.],
[1., 0.],
[1., 0.]])
之后再进行log loss的模型评估:
# log loss模型评估
from sklearn.metrics import log_loss
log_loss_value = log_loss(y_test, y_pred, eps=1e-15, normalize=True)
print(f"Log Loss为:{log_loss_value:.4f}")
Log Loss为:7.7372
改变预测值的输出模式,让输出结果为百分占比,从而降低Log Loss值
# 改变预测值的输出模式,让输出结果为百分占比,从而降低Log Loss值
y_pred_proba = rfc.predict_proba(x_test)
y_pred_proba
array([[0. , 0.27, 0.08, ..., 0. , 0. , 0.01],
[0.26, 0.01, 0. , ..., 0.11, 0.48, 0.07],
[0.23, 0.04, 0.03, ..., 0.17, 0.16, 0.3 ],
...,
[0.09, 0.03, 0.04, ..., 0.03, 0.66, 0.02],
[0. , 0.57, 0.24, ..., 0.05, 0.01, 0.01],
[0.05, 0.1 , 0.15, ..., 0.08, 0.07, 0.04]])
# 再进行Log Loss的计算
log_loss_value = log_loss(y_test, y_pred_proba, eps=1e-15, normalize=True)
print(f"Log Loss为:{log_loss_value:.4f}")
Log Loss为:0.7355
五、模型调优
我们可以对一些超参数调优:
n_estimators
max_feature
max_depth
min_samples_leaf
五·一 确定最优的n_estimators
# 5. 模型调优
## 5.1 确定最优的n_estimators
### 5.1.1 确定n_estimators的取值范围
tuned_parameters = range(10, 200, 10)
### 5.1.2 创建添加accuracy的一个numpy对象
accuracy_t = np.zeros(len(tuned_parameters))
### 5.1.3 创建添加error的一个numpy对象
error_t = np.zeros(len(tuned_parameters))
## 5.2 调优过程实现
for j, one_parameter in enumerate(tuned_parameters):
rfc = RandomForestClassifier(n_estimators=one_parameter, max_depth=10,
max_features=10, min_samples_leaf=10,
oob_score=True, random_state=0, n_jobs=-1)
rfc.fit(x_train, y_train)
# 输出accuracy
accuracy_t[j] = rfc.oob_score_
# 输出Log Loss
y_proba = rfc.predict_proba(x_test)
error_t[j] = log_loss(y_test, y_proba, eps=1e-15, normalize=True)
[1.12335669 1.12266148 1.1233354 1.11860337 1.11340172 1.11250767
1.11215495 1.11258237 1.11293157 1.10973597 1.10747978 1.10730151
1.10738276 1.10711527 1.10663725 1.10629273 1.1068497 1.10701413
1.10695667]
# 优化结果过程可视化
fig, axes = plt.subplots(1, 2, figsize=(16, 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")
axes[1].set_xlabel("n_estimators")
axes[1].set_ylabel("acc")
axes[0].grid(True)
axes[1].grid(True)
plt.show()
经过图像展示,最后确定 n_estimators=175
的时候,模型表现效果不错。
五·二 确定最优的max_feature
# 5. 模型调优
## 5.2 确定最优的max_feature
### 5.2.1 确定max_feature的取值范围
tuned_parameters = range(5, 40, 5)
### 5.2.2 创建添加accuracy的一个numpy对象
accuracy_t = np.zeros(len(tuned_parameters))
### 5.2.3 创建添加error的一个numpy对象
error_t = np.zeros(len(tuned_parameters))
## 5.2 调优过程实现
for j, one_parameter in enumerate(tuned_parameters):
rfc = RandomForestClassifier(n_estimators=175, max_depth=10,
max_features=one_parameter, min_samples_leaf=10,
oob_score=True, random_state=0, n_jobs=-1)
rfc.fit(x_train, y_train)
# 输出accuracy
accuracy_t[j] = rfc.oob_score_
# 输出Log Loss
y_proba = rfc.predict_proba(x_test)
error_t[j] = log_loss(y_test, y_proba, eps=1e-15, normalize=True)
print(f"最小的损失为:{min(error_t)},位置为:{np.argmin(error_t)}")
# 最小的损失为:1.0432210801563477,位置为:5
# 优化结果过程可视化
fig, axes = plt.subplots(1, 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")
axes[1].set_xlabel("max_features")
axes[1].set_ylabel("acc")
axes[0].grid(True)
axes[1].grid(True)
plt.show()
经过图像展示,最后确定 max_feature=15
的时候,模型表现效果不错。
五·三 确定最优的max_depth
# 5. 模型调优
## 5.3 确定最优的max_depth
### 5.3.1 确定max_depth的取值范围
tuned_parameters = range(10, 100, 10)
### 5.3.2 创建添加accuracy的一个numpy对象
accuracy_t = np.zeros(len(tuned_parameters))
### 5.3.3 创建添加error的一个numpy对象
error_t = np.zeros(len(tuned_parameters))
## 5.2 调优过程实现
for j, one_parameter in enumerate(tuned_parameters):
rfc = RandomForestClassifier(n_estimators=175, max_depth=one_parameter,
max_features=15, min_samples_leaf=10,
oob_score=True, random_state=0, n_jobs=-1)
rfc.fit(x_train, y_train)
# 输出accuracy
accuracy_t[j] = rfc.oob_score_
# 输出Log Loss
y_proba = rfc.predict_proba(x_test)
error_t[j] = log_loss(y_test, y_proba, eps=1e-15, normalize=True)
print(f"最小的损失为:{min(error_t)},位置为:{np.argmin(error_t)}")
print(f"最高的准确率为:{max(accuracy_t)},位置为:{np.argmax(accuracy_t)}")
# 最小的损失为:0.8220898016223404,位置为:2
# 最高的准确率为:0.7469758064516129,位置为:4
# 优化结果过程可视化
fig, axes = plt.subplots(1, 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_depth")
axes[0].set_ylabel("error")
axes[1].set_xlabel("max_depth")
axes[1].set_ylabel("acc")
axes[0].grid(True)
axes[1].grid(True)
plt.show()
经过图像展示,最后确定 max_depth=30
的时候,模型表现效果不错。
五·四 确定最优的min_sample_leaf
# 5. 模型调优
## 5.4 确定最优的min_samples_leaf
### 5.4.1 确定min_samples_leaf的取值范围
tuned_parameters = range(1, 10, 2)
### 5.4.2 创建添加accuracy的一个numpy对象
accuracy_t = np.zeros(len(tuned_parameters))
### 5.4.3 创建添加error的一个numpy对象
error_t = np.zeros(len(tuned_parameters))
## 5.2 调优过程实现
for j, one_parameter in enumerate(tuned_parameters):
rfc = RandomForestClassifier(n_estimators=175, max_depth=30,
max_features=15, min_samples_leaf=one_parameter,
oob_score=True, random_state=0, n_jobs=-1)
rfc.fit(x_train, y_train)
# 输出accuracy
accuracy_t[j] = rfc.oob_score_
# 输出Log Loss
y_proba = rfc.predict_proba(x_test)
error_t[j] = log_loss(y_test, y_proba, eps=1e-15, normalize=True)
print(f"最小的损失为:{min(error_t)},位置为:{np.argmin(error_t)}")
print(f"最高的准确率为:{max(accuracy_t)},位置为:{np.argmax(accuracy_t)}")
# 最小的损失为:0.705443621042576,位置为:0
# 最高的准确率为:0.7696572580645161,位置为:0
# 优化结果过程可视化
fig, axes = plt.subplots(1, 2, figsize=(20, 4), dpi=100)
axes[0].plot(tuned_parameters, error_t)
axes[1].plot(tuned_parameters, accuracy_t)
axes[0].set_xlabel("min_samples_leaf")
axes[0].set_ylabel("error")
axes[1].set_xlabel("min_samples_leaf")
axes[1].set_ylabel("acc")
axes[0].grid(True)
axes[1].grid(True)
plt.show()
经过图像展示,最后确定 min_sample_leaf=1
的时候,模型表现效果不错。
因此我们可以确定最优模型:
n_estimators=175
max_feature=15
max_depth=30
min_samples_leaf=1
rfc_best = RandomForestClassifier(n_estimators=175, max_depth=30,
max_features=15, min_samples_leaf=1,
oob_score=True, random_state=0, n_jobs=-1)
rfc_best.fit(x_train, y_train)
y_pred = rfc_best.predict(x_test)
print("模型预测结果为:\r\n", y_pred)
print(f"模型袋外估计准确率为:{rfc_best.oob_score_ * 100:.2f}%")
y_pred_proba = rfc_best.predict_proba(x_test)
log_loss_value = log_loss(y_test, y_pred_proba, eps=1e-15, normalize=True)
print(f"Log Loss为:{log_loss_value:.4f}")
模型预测结果为:
[5 4 1 ... 8 8 1]
模型袋外估计准确率为:77.20%
Log Loss为:0.7201
相比默认参数的模型,此时模型的性能均有提升。
# 读取测试集
test_data = pd.read_csv("./data/otto-group-product-classification-challenge/test.csv")
# 丢弃id列
test_data_drop_id = test_data.drop(["id"], axis=1)
test_data_drop_id.head()
# 得到预测结果(概率的形式)
y_pred_test = rfc_best.predict_proba(test_data_drop_id)
# 将ndarray转换为df
res_data = pd.DataFrame(y_pred_test, columns=["Class_" + str(i) for i in range(1, 10)])
# 添加id
res_data.insert(loc=0, column="id", value=test_data.id)
# 保存为本地文件
res_data.to_csv("./data/otto-group-product-classification-challenge/submission.csv", index=False)
学习目标:
Boosting 是一种机器学习算法,它可以用来减小监督式学习中偏差。Boosting 是一种串行的工作机制,即个体学习器的训练存在依赖关系,必须一步一步序列化进行。它通过组合多个弱学习器形成一个强学习器,提高模型的整体预测精度。Boosting 总是更加关注被错误分类的弱规则。
模型随着学习的积累从弱到强。
简而言之:每新加入一个弱学习器,整体能力就会得到提升。
代表算法:
Boosting 和 Bagging 都是机器学习模型融合中的两种策略。它们都可以将弱分类器融合之后形成一个强分类器,而且融合之后的效果会比最好的弱分类器更好。但是它们之间也有一些区别:
步骤一:初始化训练数据权重相等,训练第一个学习器。
假设每个训练样本在基分类器的学习中作用相同。这一假设可以保证第一步能够在原始数据上学习基本分类器 H 1 ( x ) H_1(x) H1(x)
步骤二:AdaBoost 反复学习基本分类器,在每一轮 m = 1 , 2 , . . . , M m = 1,2,..., M m=1,2,...,M 顺次的执行下列操作:
将下一轮学习器的注意力集中在错误数据上
步骤三:对 m m m 个学习器进行加权投票 H ( x ) = s i g n ( ∑ i = 1 m α i h i ( x ) ) H(x) = \mathrm{sign}(\sum_i=1^m\alpha_i h_i(x)) H(x)=sign(i∑=1mαihi(x))
给定下面这张训练数据表所示的数据,假设弱分类器由 x v xv xv 产生,其阈值 v v v 使该分类器在训练数据集上的分类误差率最低,试用 AdaBoost 算法学习一个强分类器。
序号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
x | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
y | 1 | 1 | 1 | -1 | -1 | -1 | 1 | 1 | 1 | -1 |
问题解答:
步骤一:初始化训练数据权重相等,训练第一个学习器: 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.1,i=1,2,...,10
步骤二:AdaBoost 反复学习基本分类器,在每一轮 m = 1 , 2 , . . . , M m = 1,2, ..., M m=1,2,...,M 顺次的执行下列操作:
当 m = 1 m=1 m=1 的时候:
6,7,8被分错
当 m = 2 m=2 m=2 的时候:
3,4,5被分错
当 m = 3 m=3 m=3 的时候:
3,4,5被分错
步骤三:对 m m 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) = \mathrm{sign}[0.4236h_1(x) + 0.6496h_2(x) + 0.7514h_3(x)] H3(x)=sign[0.4236h1(x)+0.6496h2(x)+0.7514h3(x)]
from sklearn.ensemble import AdaBoostClassifier
API 链接:https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.AdaBoostClassifier.html#sklearn.ensemble.AdaBoostClassifier
小结:
学习目标:
GBDT的全称是Gradient Boosting Decision Tree,梯度提升树。在传统机器学习算法中,GBDT算的上是TOP-3的算法。想要理解GBDT的真正意义,那就必须理解GBDT中的Gradient Boosting和Decision Tree分别是什么。
首先,GBDT使用的决策树是CART回归树,无论是处理回归问题还是二分类以及多分类,GBDT使用的决策树通通都是都是CART回归树。
Q:为什么不用CART分类树呢?
A:因为GBDT每次迭代要拟合的是梯度值,是连续值所以要用回归树。
对于回归树算法来说最重要的是寻找最佳的划分点,那么回归树中的可划分点包含了所有特征的所有可取的值。
在分类树中最佳划分点的判别标准是熵或者基尼系数,都是用纯度来衡量的,但是在回归树中的样本标签是连续数值,所以再使用熵之类的指标不再合适,取而代之的是平方误差,它能很好的评判拟合程度。
梯度提升树(Gradient Boosting)是提升树(Boosting Tree)的一种改进算法,所以在讲梯度提升树之前先来说一下提升树。
先来个通俗理解:假如有个人30岁,我们首先用20岁去拟合,发现损失有10岁,这时我们用6岁去拟合剩下的损失,发现差距还有4岁,第三轮我们用3岁拟合剩下的差距,差距就只有一岁了。如果我们的迭代轮数还没有完,可以继续迭代下面,每一轮迭代,拟合的岁数误差都会减小。最后将每次拟合的岁数加起来便是模型输出的结果。
提升树算法:
Q:上面伪代码中的残差是什么?
A:在提升树算法中:
假设我们前一轮迭代得到的强学习器是 f t − 1 ( x ) f_{t-1}(x) ft−1(x)
损失函数是 L ( y , f t − 1 ( x ) ) L(y,f_{t-1}(x)) L(y,ft−1(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,ft—1(x)+ht(x))
当采用平方损失函数时: L ( y , f t − 1 ( x ) + h t ( x ) ) = ( y − f t − 1 ( x ) − h t ( x ) ) 2 = ( r − h t ( x ) ) 2 L(y, f_{t-1}(x) + h_t(x)) = (y - f_{t-1}(x) - h_t(x))^2 = (r-h_t(x))^2 L(y,ft−1(x)+ht(x))=(y−ft−1(x)−ht(x))2=(r−ht(x))2
这里, r = y − f t − 1 ( x ) r = y - f_{t-1}(x) r=y−ft−1(x)是当前模型拟合数据的残差(residual) 。
所以,对于提升树来说只需要简单地拟合当前模型的残差。
回到我们上面讲的那个通俗易懂的例子中,第一次迭代的残差是10岁,第二次残差4岁…
当损失函数是平方损失和指数损失函数时,梯度提升树每一步优化是很简单的,但是对于一般损失函数而言,往往每一步优化起来不那么容易。
针对这一问题,Friedman提出了梯度提升树算法,这是利用最速下降的近似方法,其关键是利用损失函数的负梯度作为提升树算法中的残差的近似值。
那么负梯度长什么样呢?
− [ ∂ L ( y , f ( x i ) ) ∂ f ( x i ) ] f ( x ) = f t − 1 ( x ) -\left[ \frac{\partial L(y, f(x_i))}{\partial f(x_i)} \right]_{f(x) = f_{t-1}(x)} −[∂f(xi)∂L(y,f(xi))]f(x)=ft−1(x)
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(y−f(xi))2
− [ ∂ L ( y , f ( x i ) ) ∂ f ( x i ) ] f ( x ) = f t − 1 ( x ) = y − f ( x i ) -\left[ \frac{\partial L(y, f(x_i))}{\partial f(x_i)} \right]_{f(x) = f_{t-1}(x)} = y - f(x_i) −[∂f(xi)∂L(y,f(xi))]f(x)=ft−1(x)=y−f(xi)
此时我们发现GBDT的负梯度就是残差,所以说对于回归问题,我们要拟合的就是残差。
那么对于分类问题呢?
本文以回归问题为例进行讲解。
上面两节分别将Decision Tree和Gradient Boosting介绍完了,下面将这两部分组合在一起就是我们的GBDT了。
步骤一:初始化弱学习器
f 0 ( x ) = a r g m i n c ∑ i = 1 N L ( y i , c ) f_0(x) = \mathrm{argmin}_c\sum_{i = 1}^N L(y_i, c) f0(x)=argminci=1∑NL(yi,c)
步骤二:对 m = 1 , 2 , . . . , M m = 1, 2, ..., M m=1,2,...,M 有:
步骤三:得到最终学习器
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_{m = 1}^M\sum_{j=1}^J\gamma_{jm}I(x\in R_{jm}) f(x)=fM(x)=f0(x)+m=1∑Mj=1∑JγjmI(x∈Rjm)
根据如下数据,预测最后一个样本的身高。
编号 | 年龄(岁) | 体重(kg) | 身高(m)(标签值/目标值) |
---|---|---|---|
0 | 5 | 20 | 1.1 |
1 | 7 | 30 | 1.3 |
2 | 21 | 70 | 1.7 |
3 | 30 | 60 | 1.8 |
4(要预测的) | 25 | 65 | ? |
learning_rate=0.1
n_trees=5
max_depth=3
步骤一:初始化弱学习器
f 0 ( x ) = a r g m i n c ∑ i = 1 N L ( y , c ) f_0(x) = \mathrm{argmin}_c\sum_{i=1}^N L(y, c) f0(x)=argminci=1∑NL(y,c)
损失函数为平方损失,因为平方损失函数是一个凸函数,直接求导,倒数等于零,得到 c c c。
∑ i = 1 N ∂ L ( y , c ) ∂ c = ∑ i = 1 N ∂ ( 1 2 ( y i − c ) 2 ) ∂ c = ∑ i = 1 N c − y i \sum^N_{i = 1}\frac{\partial L(y, c)}{\partial c} = \sum_{i = 1}^N \frac{\partial(\frac{1}{2}(y_i - c)^2)}{\partial c} = \sum_{i=1}^N c-y_i i=1∑N∂c∂L(y,c)=i=1∑N∂c∂(21(yi−c)2)=i=1∑Nc−yi
令导数等于0:
∑ i = 1 N c − y i = 0 \sum_{i=1}^N c-y_i = 0 i=1∑Nc−yi=0
c = ∑ i = 1 N y i N c = \frac{\sum^N_{i = 1}y_i}{N} c=N∑i=1Nyi
所以初始化时, c c c 取值为所有训练样本标签值的均值。 c = ( 1.1 + 1.3 + 1.7 + 1.8 ) / 4 = 1.475 c =(1.1+1.3+1.7+1.8)/4= 1.475 c=(1.1+1.3+1.7+1.8)/4=1.475,此时得到初始学习器 f 0 ( x ) f_0(x) f0(x):
f 0 ( x ) = c = 1.475 f_0(x) = c = 1.475 f0(x)=c=1.475
步骤二:对迭代轮数 m = 1 , 2 , . . . , M m=1,2,...,M m=1,2,...,M:
由于我们设置了迭代次数:n_trees=5
,这里的 M = 5 M =5 M=5。
计算负梯度,根据上文损失函数为平方损失时,负梯度就是残差,再直白一点就是 y y y 与上一轮得到的学习器 f m − 1 f_{m-1} fm−1 的差值:
r i 1 = − [ ∂ L ( y , f ( x i ) ) ∂ f ( x i ) ] f ( x ) = f 0 ( x ) r_{i1} = -\left[ \frac{\partial L(y, f(x_i))}{\partial f(x_i)} \right]_{f(x)=f_0(x)} ri1=−[∂f(xi)∂L(y,f(xi))]f(x)=f0(x)
残差在下表列出:
编号 | 真实值 | f 0 ( x ) f_0(x) f0(x) | 残差 |
---|---|---|---|
0 | 1.1 | 1.475 | -0.375 |
1 | 1.3 | 1.475 | -0.175 |
2 | 1.7 | 1.475 | 0.225 |
3 | 1.8 | 1.475 | 0.325 |
此时将残差作为样本的真实值来训练弱学习器 f 1 ( x ) f_1(x) f1(x),即下表数据:
编号 | 年龄(岁) | 体重(kg) | 身高(m)(标签值/目标值) |
---|---|---|---|
0 | 5 | 20 | -0.375 |
1 | 7 | 30 | -0.175 |
2 | 21 | 70 | 0.225 |
3 | 30 | 60 | 0.325 |
接着,寻找回归树的最佳划分节点,遍历每个特征的每个可能取值。
从年龄特征的5开始,到体重特征的70结束,分别计算分裂后两组数据的平方损失(Square Error)。
S E l \mathrm{SE_l} SEl 为左节点平方损失, S E r \mathrm{SE_r} SEr 为右节点平方损失,找到使平方损失和 S E s u m = S E l + S E r \mathrm{SE_{sum} = SE_l + SE_r} SEsum=SEl+SEr 最小的那个划分节点,即为最佳划分节点。
例如︰以年龄21为划分节点,将小于21的样本划分为到左节点,大于等于21的样本划分为右节点。左节点包括 x 0 , x 1 x_0, x_1 x0,x1,右节点包括样本 x 2 , x 3 x_2, x_3 x2,x3,
S E l = 0.02 , S E r = 0.005 , S E s u m = 0.025 \mathrm{SE_l} = 0.02, \mathrm{SE_r} = 0.005, \mathrm{SE_{sum}} = 0.025 SEl=0.02,SEr=0.005,SEsum=0.025
S E l = [ − 0.375 − ( − 0.275 ) ] 2 + [ − 0.175 − ( − 0.275 ) ] 2 = 0.02 \mathrm{SE_l} = [-0.375 - (-0.275)]^2 + [-0.175 - (-0.275)]^2 = 0.02 SEl=[−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 \mathrm{SE_r} = [0.225 - 0.275]^2 + [0.325 - 0.275]^2 = 0.005 SEr=[0.225−0.275]2+[0.325−0.275]2=0.005
所有可能划分情况如下表所示:
划分点 | 小于划分点的样本 | 大于等于划分点的样本 | S E l \mathrm{SE_l} SEl | S E r \mathrm{SE_r} SEr | S E s u m \mathrm{SE_{sum}} SEsum |
---|---|---|---|---|---|
年龄5 | / | 0, 1, 2, 3 | 0 | 0.327 | 0.327 |
年龄7 | 0 | 1, 2, 3 | 0 | 0.14 | 0.14 |
年龄21 | 0, 1 | 2, 3 | 0.02 | 0.005 | 0.025 |
年龄30 | 0, 1, 2 | 3 | 0.187 | 0 | 0.187 |
体重20 | / | 0, 1, 2, 3 | 0 | 0.327 | 0.327 |
体重30 | 0 | 1, 2, 3 | 0 | 0.14 | 0.14 |
体重60 | 0, 1 | 2, 3 | 0.02 | 0.005 | 0.025 |
体重70 | 0, 1, 3 | 2 | 0.26 | 0 | 0.26 |
以上划分点是的总平方损失最小为0.025,有两个划分点:年龄21和体重60,所以随机选一个作为划分点,这里我们选年龄21。
现在我们的第一棵树长这个样子:
我们设置的参数中树的深度max_depth=3
,现在树的深度只有2,需要再进行一次划分,这次划分要对左右两个节点分别进行划分:
对于左节点,只含有 0, 1 两个样本,根据下表我们选择年龄7划分。
划分点 | 小于划分点的样本 | 大于等于划分点的样本 | S E l \mathrm{SE_l} SEl | S E r \mathrm{SE_r} SEr | S E s u m \mathrm{SE_{sum}} SEsum |
---|---|---|---|---|---|
年龄5 | / | 0, 1 | 0 | 0.02 | 0.02 |
年龄7 | 0 | 1 | 0 | 0 | 0 |
体重20 | / | 0, 1 | 0 | 0.02 | 0.02 |
体重30 | 0 | 1 | 0 | 0 | 0 |
对于右节点,只含有 2,3 两个样本,根据下表我们选择年龄30划分(也可以选体重70)。
划分点 | 小于划分点的样本 | 大于等于划分点的样本 | S E l \mathrm{SE_l} SEl | S E r \mathrm{SE_r} SEr | S E s u m \mathrm{SE_{sum}} SEsum |
---|---|---|---|---|---|
年龄21 | / | 2, 3 | 0 | 0.005 | 0.005 |
年龄30 | 2 | 3 | 0 | 0 | 0 |
体重60 | / | 2, 3 | 0 | 0.005 | 0.005 |
体重70 | 3 | 2 | 0 | 0 | 0 |
现在我们的第一棵树长这个样子:
此时我们的树深度满足了设置,还需要做一件事情,给这每个叶子节点分别赋一个参数 γ \gamma γ,来拟合残差。
γ j 1 = a r g m i n γ ∑ x i ∈ R j 1 L ( y i , f 0 ( x i ) + γ ) \gamma_{j1} = \underset{\gamma}{\mathrm{argmin}}\sum_{x_i \in R_{j1}} L(y_i, f_{0}(x_i) + \gamma) γj1=γargminxi∈Rj1∑L(yi,f0(xi)+γ)
这里其实和上面初始化学习器是一个道理,平方损失,求导,令导数等于零,化简之后得到每个叶子节点的参数 γ \gamma γ,其实就是标签值的均值。这个地方的标签值不是原始的 y y y,而是本轮要拟合的标残差 y − f 0 ( x ) y - f_0(x) y−f0(x)。
根据上述划分结果,为了方便表示,规定从左到右为第 1,2,3,4 个叶子结点
( x 0 ∈ R 11 ) , γ 11 = − 0.375 ( x 1 ∈ R 21 ) , γ 21 = − 0.175 ( x 2 ∈ R 31 ) , γ 31 = 0.225 ( x 3 ∈ R 41 ) , γ 41 = 0.325 (x_0 \in R_{11}), \gamma_{11} = -0.375 \\ (x_1 \in R_{21}), \gamma_{21} = -0.175 \\ (x_2 \in R_{31}), \gamma_{31} = 0.225 \\ (x_3 \in R_{41}), \gamma_{41} = 0.325 \\ (x0∈R11),γ11=−0.375(x1∈R21),γ21=−0.175(x2∈R31),γ31=0.225(x3∈R41),γ41=0.325
此时的树长这个样子:
此时可更新强学习器,需要用到参数学习率:learning_rate=0.1
,用 l r lr lr 表示。
f 1 ( x ) = f 0 ( x ) + l r ∗ ∑ j = 1 4 γ j 1 I ( x ∈ R j 1 ) f_1(x) = f_0(x) + lr * \sum_{j=1}^4\gamma_{j1}I(x \in R_{j1}) f1(x)=f0(x)+lr∗j=1∑4γj1I(x∈Rj1)
为什么要用学习率呢?这是Shrinkage的思想,如果每次都全部加上(学习率为1)很容易一步学到位导致过拟合。
重复此步骤,直到 m > 5 m>5 m>5 结束,最后生成5棵树。
结果中,0.9倍这个现象,和其学习率有关。这是因为数据简单每棵树长得一样,导致每一颗树的拟合效果一样,而每棵树都只学上一棵树残差的0.1倍,导致这颗树只能拟合剩余0.9了。
步骤三:得到最后的强学习器
f ( x ) = f 5 ( x ) = f 0 ( x ) + ∑ m = 1 5 ∑ j = 1 4 γ j m I ( x ∈ R j m ) f(x) = f_5(x) = f_0(x) + \sum^5_{m=1}\sum_{j=1}^4 \gamma_{jm}I(x \in R_{jm}) f(x)=f5(x)=f0(x)+m=1∑5j=1∑4γjmI(x∈Rjm)
步骤四:预测样本
最终预测结果为:
f ( x ) = 1.475 + 0.1 × ( 0.225 + 0.2025 + 0.1823 + 0.164 + 0.1476 ) = 1.56714 f(x)= 1.475 + 0.1 \times (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
小结:
GBDT 算法首先初始化弱学习器,然后对每个样本计算负梯度,即残差。接下来,将上一步得到的残差作为样本新的真实值,并将数据作为下棵树的训练数据,得到一颗新的回归树。然后,对叶子区域计算最佳拟合值,并更新强学习器。最后,得到最终学习器。
在 GBDT 算法原理中,公式的解释如下: