一个强大分类算法模型,LightGBM!!

文摘   2024-10-09 16:38   北京  

大家好~

今儿和大家再聊聊LightGBM~

LightGBM(Light Gradient Boosting Machine)是一种高效的梯度提升框架,主要用于分类、回归和排序等任务。

很多同学不太理解其中的原理,咱们首先举一个简单的例子和大家解释一下~

比喻:盖房子

想象你和你的朋友们在一个小镇上盖房子。每个人都有自己的设计风格,可能是现代的、古典的,或者是乡村风格。每次你们盖一层楼(即建立一个模型),都会根据之前的设计(之前的模型)来改进。

1. 每层楼代表一个模型:你们每个人都设计了一层楼,但因为第一层可能有些不完美,所以在第二层时会根据第一层的缺点进行改进。

2. 集合的力量:最终,你们把这些楼层叠起来,形成了一座独特的房子。每一层都在上面一层的基础上进行了改进,这样整个房子就变得越来越完美。

核心概念

1. 梯度提升:LightGBM的核心思想是通过不断添加新的模型来纠正之前模型的错误。这就像在盖房子时不断改进设计一样。

2. 决策树:LightGBM使用决策树作为基本模型。决策树是根据特征(如房子的颜色、大小等)来进行分类或预测的工具。

3. 快速和高效:LightGBM特别快速,因为它使用了一种叫做“直方图”的方法来加速决策树的构建过程。就像你在画图时用直尺画直线,可以更快地得到结果。

4. 处理大数据:LightGBM非常适合处理大规模的数据集,这就像一个能够在很短的时间内完成大量房子建设的施工队。

举个例子

假设你正在预测一个房子的价格。你有很多特征,比如房子的面积、卧室数量、地段等。你可以使用LightGBM来建立一个模型,步骤如下:

1. 收集数据:收集房子的历史售价及其特征(面积、卧室数量等)。

2. 训练模型:用这些数据训练LightGBM模型。模型会通过多个决策树来学习如何根据特征预测房价。

3. 预测:当你有一个新房子(新的特征)时,模型会根据之前学到的知识来预测它的售价。

4. 模型评估:最后,你可以通过比较预测的价格和实际售价来评估模型的性能。

LightGBM就像是一个高效的施工团队,能够通过不断改进设计,快速而准确地建立出一个完美的房子。它通过梯度提升的方式,让每一层都能更好地学习和纠正错误,最终形成一个强大的预测模型。

原理和案例

下面,咱们从原理和案例和大家一起好好细化其中的逻辑~

1. 公式推理

LightGBM属于梯度提升树模型的一种实现,核心思想是每一轮训练时,新的决策树用于拟合上一个模型的残差(即当前模型的错误),从而逐步提升整个模型的准确率。为了简单明了,下面我简化了公式推理过程。

梯度提升的基本公式

假设我们有一个训练数据集 ,其中  是特征集, 是对应的标签。我们想要拟合一个模型  来最小化损失函数 。在每一轮  次迭代中,模型会不断更新,直到误差最小。

1. 目标函数:梯度提升的目标是通过最小化损失函数来拟合一个模型。

其中, 是某种损失函数,比如平方损失函数(用于回归)或交叉熵损失函数(用于分类)。

2. 残差更新:每一轮  次迭代中,我们构建一个新的弱学习器(决策树),来拟合当前模型的残差。残差是目标值与当前模型预测值之间的差值。

在每一轮,我们基于当前模型的梯度信息来生成新的决策树。

3. 模型更新:然后,我们更新模型的预测:

其中, 是学习率,控制每次更新的步长。

4. 直方图优化:LightGBM的一个关键改进是使用直方图加速决策树的构建过程。它将特征值划分成离散的区间(即直方图的bins),从而减少计算量。

2. 完整案例

接下来,我们将通过手写实现梯度提升树来实现LightGBM的简单版。这里使用虚拟的数据集来进行回归预测,包含多个特征。并且我们将绘制4个以上的数据分析图,包括特征重要性、损失函数的下降过程、残差分布等。

import numpy as np
import matplotlib.pyplot as plt

# 虚拟数据集生成
np.random.seed(42)
X = np.random.rand(1004)  # 100个样本,4个特征
true_coefficients = np.array([5-234])
y = np.dot(X, true_coefficients) + np.random.randn(100) * 0.5  # 添加一些噪声

# 梯度提升参数
n_estimators = 50  # 决策树的数量
learning_rate = 0.1  # 学习率
max_depth = 2  # 决策树的最大深度


# 定义损失函数(平方损失,用于回归)
def mse_loss(y_true, y_pred):
    return np.mean((y_true - y_pred) ** 2)


