突破GBDT算法,使用GBDT进行预测 !!

文摘   2024-10-22 15:36   北京  

哈喽,我是cos大壮!~

今儿和大家聊聊关于GBDT算法模型的一个案例:房价预测。

本身房价预测是一个非常经典的回归问题,在金融、房地产市场中有广泛应用。机器学习中的梯度提升决策树(Gradient Boosting Decision Trees,GBDT)被认为是解决该类问题的强大工具之一,尤其擅长处理非线性关系和特征之间的复杂交互。

今天就使用GBDT模型通过构建虚拟数据集进行房价预测,完整介绍该模型的原理、算法、代码实现,并对结果进行可视化分析。

老规矩如果大家伙觉得近期文章还不错!欢迎大家点个赞、转个发,文末赠送《机器学习学习小册》

文末可取本文PDF版本~

GBDT原理

GBDT是一种集成学习方法,通过多个弱学习器(通常是决策树)集成来提高预测的准确性。GBDT的核心思想是通过逐步修正模型的预测误差来优化损失函数。具体来说,GBDT的每棵树都根据前一轮的残差进行训练,从而一步步提升模型的性能。

GBDT的基本思想可以通过以下步骤进行概述:

1. 初始化模型

假设我们有一个目标函数 ,初始化时我们用一个常数值模型来预测目标值。

其中, 是损失函数,通常对于回归问题使用的是均方误差(MSE):

初始化模型的常数项通常是目标值的均值。

2. 迭代训练

在每一步迭代中,模型尝试拟合前一步的残差(即误差)。假设当前的模型为 ,模型会生成一个新的决策树来拟合残差。

然后通过学习器(例如决策树)拟合这个残差,更新模型:

其中, 是学习率,控制每次更新的步幅大小, 是第 m 棵决策树。

3. 最终模型

经过 M 次迭代后,模型会收敛到一个能够较好地拟合数据的函数。

完整案例

为演示GBDT的房价预测模型,这里我们构建一个虚拟数据集。

数据集包含以下特征:

  • 面积(Area):房子的面积,单位是平方英尺
  • 卧室数(Bedrooms):房间数量
  • 距市中心距离(Distance_to_city_center):房子距离市中心的距离,单位是公里
  • 房龄(House_age):房子的年龄,单位是年

房价(Price)作为预测目标,满足如下线性关系,并带有一些噪声:

其中, 是服从正态分布的噪声项。

import torch
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split

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

# 生成虚拟数据集
n_samples = 1000
area = np.random.uniform(5005000, n_samples)  # 面积 500 到 5000 平方英尺
bedrooms = np.random.randint(16, n_samples)   # 卧室数量 1 到 5
distance_to_city_center = np.random.uniform(120, n_samples)  # 距市中心距离 1 到 20 公里
house_age = np.random.uniform(050, n_samples) # 房龄 0 到 50 年

# 定义房价计算公式,添加噪声项
noise = np.random.normal(050000, n_samples)
price = 3000 * area + 50000 * bedrooms - 1000 * distance_to_city_center - 2000 * house_age + noise

# 构建 DataFrame
data = pd.DataFrame({
    'Area': area,
    'Bedrooms': bedrooms,
    'Distance_to_city_center': distance_to_city_center,
    'House_age': house_age,
    'Price': price
})

# 查看数据集
print(data.head())

数据生成后,我们将其分为训练集和测试集,以便后续训练模型和评估模型的泛化能力。

# 分割数据集为训练集和测试集
X = data[['Area''Bedrooms''Distance_to_city_center''House_age']]
y = data['Price']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

模型训练

使用pytorch实现一个GBDT模型。为简化起见,我们手动实现一个GBDT版本,重点展示模型的工作机制

虽然pytorch本身不是GBDT的常规实现工具,但我们将使用它来说明决策树的训练过程。

class GBDT(torch.nn.Module):
    def __init__(self, n_estimators, learning_rate, y_train_mean):
        super(GBDT, self).__init__()
        self.n_estimators = n_estimators
        self.learning_rate = learning_rate
        self.trees = []
        self.y_train_mean = y_train_mean  # 保存y_train的均值

    def forward(self, x):
        # 初始化预测为常数值(使用训练集的目标均值)
        pred = torch.ones(x.shape[0], dtype=torch.float32) * self.y_train_mean
        for tree in self.trees:
            pred += self.learning_rate * tree(x).squeeze()  # 使用 .squeeze() 来匹配形状
        return pred

    def fit(self, X_train, y_train):
        residuals = y_train
        for i in range(self.n_estimators):
            # 拟合残差
            tree = torch.nn.Linear(X_train.shape[1], 1)
            criterion = torch.nn.MSELoss()
            optimizer = torch.optim.Adam(tree.parameters(), lr=0.01)
            for epoch in range(100):  # 简单训练 100 个 epoch
                optimizer.zero_grad()
                output = tree(X_train)  # 这里可以不使用 .squeeze(),因为我们要使用形状为 [batch_size, 1]
                loss = criterion(output, residuals.unsqueeze(1))  # 调整 residuals 的形状
                loss.backward(retain_graph=True)  # 保留计算图
                optimizer.step()

            # 更新残差
            self.trees.append(tree)
            residuals = residuals - self.learning_rate * tree(X_train).squeeze()

    def predict(self, X):
        return self.forward(X)

# 计算 y_train 的均值
y_train_mean = torch.mean(torch.tensor(y_train.values, dtype=torch.float32))

# 实例化模型,设置学习率和弱学习器数量
n_estimators = 500
learning_rate = 0.1
model = GBDT(n_estimators, learning_rate, y_train_mean)

