ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

房价预测实战:随机森林与SVR的对数变换及网格搜索调参对比

房价预测实战:随机森林与SVR的对数变换及网格搜索调参对比 简介这份资源是一套面向数据科学学习者与机器学习初学者的房价预测实战案例包围绕房产数据集展开完整建模流程重点对比随机森林与支持向量机回归器在房价对数预测和价格直接预测两种策略下的表现差异并包含网格搜索超参数调优与预测效果评估环节适合希望把理论落地到真实项目的读者。压缩包共8个文件约2.62MB涵盖ipynb交互式笔记本、py脚本、csv数据集、pdf与docx说明文档、md说明及tgz数据包兼顾代码复现与文档阅读。目前已有166人学习下载。通过该案例读者可掌握从数据加载、特征处理、模型构建、超参数搜索到误差分析与结果可视化的完整链路理解对数变换对预测精度的影响并借助附赠说明文件快速搭建实验环境适合作为课程设计或自学练手项目。1. 房价预测项目为什么值得从随机森林和支持向量机开始房价预测是机器学习回归任务里最经典的练手场景但真正把它做完整的人并不多。大多数人停在train_test_split加RandomForestRegressor跑出一个 R² 就结束了既没有做对数变换也没有对比直接预测价格的差异更没碰过网格搜索调参。这个项目标题指向的正是这条完整链路基于房产数据集用随机森林和支持向量机回归器分别做房价对数预测与价格直接预测的对比分析中间穿插网格搜索超参数调优和效果展示。它适合两类人一类是刚学完机器学习基础、想找一个能写进简历的端到端项目的数据科学学生另一类是做业务建模的工程师想搞清楚目标变量变换到底能带来多大收益。核心问题只有一个——房价这种右偏分布的目标到底该直接回归原始价格还是先取对数再回归随机森林和 SVR 在这个问题上的表现差异又在哪里把这两个问题回答清楚这个项目的价值就立住了。2. 数据准备与目标变量变换对数预测和直接预测的分叉点2.1 房产数据集的字段结构与清洗要点常见做法是使用带面积、房间数、楼层、地段评分、房龄等字段的房产数据集。不管具体来源是什么拿到手第一步不是建模而是确认三件事目标列是否右偏、缺失值分布、类别字段的基数。import pandas as pd import numpy as np import matplotlib.pyplot as plt df pd.read_csv(house_prices.csv) # 1. 看目标变量分布形态 print(df[price].describe()) print(偏度:, df[price].skew()) # 大于1说明明显右偏 print(峰度:, df[price].kurt()) # 2. 缺失值排查 missing df.isnull().sum().sort_values(ascendingFalse) print(missing[missing 0]) # 3. 类别字段基数 cat_cols df.select_dtypes(includeobject).columns for c in cat_cols: print(c, df[c].nunique()) # 4. 画一下原始价格分布 plt.hist(df[price], bins50) plt.title(price distribution) plt.show()这段代码的逻辑是先诊断再动手。偏度大于 1 基本可以判定需要做对数变换缺失值超过 30% 的列考虑直接丢弃低于 5% 的数值列用中位数填充类别字段基数超过 50 的独热编码会炸维度得换目标编码或频次编码。参数上bins50只是看形态不影响建模。清洗阶段我一般会做这几步把明显不合理的面积比如 0 或超过 10000置为缺失再填把房龄为负数的记录删掉对地段评分做 0-1 归一化。这些操作看起来琐碎但后面模型效果差异往往就出在这里。2.2 对数变换为什么能救右偏目标房价分布天然右偏——大部分房子集中在中间价位少数豪宅把尾巴拉得很长。直接对原始价格做回归模型会被极端高价样本带偏损失函数里 MSE 对大误差惩罚极重导致模型过度关注那几套豪宅。对数变换把乘法关系变成加法关系压缩了尾部。log1p比log更稳因为价格可能为 0虽然房价少见但租金数据常见log1p能避免log(0)报错。# 对数变换 df[log_price] np.log1p(df[price]) print(变换后偏度:, df[log_price].skew()) fig, axes plt.subplots(1, 2, figsize(12, 4)) axes[0].hist(df[price], bins50) axes[0].set_title(raw price) axes[1].hist(df[log_price], bins50) axes[1].set_title(log1p price) plt.show()变换后偏度通常会从 3 以上降到 0.5 以内接近正态。这一步是整个项目对比分析的基础后面所有模型都要跑两遍一遍预测log_price一遍预测price最后统一换算到原始价格空间比较。注意对数空间训练出来的模型预测值要还原回原始空间才能算真实误差。还原用np.expm1不要用np.exp否则会系统性偏高。2.3 特征工程与训练集划分的固定随机种子特征工程不需要花哨但要有针对性。面积取对数、房龄分箱、地段评分和面积的交互项这三样通常能带来稳定提升。from sklearn.model_selection import train_test_split df[area_log] np.log1p(df[area]) df[age_bin] pd.cut(df[age], bins[0, 5, 15, 30, 100], labels[0, 1, 2, 3]).astype(int) df[area_loc] df[area_log] * df[location_score] feature_cols [area_log, rooms, age_bin, location_score, area_loc, floor] X df[feature_cols].fillna(df[feature_cols].median()) y_raw df[price].values y_log df[log_price].values X_train, X_test, y_train_raw, y_test_raw train_test_split( X, y_raw, test_size0.2, random_state42 ) _, _, y_train_log, y_test_log train_test_split( X, y_log, test_size0.2, random_state42 )关键参数是random_state42两次划分必须用同一个种子否则训练集和测试集不一致对比就失去意义。test_size0.2是常规选择数据量小于 5000 时可以用 0.25 让测试集更稳。特征列里没有放原始area因为area_log已经包含了信息且分布更好。3. 随机森林回归器对数预测与直接预测的完整实现3.1 随机森林的核心参数与默认值陷阱随机森林回归器看起来开箱即用但默认参数在房价数据上经常过拟合。n_estimators100是默认值对中小数据集够用但树太深时每棵树都记住了训练集噪声。max_depth默认 None意味着树可以长到叶子只有一个样本这是过拟合的头号来源。我一般先跑一个基线看训练集和测试集的 R² 差距。如果训练集 0.98、测试集 0.72那就是典型过拟合必须限制深度和叶子节点最小样本数。from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score def evaluate(model, X_tr, y_tr, X_te, y_te, is_logFalse): pred_tr model.predict(X_tr) pred_te model.predict(X_te) if is_log: pred_tr np.expm1(pred_tr) pred_te np.expm1(pred_te) y_tr np.expm1(y_tr) y_te np.expm1(y_te) rmse np.sqrt(mean_squared_error(y_te, pred_te)) mae mean_absolute_error(y_te, pred_te) r2 r2_score(y_te, pred_te) print(fTrain R2: {r2_score(y_tr, pred_tr):.4f}) print(fTest R2: {r2:.4f} RMSE: {rmse:.2f} MAE: {mae:.2f}) return {rmse: rmse, mae: mae, r2: r2} # 基线默认参数 rf_base RandomForestRegressor(random_state42, n_jobs-1) rf_base.fit(X_train, y_train_log) print( RF 对数预测 基线 ) evaluate(rf_base, X_train, y_train_log, X_test, y_test_log, is_logTrue) rf_base_raw RandomForestRegressor(random_state42, n_jobs-1) rf_base_raw.fit(X_train, y_train_raw) print( RF 直接预测 基线 ) evaluate(rf_base_raw, X_train, y_train_raw, X_test, y_test_raw, is_logFalse)n_jobs-1用满所有 CPU 核心训练时间能砍一半以上。evaluate函数里对数预测的还原逻辑是重点预测值、训练标签、测试标签都要expm1还原否则 R² 算出来是错的。基线跑完你会看到对数预测的 RMSE 通常比直接预测低 15% 到 30%这就是对数变换的价值。3.2 网格搜索调参随机森林的四个必调参数网格搜索不是把所有参数都塞进去那样组合爆炸。随机森林真正值得调的就四个n_estimators、max_depth、min_samples_split、min_samples_leaf。max_features在特征数不多时影响有限可以先固定。from sklearn.model_selection import GridSearchCV param_grid_rf { n_estimators: [100, 200, 300], max_depth: [8, 12, 16, None], min_samples_split: [2, 5, 10], min_samples_leaf: [1, 2, 4] } grid_rf GridSearchCV( RandomForestRegressor(random_state42, n_jobs-1), param_grid_rf, cv5, scoringneg_root_mean_squared_error, n_jobs-1, verbose1 ) grid_rf.fit(X_train, y_train_log) print(最优参数:, grid_rf.best_params_) print(最优 CV RMSE:, -grid_rf.best_score_)参数说明cv5是五折交叉验证数据量小于 2000 时用 5 折大于 10000 可以用 3 折省时间。scoring用负 RMSE 而不是 R²因为 RMSE 对房价这种有量纲的目标更直观。n_jobs-1在 GridSearchCV 和模型里都设并行度叠加注意别把内存跑爆。组合数是 3×4×3×3108乘以 5 折等于 540 次训练。中小数据集上大概几分钟到十几分钟。如果嫌慢先粗调max_depth和n_estimators锁定范围后再细调另外两个。3.3 对数预测与直接预测的结果对比调参完成后用最优模型分别跑对数预测和直接预测把结果放一起看。best_rf grid_rf.best_estimator_ print( RF 对数预测 调参后 ) res_log evaluate(best_rf, X_train, y_train_log, X_test, y_test_log, is_logTrue) # 直接预测也做一次网格搜索参数空间相同 grid_rf_raw GridSearchCV( RandomForestRegressor(random_state42, n_jobs-1), param_grid_rf, cv5, scoringneg_root_mean_squared_error, n_jobs-1 ) grid_rf_raw.fit(X_train, y_train_raw) best_rf_raw grid_rf_raw.best_estimator_ print( RF 直接预测 调参后 ) res_raw evaluate(best_rf_raw, X_train, y_train_raw, X_test, y_test_raw, is_logFalse)跑完对比通常会发现对数预测的 RMSE 明显更低但 MAE 差距没那么大。原因是 RMSE 对大误差敏感对数变换压缩了高价样本的误差贡献MAE 看的是中位数级别的误差两者差距被拉平。这个细节在写报告时值得单独说一句——如果你的业务更在意别把豪宅估太低对数预测更合适如果只关心普通房子的平均误差两者差别没那么夸张。4. 支持向量机回归器核函数选择与对数预测的配合4.1 SVR 在房价数据上的标准化前提SVR 和随机森林最大的区别是它对特征尺度极度敏感。随机森林靠分裂点特征缩放不影响SVR 靠距离计算一个面积特征动辄几百房间数只有个位数核函数会被大面积特征主导。所以 SVR 之前必须做标准化。from sklearn.preprocessing import StandardScaler from sklearn.svm import SVR scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) # 注意scaler 只在训练集上 fit测试集只 transform这里有个容易翻车的地方fit_transform只能用在训练集测试集必须用同一个 scaler 做transform。如果测试集也fit_transform等于把测试集的分布信息泄露给了训练过程交叉验证分数会虚高。4.2 RBF 核与线性核的选型依据SVR 常用核函数就两个rbf和linear。房价特征和价格的关系通常是非线性的面积翻倍价格不一定翻倍所以 RBF 是首选。线性核只在特征数远大于样本数时才考虑房价数据一般不符合这个条件。svr_rbf SVR(kernelrbf, C1.0, epsilon0.1, gammascale) svr_rbf.fit(X_train_scaled, y_train_log) print( SVR RBF 对数预测 ) evaluate(svr_rbf, X_train_scaled, y_train_log, X_test_scaled, y_test_log, is_logTrue)gammascale是 sklearn 的默认值等于1/(n_features * X.var())比自己手调省事。epsilon0.1控制容忍带宽度对数空间里 0.1 大概对应原始价格 10% 的波动。C1.0是正则化强度的倒数越大越容易过拟合。4.3 SVR 的网格搜索C、epsilon、gamma 三参数联动SVR 调参比随机森林更敏感因为三个参数互相影响。C大gamma大模型复杂度爆炸C小gamma小模型欠拟合。网格搜索时建议用对数刻度。param_grid_svr { C: [0.1, 1, 10, 100], epsilon: [0.01, 0.05, 0.1, 0.2], gamma: [scale, 0.01, 0.1, 1] } grid_svr GridSearchCV( SVR(kernelrbf), param_grid_svr, cv5, scoringneg_root_mean_squared_error, n_jobs-1, verbose1 ) grid_svr.fit(X_train_scaled, y_train_log) print(SVR 最优参数:, grid_svr.best_params_) print(SVR 最优 CV RMSE:, -grid_svr.best_score_)组合数 4×4×464乘以 5 折 320 次训练。SVR 单次训练比随机森林慢但 320 次在几千样本上通常几分钟能跑完。如果数据超过 2 万行SVR 会明显吃力这时候要么降采样要么换LinearSVR。调参后同样跑对数预测和直接预测的对比。SVR 在对数空间的表现通常比直接预测好更多因为 SVR 的 epsilon 容忍带在原始价格空间里对高价样本极不友好——一个 50 万的误差在 500 万的房子上算小在 50 万的房子上算大epsilon 没法同时适配。5. 避坑与排查房价预测项目里最容易翻车的五个地方5.1 对数还原用了 exp 而不是 expm1现象预测出来的房价系统性偏高RMSE 比预期大一大截。 原因训练时用了log1p还原时用了np.exp。log1p(x) log(1x)逆运算应该是exp(x)-1也就是expm1。用exp会多算 1 个单位在对数空间里 1 个单位对应原始价格可能差几万。 解决所有对数还原统一用np.expm1包括预测值和标签。写个工具函数封装别到处手写。5.2 标准化在交叉验证里泄露了测试集信息现象交叉验证分数很高但换一批新数据测试时效果暴跌。 原因在GridSearchCV之前就对全量数据做了fit_transform交叉验证的每一折里都混入了其他折的统计信息。 解决用Pipeline把 scaler 和模型串起来让 scaler 在每一折内部只 fit 训练部分。from sklearn.pipeline import Pipeline pipe_svr Pipeline([ (scaler, StandardScaler()), (svr, SVR(kernelrbf)) ]) param_grid_pipe { svr__C: [0.1, 1, 10], svr__epsilon: [0.05, 0.1, 0.2], svr__gamma: [scale, 0.1] } grid_pipe GridSearchCV(pipe_svr, param_grid_pipe, cv5, scoringneg_root_mean_squared_error, n_jobs-1) grid_pipe.fit(X_train, y_train_log)注意参数名要加svr__前缀这是 Pipeline 的命名规则。5.3 网格搜索参数空间设太宽导致跑不完现象GridSearchCV 跑了半小时还没结束CPU 满载但进度条不动。 原因参数组合数爆炸。比如n_estimators设了 10 个值max_depth设了 8 个值再乘其他参数轻松上千组合。 解决先粗后细。第一轮每个参数只取 3 个值锁定大致范围第二轮在最优值附近取更细的粒度。另外verbose1能看到进度n_jobs-1用满核心但内存不够时反而会拖慢。5.4 类别特征直接丢进模型没做编码现象代码报could not convert string to float或者模型跑通了但效果极差。 原因房产数据里的地段、房型是字符串sklearn 的模型不认。 解决基数低的用独热编码基数高的用目标编码。独热编码注意drop_firstTrue避免共线性。df pd.get_dummies(df, columns[location, house_type], drop_firstTrue)如果类别超过 20 个独热编码会让特征维度爆炸这时候用频次编码或目标编码更合适。5.5 用测试集反复调参导致过拟合测试集现象测试集 R² 0.85上线后实际误差大得离谱。 原因反复在测试集上试不同参数、不同特征组合测试集实际上变成了第二个训练集。 解决划出三份数据——训练集、验证集、测试集。调参和特征选择只看验证集测试集只在最后跑一次。如果数据量不够用交叉验证代替固定验证集测试集始终封存。6. 效果展示与进阶技巧把对比分析做成可复用的评估框架6.1 用一张表把四个模型的结果摆在一起项目最有说服力的产出是一张对比表。四个组合RF 对数、RF 直接、SVR 对数、SVR 直接每个都记录 RMSE、MAE、R²。模型目标空间RMSEMAER²RF 调参后log1p最低中等最高RF 调参后原始较高中等中等SVR 调参后log1p中等最低较高SVR 调参后原始最高较高最低具体数值取决于数据集但规律稳定对数预测在两个模型上都优于直接预测RF 的 RMSE 通常最低SVR 的 MAE 有时反超。这个规律本身就是结论——目标变换的收益大于模型选择。6.2 残差图比 R² 更能说明问题R² 只给一个数残差图能看出模型在哪里翻车。import matplotlib.pyplot as plt pred_log np.expm1(best_rf.predict(X_test)) residuals y_test_raw - pred_log plt.figure(figsize(10, 4)) plt.subplot(1, 2, 1) plt.scatter(pred_log, residuals, alpha0.5) plt.axhline(0, colorred, linestyle--) plt.xlabel(predicted price) plt.ylabel(residual) plt.title(RF log-target residuals) plt.subplot(1, 2, 2) plt.scatter(y_test_raw, pred_log, alpha0.5) plt.plot([y_test_raw.min(), y_test_raw.max()], [y_test_raw.min(), y_test_raw.max()], r--) plt.xlabel(actual) plt.ylabel(predicted) plt.title(predicted vs actual) plt.tight_layout() plt.show()残差图如果呈现喇叭形——低价段残差小、高价段残差大——说明模型对高价样本仍然欠拟合可以考虑对高价样本加权或者单独训练一个高价段模型。如果残差围绕 0 均匀分布说明模型没有系统性偏差。6.3 特征重要性排序与业务解释随机森林自带feature_importances_但直接用会有偏差——高基数特征容易被高估。用排列重要性更稳。from sklearn.inspection import permutation_importance perm permutation_importance( best_rf, X_test, y_test_log, n_repeats10, random_state42, n_jobs-1 ) for i in perm.importances_mean.argsort()[::-1]: print(f{feature_cols[i]}: {perm.importances_mean[i]:.4f} f/- {perm.importances_std[i]:.4f})n_repeats10表示每个特征打乱 10 次取平均次数越多越稳但越慢。排列重要性的含义是把这个特征的值随机打乱后模型误差增加多少。增加越多说明越重要。这个结果可以直接写进业务报告——如果location_score排第一说明地段是房价的第一驱动因素和常识一致模型可信度就上来了。6.4 我踩过的最大一个坑这个项目我做过三遍前两遍都栽在同一个地方忘了在交叉验证里隔离 scaler。第一遍跑出来 SVR 的 CV R² 0.91我兴冲冲拿去给同事看他用 Pipeline 重跑了一遍CV R² 只有 0.78。差了 0.13原因就是我在GridSearchCV之前对全量数据做了标准化每一折的验证集统计信息泄露进了训练。从那以后我养成了一个习惯只要模型对特征尺度敏感一律用 Pipeline 包起来参数名加前缀绝不手动在外面做fit_transform。这个习惯帮我省了至少两次返工。另外对数还原的expm1我也写成了一个函数inverse_log放在项目工具文件里所有地方统一调用再也没出现过还原错误。如果你正准备做这个房价预测项目我的建议是先把 Pipeline 和expm1这两件事做对再去调参。参数调得再细数据泄露和还原错误也会让所有结果失去意义。希望帮到你。本文还有配套的精品资源点击获取
返回列表