如果让我给刚学完机器学习基础的人推荐一个必做的练习,我会毫不犹豫地说:去Kaggle打一场房价预测。这个全称叫 House Prices - Advanced Regression Techniques 的比赛,从2016年上线到现在热度一直没降,被无数人当作人生第一场Kaggle竞赛。它的数据量不大、任务清晰、社区资料多到几乎能搜到任何问题的答案,非常适合把教材上学到的模型、交叉验证、特征工程真正落到一个端到端的项目里。
这篇内容我想按自己实际打这个比赛的完整流程来讲——从注册账号、下载数据,到吃透数据、建模调参、提交得分,再到把同一套打法迁移到波士顿房价、Airbnb房价这类其他预测场景。如果你正打算报名这场竞赛,或者刚注册完账号面对train.csv不知道从哪下手,这篇应该能把整条链路串起来。
1. 为什么"房价预测"能成为Kaggle最经典的入门赛
1.1 竞赛到底在做什么:一份数据玩到80个特征的回归问题
这场竞赛用的是美国艾奥瓦州Ames市的房屋销售数据集,由Dean De Cock整理发布。训练集包含1460条房屋记录,79个解释变量,覆盖面积、房间数、建造年份、材料质量、地下室状态、临街面、车库、游泳池等方方面面,目标是预测每套房子的最终成交价SalePrice。测试集则去掉SalePrice,要求参赛者通过模型预测后按指定格式提交。
这里有个容易被忽略的细节:名字虽然叫"房价预测",但它实际是一个监督学习中的回归问题,评估指标是均方根对数误差(RMSLE)。具体公式是:先对真实价格和预测价格都取log(1+x),再计算均方误差开根号。这种对数空间的误差度量意味着,同样差1万美元,对10万的房子和对100万的房子造成的误差权重是不同的——它更关注预测误差的比例,而不是绝对金额。
这也是为什么社区里常有人说这个比赛"简单但不无脑"。它不需要你训练图像识别或者处理长文本,只要把一行行的表格数据处理好就能出成绩,但对数据清洗、特征工程和模型集成的完整流程要求一点都不含糊。每一条经验都能直接迁移到其他表格类竞赛。
1.2 相比波士顿房价,为什么更推荐直接上Ames数据集
很多人最早接触的回归数据集是波士顿房价(Boston Housing),sklearn里自带,506条样本、13个特征,用几行代码就能跑出结果。但说实话,如果有心认真练习,我建议别止步在波士顿,直接上Ames数据集收获会大得多。
| 数据集 | 样本量 | 特征数 | 适合做什么 | 局限 |
|---|---|---|---|---|
| Boston Housing | 506 | 13 | 快速验证线性回归、教学演示 | 特征太少,练不了特征工程;且该数据集有历史争议,已被部分库移除 |
| Ames Housing | 1460 | 79 | 完整跑通Kaggle竞赛流程 | 特征较多,需要处理缺失值和类别编码,对新手有一定门槛 |
波士顿数据本身属于"玩具级",你很难在里面体会缺失值策略、偏态分布、类别变量编码、共线性诊断这些真实比赛里天天碰到的问题。而Ames数据集里,面积、质量分、房龄、地下室、车库差分等维度互相纠缠,一张图、一个编码方式选错都会直接影响最终分数。跑完Ames,再看波士顿会觉得简单得像在复习小学算术。
1.3 这个赛题到底能练什么:从数据处理到模型集成的完整闭环
我见过不少朋友报名Kaggle后,下载了数据却不知道第一步做什么。问题通常不在"算法不够熟",而在于没有形成一套可复用的项目流程。房价预测这个题目能把以下能力全部串起来:
- 探索性数据分析(EDA):用pandas和seaborn看目标分布、特征相关性、缺失率。
- 数据清洗:区分真实缺失与"无此项"缺失,选择合理的填充策略。
- 特征工程:组合出总面积、房龄、质量乘积等新特征。
- 交叉验证:建立稳定的KFold流程,评估模型泛化能力。
- 模型调参与集成:从Ridge等线性模型到XGBoost、LightGBM等树模型,最后用加权融合提升。
这套流程练熟之后,去接UCI二分类、销售预测、租金预测等比赛,基本就是换数据和换指标的问题,骨架完全通用。这也是我把这个比赛排在"人生第一场Kaggle竞赛"位置上的原因。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开赛前的两件小事:账号注册和数据获取都踩过的坑
2.1 Kaggle注册时验证码不显示,captcha must be filled out到底怎么解决
注册Kaggle账号本来不算事,但热词里反复出现"验证码不显示""captcha must be filled out"这类问题,可见不少人在第一步就被卡住了。我早期帮朋友排查这个问题的次数,比想象中多得多。先解释一下为什么会出现这个提示:Kaggle的注册页面依赖一个人机验证组件,需要浏览器从外部加载一段JavaScript脚本才能渲染出验证码框。如果这段脚本加载失败、被拦截、或者组件还没有渲染完你就点了提交,页面就会提示"captcha must be filled out"。
我的排查思路是逐层排除环境问题,而不是反复刷新页面:
- 换一个现代浏览器试试,比如Chrome、Edge或者Firefox。有些老版本浏览器对验证码组件支持不好,切换到无痕窗口也能排除插件干扰。
- 关闭浏览器里的广告拦截类扩展。很多拦截器把人机验证iframe误判成广告,加载到一半就给砍掉了。
- 清理该站点的Cookie和缓存后重新访问。如果之前有一次失败的注册请求,残留的本地状态可能干扰后续加载。
- 打开注册页后不要急着点提交,等验证码组件完全渲染出来。我在部分网络条件下遇到过需要等待10到20秒才出验证码的情况。
- 检查系统时间是否准确。人机验证服务依赖时间戳,系统时间偏差太大会出现校验失败。
- 如果以上操作都无效,可能是服务端临时波动或网络不稳定导致的加载超时。换一个更稳定的网络环境,或者错峰再试。
还有一个容易忽视的点:不要高频刷新注册页。触发风控之后反而更难出现验证码。如果注册时要求填写邮箱验证码或手机号验证,尽量一次性填对,避免反复提交。
2.2 数据集的三种获取方式:网页下载和命令行API
账号注册完成后,进入比赛主页就可以准备拿数据了。比赛页面地址在Kaggle官网搜索"house-prices"即可找到。数据集包含四个主要文件:train.csv(1460行训练数据)、test.csv(1459行测试数据)、data_description.txt(字段说明文档)和sample_submission.csv(提交样例)。
第一种方式是网页下载。在比赛页面Data页签下,点击Download All就能把整个数据集打包下载。优点是直观,缺点是如果网络不稳定容易中断。
第二种方式是Kaggle命令行API,也是我现在最推荐的方式。先在Kaggle账号设置里创建API Token,下载一个kaggle.json文件,放到~/.kaggle/目录下。之后终端里执行:
bash复制pip install kaggle
kaggle competitions download -c house-prices
unzip house-prices.zip
这里要注意,对局(competition)下载前需要在比赛页面点击Agree按钮同意规则,否则API会提示403。下载完成后最好抽查一下文件行数和大小,避免中途断网导致文件损坏,拿一个残缺的train.csv去跑建模,后面所有结论都会跟着出错。
2.3 本地环境清单:跑通房价预测需要装什么
我的建议是使用Python 3.8以上的环境,核心库按需安装。这个比赛不需要GPU,普通笔记本的CPU就足够跑完整个流程。
bash复制pip install numpy pandas scikit-learn matplotlib seaborn xgboost lightgbm optuna
其中xgboost和lightgbm是树模型的主力,optuna用来做超参数搜索,seaborn用来画EDA图表。这里提醒一句:不必盲目追求最新版本。Kaggle很多历史kernel跑在特定版本上,新版库偶尔会修改默认参数或者废弃旧接口。比如LightGBM在较新版本里对某些参数名做了调整,照搬网上旧代码可能直接报错。碰到这类问题,我会固定一个与参考资料兼容的版本,而不是反复折腾环境。
本地环境准备完毕,下一步就是读数据、做分析。别急着建模,先把数据长什么样看清楚。
3. 先别急着建模,把Ames房价数据彻底吃透
3.1 数据形态:81列里哪些是"假数值"变量
加载train.csv后,你会看到一个1460行81列的DataFrame。第一列是Id(房屋唯一标识),最后一列是SalePrice,中间79列是特征。先别急着看相关性热力图,第一步应该做的是逐列检查类型和唯一值数量:
python复制import pandas as pd
train = pd.read_csv('train.csv')
print(train.shape)
print(train.nunique().sort_values())
重点要警惕的是那些"看起来是数字,实际是分类"的列。最典型的例子是MSSubClass(房屋类型),取值是20、30、60、120这种编码,数字之间的大小完全没有数学意义,20不等于10加10,60也不大于30的两倍。如果直接当作数值特征喂给线性模型,模型会学出一堆没有逻辑的系数。同样需要留意的还有OverallQual(整体材料质量,1到10的有序等级)和OverallCond(整体状况等级),它们虽然也是整数,但可以当作有序类别来编码,也可以当作数值变量,两条路都试一下,用CV分数来裁决。
另一个高基数特征是Neighborhood(街区),它有20多个类别。类别型变量不能直接进线性模型,需要做one-hot编码或者序号编码;对树模型则可以留作类别特征交给LightGBM处理。数据形态的判断直接决定了后续编码方案,这个阶段花20分钟把每一列的类型确认清楚,后面会省下很多返工时间。
3.2 目标变量为什么要做log1p变换
看一下SalePrice的分布,你会发现它明显右偏:大多数房子在10万到20万美元之间,少数豪宅能到50万甚至更高,长尾拖得很长。这种偏态分布对线性模型特别不友好,因为模型会花很大力气去拟合尾部高价房,普通价位的预测反而不够准。而且这个比赛的评估指标本身就是在对数空间计算的,所以最自然的做法是先对SalePrice取对数,训练完成后再用指数还原。
python复制import numpy as np
y = np.log1p(train['SalePrice'])
为什么用log1p而不是log?因为log1p计算的是log(1+x),在x为0时依然有定义。房价不太可能为0,这只是工程上的防御性写法——反正log1p和log在数值上几乎没区别,习惯了就不用每次判断边界。提交预测时再用np.expm1还原为实际房价。
对目标变量做变换是整个项目中最划算的"免费提升"。很多新手忽略这一步,直接拿原始房价训练回归模型,在线性模型下会明显吃亏。而对数变换后,既贴合了RMSLE指标,又意外缓解了异方差问题。
3.3 缺失值的"有含义"与"无含义",填法完全不同
Ames数据集的缺失值非常有教学价值,因为它的缺失分两种。一种是真正的数据缺失,比如LotFrontage(临街长度)因为地块特殊没测量到;另一种是"因为不存在而没有值",比如PoolQC(泳池质量)为NaN,代表的不是"数据漏了",而是"这栋房子压根没有泳池"。
我见过不少人在Missingno图上看到一堆NaN,想都不想就填了中位数,这其实是把有效信息给抹掉了。对于PoolQC、MiscFeature、Alley、Fence、FireplaceQu这类"无即信息"的列,正确做法是先转换成字符串,然后用'None'填充,再交给编码器去处理:
python复制for col in ['PoolQC', 'MiscFeature', 'Alley', 'Fence', 'FireplaceQu']:
train[col] = train[col].fillna('None').astype(str)
对于Garage和Bsmt相关的列,处理逻辑类似:如果GarageCars为NaN,通常表明没有车库,那么GarageArea也应该填0;如果地下室相关列是NaN,面积同样填0。这条逻辑可以用两行代码做精确覆盖。最省事但不太严谨的做法是把所有数值列缺失填0、类别列缺失填'None',再加上KNN填充,实际测试下来效果不差,但理解"缺失的含义"能让你在特征工程上有更多主动权。
3.4 几个高性价比的特征工程:面积、房龄、质量分
网上关于Ames的特征工程方案多如牛毛,真没必要全抄。我按性价比排序,最实用的就三类。
第一是面积。房价和面积的相关性几乎是所有特征里最高的。Ames数据把面积拆成了地下室面积(TotalBsmtSF)、一楼面积(1stFlrSF)、二楼面积(2ndFlrSF),可以组合成总居住面积:
python复制train['TotalSF'] = train['TotalBsmtSF'] + train['1stFlrSF'] + train['2ndFlrSF']
第二是房龄和翻新。YearBuilt是初次建造年份,YearRemodAdd是最近一次翻新年份。两者相减如果是正数,说明房子在后续年份被翻新过,这比单纯看建造年份更有信息量。还可以算一个"当前房龄":用一个固定基准年减去YearBuilt,虽然不精确,但作为相对排序变量足够用。
第三是质量分交互。OverallQual与TotalSF相乘,构造一个"面积乘以质量"的交互特征,它抓住的是"大而好"和"大而糙"的差异。类似思路还可以用在OverallQual与OverallCond的组合上,因为等级与状况通常呈正相关,联合特征能揭示个别"高等级但低状况"的异常房屋。
做完这三个方向,特征数量不用堆太多,一个Ridge模型加5折交叉验证就能跑到0.13左右,已经超过相当一部分公开kernel了。做特征工程时记得始终保持train和test使用同一套编码规则——对train做fit,再对test只做transform,防止两者类别集合不一致。
4. 建模路线的选择:从正则化线性模型到树模型大融合
4.1 为什么先用Ridge和Lasso打底
很多新手一上来就上XGBoost,这是个误区。先跑一个线性模型至少有三个好处:第一,用最少的调试成本拿到一个可靠的baseline,后续所有模型改进都有了参照物;第二,特征处理中的编码错误往往在线性模型里暴露得最快;第三,Ridge在Ames数据集上配合足够好的特征工程,成绩并不差。
Ridge是L2正则化的线性回归,适合处理特征间存在共线性的场景。Ames特征经过one-hot后维度可能超过两百,特征之间相关性很高,L2正则能约束系数幅度,防止模型被个别强相关特征带走。Lasso则做L1稀疏化,能自动把无效特征权重压到0,有点特征选择的味道。实际使用中我更喜欢ElasticNet(L1和L2的加权组合),两种正则的收益都能吃到。
代码骨架很简单:
python复制from sklearn.linear_model import Ridge
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import cross_val_score
model = Pipeline([
('scaler', StandardScaler()),
('ridge', Ridge(alpha=12.0))
])
scores = cross_val_score(model, X_train, y, cv=5, scoring='neg_root_mean_squared_error')
print(-scores.mean())
需要提醒的是:线性模型对特征尺度敏感,必须经过StandardScaler标准化;类别变量要做one-hot编码,不能直接把字符串丢进去。这一步跑通,后续树模型就在同样交叉验证框架上实验,评分可以直接对比。
4.2 XGBoost与LightGBM的工程细节
树模型几乎不需要特征标准化,对缺失值也天然有处理策略,所以从Ridge切到XGBoost时,代码变化不大,主要是模型类替换和参数调整。
XGBoost和LightGBM两者我都用,但角色略有分工。XGBoost在中小数据集上更稳,默认参数翻车概率低;LightGBM训练速度快,但Leaf-wise的生长方式在小数据集上更容易过拟合,必须限制num_leaves和min_data_in_leaf。CatBoost则对类别特征有原生支持,如果不想手动做太多编码,可以把它当成备选。
参数上我给一组经过实测的起始值:
python复制import lightgbm as lgb
params = {
'objective': 'regression',
'metric': 'rmse',
'learning_rate': 0.02,
'num_leaves': 31,
'max_depth': 5,
'min_data_in_leaf': 20,
'feature_fraction': 0.8,
'bagging_fraction': 0.8,
'bagging_freq': 1,
'verbose': -1
}
配合Early Stopping来判断最佳迭代轮数,比手动设定n_estimators省心得多。调参顺序建议是:先固定learning_rate,再逐步调max_depth和min_data_in_leaf,然后调特征采样和样本采样比例,最后才动正则化系数。每次只动一个参数,拿交叉验证分数看变化,不要一上来就搞网格搜索参数组合爆炸。
交叉验证方面,我会用shuffle=True的KFold,折数取10或者5都行。如果担心不同折之间目标分布差异太大,可以先把log1p后的SalePrice分箱,再用StratifiedKFold按分箱标签做分层,这样每折的目标分布接近,训练更稳定。
4.3 用OOF策略做模型融合,别把验证集喂成了自己的"答案"
模型做到一定程度,单模型很难再有明显提升,这时候该考虑融合了。最简单的融合是预测值平均:把Ridge、XGBoost、LightGBM、CatBoost各自在测试集上的预测做算术平均,或者按验证集表现加权平均。别小看这个操作,它往往能带来零点几个百分点的CV提升,而且不引入过拟合风险。
如果还想更进一步,可以用Stacking。第一层多个模型各自输出预测,第二层用一个简单的线性模型(通常是Ridge)学着怎么组合这些预测。这里最关键的一个概念是OOF(Out-Of-Fold)预测:每个模型在K折交叉验证中对各自验证折产生的预测要单独存下来,第二层模型只能吃这些OOF预测,绝不能拿模型在训练集上的预测来当第二层特征。否则第二层模型看到的是一堆"开卷答案",融合结果在本地看着飘红,一到测试集就变脸。
我的融合策略分享:
- 第一轮用5折,得到每个模型的OOF预测。
- 对比各模型OOF与真实y的相关系数,相关系数低的模型可能不是差模型,而是预测模式不同,融合价值反而高。
- 先用网格搜索给各模型分配权重(比如限制权重非负、和为1),再做简单平均。
- 不出意外的话,融合后的OOF分数会略微好于最好的单一模型。
5. 提交得分的那些坑:格式、指标和公共榜心理战
5.1 提交文件格式的三个常见错误
辛苦跑完模型,最后在提交环节翻车的案例我见过太多了。这个比赛要求提交一个CSV文件,里面只有两列:Id和SalePrice。第一个常见错误是列名写错,比如写成"id""salesprice",系统会提示列缺失。第二个常见错误是行数对不上,test.csv是1459行,输出文件必须是1459行,多一行少一行都会报错。第三个常见错误是忘了对预测值做expm1还原,直接把log1p空间的预测提交上去,分数会差得离谱。
一个稳妥的提交模板:
python复制submission = pd.DataFrame({
'Id': test['Id'],
'SalePrice': np.expm1(pred_test)
})
submission.to_csv('submission.csv', index=False)
# 提交前自检
check = pd.read_csv('submission.csv')
print(check.shape)
print(check.head())
我曾见过有人把训练集的Id也混进提交文件,结果行数变成2919,白白浪费一次提交机会。自检这一步别看简单,能救命的。
5.2 RMSLE指标与预测负值问题
这个比赛用的RMSLE我前面解释过,是log(1+真实值)和log(1+预测值)之间的均方根误差。这里有一个容易被忽略的坑:如果你的模型预测出了负房价,log(1+负数)会直接变成NaN。树模型在极端情况下可能会预测出小于0的值,线性模型更有可能。因此在提交前,稳妥做法是把所有预测值clip到0以上:
python复制pred_test = np.clip(pred_test, 0, None)
这个操作不会对正常预测造成影响,但能彻底避免NaN错误。用模型输出直接算RMSLE之前,也建议对OOF预测做同样的clip,否则验证集分数会严重失真。
很多新手会犯的另一个错误是:本地用MSE或者RMSE调参,而不是用竞赛指标RMSLE。因为一个在普通RMSE上表现好的模型,在RMSLE口径下不一定好。调参、选特征、融合权重都以RMSLE为准,整个项目才不走偏。
5.3 Public榜和Private榜:水榜一时爽,换榜火葬场
Kaggle竞赛的排行榜在比赛期间显示的是Public Leaderboard,它只计算测试集一部分样本(一般是一半);比赛结束后,用另一部分样本重新排名,产生Private Leaderboard。这个机制造成了一个经典心理陷阱:有人Public阶段排名前10,看起来很厉害,结果Private阶段掉到50%开外。
为什么会出现这种翻车?因为参赛者会反复参考Public LB来调参和调特征。如果调参方向是"哪个参数让Public LB更高就选哪个",就会把注意力集中在Public子集上,相当于在没见过的Private子集上过拟合了Public子集。这是数据竞赛中最微妙的过拟合形式。
我的态度很明确:把本地CV当作第一参考,Public LB只是风向标。理想情况下,本地CV好的模型在Public LB上也应该不错;如果某个操作让本地CV提升但Public LB反而下降,先别急着回退,检查是不是两个子集差异导致波动,不要被单次LB成绩刺激。比赛不是冲刺,而是稳定输出。
6. 从Ames房价走出来:这套打法还能迁移到哪里
6.1 波士顿房价:小数据集里回归问题的天花板
打完Ames再回头看波士顿房价,你会轻松很多。sklearn里加载波士顿数据这行代码在部分版本里已经因为数据集的伦理争议被移除了,所以我会直接从公开渠道获取CSV版本,或者使用加载函数里标注替代数据集的调用方式。波士顿数据只有506行、13个特征,没有缺失值,特征也是纯数值,整个训练过程可能一分钟都用不到。
这种小数据集对模型选择非常挑剔:复杂模型特别容易过拟合,反而是Ridge加上简单特征能到不错水平。你可以把Ames的流程完整跑一遍,但会发现特征工程几乎没有施展空间——13个特征都是预处理好的一维数值,没有类别变量、没有时间列、没有可组合的原始字段。所以它更适合作为"验证你的代码流程是否正确"的冒烟测试,而不是作为练习竞赛水平的项目。
6.2 用AI辅助做房价预测的新玩法(回复提示词示例)
最近不少人开始用生成式AI辅助数据竞赛,这个方向其实挺有意思。我自己试过的有效用法之一是让AI审查特征工程方案和潜在风险。比如我会把列名清单和缺失值策略发给AI,让它找出可能的数据泄露点,或者让它对特征组合优先级排序。
这里放一个可参考的提示词模板:
text复制这是Kaggle房价预测数据集的列名和类型清单(附上你的DataFrame.info()输出)。
请按特征工程价值从高到低排序,并说明每组特征组合的理由。
同时请指出哪些特征组合可能存在数据泄露风险。
最后给出3个你一定会尝试的交互特征,并解释原因。
AI生成的结果只能当参谋,不能当决策官。任何特征组合和建议最后都要过一遍交叉验证,用分数说话。我踩过的一个典型坑是:AI建议了一个看起来很合理的交互特征,结果放进交叉验证后分数反而下降,原因是这个特征在测试集上的分布和训练集差异较大。所以它的价值更多在于提供思路,而不是替代你的验证环节。
6.3 回归竞赛的通用套路:换汤不换药
这一整套打Ames的流程,迁移到其他预测类比赛时几乎不需要改动骨架。销售预测、租金预测、Airbnb房源价格预测,甚至UCI上的各类二分类任务,底层流程都是"读数据-EDA-清洗-特征工程-基线模型-调参-融合-提交"。区别只在于目标变量是连续值还是类别标签,以及评估指标是RMSLE还是LogLoss/AUC。
如果是二分类,只需把目标编码改成0/1,评价指标换成LogLoss或AUC,最后一层输出改成概率而不是价格。特征工程和交叉验证的逻辑可以完全复用。这个通用套路是我认为打Kaggle最值得沉淀的能力。
最后分享一个我实际养成的习惯:把所有模型的OOF预测保存成npz文件,后续做任何融合实验都不用重跑模型,几秒钟就能加载数据试权重。这个习惯让我在Ames项目后期省下大量等待时间。另一个体会是,别太迷信排行榜上的Public成绩,本地CV稳定提升才是真正属于你的进步。这个比赛跑完之后,你可以尝试把同一套流程迁移到Airbnb房价或者其他回归任务,就会发现自己在不知不觉间已经是一个能独立参赛的人了。
