超全面讲透一个算法模型,XGBoost !!

文摘   2024-09-20 16:26   北京  

大家好,我是小白~

今儿是基础算法的第6天,我们来聊XGBoost~

XGBoost 是一种非常强大的机器学习算法,常常被用在比赛和实际问题中,特别是预测类问题。虽然名字听起来有点复杂,但其实可以通过简单的例子来理解。

XGBoost 是什么?

XGBoost 是「Extreme Gradient Boosting」的缩写,属于「提升树(Boosting Tree)」的一种。它的核心思想是通过逐步修正预测错误来提升整体模型的准确率。

可以简单理解为:多个弱小的模型(决策树)组合起来形成一个强大的模型。每个小模型在不同的地方犯错,但通过不断学习前面的错误,最终的模型就会越来越好。

举个例子

假设你是一个老师,给学生评分。但是你发现一次考试你评分不太准,总是有一些学生的分数给高了,另一些学生的分数给低了。那么怎么办呢?

XGBoost 的思路是这样的:

  • 第一步:你先用一个简单的方法给所有学生打分(比如看他们答对了几个问题),这就是第一个弱模型
  • 第二步:你发现这个评分有一些偏差,比如某些聪明的学生得分太低了,某些学生虽然答题对了但思路不对却得分高了。
  • 第三步:你再根据这些偏差构建第二个评分标准,专门纠正第一个模型的错误。这时我们就有了第二个弱模型
  • 第四步:你继续这么做,不断纠正前面的模型,直到你的评分结果非常准确。

XGBoost 就是通过不断构建新的弱模型(小决策树)来修正前面模型的错误,最后将所有的模型组合起来,形成一个强大的模型

XGBoost 怎么修正错误?

XGBoost 的特别之处在于,它使用了一种叫梯度提升(Gradient Boosting)的方法来修正模型错误。

用简单话说,XGBoost 会看模型预测和实际结果之间的差距(也就是残差),然后它会想办法去「追着」这些差距,专门针对差距大的地方做出改进。

  • 如果预测结果太低,XGBoost 就会提升这个地方的预测值。
  • 如果预测结果太高,它就会降低这里的预测值。
  • 这个过程类似于我们爬山时,沿着坡度(误差)不断调整方向,最终找到最好的路径。

XGBoost 的特点

XGBoost 之所以受欢迎,主要有以下几个特点:

  • 速度快:XGBoost 经过了很多优化,计算速度非常快,能够在大数据集上高效运行。
  • 准确率高:它的提升方法可以大大提高模型的预测准确度。
  • 防止过拟合:它有一些技巧,能够防止模型在训练数据上表现很好,但在新数据上效果变差。

实际例子

假如你是某个公司的市场部主管,想预测每个客户购买某种产品的可能性。你有客户的历史行为、年龄、性别等数据。你可以用 XGBoost 来分析这些数据,并训练模型去预测哪个客户最有可能购买你的产品。通过不断优化,你的模型会越来越准,从而帮助你更好地制定市场策略。

总结来说,XGBoost 是一个通过不断修正错误,组合多个弱模型(决策树)来形成强大模型的机器学习算法。

原理部分

梯度提升公式

XGBoost 是一种基于梯度提升(Gradient Boosting)的决策树算法。在这里,我们详细推导 XGBoost 的核心公式,并解释其逻辑。

目标函数

XGBoost 的目标是最小化一个损失函数 ,并带有正则化项  来控制模型复杂度。总体目标函数定义为:

其中:

  •  是损失函数,用来衡量真实值  和预测值  之间的差距。常见的选择是平方误差损失:
  •  是正则化项,用来控制决策树的复杂度,防止过拟合。常见形式是:,其中  是树的叶节点数, 是每个叶子节点的权重,   是超参数。

模型构建

XGBoost 的预测值  是通过一系列的弱学习器(决策树)累加得到的:

在每一步,我们通过拟合当前的残差来更新模型,即学习如何更好地预测误差。

二阶泰勒展开

为了优化目标函数,XGBoost 使用了二阶泰勒展开对损失函数进行近似:

其中:

  •  是损失函数对预测值的梯度。
  •  是损失函数的二阶导数(Hessian)。

叶子节点的分裂与权重优化

我们需要决定如何分裂节点,以及如何分配叶子节点的权重。对于每个叶子节点,我们可以通过最小化以下公式来得到最优的权重:

其中, 是叶子节点  包含的样本集合。

而分裂节点的增益(Gain)是:

增益决定了分裂是否值得,若增益较大则进行分裂。

代码实现

接下来,我们基于上述推导,手动实现一个简单的 XGBoost 算法~

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split

