ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

房价预测中随机森林与SVR的对数变换调优实战

房价预测中随机森林与SVR的对数变换调优实战 简介这份资源是一套面向数据科学学习者与机器学习初学者的完整实践案例围绕房产数据集展开房价预测任务重点对比随机森林与支持向量机回归器在对数预测和价格直接预测两种策略下的表现差异并系统讲解网格搜索超参数调优与模型效果评估方法。压缩包共8个文件约2.62MB涵盖Jupyter Notebook、Python脚本、CSV数据集、PDF讲义、Markdown说明及附赠文档等类型分别用于代码运行、数据读取、理论讲解与练习参考。目前已有166人学习下载。读者可借助完整案例掌握从数据加载、特征处理、模型构建、超参数搜索到误差分析与结果可视化的全流程理解不同建模策略对预测精度的影响并对照练习文件巩固实际操作适合希望将机器学习理论落地到真实数据场景的初学者与专业人员。1. 房价预测项目为什么对数变换能让随机森林和支持向量机少翻车房价预测是机器学习回归任务里最经典的练手项目没有之一。波士顿房价数据集被 scikit-learn 从内置加载器里移除之后大家更多转向 California Housing、Kaggle 的 Ames 房价数据集或者自己手头的一份二手房成交表。不管用哪份数据只要目标变量是成交价格你几乎一定会遇到同一个现象直接拿原始价格去拟合模型在低价段还行一到高价段误差就大得离谱换成对价格取对数之后再拟合整体误差指标立刻好看一截。这不是玄学是价格分布本身右偏导致的。这个项目要讲清楚的事情就三件第一用随机森林回归和支持向量机回归两个模型分别做「对数价格预测」和「原始价格直接预测」把两组结果摆在一起对比第二用网格搜索把两边的超参数调到合理区间而不是拍脑袋设默认值第三把评估指标、残差分布、预测散点图都画出来让你能判断模型到底哪里不行。适合已经会写 Python、用过 pandas 和 scikit-learn 基础 API、但还没完整跑通过一个回归调优流程的人。读完你应该能拿自己手上的房价表照着搭出一套可复现的对比实验。2. 数据准备与两种预测目标先搞清楚对数价格和原始价格差在哪2.1 房价数据集的典型结构和清洗动作房价类数据集的结构高度相似一堆数值特征面积、房龄、房间数、经纬度、一堆类别特征学区、朝向、装修等级加一个连续的目标列成交价。真正动手前先做三件事顺序别乱。第一件是看目标列的分布。用describe()加直方图重点看偏度和最大值与中位数的比值。房价的偏度通常在 1.5 以上最大值往往是中位数的五到十倍这就是右偏的直接证据。第二件是处理缺失值。数值列用中位数填充比均值稳因为均值会被极端高价拉偏类别列单独开一个Missing类别比直接删行更安全因为缺失本身可能带信息。第三件是识别并处理异常值。房价数据里常见的坑是录入错误比如把 500 万写成 5000 万。我一般用目标列的分位数做软截断而不是直接删保留样本量的同时压住极端值的影响。import numpy as np import pandas as pd from sklearn.model_selection import train_test_split # 读取数据假设目标列名为 price df pd.read_csv(house_prices.csv) # 看目标分布偏度和极值比 print(偏度:, df[price].skew()) print(最大值/中位数:, df[price].max() / df[price].median()) # 数值列中位数填充类别列补 Missing num_cols df.select_dtypes(include[np.number]).columns.drop(price) cat_cols df.select_dtypes(include[object]).columns df[num_cols] df[num_cols].fillna(df[num_cols].median()) df[cat_cols] df[cat_cols].fillna(Missing) # 对目标列做 1% 和 99% 分位软截断压住录入错误 low, high df[price].quantile([0.01, 0.99]) df[price] df[price].clip(low, high) # 构造对数目标列 df[log_price] np.log1p(df[price])这段代码的关键在最后一行。np.log1p是log(1x)比直接np.log安全因为价格理论上不会为负但清洗后偶尔出现 0 也不会报错。截断用分位数而不是固定阈值是因为不同城市、不同年份的价格量级差异很大固定阈值换个数据集就失效。2.2 对数变换为什么对随机森林和 SVM 都有效很多人以为对数变换只是「让分布更正态」这个理解只对了一半。对随机森林来说它不假设目标分布但它的分裂准则是基于方差或 MSE 的。当目标右偏时少数极高价样本会主导分裂点的选择树会把大量精力花在区分「很贵」和「非常贵」上反而对中间价位的区分变粗。取对数压缩了高价段的绝对差距让分裂更均衡。对支持向量机回归来说影响更直接。SVR 的损失函数对超出 epsilon 管的残差是线性的但它内部的核函数计算依赖特征和目标的尺度。原始价格动辄几十上百万核矩阵的数值范围会非常大导致优化不稳定、收敛慢。取对数之后目标落在个位数到十几之间和标准化后的特征量级接近SVR 的收敛速度和精度都会明显改善。这里有个容易忽略的点对数变换之后评估指标不能直接和原始价格模型比。你必须把对数模型的预测值用np.expm1还原回原始价格再算 MAE、RMSE否则两组数字没有可比性。这个还原步骤是后面所有对比的前提。from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline # 特征工程管道数值标准化 类别独热 preprocessor ColumnTransformer([ (num, StandardScaler(), num_cols), (cat, OneHotEncoder(handle_unknownignore), cat_cols) ]) # 划分数据注意两个目标列都要带上 X df.drop(columns[price, log_price]) y_log df[log_price] y_raw df[price] X_train, X_test, ylog_train, ylog_test, yraw_train, yraw_test train_test_split( X, y_log, y_raw, test_size0.2, random_state42 )ColumnTransformer把数值和类别分开处理避免把独热后的 0/1 列也做标准化。handle_unknownignore是必须的否则测试集出现训练集没见过的类别会直接报错。random_state42固定下来保证你后面每次调参的划分一致不然网格搜索的结果没法复现。3. 随机森林回归实战从默认参数到网格搜索调优3.1 随机森林的两个版本直接预测和对数预测随机森林回归的核心参数不多但每个都影响明显。先跑一个基线版本把两个目标分别训一遍看看差距有多大。from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error def evaluate(model, X_test, y_true, is_logFalse): pred model.predict(X_test) if is_log: pred np.expm1(pred) # 对数预测还原回原始价格 mae mean_absolute_error(y_true, pred) rmse np.sqrt(mean_squared_error(y_true, pred)) return mae, rmse, pred # 版本一直接预测原始价格 rf_raw Pipeline([(prep, preprocessor), (model, RandomForestRegressor(n_estimators200, random_state42))]) rf_raw.fit(X_train, yraw_train) mae_raw, rmse_raw, pred_raw evaluate(rf_raw, X_test, yraw_test) # 版本二预测对数价格再还原 rf_log Pipeline([(prep, preprocessor), (model, RandomForestRegressor(n_estimators200, random_state42))]) rf_log.fit(X_train, ylog_train) mae_log, rmse_log, pred_log evaluate(rf_log, X_test, yraw_test, is_logTrue) print(f直接预测 MAE{mae_raw:.0f} RMSE{rmse_raw:.0f}) print(f对数预测 MAE{mae_log:.0f} RMSE{rmse_log:.0f})跑完你大概率会看到对数版本的 MAE 和 RMSE 都更低尤其是 RMSE 降幅更明显因为 RMSE 对大误差惩罚重而直接预测在高价段的误差正是被放大的那部分。注意evaluate里还原预测值的逻辑这是两组对比能成立的关键漏掉这一步对比就废了。3.2 网格搜索调参哪些参数值得搜哪些是浪费时间随机森林值得搜的参数其实就四个n_estimators、max_depth、min_samples_leaf、max_features。n_estimators越大越稳但边际收益递减一般 200 到 500 之间够用max_depth控制树深太深过拟合太浅欠拟合min_samples_leaf是防过拟合最有效的参数房价数据里通常设在 1 到 10 之间max_features影响树之间的相关性回归任务常用1.0、0.5、sqrt三档。不要搜criterion回归任务默认 squared_error 就够了换 absolute_error 会让训练慢很多且提升不稳定。也不要同时搜七八个参数组合爆炸跑一晚上都出不来。from sklearn.model_selection import GridSearchCV param_grid { model__n_estimators: [200, 400], model__max_depth: [None, 15, 25], model__min_samples_leaf: [1, 3, 5], model__max_features: [1.0, 0.5] } grid_log GridSearchCV( Pipeline([(prep, preprocessor), (model, RandomForestRegressor(random_state42))]), param_grid, cv5, scoringneg_mean_absolute_error, n_jobs-1, verbose1 ) grid_log.fit(X_train, ylog_train) print(最优参数:, grid_log.best_params_) print(最优 CV MAE(对数空间):, -grid_log.best_score_)参数名前面加model__是因为它们在 Pipeline 里这个前缀不能省。scoring用负 MAE 是因为 sklearn 的网格搜索默认越大越好MAE 本身越小越好所以取负。n_jobs-1用满所有 CPU 核cv5是五折交叉验证房价数据量不大的话这个配置几分钟能跑完。调完之后用最优参数在测试集上再评估一次和基线对比。通常调参能把 MAE 再压下去 5% 到 15%具体幅度取决于你的数据质量和基线参数离最优有多远。4. 支持向量机回归实战核函数选择和尺度问题4.1 SVR 在房价数据上的适用边界SVR 和随机森林的脾气完全不同。随机森林对特征尺度不敏感对缺失和异常值也相对宽容SVR 对尺度极其敏感对参数 C 和 gamma 的选择也很挑剔。房价数据动辄几万行SVR 的训练复杂度在样本量上是超线性的几万行以上就会明显变慢。所以 SVR 更适合中小规模、特征维度不高的房价数据比如几千行、几十个特征。核函数选择上RBF 核是默认首选它能拟合非线性关系且参数少。线性核只在特征和目标近似线性时才用房价数据里很少满足。多项式核参数多、容易过拟合除非你有明确理由否则不碰。SVR 的三个核心参数C控制对训练误差的惩罚越大越容易过拟合epsilon是不敏感管宽度越大模型越简单、支持向量越少gamma是 RBF 核的宽度参数越大越容易过拟合。这三个参数必须一起调单独调一个往往得不到好结果。4.2 SVR 的网格搜索C、gamma、epsilon 的联合调优SVR 调参建议用对数尺度搜索因为这三个参数的最优值通常跨越几个数量级。直接给[0.1, 1, 10]这种线性网格很容易错过真正的最优区间。from sklearn.svm import SVR svr_param_grid { model__C: [0.1, 1, 10, 100], model__gamma: [scale, 0.01, 0.1, 1], model__epsilon: [0.01, 0.05, 0.1, 0.2] } svr_grid GridSearchCV( Pipeline([(prep, preprocessor), (model, SVR(kernelrbf))]), svr_param_grid, cv5, scoringneg_mean_absolute_error, n_jobs-1, verbose1 ) svr_grid.fit(X_train, ylog_train) print(SVR 最优参数:, svr_grid.best_params_)gammascale是 sklearn 的默认值等于1/(n_features * X.var())在标准化后的数据上通常是个合理的起点。epsilon在房价对数空间里0.01 到 0.2 对应的是原始价格上大约 1% 到 20% 的容忍带具体取哪个要看你的业务对误差的容忍度。这里有个血泪经验SVR 在几万行数据上跑网格搜索即使n_jobs-1也可能要几十分钟甚至更久。如果数据超过两万行建议先用随机子集调参确定大致区间后再用全量数据精调或者直接换RandomizedSearchCV减少组合数。调完之后同样要在测试集上评估并且和随机森林的结果放在一起对比。SVR 在中小数据上有时能超过随机森林但在大数据上通常打不过这是由算法本身的复杂度决定的不是调参能弥补的。5. 避坑与排查房价预测项目里最容易翻车的五个地方5.1 对数还原漏做两组指标根本没法比现象对数模型的 MAE 看起来比直接预测小一个数量级你以为模型效果逆天结果发现是忘了np.expm1拿对数空间的误差当原始价格误差了。原因对数空间的 0.1 误差还原到原始价格可能是几万块的差距量级完全不对等。解决所有对数模型的预测值在算任何指标之前先过一遍np.expm1。把还原逻辑封装进评估函数别靠记忆。5.2 网格搜索在测试集上调参指标虚高现象调参后测试集 MAE 特别好看换一批数据就崩。原因把测试集也喂给了GridSearchCV等于用测试集选参数信息泄漏。解决GridSearchCV只传训练集测试集留到最后评估一次。需要更严谨的话从训练集里再切一个验证集或者直接用交叉验证的分数做模型选择。5.3 SVR 没做特征标准化收敛慢且效果差现象SVR 训练时间极长或者直接不收敛MAE 高得离谱。原因房价特征里面积是几百、房龄是几十、经纬度是小数量级差异巨大RBF 核在这种尺度上计算出的距离几乎全由大面积特征主导。解决SVR 前面必须接StandardScaler这一点和随机森林不同。用 Pipeline 把标准化和 SVR 串起来避免手动处理时漏掉。5.4 类别特征独热后维度爆炸训练变慢现象加了几个高基数类别特征比如小区名、邮编之后特征维度从几十涨到几千训练时间翻倍。原因OneHotEncoder对每个类别值生成一列高基数类别直接撑爆维度。解决高基数类别要么做目标编码要么按频率合并稀有类别要么直接丢掉。独热只适合基数在几十以内的类别特征。5.5 随机森林的n_jobs和网格搜索的n_jobs冲突现象网格搜索跑得比预期慢很多CPU 占用率上不去。原因GridSearchCV的n_jobs-1和RandomForestRegressor内部的n_jobs同时开多进程进程数超过核数互相抢资源。解决网格搜索时把模型内部的n_jobs设为 1让GridSearchCV统一调度或者反过来网格搜索单进程模型内部并行。两者只开一个。6. 效果展示与进阶技巧把对比做扎实的最后一公里模型跑完、参数调完最后一步是把结果展示做得能说服人。很多人到这里就贴一个 MAE 数字完事但房价预测这种项目光看一个指标看不出模型哪里行哪里不行。我一般会固定做三张图加一张表。第一张是预测值对真实值的散点图两组模型画在同一张图上加一条 45 度参考线。对数模型还原后的点如果更贴近参考线尤其是高价段不塌就说明对数变换确实起了作用。第二张是残差分布图看残差是否围绕 0 对称如果直接预测的残差在高价段明显偏负说明模型系统性低估高价房。第三张是特征重要性条形图随机森林可以直接出SVR 需要用置换重要性来算这张图帮你判断模型有没有学到合理的特征。对比表我一般放这几列模型、目标变换、最优参数、MAE、RMSE、R²。表格里对数模型的指标必须是还原到原始价格之后算的否则没有可比性。import matplotlib.pyplot as plt from sklearn.inspection import permutation_importance fig, axes plt.subplots(1, 2, figsize(12, 5)) # 散点图直接预测 vs 对数预测 axes[0].scatter(yraw_test, pred_raw, alpha0.3, label直接预测, s10) axes[0].scatter(yraw_test, pred_log, alpha0.3, label对数预测, s10) lims [yraw_test.min(), yraw_test.max()] axes[0].plot(lims, lims, k--, linewidth1) axes[0].set_xlabel(真实价格) axes[0].set_ylabel(预测价格) axes[0].legend() # 残差分布 axes[1].hist(yraw_test - pred_raw, bins50, alpha0.5, label直接预测) axes[1].hist(yraw_test - pred_log, bins50, alpha0.5, label对数预测) axes[1].set_xlabel(残差) axes[1].legend() plt.tight_layout() plt.savefig(model_comparison.png, dpi150)散点图里如果对数预测的点在高价段仍然偏离参考线说明对数变换还不够可以考虑对目标做更一般的 Box-Cox 变换或者对高价样本单独建模。残差图如果出现双峰说明数据里可能混了两个不同来源的子群体比如新房和二手房这时候该考虑加一个来源特征或者分群建模。一个我常用的进阶技巧是把随机森林的预测值作为新特征喂给 SVR 做 stacking。随机森林擅长捕捉非线性交互SVR 在小样本上泛化稳两者互补。做法很简单用交叉验证生成随机森林的 out-of-fold 预测拼到原始特征后面再训 SVR。这个组合在房价数据上经常能比单模型再降几个点的 MAE代价是流程复杂一些调参时间翻倍。最后说个习惯每次跑完对比实验把参数、指标、随机种子、数据版本记在一个固定的实验日志里。我吃过太多次亏两周后想复现某个结果发现忘了当时用的是哪个划分比例只能全部重跑。这个习惯比任何调参技巧都值钱。希望帮到你。本文还有配套的精品资源点击获取
返回列表