☰
XGBoost回归预测实战:数据清洗、参数调优与可视化
2026/10/14 9:44:54 网站建设 项目流程

简介:基于XGBoost的数据回归预测代码是一套面向机器学习初学者和数据分析人员的完整回归项目,以经典的波士顿房价数据集为对象,演示梯度提升回归模型从数据导入到结果可视化的全流程。资源使用Excel文件存放训练与测试数据,脚本完成数据划分、XGBoost模型训练、测试集预测与评估,并计算均方根误差(RMSE)作为精度指标。同时生成三张可视化图:训练集真实值-预测值散点图、测试集真实值-预测值散点图,以及测试集逐样本的折线图,能直观反映模型拟合效果和预测偏差,适合教学演示、课程设计、毕业设计参考,也可作为回归预测任务的快速基线。压缩包共5个文件,大小仅191KB:两个xlsx数据文件、一个py脚本、一个png示意图和一个md说明文档,代码、数据、结果与说明齐备,结构简洁清晰。已有4297人学习下载,资源虽小但流程闭环完整,入门者可对照脚本逐步理解XGBoost回归建模的各个步骤,有基础者还能在此基础上扩展特征工程、参数调优或与其他回归算法对比等实验。

1. 这份XGBoost回归预测代码能帮你把什么跑通

XGBoost在结构化数据回归预测里属于那种“你还没把参数调明白,它已经把活干完”的模型。无论是销量预测、能耗预测还是设备寿命预估,给一张带目标列的表,训练几轮就能拿到能用的结果,这也是它一直是表格类竞赛和业务建模首选的原因。标题里的散点图和折线图不是附加功能:散点图能一眼看出预测值贴不贴近真实值,折线图能看清趋势跟随能力,两张图加起来,模型好坏不再是个黑匣子。这篇文章直接给一套可复现的Python代码,从数据准备讲到训练调参,再讲到两张图的画法,同时把回归预测里常见的翻车点逐个拆开。适合两类人:正在做结构化数据回归建模的工程师,以及想快速出结果、又要能汇报的分析师。

2. 建模前的数据准备:把脏数据挡在训练之前

2.1 回归预测对输入数据的三个基本要求

回归任务和分类任务对数据的要求有明显差别。分类模型偶尔扔掉几行异常标签关系不大,回归不一样:目标变量里的极端值会把损失函数直接拉爆,模型为了迁就那几个离群点,最后学出来的预测值全部挤在均值附近,散点图画出来就是一条横带。所以动手训练之前,先确认三件事:目标变量是连续数值,不能是整数编号;特征矩阵里不能有大量全空列;数据行数不需要多到夸张,几千条结构化数据就足够XGBoost训练出可用结果。

我一般在拿到数据后先跑一遍df.info()和df.describe(),看缺失量、看目标变量的分位数。这一步花五分钟,能省掉后面两个小时的调参时间。XGBoost确实自带缺失值处理能力,训练时遇到空值会自动选择最优分裂方向,但那是给“少量缺失”兜底的,不是让你把垃圾直接丢给它。特征缺失率超过四成的列,建议直接删掉;缺失率不高的列,用中位数填充比均值更稳,因为均值容易被极端值带偏。

2.2 pandas清洗:缺失值、重复行、异常值一步到位

import pandas as pd import numpy as np df = pd.read_csv("sales_data.csv") print("原始形状:", df.shape) print("各列缺失率:\n", df.isnull().mean()) # 缺失率超过40%的特征直接丢弃,保留信息量足够的列 high_missing = df.columns[df.isnull().mean() > 0.4] df = df.drop(columns=high_missing) # 剩余缺失值用中位数填充 for col in df.columns: if df[col].isnull().sum() > 0: df[col] = df[col].fillna(df[col].median()) # 完全重复的行删除 df = df.drop_duplicates().reset_index(drop=True) print("清洗后形状:", df.shape)

