首页 / 资讯中心 / 文章详情

北京二手房房价预测实战:从链家安居客数据采集到XGBoost建模与SHAP解释

北京二手房房价预测实战:从链家安居客数据采集到XGBoost建模与SHAP解释 ★ FEATURED ARTICLE
简介这是一份面向计算机相关专业学生的Python机器学习实战资源以房价预测为完整案例适用于课程设计、期末大作业与实战练习。项目曾获98分并获导师认可数据由爬虫从链家、安居客获取仅供学习研究。压缩包共26个文件约1.28MB包含15个py脚本、1个ipynb笔记本、2个csv数据集、1个html报告及4张jpg可视化图另有read、md、gitignore等说明文件覆盖数据采集、预处理、建模到评估的全流程。已有103人学习。读者可参照代码注释与使用说明掌握数据清洗、特征工程、模型选择、交叉验证与超参数调整等技能并借助NumPy、Pandas、Scikit-learn、Matplotlib完成回归分析与可视化理解均方误差等评估指标快速搭建可复现的房价预测项目。1. 从一份 98 分课设说起这套房价预测资源到底能跑出什么如果你正在为机器学习课程设计或者期末大作业发愁大概率会刷到各种「房价预测」的题目。但真正拿到手能跑通、能讲清楚、还能扛住导师追问的项目其实不多。这份资源是一套完整的北京二手房房价预测案例包含 Jupyter Notebook 主分析文件、爬虫源码、可视化脚本和导出的 HTML 报告数据来自链家和安居客两个平台。它不是那种只丢一个 CSV 让你自己猜的「半成品」而是从数据采集、清洗、特征工程到建模评估全流程都铺开了。适合谁计算机相关专业做课设的学生、刚入门机器学习想找一个完整项目练手的开发者以及需要一份可复现案例来对照自己流程的从业者。下面我按实际拆包的顺序把这份资源的技术细节和落地路径讲清楚。2. 拆开压缩包先看什么文件结构与数据管线的真实面貌拿到一个资源包我习惯先不急着跑代码而是把目录结构过一遍。这份资源的文件组织方式其实透露了作者的设计思路——它不是一个单文件脚本而是一条从爬虫到分析再到可视化的完整链路。理解这个结构后面调参和排错才不会迷路。2.1 核心文件清单与各自职责解压后你会看到这些内容北京二手房房价预测与分析.ipynb是主分析文件所有建模逻辑都在里面visuals.py是独立出来的可视化脚本说明作者把绘图逻辑做了模块化lianjia.csv和anjuke.csv是两个平台的原始数据spiders文件夹下放着lianjia_scrapy_crawl和anjuke_scrapy_crawl两套爬虫代码read文件夹可能是数据读取的辅助脚本三张 JPG 图片是分析结果的截图还有一个导出的 HTML 版本方便直接浏览。文件/文件夹类型作用北京二手房房价预测与分析.ipynbNotebook主流程清洗、特征、建模、评估visuals.pyPython 脚本封装可视化函数供 Notebook 调用lianjia.csv / anjuke.csv数据文件两个平台的原始二手房数据spiders/爬虫目录链家和安居客的采集代码read/辅助目录数据读取与预处理脚本北京二手房房价预测与分析.html导出报告Notebook 的静态 HTML 版本这个结构的好处是职责分离爬虫归爬虫分析归分析可视化单独抽出来。你改模型的时候不用碰爬虫代码换数据源的时候也不用动 Notebook 里的建模逻辑。常见做法是把所有东西塞一个.py里跑起来是快但一旦报错就得从头翻到尾这种模块化的组织在课设答辩时也更好讲清楚「你做了什么」。2.2 数据从哪来链家与安居客的字段差异两个 CSV 的字段并不完全一致。链家的数据通常包含小区名、户型、面积、朝向、楼层、建造年份、总价和单价安居客的字段命名习惯不同可能用「房屋总价」而不是「总价」面积单位也可能有差异。这意味着你在合并或对比两个数据源之前必须先做字段对齐。我一般会先用 pandas 快速看一眼两个表的列名和数据类型import pandas as pd # 分别读取两个平台的数据先看结构 lianjia pd.read_csv(lianjia.csv) anjuke pd.read_csv(anjuke.csv) # 查看列名、数据类型、缺失情况 print(链家数据形状:, lianjia.shape) print(安居客数据形状:, anjuke.shape) print(\n链家列名:, lianjia.columns.tolist()) print(\n安居客列名:, anjuke.columns.tolist()) print(\n链家缺失值:\n, lianjia.isnull().sum()) print(\n安居客缺失值:\n, anjuke.isnull().sum())这段代码不涉及任何建模但它是整个项目的地基。shape告诉你数据量够不够columns告诉你字段能不能直接对齐isnull().sum()告诉你哪些列需要填充或丢弃。很多人拿到数据直接df.dropna()一了百了结果发现训练集少了一半——这就是没先看缺失分布就动手的后果。参数方面pd.read_csv()默认用逗号分隔、UTF-8 编码。如果链家数据是 GBK 编码早期爬虫常见你会遇到UnicodeDecodeError这时候加encodinggbk或encodingutf-8-sig就能解决。另外注意anjuke.csv如果有 BOM 头用utf-8-sig读进来列名才不会带隐藏字符。2.3 爬虫代码的角色为什么它只是参考spiders文件夹里的两套爬虫代码用的是 Scrapy 框架。链家的页面结构相对规整分页逻辑清晰安居客的反爬策略更激进可能需要处理动态加载和请求头校验。但这份资源的核心价值不在爬虫本身——数据已经给你了爬虫代码更多是让你理解数据是怎么来的以及在答辩时能说清楚「数据采集环节我做了哪些工作」。如果你打算自己重新跑爬虫有几个现实问题要面对链家的列表页有翻页限制通常只能看到前 100 页安居客对同一 IP 的请求频率有阈值跑太快会被临时限制。常见做法是设置DOWNLOAD_DELAY参数控制请求间隔在settings.py里加上# Scrapy settings.py 中的关键配置 DOWNLOAD_DELAY 3 # 每次请求间隔3秒降低被限制的概率 RANDOMIZE_DOWNLOAD_DELAY True # 在0.5到1.5倍之间随机浮动 USER_AGENT Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... # 模拟浏览器DOWNLOAD_DELAY设为 3 秒意味着爬 1000 条数据大约需要 50 分钟这是速度和稳定之间的折中。RANDOMIZE_DOWNLOAD_DELAY让间隔不固定避免被识别出机器规律。但说实话如果你只是做课设直接用现成的 CSV 就够了爬虫代码理解逻辑即可不必花大量时间在采集上——导师看的是你的分析和建模能力不是你爬了多少条数据。3. 从原始 CSV 到特征矩阵数据清洗与特征工程的实操细节数据清洗和特征工程是房价预测项目里最耗时也最能拉开差距的环节。模型选得再花哨特征没处理好结果就是欠拟合或者过拟合。这一章我把 Notebook 里的关键步骤拆开讲包括缺失值处理、类别特征编码和衍生特征的构造。3.1 缺失值与异常值的处理策略北京二手房数据里缺失值主要集中在建造年份、楼层和朝向这几列。建造年份缺失可能是因为老小区没有明确记录楼层缺失可能是爬虫没抓到朝向缺失则可能是房源描述里没写。处理方式不能一刀切。import numpy as np # 建造年份用中位数填充同时标记是否缺失 df[建造年份_缺失] df[建造年份].isnull().astype(int) df[建造年份].fillna(df[建造年份].median(), inplaceTrue) # 楼层提取数字部分转为数值型 df[楼层数值] df[楼层].str.extract(r(\d)).astype(float) df[楼层数值].fillna(df[楼层数值].median(), inplaceTrue) # 面积过滤掉极端异常值 df df[(df[面积] 10) (df[面积] 1000)] # 总价去掉明显录入错误的记录 df df[df[总价] 0]这里有几个设计决策值得说。建造年份用中位数而不是均值是因为年份分布可能偏斜均值会被极端值拉偏。加一个「是否缺失」的标记列是让模型知道这个值原本是空的有时候「缺失」本身就是信息——比如某些高端小区就是不公开建造年份。面积过滤掉小于 10 平米和大于 1000 平米的记录是因为这些大概率是录入错误或者特殊物业类型留在训练集里会干扰模型。异常值检测还可以用 IQR 方法# 用四分位距检测总价异常值 Q1 df[总价].quantile(0.25) Q3 df[总价].quantile(0.75) IQR Q3 - Q1 lower_bound Q1 - 1.5 * IQR upper_bound Q3 1.5 * IQR # 查看异常值占比决定是否剔除 outliers df[(df[总价] lower_bound) | (df[总价] upper_bound)] print(f异常值占比: {len(outliers) / len(df) * 100:.2f}%)如果异常值占比在 5% 以内可以考虑剔除如果超过 10%就要谨慎了——可能是数据分布本身就这样强行剔除会丢失重要信息。我一般会先画个箱线图看看分布形态再决定。3.2 类别特征编码独热编码还是目标编码户型、朝向、装修情况这些类别特征不能直接喂给模型。最常见的做法是独热编码One-Hot Encoding但它有个问题如果某个特征的类别很多比如小区名可能有几千个独热编码会让特征维度爆炸。# 对低基数类别特征做独热编码 low_card_cols [朝向, 装修情况, 楼层类型] df pd.get_dummies(df, columnslow_card_cols, drop_firstTrue) # 对高基数特征如小区名用目标编码 # 目标编码用该类别对应的目标变量均值替换类别值 target_mean df.groupby(小区名)[单价].mean() df[小区名_目标编码] df[小区名].map(target_mean) df.drop(小区名, axis1, inplaceTrue)drop_firstTrue是为了避免虚拟变量陷阱——如果朝向有 4 个类别独热编码会产生 4 列但其中一列可以由其他三列推导出来造成多重共线性。目标编码则用小区对应的平均单价来替换小区名这样既保留了小区信息又不会让维度膨胀。但目标编码有个坑如果某个小区只有一两条记录它的均值就不可靠容易导致过拟合。常见做法是加平滑处理# 带平滑的目标编码 global_mean df[单价].mean() smoothing 10 # 平滑参数越大越保守 target_mean df.groupby(小区名)[单价].agg([mean, count]) target_mean[smoothed] ( (target_mean[mean] * target_mean[count] global_mean * smoothing) / (target_mean[count] smoothing) ) df[小区名_目标编码] df[小区名].map(target_mean[smoothed])平滑参数smoothing10的意思是如果一个小区只有 2 条记录它的编码值会更靠近全局均值如果有 100 条记录就更靠近自己的均值。这样既保留了信息又避免了小样本的噪声。3.3 衍生特征从面积和总价里挖出更多信号原始特征往往不够用衍生特征能显著提升模型表现。房价预测里最有效的衍生特征包括单价总价除以面积、房间数从户型字符串里提取、房龄当前年份减去建造年份、楼层占比当前楼层除以总楼层。# 单价最直接的价格信号 df[单价] df[总价] / df[面积] # 房间数从2室1厅中提取数字 df[房间数] df[户型].str.extract(r(\d)室).astype(float) df[厅数] df[户型].str.extract(r(\d)厅).astype(float) # 房龄假设当前年份为2024 df[房龄] 2024 - df[建造年份] # 楼层占比低楼层和高楼层的价格差异可能非线性 df[楼层占比] df[楼层数值] / df[总楼层] # 面积分段把连续面积离散化捕捉非线性关系 df[面积分段] pd.cut(df[面积], bins[0, 50, 90, 140, 300, 1000], labels[小户型, 刚需, 改善, 大平层, 豪宅]) df pd.get_dummies(df, columns[面积分段], drop_firstTrue)单价是最重要的衍生特征因为总价受面积影响太大单价才能反映地段和品质。房龄捕捉了新房和二手房的溢价差异。楼层占比比单纯的楼层数值更有意义——6 层楼的第 3 层和 30 层楼的第 3 层居住体验完全不同。面积分段则是把连续变量离散化让线性模型也能捕捉非线性关系。做完这些你可以用df.info()看一下最终的特征矩阵有多少列、有没有残留的缺失值。一般来说经过这一轮处理特征数会从原始的十几个扩展到三四十个训练集和测试集的划分建议用train_test_split按 8:2 切分随机种子固定为 42 保证可复现。4. 模型训练与评估线性回归、随机森林和 XGBoost 的对比特征准备好之后就进入建模环节。这份 Notebook 里大概率用了线性回归做基线然后上随机森林或梯度提升树做提升。我按实际跑下来的经验把三种模型的训练、调参和评估讲清楚。4.1 基线模型线性回归能告诉你什么线性回归不是用来拿高分的它的价值在于给你一个可解释的基线。如果线性回归的 R² 只有 0.5而随机森林到了 0.85说明特征和目标之间存在大量非线性关系如果两者差距不大说明线性模型已经够用了。from sklearn.linear_model import LinearRegression from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error, r2_score import numpy as np # 准备特征矩阵和目标变量 X df.drop([总价, 单价], axis1) # 注意单价不能作为特征否则数据泄露 y df[总价] # 划分训练集和测试集 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) # 训练线性回归 lr LinearRegression() lr.fit(X_train, y_train) # 预测与评估 y_pred_lr lr.predict(X_test) rmse_lr np.sqrt(mean_squared_error(y_test, y_pred_lr)) r2_lr r2_score(y_test, y_pred_lr) print(f线性回归 RMSE: {rmse_lr:.2f} 万元) print(f线性回归 R²: {r2_lr:.4f})这里有个关键点单价必须从特征里去掉。因为单价 总价 / 面积如果你把单价作为特征去预测总价模型会直接学到这个公式R² 会虚高到 0.99但这是数据泄露答辩时被问到就露馅了。random_state42是固定随机种子保证每次划分结果一致方便对比不同模型。线性回归的系数还能告诉你哪些特征影响最大# 查看特征系数 coef_df pd.DataFrame({ 特征: X.columns, 系数: lr.coef_ }).sort_values(系数, keyabs, ascendingFalse) print(coef_df.head(10))如果面积的系数是正的且绝对值最大说明面积是影响总价的最主要因素这符合直觉。如果某个特征的系数符号和常识相反比如房龄越大价格越高那就要检查是不是有多重共线性或者数据有问题。4.2 随机森林与 XGBoost调参的重点在哪随机森林和 XGBoost 是这类项目里最常用的两个模型。随机森林靠 bagging 降低方差XGBoost 靠 boosting 降低偏差。两者的调参重点不同。from sklearn.ensemble import RandomForestRegressor from xgboost import XGBRegressor # 随机森林重点调 n_estimators 和 max_depth rf RandomForestRegressor( n_estimators200, # 树的数量越多越稳定但计算越慢 max_depth15, # 树的最大深度控制过拟合 min_samples_split5, # 节点分裂所需最小样本数 min_samples_leaf2, # 叶节点最小样本数 random_state42, n_jobs-1 # 并行计算 ) rf.fit(X_train, y_train) y_pred_rf rf.predict(X_test) rmse_rf np.sqrt(mean_squared_error(y_test, y_pred_rf)) r2_rf r2_score(y_test, y_pred_rf) print(f随机森林 RMSE: {rmse_rf:.2f} 万元, R²: {r2_rf:.4f}) # XGBoost重点调 learning_rate 和 n_estimators xgb XGBRegressor( n_estimators300, learning_rate0.05, # 学习率越小需要越多树 max_depth6, # XGBoost 的树通常更浅 subsample0.8, # 行采样比例 colsample_bytree0.8, # 列采样比例 reg_alpha0.1, # L1 正则化 reg_lambda1.0, # L2 正则化 random_state42 ) xgb.fit(X_train, y_train) y_pred_xgb xgb.predict(X_test) rmse_xgb np.sqrt(mean_squared_error(y_test, y_pred_xgb)) r2_xgb r2_score(y_test, y_pred_xgb) print(fXGBoost RMSE: {rmse_xgb:.2f} 万元, R²: {r2_xgb:.4f})随机森林的n_estimators设到 200 通常够用再往上收益递减。max_depth15是防止每棵树长得太深导致过拟合。XGBoost 的learning_rate0.05配合n_estimators300是常见组合学习率低就需要更多树来补偿但泛化能力更好。subsample和colsample_bytree都是 0.8意思是每棵树随机用 80% 的样本和 80% 的特征这能增加模型的多样性降低过拟合风险。调参不要盲目网格搜索先用手动调参找到大致范围再用GridSearchCV精细搜索。比如先试max_depth在 5、10、15、20 上的表现找到拐点后再细化。4.3 评估指标RMSE、MAE 和 R² 各自说明什么RMSE均方根误差对大误差敏感适合你关心「最差能差多少」的场景。MAE平均绝对误差对所有误差一视同仁适合你关心「平均差多少」的场景。R² 告诉你模型解释了目标变量多少比例的方差。from sklearn.metrics import mean_absolute_error # 对比三个模型的指标 models {线性回归: y_pred_lr, 随机森林: y_pred_rf, XGBoost: y_pred_xgb} for name, pred in models.items(): rmse np.sqrt(mean_squared_error(y_test, pred)) mae mean_absolute_error(y_test, pred) r2 r2_score(y_test, pred) print(f{name}: RMSE{rmse:.2f}, MAE{mae:.2f}, R²{r2:.4f})如果 XGBoost 的 RMSE 是 45 万意思是预测总价和实际总价平均差了 45 万左右。对于北京二手房来说这个误差算合理——毕竟同一小区不同楼层的价格都能差几十万。如果 RMSE 超过 100 万那就要检查特征里是不是漏了关键信息或者数据里有大量异常值没处理。还有一个容易被忽略的点交叉验证。单次 train_test_split 的结果可能受划分方式影响用 5 折交叉验证能得到更稳定的评估from sklearn.model_selection import cross_val_score # 5折交叉验证评估模型稳定性 cv_scores cross_val_score(xgb, X, y, cv5, scoringr2) print(fXGBoost 5折交叉验证 R²: {cv_scores.mean():.4f} (/- {cv_scores.std():.4f}))如果交叉验证的均值比单次测试低很多说明模型对数据划分敏感可能需要更多数据或者更简单的模型。5. 避坑与排查跑这份代码时最容易翻车的五个地方这一章是我实际跑下来踩过的坑以及帮别人排查时反复遇到的问题。每个都按「现象 → 原因 → 解决」的结构写你遇到类似情况可以直接对照。5.1 中文列名导致的 KeyError现象运行df[总价]时报KeyError: 总价但明明 CSV 里就有这一列。原因CSV 文件的编码不是 UTF-8pandas 读进来后中文列名变成了乱码比如鎬讳环。这种情况在 Windows 上用 Excel 保存的 CSV 里特别常见。解决先检查编码用chardet检测或者直接试几种编码# 尝试不同编码读取 for enc in [utf-8, gbk, utf-8-sig, gb18030]: try: df pd.read_csv(lianjia.csv, encodingenc) print(f编码 {enc} 读取成功列名: {df.columns.tolist()[:5]}) break except UnicodeDecodeError: continue如果列名还是乱码可以在读取后手动重命名df.columns [小区名, 户型, 面积, ...]。最稳妥的做法是先用utf-8-sig试不行再换gbk。5.2 特征矩阵里混入了目标变量现象模型 R² 高达 0.99RMSE 只有几万但实际预测新数据时误差巨大。原因特征矩阵里包含了单价或者总价的衍生列。比如你构造了单价特征又用总价做目标模型直接学到了总价 单价 × 面积这个恒等式。解决在train_test_split之前明确列出要丢弃的列# 明确指定哪些列不能作为特征 drop_cols [总价, 单价, 总价_分段] # 所有和目标直接相关的列 X df.drop(columns[c for c in drop_cols if c in df.columns]) y df[总价]养成习惯每次构造新特征后检查一下它和目标变量的相关系数。如果某个特征和目标的相关性超过 0.95大概率是泄露了。5.3 XGBoost 安装失败或版本不兼容现象import xgboost报ModuleNotFoundError或者训练时报AttributeError: XGBRegressor object has no attribute fit。原因XGBoost 没有安装或者安装的版本和 scikit-learn 版本不兼容。在 Windows 上还可能缺少 Visual C 运行库。解决用 pip 安装指定版本避免最新版和旧版 sklearn 冲突# 安装 XGBoost指定兼容版本 pip install xgboost1.7.6 scikit-learn1.2.2 # 如果安装报错先升级 pip pip install --upgrade pip setuptools wheel如果还是不行可以退而求其次用GradientBoostingRegressorsklearn 自带效果接近但不需要额外安装from sklearn.ensemble import GradientBoostingRegressor gbr GradientBoostingRegressor(n_estimators300, learning_rate0.05, max_depth6)5.4 可视化脚本报字体错误现象运行visuals.py时中文显示为方框或者报RuntimeWarning: Glyph missing from current font。原因matplotlib 默认字体不支持中文需要手动指定中文字体。解决在绘图前设置字体参数import matplotlib.pyplot as plt # 设置中文字体Windows 用 SimHeiMac 用 Arial Unicode MS plt.rcParams[font.sans-serif] [SimHei, Arial Unicode MS, DejaVu Sans] plt.rcParams[axes.unicode_minus] False # 解决负号显示问题如果SimHei不存在可以用matplotlib.font_manager查找系统可用字体from matplotlib.font_manager import FontManager fm FontManager() chinese_fonts [f.name for f in fm.ttflist if Hei in f.name or Song in f.name] print(可用中文字体:, chinese_fonts)5.5 Notebook 内存溢出导致内核崩溃现象运行到特征工程或模型训练时Jupyter 内核突然重启之前的变量全部丢失。原因数据量太大或者特征维度太高内存不够用。特别是做独热编码后如果小区名有几千个类别特征矩阵会瞬间膨胀到几万列。解决分步处理及时释放不用的变量import gc # 删除中间变量释放内存 del df_raw, temp_df gc.collect() # 对高基数特征用目标编码代替独热编码 # 如果必须用独热编码先限制类别数量 top_categories df[小区名].value_counts().head(50).index df[小区名_简化] df[小区名].where(df[小区名].isin(top_categories), 其他) df pd.get_dummies(df, columns[小区名_简化], drop_firstTrue)另外把数据类型从float64降为float32也能省一半内存# 降低数值精度 for col in df.select_dtypes(include[float64]).columns: df[col] df[col].astype(float32)6. 进阶技巧用特征重要性和残差图验证模型是否真的学到了东西模型跑出 R² 0.85 不算完你得知道它为什么能到 0.85以及它在哪些样本上翻车。这一章讲两个我每次做完回归都会走的验证步骤以及一个能直接提升答辩表现的小技巧。6.1 特征重要性排序确认模型没走捷径随机森林和 XGBoost 都提供feature_importances_属性但直接看数值不够直观我一般会画成横向条形图import matplotlib.pyplot as plt import pandas as pd # 获取特征重要性 importance pd.DataFrame({ 特征: X.columns, 重要性: xgb.feature_importances_ }).sort_values(重要性, ascendingTrue) # 只显示前15个重要特征 top_features importance.tail(15) plt.figure(figsize(10, 8)) plt.barh(top_features[特征], top_features[重要性], colorsteelblue) plt.xlabel(重要性得分) plt.title(XGBoost 特征重要性 Top 15) plt.tight_layout() plt.show()如果面积、单价注意这里指的是作为衍生特征但没泄露的那个版本、房龄、楼层占比排在前列说明模型学到了符合常识的规律。如果某个你随手构造的无关特征排到了前三那就要警惕了——可能是数据泄露也可能是这个特征和某个关键特征高度相关。我还会做一件事把重要性最低的 10 个特征删掉重新训练一次看 R² 有没有明显下降。如果没有下降说明这些特征确实是噪声删掉能让模型更简洁。6.2 残差分析找出模型在哪些房子上预测不准残差图是回归模型最诚实的体检报告。横轴是预测值纵轴是残差实际值减预测值理想情况下残差应该随机分布在 0 附近没有明显规律。# 计算残差 residuals y_test - y_pred_xgb plt.figure(figsize(10, 6)) plt.scatter(y_pred_xgb, residuals, alpha0.5, s10) plt.axhline(y0, colorred, linestyle--, linewidth1) plt.xlabel(预测总价万元) plt.ylabel(残差万元) plt.title(XGBoost 残差分布) plt.tight_layout() plt.show() # 统计残差超过100万的样本占比 large_residual (abs(residuals) 100).sum() print(f残差超过100万的样本数: {large_residual}, 占比: {large_residual/len(residuals)*100:.2f}%)如果残差图呈现喇叭形预测值越大残差越大说明模型对高价房的预测能力不足可能需要单独对高价房建模或者对目标变量做对数变换。如果残差在某个区间集中偏离 0说明模型在这个价格段有系统性偏差。我一般还会把残差最大的 10 个样本单独拎出来看看# 找出预测误差最大的样本 error_df pd.DataFrame({ 实际总价: y_test, 预测总价: y_pred_xgb, 残差: residuals }) worst_predictions error_df.reindex(error_df[残差].abs().sort_values(ascendingFalse).index) print(worst_predictions.head(10))这些样本往往是特殊房源——比如学区房、别墅、或者数据录入有误的记录。分析它们能帮你判断是模型问题还是数据问题。6.3 一个答辩加分项用 SHAP 值解释单个预测SHAPSHapley Additive exPlanations能告诉你每个特征对某一条预测结果的贡献是多少。这在答辩时非常有用——导师问「为什么这套房子预测 500 万」你可以直接展示 SHAP 图说明每个特征的加减分。import shap # 创建 SHAP 解释器 explainer shap.TreeExplainer(xgb) shap_values explainer.shap_values(X_test) # 全局特征重要性SHAP 版本 shap.summary_plot(shap_values, X_test, plot_typebar, showFalse) plt.tight_layout() plt.show() # 单个样本的解释 sample_idx 0 shap.force_plot( explainer.expected_value, shap_values[sample_idx], X_test.iloc[sample_idx], matplotlibTrue, showFalse ) plt.tight_layout() plt.show()SHAP 的安装用pip install shap即可。如果报错检查 numpy 版本是否兼容常见做法是pip install shap --upgrade配合numpy1.20。从那以后我每次做完回归项目都会强制走一遍「特征重要性 → 残差图 → SHAP 单样本解释」这三步。它不仅能帮你发现模型的问题还能让你在答辩时面对「你这个模型可解释性怎么样」的提问时有实打实的东西可以展示。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站