前言

当做重要决定时,大家可能都会考虑吸取多个专家而不只是一个人的意见。机器学习处理问题时也是如此,这就是元算法(meta—algorithm)背后的思路元算法是对其他算法进行组合的一种方式。接下来我们将集中关注一个称作 AdaBoost(读作[ˈeɪdəˌbust]) 的最流行的元算法。

本文内容学习来源感谢:

  • 《Machine Learning in Action》
  • 《智能之门》
  • 哔哩哔哩,维基百科等网络

前排说明

  • 本文所有代码基于 Python 3.10 版本,理论上你的版本为 3.X 皆可运行。
  • 在代码的注释中看到符号:❓,则表示注释的代码部分函数解释说明可以在0最后的相关函数中查阅。

基于数据集多重抽样的分类器

前面已经介绍了五种不同的分类算法,它们各有优缺点。我们自然可以将不同的分类器组合起来,而这种组合结果则被称为集成方法(ensemble method)或者元算法(meta-algorithm)。使 用集成方法时会有多种形式:可以是不同算法的集成,也可以是同一算法在不同设置下的集成,还可以是数据集不同部分分配给不同分类器之后的集成。

AdaBoost

  • 优点:泛化错误率低,易编码,可以应用在大部分分类器上,无参数调整。
  • 缺点:对离群点敏感
  • 适用数据类型:数值型和标称型数据。

bagging

Bagging(读作[ˈbæɡɪŋ]),它是 Bootstrap Aggregating (引导聚集算法,又称装袋算法)的缩写,是在从原始数据集选择 S 次后得到 S 个新数据集的一种技术。新数据集和原数据集的大小相等。每个数据集都是通过在原始数据集中随机选择一个样本来进行替换而得到的。这里的替换就意味着可以多次地选择同一样本。这一性质就允许新数据集中可以有重复的值,而原始数据集的某些值在新集合中则不再出现。

S个数据集建好之后,将某个学习算法分别作用于每个数据集就得到了S个分类器。当我们要对新数据进行分类时,就可以应用这S个分类器进行分类。与此同时,选择分类器投票结果中最多的类别作为最后的分类结果

Bagging在降低模型的方差和提高泛化能力方面表现出色,特别是在复杂模型和高维数据上。常见的Bagging算法包括随机森林(Random Forests)它是基于决策树的一种集成学习方法

boosting

Boosting提升方法)是一种与bagging很类似的技术。不论是在boosting还是bagging当中,所使用的多个分类器的类型都是一致的。但是在前者当中,不同的分类器是通过串行训练而获得的,每个新分类器都根据已训练出的分类器的性能来进行训练(通过串行训练多个弱学习器1(weak learners),然后将它们组合成一个强学习器(strong learner))。

Boosting 的核心思想是重点关注被前一轮弱学习器分类错误的样本,对它们进行加权,然后在下一轮中训练新的弱学习器来纠正之前的错误。由于boosting分类的结果是基于所有分类器的加权求和结果的,因此 boosting 与 bagging 不太一样。bagging中的分类器权重是相等的,而 boosting 中的分类器权重并不相等,每个权重代表的是其对应分类器在上一轮迭代中的成功度。

boosting 方法拥有多个版本,本章将只关注其中一个最流行的版本 AdaBoost

训练算法:基于错误提升分类器的性能

能否使用弱分类器和多个实例来构建一个强分类器?这是一个非常有趣的理论问题。这里的“弱”意味着分类器的性能比随机猜测要略好,但是也不会好太多。这就是说,在二分类情况下弱分类器的错误率会高于 50%,而“强”分类器的错误率将会低很多。AdaBoost 算法即脱胎于上述理论问题。

AdaBoost 运行过程如下:训练数据中的每个样本,并赋予其一个权重,这些权重构成了向量D。一开始,这些权重都初始化成相等值。首先在训练数据上训练出一个弱分类器并计算该分类器的错误率,然后在同一数据集上再次训练弱分类器。在分类器的第二次训练当中,将会重新调整每个样本的权重,其中第一次分对的样本的权重将会降低,而第一次分错的样本的权重将会提高。为了从所有弱分类器中得到最终的分类结果,AdaBoost 为每个分类器都分配了一个权重值 alpha ,这些 alpha 值是基于每个弱分类器的错误率进行计算的。其中,错误率 ε 的定义为:$\huge\varepsilon=\frac{未正确分类的样本数目}{所有样本数目}$ 。

