大数跨境
0
0

如何理解决策树和梯度提升的数学底层逻辑

如何理解决策树和梯度提升的数学底层逻辑 数据分析学习与实践
2025-01-12
2
导读:我们试图提供对决策树的直观理解,梯度提升保持了决策树的灵活性,但对过度拟合的鲁棒性更强。我们展示了如何在 Python 中从头开始实现梯度提升,并解释了为什么它更能抵抗过度拟合

决策树和梯度提升背后的数学底层逻辑的直观解释


决策树是一种非参数的监督学习算法,可以用于分类和回归。它使用类似树状的结构来表示决策及其潜在结果。决策树易于理解和解释,并且可以轻松地可视化。然而,当决策树模型变得过于复杂时,它就无法很好地从训练数据中泛化,从而导致过拟合。

梯度提升是一种集成学习模型,其中我们将许多弱学习器组合起来以开发一个强学习器。弱学习器是单个的决策树,每个学习器都试图关注前一个学习器的错误。与单个深度决策树相比,梯度提升通常不易发生过拟合。

本文将直观地解释用于分类和回归问题的决策树背后的原理。我们将了解该模型的工作原理以及它为何可能导致过拟合。接下来,我们将介绍梯度提升,并了解它是如何提高单个决策树的性能的。我们将从0开始在 Python 中实现一个梯度提升回归器和分类器。最后,将彻底解释梯度提升的数学原理。

1. 决策树分类器

决策树模型可用于分类和回归问题。让我们看看决策树分类器是如何实现的。首先,我们创建一个数据集。此数据集与一个二元分类问题相关。我们有 1160 个数据点,具有两个特征 和一个具有两个标签的二元目标 ( , )。数据点是从均匀分布中随机选择的,但在 的数据点之间存在一个边界,看起来像一个弧形。Listing 1 创建了这个数据集并在图 1 中绘制出来。

#Listing 1
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.tree import DecisionTreeClassifier
from sklearn.tree import DecisionTreeRegressor
from sklearn import tree
from matplotlib.colors import ListedColormap

np.random.seed(7)
low_r = 10
high_r = 15
n = 1550
X = np.random.uniform(low=[00], high=[44], size=(n,2)) # 1550行 * 2列
drop = (X[:, 0]**2 + X[:, 1]**2 > low_r) & (X[:, 0]**2 + X[:, 1]**2 < high_r) # 环 
X = X[~drop] # 排除环
y = (X[:, 0]**2 + X[:, 1]**2 >= high_r).astype(int) #转化为 0,1