# 将数据转化为 PyTorch 张量
X_train_tensor = torch.tensor(X_train.values, dtype=torch.float32)
y_train_tensor = torch.tensor(y_train.values, dtype=torch.float32)

# 训练模型
model.fit(X_train_tensor, y_train_tensor)

# 预测结果
X_test_tensor = torch.tensor(X_test.values, dtype=torch.float32)
predictions = model.predict(X_test_tensor).detach().numpy()

# 打印一些预测结果
print(predictions[:5])

结果分析和可视化

接下来,我们使用Matplotlib绘制分析图表,以帮助解释GBDT模型的效果。

1. 特征与目标之间的关系散点图:展示各个特征与房价之间的关系。

2. 残差图:展示预测误差的分布情况,以便观察模型的偏差和方差。

3. 特征重要性图:展示每个特征对模型预测结果的重要性。

# 1. 特征与房价的关系
fig, axs = plt.subplots(22, figsize=(1010))

axs[00].scatter(data['Area'], data['Price'], alpha=0.5)
axs[00].set_title('Area vs Price')
axs[00].set_xlabel('Area')
axs[00].set_ylabel('Price')

axs[01].scatter(data['Bedrooms'], data['Price'], alpha=0.5)
axs[01].set_title('Bedrooms vs Price')
axs[01].set_xlabel('Bedrooms')
axs[01].set_ylabel('Price')

axs[10].scatter(data['Distance_to_city_center'], data['Price'], alpha=0.5)
axs[10].set_title('Distance to City Center vs Price')
axs[10].set_xlabel('Distance to City Center')
axs[10].set_ylabel('Price')

axs[11].scatter(data['House_age'], data['Price'], alpha=0.5)
axs[11].set_title('House Age vs Price')
axs[11].set_xlabel('House Age')
axs[11].set_ylabel('Price')

plt.tight_layout()
plt.show()

# 2. 残差图
residuals = y_test - predictions
plt.figure(figsize=(64))
plt.scatter(predictions, residuals, alpha=0.5)
plt.axhline(y=0, color='r', linestyle='--')
plt.title('Residual Plot')
plt.xlabel('Predicted Price')
plt.ylabel('Residuals')
plt.show()

# 3. 特征重要性图(使用树的权重或均方误差降低量作为特征重要性的近似)
# 简单用回归系数的绝对值作为近似
feature_importance = np.abs([tree.weight.item() for tree in model.trees])

plt.figure(figsize=(64))
plt.barh(X.columns, feature_importance)
plt.title('Feature Importance')
plt.xlabel('Importance')
plt.show()

下面,咱们分享一下呈现的数据分析图:

1. 特征与目标之间的关系散点图:通过各个特征与房价的散点图,我们可以直观地看到面积、卧室数量与房价呈正相关,距离市中心和房龄与房价呈负相关。

2. 残差图:残差图可以帮助我们查看模型的拟合效果。如果残差围绕零轴随机分布,说明模型的误差是随机的,拟合较好;若残差呈现某种系统性的模式,则模型可能存在偏差。

3. 特征重要性图:特征重要性展示了各个特征对模型预测结果的贡献程度。较大的值表明该特征对模型预测有更大的影响。

模型优化与调参

在GBDT模型中,几个关键的超参数对模型性能有显著影响:

  • 树的数量(n_estimators):增加树的数量通常会提高模型的表现,但过多的树可能会导致过拟合。
  • 学习率(learning_rate):学习率决定了每棵树对最终模型的贡献。较小的学习率可以获得更好的效果,但需要更多的树。
  • 最大深度(max_depth):树的深度控制了每棵树的复杂度,较大的深度可以拟合复杂的关系,但也更容易过拟合。

调参过程可以使用网格搜索(Grid Search)或随机搜索(Random Search)进行自动化搜索。

from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import GradientBoostingRegressor

# 定义超参数范围
param_grid = {
    'n_estimators': [50100200],
    'learning_rate': [0.010.050.1],
    'max_depth': [357],
}

# 实例化GBDT模型
gbdt = GradientBoostingRegressor()

# 网格搜索
grid_search = GridSearchCV(estimator=gbdt, param_grid=param_grid, cv=5, scoring='neg_mean_squared_error')
grid_search.fit(X_train, y_train)

# 最优超参数
print(f"Best parameters: {grid_search.best_params_}")

# 使用最优超参数训练模型
best_model = grid_search.best_estimator_
best_model.fit(X_train, y_train)

# 预测测试集
y_pred_best = best_model.predict(X_test)

# 计算测试集误差
test_mse = np.mean((y_test - y_pred_best) ** 2)
print(f"Test MSE with best parameters: {test_mse}")

最后

本文使用GBDT模型进行了房价预测的完整实现,并展示了模型的训练过程、超参数调优、结果分析与可视化。GBDT通过迭代地拟合残差,能够很好地处理非线性回归问题。实际应用中,通过合理的调参和优化,可以显著提高模型性能。

大家有问题可以直接在评论区留言即可~

喜欢本文的朋友可收藏、点赞、转发起来!

需要本文PDF的同学,扫码备注「案例汇总」即可~ 
关注本号,带来更多算法干货实例,提升工作学习效率!
最后,给大家准备了《机器学习学习小册》PDF版本16大块的内容,124个问题总结
100个超强算法模型,大家如果觉得有用,可以点击查看~

推荐阅读

原创、超强、精华合集
100个超强机器学习算法模型汇总
机器学习全路线
机器学习各个算法的优缺点
7大方面,30个最强数据集
6大部分,20 个机器学习算法全面汇总
铁汁,都到这了,别忘记点赞呀~


深夜努力写Python
Python、机器学习算法
 最新文章