而 alpha 的计算公式为:$\huge \alpha = \frac{1}{2} \ln{(\frac{1-\varepsilon}{\varepsilon})}$ 。其算法流程如下图所示:

image-20230726224354493

计算出 alpha 值之后,可以对权重向量D进行更新,以使得那些正确分类的样本的权重降低而错分样本的权重升高。D的计算方法如下。

  • 如果某个样本被正确分类,那么该样本的权重更改为:$\huge D_i^{(t+1)}=\frac{D_i^{(t)}e^{- \alpha}}{Sum(D)}$ 。
  • 如果某个样本被错分,那么该样本的权重更改为:$\huge D_i^{(t+1)}=\frac{D_i^{(t)}e^{ \alpha}}{Sum(D)}$ 。

在计算出D之后,AdaBoost又开始进入下一轮迭代。AdaBoost算法会不断地重复训练和调整权重的过程,直到训练错误率为 0 或者弱分类器的数目达到用户的指定值为止。

基于单层决策树构建弱分类器

单层决策树(decision stump,也称决策树桩)是一种简单的决策树。前面我们已经介绍了决策树的工作原理,接下来将构建一个单层决策树,而它仅基于单个特征来做决策。由于这棵树只有一次分裂过程,因此它实际上就是一个树桩。

现在我们创建一个名称为 adaboost.py 的新文件来添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
from numpy import *
# 加载数据
def loadSimpData():
# 创建数据集
datMat = matrix([[ 1. , 2.1],
[ 2. , 1.1],
[ 1.3, 1. ],
[ 1. , 1. ],
[ 2. , 1. ]])
# 数据集对应目标值
classLabels = [1.0, 1.0, -1.0, -1.0, 1.0]
# 返回数据集及其对应目标值
return datMat,classLabels

下图给出了上述数据集的示意图。如果想要试着从某个坐标轴上选择一个值(即选择一条与坐标轴平行的直线2)来将所有的圆形点和方形点分开,这显然是不可能的。这就是单层决策树难以处理的一个著名的问题。通过使用多棵单层决策树,我们就可以构建出一个能够对该数据集完全正确分类的分类器。

image-20230726232433851

有了数据,接下来就可以通过构建多个函数来建立单层决策树。

第一个函数将用于测试是否有某个值小于或者大于我们正在测试的阈值。第二个函数则更加复杂一些,它会在一个加权数据集中循环,并找到具有最低错误率的单层决策树。现在在 adaboost.py 文件中添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# 数据划分
def stumpClassify(dataMatrix, dimen, threshVal, threshIneq):
"""
:param dataMatrix: 一个二维的数据矩阵,其中每行表示一个样本,每列表示一个特征。
:param dimen: 一个整数,表示要使用的特征列的索引。
:param threshVal: 特征列上的阈值,将数据划分成两个类别。
:param threshIneq: 一个字符串,表示阈值比较的方式。如果为'lt'(less than),则将特征值小于等于threshVal的样本分为一类;
:return: 返回列表,其中标记为-1.0的样本属于一类,标记为1.0的样本属于另一类。
"""
# 创建一个结果列表,和数据集同一数量一列,且默认填充 1
retArray = ones((shape(dataMatrix)[0], 1))
# 根据字符串判断比较方式
if threshIneq == 'lt':
# 将数据集根据阈值(决策树最优解)划分类别,写入分类结果列表
retArray[dataMatrix[:, dimen] <= threshVal] = -1.0
else:
retArray[dataMatrix[:, dimen] > threshVal] = -1.0
# 返回分类结果
return retArray