# 构建简单的决策树(用于回归)
class SimpleTree:
    def __init__(self, max_depth=2):
        self.max_depth = max_depth
        self.tree = None

    def fit(self, X, y):
        self.tree = self._build_tree(X, y, depth=0)

    def _build_tree(self, X, y, depth):
        if depth == self.max_depth or len(X) <= 2:
            return np.mean(y)

        # 寻找最佳的分割点
        best_feature, best_threshold, best_loss = NoneNone, float('inf')
        for feature in range(X.shape[1]):
            thresholds = np.unique(X[:, feature])
            for threshold in thresholds:
                left_indices = X[:, feature] <= threshold
                right_indices = X[:, feature] > threshold
                if sum(left_indices) == 0 or sum(right_indices) == 0:
                    continue

                left_loss = mse_loss(y[left_indices], np.mean(y[left_indices]))
                right_loss = mse_loss(y[right_indices], np.mean(y[right_indices]))
                total_loss = left_loss * sum(left_indices) + right_loss * sum(right_indices)

                if total_loss < best_loss:
                    best_loss = total_loss
                    best_feature = feature
                    best_threshold = threshold

        left_indices = X[:, best_feature] <= best_threshold
        right_indices = X[:, best_feature] > best_threshold

        return {
            'feature': best_feature,
            'threshold': best_threshold,
            'left': self._build_tree(X[left_indices], y[left_indices], depth + 1),
            'right': self._build_tree(X[right_indices], y[right_indices], depth + 1)
        }

    def predict_one(self, x):
        node = self.tree
        while isinstance(node, dict):
            if x[node['feature']] <= node['threshold']:
                node = node['left']
            else:
                node = node['right']
        return node

    def predict(self, X):
        return np.array([self.predict_one(x) for x in X])


# 梯度提升实现
class GradientBoosting:
    def __init__(self, n_estimators=50, learning_rate=0.1, max_depth=2):
        self.n_estimators = n_estimators
        self.learning_rate = learning_rate
        self.max_depth = max_depth
        self.trees = []
        self.losses = []

    def fit(self, X, y):
        # 初始预测值
        y_pred = np.mean(y)
        self.y_pred_history = [y_pred]

        for i in range(self.n_estimators):
            residuals = y - y_pred
            tree = SimpleTree(max_depth=self.max_depth)
            tree.fit(X, residuals)
            y_pred += self.learning_rate * tree.predict(X)
            self.trees.append(tree)
            self.losses.append(mse_loss(y, y_pred))
            self.y_pred_history.append(y_pred)

    def predict(self, X):
        y_pred = np.mean(y)  # 初始预测
        for tree in self.trees:
            y_pred += self.learning_rate * tree.predict(X)
        return y_pred


# 训练模型
model = GradientBoosting(n_estimators=n_estimators, learning_rate=learning_rate, max_depth=max_depth)
model.fit(X, y)

# 预测
y_pred = model.predict(X)

# 1. 损失函数的变化
plt.figure(figsize=(106))
plt.plot(model.losses, label="Loss over iterations", color="red")
plt.title("Loss Function Over Iterations", fontsize=16)
plt.xlabel("Iterations", fontsize=14)
plt.ylabel("MSE Loss", fontsize=14)
plt.legend()
plt.show()

# 2. 实际值与预测值的比较
plt.figure(figsize=(106))
plt.scatter(y, y_pred, label="Predictions vs Actuals", color="green")
plt.plot([min(y), max(y)], [min(y), max(y)], color="blue", linestyle="--", label="Perfect fit")
plt.title("Predictions vs Actuals", fontsize=16)
plt.xlabel("Actual Values", fontsize=14)
plt.ylabel("Predicted Values", fontsize=14)
plt.legend()
plt.show()

# 3. 残差分布
plt.figure(figsize=(106))
residuals = y - y_pred
plt.hist(residuals, bins=20, color="purple", edgecolor="black")
plt.title("Residual Distribution", fontsize=16)
plt.xlabel("Residuals", fontsize=14)
plt.ylabel("Frequency", fontsize=14)
plt.show()

# 4. 每个特征的预测贡献
# 计算每个特征的平均贡献
contributions = np.mean(X * true_coefficients, axis=0)
# 绘制每个特征的贡献图
plt.figure(figsize=(106))
plt.bar(range(15), contributions, color=["blue""red""green""orange"], edgecolor="black")
plt.title("Feature Contributions", fontsize=16)
plt.xlabel("Feature Index", fontsize=14)
plt.ylabel("Average Contribution to Prediction", fontsize=14)
plt.show()

1. 损失函数的下降过程:通过观察损失函数的变化,我们可以确定模型是否在不断改进。如果曲线平缓甚至上升,可能意味着模型出现了过拟合或收敛困难。

2. 预测值与实际值的散点图:理想情况下,所有点都应该落在 (y = x) 直线附近,表示预测值与实际值非常接近。如果散点大幅偏离,说明模型拟合还存在问题。

3. 残差分布:如果残差呈现正态分布且均值接近零,说明模型的误差比较随机,没有系统性偏差。如果残差分布偏斜,可能模型对某些特定输入有偏差。

4. 特征贡献图:帮助我们理解模型的解释能力,尤其是在实际应用中,这种分析有助于优化模型、选择特征,以及减少不重要特征对模型的影响。


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