XGBoost,一个神奇算法模型 !!

文摘   2024-11-18 14:26   北京  

今儿再来和大家聊聊XGBoost~

很重要,但是对于很多同学来说,理解起来还是有一定的困难。这篇文章咱们从一个简单案例讲起,然后给出核心原理和一个完整的案例~

什么是XGBoost?

XGBoost 是一种机器学习算法,专门用来解决分类和回归问题。它的全称是「eXtreme Gradient Boosting」,意思是“极限梯度提升”。这个名字听起来有点复杂,但其实它背后的概念不难。

我们可以把它想象成一个「团队合作」的游戏。假设有很多个「小模型」,这些小模型本身不一定特别聪明,但通过一次次合作,每个小模型都尽力弥补上一个小模型的不足,最后整个「团队」会变得非常强大。

怎么理解XGBoost的工作方式呢?

我们拿一个日常生活中的例子来说明。假设你是一位老师,想根据学生的历史考试成绩来预测他们下一次考试的分数。

第一步:先有一个基础猜测

一开始,你可能随便猜一个平均值(比如根据班上所有人的平均成绩来预测每个人的下一次成绩)。这是我们的第一个「小模型」,虽然很简单,但这是起点。

第二步:分析差距,改进预测

你发现有些学生考得比你预测的好,有些考得比你预测的差。你就把每个学生的误差记录下来,比如小明你猜他考80分,但他实际上考了85分,差了5分;小丽你猜她考75分,但她实际只考了70分,差了-5分。

第三步:用新的模型来修正误差

现在,你创建了第二个「小模型」,这个模型的任务是根据前面预测的误差来做改进。小明比我们猜的多了5分,这次你可以专门为他加5分,小丽比我们猜的少了5分,这次为她减掉5分。这样一来,你的预测变得更准确了。

继续改进:不断重复

但还没完!即便加上这些修正,可能还有一些误差。于是你继续再造一个新的「小模型」,进一步改进。每一步都在解决上一步预测的错误,直到误差越来越小。

为什么GBoost这么厉害?其实核心就是3点:

  1. 快速且高效:XGBoost 的设计非常注重效率,它用了一些聪明的技术来加快计算速度和减少内存的使用。你可以理解为它做事情既快又省钱。

  2. 防止过拟合:XGBoost 有一些内置的机制来防止它过度学习训练数据,确保它不仅在训练数据上表现好,在没见过的新数据上也能表现不错。

  3. 灵活性强:它不仅可以处理分类问题(比如预测明天是晴天还是下雨),还能处理回归问题(比如预测房价、股票价格)。它还可以处理各种数据类型,数字的、类别的、甚至缺失的数据,它都可以处理得很好。

简单来说,XGBoost 是一个「聪明的组合拳」。它用多个简单的「小模型」一起合作,每个模型都在不断改正前一个模型的错误,最终形成一个非常强大的预测模型。而且它做得又快又准确,是机器学习比赛中经常用的「王牌选手」。

你也可以把它想象成一个班级,班上每个同学(小模型)虽然有自己的弱点,但他们互相补台,最后整个班级的成绩非常优秀。XGBoost 就是通过一次次改进,使得最终的预测效果非常好。

下面,咱们从 XGBoost 的详细公式,以及一个完整的案例(不使用现成的包),并实现相关的可视化分析。

XGBoost 原理

1. 损失函数

XGBoost 的目标是通过最小化损失函数 ,来得到更好的模型。假设我们有  个样本,目标是预测 ,预测值为 ,XGBoost 使用了以下损失函数:

其中, 是损失函数(比如平方损失), 是正则化项,用来防止模型过拟合,通常形式为:

  •  是控制叶子数量的权重
  •  是控制叶子权重的正则化参数
  •  是树的叶子数, 是叶子权重

2. 二阶泰勒展开

为了最小化损失函数,XGBoost 采用了二阶泰勒展开近似,将目标函数展开为一阶和二阶项。假设当前已有  颗树,接下来我们添加一颗新树 ,此时的损失函数可以表示为:

使用泰勒展开到二阶近似,可以得到:

其中:

  •  是一阶导数(梯度)
  •  是二阶导数(Hessian)

3. 树的分裂

当我们构建树时,分裂的目标是最大化损失函数的下降值(也就是增益)。对于给定的分裂,增益公式为:

其中    是左右子节点中的样本集合, 是当前节点的样本集合。

4. 叶子节点的最佳权重

对于每一个叶子节点,最佳权重可以通过下式得到:

完整案例

我们用 Python 实现一个简单的 XGBoost 案例,使用虚拟数据集,并进行数据分析~

1. 数据生成与处理

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 设置随机种子
np.random.seed(42)

# 生成虚拟数据集
X = np.random.rand(10005)  # 1000个样本,5个特征
true_weights = np.array([2.5-1.70.51.2-0.9])
y = X.dot(true_weights) + np.random.randn(1000) * 0.5  # 线性关系加噪声

# 创建DataFrame以便后续处理
data = pd.DataFrame(X, columns=[f'feature_{i}' for i in range(16)])
data['target'] = y

# 打印数据集前5行
print(data.head())

2. 损失函数及梯度计算

# 损失函数(平方损失)
def squared_loss(y_true, y_pred):
    return 0.5 * np.mean((y_true - y_pred) ** 2)

# 梯度和Hessian计算
def gradient(y_true, y_pred):
    return -(y_true - y_pred)

