XGBoost 实战:从房价预测到信用卡欺诈识别
本文从两个典型案例入手:房价预测(回归问题)和信用卡欺诈识别(二分类问题)。通过实际 Python 代码,介绍 XGBoost 的基本原理、建模流程、核心参数,以及如何选择真正符合业务目标的评价指标。
一、XGBoost 到底是什么?
XGBoost,全称 eXtreme Gradient Boosting,是一种基于梯度提升树的机器学习算法。
它的核心思想并不复杂:训练一系列决策树,让后面的树不断弥补前面模型的不足,最终将多棵树的预测结果组合起来。
例如,要预测一套房子的价格。
- 第一棵树根据面积、地段等信息,给出一个初步估计。
- 第二棵树重点学习第一棵树预测不准确的部分。
- 第三棵树继续修正当前模型的误差。
- 经过多轮迭代,模型逐渐形成更准确的预测结果。
严格来说,后续的树并非简单地拟合原始残差,而是利用损失函数的一阶梯度和二阶梯度信息,寻找能够降低整体目标函数的分裂与叶节点权重。
XGBoost 的优化目标可以概括为:
[
\text{目标函数}=\text{预测损失}+\text{模型复杂度惩罚}
]
预测损失衡量模型预测得有多差;复杂度惩罚则限制树的规模和叶节点权重,降低过拟合风险。
这也是 XGBoost 与简单地不断增加决策树有所不同的地方:它不仅追求拟合效果,还对模型复杂度进行显式约束。
XGBoost 适合解决什么问题?
| 问题类型 | 典型任务 | 常用模型 |
|---|---|---|
| 回归 | 房价、销售额、违约损失金额预测 | XGBRegressor |
| 二分类 | 违约识别、欺诈检测、客户流失预测 | XGBClassifier |
| 多分类 | 客户分群、风险等级分类 | XGBClassifier |
| 排序 | 搜索结果排序、推荐候选排序 | XGBoost 排序模型 |
| 生存分析 | 违约时间、设备失效时间预测 | XGBoost AFT 等目标函数 |
它的优势主要在于:
- 擅长表格数据。 数值特征、经过编码的类别特征以及非线性变量关系都可以纳入模型。
- 能够捕捉特征交互。 例如,贷款期限本身未必能决定违约风险,但它可能与客户收入、负债率共同影响风险。
- 支持正则化。 可以通过限制树的复杂度、惩罚叶节点权重等方式控制过拟合。
- 计算效率较高。 支持直方图算法、多线程,以及多种分布式训练方案。
- 支持缺失值处理。 树模型可以学习缺失值在分裂时的默认方向,但这不意味着所有数据质量问题都可以忽略。
需要注意,XGBoost 并不是所有任务的最佳选择。对于原始图像、音频或复杂文本等非结构化数据,深度学习模型通常更合适。
二、案例一:使用 XGBoost 预测房价
1. 业务背景
第一个案例参考 Kaggle 的经典竞赛 House Prices: Advanced Regression Techniques。
数据集包含美国艾姆斯市的住宅销售记录,每套房屋有多种属性,例如:
OverallQual:房屋整体质量评分。GrLivArea:地上居住面积。GarageCars:车库可容纳的车辆数量。YearBuilt:建造年份。Neighborhood:所在社区。SalePrice:实际销售价格,即需要预测的目标变量。
这属于典型的监督学习回归问题。
输入是房屋属性 (X),输出是预测价格 (\hat y)。模型通过历史样本学习房屋属性与销售价格之间的关系,再对新房屋进行预测。
2. 安装依赖与准备数据
安装所需的 Python 库:
pip install xgboost pandas numpy scikit-learn
去 Kaggle 的 House Prices 竞赛页面下载 train.csv,放在代码运行目录下。
下面的代码会自动处理数值特征、类别特征和缺失值,并划分训练集与验证集。
3. 完整实践代码
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.metrics import mean_absolute_error
from sklearn.metrics import mean_squared_error, r2_score
from xgboost import XGBRegressor
# 1. 读取数据
df = pd.read_csv("train.csv")
X = df.drop(columns=["SalePrice"])
y = df["SalePrice"]
# 2. 划分训练集和验证集
X_train, X_valid, y_train, y_valid = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 3. 区分数值特征和类别特征
num_cols = X.select_dtypes(include=np.number).columns
cat_cols = X.select_dtypes(exclude=np.number).columns
# 4. 定义预处理流程
numeric_pipe = SimpleImputer(strategy="median")
categorical_pipe = Pipeline([
("imputer", SimpleImputer(strategy="most_frequent")),
("onehot", OneHotEncoder(handle_unknown="ignore"))
])
preprocessor = ColumnTransformer([
("num", numeric_pipe, num_cols),
("cat", categorical_pipe, cat_cols)
])
# 5. 构建 XGBoost 回归模型
model = XGBRegressor(
n_estimators=700,
max_depth=4,
learning_rate=0.03,
subsample=0.8,
colsample_bytree=0.8,
reg_lambda=5,
objective="reg:squarederror",
tree_method="hist",
n_jobs=-1,
random_state=42
)
pipeline = Pipeline([
("prep", preprocessor),
("model", model)
])
# 6. 对房价取 log1p,降低目标变量偏态的影响
pipeline.fit(X_train, np.log1p(y_train))
# 7. 预测并还原到原始价格尺度
pred_log = pipeline.predict(X_valid)
pred = np.expm1(pred_log)
# 8. 评价模型
mae = mean_absolute_error(y_valid, pred)
rmse = np.sqrt(mean_squared_error(y_valid, pred))
r2 = r2_score(y_valid, pred)
print(f"MAE: {mae:.2f}")
print(f"RMSE: {rmse:.2f}")
print(f"R²: {r2:.4f}")
# 9. 查看重要特征
feature_names = pipeline.named_steps[
"prep"
].get_feature_names_out()
importance = pd.Series(
pipeline.named_steps["model"].feature_importances_,
index=feature_names
).sort_values(ascending=False)
print("\n重要特征 Top 15:")
print(importance.head(15))
4. 这段代码的关键是什么?
第一,为什么要进行 One-Hot 编码?
Neighborhood 等类别变量并不是具有天然数值大小的变量。如果直接将社区编码成 1、2、3,模型可能错误地利用这些数字之间的大小关系。
One-Hot 编码会将不同类别转换为独立的指示变量。例如:
Neighborhood
↓
Neighborhood_A = 1 或 0
Neighborhood_B = 1 或 0
Neighborhood_C = 1 或 0
这使模型能够学习不同类别对应的预测差异。
第二,为什么要对房价取对数?
房价往往呈现右偏分布,少数高价房屋可能对平方误差产生很大影响。
使用 log1p 对目标变量进行变换,可以压缩极端价格之间的数值差距。预测后使用 expm1 还原,便于直接用美元等原始价格单位计算误差。
这是一种可选的建模策略,不是 XGBoost 的硬性要求;最终仍应通过验证集判断它是否有效。
第三,如何评价模型?
| 指标 | 含义 | 解读 |
|---|---|---|
| MAE | 平均绝对误差 | 预测价格平均偏离实际价格多少 |
| RMSE | 均方根误差 | 对较大的预测误差给予更高惩罚 |
| R² | 决定系数 | 衡量模型相对均值基准解释目标变量变化的能力 |
MAE 和 RMSE 越低通常越好,R² 越高通常越好。
例如,若 MAE 为 15,000,意味着验证集上的平均绝对预测误差约为 15,000 个货币单位。这里仅作指标解释,并非上述代码的实际运行结果。
5. 如何理解特征重要性?
代码最后会输出模型认为较重要的特征。
如果 OverallQual、GrLivArea 或 Neighborhood 排名靠前,说明这些变量对当前模型的预测贡献较大。
但必须区分两件事:
- 预测重要性: 该变量对当前模型的预测有多大贡献。
- 因果效应: 改变该变量是否会导致房价发生变化。
特征重要性只能直接支持前者,不能证明后者。对于高度相关的变量,重要性还可能被分散。
本案例的 GitHub 实践代码可参考:
https://github.com/AloneMask001/Ames-Housing-Price-Prediction
三、案例二:使用 XGBoost 识别信用卡欺诈
如果说房价预测展示了 XGBoost 如何解决回归问题,那么信用卡欺诈检测则展示了它在不平衡二分类问题中的应用。
1. 业务背景
参考经典的 Credit Card Fraud Detection 数据集。该数据集包含约 28.5 万笔交易,其中欺诈交易只有 492 笔,占比约 0.17%。
每条交易记录包含交易金额、时间,以及经过匿名化处理的特征 V1 至 V28。目标变量为:
Class = 0:正常交易。Class = 1:欺诈交易。
模型需要根据交易特征,估计一笔交易属于欺诈交易的概率。
这个问题的难点不在于普通分类,而在于正负样本极度不平衡,以及漏报和误报的业务成本不同。
假如模型把所有交易都判断为正常交易,准确率仍可能达到约 99.83%,但欺诈识别能力实际上为零。
因此,不能仅仅追求准确率。
2. 完整实践代码
将下载的数据集保存为 creditcard.csv,然后运行以下代码。
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import (
average_precision_score,
precision_recall_curve,
classification_report,
confusion_matrix
)
from xgboost import XGBClassifier
# 1. 读取数据
df = pd.read_csv("creditcard.csv")
X = df.drop(columns=["Class"])
y = df["Class"]
# 2. 划分训练集、验证集和测试集
X_train_valid, X_test, y_train_valid, y_test = (
train_test_split(
X, y, test_size=0.2,
stratify=y, random_state=42
)
)
X_train, X_valid, y_train, y_valid = (
train_test_split(
X_train_valid, y_train_valid,
test_size=0.25,
stratify=y_train_valid,
random_state=42
)
)
# 3. 计算类别不平衡权重
neg = (y_train == 0).sum()
pos = (y_train == 1).sum()
scale_pos_weight = neg / pos
# 4. 建立分类模型
model = XGBClassifier(
n_estimators=500,
max_depth=4,
learning_rate=0.05,
subsample=0.8,
colsample_bytree=0.8,
reg_lambda=5,
scale_pos_weight=scale_pos_weight,
eval_metric="aucpr",
tree_method="hist",
n_jobs=-1,
random_state=42
)
model.fit(X_train, y_train)
# 5. 在验证集上选择分类阈值
valid_prob = model.predict_proba(X_valid)[:, 1]
precision, recall, thresholds = precision_recall_curve(
y_valid, valid_prob
)
# 选择验证集 F1 最大的阈值
f1 = (
2 * precision[:-1] * recall[:-1]
/ (precision[:-1] + recall[:-1] + 1e-12)
)
best_threshold = thresholds[np.argmax(f1)]
# 6. 在独立测试集上评估
test_prob = model.predict_proba(X_test)[:, 1]
test_pred = (test_prob >= best_threshold).astype(int)
print("验证集选定阈值:", best_threshold)
print(
"测试集 Average Precision:",
average_precision_score(y_test, test_prob)
)
print("\n分类报告:")
print(classification_report(
y_test, test_pred,
digits=4, zero_division=0
))
print("混淆矩阵:")
print(confusion_matrix(y_test, test_pred))
3. 重点理解三个问题
第一,为什么要使用 scale_pos_weight?
正常交易远多于欺诈交易。如果不进行任何调整,模型可能更关注数量占绝大多数的正常交易。
scale_pos_weight 用于提高正类样本在目标函数中的相对权重。常见初始设置是:
[
w=\frac{N_{\text{负类}}}{N_{\text{正类}}}
]
这样可以增强模型对欺诈样本的关注。
但这个比值只是起点。权重过高也可能增加误报,因此实际项目中应结合验证集、业务成本和模型表现进行调整。
第二,为什么要单独划分验证集和测试集?
训练集用于拟合模型;验证集用于选择分类阈值和调整模型;测试集则用于评估最终方案的泛化能力。
如果反复根据测试集结果修改阈值,测试集就不再是独立评估依据,最终表现容易产生乐观偏差。
对于真实交易场景,最好进一步采用按时间划分的数据集:使用较早的交易训练模型,再用后续交易验证和测试,避免随机切分掩盖时间漂移。
第三,为什么不直接使用 0.5 作为分类阈值?
predict_proba 输出的是模型估计的正类概率,0.5 只是常见的默认分类阈值,并不是业务上的最佳阈值。
例如:
- 阈值较低:更多交易会被标记为可疑,通常有利于减少漏报,但可能增加误报。
- 阈值较高:触发告警的交易减少,但可能漏掉更多欺诈交易。
代码使用验证集上的 F1 分数选择阈值,是一个便于教学的起点。
但在实际风控中,F1 最大不一定意味着业务收益最大。如果漏掉一笔大额欺诈的损失远高于人工审核一笔正常交易的成本,就应当根据损失函数、审核能力和风险容忍度确定阈值,而不是机械地最大化 F1。
4. 如何看懂评价结果?
本案例重点关注以下指标:
| 指标 | 业务含义 |
|---|---|
| Precision(精确率) | 被模型判定为欺诈的交易中,真正欺诈的比例 |
| Recall(召回率) | 全部真实欺诈交易中,被模型识别出来的比例 |
| F1 | Precision 与 Recall 的调和平均 |
| Average Precision(AP) | 综合评价不同分类阈值下精确率与召回率表现的指标 |
| 混淆矩阵 | 直观统计正常交易、欺诈交易的正确识别和误判数量 |
对于严重不平衡的数据,AP 通常比单纯的准确率更有参考价值。
不过,模型指标最终必须转化为业务指标。例如,每识别 100 笔可疑交易,需要人工审核多少笔?漏掉的欺诈交易造成多少损失?这些问题决定了模型在生产环境中是否真正有效。
本案例的 GitHub 实践代码可参考:
https://github.com/too-baa/credit-card-fraud-detection
四、掌握 XGBoost 时,哪些参数最重要?
初学阶段不必一次性研究所有参数。以下参数足以帮助你理解大部分实际建模过程。
| 参数 | 作用 | 调整方向 |
|---|---|---|
n_estimators |
树的数量 | 树太少可能欠拟合,太多可能过拟合且增加训练成本 |
learning_rate |
每棵树的学习步长 | 越小通常需要更多树,训练也可能更慢 |
max_depth |
单棵树的最大深度 | 深度越大,模型越复杂,也越容易过拟合 |
min_child_weight |
限制子节点所需的最小二阶梯度权重 | 增大通常会让模型更加保守 |
subsample |
每棵树使用的训练样本比例 | 适度采样有助于降低过拟合 |
colsample_bytree |
每棵树使用的特征比例 | 减少特征采样比例有时能提升泛化能力 |
reg_alpha |
L1 正则化系数 | 增大通常会加强对叶节点权重的约束 |
reg_lambda |
L2 正则化系数 | 增大通常会让模型更保守 |
scale_pos_weight |
正类相对权重 | 用于处理不平衡分类,需结合验证集调整 |
一个实用的调参顺序是:
- 先建立简单基线,确认数据切分和评价指标没有问题。
- 调整
max_depth、min_child_weight,控制单棵树的复杂度。 - 调整
learning_rate和n_estimators,平衡训练轮数与学习步长。 - 尝试
subsample、colsample_bytree和正则化参数,改善泛化能力。 - 使用交叉验证或早停机制,判断增加树的数量是否仍然带来有效收益。
不建议一开始就对十几个参数进行大规模网格搜索。先搞清楚模型在哪些数据上表现不好,再决定需要调整什么,通常更有效。
五、从两个案例中总结一套建模流程
无论预测房价,还是识别欺诈交易,XGBoost 的基本工作流程都可以概括为:
明确业务目标 → 整理数据 → 划分训练、验证和测试集 → 建立基线 → 训练 XGBoost → 选择指标与阈值 → 评估泛化能力 → 部署与监控。
其中最容易被忽略的,恰恰不是算法本身,而是数据与评价设计。
房价预测需要防止高价异常样本影响模型,合理处理类别变量,并选择符合实际业务含义的误差指标。
欺诈识别需要处理严重的类别不平衡,关注漏报与误报的权衡,并确保阈值选择没有使用测试集信息。
在金融风险、信用评分或违约损失预测等领域,还需要进一步考虑样本选择偏差、时间漂移、模型校准、解释性和模型验证要求。XGBoost 可以作为强有力的预测工具,但不能代替完整的风险管理流程。
最后记住一点:XGBoost 的价值不在于把树的数量堆得足够多,而在于通过合理的数据处理、目标函数、正则化和验证方法,让模型学到可以泛化到新样本的规律。 先把这两个案例跑通,再深入学习特征工程、早停、交叉验证和模型解释,会更容易建立完整的技术体系。
- 点赞
- 收藏
- 关注作者
评论(0)