# 构建树桩(弱分类器)
def buildStump(dataArr, classLabels, D):
"""
:param dataArr: 一个二维的数据列表,其中每行表示一个样本,每列表示一个特征。
:param classLabels: 一个包含样本类别标签的列表,用于分类问题。
:param D: 一个包含样本权重的向量,用于加权错误率计算。
:return: 返回分类字典结果,最小错误权重,最小错误的目标值分类结果
"""
# 列表转换为矩阵
dataMatrix = mat(dataArr)
# 目标值列表转换为矩阵并转置
labelMat = mat(classLabels).T
# 获取数据集的样本数目 m 和维度(特征数目) n
m, n = shape(dataMatrix)
# 划分步数
numSteps = 10.0
# 分类结果(字典类型)
bestStump = {}
# 形状为 m 行 1 列的全 0 矩阵
bestClasEst = mat(zeros((m, 1)))
#❓(inf) 最小错误为正无穷大
minError = inf
# 遍历所有特征
for i in range(n):
# 拿到第 i 列的最小值
rangeMin = dataMatrix[:, i].min()
# 拿到第 i 列的最大值
rangeMax = dataMatrix[:, i].max()
# 将一个特征的取值范围划分为若干个离散区间
stepSize = (rangeMax - rangeMin) / numSteps
# 遍历每个分区从最小到最大
for j in range(-1, int(numSteps) + 1):
# 根据阈值分类规则遍历
for inequal in ['lt', 'gt']:
# 计算划分阈值,每次增加/减少(取决于 j 的正负)一个分区间隔
threshVal = (rangeMin + float(j) * stepSize)
# 根据阈值 threshVal 划分数据类别
predictedVals = stumpClassify(dataMatrix, i, threshVal, inequal)
# 创建一个 m 行 1 列的结果列表
errArr = mat(ones((m, 1)))
#❓ 判断根据阈值划分的目标值正确率,正确的将其值赋值为 0
errArr[predictedVals == labelMat] = 0
# 错误权重 = 权重 x 是否错误
weightedError = D.T * errArr
# 输出信息
print(f"划分信息: 当前划分特征 {i}, 划分阈值 {threshVal}, 划分规则: {inequal}, 错误权重 {weightedError}")
# 如果错误权重小于当前记录的最小错误
if weightedError < minError:
# 替换当前错误权重为最小权重
minError = weightedError
# 记录根据阈值分类结果
bestClasEst = predictedVals.copy()
# 记录当前分类特征;dim 是单词 dimension(维度) 的缩写
bestStump['dim'] = i
# 记录弱分类器的划分阈值;thresh 是单词 threshold(阈值)的缩写
bestStump['thresh'] = threshVal
# 记录弱分类器划分规则,是小于阈值还是大于阈值;ineq 是单词 inequality(不等式)的缩写
bestStump['ineq'] = inequal
# 返回分类字典结果,最小错误权重,最小错误的目标值分类结果
return bestStump, minError, bestClasEst

现在我们使用如下代码进行测试:

1
2
3
4
5
6
# 默认权重 0.2
D = mat(ones((5,1))/5)
# 获得数据集及其对应的目标值
datMat,classLabels = loadSimpData()
# 弱分类器获得分类结果
print(buildStump(datMat,classLabels,D))