# 生成虚拟数据集
X, y = make_regression(n_samples=300, n_features=1, noise=0.1, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 定义一个简单的回归树类
class SimpleTree:
    def __init__(self, depth=3, min_samples_split=10, lambda_reg=1):
        self.depth = depth
        self.min_samples_split = min_samples_split
        self.lambda_reg = lambda_reg
        self.tree = None

    # 拟合树
    def fit(self, X, g, h):
        self.tree = self._build_tree(X, g, h, depth=0)

    def _build_tree(self, X, g, h, depth):
        if depth == self.depth or len(X) < self.min_samples_split:
            w = -np.sum(g) / (np.sum(h) + self.lambda_reg)  # 最优叶子节点权重
            return {"leaf"True"weight": w}
        
        best_gain = 0
        best_split = None
        best_left = None
        best_right = None

        for feature in range(X.shape[1]):
            thresholds = np.unique(X[:, feature])
            for threshold in thresholds:
                left_idx = X[:, feature] <= threshold
                right_idx = X[:, feature] > threshold

                if sum(left_idx) == 0 or sum(right_idx) == 0:
                    continue

                g_left, g_right = g[left_idx], g[right_idx]
                h_left, h_right = h[left_idx], h[right_idx]

                gain = self._calc_gain(g, h, g_left, g_right, h_left, h_right)
                if gain > best_gain:
                    best_gain = gain
                    best_split = (feature, threshold)
                    best_left = left_idx
                    best_right = right_idx

        if best_split is None:
            w = -np.sum(g) / (np.sum(h) + self.lambda_reg)
            return {"leaf"True"weight": w}

        left_tree = self._build_tree(X[best_left], g[best_left], h[best_left], depth + 1)
        right_tree = self._build_tree(X[best_right], g[best_right], h[best_right], depth + 1)

        return {"leaf"False"feature": best_split[0], "threshold": best_split[1], "left": left_tree, "right": right_tree}

    def _calc_gain(self, g, h, g_left, g_right, h_left, h_right):
        gain = 0.5 * (
            (np.sum(g_left) ** 2 / (np.sum(h_left) + self.lambda_reg)) +
            (np.sum(g_right) ** 2 / (np.sum(h_right) + self.lambda_reg)) -
            (np.sum(g) ** 2 / (np.sum(h) + self.lambda_reg))
        )
        return gain

    # 预测
    def predict(self, X):
        return np.array([self._predict_row(x, self.tree) for x in X])

    def _predict_row(self, x, tree):
        if tree["leaf"]:
            return tree["weight"]
        feature, threshold = tree["feature"], tree["threshold"]
        if x[feature] <= threshold:
            return self._predict_row(x, tree["left"])
        else:
            return self._predict_row(x, tree["right"])

# 定义XGBoost模型
class XGBoost:
    def __init__(self, n_estimators=50, learning_rate=0.1, max_depth=3, lambda_reg=1):
        self.n_estimators = n_estimators
        self.learning_rate = learning_rate
        self.max_depth = max_depth
        self.lambda_reg = lambda_reg
        self.trees = []

    # 拟合模型
    def fit(self, X, y):
        self.base_prediction = np.mean(y)
        y_pred = np.full(y.shape, self.base_prediction)

        for _ in range(self.n_estimators):
            g = 2 * (y_pred - y)  # 一阶导数(梯度)
            h = 2 * np.ones_like(y)  # 二阶导数(Hessian)

            tree = SimpleTree(depth=self.max_depth, lambda_reg=self.lambda_reg)
            tree.fit(X, g, h)
            y_pred += self.learning_rate * tree.predict(X)
            self.trees.append(tree)

    # 预测
    def predict(self, X):
        y_pred = np.full(X.shape[0], self.base_prediction)
        for tree in self.trees:
            y_pred += self.learning_rate * tree.predict(X)
        return y_pred

# 创建并训练XGBoost模型
model

 = XGBoost(n_estimators=10, learning_rate=0.1, max_depth=3, lambda_reg=1)
model.fit(X_train, y_train)

# 预测结果
y_train_pred = model.predict(X_train)
y_test_pred = model.predict(X_test)

# 绘制数据分析图
plt.figure(figsize=(108))

# 图1:训练数据和预测结果
plt.subplot(221)
plt.scatter(X_train, y_train, color='blue', label='True', alpha=0.6)
plt.scatter(X_train, y_train_pred, color='red', label='Predicted', alpha=0.6)
plt.title("Training Data vs Predictions")
plt.legend()

# 图2:测试数据和预测结果
plt.subplot(222)
plt.scatter(X_test, y_test, color='green', label='True', alpha=0.6)
plt.scatter(X_test, y_test_pred, color='orange', label='Predicted', alpha=0.6)
plt.title("Testing Data vs Predictions")
plt.legend()

# 图3:训练误差
plt.subplot(223)
plt.plot(np.arange(len(y_train)), y_train - y_train_pred, color='purple')
plt.title("Training Residuals")

# 图4:测试误差
plt.subplot(224)
plt.plot(np.arange(len(y_test)), y_test - y_test_pred, color='brown')
plt.title("Testing Residuals")

plt.tight_layout()
plt.show()
  1. SimpleTree: 实现了一个简单的决策树,包括拟合和预测的功能。
  2. XGBoost: 使用梯度信息逐步修正误差,手动实现多棵树组合的模型。

  • 图1: 训练数据与预测值的对比。
  • 图2: 测试数据与预测值的对比。
  • 图3: 训练数据的残差图。
  • 图4: 测试数据的残差图。

所有上述原理,以及这段代码手动实现了XGBoost算法,并使用了虚拟数据集进行训练和预测,同时展示了模型的训练与测试性能。大家可以根据原理和实际的编码,进一步理解内容~

机器学习和人工智能AI
让我们一起期待 AI 带给我们的每一场变革!推送最新行业内最新最前沿人工智能技术!
 最新文章