colors = ['red''blue'#配色
plt.figure(figsize=(66))
for i in np.unique(y):
    plt.scatter(X[y==i, 0], X[y==i, 1], label = "y="+str(i),
                color=colors[i], edgecolor="white", s=50)
circle = plt.Circle((00), 3.5, color='black', fill=False,
                    linestyle="--", label="Acturl boundary")
plt.xlim([-0.14.2])
plt.ylim([-0.15])
ax = plt.gca()
ax.set_aspect('equal')
ax.add_patch(circle)
plt.xlabel('$x_1$', fontsize=16)
plt.ylabel('$x_2$', fontsize=16)
plt.legend(loc='best', fontsize=11)
plt.show()

图 1

接下来,我们使用 Scikit-learn 库在此数据集上创建并训练一个决策树分类器。拟合模型后,我们可以使用 plot_tree() 函数可视化决策树。结果树如图 2 所示。

tree_clf = DecisionTreeClassifier(random_state=0)
tree_clf.fit(X, y)

plt.figure(figsize=(17,12))
tree.plot_tree(tree_clf, fontsize=17, feature_names=["x1""x2"])
plt.show()
图 2

现在让我们更深入地了解这棵树。决策树是一个分层结构。它是通过边连接的节点的集合。决策树的最顶层节点称为,并作为其起点。根连接到较低级别的两个节点。这两个节点称为根的子节点,分别是左子节点右子节点。这些节点也各有两个子节点。最低层没有子节点的节点称为树的叶子。节点的深度是指从节点到树的根节点的边的数量。因此,决策树的最大深度是其最深叶子的深度。

此决策树用于对数据点进行分类。我们将数据点传递给它,它能够确定其标签 ( )。这是通过将数据点的特征值传递给决策树来完成判定的。假设我们想使用图 2 中的决策树来确定数据点 ( , ) 的标签。让我们仔细看看根节点。根节点将特征  的值与一个阈值进行比较,这里的阈值为 2.73。如果 ≤2.73,我们转到左子节点。否则,我们转到右子节点。因此,根节点通过直线 =2.73 将特征空间划分为两个区域,该直线是使用阈值定义的。在一个区域中我们有 ≤2.73,在另一个区域中我们有 >2.73。如果我们将根视为一个简单的分类器,则这条线充当决策边界。如图 3 所示。

图 3

理想情况下,决策边界应分隔所有具有不同标签的数据点。因此,在决策边界产生的每个区域中,我们应该只有具有相同标签的数据点。如图 3 所示,根线对于我们的数据集来说不是一个好的分类器,因为在每个区域中我们都有 的数据点的混合。因此,决策树应该添加更多节点以提高分类的准确性。

如果添加更多节点时会发生什么。我们向树中添加根的左子节点和右子节点,则它看起来像图 4。根节点将原始空间分成两个区域。每个区域都继续产生一个新子节点。每个子节点都有一个阈值,该阈值将其对应的区域分成两个新的区域(图 4)。

图 4

图 4

现在,我们可以添加更多节点并使树更深。当添加一个新节点时,它会获取从其父节点划分的区域,并使用垂直或水平线将其拆分为两个新区域(此线代表该节点的决策边界)。我们可以添加更多节点,直到生成的区域是纯净的,这意味着所有区域仅包含具有相同标签的数据点。listing 3 中的递归函数绘制了决策树中所有节点的决策边界。

#listing 3
def plot_boundary_lines(tree_model):
    def helper(node, x1_min, x1_max, x2_min, x2_max):
        if feature[node] == 0:
            plt.plot([threshold[node], threshold[node]],
                     [x2_min, x2_max], color="black")
        if feature[node] == 1:
            plt.plot([x1_min, x1_max], [threshold[node],
                                        threshold[node]], color="black")
        if children_left[node] != children_right[node]:
            if feature[node] == 0:
                    helper(children_left[node], x1_min,
                           threshold[node], x2_min, x2_max)
                    helper(children_right[node], threshold[node],
                           x1_max, x2_min, x2_max)
            else:
                    helper(children_left[node], x1_min, x1_max,
                           x2_min, threshold[node])
                    helper(children_right[node], x1_min, x1_max,
                           threshold[node], x2_max)
    feature = tree_model.tree_.feature
    threshold = tree_model.tree_.threshold
    children_left = tree_model.tree_.children_left
    children_right = tree_model.tree_.children_right
    x1_min = x2_min = -1
    x1_max = x2_max = 5
    helper(0, x1_min, x1_max, x2_min, x2_max)

在listing 4 中,我们还定义了另一个函数,该函数在 2-D 空间上创建一个网格,并获取训练后的决策树对该网格上每个点的预测。它将浅蓝色分配给预测标签为 1 ( ) 的点,将橙色分配给预测标签为 0 ( ) 的点。使用此函数,我们可以看到决策树对 2-D 图中所有点的预测。

#listing 4
def plot_boundary(X, y, clf, lims):
    gx1, gx2 = np.meshgrid(np.arange(lims[0], lims[1],
                                     (lims[1]-lims[0])/300.0),
                           np.arange(lims[2], lims[3],
                                     (lims[3]-lims[2])/300.0))
    cmap_light = ListedColormap(['lightsalmon''aqua'])
    gx1l = gx1.flatten()
    gx2l = gx2.flatten()
    gx = np.vstack((gx1l,gx2l)).T
    gyhat = clf.predict(gx)
    gyhat = gyhat.reshape(gx1.shape)
    plt.pcolormesh(gx1, gx2, gyhat, cmap=cmap_light)
    plt.scatter(X[y==00], X[y==0,1], label="y=0", alpha=0.7,
                color="red", edgecolor="white", s=50)
    plt.scatter(X[y==10], X[y==1,1], label="y=1", alpha=0.7,
                color="blue", edgecolor="white", s=50)
    plt.legend(loc='upper left')

现在,我们可以将其用于我们在数据集上训练的决策树(图 2 中显示的树)。图 5 显示了结果。

#listing 5
plt.figure(figsize=(6,6))
plot_boundary(X, y, tree_clf, lims=[-15-15])
plot_boundary_lines(tree_clf)
ax = plt.gca()
ax.set_aspect('equal')
plt.xlim([-15])
plt.ylim([-15])
plt.xlabel('$x_1$', fontsize=16)
plt.ylabel('$x_2$', fontsize=16)
plt.show()
1_JTr1U3Z2vR0aR2Fd_OA3Fg.png

正如你所见,原始数据集被划分为 9 个矩形。那是因为我们的决策树中有 9 个叶子(图 2)。每个矩形代表此树中的一个叶子。这些矩形创建了纯净的区域。每个区域中的数据点具有相同的标签,并且此标签分配给相应的叶子。当我们给这个训练好的决策树一个测试点时,它首先确定该点属于哪个叶子,然后将叶子的标签分配给该点。换句话说,数据点的预测标签只是该点所在的区域(或矩形)的标签。

每条水平或垂直线代表该树中一个节点的阈值。可以将这些线组合起来创建树的总决策边界,如图 6 所示。

plt.figure(figsize=(6,6))
plot_boundary(X, y, tree_clf, lims=[-15-15])
circle = plt.Circle((00), 3.5, color='black', fill=False,
                    linestyle="--", label="实际边界")
plt.text(3.54.5r"$\hat{y}=1$", fontsize=13)
plt.text(2.352.1r"$\hat{y}=0$", fontsize=13)
ax = plt.gca()
ax.set_aspect('equal')
ax.add_patch(circle)
plt.xlabel('$x_1$', fontsize=16)
plt.ylabel('$x_2$', fontsize=16)
plt.xlim([-0.14.2])
plt.ylim([-0.15])
plt.legend(loc='upper left')
plt.show()
图 6

该图还显示了训练数据集的实际边界,这是一个弧形。重要的是要注意决策树分类器如何使用垂直和水平线的组合来估计此弧形。

到目前为止,我们仅研究了一个具有两个特征的数据集,但是相同的想法可以应用于具有更高维度的数据集。如果我们有 3 个特征,则每个节点都会获取从其父节点传递的区域,并使用平行于其中一个轴(  或 )的平面将其拆分为两个新区域。当这些平面组合在一起时,它们会创建树的决策边界。通常,对于具有 n 个特征的数据集,每个节点都会使用其阈值创建一个超平面,并且这些超平面组合在一起以形成决策树分类器的决策边界。

2. 决策树分类器中的过拟合

在二元分类问题中,我们可以假设具有不同标签的数据点由一个假设的边界分隔。此边界是由生成数据的过程创建的。在我们的数据集中,此边界是一个弧形。决策树分类器是一个强大的机器学习模型,从理论上讲,它可以添加尽可能多的节点来解决任何非线性分类问题。在 2-D 空间中,无论实际边界多么复杂,始终可以通过添加更多水平线和垂直线来近似(图 7)。

图 7

对于 n-D 也是如此,我们可以在其中添加越来越多的超平面来模拟边界。然而,这个强大的模型有一个显着的缺点:过拟合。当机器学习模型变得过于复杂并开始学习训练数据的噪声时,就会发生过拟合。结果,它将无法很好地泛化到新的未见过的数据。

当决策树的决策边界变得比原始数据集的实际边界复杂得多时,就会发生过拟合。这是一个例子。假设我们有一个嘈杂的数据集,其中直线是具有不同标签的数据点的边界。此数据集在listing 7 中定义,并在图 8 中绘制。

#listing 7
np.random.seed(1)
n = 550
X1 = np.random.uniform(low=[00], high=[44], size=(n,2))
drop = (X1[:, 0] > 1.8) & (X1[:, 0] < 1.9)
X1 = X1[~drop]
y1 = (X1[:, 0] > 1.9).astype(int)

X2 = np.random.uniform(low=[1.70], high=[1.94], size=(15,2))
y2 = np.ones(15).astype(int)

X = np.concatenate((X1, X2), axis=0)
y = np.concatenate((y1, y2))

colors = ['red''blue']
for i in np.unique(y):
    plt.scatter(X[y==i, 0], X[y==i, 1], label = "y="+str(i),
                color=colors[i], edgecolor="white", s=50)
plt.axvline(x=1.8, color="black", linestyle="--")
plt.legend(loc='best')
plt.xlim([-0.54.5])
plt.ylim([-0.25])
ax = plt.gca()
ax.set_aspect('equal')
plt.xlabel('$x_1$', fontsize=16)
plt.ylabel('$x_2$', fontsize=16)
plt.show()

图 8

现在,我们在此数据集上训练一个决策树分类器。该树如图 9 所示。

#Listing 8
tree_clf = DecisionTreeClassifier(random_state=1)
tree_clf.fit(X, y)

plt.figure(figsize=(13,10))
tree.plot_tree(tree_clf, fontsize=9, feature_names=["x1""x2"])
plt.show()
图 9

图 9

最后,我们在图 9 中绘制了树分类器的决策边界。


#Listing 9
plt.figure(figsize=(6,6))
plot_boundary(X, y, tree_clf, lims=[-15-15])
plt.axvline(x=1.8, color="black", linestyle="--", label="实际边界")
plt.text(0-0.3r"$\hat{y}=0$", fontsize=13)
plt.text(3-0.3r"$\hat{y}=1$", fontsize=13)
ax = plt.gca()
ax.set_aspect('equal')
plt.xlim([-0.54.5])
plt.ylim([-0.54.5])
plt.xlabel('$x_1$', fontsize=16)
plt.ylabel('$x_2$', fontsize=16)
plt.legend(loc="best")
plt.show()

图 10

正如你所见,实际边界是一条直线,但是拟合到此数据集的树分类器的决策边界是一条弯曲的线。那是因为该模型试图学习嘈杂的数据点,因此它通过添加更多节点来扩展决策边界以包含它们。这是一个明显的过拟合示例。我们可以通过限制树的最大深度来降低过拟合的风险。为此,我们在 DecisionTreeClassifier() 中使用参数 max_depth

#Listing 10
tree_clf1 = DecisionTreeClassifier(random_state=1, max_depth=1)
tree_clf1.fit(X, y)

plt.figure(figsize=(10,5))
tree.plot_tree(tree_clf1, fontsize=9, feature_names=["x1""x2"])
plt.show()
图 11

图 11

图 11 显示了结果树。现在决策树叶子的最大深度为 1,因此我们只有带有两个叶子的根节点。此新树的决策边界如图 12 所示。

#Listing 11
plt.figure(figsize=(6,6))
plot_boundary(X, y, tree_clf1, lims=[-15-15])
plt.axvline(x=1.8, color="black", linestyle="--", label="实际边界")
plt.text(0-0.3r"$\hat{y}=0$", fontsize=13)
plt.text(3-0.3r"$\hat{y}=1$", fontsize=13)
ax = plt.gca()
ax.set_aspect('equal')
plt.xlim([-0.54.5])
plt.ylim([-0.54.5])
plt.xlabel('$x_1$', fontsize=16)
plt.ylabel('$x_2$', fontsize=16)
plt.legend(loc="best")
plt.show()

图 12

由于我们只有一个节点,因此决策边界是一条直线,并且没有过拟合。当我们限制决策树的深度时,我们可能不会最终得到叶子的纯净区域。在这种情况下,每个叶子的标签由该叶子相应区域中的投票决定。例如,在图 12 中,左侧区域的标签为零。那是因为该区域中的大多数训练数据点的标签为零 ( )。同样,右侧区域的标签为零。

3. 不纯度度量和基尼指数

决策树如何确定每个节点应使用哪个特征和阈值?它使用不纯度度量来做到这一点。在我们的示例中,使用的不纯度度量是基尼指数。基尼指数是样本中不纯度的度量。当一个节点将给定的空间拆分为两个区域时,我们可以使用该区域中数据点的标签来计算每个区域的基尼指数。一个区域的基尼指数定义为:

其中 C 是所有标签(类别)的集合,而 遍历这些标签中的每一个。这里的 是在该区域中选择具有标签 的数据点的概率。在二元分类问题中,如果一个区域中的所有数据点都具有相同的标签(例如标签 0):

基尼指数为 0 表示完全纯净,这意味着区域中的所有数据点都具有相同的标签。这是基尼指数可以取的最小值。较高的基尼指数意味着较高的不纯度。如果我们在一个区域中有相同数量的具有不同标签的数据点,则基尼指数为:

基尼指数为 0.5 表示最高的不纯度,它是基尼指数的最高值。因此,基尼指数始终在 0 和 0.5 之间。

当决策树中的一个节点将其空间拆分为两个区域时,我们可以计算每个结果区域的基尼指数。当决策树算法添加一个新节点时,它会评估每个特征针对不同潜在阈值的基尼指数。然后,它选择使该节点平均基尼指数最低的特征和阈值(这意味着该节点产生的两个区域的平均纯度最高)。让我们看一个例子。在图 11 中显示的决策树中,发送到根节点的原始数据集中有 552 个数据点,其中 247 个数据点的标签为 0,而 305 个数据点的标签为 1。原始数据集的基尼指数计算如下:

这是根节点的基尼指数。

图 11

根节点将初始数据集分为两个区域。在左侧区域,我们有 254 个数据点:247 个标签为 的点和 7 个标签为 的点。因此,基尼指数为:

这是左子节点的基尼指数。

在右侧区域,我们有 298 个数据点,所有数据点的标签均为 1。因此,右子节点的基尼指数简单地为 0。

接下来,我们计算两个区域的基尼指数的加权和:

因此,根节点处的拆分将原始基尼指数 0.494 降低到平均基尼指数 0.025(对于两个区域)。根节点中使用的特征 ( ) 和阈值 (1.801) 的选择是为了在拆分后给出最低的平均基尼指数,即 0.025。请注意,scikit-learn 绘制的决策树显示了传递到每个节点的数据点数量、每个标签的计数以及节点的基尼指数。

4. 决策树回归器

如前所述,我们可以将决策树用于分类和回归问题。本节介绍如何创建决策树以解决回归问题。我们使用Listing 12 为此目的创建另一个数据集。此数据集如图 13 所示。

#Listing 12
np.random.seed(4)
x = np.linspace(0360)
x1 = np.linspace(0120)
x2 = np.linspace(1340)
y = x.copy()
y[x>=1] = 1
y = y + np.random.normal(scale=0.1, size=60)
X = x.reshape(-11)

plt.figure(figsize=(8,8))
plt.scatter(x, y, label="带噪声的数据点")
plt.plot(x1, x1, color="blue", alpha=0.5, label="趋势")
plt.plot(x2, len(x2)*[1], color="blue", alpha=0.5)
plt.xlim([-0.13.1])
plt.ylim([-0.12])
plt.xlabel('$x$', fontsize=16)
plt.ylabel('$y$', fontsize=16)
ax = plt.gca()
ax.set_aspect('equal')
plt.legend(loc="best", fontsize=14)
plt.show()
1_HyecjZIunJDQgtRrmcltMQ.png

此数据集是通过向两个线段 ( , ) 上的点添加噪声(具有正态分布)创建的。这里 是此数据集的唯一特征,而 是目标。现在我们要使用决策树回归器来学习此数据集。下一个Listing创建一个决策树回归器并将其拟合到数据集。此树回归器在图 14 中绘制。请注意,此树的最大深度为 3。

tree_regres = DecisionTreeRegressor(random_state=0, max_depth=3)
tree_regres.fit(X, y)

plt.figure(figsize=(17,8))
tree.plot_tree(tree_regres, fontsize=10, feature_names=["x"])
plt.show()

图 14

让我们看看树回归器是如何工作的。树回归器中的节点类似于树分类器中的节点。每个节点都有一个与阈值进行比较的特征。决策树分类器和决策树回归器之间的主要区别在于叶子值。在树分类器中,数据集的目标是具有某些标签的离散变量,并且每个叶子代表一个标签。但是,在树回归器中,目标是连续变量,每个叶子代表此目标的可能值。例如,在图 14 中绘制的树回归器中,从左侧开始的第一个叶子的值为 0.036。因此,当我们最终到达此叶子时,目标的预测值将为 0.036。如果我们给这个训练好的决策树回归器一个测试点,它首先需要确定它属于哪个叶子,然后将叶子的值分配给该点。

我们从根节点开始。数据集有 60 个数据点。目标的平均值为:

y.mean()
0.828

如果我们使用此平均值作为整个数据集的简单预测器,则其均方误差 (MSE) 将为:

((y.mean()-y)**2).mean()
0.102

此信息显示在根节点上。因此,在根节点开始拆分数据集之前, 的平均值是我们拥有的最佳估计量。根节点使用其阈值 0.585 拆分数据。如图 15 所示。拆分后,左侧区域(传递到左子节点)中有 12 个数据点,右侧区域(传递到右子节点)中有 48 个数据点。

图 15

在每个区域中, 的平均值表示模型预测。例如,在图 15 的左侧区域中,我们只有 ≤0.585 的数据点。这些数据点目标的平均值为:

y[(X <=0.585).flatten()].mean()
0.262

如果我们使用这些值作为这些数据点的模型预测,我们可以计算其均方误差 (MSE):

((0.262 - y[(X <= 0.585).flatten()])**2).mean()
0.037

这些数字在图 15 中左子节点内显示为 valuesquared_error。同样,右侧区域的预测和 MSE 分别为 0.97 和 0.018。图 16 显示了当我们将树的最大深度增加 1 时会发生什么。当添加一个新节点时,它会获取从其父节点传递的区域,并使用其阈值将其拆分为两个新区域。随着树变得更深,节点的 MSE 减小,并且每个区域的预测都更接近实际数据点。

图 16

Listing 14 绘制了最终树的预测(此树在图 14 中显示)。结果绘制在图 17 中。

#Listing 14
x1 = np.linspace(0120)
x2 = np.linspace(1340)
X_space = np.linspace(-0.33.31000).reshape(-11)
yhat = tree_regres.predict(X_space)

plt.figure(figsize=(8,6))
plt.scatter(x, y, label="训练数据")
plt.plot(X_space, yhat, color="red", label="预测")
plt.plot(x1, x1, color="blue", alpha=0.5, label="趋势")
plt.plot(x2, len(x2)*[1], color="blue", alpha=0.5)
plt.legend(loc="best", fontsize=14)
plt.xlim([-0.33.3])
plt.ylim([-0.12])
ax = plt.gca()
ax.set_aspect('equal')
plt.xlabel('$x$', fontsize=14)
plt.ylabel('$y$', fontsize=14)
plt.show()

图 17

图 14 中绘制的树有 8 个叶子,图 17 中的图包含 8 个水平红色线段,每个线段代表其中一个叶子的预测。当这些线段组合在一起时,它们形成一条阶梯线,表示给定间隔中整个决策树的预测。这条阶梯线是对训练数据集实际趋势的估计,并且是决策树回归器的特征。你可能会认为,通过增加树的最大深度并添加更多线段(更多节点),阶梯线将更接近实际趋势。不幸的是,这种情况不会发生,并且增加最大深度只会增加过拟合的风险。

5.泛化问题

查看之前的一些图,你可能已经注意到决策树存在一个问题。数据集定义在区间 [0, 3] 中。对于所有小于零的 值,我们得到一个恒定的预测,因为预测是一条水平线(图 18)。同样,对于所有大于 3 的 值,我们得到一个恒定的预测。决策树回归器无法推断超出训练数据范围的值

图18

请记住,树在每个区域中的预测只是该区域中数据点目标的平均值。此预测由水平线段表示。树中具有最低阈值的节点会创建一个位于训练数据集边缘的区域。例如,在图 14 中显示的树中,此节点是最左侧的节点,其中 ≤0.127。此节点的左叶创建一个位于训练数据集左边缘的区域,其线段延伸到负无穷大。同样,具有最大阈值的节点创建一个叶子,该叶子的区域位于训练数据集的右边缘,并且其线段延伸至无穷大。

我们对于决策树分类器也有同样的问题。如图 6 所示,决策边界始终以其边缘处的垂直或水平线结束,并且不能采用任何其他形式。外推问题与决策树的结构有关。在决策树中,每个节点都会创建一个简单的预测。在训练数据集区间内,这些预测可以组合成一种复杂的形式,但是,在该区间之外,我们只剩下覆盖该区域的一个节点的简单预测。

6. 决策树回归器中的过拟合

当我们在数据集上训练图 14 所示的决策树回归器时,我们将最大深度设置为 3。让我们看看如果消除此限制会发生什么。Listing 15 这次将新的决策树回归器拟合到数据集,没有最大深度限制。它绘制了树的预测与原始数据集的关系,如图 19 所示。

#Listing  15  
  
X_space = np.linspace(-0.33.31000).reshape(-11)  
tree_regres = DecisionTreeRegressor(random_state=1)  
tree_regres.fit(X, y)  
yhat = tree_regres.predict(X_space)  
plt.figure(figsize=(8,6))  
plt.scatter(X, y, label="Training data")  
plt.plot(X_space, yhat, color="red", label="prediction")  
  
plt.xlim([-0.33.3])  
plt.ylim([-0.12])  
plt.legend(loc="best", fontsize=14)  
plt.xlabel('$x$', fontsize=14)  
plt.ylabel('$y$', fontsize=14)  
ax = plt.gca()  
ax.set_aspect('equal')  
plt.show()

图 19

我们可以使用以下语句显示这棵树的最大深度:

tree_regres.tree_.max_depth

11

我们还可以打印这棵树的叶子数量:

tree_regres.get_n_leaves()

60

如图 19 所示,这棵树正在过度拟合,并且正在学习原始数据集中的噪声。主要问题是树回归器无法看到实际趋势。它唯一看到的是原始数据点,当添加新节点时,它们会尝试靠近这些点。最后,叶子的数量将等于数据点的数量。最后一次分割后,每个叶子只剩下一个数据点,并且简单地返回其值。因此,在每个叶子中,MSE 为零。事实上,训练数据集的预测误差为零,因此训练数据集的 为 1:

tree_regres.score(X,y)
1.0

现在,树回归器对训练数据集给出了完美的预测,但是,它无法很好地推广到新的未见过的数据,因为它无法了解训练数据集的实际趋势。

7. 决策树是一种非参数模型

机器学习模型分为两大类:参数模型和非参数模型。参数模型采用将特征映射到目标的函数的特定形式。这通常涉及假设数据遵循已知分布。另一方面,非参数模型不假设映射函数的特定形式。他们也不对数据的分布做出任何假设。因此,它们更加灵活并且可以适应数据结构。

决策树是非参数模型的一个例子。让我们以决策树回归器与线性回归模型为例进行比较。线性回归是一种参数模型,假设目标和特征之间存在线性关系。当数据集只有一个特征时,这种线性关系由以下方程给出:

其中 是训练过程中确定的模型参数。根据该方程,模型预测 ( ) 位于一条直线上。图 20 将线性模型的预测与决策树回归器的预测进行了比较。这里我们有两个不同的数据集,我们将两个模型都拟合到每个数据集上。

图 20

线性模型对两个数据集的预测都是一条直线,如左图所示。当数据集改变时,直线的斜率和截距也会改变(因为 正在改变),但它仍然是一条直线。这是因为该模型是参数化的,并假设特征和目标之间存在线性关系。树回归器的预测如右图所示。它是一个非参数模型,因此它试图模仿数据的趋势,并且模型的预测适应训练数据集的形状。因此,随着训练数据集的变化,模型预测的形状也会随之变化。

我们可以将非参数模型视为一根橡皮筋,它会改变其形状以模仿训练数据集的形状。相反,线性模型就像一根刚性杆,可以改变其位置,但不能改变其形状。

8. 梯度提升

梯度提升是集成学习的一个例子。集成学习是一种机器学习方法,通过组合多个模型的预测来提高预测性能。Boosting是一种集成学习方法,它顺序组合多个弱学习模型以提高预测性能。梯度提升可用于分类和回归问题。由于它基于提升的概念,因此它通过顺序组合多个弱学习器来创建强学习器。

梯度提升回归

梯度提升回归涉及几个步骤。在这里,我们使用我们的数据集解释这些步骤。梯度提升首先使用训练数据集对预测进行初步猜测。初始猜测是训练数据集中所有数据点的平均目标。因此,我们有:

我们可以将 视为预测 目标的简单基础模型。训练数据集中所有示例的该基本模型的残差计算如下:

然后,我们创建一个浅层决策树回归器来预测训练数据集的残差。该树回归器用 表示,以 作为特征,以 作为目标。该回归量的预测由 表示。我们将该模型的预测添加到基本模型的预测中:

与原始预测  相比,这里  是对目标更好的预测。现在我们计算训练数据集中所有示例的  残差:

然后我们可以训练另一个浅层决策树,以 为特征,以 -  为目标。该模型的预测由 表示,并添加到  中:

 相比, 现在是更好的预测。这个过程可以重复M次。每次我们计算训练数据集中所有示例的先前模型的残差:

然后我们以 为特征、 为目标来训练决策树 。这棵树的预测被添加到前一棵树的预测中以改进它:

在实践中,我们在这个方程中添加一个称为学习率的参数,用η表示,其范围在 0 到 1 之间:

现在,如果简单地使用这个递归方程,则可以得出:

这里 是 boosting 模型的最终预测。它是将这个集成中所有模型的预测添加到基础模型 的结果。我们使用训练数据集的示例来获得 ,但现在它也可以用于预测训练数据集中不存在的未见特征  的目标。图 21 演示了梯度提升过程。

图 21

图 21

因此,最终模型是 M 个弱模型的集合,其预测为 。每个模型都会学习前一个模型的错误,并尝试改进其预测。学习率是该集成模型的超参数,其作用是通过缩小集成中每个模型的预测来抑制过拟合。在每一步中,我们都希望改进先前模型的预测,但如果改进太多,我们可能会开始学习训练数据集中的噪声,结果是过拟合。一般来说,集成模型中的学习率和树的数量之间存在权衡。当我们增加树的数量时,学习率应该降低以减轻过度拟合。

Listing 16 用 Python 实现了梯度增强算法。

#Listing 16  
  
class GradBoostingRegressor():  
 def __init__(self, num_estimators, learning_rate, max_depth=1):  
  self.num_estimators = num_estimators  
  self.learning_rate = learning_rate  
  self.max_depth = max_depth  
  self.tree_list = []  
 def fit(self, X, y):  
  self.F0 = y.mean()  
  Fm = self.F0  
  for i in range(self.num_estimators):  
  tree_reg = DecisionTreeRegressor(max_depth=self.max_depth,  
  random_state=0)  
  tree_reg.fit(X, y - Fm)  
  Fm += self.learning_rate * tree_reg.predict(X)  
  self.tree_list.append(tree_reg)  
 def predict(self, X):  
  y_hat = self.F0 + self.learning_rate * \  
   np.sum([t.predict(X) for t in self.tree_list], axis=0)  
return y_hat

GradientBoostingRegressor()类有两种方法用于拟合数据集和预测目标。请注意,此类采用估计器的数量,其中包括 。因此,如果我们有M 棵树,则num_estimators = M +1。我们可以使用此类将梯度增强算法应用于Listing 12 中定义的数据集。Listing 17 使用此类来绘制梯度增强回归器的不同步骤。结果如图 22 所示。该回归器是 9 个深度为 1 的决策树的集合( M = 9, num_estimators = 10)。回归器在Listing 12 中定义的数据集上进行训练。左侧绘制了 ,右侧绘制了残差 和显示了对其训练的浅层决策树的预测。

#Listing 17  
  
M = 9  
X_space = np.linspace(-0.33.31000).reshape(-11)  
gbm_reg = GradBoostingRegressor(num_estimators=M+1, learning_rate=0.3)  
gbm_reg.fit(X, y)  
  
fig, axs = plt.subplots(M+12, figsize=(1145))  
plt.subplots_adjust(hspace=0.3)  
  
axs[00].axis('off')  
axs[01].scatter(X, y, label="y")  
axs[01].axhline(y=gbm_reg.F0, color="red", label="$F_0(x)$")  
axs[01].set_title("m=0", fontsize=14)  
axs[01].set_xlim([-0.33.3])  
axs[01].set_ylim([-0.52])  
axs[01].legend(loc="best", fontsize=12)  
axs[01].set_aspect('equal')  
axs[01].set_xlabel("x", fontsize=13)  
axs[01].set_ylabel("y", fontsize=13)  
  
for i in range(1, M+1):  
 Fi_minus_1 = gbm_reg.F0 + gbm_reg.learning_rate * \  
 np.sum([t.predict(X) for t in gbm_reg.tree_list[:i-1]],  
 axis=0)  
 axs[i, 0].scatter(X, y-Fi_minus_1, label=f"$y-F_{{{i-1}}}(x)$")  
 axs[i, 0].plot(X_space, gbm_reg.tree_list[i-1].predict(X_space),  
 color="red",label=f"$h_{{{i}}}(x)$")  
 axs[i, 0].set_title("m={}".format(i), fontsize=14)  
 axs[i, 0].set_xlim([-0.33.3])  
 axs[i, 0].set_ylim([-12])  
 axs[i, 0].set_xlabel("x", fontsize=13)  
 axs[i, 0].set_ylabel("residual", fontsize=13)  
 axs[i, 0].legend(loc="best", fontsize=12)  
 axs[i, 0].set_aspect('equal')  
   
 axs[i, 1].scatter(X, y, label="y")  
 Fi = gbm_reg.F0 + gbm_reg.learning_rate * \  
 np.sum([t.predict(X_space) for t in gbm_reg.tree_list[:i]],  
 axis=0)  
 axs[i, 1].plot(X_space, Fi, color="red", label=f"$F_{{{i}}}(x)$")  
 axs[i, 1].set_title("m={}".format(i), fontsize=14)  
 axs[i, 1].set_xlim([-0.33.3])  
 axs[i, 1].set_ylim([-0.52])  
 axs[i, 1].set_xlabel("x", fontsize=13)  
 axs[i, 1].set_ylabel("y", fontsize=13)  
 axs[i, 1].legend(loc="best", fontsize=13)  
 axs[i, 1].set_aspect('equal')  
 plt.show()
如图 22

图 22

如图 22 所示,模型  的总预测在每个步骤中都有所提高。在这个例子中,我们只使用了 9 个决策树,但是如果使用更多会发生什么?Listing 18 显示了使用 50 个估计器(49 棵树)的梯度增强回归器的预测(图 23)。

#Listing 18  
  
X_space = np.linspace(-0.33.31000).reshape(-11)  
gbm_reg = GradBoostingRegressor(num_estimators=50, learning_rate=0.3)  
gbm_reg.fit(X, y)  
y_hat = gbm_reg.predict(X_space)  
  
plt.figure(figsize=(8,6))  
plt.scatter(x, y, label="Training data")  
plt.plot(X_space, y_hat, color="red", label="prediction")  
  
plt.xlim([-0.33.3])  
plt.ylim([-0.12])  
plt.legend(loc="best", fontsize=14)  
plt.xlabel('$x$', fontsize=14)  
plt.ylabel('$y$', fontsize=14)  
ax = plt.gca()  
ax.set_aspect('equal')  
plt.show()

图 23

scikit-learn库中的GradientBoostingRegressor类也可用于梯度增强回归。这里我们使用这个类来测试我们的实现:

from sklearn.ensemble import GradientBoostingRegressor  
gbm_reg_sklrean = GradientBoostingRegressor(n_estimators=50,  
learning_rate=0.3,  
max_depth=1)  
gbm_reg_sklrean.fit(X, y)  
y_hat_sklrean = gbm_reg_sklrean.predict(X_space)  
np.allclose(y_hat, y_hat_sklrean)

#True

让我们将其与图 19 中所示的决策树回归器的预测进行比较。请记住,该树的深度为 11,并且在同一数据集上训练时会过度拟合。为什么具有 49 棵树的梯度增强回归器不会出现过度拟合?为了回答这个问题,我们应该更深入地研究这两个模型以及它们如何看待训练数据集。图 24 显示了决策树回归器中的不同节点如何处理训练数据集。

图 24

图24

作为最顶层节点的根可以看到整个数据集并正确检测到其增长趋势。根可以区分数据集的趋势和噪声,因为它可以看到全局。相反,更深的节点只能看到从其父节点传递给它的原始数据集的一小部分。这小部分的变化主要是由于噪声造成的,但是,由于这是节点看到的唯一事物,因此它以噪声为趋势并预测其下降趋势。随着树变得更深,节点更多地暴露于噪声并尝试学习它。结果是过度拟合。

图 25 说明了梯度增强回归器中的不同模型(或节点)如何看待训练数据集。这里每个模型都会看到整个数据集的残差。因此,它可以更可靠地检测主要趋势。因此,它对于过拟合更加稳健。

图 25

有一个古老的故事:盲人摸象,讲述的是一些盲人第一次遇到大象。他们试图通过触摸大象来想象它是什么样子。每个盲人都触摸了大象身体的不同部位。然后,他们根据自己有限的经验描述了大象,但他们的描述最终彼此不同。他们都错了,因为他们看不到全貌,只能根据自己有限的经验来判断。对于深度决策树也会发生同样的情况。深层节点只能看到整个数据集的有限部分,因此它们倾向于学习噪声而不是趋势。

1_rmwWL3YQo1BzV6RT82dKkQ.png

请记住,虽然梯度提升更能抵抗过度拟合,但它仍然容易发生。例如,在Listing 18 中使用的数据集中,数据点的噪声来自具有恒定方差的正态分布。如果噪声的方差发生变化或变得太大,梯度增强模型仍然会过拟合。

9. 梯度增强回归器背后的数学(可选)

到目前为止,我们了解了梯度增强回归器的工作原理。但为什么它有这样的名字,为什么我们需要在每一步学习残差呢?梯度提升和梯度下降算法之间存在微妙的联系。梯度下降是一种用于寻找可微函数的局部最小值的优化算法。它是逻辑回归和神经网络等许多机器学习模型的主力。

在机器学习中,通过优化损失函数 来训练模型,该损失函数 衡量模型的预测与实际数据的匹配程度。梯度下降可以确定使损失函数 最小化的模型参数值。对于回归问题,我们可以使用均方误差(MSE)作为损失函数 ,其定义如下:

其中 第 i个示例的训练数据集的目标, 是其模型预测。训练数据集中的示例数量为n ,… 是模型参数,为简洁起见,用***θ*表示。梯度下降是一种迭代算法。它从每个参数 的初始猜测开始,并使用损失函数 的梯度在每次迭代中更新其值。参数 的初始猜测由 表示。在每次迭代中,使用其当前值和在其当前值评估的损失函数 (相对于该参数)的导数来计算θᵢ的下一个值。

这里 分别是参数的当前值和下一个值, α是称为学习率的常数。例如,对于具有一个特征的线性回归模型,损失函数 可写为:

其中 是数据集第 个示例中的特征。使用这个损失函数 ,可以得出:

现在想象一下,我们想要将梯度下降算法用于非参数回归模型,例如决策树回归器。模型没有参数,那么每一步会更新什么以及损失函数 的梯度应该如何计算?

首先,回归模型的损失函数 保持不变,尽管它不再是参数的函数:

接下来,我们可以修改梯度下降公式来更新模型预测而不是模型参数:

所以,这里每一步要更新的是模型预测。现在,如果我们将损失函数 代入这个方程,我们会得到:

然而,这个方程存在一个根本问题。为了计算  的下一个值,我们需要目标 的实际值。对于训练数据集中的示例来说这是可能的,但是如果我们想要获得不在训练数据集中的看不见的数据点的模型预测怎么办?在这里,我们不知道目标的实际价值。在参数模型中,我们使用梯度下降算法来找到模型参数的最优值。一旦找到它们,我们就可以使用它们来预测任何看不见的数据点的目标。例如,在线性回归的情况下,一旦我们确定了θ ₀ 和θ ₁ 的最佳值,我们就可以使用该模型来预测未见过的数据点 x 的目标:

在非参数模型中,不可能使用这种方法。为了避免这个问题,我们在这里采取不同的方法。对于单个任意数据点,损失函数 定义为:

使用这个损失函数 ,梯度下降公式可以写为:

当然,如果数据点不在训练数据集中,我们不知道y的值。因此,在每一步中,我们都会训练一个弱模型来预测它。该模型采用特征 并预测残差:

我们使用训练数据集来训练该模型。如果我们用 表示这个弱模型的预测,我们可以将梯度下降公式写为:

最后,如果我们改变我们的符号并替换:

结合  和αη ,我们得到:

这就是我们之前使用的梯度提升的递归方程。因此,我们看到梯度提升就像是对非参数模型的传统梯度下降算法的修改。我们训练浅树回归器来预测残差,因为它们代表损失函数 的梯度。每次我们向集成中添加一棵新的浅树时,我们都会再添加一次迭代,并且每次迭代都会更新和改进增强模型的预测。

在梯度下降中,我们从参数的初始随机猜测开始,但在梯度提升中,我们做得更明智。我们的起点是一个常数值,它可以最小化训练数据集的平均损失函数 。如果我们假设:

然后我们可以将训练数据集的损失函数 写为:

如果我们最小化损失函数 ,我们可以找到c的值:

因此,基础模型 ( ( *x* )) 只是训练数据集中所有数据点的平均目标。### 梯度提升分类 在本节中,我们解释梯度增强分类的算法,但我们只关注二元分类。在二元分类问题中,目标只能采用两个标签,分别用 0 和 1 表示。设获得 1 的概率为  。事件发生的*概率*是事件发生的概率与不发生的概率之比。因此,得到 1 的几率是:$$

\text{odds} = \frac{p}{1 - p}

\log(\text{odds}) = \log\left(\frac{p}{1 - p}\right)

\log(\text{odds}) = \log\left(\frac{p}{1 - p}\right) \implies \frac{p}{1 - p} = e^{\log(\text{odds})} \implies p = \frac{e^{\log(\text{odds})}}{1 + e^{\log(\text{odds})}}

p(x) = \frac{\sum_{i=1}^{N} y_i}{N}

F_0(x) = \log\left(\frac{p(x)}{1 - p(x)}\right)

y-p(x)$$

请注意,残差是使用概率而不是赔率的对数计算的。接下来,我们创建一个浅层决策树回归器来预测训练数据集的残差。该树回归器用 表示,以 作为特征,以 作为目标。训练树回归器后,我们需要修改其叶子的值。对于树中的每个叶子(用 表示),我们将叶子 ( ) 的值更改为:

其中L是训练数据集中落在该叶子中的所有示例的集合。我们用 (  , ) 表示L中的每个示例, 中特征 的目标等于 1 的预测概率。因此,分子是 中所有示例的残差之和,分母是 中所有示例的 之和。使用修改后的树,我们现在可以预测训练数据集中所有示例的目标。

修改后的树回归器的预测由  表示。我们将此树的预测添加到基本模型的预测中:

与原始预测  相比,这里 是对目标更好的预测,并且它预测的是特征 的目标几率的对数。现在,我们使用以下公式计算训练数据集中所有示例的目标等于 1 的预测概率:

接下来,我们计算训练数据集的 残差:

现在,我们训练另一个浅层决策树,以 为特征,以  为目标。该模型的预测由 表示,并添加到 中:

这个过程可以重复M次。每次我们根据之前的模型计算训练数据集的残差,并训练决策树 来预测这些残差。因此, 作为其特征,并将  作为其目标。这棵树的预测被添加到前一棵树的预测中以改进它:

简化这个递归方程,我们得到boosting模型的最终预测如下:

这与我们用于梯度增强回归器的方程完全相同。请注意,方程给出了特征 的预测赔率对数,它也可以用于训练数据集中不存在的看不见的特征  。特征 的目标等于 1 的预测概率由以下等式给出:

我们可以将该概率与阈值进行比较,以获得二元目标的最终预测。该阈值通常为 0.5。如果 则预测目标为 1,否则为 0。

Listing 19 实现了梯度增强分类器。

# Listing 19  
  
class GradBoostingClassifier():  
 def __init__(self, num_estimators, learning_rate, max_depth=1):  
  self.num_estimators = num_estimators  
  self.learning_rate = learning_rate  
  self.max_depth = max_depth  
  self.tree_list = []  
 def fit(self, X, y):  
  probability = y.mean()  
  log_of_odds = np.log(probability / (1 - probability))  
  self.F0 = log_of_odds  
  Fm = np.array([log_of_odds]*len(y))  
  probs = np.array([probability]*len(y))  
  for i in range(self.num_estimators):  
   residuals = y - probs  
   tree_reg = DecisionTreeRegressor(max_depth=self.max_depth)  
   tree_reg.fit(X, residuals)  
   # Correcting leaf vlaues  
   h = probs * (1 - probs)  
   leaf_nodes = np.nonzero(tree_reg.tree_ .children_left == -1)[0]  
   leaf_node_for_each_sample = tree_reg.apply(X)  
   for leaf in leaf_nodes:  
    leaf_samples = np.where(leaf_node_for_each_sample == leaf)[0]  
    residuals_in_leaf = residuals.take(leaf_samples, axis=0)  
    h_in_leaf = h.take(leaf_samples, axis=0)  
    value = np.sum(residuals_in_leaf) / np.sum(h_in_leaf)  
    tree_reg.tree_.value[leaf, 00] = value  
      
   self.tree_list.append(tree_reg)  
   reg_pred = tree_reg.predict(X)  
   Fm += self.learning_rate * reg_pred  
   probs = np.exp(Fm) / (1+ np.exp(Fm))  
   
 def predict_proba(self, X):  
  FM = self.F0 + self.learning_rate * \  
  np.sum([t.predict(X) for t in self.tree_list], axis=0)  
  prob = np.exp(FM) / (1+ np.exp(FM))  
  return prob  
   
 def predict(self, X):  
  yhat = (self.predict_proba(X) >= 0.5).astype(int)  
  return yhat

此类中的函数predict_proba()返回 ,函数predict()返回预测的二进制目标。请注意,此类采用估计器的数量,其中包括 。因此,如果我们有M棵树,则num_estimators = M +1。现在让我们在Listing 7 中定义的数据集上尝试这个类(如图 8 所示)。请记住,我们之前为此数据集安装了决策树分类器。这棵树的深度为 8(参见图 9),并且过度拟合(参见图 10)。现在我们将梯度增强分类器拟合到该数据集。

gbm_clf = GradBoostingClassifier(num_estimators=30,                                 learning_rate=0.1, max_depth=1)gbm_clf.fit(X, y) 

Listing 20 绘制了该模型的决策边界,图 27 显示了结果。

# Listing 20  
  
plt.figure(figsize=(88))  
plot_boundary(X, y, gbm_clf, lims=[-15-15])  
plt.axvline(x=1.8, color="black", linestyle="--", label="Actual boundary")  
plt.text(0-0.3r"$\hat{y}=0$", fontsize=15)  
plt.text(3-0.3r"$\hat{y}=1$", fontsize=15)  
ax = plt.gca()  
ax.set_aspect('equal')  
plt.xlim([-0.54.5])  
plt.ylim([-0.54.6])  
plt.xlabel('$x_1$', fontsize=18)  
plt.ylabel('$x_2$', fontsize=18)  
plt.legend(loc="best", fontsize=14)  
  
plt.show()

图 27

您会看到,即使集合中有 29 棵树,模型仍然不会过度拟合并正确预测边界。scikit-learn库中的GradientBoostingClassifier类也可用于梯度提升分类,我们可以用它来测试我们的实现:

from sklearn.ensemble import GradientBoostingClassifier
gbm_clf_sklrean = GradientBoostingClassifier(n_estimators=30,                                             learning_rate=0.1,                                                       max_depth=1)
gbm_clf_sklrean.fit(X, y)
phat_sklrean = gbm_clf_sklrean.predict_proba(X)[:,1]
phat = gbm_clf.predict_proba(X)
np.allclose(phat, phat_sklrean)

10. 梯度增强分类器背后的数学(可选)

如前所述,梯度提升就像是对非参数模型的梯度下降算法的修改。请记住,对于梯度增强回归器,我们最终得到了这个方程:

其中 是模型对目标的预测。在梯度提升分类器中,我们修改梯度下降公式来更新赔率对数的模型预测:

但是我们如何定义损失函数 ( )呢?分类器模型预测的是目标等于1的概率。如果目标的实际值为1,我们希望最大化这个概率,如果它为零,我们希望它最小化。从数学上讲,这相当于最大化此项:

时,此项等于 )。因此,最大化它将会最大化  。当 时,它等于log(1-  ) 并且最大化它将使 最小化。该术语称为对数似然函数。我们通常更喜欢最小化损失函数 ,因此我们的最终损失函数 被定义为负对数似然:

我们已经证明:

现在使用这个公式,我们可以简化损失函数 :

接下来,我们计算损失函数 相对于log ( odds ) 的导数:

因此,可以得出:

如果数据点不在训练数据集中,我们不知道y的值。因此,在每一步中,我们都会训练一个弱模型来预测它。该模型采用特征 并预测残差 -  。训练数据集用于训练该模型。如果我们用 表示这个弱模型的预测,假设  给出预测的log ( odds ),并将α替换为η ,我们得到梯度提升分类器的递归公式:

我们还需要解释修改每棵树中叶子值的公式。树中叶子l的值(用 表示)通过求解以下方程获得:

首先,我们用 写出之前导出的损失函数 :

然后,我们使用泰勒级数来近似落在叶子中的训练数据集示例的损失函数 :

如果我们包含所有示例,我们会得到:

现在,为了最小化损失函数 ,我们取其关于vₗ的导数并将其设置为零:

通过求解 方程,我们有:

接下来,我们计算损失函数 的一阶和二阶导数:

如果我们将它们代入 的方程中,则得出:

在本文中,我们试图提供对决策树的直观理解。决策树是由一些节点组成的非参数模型。每个节点只是一个线性分类器,但是当组合起来时,它们可以学习数据集中的任何非线性模式。这种灵活性是以过度拟合为代价的,这意味着当树生长过多时,它就会开始学习数据点的噪声。梯度提升是一种集成方法。它由一系列弱决策树组成,其中每棵树都试图改进前一棵树的预测。梯度提升保持了决策树的灵活性,但对过度拟合的鲁棒性更强。我们展示了如何在 Python 中从头开始实现梯度提升,并解释了为什么它更能抵抗过度拟合。

我希望您喜欢阅读这篇文章。如果您觉得我的文章有帮助.


【声明】内容源于网络
0
0
数据分析学习与实践
数据分析,数据科学,线性代数,统计学,AI,python,可视化,excel
内容 343
粉丝 0
数据分析学习与实践 数据分析,数据科学,线性代数,统计学,AI,python,可视化,excel
总阅读94
粉丝0
内容343