运行结果如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
划分信息: 当前划分特征 0, 划分阈值 0.9, 划分规则: lt, 错误权重 [[0.4]]
划分信息: 当前划分特征 0, 划分阈值 0.9, 划分规则: gt, 错误权重 [[0.6]]
划分信息: 当前划分特征 0, 划分阈值 1.0, 划分规则: lt, 错误权重 [[0.4]]
划分信息: 当前划分特征 0, 划分阈值 1.0, 划分规则: gt, 错误权重 [[0.6]]
划分信息: 当前划分特征 0, 划分阈值 1.1, 划分规则: lt, 错误权重 [[0.4]]
划分信息: 当前划分特征 0, 划分阈值 1.1, 划分规则: gt, 错误权重 [[0.6]]
划分信息: 当前划分特征 0, 划分阈值 1.2, 划分规则: lt, 错误权重 [[0.4]]
划分信息: 当前划分特征 0, 划分阈值 1.2, 划分规则: gt, 错误权重 [[0.6]]
划分信息: 当前划分特征 0, 划分阈值 1.3, 划分规则: lt, 错误权重 [[0.2]]
划分信息: 当前划分特征 0, 划分阈值 1.3, 划分规则: gt, 错误权重 [[0.8]]
划分信息: 当前划分特征 0, 划分阈值 1.4, 划分规则: lt, 错误权重 [[0.2]]
划分信息: 当前划分特征 0, 划分阈值 1.4, 划分规则: gt, 错误权重 [[0.8]]
划分信息: 当前划分特征 0, 划分阈值 1.5, 划分规则: lt, 错误权重 [[0.2]]
划分信息: 当前划分特征 0, 划分阈值 1.5, 划分规则: gt, 错误权重 [[0.8]]
划分信息: 当前划分特征 0, 划分阈值 1.6, 划分规则: lt, 错误权重 [[0.2]]
划分信息: 当前划分特征 0, 划分阈值 1.6, 划分规则: gt, 错误权重 [[0.8]]
划分信息: 当前划分特征 0, 划分阈值 1.7000000000000002, 划分规则: lt, 错误权重 [[0.2]]
划分信息: 当前划分特征 0, 划分阈值 1.7000000000000002, 划分规则: gt, 错误权重 [[0.8]]
划分信息: 当前划分特征 0, 划分阈值 1.8, 划分规则: lt, 错误权重 [[0.2]]
划分信息: 当前划分特征 0, 划分阈值 1.8, 划分规则: gt, 错误权重 [[0.8]]
划分信息: 当前划分特征 0, 划分阈值 1.9, 划分规则: lt, 错误权重 [[0.2]]
划分信息: 当前划分特征 0, 划分阈值 1.9, 划分规则: gt, 错误权重 [[0.8]]
划分信息: 当前划分特征 0, 划分阈值 2.0, 划分规则: lt, 错误权重 [[0.6]]
划分信息: 当前划分特征 0, 划分阈值 2.0, 划分规则: gt, 错误权重 [[0.4]]
划分信息: 当前划分特征 1, 划分阈值 0.89, 划分规则: lt, 错误权重 [[0.4]]
划分信息: 当前划分特征 1, 划分阈值 0.89, 划分规则: gt, 错误权重 [[0.6]]
划分信息: 当前划分特征 1, 划分阈值 1.0, 划分规则: lt, 错误权重 [[0.2]]
划分信息: 当前划分特征 1, 划分阈值 1.0, 划分规则: gt, 错误权重 [[0.8]]
划分信息: 当前划分特征 1, 划分阈值 1.11, 划分规则: lt, 错误权重 [[0.4]]
划分信息: 当前划分特征 1, 划分阈值 1.11, 划分规则: gt, 错误权重 [[0.6]]
划分信息: 当前划分特征 1, 划分阈值 1.22, 划分规则: lt, 错误权重 [[0.4]]
划分信息: 当前划分特征 1, 划分阈值 1.22, 划分规则: gt, 错误权重 [[0.6]]
划分信息: 当前划分特征 1, 划分阈值 1.33, 划分规则: lt, 错误权重 [[0.4]]
划分信息: 当前划分特征 1, 划分阈值 1.33, 划分规则: gt, 错误权重 [[0.6]]
划分信息: 当前划分特征 1, 划分阈值 1.44, 划分规则: lt, 错误权重 [[0.4]]
划分信息: 当前划分特征 1, 划分阈值 1.44, 划分规则: gt, 错误权重 [[0.6]]
划分信息: 当前划分特征 1, 划分阈值 1.55, 划分规则: lt, 错误权重 [[0.4]]
划分信息: 当前划分特征 1, 划分阈值 1.55, 划分规则: gt, 错误权重 [[0.6]]
划分信息: 当前划分特征 1, 划分阈值 1.6600000000000001, 划分规则: lt, 错误权重 [[0.4]]
划分信息: 当前划分特征 1, 划分阈值 1.6600000000000001, 划分规则: gt, 错误权重 [[0.6]]
划分信息: 当前划分特征 1, 划分阈值 1.77, 划分规则: lt, 错误权重 [[0.4]]
划分信息: 当前划分特征 1, 划分阈值 1.77, 划分规则: gt, 错误权重 [[0.6]]
划分信息: 当前划分特征 1, 划分阈值 1.8800000000000001, 划分规则: lt, 错误权重 [[0.4]]
划分信息: 当前划分特征 1, 划分阈值 1.8800000000000001, 划分规则: gt, 错误权重 [[0.6]]
划分信息: 当前划分特征 1, 划分阈值 1.9900000000000002, 划分规则: lt, 错误权重 [[0.4]]
划分信息: 当前划分特征 1, 划分阈值 1.9900000000000002, 划分规则: gt, 错误权重 [[0.6]]
划分信息: 当前划分特征 1, 划分阈值 2.1, 划分规则: lt, 错误权重 [[0.6]]
划分信息: 当前划分特征 1, 划分阈值 2.1, 划分规则: gt, 错误权重 [[0.4]]
({'dim': 0, 'thresh': 1.3, 'ineq': 'lt'}, matrix([[0.2]]), array([[-1.],
[ 1.],
[-1.],
[-1.],
[ 1.]]))

