资讯详情

Kaggle房价预测实战:从缺失值处理到模型集成全解析

📅 2026/10/3 11:40:15 | 华诺云谱 👁 阅读
Kaggle房价预测实战:从缺失值处理到模型集成全解析
打开这个比赛的 Notebook 时我做了三件蠢事先去看了别人的高分代码然后尝试原样运行最后对着完全不同的输出一脸茫然。后来我才明白问题根本不是模型跑不起来而是我压根没搞懂这个数据集在表达什么。这个比赛就是 Kaggle 上经典的《房价预测高级回归技巧》House Prices: Advanced Regression Techniques。你一听名字可能觉得普通但它常年霸占 Kaggle 入门推荐榜第一名靠的不是花哨的模型而是它以极小的数据量覆盖了回归建模里几乎所有关键环节缺失值处理、偏态分布、类别特征编码、交叉验证、正则化、集成学习。网上一搜这个比赛能搜出大量图文教程光标题叫“三万字深度解析”的就不止一篇。但很多内容是把别人的代码流程重新排列组合了一遍真正把“每一步为什么要这样做”讲清楚的少。这篇我想换一个角度不帮你逐行背诵别人的 Notebook而是带着你把整条解题链路拆开看看每一步后面到底是什么逻辑以及复现过程中你在哪几个环节最容易翻车。无论你是刚注册完 Kaggle 账号准备跑第一个比赛还是已经跑通 Baseline 想继续涨分这篇应该都能给你一些参考。先回答一个大家反复在问的问题参加 Kaggle 竞赛对求职到底有没有帮助我的看法是有但帮助的大小取决于你怎么参赛。如果你是照着别人的 Notebook 一键运行然后提交那充其量是熟悉了平台操作但如果你真的把这个比赛从头到尾推导过一遍能够解释清楚你填的每个缺失值都用了什么策略、为什么交叉验证要用 10 折而不用 5 折、集成模型比单一模型好在哪那这种经历才是面试里拿得出手的东西。所以这篇文章的目标很清楚不只要让你“跑通”更要让你“讲得清”。1. 为什么我在入门 Kaggle 时死磕这个比赛1.1 这个比赛看起来是预测房价实际上是在训练建模全局观房价预测这个比赛的赛题描述很简单给你 79 个解释变量和 1460 条训练数据让你预测测试集里 1459 套房子的 SalePrice。数据集来自美国爱荷华州 Ames 市所以你也经常看到有人管它叫 Ames 房价数据集。它不像很多企业里的真实数据那样存在大量乱码、重复记录、字段错位但缺失值、偏态分布、异常值、高相关特征这些该有的“教学素材”一样不少非常适合用来练手。我个人复盘这个比赛时最大的感受是它其实不是让你去发明什么高深算法而是逼你在一套完整的建模流水线里做一百个选择题。比如某个列缺失了 80%你选择直接删掉还是构造一个“是否缺失”的标志位某个数值特征严重右偏你选择 Log 变换还是 Yeo-Johnson 变换整体质量 OverallQual 明明是 1 到 10 的离散打分你是当连续值用还是当有序类别用这些选择单独看都不难但把它们串在一起就决定了你的模型最终能摸到多高的分数。1.2 和波士顿房价数据集相比Ames 数据集好在哪很多人在搜“波士顿房价预测”时会被引导到这个比赛。波士顿房价确实是教科书级数据集13 个特征506 条数据做线性回归演示非常合适。但放到现代机器学习竞赛的语境下它有一个致命缺点太干净了。没有缺失值没有类别特征没有明显的偏态特征不需要你做任何特征工程。这导致你用它只能学到“调用 LinearRegression 再算个 R2”学不到真正工程化的建模流程。Ames 数据集则完全不同。它有 79 个特征其中既有连续数值型特征如地块面积 LotArea、地面生活面积 GrLivArea也有丰富的类别型特征如 MSZoning区域划分、Neighborhood街区、SaleCondition销售条件还有大量有序的评分特征如 OverallQual整体质量、OverallCond整体状况。数据集中一部分列缺失率高得离谱比如 PoolQC泳池质量缺失率超过 99%Alley巷子类型缺失率也超过 90%。你不可能跳过处理直接训练模型必须动手做决策。这正是这个比赛作为入门进阶的价值所在它逼着你离开舒适区。1.3 求职角度怎么让这段比赛经历真正加分关于“参加 Kaggle 竞赛对求职的帮助”我见过两种极端。一种人把这个比赛刷到 Top 1%简历写得很漂亮但面试时被问“你的缺失值填充为什么用中位数而不是均值”就卡壳了。另一种人分数一般但能把从数据探索到模型集成的链路讲得清清楚楚反而拿下了 offer。这背后的原因很简单面试官想从项目经历里看到的不是你背诵了多少 API 调用而是你有没有形成一套自己的建模思考框架。所以建议你复现这个比赛时每做完一步就在旁边记一笔“为什么”。为什么填中位数因为 SalePrice 和很多特征存在偏态均值更容易被极端值拉偏。为什么做特征标准化因为 Lasso 和 Ridge 这类带 L1/L2 惩罚项的模型对特征的尺度敏感。这些记录不仅是写博客的素材更是你面试时的弹药。从这个角度说你花在理解上的时间远比花在刷分上的时间回报率高。2. 赛前拆解赛题机制与那些容易忽略的规则细节2.1 四份文件各有用途别只盯着 train.csv进入比赛的 Data 页面你会看到四个文件train.csv、test.csv、sample_submission.csv、data_description.txt。新手最容易犯的错误是一上来就激动地打开 train.csv 开始跑其他三个文件一律无视。这其实是个效率极低的习惯。train.csv包含 1460 行79 个特征列和 1 个目标列 SalePrice用于训练模型。test.csv包含 1459 行只有 79 个特征列没有 SalePrice你的任务是预测这些房子的价格并提交。sample_submission.csv提交样本包含 Id 和 SalePrice 两列。SalePrice 这一列的值是随便填的真正的意义是告诉你提交格式长什么样。data_description.txt这是整个比赛里最重要的文件但也是被读得最少的一个。里面详细解释了每一个特征字段的含义。比如 MSSubClass 表面上是一串数字但它代表的是建筑类型编码其中 20 表示“1 层 1946 年后建”60 表示“多层 1946 年后建”数字之间没有大小关系是名目类别而不是数值。我强烈建议你花半小时老老实实读一遍 data_description.txt把每个字段的语义搞清楚再动手写代码。你以为你在做回归其实你是在用一堆建筑、区位、交通、装修的碎片信息推断一栋房子的价值。不理解字段含义你做的特征工程就是闭着眼睛猜。2.2 评估指标 RMSLE一个影响全局的小细节这个比赛用的评估指标是均方根对数误差英文缩写 RMSLERoot Mean Squared Logarithmic Error公式长这样RMSLE sqrt( (1/n) * sum( (log(1 p_i) - log(1 a_i))^2 ) )其中 p_i 是预测值a_i 是实际值。对比我们更熟悉的 RMSERMSLE 有三个明显特点。第一它在对数空间里计算误差所以对价格高的房子的绝对误差惩罚相对更小。预测一栋 20 万美元的房子差 1 万美元和一栋 50 万美元的房子差 1 万美元在线性空间里同样严重但在对数空间里后者相对误差更小RMSLE 的惩罚更轻。这实际上和房价预测的现实逻辑更贴近人们通常关心的是“这房子估值偏了多少个百分点”而不是“偏了多少美元”。第二RMSLE 对低估的惩罚大于高估。因为 log(1 p) 增长是压缩的当预测值小于实际值时同样偏差产生的对数差往往更大。用大白话说你把一栋值 30 万的房子预测成 15 万比预测成 60 万“扣分”更狠。这个特性会直接影响你对模型的选择和调参取向。第三因为要在对数空间比较最简单也最常用的预处理方式就是对 SalePrice 做 log1p 变换即 y_log log(1 SalePrice)。做完这个变换之后你在对数空间里用 RMSE 优化等价于在原始空间里优化 RMSLE。这也是为什么几乎所有公开方案的第一步都是对目标列做 log1p——不是玄学是顺着指标倒推出来的必然选择。顺带一提数据科学界还有个经典的波士顿房价预测数据集用的也是回归但指标通常是 RMSE 或 R2没有 RMSLE 这种对数空间的约束。两者对比着做一次你会对“指标选择如何影响建模行为”有很直观的体会。2.3 开局先摸清 79 个特征的缺失率分布拿到数据之后不要立刻建模。先用一行代码统计每一列的缺失率把特征分一下类。这个比赛的缺失值分布非常有层次感大致可以分成四种情况。缺失率范围典型字段处理策略90%PoolQC、MiscFeature、Alley、Fence这些字段绝大多数房子压根没有对应属性缺失本身就是“无”的含义填 None 或构造布尔标志位60%-90%FireplaceQu房子没有壁炉所以质量字段缺失。填 None 或构造 HasFireplace10%-60%LotFrontage、GarageType、GarageFinish、BsmtQual缺失大多因为该房子没有车库或地下室但也存在个别真缺失需要结合业务判断填充10%Electrical、MasVnrArea、MasVnrType少量随机缺失用众数或中位数填充即可这张表是我自己整理时的习惯你也可以按自己的理解重排。关键是脑子里要有一张清晰的“缺失地图”而不是对着 null 百分比发呆。我在第一次复现时直接对所有缺失列统一填了 None导致 LotFrontage 这种数值列变成字符串后面做特征缩放时报错才回头处理。类似的坑你不必再踩一遍。3. 特征工程是上限模型只是逼近这个上限3.1 目标变量先做 Log 变换不只是为了指标在动手处理任何特征之前第一件事就是看 SalePrice 的分布。你会发现它明显右偏长尾拖得很长有几栋房子价格明显高于整体水平。对于线性模型来说偏态目标会导致残差非正态影响模型效果对于树模型来说虽然影响没那么致命但在集成学习和交叉验证的框架下所有模型的预测误差都共享同一个评估指标所以统一做变换依然是收益最大的做法。实际操作很直接import numpy as np import pandas as pd train pd.read_csv(train.csv) test pd.read_csv(test.csv) train[SalePrice] np.log1p(train[SalePrice])注意这里存下来的 y 已经是对数空间的值了。后面所有模型训练都基于变换后的目标等到生成提交文件时记得用 np.expm1() 把预测值还原回原始房价否则你提交的价格会是对数量级分数直接崩盘。3.2 缺失值填补的顺序和策略比你想的更讲究缺失值填充没有唯一正确答案但有一些通用原则。对类别特征我推荐的策略是缺失率高的列统一填 None并额外构造一个该字段是否缺失的辅助特征。比如 Alley巷子类型大多数房子根本没有巷子入口缺失是常态而不是异常你直接填 None 再做一个 HasAlley 列模型就能同时学到“没有巷子”和“有巷子但类型未知”两种信息。缺失率低的类别列则用众数填充比如 Electrical 只有一个缺失值填最频繁的 SBrkr 完全没问题。对数值特征中位数通常比均值更稳。原因在于这个数据集里有不少偏态分布的特征比如 LotFrontage临街长度和 LotArea地块面积均值会被极端值拉高中位数更能代表典型值。另外我习惯在填充完数值特征后增加一列该列是否缺失的 0/1 标志位尤其是当缺失率在 5%-20% 这个区间时这个标志位本身往往就是有用的预测信号。顺序问题也很关键。我的做法是先合并 train 和 test 到一个统一的 all_data 里再统一做填充和编码。这样做的意义在于测试集和训练集中的特征分布来自同一批数据生成过程合并后计算中位数和众数时用的样本量更大估计更稳定。具体到这个比赛训练集 1460 条测试集 1459 条几乎一半一半如果你只按训练集算中位数等于扔掉了一半有效信息。3.3 偏态特征与组合特征从“字段”到“领域知识”处理好缺失值之后下一步是看连续型特征的偏度。SalePrice 是偏态的但不代表只有它需要处理。用 df.skew() 扫一遍你会发现很多特征比如 LotArea、GrLivArea、BsmtFinSF1 等都存在明显偏态。处理方式有两种一是直接对正偏特征做 Log 变换二是用 scipy 的 Box-Cox 变换自动估计最优 lambda。Box-Cox 要求输入严格为正而 Yeo-Johnson 可以处理负值实际使用中我一般直接用 sklearn 的 PowerTransformer(methodyeo-johnson)一步到位。特征工程的重头戏是组合特征。Ames 数据集的字段之间天然存在很多业务关系组合出来的新特征往往比原始字段更有预测力。我复现过程中长期保留并验证有效的三个组合特征TotalSF TotalBsmtSF 1stFlrSF 2ndFlrSF。它是房子总面积的近似值比单独看某一个面积字段更能代表房子体量。OverallScore OverallQual * OverallCond。质量和状况是两回事但一栋房子最终是否宜居取决于质量与维护状况的乘积关系。质量 9 但状况 2 的房子显然不如质量 6 状况 7 的房子住得舒适。Age YrSold - YearBuilt以及 RemodAge YrSold - YearRemodAdd。房龄和翻修年龄是折旧的核心变量很多真实房价模型里它们比建造年份本身更重要。这些组合特征不是拍脑袋想的而是从 data_description.txt 的字段解释里读出来的业务逻辑。这也是我反复强调读文档的原因任何高级特征都没有捷径先理解字段才能组合字段。3.4 编码方式怎么选one-hot、label encoding 还是 frequency encoding类别特征的处理是新手最容易晕的地方。这个数据集里类别特征接近 40 个策略不对很容易把特征矩阵撑爆。我压箱底的建议分三层。对确实有序的类别特征比如 ExterQual外部材质质量分为 Po、Fa、TA、Gd、Ex 五档用 LabelEncoder 即可因为档位之间天然有顺序关系编码成 0-4 不会误导模型。对无序且取值数量少的类别特征比如 MSZoning、SaleCondition用 one-hot 编码列数膨胀可控。对无序但取值极多的类别特征比如 Neighborhood街区多达 20 种one-hot 会让矩阵多出几十列而且在 1460 样本的限制下很多类别出现的频率很低容易造成稀疏问题。这种字段我用 frequency encoding用“每个类别在该字段中出现的次数”替换原始字符串。这个思路的本质是告诉模型居住在某个人口密集街区和在某个稀有街区本身隐含了不同的市场结构信息。4. 高级回归技巧真正高级在哪4.1 正则化线性模型在表格数据里的统治力很多人看到“高级回归技巧”这个标题觉得一定是 XGBoost 或者深度学习。但在这个比赛里带正则化的线性模型表现一点都不差甚至经常是融合模型里不可或缺的底座。原因在于特征经过 one-hot 和 frequency encoding 后会变得非常多变量之间相关性极强普通线性回归在这种情况下会出现系数极度不稳定的问题。LassoL1 正则可以稀疏化特征RidgeL2 正则可以压缩系数幅度ElasticNet 则是两者的结合。在 sklearn 里用 LassoCV 可以很方便地完成带交叉验证的调参from sklearn.linear_model import LassoCV lasso LassoCV(alphasnp.logspace(-4, 0, 100), cv5, random_state42) lasso.fit(X_train, y_train)LassoCV 会在内部跑交叉验证并选出最优 alpha省去了手动调参的麻烦。实际运行中你会发现它选出来的模型在验证集上的表现和树模型几乎打平这在很多实际业务场景里也同样成立表格数据、特征中等到高维、特征间存在复杂线性关系时正则化线性模型是性价比最高的起点。4.2 树模型为什么强以及它最容易过拟合的地方在哪如果说正则化线性模型是这个比赛的地基树模型就是主力部队。XGBoost、LightGBM、CatBoost 这三个在表格数据竞赛里轮流称王房价预测这个比赛自然也不能缺席。它们的核心优势是能捕捉非线性关系而且对异常值相对鲁棒对特征的尺度不敏感省去很多标准化的麻烦。但优势也是劣势的来源。树模型太容易在 1460 条样本上过拟合了。处理过拟合的几个有效手段我在复现中反复验证过控制深度max_depth 一般设在 3 到 6 之间深度超过 8 在这么小的数据集上基本就开始“背答案”了。控制叶子节点信息量XGBoost 的 min_child_weight 和 LightGBM 的 min_data_in_leaf 都可以适当调高防止模型从极少样本里学出无意义的规则。随机抽样subsample 和 colsample_bytree 分别控制在 0.7 到 0.9既增加多样性又能防止过拟合。早停learning_rate 调低到 0.01-0.05同时配合 early_stopping_rounds观察验证集误差不再下降就及时停止。4.3 交叉验证是整条流水线里最不能用默认配置的一环如果你的模型只用一次 train/test 划分来验证效果那在这个比赛里你会被分数波动折磨到怀疑人生。1460 条样本实在太少了随机划分的运气成分非常大。我推荐的方式是 K 折交叉验证K 取 10。为什么是 10 而不是 5因为样本量小10 折让每一折训练集都有 1314 条验证集有 146 条既保证了训练充分又让验证集稍微大一点评估指标更稳定。当然你完全可以用 RepeatedKFold 做多次重复的 10 折交叉验证然后取均值这样能把波动压得更低。交叉验证还有一个隐藏用途生成 OOFOut-Of-Fold预测。简单说在每一轮交叉验证中用训练好的模型对验证折做出预测最后把每一折的验证预测拼接起来就得到了一个“模型在训练集上的全局预测”。这个 OOF 预测是集成学习和 Stacking 的基础材料。4.4 从简单平均到加权集成再到 Stacking单独调用一个模型在这个比赛里大概能到一个还不错的分数但想继续往上走几乎必然要进入集成阶段。最简单的集成是取多个模型的预测值平均。先对每个模型做交叉验证记下它的验证分数然后两个模型一组测试不同权重组合下的验证分数变化。比如 Lasso 权重 0.3、XGBoost 权重 0.7可能会比他们各自的 0.5/0.5 更好。因为 Lasso 在捕捉线性关系上有优势XGBoost 在非线性特征交互上有优势两者的误差相关性低互补性强。更高阶的集成就叫 Stacking用基模型的 OOF 预测作为特征训练一个二级模型通常用 Ridge让模型自动学习不同基模型的权重。二级模型一般不能太复杂否则同样会过拟合。在这个数据量下Stacking 的提升幅度不一定比手工加权平均大多少但它能让你节省大量的手动调权重时间属于工程师更偏好的方案。5. 从零复现一条跑分流水线可运行级代码5.1 环境准备本地跑还是 Kaggle Notebook这是个效率问题我复现这个比赛时第一版代码是在本地跑的原因是迭代速度快、调试方便。你不需要 GPU这个数据集小到 CPU 跑全流程也就一两分钟。本地环境需要安装 pandas、numpy、scikit-learn、xgboost、lightgbm、scipy 这几个核心库。Kaggle 官方也提供了在线 Notebook 环境预装好了几乎所有常用库而且可以一键提交省去了本地文件上传的麻烦。这里集中回答一下经常被问到平台问题。Kaggle 注册时如果遇到验证码不显示或者收不到验证邮件的情况可以先看垃圾邮件箱或者换一个浏览器重试清除浏览器缓存也能解决一部分问题。如果你在学校或公司网络下注册不了可以试试切换手机热点之类的网络环境再操作很多时候问题出在网络环境而不是平台本身。成功注册之后在 Notebook 右上角的 “Settings” 面板里可以勾选 “Turn on Internet” 来访问外部数据如果不勾选Notebook 只能访问比赛数据安装额外包或加载预训练模型都会失败。提交时只要点右上角的 “Submit to Competition” 按钮就可以了。5.2 数据预处理与建模主流程代码下面这份代码是我简化后的核心流程不是最优方案但足够你跑通并理解整个链路。它的思路是读数据 - 合并 - 补缺失 - 编码与变换 - 建模 - 交叉验证 - 集成交付。import numpy as np import pandas as pd from scipy.stats import skew from sklearn.model_selection import KFold from sklearn.preprocessing import LabelEncoder, PowerTransformer from sklearn.linear_model import LassoCV from xgboost import XGBRegressor from lightgbm import LGBMRegressor train pd.read_csv(train.csv) test pd.read_csv(test.csv) y np.log1p(train[SalePrice]) test_id test[Id] ntrain train.shape[0] ntest test.shape[0] # 合并处理保证填充统计量的一致性 all_data pd.concat([train.drop(SalePrice, axis1), test], axis0, ignore_indexTrue) all_data all_data.drop(Id, axis1) # 缺失率高的类别特征统一填 None并增加标志位 high_missing_cols [PoolQC, MiscFeature, Alley, Fence, FireplaceQu] for col in high_missing_cols: all_data[col _missing] all_data[col].isnull().astype(int) all_data[col] all_data[col].fillna(None) # 数值特征统一填中位数 num_cols all_data.select_dtypes(includenp.number).columns for col in num_cols: if all_data[col].isnull().sum() 0: all_data[col] all_data[col].fillna(all_data[col].median()) # 有意义的类别特征填众数 cat_cols all_data.select_dtypes(includeobject).columns for col in cat_cols: if all_data[col].isnull().sum() 0: all_data[col] all_data[col].fillna(all_data[col].mode()[0]) # 组合特征 all_data[TotalSF] all_data[TotalBsmtSF] all_data[1stFlrSF] all_data[2ndFlrSF] all_data[OverallScore] all_data[OverallQual] * all_data[OverallCond] all_data[Age] all_data[YrSold] - all_data[YearBuilt] all_data[RemodAge] all_data[YrSold] - all_data[YearRemodAdd] # 类别编码 for col in cat_cols: all_data[col] LabelEncoder().fit_transform(all_data[col]) # 偏态数值特征做 Yeo-Johnson 变换 skewed_feats all_data[num_cols].apply(lambda x: skew(x.dropna())).sort_values(ascendingFalse) skewed_feats skewed_feats[abs(skewed_feats) 0.5].index pt PowerTransformer(methodyeo-johnson) all_data[skewed_feats] pt.fit_transform(all_data[skewed_feats]) # 切回训练集和测试集 X_train all_data.iloc[:ntrain].reset_index(dropTrue) X_test all_data.iloc[ntrain:].reset_index(dropTrue) # 10 折交叉验证设置 folds KFold(n_splits10, shuffleTrue, random_state42) def cv_predict(model, X, y, folds, is_lgbFalse, is_xgbFalse): oof np.zeros(X.shape[0]) preds np.zeros(X_test.shape[0]) for trn_idx, val_idx in folds.split(X): trn_x, trn_y X.iloc[trn_idx], y.iloc[trn_idx] val_x, val_y X.iloc[val_idx], y.iloc[val_idx] if is_lgb: model.fit(trn_x, trn_y, eval_set[(val_x, val_y)], eval_metricrmse, callbacks[lgb.early_stopping(100)]) elif is_xgb: model.fit(trn_x, trn_y, eval_set[(val_x, val_y)], verboseFalse) else: model.fit(trn_x, trn_y) oof[val_idx] model.predict(val_x) preds model.predict(X_test) / folds.n_splits return oof, preds lasso LassoCV(alphasnp.logspace(-4, 0, 100), cv5, random_state42) lasso_oof, lasso_pred cv_predict(lasso, X_train, y, folds) xgb XGBRegressor(n_estimators3000, learning_rate0.01, max_depth4, subsample0.8, colsample_bytree0.8, reg_alpha0.1, reg_lambda1.0, random_state42) xgb_oof, xgb_pred cv_predict(xgb, X_train, y, folds, is_xgbTrue) lgb LGBMRegressor(n_estimators3000, learning_rate0.01, max_depth4, subsample0.8, colsample_bytree0.8, reg_alpha0.1, reg_lambda1.0, random_state42) lgb_oof, lgb_pred cv_predict(lgb, X_train, y, folds, is_lgbTrue) # 加权集成 final_pred 0.3 * lasso_pred 0.35 * xgb_pred 0.35 * lgb_pred sub pd.DataFrame({Id: test_id, SalePrice: np.expm1(final_pred)}) sub.to_csv(submission.csv, indexFalse)上面这段代码可以直接运行但不建议直接抄完就提交。我特意把交叉验证封装成了一个函数因为不同模型的接口和训练方式不一样用同一个函数处理能大幅减少代码重复也更方便你之后加新模型。权重 0.3/0.35/0.35 只是我随手给的初始值真正合适的最优权重需要你根据自己 OOF 分数的表现手动微调。5.3 提交文件格式最容易在最后关头翻车的细节提交阶段最常见的两个错误第一个是忘了把 SalePrice 从 log 空间还原第二个是提交文件的行数或顺序和 sample_submission.csv 不一致。测试集 1459 行submission 必须也正好是 1459 行。提交文件的 Id 顺序最好保持原始 test.csv 的顺序虽然 Kaggle 系统会按 Id 匹配但顺序混乱很容易让你自己在核对时昏头。最后提交前用一行代码检查assert sub.shape (1459, 2) assert not sub[SalePrice].isnull().any()如果 Safely 通过再提交。分数出来如果和你本地 CV 分数差距很大第一件事看格式第二件事看是不是自己忘了 expm1。6. 复现过程中最容易翻车的几个点6.1 合并训练集和测试集做填充到底算不算数据泄漏有些新手教程会告诉你绝对不能合并训练集和测试集否则会数据泄漏。这个说法在这个比赛里并不完全成立。要分清楚什么是泄漏什么不是。泄漏指的是你的模型在训练阶段接触到了测试集的目标信息或者用未来的信息预测过去。但在 train.csv 和 test.csv 合并填充缺失值这个操作里我们只用到了测试集的特征分布没有用到任何目标信息。而且在真实的预测场景里模型最终要对测试集逐条预测你本来就有权查看测试集的特征。所以合并填充是安全的而且因为样本量增大了填充统计量反而更稳定。真正危险的泄漏是另外两种情况。一种是你在做异常值处理时用全体数据包括测试集的分布来决定哪些点是异常点并删除这会让你对测试集的判断先入为主。另一种是你在做特征缩放时把训练集和测试集一起标准化这本身没问题但如果你在交叉验证的每一折里都用了全体数据的均值和标准差每一折的验证集信息就被提前混入训练过程了。同样的道理也适用于 PowerTransformer。正确做法是在交叉验证内只用训练折的数据去 fit 变换器再 transform 验证折和测试集。初学者为了省事常常直接对全量数据 fit_transform这也是很多本地分数虚高的根源。6.2 填缺失值的顺序会影响结果这不是玄学我在第三节提到先合并再统一填充实际操作中你会发现一个有趣的现象对于中位数这种依赖分布的统计量你在合并后的 all_data 上计算出的中位数和只在 train 上计算的中位数可能很接近也可能完全不同。一个字段如果有 20% 的缺失而测试集的分布又和训练集有差异那么两种方式填充后的值就会不一样后面模型结果自然也不同。更隐蔽的是类别特征。如果你先填了 None再做 LabelEncoder编码的结果会因为你填充顺序不同而不同。假设某个列里原来的值是 A、B、C缺失部分填了 None编码后 None 变成 0A 变成 1如果你先编码再填充A 是 0None 反而被映射到某个新值。两种方式 A 的编码数字完全不同模型训练出来的特征重要性也会跟着变。如果模型是树模型其实对单调变换不敏感但对线性模型来说编码顺序完全影响结果。所以请你固定一套流程不要一会儿先补缺失再编码一会儿先编码再补缺失。你不需要判断哪种绝对正确只需要保证交叉验证和最终预测用完全一致的处理顺序。6.3 为什么我劝你别用 TPU 跑这个比赛Kaggle 提供了免费的 TPU 配额很多人一上来就想着用 TPU 加速。但在这个比赛里TPU 不是加速器而是麻烦制造者。原因有三点。第一TPU 主要面向大规模张量运算典型的应用场景是深度学习的卷积和 Transformer 训练而房价预测这种小规模表格数据用 TPU 跑树模型基本发挥不出任何硬件优势。第二TPU 的 API 习惯和 CPU/GPU 完全不同比如 TPU 上处理数据需要经过 tf.data 管道你原本用 pandas 和 numpy 处理好的数据还得费劲转换格式。第三Kaggle 的 TPU 配额有每周上限你用在这种小比赛上纯属浪费。我的建议是本地或者 Kaggle Notebook 的 CPU/GPU 跑都足够。如果你已经选了 TPU 运行也可以在 Notebook 的 Settings 里直接切换回 GPU不需要重新创建笔记本。6.4 关于随机性、Seed ensemble 与分数波动的观察同一个模型同一个参数只改 random_state你得到的分数会在一个区间内波动。1460 条训练样本太少随机种子的影响会被放大。所以很多人问我“为什么我和教程用一样的代码提交分数却不同”答案很可能就是随机种子不同。处理方式有两种一是固定一个随机种子并接受波动二是使用 Seed ensemble也就是用多个不同的随机种子分别训练模型最后对预测值取平均。后者的核心逻辑是减少单次随机性带来的方差在实践中通常能稳定提升一点点分数。做 Seed ensemble 时需要注意它不是简单地把多个模型输出平均就完事了。你最好为每个种子都记录下它的 OOF 分数剔除明显偏离平均水平的种子。有时某个种子训练出来的模型在本地表现很好但在测试集上翻车反而是加入更多种子的平均结果更稳定。6.5 提交报错与平台问题速查最后把我在实践里遇到过的 Kaggle 平台问题做一个速查表希望能帮你省掉不少时间。问题原因解决方法提交后提示 Incorrect number of rows提交文件行数和测试集不一致重新检查 test.csv 行数确保 submission 行数一致提交文件报错 Missing value in SalePrice预测值里有 NaN检查模型预测前是否有未处理的缺失值或在生成文件前用 isnull() 检查Notebook 运行时报错找不到 xgboost/lightgbm当前环境未安装在 Notebook 开头执行 !pip install xgboost lightgbm并开启 Internet 访问注册后收不到验证邮件邮件被拦截或延迟检查垃圾邮件箱等待几分钟后重新获取验证邮件注册页面验证码不出现浏览器插件或缓存问题更换浏览器、清除缓存、关闭广告拦截插件后重试Notebook 无法连接外部数据Internet 设置未开启在 Settings 面板勾选 “Turn on Internet”这些平台问题大多数不难解决但卡住的时候确实耽误时间。顺便说一下如果你看到 Notebook 页面有 “Schedule” 功能它是用来设置定时运行的通常用于定期刷取最新数据或者自动跑完流程后把结果存到数据集里。对于房价预测这种静态赛题用处不大我更建议你先在本地把逻辑跑通再决定要不要用平台自动化功能。7. 涨分进阶从 Baseline 到 Top 方案还差什么7.1 继续加模型Stacking 的正确打开方式当你手上已经有了 Lasso、XGBoost、LightGBM 三个模型的 OOF 预测后接下来最自然的涨分手段就是把它们 Stacking 起来。具体做法是把三个模型的 OOF 预测并成一个新特征矩阵用 Ridge 回归作为二级模型去拟合真实的 y。Ridge 的正则化项在这里很重要它能防止二级模型过拟合。如果你没有用正则化而直接用线性回归去拟合Stacking 很可能不如手工加权平均。如果继续往上层堆你可以再训练一个神经网络模型或者加入 CatBoost让二级模型看到更多不同风格的预测器。每次加一个新的基模型都要重新生成它的 OOF 预测和测试集预测然后重新训练二级模型。这个过程可以迭代很多轮但收益会逐渐递减。7.2 特征工程的业务理解进阶从 Neighborhood 与 SaleCondition 里挖信息到了后期你会发现模型分数提升的瓶颈不在模型结构而在特征的信息量。Top 方案并不会使用特别神秘的算法而是在特征里加入了更多领域知识。举两个例子。Neighborhood街区是 Ames 市区内部不同地段的划分。不同街区的房价中位数差异巨大这种差异靠 one-hot 或 frequency encoding 虽然能捕捉一部分但无法捕捉“街区价格梯队”的连续信息。更高级的做法是用训练集里每个街区的平均 SalePrice 作为特征编码直接告诉模型这个街区的整体价位水平。这种目标编码Target Encoding有泄漏风险必须配合交叉验证在每一折内单独计算但处理得当的话效果常常优于 frequency encoding。SaleCondition销售条件也是类似道理。字段里包含 Normal正常、Abnorml异常比如法拍或遗产出售、Partial部分例如房子未完工时已经签约等情况。Partial 条件下成交的房子通常是买家参与了建造过程价格形成机制和 Normal 完全不同。你可以构造一个特征表示“是否为 Partial 销售”或者把 Abnorml 和 Family 等其他非正常条件分组让模型能区分出不同的议价环境。这些特征单看似乎都是小改动但组合起来的提升往往比调参明显。因为模型的上限由信息量决定特征工程就是喂给模型更多信息的过程。7.3 这个比赛的经验如何迁移到真实业务里最后想聊一点更长远的。Kaggle 比赛和企业里的建模工作之间最大的差异不是模型复杂度而是问题定义方式。比赛中所有评估指标都是给定的测试集也是固定的而在真实业务里目标定义、指标选择、数据采集都是你自己要回答的问题。但房价预测这个比赛教给你的东西尤其是“先理解字段语义再做特征工程”“用交叉验证衡量模型稳定性”“把多个模型集成降低波动”这几件事在任何业务场景里都是通用的。我后来在做信用评分、销量预测这些实际项目时遇到一个新的数据集第一反应仍然是先画缺失地图、看分布、读字段说明而不是急着训练模型。这个习惯就是从这个比赛开始养成的。关于这个比赛网上资料很多从几千字到几万字的都有。但如果你只带走一个观念我希望是模型是最后一个环节前面所有的数据理解、缺失值策略、特征工程、交叉验证设计才是决定你分数上限的东西。你先想清楚“为什么”再动手写代码整个过程会顺利得多。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