ARTICLE DETAIL

资讯详情

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

机器学习房价预测实战:从数据清洗到误差复盘的完整流程

机器学习房价预测实战:从数据清洗到误差复盘的完整流程 我第一次正儿八经用机器学习做项目选的就是房屋销售价格预测。当时心里想得很简单找个数据集跑个回归看个R²完事。结果真上手才发现光是把数据洗干净就花了两天后面调模型的时间反而不是最多的。这个项目之所以值得写是因为它几乎覆盖了机器学习应用流程里所有关键环节数据理解、清洗、特征工程、模型选型、验证策略、误差复盘。而且房价预测的业务直觉人人都有——不用解释什么叫成交价每个人都能对“哪些因素影响房价”说出几句。但恰恰因为太熟悉很多细节容易被忽略。这篇文章适合三类人刚入门机器学习、想找一个完整项目练手的同学已经会调包但没做过完整比赛的初学者以及工作中偶尔需要处理回归问题、想看一套可复盘的工程思路的开发者。我会尽量把每一步的“为什么”也讲清楚而不是只给代码。1. 先估摸清楚数据成交价到底被哪些字段牵着走1.1 字段概览与第一眼直觉哪些列天生就是“钱”我当时用的是一份公开的房屋销售数据样本量不大1460条左右输入字段接近80个。这个规模放在今天的深度学习任务面前简直不够看但对树模型和线性模型来说是一个很典型的“中等偏小”的表格数据集。拿到数据第一件事不是跑模型而是把所有字段按业务直觉分成几类。我会在纸上列一下面积类、房间数量类、质量评分类、地段类、时间类、外部设施类。面积类里面地上居住面积GrLivArea几乎肯定是核心变量因为房子的物理大小直接决定价格。质量评分类OverallQual也是1到10的整数分跟价格的条线关系肉眼可见。地段类在真实业务里通常是最重要的因子但这套数据里只有邻居划分Neighborhood没有更精细的坐标信息后面得靠它来近似地段效应。分类之后我习惯马上做一次相关性扫描。用pandas的corr()扫一遍最大的几个相关性基本不会出人意料总体面积、质量评分、地下室面积、车库面积。这一步的意义不是发现什么新大陆而是建立“模型是否学歪了”的参照系。如果后面训练出来的模型特征重要性排名跟业务常识严重冲突那就说明数据或流程出了问题而不是模型发现了什么人类不知道的规律。1.2 时间维度、地段缺失与样本量三个容易埋雷的前提这份数据有个隐蔽的问题时间跨度。房屋的出售月份分布在一年内的不同月份年份也可能跨了好几年。如果不处理时间维度模型会默认“所有年份的房价水平一样”这在市场波动大的时候会直接导致预测偏差。我当时没有拿到精确的成交年份只有年份和月份的组合于是干脆用“销售年份月份”拼成一个数值特征虽然粗糙但至少给模型一个捕捉时间趋势的机会。更隐蔽的坑是缺失值。很多人一看到NaN就想到“填充”但房价数据里的缺失往往带着业务语义。这点我会在下一节详细讲这里先提一句很多列的缺失在原始问卷里意味着“没有这项设施”而不是“数据丢了”。用中位数或均值去填反而会把真正有价值的信息抹掉。样本量1460条也决定了后面模型选型的天花板。树模型在几千条样本上表现稳定深度学习在这种规模下很容易过拟合除非做大量数据增强否则性价比极低。这一点在后面选型时是决定性因素。2. 数据清洗的心法缺失值不是填空题是判断题2.1 各列缺失值的真实语义没有泳池不等于泳池质量差这套数据里有一堆列比如泳池质量、地下室类型、车库完成年份、门廊面积。它们的NaN含义完全不一样。泳池质量这个字段如果房子没有泳池那确实该是缺失但填充成“无”或者“0质量分”才是对的用中位数填一个5分进去等于凭空给没有泳池的房子加了泳池。地下室也一样很多房子没有地下室那这类字段要么填“无”要么填0面积绝不能填均值。我的处理原则是先看每一列的缺失率再翻数据说明文档判断缺失是“结构性缺失”还是“随机缺失”。结构性缺失比如没有车库导致车库相关字段全空就统一填0或“None”。随机缺失比如某个质量评分为空但房子确实存在才考虑众数或模型预测填充。实际操作里我把大量时间花在了区分这两类情况上因为填错一个就可能引入系统性偏差。还有一类字段很典型比如“装修年份”缺失。这种情况往往意味着房子从未翻新这时候填0反而不对因为0会被模型当成“公元0年装修”。更好的做法是填“等于建造年份”表示房子维持原样。这一类细节如果没人提醒新手很容易踩坑。2.2 偏态数值与类别编码log变换和目标编码的高危操作数值特征里面积、价格这类量级差异大的变量几乎都呈右偏分布。右偏意味着大部分样本集中在低值区少数样本拖着一条长尾巴。这种分布对线性模型特别不友好因为极端值会拽着拟合线走。处理办法是取对数或者做Box-Cox变换。比如地块面积原始尺度下从几千到几万不等取log之后就平滑很多。但要注意log变换不是所有数值特征都要做。计数类的特征比如卫生间数量、卧室数量本身是离散的且取值少做log反而画蛇添足。我一开始图省事对所有数值列无脑log结果模型效果反而下降因为有些特征本来就接近正态变换后分布反而畸形了。类别特征的编码也是重灾区。Neighborhood这个字段有二十几个取值直接LabelEncoder会强加一个虚假的排序关系模型会以为邻居A比邻居B“大”。我最后用的是目标编码用该类别下目标变量的均值经过平滑来替代原始类别。这个技巧对房价这类有强地段效应的数据特别有效。但目标编码有一个致命风险——数据泄漏。如果先用全量数据计算均值再切分训练集和验证集模型就提前看到了验证集里的地段价格水平本地验证会虚高。正确做法是在交叉验证的每一折内单独计算或者用保留均值的方式。我后面在第四节会具体讲怎么在交叉验证里安全地做目标编码。2.3 手工特征组合总面积、房龄、翻新年限的加法与减法原始数据里有很多信息藏在不同列的“加法关系”里。最典型的是总居住面积一层面积、二层面积、地下室面积分开给但业务上人们更关心的是全屋总面积。于是我构造了总地面面积和总建筑面积两个组合特征。这类特征对树模型来说尤其有效因为树模型本质上是在做条件分裂给它一个已经加好的总面积相当于帮它省去了一次隐式组合的尝试分裂效率更高。房龄也是一个必需的特征。原始数据里只有建造年份没有“卖房时房子多少岁”这个直感变量。我用销售年份减建造年份得到房龄再用“是否翻新过”作为开关如果翻新过就生成一个新特征“翻新后房龄”否则就等于原始房龄。这样模型就能区分“老房子但刚翻新”和“老房子没动过”——前者的价格往往比后者高出一截这个差异在纯原始字段里很难被树模型发现。组合特征不是越多越好。我试过把车库面积和门廊面积相加结果模型根本没用到这个特征因为两份面积加在一起之后噪音占了多数。做组合特征的判断标准很简单这个组合在业务上是否站得住脚、直觉上是否直接影响买家的出价。没有业务依据的组合加了反而是过拟合的来源。3. 先别上模型用基线和线性回归探明“基准水位”3.1 均值模型和单变量回归给自己一个可接受的底真正开始建模前我习惯先建立一个最傻的基线用训练集目标变量的均值去预测所有样本。这个模型不看任何特征它的误差就是后续所有模型的“及格线”。在log变换后的价格上均值模型的RMSE大概在0.4左右。看到这个数字后续模型只要能明显低于0.4就说明特征有信号否则说明数据出问题了。基线建立之后再做一次单变量线性回归只看总体面积这一个特征。结果R²能到0.6左右log尺度RMSE降到0.25上下。这一步的意义是让心里有数单个特征已经能解释一大半方差那后续所有复杂模型的上限应该还能显著降低误差。同时也可以顺带检查这个单变量关系的残差分布为后面的多变量模型做准备。3.2 线性回归的失效现场残差图的喇叭口说明什么多变量线性回归跑起来之后R²能做到0.8附近看起来挺像回事但画残差图的时候问题暴露了。横坐标是拟合值纵坐标是残差图上出现了一个明显的“喇叭口”房价预测值越高残差波动越大。这就是统计学里常说的异方差性。为什么会出现喇叭口因为房价的本质是乘性效应而不是加性效应。一套100万的房子涨10%是10万一套20万的房子涨10%只有2万。如果用线性回归直接拟合原始价格模型会被高价房的大额残差牵着走同时低价房的相对误差反而显得小。也就是说线性模型在“高价位段”和“低价位段”之间被迫做了一次不公平的取舍。还有一个表现是有些特征的系数符号跟业务直觉矛盾。比如某个区域属性在单变量分析里跟价格正相关放进多变量模型后系数变负。这往往是特征之间的共线性在作祟也可能是目标变量分布没处理好。我当时的应对是先停掉追特征回头处理目标变量。3.3 对成交价做log变换从“加法世界”切换到“乘法世界”把成交价取log之后残差喇叭口明显收窄模型的表现也变得稳定很多。虽然我平时不太喜欢依赖经验技巧但“回归问题里对强正偏的目标做log”这个操作确实是性价比最高的处理方式之一。背后的道理并不复杂。线性回归假设的是“特征每变化一个单位目标值变化一个固定幅度”这是加法世界。但房价更接近“特征每变化一个单位价格变化一个百分比”这是乘法世界。取log之后乘法关系变成加法关系正好和线性回归的假设对齐。用通俗的话说不是面积每多1平米房子贵2万块而是面积每多1%房价贵2%。log变换还有一个附带的好处模型直接预测的是log价格在评估的时候要把它指数化还原成真实价格来计算误差。我用的评估指标是RMSE和MAE分别在log尺度上算也在还原后的价格上算。后者更符合业务认知——买家要知道的是误差几万块而不是误差0.1个log单位。4. 树模型的实战选择随机森林、XGBoost与LightGBM怎么取舍4.1 为什么结构化数据里树模型比深度学习更实在我见过很多新手一上来就想上神经网络理由也简单“深度学习听起来高级”。但在这个项目里1460条样本、几十个表格特征神经网络很难占到便宜。深度学习擅长的是从原始高维数据里自动提取特征比如图像、文本、语音而表格数据里的特征已经被人为整理过信息密度高但量小这时候树模型对“特征与目标之间的非线性关系”的拟合能力反而更强而且训练速度快、可解释性好。这不是说神经网络不行而是说选型要看数据和问题性质。表格数据回归任务里随机森林、梯度提升树这类模型通常能打到80分甚至90分的表现而深度学习要花更多时间调网络结构、学习率、正则化最后可能还不如一棵调好参的LightGBM。我个人的原则是先用树模型作为主力如果业务上明确需要端到端自动特征提取再考虑深度学习。4.2 三模型训练配置与调参心得早停、K折和目标编码我在这个项目里依次试了随机森林、XGBoost和LightGBM。随机森林作为起点很好因为它几乎不挑参数默认配置就能拿到还不错的分数。缺点是它对噪声较敏感、对特征工程的依赖比较强而且预测结果不够平滑。XGBoost和LightGBM同属梯度提升但实现细节不同LightGBM用基于直方图的算法训练速度更快内存占用更小对类别特征也有原生支持。参数调优方面我踩过最大的坑是“n_estimators调太大却不配合早停”。梯度提升模型每多一棵树就是在当前残差上继续拟合树的数量太多就会过拟合到训练集。正确的做法是设置一个较大的树数量上限配合early_stopping在验证集指标不再改善时提前终止。我当时给LightGBM设置了1000棵的上限实际跑了三四百棵就触发了早停省下大量时间。这里给一段我当时配置LightGBM的核心代码思路不涉及完整数据但逻辑可以直接照搬import lightgbm as lgb params { objective: regression, metric: rmse, learning_rate: 0.03, max_depth: 4, num_leaves: 15, subsample: 0.8, colsample_bytree: 0.8, random_state: 42 } # 每一折内单独做目标编码避免数据泄漏 for train_idx, valid_idx in kfold.split(X, y): X_train_fold target_encode(X.iloc[train_idx], y.iloc[train_idx]) X_valid_fold target_encode(X.iloc[valid_idx], y.iloc[train_idx]) model lgb.train( params, lgb.Dataset(X_train_fold, labely.iloc[train_idx]), num_boost_round1000, valid_sets[lgb.Dataset(X_valid_fold, labely.iloc[valid_idx])], callbacks[lgb.early_stopping(50)] )学习率设低到0.03配合更多轮数效果通常比学习率0.1少轮数更好。max_depth和num_leaves要一起控制防止单棵树过深导致过拟合。subsample和colsample_bytree是两种正则化手段前者是行采样、后者是列采样能在训练速度几乎不变的情况下提升泛化能力。4.3 本地CV与线上分数的差异验证策略优先于模型技巧三个模型的本地表现大致是随机森林log RMSE在0.115左右XGBoost能做到0.105上下LightGBM最好到0.098。加上目标编码和特征工程之后LightGBM能继续压到0.088附近。看到这个趋势我当时已经比较满意了。但这里有个经验值得单独说本地分数和外部验证分数如果差异大第一反应不要怀疑模型参数而是怀疑验证策略。我用的是5折交叉验证K折切分要保证每一折的房源分布大致一致。如果使用随机切分可能某一折里全是高价房、另一折全是低价房这样验证出来的误差波动会很大。我还发现一个常见问题如果提前对全量数据做目标编码再切分本地CV分数会明显虚高因为模型已经在目标编码阶段看到了验证集的价格均值。这也是为什么我在4.2的代码里把目标编码放进了每一折内部。验证策略永远是先把数据泄漏的漏洞堵上再去调参数。否则后面的一切调优都是往沙地上盖楼。5. 误差复盘与特征解释模型不骗人但数据会撒谎5.1 SHAP值怎么看哪些特征真正决定一套房的估值模型训练完成之后我习惯用一个模型解释工具看特征重要性。SHAPSHapley Additive exPlanations比传统的feature_importance更能说明“每个特征对每个样本的具体影响方向和大小”。传统重要性只告诉你哪个特征用得多SHAP能告诉你面积增加对预测值到底推高了多少、有没有某些样本里面积反而压低价格。我的SHAP结果里总体面积、总体质量评分、总建筑面积排在前三后面跟着地段标签、房龄、装修状态。这个排名和业务直觉高度一致。看到这样的结果我才敢说模型学到的规律是正常的。如果哪天SHAP结果里出现一个莫名其妙的特征排在第一名那大概率是清洗时出了bug比如把ID列当成数值特征喂进去了或者某个列被错误填充。SHAP还有一个好处单样本解释。我可以随机挑一套被预测得很准的房和一套被预测偏很多的房逐个看哪些特征把预测值往上推、哪些往下压。这个能力在做业务汇报时特别有用因为听众不关心RMSE但很关心“为什么这栋房子被估高了10万”。5.2 预测最差的样本画像豪宅、小户型与“处处都特殊”的房子误差复盘是机器学习项目里最值得花时间的一步可惜很多人跳过了。我把验证集里预测误差最大的20个样本全部列出来一个个看它们的特征结果发现了一个清晰的模式误差最大的几乎都是豪宅和极小户型。豪宅的绝对误差大原因很好理解价格基数高同比例误差换算成金额就大。但有趣的是即使改成相对误差豪宅的预测也普遍偏差。原因可能是数据里豪宅样本太少模型很难学到这类房屋的组合规律又或许是豪宅的“独特性”太强每套豪宅都有自己的特殊配置通用模型对它们束手无策。极小户型的误差则是另一个方向面积虽然小但单价可能极高比如核心地段的小户型原始字段里没有单价这个概念模型自然很难预测。我后来做了一次分组误差分析把样本按面积分成5档分别计算每一档的RMSE。结果证实了高阶样本的误差显著高于中段。这类分析的价值是帮助设定预期一个模型不可能在所有价位段都表现完美知道它在哪里弱业务上就可以针对性地做人工复核。5.3 还可以继续做的方向空间特征与外部数据这个项目做到这里模型已经收敛在一个比较稳定的水平。如果再往下推进我会优先考虑外部数据。比如给每个房源加上经纬度然后从空间上计算周边配套设施密度、通勤时间、学区质量这类特征在真实房价预测里往往比面积更能解释价格差异。地理空间特征是一种很自然的扩展距离最近的地铁站、距离市中心的直线距离、周边学校的评分均值这些都可以用公开数据拼出来。树的特性决定了它很适合吃这类“地理距离”特征因为距离天然是非线性的影响——离地铁站300米和500米的差距也许不大但3公里和5公里的差距可能就很显著。但引入外部数据的代价是时间和不确定性。公开数据的口径、覆盖率、时效性都需要仔细排查一不留神就会引入新的偏差。在竞赛或练手场景下我通常先把纯数据内部的特征工程和验证做到位再考虑外部数据。因为这个项目的主线是学习完整流程而不是无限堆高分数。做完整套流程之后我个人最大的体会是模型本身的选择随机森林还是LightGBM对最终分数的影响其实小于数据处理和验证策略的影响。很多初学者把精力全放在调参上忽略了数据清洗和特征工程才是决定模型上限的部分。如果你也想拿房价预测练手建议先把目标变量的分布、缺失值的语义、目标编码的数据泄漏这三个环节吃透再讨论超参数。这样冒的险更少学到的也更贴近真实项目。
返回列表