-
视频链接
- 数据集下载地址:无需下载
1. 集成学习算法简介
学习目标
:
- 了解什么是集成学习
- 知道机器学习中的两个核心任务
- 了解集成学习中的 Boosting 和 Bagging
1.1 什么是集成学习
集成学习(Ensemble Learning)通过建立几个模型来解决单一预测问题。它的工作原理是生成多个分类器(模型),各自独立地学习和作出预测。这些预测最后结合成组合预测,因此优于任何一个单分类的做出预测。
Ensemble:英 [ɒnˈsɒmbl] 美 [ɑːnˈsɑːmbl]
- n. 合奏,合唱; (经常在一起演出的小型)乐团,剧团,舞剧团; 全体,整体; 成套的东西;
- adv. 一起,同时;
1.2 复习:机器学习的两个核心任务
-
任务一
:如何优化训练数据 -> 主要用于解决
欠拟合
问题 -
任务二
:如何提升泛化性能 -> 主要用于解决
过拟合
问题
1.3 集成学习中的 Boosting 和 Bagging
注意
:
-
这里的
变强
是指模型的拟合能力更加出色(因为一开始模型是欠拟合的) -
这里的
变壮
是指模型变得更加稳健(Robust)(因为一开始模型是过拟合的)
只要单分类器的表现不太差,集成学习的结果总是要好于单分类器的
。
小结
:
-
什么是集成学习【了解】
- 通过建立几个模型来解决单一预测问题
-
机器学习两个核心任务【知道】
-
解决欠拟合问题
-
弱弱组合变强
→\rightarrow
→
Boosting
-
弱弱组合变强
-
解决过拟合问题
-
互相遏制变壮
→\rightarrow
→
Bagging
-
互相遏制变壮
-
解决欠拟合问题
注意
:
- 这里的变强是指模型的拟合能力更加出色(因为一开始模型是欠拟合的)
- 这里的变壮是指模型变得更加稳健(Robust)(因为一开始模型是过拟合的)
2. Bagging 和 随机森林
学习目标
:
- 知道 Bagging 集成原理
- 知道 随机森林 构造过程
- 知道什么是 包外估计
- 知道 RandomForestClassifier 的使用
- 了解 Bagging 集成的优点
2.1 Bagging 集成的原理
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
:强学习器和弱学习器并不直接与过拟合和欠拟合相关。
- 强学习器指的是一个能够在训练数据上达到较高准确率的模型
- 弱学习器指的是一个在训练数据上准确率略高于随机猜测的模型
因此,强学习器和弱学习器并不直接与过拟合和欠拟合相关。一个强学习器可能会过拟合,也可能不会;同样,一个弱学习器也可能会欠拟合,也可能不会。
目标
:把下面的圈和方块进行分类。
实现过程
:
一
、采样不同数据集
二
、训练分类器
三
、平权投票,获取最终结果
四
、主要实现过程小结
2.2 随机森林构造过程
在机器学习中,随机森林是一个包含多个决策树的分类器,并且其输出的类别是由个别树输出的类别的众数而定。
随机森林
=
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 方法。随机森林通过对训练数据进行自助采样(bootstrap sampling)来构建多个决策树,并对这些决策树的预测结果进行聚合,从而得到最终的预测结果。
2.3 包外估计(Out-of-Bag Estimate,OOB Estimate)
在随机森林的构造过程中,如果进行有放回的抽样,我们会发现:
总是有一部分样本我们选不到的
。那么就带来一些问题:
-
这部分数据占整体数据的比重有多大呢
? -
这部分数据有什么用呢
?
2.3.1 包外估计的定义
在随机森林的 Bagging 过程中,对于每一棵训练出的决策树
g
t
g_t
g
t
,与数据集
D
D
D
有如下关系:
数据点 |
决策树 g 1 g_1 g 1 |
决策树 g 2 g_2 g 2 |
决策树 g 3 g_3 g 3 |
… |
决策树 g T g_T g T |
---|---|---|---|---|---|
( x 1 , y 1 ) (x_1, y_1) ( x 1 , y 1 ) |
D 1 D_1 D 1 |
* |
D 3 D_3 D 3 |
… |
D T D_T D T |
( x 2 , y 2 ) (x_2, y_2) ( x 2 , y 2 ) |
* | * |
D 3 D_3 D 3 |
… |
D T D_T D T |
( x 3 , y 3 ) (x_3, y_3) ( x 3 , y 3 ) |
* |
D 2 D_2 D 2 |
* | … |
D T D_T D T |
… | … | … | … | … | … |
( x N , y N ) (x_N, y_N) ( x N , y N ) |
D 1 D_1 D 1 |
D 2 D_2 D 2 |
* | … | * |
这张表格展示了随机森林中每一棵决策树
g
t
g_t
g
t
与数据集
D
D
D
的关系。表格中的每一行代表一个数据点
(
x
n
,
y
n
)
(x_n, y_n)
(
x
n
,
y
n
)
,每一列代表一棵决策树
g
t
g_t
g
t
。如果一个数据点
(
x
n
,
y
n
)
(x_n, y_n)
(
x
n
,
y
n
)
被用于训练某棵决策树
g
t
g_t
g
t
,则对应的单元格中会显示该决策树的训练数据集
D
t
D_t
D
t
。
如果一个数据点
(
x
n
,
y
n
)
(x_n, y_n)
(
x
n
,
y
n
)
没有被用于训练某棵决策树
g
t
g_t
g
t
,则对应的单元格中会显示一个星号
*
,表示这个数据点是包外数据(Out-of-bag,OOB)
。
当数据足够多,对于任意一组数据
(
x
n
,
y
n
)
(x_n, y_n)
(
x
n
,
y
n
)
是包外数据的概率为:
(
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
−
N
1
)
N
=
(
N
−
1
N
)
N
1
=
(
1
+
N
−
1
1
)
N
1
≈
e
1
≈
36.8%
由于基分类器是构建在训练样本的自助抽样集上的,
只有约 63.2% 原样本集出现在中,而剩余的 36.8% 的数据作为包外数据,可以用于基分类器的验证集
。
经验证,包外估计是对集成分类器泛化误差的无偏估计。
在随机森林算法中数据集属性的重要性、分类器集强度和分类器间相关性计算都依赖于包外数据。
2.3.2 【拓展】如何理解无偏估计?无偏估计有什么用?
2.3.2.1 如何理解无偏估计
无偏估计就是我们认为所有样本出现的概率一样。
假如有
N
N
N
种样本我们认为所有样本出现概率都是
1
/
N
1/N
1/
N
。然后根据这个来计算数学期望。此时的数学期望就是我们平常讲的平均值。
数学期望本质就是平均值
2.3.2.2 无偏估计为何叫做“无偏”?它要“估计”什么?
首先回答
第一个问题
:它要“估计”什么?
-
它要
估计的是整体的数学期望(平均值)
。
第二个问题
:那为何叫做无偏?有偏是什么?
假设这个是一些样本的集合
X
=
x
1
,
x
2
,
x
3
,
.
.
.
,
x
N
X = {x_1, x_2, x_3, …, x_N}
X
=
x
1
,
x
2
,
x
3
,
…
,
x
N
。我们根据样本估计整体的数学期望(平均值)。
因为正常求期望是加权和,什么叫加权和?
E
(
X
)
=
E
(
P
i
×
x
i
)
E(X)=E(P_i \times x_i)
E
(
X
)
=
E
(
P
i
×
x
i
)
这个就叫加权和。
因为每个样本出现概率不一样,概率大的加起来就大,这就产生偏重了(有偏估计)。但是,我们不知道某个样本出现的概率啊。
比如你从别人口袋里面随机拿了 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
=
55
10 \times 1/2 + 100 \times 1/2 = 55
10
×
1/2
+
100
×
1/2
=
55
。
有偏估计那就是偏重那些出现次数多的样本,认为样本的概率是不一样的。
比如我出现了两次 10 块钱,那么我认为 10 块钱的概率是
2
/
3
2/3
2/3
,100 块钱概率只有
1
/
3
1/3
1/3
。有偏所估计的别人口袋每张钱的数学期望(平均值)=
10
×
2
/
3
+
100
×
1
/
3
≈
40
10 \times 2/3 + 100 \times 1/3 \approx 40
10
×
2/3
+
100
×
1/3
≈
40
。
2.3.3 包外估计的用途
-
当
基学习器是决策树
时,可使用包外样本来辅助剪枝,或用于估计决策树中各结点的后验概率以辅助对零训练样本结点的处理。 -
当
基学习器是神经网络
时,可使用包外样本来辅助早期(Early Stop)停止以减小过拟合。
2.4 随机森林 API 介绍
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. 一起,同时;
注意
:
- 随机森林算法本身就利用了 Bagging 方法。随机森林通过对训练数据进行自助采样(bootstrap sampling)来构建多个决策树,并对这些决策树的预测结果进行聚合,从而得到最终的预测结果。
- 从 随机森林
RandomForestClassifier
的导入就可以看到
from sklearn.ensemble import RandomForestClassifier
,随机森林本身就是 Bagging 的一种实际应用。
-
作用
:
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_))
个特征。 -
如果为 “auto”,则每次拆分时考虑
sqrt(n_features)
个特征。 -
如果为 “sqrt”,则每次拆分时考虑
sqrt(n_features)
个特征。 -
如果为 “log2”,则每次拆分时考虑
log2(n_features)
个特征。 - 如果为 None,则每次拆分时都会考虑所有的特征。
-
如果为整数,则在每次拆分时考虑
-
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
2.5 随机森林预测案例流程
零
:数据读取及其处理
这里仍然使用 Titanic 数据集。
数据集详情见
[学习笔记] [机器学习] 6. [下]决策树算法(熵Entropy、信息增益(率)、基尼值(指数)、CART剪枝、特征工程特征提取、回归决策树)
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 超参数调优
gscv = GridSearchCV(estimator=rf, param_grid=hyper_params, cv=2) # 2-fold
四、模型训练及评估
## 5.1 模型训练
gscv.fit(x_train, y_train)
## 5.2 模型评估
print("随机森林预测的准确率为:", gscv.score(x_test, y_test))
运行结果:
随机森林预测的准确率为: 0.7713414634146342
之前我们仅使用决策树取得的准确率为:0.7560975609756098
注意
:
- 随机森林的建立过程
- 树的深度、树的个数等需要进行超参数调优
2.5 Bagging 集成的特点以及优缺点
Bagging(Bootstrap Aggregating,自助聚合法)是一种集成学习算法,它通过并行地训练一系列各自独立的同类模型,然后再将各个模型的输出结果按照某种策略进行聚合(例如分类中可采用投票策略,回归中可采用平均策略)。Bagging 算法可与其他分类、回归算法结合,提高其准确率、稳定性的同时,通过降低结果的方差,避免过拟合的发生。
B
a
g
g
i
n
g
+
决策树
/
线性回归
/
逻辑回归
/
深度学习
.
.
.
=
B
a
g
g
i
n
g
集成学习方法
\mathrm{Bagging} + 决策树/线性回归/逻辑回归/深度学习… = \mathrm{Bagging} \ 集成学习方法
Bagging
+
决策树
/
线性回归
/
逻辑回归
/
深度学习
…
=
Bagging
集成学习方法
2.5.1 特点
Bagging 算法的特点
:
-
能够
并行地
训练多个模型,因此训练速度快,适合处理大数据 - 由于 Bagging 算法每次都进行有放回地采样来训练模型,因此泛化能力很强,对于降低模型的方差很有作用
2.5.2 优点
Bagging 集成的优点
:
-
并行性好
:Bagging 算法能够并行地训练多个模型,因此训练速度快,适合处理大数据。 -
泛化能力强
:由于 Bagging 算法每次都进行采样来训练模型,因此泛化能力很强,对于降低模型的方差(提高模型的泛化能力)很有作用。- 决策树在原有算法上加上 Bagging 提高约 2% 左右的泛化正确率
-
避免过拟合
:Bagging 算法可与其他分类、回归算法结合,提高其准确率、稳定性的同时,通过降低结果的方差,避免过拟合的发生。
2.5.3 缺点
Bagging 集成的缺点包括:
-
模型解释性差
:由于 Bagging 集成是通过组合多个模型的预测结果来进行预测的,因此它的模型解释性不如单个模型。 -
计算成本高
:虽然 Bagging 算法能够并行地训练多个模型,但是它仍然需要训练多个模型,因此
计算成本较高
。
小结
:
-
Bagging 集成过程【知道】
- 采样:从所有样本里面采样一部分
- 学习:训练弱学习器
- 集成使用平权投票
-
随机森林介绍【知道】
-
随机森林定义:
随机森林=
B
a
g
g
i
n
g
+
决策树
随机森林 = \mathrm{Bagging} + 决策树
随机森林
=
Bagging
+
决策树
-
流程:
-
随机选取
mm
m
条数据 -
随机选取
kk
k
个特征 - 训练决策树
- 重复 1-3
- 对上面的若决策树进行平权投票
-
随机选取
-
注意:
- 随机选取样本,且是有放回的抽取
-
选取特征时随机选出
mm
m
个特征(
m≪
M
m \ll M
m
≪
M
)
-
包外估计:
- 如果进行有放回的对数据集抽样,会发现,总是有一部分样本选不到
- 因此我们可以使用包外估计进行无偏估计
-
API:
-
sklearn.ensemble.RandomForestClassifier()
-
-
随机森林定义:
-
Ba
g
g
i
n
g
+
决策树
/
线性回归
/
逻辑回归
/
深度学习
.
.
.
=
B
a
g
g
i
n
g
集成学习方法
\mathrm{Bagging} + 决策树/线性回归/逻辑回归/深度学习… = \mathrm{Bagging} \ 集成学习方法
Bagging
+
决策树
/
线性回归
/
逻辑回归
/
深度学习
…
=
Bagging
集成学习方法
【了解】 -
Bagging的特点、优点以及缺点【了解】
-
特点:
- 能够并行地训练多个模型,因此训练速度快,适合处理大数据
- 由于 Bagging 算法每次都进行采样来训练模型,因此泛化能力很强,对于降低模型的方差很有作用
-
优点:
-
并行性好
:Bagging 算法能够并行地训练多个模型,因此训练速度快,适合处理大数据。 -
泛化能力强
:由于 Bagging 算法每次都进行采样来训练模型,因此泛化能力很强,对于降低模型的方差很有作用。- 决策树在原有算法上加上 Bagging 提高约 2% 左右的泛化正确率
-
避免过拟合
:Bagging 算法可与其他分类、回归算法结合,提高其准确率、稳定性的同时,通过降低结果的方差,避免过拟合的发生。
-
-
缺点:
-
模型解释性差
:由于 Bagging 集成是通过组合多个模型的预测结果来进行预测的,因此它的模型解释性不如单个模型。 -
计算成本高
:虽然 Bagging 算法能够并行地训练多个模型,但是它仍然需要训练多个模型,因此计算成本较高。
-
-
特点:
3. 随机森林案例 —— Otto Group Product Classification Challenge
3.1 Otto Group Product Classification Challenge 案例介绍
奥托集团是世界上最大的电子商务公司之一,在 20 多个国家设有子公司。该公司每天都在世界各地销售数百万种产品,所以对其产品根据性能合理的分类非常重要。
不过,在实际工作中,工作人员发现许多相同的产品得到了不同的分类。本案例要求你对奥拓集团的产品进行正确的分分类。尽可能的提供分类的准确性。
数据集下载地址:
Otto Group Product Classification Challenge
对于这次比赛,我们提供了一个包含超过 200,000 种产品的 93 个特征的数据集。目标是建立一个预测模型,能够区分我们的主要产品类别。
3.2 数据集介绍
本案例中,数据集包含大约 200,000 种产品的 93 个特征。其目的是建立一个能够区分 otto 公司主要产品类别的预测模型。所有产品共被分成九个类别(例如时装,电子产品等)。
其中:
-
id
:产品ID -
feat_1, feat_2, ..., feat_93
:产品的各个特征 -
target
:产品被划分的类别
3.3 评分标准
本案例中,最后结果使用多分类对数损失进行评估。具体公式为:
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
=
−
N
1
i
=
1
∑
N
j
=
1
∑
M
y
ij
lo
g
(
p
ij
)
其中:
-
ii
i
表示样本 -
jj
j
表示类别 -
pi
j
p_{ij}
p
ij
代表第
ii
i
个样本属于类别
jj
j
的概率 -
如果第
ii
i
个样本真的属于类别
jj
j
,则
yi
j
y_{ij}
y
ij
等于1,否则为0
根据上公式,假如模型将所有的测试样本都正确分类,即所有
p
i
j
p_{ij}
p
ij
都是1,那每个
log
(
p
i
j
)
\log{(p_{ij})}
lo
g
(
p
ij
)
都是0,最终的
l
o
g
l
o
s
s
(
p
i
j
)
\mathrm{log \ loss}{(p_{ij})}
log
loss
(
p
ij
)
也是0。
假如第 1 个样本本来是属于 1 类别的,但模型给它的类别概率
p
i
j
=
0.1
p_{ij} = 0.1
p
ij
=
0.1
,那
l
o
g
l
o
s
s
\mathrm{log \ loss}
log
loss
就会累加上
log
(
0.1
)
\log(0.1)
lo
g
(
0.1
)
这一项。我们知道这一项是负数,而且
p
i
j
p_{ij}
p
ij
越小,负得越多,如果
p
i
j
=
0
p_{ij} = 0
p
ij
=
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
−
1
0
−
15
)
,
1
0
−
15
)
也就是说,最小不会小于
1
0
−
15
10^{-15}
1
0
−
15
。
注意
:在 sklearn 库中,
log_loss
函数本身就对非常小的值进行了处理,因此我们不需要再次处理
。在
log_loss
函数中,我们可以通过设置
eps
参数来控制对非常小值的处理。默认情况下,
eps
的值为
1e-15
,这意味着所有小于
1e-15
的概率都会被设置为
1e-15
,所有大于
1 - 1e-15
的概率都会被设置为
1 - 1e-15
。
我们绘制一个自然对数的图像就知道了:
import numpy as np
import matplotlib.pyplot as plt
# 创建图像
fig, ax = plt.subplots(dpi=300)
# 生成 x 值
x = np.linspace(0.1, 10, 100)
# 计算 y 值
y = np.log(x)
# 绘制图像
ax.plot(x, y)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title('Natural Logarithm Function')
# 隐藏边框
for spine in ['top', 'right', 'bottom', 'left']:
ax.spines[spine].set_visible(False)
# 绘制 x 和 y 轴
ax.axhline(y=0, color='k', linewidth=2)
ax.axvline(x=0, color='k', linewidth=2)
# 设置 x 轴刻度值的间隔
ax.set_xticks(np.arange(int(min(x)), int(max(x))+1, 1))
# 显示图像
plt.show()
3.4 实现过程
3.4.1 流程分析
- 获取数据
-
数据基本处理
- 数据量比较大,尝试是否可以进行数据分割
- 转换目标值表示方式
- 模型训练
- 模型评估
- 模型调优
3.4.2 代码实现
导入库
:
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()
说明使用
上述的方式截取数据是不可行的
,需要使用 随机欠采样 截取得到部分数据集。
我们之前在
[学习笔记] [机器学习] 5. 逻辑回归(逻辑回归、混淆矩阵、分类评估指标、ROC曲线、AUC指标、类别不均衡问题、PR曲线、AP、mAP)
中学过 :
过采样
(Oversampling,OS):增加数量较少那一类样本的数量,使得正负样本比例均衡。
欠采样
(Undersampling,US):减少数量较多那一类样本的数量,使得正负样本比例均衡。因为我们是嫌数据量多,所以使用欠采样(减少数据量)
注意
:使用到欠采样的场景比较少,因为获取数据是不容易的,我们大多数情况下使用的过采样,即增加数据量。
# 随机欠采样获取部分数据集
## 首先需要确定特征值和目标值
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])
【
补充
】
LabelEncoder
是
sklearn.preprocessing
模块中的一个类,它用于将类别变量编码为整数。
LabelEncoder
可以将类别变量转换为从 0 到 n_classes-1 的整数值,其中 n_classes 是类别的数量。
下面是一个简单的示例,它演示了如何使用
LabelEncoder
将类别变量编码为整数:
from sklearn.preprocessing import LabelEncoder
# 创建 LabelEncoder 对象
encoder = LabelEncoder()
# 定义类别变量
categories = ['cat', 'dog', 'bird', 'fish']
# 对类别变量进行编码
encoded_categories = encoder.fit_transform(categories)
# 显示编码结果
print(encoded_categories)
[1 2 0 3]
上面的代码创建了一个
LabelEncoder
对象,并使用
fit_transform
方法对类别变量进行了编码。编码结果是一个整数数组,它表示每个类别对应的整数值。
分割数据:分为训练集和测试集
# 分割数据
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)
在我们提供的代码中,
oob_score=True
表示在训练随机森林模型时使用袋外估计(Out-of-Bag Estimate)来评估模型的泛化能力。
袋外估计是一种用于评估集成学习模型(如随机森林)的方法。在训练随机森林模型时,每个决策树都是使用自助采样(bootstrap sampling)从训练数据中抽取的样本进行训练的。这意味着,对于每个决策树,都有一部分训练数据没有被用于训练,这部分数据称为袋外数据(Out-of-Bag data)。
袋外估计就是使用袋外数据来评估模型的泛化能力
。
当我们在创建
RandomForestClassifier
对象时设置
oob_score=True
时,模型会在训练过程中计算袋外估计。
我们可以通过访问模型的
oob_score_
属性来获取袋外估计的值
。
四、模型评估
y_pred = rfc.predict(x_test)
print("模型预测结果为:", y_pred)
score = rfc.score(x_test, y_test)
print(f"测试集上,模型准确率为:{score * 100:.2f}%")
# 因为在定义随机森林模型时使用了OOB,所以我们可以查看OOB的准确率
print(f"模型袋外估计准确率为:{rfc.oob_score_ * 100:.2f}%")
# 绘图展示模型预测结果
fig = plt.figure(dpi=80)
sns.countplot(x=y_pred)
plt.show()
模型预测结果为: [6 2 5 ... 8 5 2]
测试集上,模型准确率为:78.38%
模型袋外估计准确率为:76.62%
虽然我们进行了模型准确率以及袋外估计 OOB,但案例要求我们使用 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]
上面报错的原因是
:log loss 在使用的时候要求输出用 one-hot 表示。而此时我们的数据为:
y_test, y_pred
"""
(array([3, 0, 8, ..., 7, 1, 3]), array([3, 7, 8, ..., 7, 1, 3]))
"""
这是因为我们直接使用
LabelEncoder
对目标值进行了数值转换(从 0 到 classes – 1),而非 One-hot 形式,所以我们需要将其转换为 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.transform(y_pred.reshape(-1, 1)) # 老规矩,对于目标值只需要transform而非fit_transform
此时我们查看一下测试集数据长什么样子:
y_test
array([[0., 0., 0., ..., 1., 0., 0.],
[0., 0., 1., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 1.],
...,
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 1., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.]])
y_pred
array([[0., 0., 1., ..., 0., 0., 0.],
[0., 0., 1., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
...,
[0., 0., 0., ..., 0., 0., 0.],
[0., 1., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 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.3294
改变预测值的输出模式,让输出结果为百分占比,从而降低 Log Loss 值
# 改变预测值的输出模式,让输出结果为百分占比,从而降低 Log Loss 值
y_pred_proba = rfc.predict_proba(x_test)
y_pred_proba
array([[0.01, 0.21, 0.29, ..., 0.21, 0.01, 0. ],
[0. , 0.17, 0.67, ..., 0.11, 0.04, 0.01],
[0.1 , 0.03, 0.11, ..., 0.07, 0.09, 0.28],
...,
[0.03, 0.22, 0.07, ..., 0.07, 0. , 0. ],
[0.03, 0.4 , 0.37, ..., 0.01, 0. , 0. ],
[0. , 0.14, 0.06, ..., 0. , 0. , 0. ]])
# 再进行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.7410
Q
:
为什么改变预测值的输出模式,让输出结果为百分占比,就可以降低 Log Loss 值
?
A
:这是因为对数损失 Log Loss 是一种用于评估模型预测概率的指标。它衡量的是模型预测概率与真实标签之间的差异。如果模型对所有测试样本的分类都是正确的,那么对数损失为 0。如果模型对某个样本的分类错误,那么对数损失会累加上一个负数,且预测概率越小,负得越多。
在一开始,我们使用了
predict
方法来获取模型的预测结果,然后使用
log_loss
函数计算对数损失。但是,
predict
方法返回的是模型预测的类别,而不是预测概率。这意味着,在计算对数损失时,
所有正确分类的样本都会被视为具有 100% 的预测概率,而所有错误分类的样本都会被视为具有 0% 的预测概率。这会导致对数损失值偏高
。
当我们使用了
predict_proba
方法来获取模型的预测概率,然后使用
log_loss
函数计算对数损失。由于
predict_proba
方法返回的是每个类别的预测概率,因此在计算对数损失时能够更准确地衡量模型预测概率与真实标签之间的差异。这就是为什么改变预测值的输出模式后,对数损失值会降低
。
五、模型调优
我们可以对一些超参数调优:
-
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()
最小的损失为:1.0645148635188089,位置为:4
经过图像展示,最后确定
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()
最小的损失为:0.8365207482350211,位置为:4
最高的准确率为:0.746831797235023,位置为:2
经过图像展示,最后确定
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()
最小的损失为:0.7188655299412146,位置为:0
最高的准确率为:0.7699452764976958,位置为:0
经过图像展示,最后确定
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
相比默认参数的模型,此时模型的性能均有提升。
3.5 生成提交数据
# 读取测试集
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)
4. Boosting介绍
学习目标
:
- 知道 Boosting 集成原理和实现过程
- 知道 Bagging 和 Boosting 集成的区别
- 知道 AdaBoost 集成原理
4.1 什么是 Boosting(提升法)
Boosting(提升法)是一种机器学习算法,它可以用来减小监督式学习中偏差。Boosting 是一种
串行
的工作机制,即个体学习器的训练存在依赖关系,必须一步一步序列化进行。
它通过组合多个弱学习器形成一个强学习器,提高模型的整体预测精度
。Boosting 总是更加关注被错误分类的弱规则。
前面我们学习到的 Bagging(自助聚合算法)是并行的,而 Boosting 是串行的。
模型随着学习的积累从弱到强。
简而言之
:每新加入一个弱学习器,整体能力就会得到提升。
代表算法:
- Adaboost
- GBDT
- XGBoost
- LightGBM
4.2 实现过程
4.2.1 训练第一个学习器
4.2.2 调整数据分布
4.2.3 训练第二个学习器
4.2.4 再次调整数据分布
4.2.5 以此训练学习器,调整数据分布
4.2.6 整体过程实现
Q
:Boosting 算法会在每次迭代调整样本权重,这个权重是什么?如何实现调整的?
A
:Boosting 算法在每次迭代时调整的样本权重是指训练样本的权重。这些权重决定了每个训练样本在训练过程中的重要性。权重越大的样本,在训练过程中会受到更多的关注。
Boosting 算法通过调整样本权重来实现对训练过程的控制。具体来说,如果某个样本被错误分类,那么它的权重会增加;如果某个样本被正确分类,那么它的权重会减少。这样,在下一次迭代时,新的弱学习器会更关注那些之前被错误分类的样本。
不同的 Boosting 算法具有不同的权重调整方法。例如,在 AdaBoost 算法中,每次迭代都会根据上一轮弱学习器的预测错误率来调整样本权重。具体来说,如果某个样本被错误分类,那么它的权重会乘以一个大于 1 的因子;如果某个样本被正确分类,那么它的权重会除以一个大于 1 的因子。
Q
:意思是说,Boosting 算法在运行的过程中会生成一个矩阵,这个矩阵记录了每一个样本的权重,对吗?
A
:是的,这样的理解是正确的。
Boosting 算法在运行过程中会维护一个权重向量,它记录了每个训练样本的权重
。在每次迭代时,算法都会根据上一轮弱学习器的预测结果来更新这个权重向量。
例如,在 AdaBoost 算法中,每次迭代都会根据上一轮弱学习器的预测错误率来调整样本权重。具体来说:
- 如果某个样本被错误分类,那么它的权重会乘以一个大于 1 的因子;
- 如果某个样本被正确分类,那么它的权重会除以一个大于 1 的因子。
4.3 Bagging 和 Boosting 集成的区别
Boosting 和 Bagging 都是机器学习模型融合中的两种策略。它们都可以将弱分类器融合之后形成一个强分类器,而且融合之后的效果会比最好的弱分类器更好。但是它们之间也有一些区别:
-
区别一
:
数据方面
- Bagging:对数据进行采样训练(有放回的随机抽样)
- Boosting:根据前一轮学习结果调整数据的重要性(每一轮训练集不变,只是训练集中的每个样例在分类器中的权重发生变化,而权重根据上一轮分类结果进行调整)
-
区别二
:
投票方面
- Bagging:所有学习器平权投票(每个样例的权重相等)
-
Boosting:对学习器进行加权投票(
根据错误率不断调整样例的权值,错误率越大则权重越大
)
-
区别三
:
学习顺序
- Bagging 的学习是并行的,每个学习器没有依赖关系(可以并行生成各个基模型)
- Boosting 学习是串行,学习有先后顺序(理论上只能顺序生产,因为每一个模型都需要上一个模型的结果)
-
区别四
:
主要作用
-
Bagging 主要用于提高泛化性能(
解决过拟合
,也可以说降低方差 Variance) -
Boosting 主要用于提高训练精度(
解决欠拟合
,也可以说降低偏差 Bias)
-
Bagging 主要用于提高泛化性能(
4.4 AdaBoost(Adaptive Boosting,自适应提升法)
AdaBoost 的英文全拼是 Adaptive Boosting,中文名称是自适应提升法。它是一种 Boosting 算法,旨在
通过结合多个弱学习器来构建一个强学习器
。AdaBoost 算法通过迭代地训练一系列的弱学习器,每次迭代都会调整样本的权重,以便更好地拟合之前被错误分类的样本。
与其他 Boosting 算法不同的是,AdaBoost 算法会根据每个弱学习器的性能自适应地调整它们在最终预测结果中的权重
。
4.4.1 构造过程细节
步骤一
:初始化训练数据权重相等,训练第一个学习器。
假设每个训练样本在基分类器的学习中作用相同。这一假设可以保证第一步能够在原始数据上学习基本分类器
H1
(
x
)
H_1(x)
H
1
(
x
)
步骤二
:AdaBoost 反复学习基本分类器,在每一轮
m
=
1
,
2
,
.
.
.
,
M
m = 1,2,…, M
m
=
1
,
2
,
…
,
M
顺次的执行下列操作:
-
在权值分布为
Dt
D_t
D
t
的训练数据上,确定基分类器 -
计算该学习器在训练数据中的错误率:
ϵt
=
P
(
h
t
(
x
t
)
≠
y
t
)
\epsilon_t= P(h_t(x_t) \ne y_t)
ϵ
t
=
P
(
h
t
(
x
t
)
=
y
t
)
-
计算该学习器的投票权重:
αt
=
1
2
ln
(
1
−
ϵ
t
ϵ
t
)
\alpha_t = \frac{1}{2}\ln{(\frac{1-\epsilon_t}{\epsilon_t})}
α
t
=
2
1
ln
(
ϵ
t
1
−
ϵ
t
)
-
根据投票权重,对训练数据重新赋权:
Dt
+
1
(
x
)
=
D
t
(
x
)
Z
t
×
{
e
−
α
t
,
预测值
=
真实值
e
α
t
,
预测值
≠
真实值
D_{t+1}(x) = \frac{D_t(x)}{Z_t} \times \begin{cases} e^{-\alpha_t}, & 预测值=真实值\\ e^{\alpha_t}, & 预测值 \ne 真实值 \end{cases}
D
t
+
1
(
x
)
=
Z
t
D
t
(
x
)
×
{
e
−
α
t
,
e
α
t
,
预测值
=
真实值
预测值
=
真实值
将下一轮学习器的注意力集中在错误数据上
-
重复执行①到④步,
mm
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
∑
=
1
m
α
i
h
i
(
x
))
4.4.2 关键点剖析
- 如何确认投票权重?
- 如何调整数据分布?
4.4.3案例
给定下面这张训练数据表所示的数据,假设弱分类器由
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})
D
1
=
(
w
11
,
w
12
,
…
,
w
110
)
w
1
i
=
0.1
,
i
=
1
,
2
,
.
.
.
,
10
w_{1i} = 0.1, i=1,2,…,10
w
1
i
=
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
的时候:-
在权值分布为
D1
D_1
D
1
的训练数据上,阈值
vv
v
取 2.5 时分类误差率最低,故基本分类器为:
h1
(
x
)
=
{
1
,
x
<
2.5
−
1
,
x
>
2.5
h_1(x)=\begin{cases} 1, & x < 2.5 \\ -1, & x > 2.5 \end{cases}
h
1
(
x
)
=
{
1
,
−
1
,
x
<
2.5
x
>
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
(
h
1
(
x
1
)
=
y
1
)
=
0.3
-
计算该学习器的投票权重:
α1
=
1
2
ln
1
−
ϵ
1
ϵ
1
=
0.4236
\alpha_1 = \frac{1}{2}\ln{\frac{1-\epsilon_1}{\epsilon_1}} = 0.4236
α
1
=
2
1
ln
ϵ
1
1
−
ϵ
1
=
0.4236
-
根据投票权重,对训练数据重新赋权:
D2
=
(
w
21
,
w
22
,
.
.
.
,
w
210
)
D_2 = (w_{21}, w_{22}, …, w_{210})
D
2
=
(
w
21
,
w
22
,
…
,
w
210
)
经计算得,
D2
D_2
D
2
的值为:
D2
=
(
0.07143
,
0.07143
,
0.07143
,
0.07143
,
0.07143
,
0.07143
,
0.16667
,
0.16667
,
0.16667
,
0.07143
)
D_2 = (0.07143,0.07143,0.07143,0.07143,0.07143,0.07143,0.16667,0.16667,0.16667,0.07143)
D
2
=
(
0.07143
,
0.07143
,
0.07143
,
0.07143
,
0.07143
,
0.07143
,
0.16667
,
0.16667
,
0.16667
,
0.07143
)
H1
(
x
)
=
s
i
g
n
[
0.4236
h
1
(
x
)
]
H_1(x) = \mathrm{sign}[0.4236h_1(x)]
H
1
(
x
)
=
sign
[
0.4236
h
1
(
x
)]
分类器
H1
(
x
)
H_1(x)
H
1
(
x
)
在训练数据集上有 3 个误分类点。
-
在权值分布为
-
当
m=
2
m=2
m
=
2
的时候:-
在权值分布为
D2
D_2
D
2
的训练数据上,阈值
vv
v
取 8.5 时分类误差率最低,故基本分类器为:
h2
(
x
)
=
{
1
,
x
<
8.5
−
1
,
x
>
8.5
h_2(x)=\begin{cases} 1, & x < 8.5 \\ -1, & x > 8.5 \end{cases}
h
2
(
x
)
=
{
1
,
−
1
,
x
<
8.5
x
>
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
(
h
2
(
x
2
)
=
y
2
)
=
0.2143
-
计算该学习器的投票权重:
α2
=
1
2
ln
1
−
ϵ
2
ϵ
2
=
0.6496
\alpha_2 = \frac{1}{2}\ln{\frac{1-\epsilon_2}{\epsilon_2}} = 0.6496
α
2
=
2
1
ln
ϵ
2
1
−
ϵ
2
=
0.6496
-
根据投票权重,对训练数据重新赋权:经计算得,
D3
D_3
D
3
的值为:
D3
=
(
0.0455
,
0.0455
,
0.0455
,
0.1667
,
0.1667
,
0.1667
,
0.1060
,
0.1060
,
0.1060
,
0.0455
)
D_3 = (0.0455,0.0455,0.0455,0.1667,0.1667,0.1667,0.1060,0.1060,0.1060,0.0455)
D
3
=
(
0.0455
,
0.0455
,
0.0455
,
0.1667
,
0.1667
,
0.1667
,
0.1060
,
0.1060
,
0.1060
,
0.0455
)
H2
(
x
)
=
s
i
g
n
[
0.4236
h
1
(
x
)
+
0.6496
h
2
(
x
)
]
H_2(x) = \mathrm{sign}[0.4236h_1(x) + 0.6496h_2(x)]
H
2
(
x
)
=
sign
[
0.4236
h
1
(
x
)
+
0.6496
h
2
(
x
)]
分类器
H2
(
x
)
H_2(x)
H
2
(
x
)
在训练数据集上有 3 个误分类点。
-
在权值分布为
-
当
m=
3
m=3
m
=
3
的时候:-
在权值分布为
D3
D_3
D
3
的训练数据上,阈值
vv
v
取 5.5 时分类误差率最低,故基本分类器为:
h2
(
x
)
=
{
1
,
x
<
5.5
−
1
,
x
>
5.5
h_2(x)=\begin{cases} 1, & x < 5.5 \\ -1, & x > 5.5 \end{cases}
h
2
(
x
)
=
{
1
,
−
1
,
x
<
5.5
x
>
5.5
3,4,5 被分错
-
计算该学习器在训练数据中的错误率:
ϵ3
=
P
(
h
3
(
x
3
)
≠
y
3
)
=
0.1820
\epsilon_3 = P(h_3(x_3) \ne y_3) = 0.1820
ϵ
3
=
P
(
h
3
(
x
3
)
=
y
3
)
=
0.1820
-
计算该学习器的投票权重:
α3
=
1
2
ln
1
−
ϵ
3
ϵ
3
=
0.7514
\alpha_3 = \frac{1}{2}\ln{\frac{1-\epsilon_3}{\epsilon_3}} = 0.7514
α
3
=
2
1
ln
ϵ
3
1
−
ϵ
3
=
0.7514
-
根据投票权重,对训练数据重新赋权:经计算得,
D4
D_4
D
4
的值为:
D4
=
(
0.125
,
0.125
,
0.125
,
0.102
,
0.102
,
0.102
,
0.065
,
0.065
,
0.065
,
0.125
)
D_4 = (0.125,0.125,0.125,0.102,0.102,0.102,0.065,0.065,0.065,0.125)
D
4
=
(
0.125
,
0.125
,
0.125
,
0.102
,
0.102
,
0.102
,
0.065
,
0.065
,
0.065
,
0.125
)
H3
(
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)]
H
3
(
x
)
=
sign
[
0.4236
h
1
(
x
)
+
0.6496
h
2
(
x
)
+
0.7514
h
3
(
x
)]
分类器
H3
(
x
)
H_3(x)
H
3
(
x
)
在训练数据集上有 0 个误分类点。
-
在权值分布为
步骤三
:对
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)]
H
3
(
x
)
=
sign
[
0.4236
h
1
(
x
)
+
0.6496
h
2
(
x
)
+
0.7514
h
3
(
x
)]
4.4.4 API介绍
from sklearn.ensemble import AdaBoostClassifier
API 链接:
sklearn.ensemble.AdaBoostClassifier
小结
:
-
什么是 Boosting【知道】
- 随着学习的积累从弱到强
- 代表算法:Adaboost、GBDT、XGBoost、LightGBM
-
Bagging 和 Boosting 的区别【知道】
-
区别一:数据方面
- Bagging:对数据进行采样训练
- Boosting:根据前一轮学习结果调整数据的重要性
-
区别二:投票方面
- Bagging:所有学习器平权投票
- Boosting:对学习器进行加权投票
-
区别三:学习顺序
- Bagging的学习是并行的,每个学习器没有依赖关系
- Boosting学习是串行,学习有先后顺序。
-
区别四:主要作用
- Bagging主要用于提高泛化性能(解决过拟合,也可以说降低方差)
- Boosting主要用于提高训练精度(解决欠拟合,也可以说降低偏差)
-
区别一:数据方面
-
AdaBoost构造过程【知道】
- 步骤一:初始化训练数据权重相等,训练第一个学习器
- 步骤二:AdaBoost反复学习基本分类器
-
步骤三:对
mm
m
个学习器进行加权投票
5. GBDT(Gradient Boosting Decision Tree,梯度提升树)
学习目标:
- 知道 GBDT 的算法原理
在传统机器学习算法中,GBDT (Gradient Boosting Decision Tree,梯度提升树)算的上是 TOP-3 的算法。想要理解 GBDT 的真正意义,那就必须理解 GBDT 中的
Gradient Boosting
和
Decision Tree
分别是什么。
5.1 Decision Tree:CART 回归树
首先,GBDT 使用的决策树是 CART 回归树,
无论是处理回归问题还是二分类以及多分类,GBDT 使用的决策树通通都是都是 CART 回归树
。
Q
:为什么不用 CART 分类树呢?
A
:因为 GBDT 每次迭代要拟合的是梯度值,是连续值所以要用回归树。
对于回归树算法来说最重要的是寻找最佳的划分点,那么回归树中的可划分点包含了所有特征的所有可取的值。
在分类树中最佳划分点的判别标准是熵(Entropy)或者基尼系数(Gini Coefficient),都是用纯度来衡量的,但是在回归树中的样本标签是连续数值,所以再使用熵(Entropy)之类的指标不再合适,
取而代之的是平方误差(MSE),它能很好的评判拟合程度
。
5.1.1 回归树生成算法(复习)
-
输入:训练数据集
DD
D
-
输出:回归树
f(
x
)
f(x)
f
(
x
)
-
在训练数据集所在的输入空间中,递归的将每个区域划分为两个子区域并决定每个子区域上的输出值,构建二叉决策树:
-
选择最优切分特征
jj
j
与切分点
ss
s
,求解
min
j
,
s
[
min
c
1
∑
x
i
∈
R
1
(
j
,
s
)
(
y
i
−
c
1
)
2
+
min
c
2
∑
x
i
∈
R
2
(
j
,
s
)
(
y
i
−
c
2
)
2
]
\min_{j, s}\left[ \min_{c_1} \sum_{x_i \in R_1(j, s)}(y_i – c_1)^2 + \min_{c_2} \sum_{x_i \in R_2(j, s)}(y_i – c_2)^2 \right]
j
,
s
min
c
1
min
x
i
∈
R
1
(
j
,
s
)
∑
(
y
i
−
c
1
)
2
+
c
2
min
x
i
∈
R
2
(
j
,
s
)
∑
(
y
i
−
c
2
)
2
遍历特征
jj
j
,对固定的切分特征
jj
j
扫描切分点
ss
s
,选择使得上式达到最小值的对
(j
,
s
)
(j,s)
(
j
,
s
)
-
用选定的对
(j
,
s
)
(j,s)
(
j
,
s
)
划分区域并决定相应的输出值:
R1
(
j
,
s
)
=
x
∣
x
(
j
)
≤
s
,
R
2
(
j
,
s
)
=
x
∣
x
(
j
)
>
s
R_1(j, s) = x|x^{(j)} \le s, R_2(j, s) = x|x^{(j)} > s
R
1
(
j
,
s
)
=
x
∣
x
(
j
)
≤
s
,
R
2
(
j
,
s
)
=
x
∣
x
(
j
)
>
s
c^
m
=
1
N
∑
x
1
∈
R
m
(
j
,
s
)
y
i
,
x
∈
R
m
,
m
=
1
,
2
\hat{c}_m = \frac{1}{N}\sum_{x_1\in R_m(j, s)}y_i, x\in R_m, m=1, 2
c
^
m
=
N
1
x
1
∈
R
m
(
j
,
s
)
∑
y
i
,
x
∈
R
m
,
m
=
1
,
2
- 继续对两个子区域调用步骤①和②,直至满足停止条件。
-
将输入空间划分为
MM
M
个区域
R1
,
R
2
,
.
.
.
,
R
M
R_1, R_2,…,R_M
R
1
,
R
2
,
…
,
R
M
,生成决策树:
f(
x
)
=
∑
m
=
1
M
c
^
m
I
(
x
∈
R
m
)
f(x) = \sum_{m=1}^M\hat{c}_m I (x\in R_m)
f
(
x
)
=
m
=
1
∑
M
c
^
m
I
(
x
∈
R
m
)
-
选择最优切分特征
5.1.2 Gradient Boosting:拟合负梯度
梯度提升树(Gradient Boosting)是提升树(Boosting Tree)的一种改进算法,所以在讲梯度提升树之前先来说一下提升树。
先来个通俗理解:假如有个人 30 岁,我们首先用 20 岁去拟合,发现损失有 10 岁,这时我们用 6 岁去拟合剩下的损失,发现差距还有 4 岁,第三轮我们用 3 岁拟合剩下的差距,差距就只有 1 岁了。如果我们的迭代轮数还没有完,可以继续迭代下面,每一轮迭代,拟合的岁数误差都会减小。最后将每次拟合的岁数加起来便是模型输出的结果。
提升树算法
:
-
初始化
f0
(
x
)
=
0
f_0(x)=0
f
0
(
x
)
=
0
-
对
m=
1
,
2
,
.
.
.
,
M
m=1,2,…, M
m
=
1
,
2
,
…
,
M
-
计算残差
rm
i
=
y
i
−
f
m
−
1
(
x
)
,
i
=
1
,
2
,
.
.
.
,
N
r_{mi}= y_i – f_{m – 1}(x), \quad i = 1,2,…,N
r
mi
=
y
i
−
f
m
−
1
(
x
)
,
i
=
1
,
2
,
…
,
N
-
拟合残差
rm
i
r_{mi}
r
mi
学习一个回归树,得到
hm
(
x
)
h_m(x)
h
m
(
x
)
-
更新
fm
(
x
)
=
f
m
−
1
+
h
m
(
x
)
f_m(x) = f_{m-1} + h_m(x)
f
m
(
x
)
=
f
m
−
1
+
h
m
(
x
)
-
计算残差
-
得到回归问题提升树
fM
(
x
)
=
∑
m
=
1
M
h
m
(
x
)
f_M(x) = \sum_{m=1}^Mh_m(x)
f
M
(
x
)
=
∑
m
=
1
M
h
m
(
x
)
Q
:上面伪代码中的残差是什么?
A
:在提升树算法中:
-
假设我们前一轮迭代得到的强学习器是
ft
−
1
(
x
)
f_{t-1}(x)
f
t
−
1
(
x
)
-
损失函数是
L(
y
,
f
t
−
1
(
x
)
)
L(y,f_{t-1}(x))
L
(
y
,
f
t
−
1
(
x
))
-
我们本轮迭代的目标是找到一个弱学习器
ht
(
x
)
h_t(x)
h
t
(
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
,
f
t
(
x
))
=
L
(
y
,
f
t
—1
(
x
)
+
h
t
(
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
,
f
t
−
1
(
x
)
+
h
t
(
x
))
=
(
y
−
f
t
−
1
(
x
)
−
h
t
(
x
)
)
2
=
(
r
−
h
t
(
x
)
)
2
-
这里,
r=
y
−
f
t
−
1
(
x
)
r = y – f_{t-1}(x)
r
=
y
−
f
t
−
1
(
x
)
是当前模型拟合数据的残差(residual) 。 -
所以,对于提升树来说只需要简单地拟合当前模型的残差。
回到我们上面讲的那个通俗易懂的例子中,第一次迭代的残差是 10 岁,第二次残差 4 岁…
当损失函数是平方损失(MSE)和指数损失(Log Loss)函数时,梯度提升树每一步优化是很简单的,但是对于一般损失函数而言,往往每一步优化起来不那么容易。
针对这一问题,Friedman 提出了梯度提升树算法(GBDT),这是利用最速下降的近似方法,
其关键是利用损失函数的负梯度作为提升树算法中的残差的近似值
。
那么负梯度长什么样呢?
-
第
tt
t
轮的第
ii
i
个样本的损失函数的负梯度为:
−
[
∂
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
(
x
i
)
∂
L
(
y
,
f
(
x
i
))
]
f
(
x
)
=
f
t
−
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
(
x
i
))
=
2
1
(
y
−
f
(
x
i
)
)
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
(
x
i
)
∂
L
(
y
,
f
(
x
i
))
]
f
(
x
)
=
f
t
−
1
(
x
)
=
y
−
f
(
x
i
)
此时我们发现
GBDT 的负梯度就是残差,所以说对于回归问题,我们要拟合的就是残差
。
那么对于分类问题呢?
- 二分类和多分类的损失函数都是 Log Loss
本文以回归问题为例进行讲解。
5.3 GBDT算法原理
上面两节分别将 Gradient Boosting(GB)和 Decision Tree(DT)介绍完了,下面将这两部分组合在一起就是我们的 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)
f
0
(
x
)
=
argmin
c
i
=
1
∑
N
L
(
y
i
,
c
)
步骤二
:对
m
=
1
,
2
,
.
.
.
,
M
m = 1, 2, …, M
m
=
1
,
2
,
…
,
M
有:
-
对每个样本
i=
1
,
2
,
.
.
.
,
N
i = 1, 2, …, N
i
=
1
,
2
,
…
,
N
,计算负梯度,即残差:
ri
m
=
−
[
∂
L
(
y
,
f
(
x
i
)
)
∂
f
(
x
i
)
]
f
(
x
)
=
f
m
−
1
(
x
)
r_{im} = -\left[ \frac{\partial L(y, f(x_i))}{\partial f(x_i)} \right]_{f(x)=f_{m-1}(x)}
r
im
=
−
[
∂
f
(
x
i
)
∂
L
(
y
,
f
(
x
i
))
]
f
(
x
)
=
f
m
−
1
(
x
)
-
将上步得到的残差作为样本新的真实值,并将数据
(x
i
,
r
i
m
)
,
i
=
1
,
2
,
.
.
.
,
N
(x_i,r_{im}), i = 1,2,…, N
(
x
i
,
r
im
)
,
i
=
1
,
2
,
…
,
N
作为下棵树的训练数据,得到一颗新的回归树
fm
(
x
)
f_m(x)
f
m
(
x
)
其对应的叶子节点区域为
Rj
m
,
j
=
1
,
2
,
.
.
.
,
J
R_{jm, j} = 1,2,…,J
R
jm
,
j
=
1
,
2
,
…
,
J
。其中
JJ
J
为回归树
tt
t
的叶子节点的个数。 -
对叶子区域
j=
1
,
2
,
.
.
J
j=1,2,..J
j
=
1
,
2
,
..
J
计算最佳拟合值
γj
m
=
a
r
g
m
i
n
γ
∑
x
i
∈
R
j
m
L
(
y
i
,
f
m
−
1
(
x
i
)
+
γ
)
\gamma_{jm} = \underset{\gamma}{\mathrm{argmin}}\sum_{x_i \in R_{jm}} L(y_i, f_{m-1}(x_i) + \gamma)
γ
jm
=
γ
argmin
x
i
∈
R
jm
∑
L
(
y
i
,
f
m
−
1
(
x
i
)
+
γ
)
-
更新强学习器
fm
(
x
)
=
f
m
−
1
(
x
)
+
∑
i
=
1
J
γ
j
m
I
(
x
∈
R
j
m
)
f_m(x) = f_{m-1}(x) + \sum_{i=1}^J \gamma_{jm}I(x\in R_{jm})
f
m
(
x
)
=
f
m
−
1
(
x
)
+
i
=
1
∑
J
γ
jm
I
(
x
∈
R
jm
)
步骤三
:得到最终学习器
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
)
=
f
M
(
x
)
=
f
0
(
x
)
+
m
=
1
∑
M
j
=
1
∑
J
γ
jm
I
(
x
∈
R
jm
)
5.4 实例介绍
5.4.1 数据介绍
根据如下数据,预测最后一个样本的身高。
编号 | 年龄(岁) | 体重(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 | ? |
5.4.2 模型训练
5.4.2.1 设置参数
-
学习率:
learning_rate=0.1
-
迭代次数:
n_trees=5
-
树的深度:
max_depth=3
5.4.2.2 开始训练
步骤一
:初始化弱学习器
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)
f
0
(
x
)
=
argmin
c
i
=
1
∑
N
L
(
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
∂
(
2
1
(
y
i
−
c
)
2
)
=
i
=
1
∑
N
c
−
y
i
令导数等于 0:
∑
i
=
1
N
c
−
y
i
=
0
\sum_{i=1}^N c-y_i = 0
i
=
1
∑
N
c
−
y
i
=
0
c
=
∑
i
=
1
N
y
i
N
c = \frac{\sum^N_{i = 1}y_i}{N}
c
=
N
∑
i
=
1
N
y
i
所以初始化时,
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)
f
0
(
x
)
:
f
0
(
x
)
=
c
=
1.475
f_0(x) = c = 1.475
f
0
(
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}
f
m
−
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)}
r
i
1
=
−
[
∂
f
(
x
i
)
∂
L
(
y
,
f
(
x
i
))
]
f
(
x
)
=
f
0
(
x
)
残差在下表列出:
编号 | 真实值 |
f 0 ( x ) f_0(x) f 0 ( 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)
f
1
(
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}
S
E
l
为左节点平方损失,
S
E
r
\mathrm{SE_r}
S
E
r
为右节点平方损失,找到使平方损失和
S
E
s
u
m
=
S
E
l
+
S
E
r
\mathrm{SE_{sum} = SE_l + SE_r}
S
E
sum
=
S
E
l
+
S
E
r
最小的那个划分节点,即为最佳划分节点。
例如
︰以年龄 21 为划分节点,将小于 21 的样本划分为到左节点,大于等于 21 的样本划分为右节点。左节点包括
x
0
,
x
1
x_0, x_1
x
0
,
x
1
,右节点包括样本
x
2
,
x
3
x_2, x_3
x
2
,
x
3
,
S
E
l
=
0.02
,
S
E
r
=
0.005
,
S
E
s
u
m
=
0.025
\mathrm{SE_l} = 0.02, \quad \mathrm{SE_r} = 0.005, \quad \mathrm{SE_{sum}} = 0.025
S
E
l
=
0.02
,
S
E
r
=
0.005
,
S
E
sum
=
0.025
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
\begin{aligned} \mathrm{SE_l} & = [-0.375 – (-0.275)]^2 + [-0.175 – (-0.275)]^2 = 0.02\\ \mathrm{SE_r} & = [0.225 – 0.275]^2 + [0.325 – 0.275]^2 = 0.005 \end{aligned}
S
E
l
S
E
r
=
[
−
0.375
−
(
−
0.275
)
]
2
+
[
−
0.175
−
(
−
0.275
)
]
2
=
0.02
=
[
0.225
−
0.275
]
2
+
[
0.325
−
0.275
]
2
=
0.005
所有可能划分情况如下表所示:
划分点 | 小于划分点的样本 | 大于等于划分点的样本 |
S E l \mathrm{SE_l} S E l |
S E r \mathrm{SE_r} S E r |
S E s u m \mathrm{SE_{sum}} S E sum |
---|---|---|---|---|---|
年龄 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} S E l |
S E r \mathrm{SE_r} S E r |
S E s u m \mathrm{SE_{sum}} S E sum |
---|---|---|---|---|---|
年龄 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} S E l |
S E r \mathrm{SE_r} S E r |
S E s u m \mathrm{SE_{sum}} S E sum |
---|---|---|---|---|---|
年龄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)
γ
j
1
=
γ
argmin
x
i
∈
R
j
1
∑
L
(
y
i
,
f
0
(
x
i
)
+
γ
)
这里其实和上面初始化学习器是一个道理,平方损失,求导,令导数等于零,化简之后得到每个叶子节点的参数
γ
\gamma
γ
,其实就是标签值的均值。这个地方的标签值不是原始的
y
y
y
,而是本轮要拟合的标残差
y
−
f
0
(
x
)
y – f_0(x)
y
−
f
0
(
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
\begin{aligned} & (x_0 \in R_{11}), \quad \gamma_{11} = -0.375 \\ & (x_1 \in R_{21}), \quad \gamma_{21} = -0.175 \\ & (x_2 \in R_{31}), \quad \gamma_{31} = 0.225 \\ & (x_3 \in R_{41}), \quad \gamma_{41} = 0.325 \\ \end{aligned}
(
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
此时的树长这个样子:
此时可更新强学习器,需要用到参数学习率:
learning_rate=0.1
,用
l
r
lr
l
r
表示。
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})
f
1
(
x
)
=
f
0
(
x
)
+
l
r
∗
j
=
1
∑
4
γ
j
1
I
(
x
∈
R
j
1
)
为什么要用学习率呢?这是 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
)
=
f
5
(
x
)
=
f
0
(
x
)
+
m
=
1
∑
5
j
=
1
∑
4
γ
jm
I
(
x
∈
R
jm
)
步骤四
:预测样本
-
f0
(
x
)
=
1.475
f_0(x) = 1.475
f
0
(
x
)
=
1.475
-
在
f1
(
x
)
f_1(x)
f
1
(
x
)
中,样本 4 的年龄为 25,大于划分节点 21 岁,又小于 30 岁,所以被预测为 0.2250 -
在
f2
(
x
)
f_2(x)
f
2
(
x
)
中,样本 4 的…此处省略…所以被预测为 0.2025 -
在
f3
(
x
)
f_3(x)
f
3
(
x
)
中,样本 4 的…此处省略…所以被预测为 0.1823 -
在
f4
(
x
)
f_4(x)
f
4
(
x
)
中,样本 4 的…此处省略…所以被预测为 0.1640 -
在
f5
(
x
)
f_5(x)
f
5
(
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 \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 算法原理【知道】
-
初始化弱学习器
f0
(
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)
f
0
(
x
)
=
argmin
c
i
=
1
∑
N
L
(
y
,
c
)
-
对
m=
1
,
2
,
.
.
.
,
M
m=1,2,…,M
m
=
1
,
2
,
…
,
M
,有:-
对每个样本
i=
1
,
2
,
.
.
.
,
N
i=1,2,…, N
i
=
1
,
2
,
…
,
N
,计算负梯度,即残差
ri
m
=
−
[
∂
L
(
y
,
f
(
x
i
)
)
∂
f
(
x
i
)
]
f
(
x
)
=
f
m
−
1
(
x
)
r_{im} = -\left[ \frac{\partial L(y, f(x_i))}{\partial f(x_i)} \right]_{f(x)=f_{m-1}(x)}
r
im
=
−
[
∂
f
(
x
i
)
∂
L
(
y
,
f
(
x
i
))
]
f
(
x
)
=
f
m
−
1
(
x
)
-
将上步得到的残差作为样本新的真实值,并将数据
(x
i
,
r
i
m
)
,
i
=
1
,
2
,
.
.
.
,
N
(x_i, r_{im}), i = 1,2,…, N
(
x
i
,
r
im
)
,
i
=
1
,
2
,
…
,
N
作为下棵树的训练数据,得到一颗新的回归树
fm
(
x
)
f_m(x)
f
m
(
x
)
其对应的叶子节点区域为
Rj
m
,
j
=
1
,
2
,
.
.
.
,
J
R_{jm}, j = 1,2,…,J
R
jm
,
j
=
1
,
2
,
…
,
J
。其中
JJ
J
为回归树
tt
t
的叶子节点的个数。 -
对叶子区域
j=
1
,
2
,
.
.
.
,
J
j=1,2,…, J
j
=
1
,
2
,
…
,
J
计算最佳拟合值
γj
m
=
a
r
g
m
i
n
γ
∑
x
i
∈
R
j
m
L
(
y
,
f
m
−
1
(
x
i
)
+
γ
)
\gamma_{jm} = \underset{\gamma}{\mathrm{argmin}}\sum_{x_i \in R_{jm}} L(y, f_{m-1}(x_i) + \gamma)
γ
jm
=
γ
argmin
x
i
∈
R
jm
∑
L
(
y
,
f
m
−
1
(
x
i
)
+
γ
)
-
更新强学习器
fm
(
x
)
=
f
m
−
1
(
x
)
+
l
r
×
∑
i
=
1
J
γ
j
m
I
(
x
∈
R
j
m
)
f_m(x) = f_{m-1}(x) + lr \times \sum_{i=1}^J\gamma_{jm}I(x\in R_{jm})
f
m
(
x
)
=
f
m
−
1
(
x
)
+
l
r
×
i
=
1
∑
J
γ
jm
I
(
x
∈
R
jm
)
-
对每个样本
-
得到最终学习器
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
)
=
f
M
(
x
)
=
f
0
(
x
)
+
m
=
1
∑
M
j
=
1
∑
J
γ
jm
I
(
x
∈
R
jm
)
-
初始化弱学习器
GBDT 算法首先初始化弱学习器,然后对每个样本计算负梯度,即残差。接下来,将上一步得到的残差作为样本新的真实值,并将数据作为下棵树的训练数据,得到一颗新的回归树。然后,对叶子区域计算最佳拟合值,并更新强学习器。最后,得到最终学习器。
在 GBDT 算法原理中,公式的解释如下:
-
f0
(
x
)
f_0(x)
f
0
(
x
)
:初始化弱学习器。 -
ar
g
m
i
n
c
∑
i
=
1
N
L
(
y
,
c
)
\mathrm{argmin}_c\sum_{i=1}^N L(y,c)
argmin
c
∑
i
=
1
N
L
(
y
,
c
)
:求解最小化损失函数
L(
y
,
c
)
L(y,c)
L
(
y
,
c
)
的参数
cc
c
。 -
mm
m
:迭代次数。 -
ri
m
r_{im}
r
im
:第
ii
i
个样本在第
mm
m
次迭代时的残差。 -
∂L
(
y
,
f
(
x
i
)
)
∂
f
(
x
i
)
\frac{\partial L(y, f(x_i))}{\partial f(x_i)}
∂
f
(
x
i
)
∂
L
(
y
,
f
(
x
i
))
:损失函数
L(
y
,
f
(
x
i
)
)
L(y, f(x_i))
L
(
y
,
f
(
x
i
))
对
f(
x
i
)
f(x_i)
f
(
x
i
)
的偏导数。 -
fm
(
x
)
f_m(x)
f
m
(
x
)
:第
mm
m
次迭代后的强学习器。 -
Rj
m
R_{jm}
R
jm
:回归树
fm
(
x
)
f_m(x)
f
m
(
x
)
的第
jj
j
个叶子节点区域。 -
γj
m
\gamma_{jm}
γ
jm
:第
jj
j
个叶子区域的最佳拟合值。 -
lr
lr
l
r
:学习率,用于控制每次迭代对模型的影响程度。 -
∑i
=
1
J
γ
j
m
I
(
x
∈
R
j
m
)
\sum_{i=1}^J\gamma_{jm}I(x\in R_{jm})
∑
i
=
1
J
γ
jm
I
(
x
∈
R
jm
)
:对所有叶子区域求和,其中
I(
x
∈
R
j
m
)
I(x\in R_{jm})
I
(
x
∈
R
jm
)
表示当样本
xx
x
属于叶子区域
Rj
m
R_{jm}
R
jm
时取值为 1,否则为 0。