这段代码里有几个细节值得说。缺失率阈值 0.4 不是硬标准,如果你的样本量足够大,0.3 也可以,核心逻辑是“缺太多的列不要强留”。中位数填充对树模型的影响很小,因为树模型分裂时只看排序,不受单调变换影响。drop_duplicates针对的是完全一样的行,做回归预测时重复样本会无形中给某些区间加权,删掉更干净。

异常值处理我单独做在目标变量上,不走标准化那套。常见做法是取 0.01 和 0.99 分位数做裁剪,把极端值压回正常范围,避免 RMSE 被几个离群样本主导。

lower, upper = df["target"].quantile([0.01, 0.99]) df["target"] = df["target"].clip(lower, upper)

注意一点:分位数统计必须只用训练集算,不能拿全量数据算完再切分,否则测试集的信息在训练前就泄露出去了。这是新手最容易忽略的边界。

2.3 训练集/测试集切分:带时间顺序的数据禁止随机切

from sklearn.model_selection import train_test_split # 如果数据里带时间字段,先按时间排序再做切分 if "date" in df.columns: df = df.sort_values("date").reset_index(drop=True) train_size = int(len(df) * 0.8) train = df.iloc[:train_size] test = df.iloc[train_size:] X_train = train.drop(columns=["target", "date"]) y_train = train["target"] X_test = test.drop(columns=["target", "date"]) y_test = test["target"] print(f"训练集: {X_train.shape}, 测试集: {X_test.shape}")

train_test_split默认是随机切分,这个函数在回归预测里恰恰是最大的坑。销量、能耗、设备寿命这类数据都带时间属性,当月的样本和上月的样本高度相关,随机切会把未来的数据混进训练集,模型等于提前看到了答案,测试集评估出来的 R2 虚高,上线后立刻现原形。

我的习惯是:只要表里有时间字段,一律按时间顺序切分。切完再看测试集的折线图走势,你会发现测试集的真实值往往延续训练集的趋势,这正是时间序列回归预测里最典型的形态。

2.4 数值型特征的尺度问题:树模型对量纲不敏感

特征列里有“用户数几百万”和“折扣率零点几”这种量纲差距巨大的列时,不需要做标准化。XGBoost 是树模型,分裂点只看特征的相对顺序,不做距离计算,所以 MinMaxScaler 和 StandardScaler 在这里都不是必须的。很多从神经网络转过来的开发者会习惯性先标准化,做了也不报错,但纯属多余,还会让特征含义变得不可解释。

真正需要处理的是类别特征。顺序型类别用OrdinalEncoder,无序类别数据量不大时直接用pd.get_dummies展开。

df = pd.get_dummies(df, columns=["category"], drop_first=True)

drop_first=True避免产生完全共线的哑变量列,树模型虽然不太在意共线性,但特征列少了,训练速度和可解释性都会好一点。如果类别取值特别多,比如几十上百个城市名,先按频次分组再编码会更稳妥。

3. 训练XGBoost回归模型:从最小代码到参数调优

3.1 先确认目标函数和评估指标

XGBoost 回归最常用的是平方损失,对应参数objective="reg:squarederror"。在 XGBoost 1.6 之后的版本里,不写这个值也会自动帮你选回归目标,但显式写出来有利于自己和同事看清楚模型在优化什么。

评估指标我习惯看三个:RMSE、MAE 和 R2。RMSE 对大误差敏感,适合需要惩罚严重偏差的场景;MAE 更稳,不容易被离群点带偏;R2 用于向非技术背景的人汇报。三个指标一起打印,模型好坏基本心里有数。

from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score y_pred = model.predict(X_test) rmse = mean_squared_error(y_test, y_pred, squared=False) mae = mean_absolute_error(y_test, y_pred) r2 = r2_score(y_test, y_pred) print(f"RMSE: {rmse:.4f}, MAE: {mae:.4f}, R2: {r2:.4f}")

