第一次看到 London House Price Prediction 这个题目时,我第一反应是:房价预测,又是经典回归题,应该不难。但真把数据下下来跑过一轮之后才发现,这个项目背后的“Advanced Techniques”不是白写的——它真正考验的不是你会不会调一个 XGBoost,而是你有没有一套完整的特征工程思路、验证策略和集成手段,能不能在中等规模数据集上把成绩从“能跑”推到“高分”。这篇文章我就围绕这个 Kaggle 竞赛项目,从数据理解、特征挖掘、模型选型到调参与排错,完整梳理一遍我的实操路线,适合正在入门 Kaggle 回归赛、或者想在表格数据领域把分数再往上提一档的朋友参考。
1. 项目背景与数据集初步解析
1.1 竞赛目标与数据集结构
这个竞赛要解决的是一个标准的监督学习回归问题:给定伦敦地区一批房产的交易属性,预测成交价格。从题目名称看,它隶属于 Kaggle 上典型的“Playground 风格”竞赛,但数据规模和特征噪声都设计得很有代表性——没有到你随便跑个线性回归就能拿高分的程度,也没复杂到需要大规模深度学习才能处理。
原始数据通常包含以下几类字段:
- 房产的基本属性:卧室数量、卫生间数量、面积、房屋类型(公寓、联排别墅、独栋等);
- 地理位置信息:经纬度坐标,这是伦敦房价预测里权重最高的信息源;
- 时间信息:挂牌时间、成交时间或者建筑年份;
- 目标变量:最终成交价格,呈显著右偏分布。
我拿到数据后做的第一件事不是直接跑模型,而是把字段逐一列出来,判断每个字段是数值型还是类别型、缺失比例有多高、与房价的相关性大概是什么方向。这个步骤看起来基础,但对后续特征工程的走向影响非常大——比如一开始我以为是面积对房价影响最大,实际跑完相关性分析后发现,位置特征的预测能力远超面积,这个认知直接改变了我后面的大部分时间投入方向。
1.2 为什么这个题目是“特征工程驱动”型任务
伦敦是全球房价差异最悬殊的城市之一。同样是一套两居室,在金融城附近和伦敦郊区,单价可能相差三倍以上。这意味着模型必须能够“理解”地理位置,否则仅靠房型、面积这类常规特征,模型预测的天花板非常低。
所以这个题目的“Advanced Techniques”核心并不在于用多么复杂的神经网络,而在于三点:
- 如何把经纬度这种连续坐标转化为模型容易利用的信息;
- 如何组合多个低阶特征,生成对价格有强解释力的交互特征;
- 如何通过合理的验证方式,让模型在不同区域、不同时间段都能保持稳定。
我采用的第一层位置特征处理方案是直接计算每个样本到伦敦几个核心地标的距离,用哈弗辛公式(haversine)算球面距离。这一步做下来,模型分数立刻有了明显提升。后续我又在这基础上扩展了格网划分、区域聚类等特征,每一层都有稳定的增量。
提示:做位置相关特征时,尽量用“距离”或“区域编号”这类对树模型友好的形式,而不是直接把经纬度两个裸数值丢进去。树的切分逻辑本质上是在做阈值判断,裸经纬度虽然也能切,但很难表达“距市中心 15 分钟车程”这种连续语义。
2. 数据清洗与特征工程的实战拆解
2.1 缺失值处理:不只是“填”那么简单
这个数据集的缺失值情况不算严重,但每个字段的缺失逻辑不一样,处理方式也不同。比如卫生间数量有些行是空的,很多情况下是挂牌信息没填全,而不是真的没有卫生间;有些字段比如建筑年份,它缺失可能意味着老房子或信息不全,这本身就包含信号。
我的处理策略分三种:
- 类别型字段缺失:用该列最频繁出现的值填充,同时增加一个“is_missing”二值特征,让模型自己判断这条样本是否缺失;
- 数值型字段缺失:优先用中位数填充,避免被个别极端值带偏;
- 有明确业务含义的字段:例如年份,如果缺失比例不高,直接删除对应行;如果比例较高,则单独归为一类“未知”,并配合 is_missing 特征。
实际测试下来,is_missing 特征在某些字段上的增益比中位数填充本身还高。原因是缺失本身往往和挂牌粗糙度有关,而挂牌越粗糙的房子,价格通常越低,模型能捕捉到这种隐含关系。
2.2 目标变量为什么要取对数
大部分房价数据集的分布都是严重右偏的——少数几套千万级别的豪宅会把均值拉得很高,但如果直接对原始价格建模,模型会把大量精力花在拟合这些极端值上,导致普通房子的预测反而不准。
解决办法是目标变量做对数变换:
y_log = log1p(price)这样做的本质是把误差惩罚从“绝对金额误差”变成“相对比例误差”。预测一个 50 万的房子相差 5 万,和预测一个 500 万的房子相差 5 万,在 log 空间里的损失量级是接近的;但在原始空间里,前者造成的误差会显著大于后者。这对业务也是合理的——买 500 万房子的人对 5 万误差的敏感度和买 50 万房子的人完全不同。
训练时使用 log 后的价格作为回归目标,提交预测时再用指数函数还原:
price_pred = expm1(y_log_pred)这里有一个细节经常被忽略:直接对 log 空间的预测值做指数还原,得到的预测均值会比原始分布的真实均值偏低,因为指数函数的非线性导致误差分布不对称。实践中可以用 smearing 估计量修正,即:
price_pred = expm1(y_log_pred) * mean(expm1(y_true_log) - expm1(y_pred_log))具体做法是计算训练集上真实值与预测值比例的平均值,乘以测试集预测结果来微调。这个修正通常能让分数小幅提升。
2.3 经纬度特征:从坐标到知识的转换
经纬度是这个数据集里信息密度最高的字段,但在原始形式上,两个整数浮点数对树模型来说既难切分也不直观。我的处理分三个层次:
第一层:距离特征。选几个有代表性的参考点——金融城、主要交通枢纽、大型公园——用哈弗辛公式计算每个样本到这些点的距离。参考点不用多,四到六个足够。核心代码逻辑如下:
import numpy as np def haversine(lat1, lon1, lat2, lon2): R = 6371.0 lat1_rad = np.radians(lat1) lon1_rad = np.radians(lon1) lat2_rad = np.radians(lat2) lon2_rad = np.radians(lon2) dlat = lat2_rad - lat1_rad dlon = lon2_rad - lon1_rad a = np.sin(dlat / 2) ** 2 + np.cos(lat1_rad) * np.cos(lat2_rad) * np.sin(dlon / 2) ** 2 return 2 * R * np.arcsin(np.sqrt(a)) train["dist_center"] = haversine( train["latitude"], train["longitude"], center_lat, center_lon )第二层:格网划分。把经纬度按不同粒度切分成格子,每个格子生成一个类别 ID。比如按 0.01 度、0.05 度、0.1 度三个粒度分别切,这样模型既能看到精细的小区域,也能看到粗粒度的大片区规律。
第三层:区域聚类。用 KMeans 对经纬度做聚类,把每个样本归入某个空间簇,然后将簇中心距离、簇编号作为特征。这一步相当于让模型自动发现“哪些位置的房价模式比较接近”,比手动设定地标要全面得多。
三层特征叠加后,模型的验证 R² 提升了至少 0.02,可以说这是整个项目里收益最高的特征工程操作。
3. 建模策略与验证策略设计
3.1 主力模型为什么选梯度提升树家族
表格类数据的建模,梯度提升树(GBDT)家族基本是标配,这个竞赛也不例外。我分别测试了 LightGBM、XGBoost 和 CatBoost,最终的结论是:三个模型各有优劣,适合做集成,而不是只押注某一个。
| 模型 | 优势 | 需要注意的点 | 适合场景 |
|---|---|---|---|
| LightGBM | 训练快、内存占用低、支持直方图加速 | leaf-wise 生长容易过拟合,需限制深度 | 大规模数据、快速迭代 |
| XGBoost | 正则化强、对缺失值有内置学习方向 | 调参维度多,默认参数往往不是最优 | 中小规模数据、追求精度 |
| CatBoost | 原生支持类别特征、有序提升防目标泄漏 | 训练相对慢,类别特征需显式声明 | 高基数类别特征多的场景 |
我个人的主力选择是 LightGBM。它的直方图算法在大样本下迭代速度极快,而且 leaf-wise 生长策略配合 max_depth 限制,实际调参可控性很强。但 CatBoost 的优势在于它对类别特征的编码方式——这个数据集里的房屋类型、格网 ID 都是高基数类别变量,CatBoost 能直接以原生形式处理,省去了手动 one-hot 或 target encoding 的麻烦。
3.2 交叉验证:别让验证分数骗了你
这是整个项目里最容易被低估的一环。常规做法是直接train_test_split或随机 KFold,但对于房价预测这种带有强空间相关性的数据,随机划分会让验证集里出现训练集的“邻居”,模型分数虚高。
我踩过这个坑。第一次用随机 KFold 跑 LightGBM,验证 R² 到了 0.91,当时还挺高兴,提交后 LB 只有 0.87,差距非常大。后来才意识到,同一片区域里不同房源的价格高度相关,随机划分把大量位置信息“泄漏”到了验证集里。
解决方案有两种:
- 按格网 ID 做 GroupKFold,保证同一区域的样本不会被同时分到训练集和验证集;
- 按时间排序后切分,用前 80% 的时间段训练,后 20% 预测,模拟真实场景中的“用已知预测未知”。
两种方案我都试过,最终选择了 GroupKFold 作为主验证方式,因为它在不损失太多训练样本的前提下,能更真实地反映模型对新区域的泛化能力。虽然验证分数下降了 0.03 左右,但和 LB 的匹配度大幅提升。
3.3 调参路线:先粗调,后细调
调参这件事,我不建议一上来就上 Optuna 或 GridSearch。我的流程分三步:
第一步,用默认参数跑通基线,确认代码流程没问题,记录验证分数; 第二步,粗调核心参数——学习率、树数量、最大深度,找到大致的最优区间; 第三步,再对 feature_fraction、bagging_fraction、min_data_in_leaf 做小范围搜索。
常用的 LightGBM 参数区间参考:
params = { "objective": "regression", "metric": "rmse", "learning_rate": 0.01, "num_leaves": 63, "max_depth": 7, "feature_fraction": 0.8, "bagging_fraction": 0.8, "bagging_freq": 1, "min_data_in_leaf": 50, "verbose": -1, }注意一点:learning_rate和num_boost_round必须联动调整。学习率越低,需要的树越多,训练时间越长。我的策略是用 0.01 的学习率和 early stopping 回调,让模型自动决定树的数量,这样比手动固定树数要稳健得多。
4. 集成方法与预测后处理
4.1 为什么单一最强模型不够
在 Kaggle 竞赛里,单一模型即使调参调得再好,排名也容易遇到瓶颈。核心原因有两个:
一是模型方差。同一个模型不同随机种子跑出来的验证分数会有波动,单次提交等于在赌运气; 二是模型偏差。LightGBM 和 CatBoost 对特征的利用方式不同,它们在捕捉价格模式时各有盲区。比如 LightGBM 对缺失值的处理是学习一个默认方向,而 CatBoost 对高基数类别特征的处理是全自动目标统计,两者在同一份数据上的错误样本往往不重合。
把多个不重合的错误来源叠加在一起,错误会互相抵消,整体精度反而提升。
4.2 五折 Stacking 的完整流程
我在这个项目里用的集成方案以 Stacking 为主、加权平均为辅。具体流程如下:
- 将训练数据按格网 ID 分成 5 折;
- 每个基模型(LightGBM、XGBoost、CatBoost)分别在 4 折上训练,对第 5 折和测试集做预测;
- 5 次循环结束后,把验证预测拼接成 OOF(Out-of-Fold)预测,测试集预测取 5 次平均;
- 用 OOF 预测作为新特征,训练第二层模型——一般用岭回归或者简单的线性回归即可;
- 第二层模型的输出作为最终预测。
Stacking 的一个关键细节是:第二层模型的输入只有基模型的 OOF 预测,绝对不能用测试集预测去训练。否则第二层模型会“看到”测试集的信息,导致提交结果失真。
另外,基模型之间的相关性要控制。如果三个模型全是同一种算法不同参数,Stacking 的收益会很小;但 LightGBM + XGBoost + CatBoost 这种异质组合,增益相对明显。我最后提交用的权重大概是 LightGBM 0.4、CatBoost 0.35、XGBoost 0.25,在私有榜上的表现比任何单一模型都高。
4.3 预测值还原与分布对齐
模型输出的是 log 空间的预测值,提交前必须还原成真实房价。这一步本身不复杂,但有几个细节值得留意:
第一,expm1还原后,预测分布整体会比训练集真实分布略微偏低,这是对 log 目标做均值回归的固有现象。可以通过计算训练集上真实价格 / 预测价格的均值作为缩放系数,对测试集预测做整体微调。
第二,一定要检查测试集预测的分布形状。我一般会把测试集预测和训练集真实价格的直方图画在一起对比,如果形状差异明显,说明模型可能存在过拟合或者数据泄漏,需要回到前面的步骤排查。
第三,提交文件前检查 ID 顺序。Kaggle 平台对提交格式有严格限制,少一行、多一行、顺序错乱都会被判 0 分。这个错误看似低级,但每次竞赛都会有一批人踩坑。
5. 常见坑点与实战排错
5.1 平台使用类问题
先回答一个很多人私信问我的问题:Kaggle 是什么?简单说,它是一个数据科学竞赛平台,企业或机构在上面发布真实业务问题,全球的数据科学家提交模型,以量化指标排名。伦敦房价预测这个竞赛就是典型的数据科学实战场景,特别适合用来练习完整的数据处理与建模流程。
关于注册,确实有朋友遇到过收不到验证邮件的情况。常见原因就是邮箱把激活邮件放进了垃圾箱,或者邮箱服务商对平台邮件做了拦截。处理办法很简单:换一个常用邮箱重新注册,或者检查邮箱的垃圾邮件目录。密码强度要求较高,必须包含大小写字母、数字和特殊字符,这也是注册失败的高频原因。如果提交注册后长时间收不到邮件,可以尝试用浏览器无痕模式重新走一遍流程,排除浏览器缓存干扰。
5.2 训练与预测中的典型问题
我在这个项目里遇到并排查过的问题,整理成了一张速查表:
| 问题场景 | 可能原因 | 排查思路 | 解决方案 |
|---|---|---|---|
| 验证分数高,LB 分数低 | 随机 KFold 造成空间信息泄漏 | 对比随机 KFold 与 GroupKFold 的分数差 | 改用按格网 ID 分组的 GroupKFold |
| 预测价格整体偏低 | log 还原后的均值回归偏差 | 画测试集与训练集价格分布对比图 | 用缩放系数对预测做整体微调 |
| CatBoost 效果反而变差 | 类别特征未声明为 category 类型 | 检查特征类型是否被当作数值处理 | 显式转换astype("category")并传入cat_features |
| LightGBM 训练时间长 | 学习率过低且未设 early stopping | 查看训练日志中的迭代数 | 提高学习率并配合 early stopping |
| 提交后分数为 0 | 预测结果与 ID 顺序不对齐 | 检查 submission 文件行数与前几行 | 对齐测试集原始 ID 顺序后重新提交 |
| 多次提交结果抖动大 | 随机种子未固定 | 查看不同种子的 OOF 分数差异 | 统一固定 seed,或用多个种子做平均 |
5.3 独家避坑技巧
最后分享两个我在实际项目中摸索出来的小技巧。
第一个是关于特征组合的。格网 ID 和房屋类型、格网 ID 和建筑年份这两个组合特征,对树模型的增益非常明显。原理是:同一个片区内,公寓和联排别墅的价格基数是完全不同的;同一个片区内,老房子和新房的价格走势也不同。把这些组合直接展开成新特征,等价于帮模型预先做了很多交互逻辑的搜索。
第二个是提交前的小习惯。我会固定随机种子后,把每个模型的五个 fold 预测文件分别保存下来,最后再做平均。这样做的价值在于:如果某次预测出现异常(比如某个 fold 的预测分布明显偏离),可以单独排查,而不是等到提交后才发现问题。
6. 一点个人心得与后续扩展思路
到这一步,整个伦敦房价预测的流程基本完整了。从数据清洗、特征工程,到模型训练、调参、集成,再到最后的预测还原,每一步都有明确的决策逻辑。我自己在这个项目里最大的体会是:Kaggle 竞赛真正锻炼的不是调参手速,而是对数据的“嗅觉”——你能不能在看到经纬度的第一眼就意识到位置价值的权重,能不能在验证分数虚高时保持冷静重新审视划分方式,这些判断力才是从竞赛到实际工作最可迁移的能力。
如果后续想继续扩展,我会建议往模型可解释性方向挖一层。用 SHAP 值分析一下哪些特征在驱动伦敦房价的预测结果,不仅能帮你自己理解模型的决策逻辑,放在业务场景里,也能直接回答“哪类房子具备升值潜力”这类真实问题。这个项目作为一次完整的数据科学实战,值得反复跑几遍,每跑一遍,你都会对模型和数据产生新的理解。