上述单层决策树的生成函数是决策树的一个简化版本。它就是所谓的弱学习器,即弱分类算法。到现在为止,我们已经构建了单层决策树,并生成了程序,做好了过渡到完整 AdaBoost 算法的准备。

完整 AdaBoost 算法的实现

现在我们拥有了实现一个完整AdaBoost算法所需要的所有信息。AdaBoost 算法实现的伪代码如下所示:

1
2
3
4
5
6
7
对每次迭代:
利用buildStump()函数找到最佳的单层决策树
将最佳单层决策树加入到单层决策树数组
计算alpha
计算新的权重向量D
更新累计类别估计值
如果错误率等于0.0,则退出循环

现在,在 adaboost.py 文件中添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# AdaBoost 算法实现
def adaBoostTrainDS(dataArr,classLabels,numIt=40):
"""
:param dataArr: 数据集
:param classLabels: 数据集对应的目标值
:param numIt: 用于指定迭代次数(默认为40次)
:return: 返回弱分类器列表和累计分类结果
"""
# 存储弱分类器的列表,AdaBoost算法会在每次迭代中生成一个弱分类器,并将其添加到这个列表中
weakClassArr = []
# 获取数据集一共多少样本(行)
m = shape(dataArr)[0]
# 初始化样本权重
D = mat(ones((m,1))/m)
# 初始化一个累计的分类结果向量,用于存储多个弱分类器的加权累计结果
aggClassEst = mat(zeros((m,1)))
# 开始迭代
for i in range(numIt):
# 迭代一共弱分类器结果
bestStump,error,classEst = buildStump(dataArr,classLabels,D)
print(f"权重:{D.T}")
#❓ 计算当前弱分类器的权重 alpha
alpha = float(0.5*log((1.0-error)/max(error,1e-16)))
# 计算的权重保存在弱分类器结果字典中
bestStump['alpha'] = alpha
# 存储弱分类器
weakClassArr.append(bestStump)
print(f"弱分类器分类结果: {classEst.T}")
#❓ 计算当前弱分类器的加权指数,用于更新样本的权重
expon = multiply(-1*alpha*mat(classLabels).T,classEst)
D = multiply(D,exp(expon))
D = D/D.sum()
# 使用当前弱分类器的加权分类结果更新累计分类结果
aggClassEst += alpha*classEst
print(f"累计分类结果: {aggClassEst.T}")
# 计算累计分类结果和实际类别标签之间的错误
aggErrors = multiply(sign(aggClassEst) != mat(classLabels).T,ones((m,1)))
# 计算错误率
errorRate = aggErrors.sum()/m
print(f"错误率: {errorRate}")
# 如果错误率为0,说明已经完美分类数据,可以提前终止迭代
if errorRate == 0.0: break
# 返回弱分类器列表和累计分类结果
return weakClassArr,aggClassEst

函数名称尾部的DS代表的就是单层决策树(decision stump),它是AdaBoost中最流行的弱分类器,当然并非唯一可用的弱分类器。上述函数确实是建立于单层决策树之上的,但是我们也可以很容易对此进行修改以引入其他基分类器。

测试算法:基于AdaBoost的分类

一旦拥有了多个弱分类器以及其对应的alpha值,进行测试就变得相当容易了。在上面的adaBoostTrainDS()中,我们实际已经写完了大部分的代码。现在,需要做的就只是将弱分类器的训练过程从程序中抽出来,然后应用到某个具体的实例上去。每个弱分类器的结果以其对应的alpha值作为权重。所有这些弱分类器的结果加权求和就得到了最后的结果。