3.2 最小可运行的训练代码

from xgboost import XGBRegressor model = XGBRegressor( n_estimators=300, learning_rate=0.05, max_depth=5, subsample=0.8, colsample_bytree=0.8, random_state=42 ) model.fit(X_train, y_train) y_pred = model.predict(X_test)

这套参数是我在绝大多数表格回归任务上的起点。n_estimators=300是基础轮数,配合learning_rate=0.05,模型既能学得动又不会一步迈太大;max_depth=5控制单棵树的深度,深度越大越容易记住噪声;subsample=0.8每轮随机抽 80% 样本训练,colsample_bytree=0.8每轮随机抽 80% 特征参与分裂,这两个就是给模型加随机性,效果等同正则化。random_state=42固定住随机种子,保证实验结果可复现,这个习惯在调参时特别重要。

3.3 三个决定模型上限的必调参数

第一组是n_estimators和learning_rate的搭配。模型效果真正拉开差距的地方在这里:learning_rate调小到 0.03 左右,n_estimators成倍往上加,精度通常会提升,但训练时间也线性增长。我的做法是先定一个偏小的学习率,再靠早停决定到底训练多少轮。

model = XGBRegressor( n_estimators=1000, learning_rate=0.03, max_depth=5, subsample=0.8, colsample_bytree=0.8, random_state=42 ) model.fit( X_train, y_train, eval_set=[(X_test, y_test)], verbose=False )

注意 XGBoost 的新版本里early_stopping_rounds已经移到fit()里传,放进构造函数会告警。上面的写法用eval_set传入测试集,模型每训练一轮就在测试集上算一次 RMSE,连续 50 轮没有下降就自动停止,取最优轮数对应的模型,这个机制能有效防止训练轮数过多导致过拟合。

第二组是max_depth。深度偏大拟合能力强,但泛化能力下降;偏小又欠拟合。表格数据通常 3 到 6 够用。第三组是采样率和列采样率,两者都在 0.6 到 0.9 之间取值,配合reg_alpha和reg_lambda做 L1/L2 正则。我把常用参数的取值范围整理成了一张表,方便快速对照调整。

参数作用常见范围说明
n_estimators提升轮数100 ~ 1000+配合 early_stopping 使用
learning_rate每轮步长0.01 ~ 0.1越小越稳,轮数要相应增加
max_depth单棵树深度3 ~ 6深度过大会过拟合
subsample样本采样率0.6 ~ 0.9每轮随机抽部分样本
colsample_bytree特征采样率0.6 ~ 0.9每轮随机抽部分特征
reg_alphaL1 正则0 ~ 1让权重稀疏
reg_lambdaL2 正则1 ~ 10默认 1,放大可抑制过拟合

参数调优不建议手动瞎试,先用上面这套基础参数跑通,再用网格搜索或者 Optuna 在范围里找最优组合。搜索前把n_estimators固定在一个较大的值,让早停决定轮数,这样搜索空间少一个维度,效率高很多。

4. 散点图与折线图:把预测结果变成能汇报的图

4.1 散点图:真实值与预测值的贴合度

import matplotlib.pyplot as plt plt.figure(figsize=(8, 8)) plt.scatter(y_test, y_pred, alpha=0.5, s=20) # 绘制对角线参考线,点越贴近这条线说明预测越准 min_val = min(y_test.min(), y_pred.min()) max_val = max(y_test.max(), y_pred.max()) plt.plot([min_val, max_val], [min_val, max_val], "r--", linewidth=1.5) plt.xlabel("真实值") plt.ylabel("预测值") plt.title("XGBoost回归预测:真实值 vs 预测值散点图") plt.tight_layout() plt.savefig("scatter.png", dpi=150) plt.show()

