这是一个 kernels-only 的 Kaggle 代码赛:预测水平井的 TVT(真垂厚)。我认真做了十几版,最后公榜最优 RMSE 14.759,在约 4628 支队伍里排到三千四百多,倒数四分之一——铜牌线大概是 7.2,我离得很远,没进牌区。
所以这篇不是战果展示。把它写出来,是因为一个”我认真做了、诚实承认没夺牌、但把为什么讲透了”的过程,比一个漂亮名次更值得留下。这里有三件事。
一、CV 口径错了,我优化的是幻觉
一开始我的本地交叉验证分数(MSE 一百七八十)和公榜分数(14.759)怎么都对不上,差了一个数量级。查下来是两个口径错误叠在一起:
- 公榜只在原本缺失的那些行上评分。 提交样例里 100% 的行都是待预测的空值,而我的 CV 是在全部训练行上算的——里面混了大量”已知答案、残差接近 0”的行,把误差稀释得看不出真实水平。
- 榜单显示的”MSE”其实是 RMSE。 我把本地 MSE 开个平方根,
sqrt(237) ≈ 15.4,一下就和公榜对上了。
修法是:CV 只在原缺失行上算、按井分组做 leave-one-well-out、并且当 RMSE 算。教训很硬——在校准评估口径之前,你所有的”这版更好”都可能是在对着错误的分布优化。 我前面好几版的进退,有一部分就是白忙。
二、一处数据泄漏,我发现了,又亲手证伪了
排查时发现训练目录里藏着和三口测试井文件名完全相同、坐标差为 0、逐行数值一模一样的完整未打码版本——理论上可以直接查到答案。
但有个矛盾:如果泄漏真能用,应该能刷到接近 0 分,可公榜榜首才 4.859。于是我没有直接用它去刷分,而是构造了一个”泄漏解”提交上去验证:结果提交状态显示完成,但公榜分返回空白。
这就把泄漏证伪了:kernels-only 代码赛评分时是用隐藏的真实测试集重新跑的,我在训练目录里看到的那批”测试井”,根本不是评分用的同一批。这个结论很反直觉,也很重要——你能看到的 test 集不等于评分集,基于它做的一切 CV,都可能在对着错误的分布优化(又一次回到第一件事的教训)。
三、一个负面结果:曲线匹配打不过一条平线
这道题我一开始按”地质导向(geosteering)“去想,以为要做井间的曲线插值匹配。做进去才发现题目结构是另一回事:每口井开头一小段(约 26%)是已知的,之后整段水平段全是空值一直到井尾——这是纯前向外推,不是插值。
于是最强的一个基线简单到离谱:保持已知段最后一个 TVT 值一路平推(flat 外推),RMSE 就有 14.7~15.9。 我随后试遍了值匹配、Viterbi、DTW、仿射标定、形状匹配、跨井漂移的 ML 模型五大类方法,没有一个在诚实的 CV 上稳定打过这条平线。
物理根因是真实的:井被主动导向、刻意保持 TVT 恒定留在目标层里,剩下的残差主要是导向误差,本身就接近不可预测;而用来定位的伽马曲线,在需要的那个 ±12 的细尺度上,噪声就有十几个单位,分辨率根本不够。
唯一一次在诚实 CV 上小胜 flat,是把几十个”单独都不如 flat”的弱特征全塞进一个 LightGBM+CatBoost 的组合模型,留井 CV 从 15.91 压到 13.62。但要如实标:这只是 CV 数字、没有真实上榜验证,而且离牌区需要的 7~9 仍然差得远。
诚实的边界
- 成绩就是差。 唯一真实上榜的分数是倒数四分之一,远在牌区外。这次同时开了另外两条 Kaggle 线(一个 agent 安全攻防、一个卡牌对战 bot),都只做到”摸清提交格式 + 写个空/随机基线”就主动砍掉、集中资源到这一条——那两条没有任何有效上榜,也就没什么可展示的。
- 13.62 打过 flat 只在 CV,没上榜。 公榜前排那批到底是真泛化还是也踩了某种数据污染,我没有精力完整复现证实,存疑。
- 代码是 AI 写的,我主导的是需求、CV 方法论、泄漏排查和结果判读——这次真正有价值的贡献恰好都在”怎么判断一个结果可不可信”这一层,而不是写了多少模型。
一个负面结果、加上把它为什么负讲清楚,有时候比一块奖牌更接近真实的工程——至少它诚实。