现在将如下代码添加到 adaboost.py 文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def adaClassify(datToClass,classifierArr):
"""
:param datToClass: 将待分类的数据集
:param classifierArr: AdaBoost 算法的集成分类器
:return: 返回整合所有弱分类器后的最终分类结果
"""
# 转换待分类数据集为矩阵
dataMatrix = mat(datToClass)
# 获取数据集的样本数(行数)
m = shape(dataMatrix)[0]
# 初始化一个累计的分类结果向量,用于存储多个弱分类器的加权累计结果
aggClassEst = mat(zeros((m,1)))
# 对 AdaBoost 中的每个弱分类器进行迭代
for i in range(len(classifierArr)):
classEst = stumpClassify(dataMatrix,classifierArr[i]['dim'],classifierArr[i]['thresh'],classifierArr[i]['ineq'])
aggClassEst += classifierArr[i]['alpha']*classEst
print(aggClassEst)
# 返回整合所有弱分类器后的最终分类结果。使用 sign 函数将累计分类结果转换为类别标签
return sign(aggClassEst)

我尝试运行这段代码,它在 3.X 版本报错,如你所见,《Machine Learning in Action》 是在 2.x版本上实现的,我尝试修改了一些代码,例如alpha = float(0.5*log((1.0-error)/maximum(float(error),minFloat)))。但是它只能解决一小部分报错,它会带来其他报错,我有点困,不想动脑子,就暂时不修改它了,如果你感兴趣,自行尝试吧。

相关函数

inf

在NumPy中,**inf表示正无穷大(positive infinity)。它是一个特殊的浮点数常量,用于表示大于所有有限浮点数的值**。

-inf则表示负无穷大(negative infinity),表示小于所有有限浮点数的值。

这两个特殊值可以用于执行各种数学运算,例如除以零、计算极限等情况。当进行数值运算时,如果结果超过浮点数表示的范围,就会得到inf-inf

errArr[predictedVals == labelMat] = 0

errArr[predictedVals == labelMat] = 0 这个代码就有点抽象了,不过我们来一点点看,首先需要了解 predictedValslabelMat 都是行数相同的矩阵(1 列),使用predictedVals == labelMat 会得到 true 或者 false 的结果,注意此结果是一个矩阵,然后根据结果矩阵的 true 来给对应的位置赋值 0。

如果你认为不理解,可以查看 NumPy 的文档 Boolean array indexing(布尔数组索引)

max()

在代码中的体现是 alpha = float(0.5*log((1.0-error)/max(error,1e-16)))。具体来说,max(error, 1e-16) 的作用是比较 error1e-16 这两个数的大小,然后返回其中较大的那个数。如果 error 大于等于 1e-16,则返回 error,否则返回 1e-16

1e-16 表示科学计数法中的小数,即 1 乘以 10 的负 16 次方,也就是 0.0000000000000001,它通常用于避免出现非常小的数值。这种写法在很多情况下用于处理误差或防止除零错误。通过将误差或某个数值与一个较小的阈值(如 1e-16)进行比较,可以确保得到一个非零的结果,避免出现异常或无效计算

multiply()

multiply() 是 NumPy 库中的一个函数,用于对两个数组中对应位置的元素进行逐元素相乘。代码示例:

1
2
3
4
5
6
7
8
import numpy as np

arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])

result = np.multiply(arr1, arr2)
print(result)
# 输出: [4 10 18]

sign()

sign() 是 NumPy 库中的一个函数,用于对数组中的元素进行符号函数运算,返回每个元素的正负号。代码示例:

1
2
3
4
5
6
7
import numpy as np

arr = np.array([-2, 0, 4, -1, 6])

result = np.sign(arr)
print(result)
# 输出: [-1 0 1 -1 1]

End

大概理解起来容易,但是实现起来又十分的繁杂且混乱😥

image-20230727231240680

  1. 1.弱学习器是指准确率略高于随机猜测的学习器,例如决策树深度较浅的模型。
  2. 2.这里说的平行于坐标轴的直线是因为决策树根据信息熵找到划分的最佳特征值,也就是示例数据集中的两个特征之一,即 x 轴特征或者 y 特征。