散点图判断的标准只有一个:点是否密集分布在对角线附近。点越贴近对角线,预测越准;如果点集整体在对角线下方,说明模型系统性低估;整体在上方则系统性高估。还有一个更隐蔽的看法是形状:如果低值段点在对角线上、高值段全部掉到线下方,说明模型对极端高值永远预测不足,这时要去查目标变量分布和特征里是否缺少相关信号。

alpha=0.5处理点重叠,样本量大时不用透明色会糊成一片。s=20控制点的大小,汇报用的图可以再调小一点。保存图片时dpi=150起步,投到 PPT 里不会糊。.tight_layout()能避免标签被裁掉,这是个常用的小习惯。

4.2 折线图:按样本顺序对比真实值与预测值的走势

plt.figure(figsize=(12, 5)) sample_n = 50 x = range(sample_n) plt.plot(x, y_test.iloc[:sample_n].values, label="真实值", marker="o", linewidth=1.5) plt.plot(x, y_pred[:sample_n], label="预测值", marker="x", linewidth=1.5) plt.xlabel("测试集样本序号") plt.ylabel("目标值") plt.title("XGBoost回归预测:真实值与预测值折线对比") plt.legend() plt.tight_layout() plt.savefig("line.png", dpi=150) plt.show()

折线图横坐标是测试集的样本序号,两条线分别代表每个样本上的真实值和预测值。全部样本画出来会很挤,我一般取前 50 个样本展示,既能看出趋势跟随能力,又不会密密麻麻看不清。

这里有个容易踩的索引坑:y_test如果是 pandas Series,切片后索引可能不是从 0 开始的连续整数,直接和y_pred一起画图时折线会错位。写法里用.values把 Series 转成 numpy 数组,强制按位置对齐,这一步必须做。marker="o"和marker="x"是给线条加样本点标记,真实值用实心圆点、预测值用叉号,黑白打印时也分得清。

4.3 中文乱码和画布风格两个小坑

plt.rcParams["font.sans-serif"] = ["SimHei"] plt.rcParams["axes.unicode_minus"] = False

中文标题一旦出现方块字,先看这两行。font.sans-serif指定中文字体,SimHei 在 Windows 上基本自带;macOS 可以换成"PingFang SC"。axes.unicode_minus必须设为False,否则坐标轴上的负号会显示成方块。

两张图画完后,我一般会顺手把两个图合并到一张画布上,左右排列,方便一次性打印汇报。用plt.subplot(1, 2, 1)和plt.subplot(1, 2, 2)即可。汇报时散点图负责说服别人“模型整体是准的”,折线图负责展示“趋势跟得住,没有明显滞后”,两张图缺一不可。

5. 避坑指南:XGBoost回归预测里最常翻车的5个现场

5.1 训练集R2接近满分,测试集上直接腰斩

现象:训练集上 R2 超过 0.95,测试集上只有 0.6 出头,散点图里点在对角线附近,但一换到测试集就发散。

原因:模型过拟合,把训练集里的噪声当成规律记了下来。常见诱因是n_estimators给得太大且没有配早停,树越加越多,最后几轮完全在背训练样本。另一个诱因是max_depth太大,单棵树太复杂。

解决:给fit()传eval_set=[(X_test, y_test)]和early_stopping_rounds=50,让模型自己判断什么时候停;同时把max_depth降到 5 以下。这两个改动加起来,泛化能力通常能迅速改善。

5.2 散点图呈现一条水平横带,预测值全挤在均值附近

现象:真实值在 10 到 1000 之间分布,预测值全部落在 300 到 400 之间,散点图几乎是一条横线,R2 甚至可能为负。

原因:目标变量有极端离群值,平方损失被少数样本拉爆,树模型为了让整体损失最小,选择输出一个“折中的常数”。这种情况多发生在销量、收入这类右偏分布的目标变量上。

解决:对目标变量做对数变换。训练前用np.log1p(y_train),预测后做np.expm1(model.predict(X_test))还原。注意在拆分前先统计分位数做裁剪,把真正的离群值压回来。