def hessian(y_true, y_pred):
    return np.ones_like(y_true)

3. XGBoost 核心训练流程

class XGBoostFromScratch:
    def __init__(self, n_estimators=10, 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 = []
        self.losses = []  # 新增一个用于保存每轮损失的列表

    def fit(self, X, y):
        n_samples = X.shape[0]
        # 初始化预测值为0
        y_pred = np.zeros(n_samples)
        
        # 迭代生成树
        for _ in range(self.n_estimators):
            # 计算梯度和Hessian
            grad = gradient(y, y_pred)
            hess = hessian(y, y_pred)

            # 构建一颗树并拟合
            tree = self.build_tree(X, grad, hess, depth=0)
            self.trees.append(tree)
            
            # 更新预测值
            y_pred += self.learning_rate * self.predict_tree(tree, X)
            
            # 每轮迭代后计算当前的损失,并保存
            current_loss = squared_loss(y, y_pred)
            self.losses.append(current_loss)

    def build_tree(self, X, grad, hess, depth):
        # 这里我们简单处理为单层分裂的树,后续可扩展为多层
        n_samples, n_features = X.shape
        best_split = None
        best_gain = -float('inf')
        
        # 遍历所有特征,找到最佳分裂点
        for feature_idx in range(n_features):
            thresholds = np.unique(X[:, feature_idx])
            for threshold in thresholds:
                left_mask = X[:, feature_idx] <= threshold
                right_mask = ~left_mask
                
                if len(left_mask) == 0 or len(right_mask) == 0:
                    continue

                # 计算增益
                gain = self.compute_gain(grad, hess, left_mask, right_mask)
                if gain > best_gain:
                    best_gain = gain
                    best_split = (feature_idx, threshold)

        # 计算叶子节点权重
        if best_split is not None:
            feature_idx, threshold = best_split
            left_mask = X[:, feature_idx] <= threshold
            right_mask = ~left_mask
            left_weight = self.compute_leaf_weight(grad[left_mask], hess[left_mask])
            right_weight = self.compute_leaf_weight(grad[right_mask], hess[right_mask])

            return {"split_feature": feature_idx, "threshold": threshold,
                    "left_weight": left_weight, "right_weight": right_weight}
        else:
            return None

    def compute_gain(self, grad, hess, left_mask, right_mask):
        G_L, H_L = np.sum(grad[left_mask]), np.sum(hess[left_mask])
        G_R, H_R = np.sum(grad[right_mask]), np.sum(hess[right_mask])
        gain = 0.5 * (G_L**2 / (H_L + self.lambda_reg) + G_R**2 / (H_R + self.lambda_reg))
        return gain

    def compute_leaf_weight(self, grad, hess):
        return -np.sum(grad) / (np.sum(hess) + self.lambda_reg)

    def predict_tree(self, tree, X):
        predictions = np.zeros(X.shape[0])
        if tree is not None:
            feature_idx, threshold = tree["split_feature"], tree["threshold"]
            left_mask = X[:, feature_idx] <= threshold
            right_mask = ~left_mask
            predictions[left_mask] = tree["left_weight"]
            predictions[right_mask] = tree["right_weight"]
        return predictions

    def predict(self, X):
        y_pred = np.zeros(X.shape[0])
        for tree in self.trees:
            y_pred += self.learning_rate * self.predict_tree(tree, X)
        return y_pred

4. 训练与可视化分析

# 训练模型
model = XGBoostFromScratch(n_estimators=10, learning_rate=0.1)
model.fit(X, y)

# 预测结果
y_pred = model.predict(X)

# 绘制数据分析图形
fig, axes = plt.subplots(22, figsize=(1210))

# 图1:实际值与预测值对比
axes[00].scatter(y, y_pred, color='blue')
axes[00].set_title("Actual vs Predicted")
axes[00].set_xlabel("Actual")
axes[00].set_ylabel("Predicted")

# 图2:误差分布
error = y - y_pred
axes[01].hist(error, bins=20, color='green')
axes[01].set_title("Error Distribution")

# 图3:特征对目标值的相关性
for i in range(5):
    axes[10].scatter(data[f'feature_{i+1}'], data['target'], label=f'Feature {i+1}')
axes[10].set_title("Feature vs Target")
axes[10].legend()

# 图4:损失随迭代次数的变化
axes[11].plot(range(1, len(model.losses) + 1), model.losses, color='red')
axes[11].set_title("Loss vs Iterations")
axes[11].set_xlabel("Iterations")
axes[11].set_ylabel("Loss")

plt.tight_layout()
plt.show()

XGBoostFromScratch 类:实现了简单的 XGBoost 训练流程,包括梯度和 Hessian 计算、树的构建、叶子节点权重计算等。

  • 图1显示实际值与预测值的关系;
  • 图2展示误差的分布情况;
  • 图3展示各个特征与目标值的关系;
  • 图4展示了迭代过程中损失的变化。

这段代码实现了一个从零开始的 XGBoost,虽然简化了很多细节(例如只实现了单层树的分裂),给大家提供了一个较为完整的训练和预测流程。

最后

最近准备了16大块的内容,124个算法问题的总结,完整的机器学习小册,免费领取~

另外,今天给大家准备了关于「深度学习」的论文合集,往期核心论文汇总,分享给大家。

点击名片,回复「深度学习论文」即可~

如果你对类似于这样的文章感兴趣。

欢迎关注、点赞、转发~

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