y_train_log = np.log1p(y_train) model.fit(X_train, y_train_log) y_pred = np.expm1(model.predict(X_test))

5.3 折线图里预测线比真实线平滑一大截,峰值全被削平

现象:折线图里真实值有剧烈的波峰波谷,预测值却是一条缓慢起伏的曲线,峰值处总差一大截。

原因:特征里没有“最近发生了什么”的信息。树模型只能根据喂给它的特征做预测,如果特征矩阵里只有日期、节假日、历史统计量这类静态信息,模型无法感知当前时刻的突变,自然预测不出尖峰。

解决:构造滞后特征(lag feature),把上一期、上一周的目标值作为特征加进去。

for lag in [1, 7, 14]: df[f"target_lag_{lag}"] = df["target"].shift(lag) df = df.dropna().reset_index(drop=True)

加了滞后特征之后,折线图上的尖峰跟随能力会明显改善。这个改动在销量预测和能耗预测里几乎每次都能生效,是回报最高的一步。

5.4 中文标题变成方块字

现象:图能正常生成,但标题、坐标轴中文全部显示成方块。

原因:Matplotlib 默认字体里没有中文字符,系统没有自动回退到中文字体。

解决:绘图前加两行rcParams设置。这属于环境配置问题,跟模型无关,但对汇报影响很大,图里出现方块字会被一眼看出不专业。

5.5 大样本训练时内存飙高,电脑直接卡死

现象:几百万行数据放进 XGBoost 训练,内存占用一路飙升,训练要等很久甚至直接卡死。

原因:默认的tree_method="exact"在每层分裂时都要扫描全部数据做精确搜索,数据量大时内存和耗时都吃不消。

解决:显式指定tree_method="hist",用直方图近似算法,训练速度和内存占用都会大幅改善,精度损失在大多数场景下可以忽略。这个参数在标题所述的常见表格数据规模下很少被注意到,但数据量一上来就是关键救场参数。

6. 进阶验证技巧:用特征重要性和交叉验证给模型做体检

6.1 用feature_importances_找出真正干活的特征

importance = model.feature_importances_ feature_names = X_train.columns ranked = sorted(zip(feature_names, importance), key=lambda x: x[1], reverse=True) for name, imp in ranked[:10]: print(f"{name}: {imp:.4f}")

回归预测跑通之后,第一件事不是急着调参,而是先看特征重要性排名。我做过一个库存预测项目,特征重要性排名前三的是一个几乎不相关的静态字段,真正有业务含义的字段排在后面,查下来发现数据采集端某字段在某个时间点换成新口径,导致特征分布整体漂移。特征重要性不是调参工具,是数据质量体检工具。排名里重要性为 0 的字段,要么是噪声,要么该考虑删掉;排名靠前的字段,值得花时间验证它的采集逻辑是否稳定。

6.2 用交叉验证代替单次划分

from sklearn.model_selection import cross_val_score, KFold kf = KFold(n_splits=5, shuffle=True, random_state=42) scores = cross_val_score(model, X_train, y_train, cv=kf, scoring="r2") print(f"CV R2: {scores.mean():.4f}, 标准差: {scores.std():.4f}")

单次划分训练集/测试集只有一个随机结果,运气不好时评估值偏高或偏低。交叉验证跑 5 折取均值,能更稳地评估模型真实水平。非时间序列数据用KFold没问题,加shuffle=True打乱顺序;时间序列数据必须换成TimeSeriesSplit,保证每折训练数据都在验证数据之前,防止未来信息泄漏。

这套流程走完,你对这个模型的判断就有了依据:散点图看贴合度,折线图看趋势跟随,特征重要性看数据质量,交叉验证看泛化稳定性。我之前吃过一次亏,光看单次测试集 R2 0.92 就急着上线,结果跨月验证直接掉到 0.7,回头查就是没做交叉验证、没看特征稳定性,从那以后我每次建模都把上面这套验证流程固定下来当习惯。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询