Kaggle NeurIPS Open Polymer Prediction 2025:从入坑到0.069的完整复盘
这是我第一次认真打NeurIPS相关的Kaggle竞赛。最初看到"Open Polymer Prediction 2025"这个名字时,心里其实有点犯怵——聚合物完全不是我的本行,化学式都看得半懂不懂。但最后结果出来,验证集RMSE停在0.069,比我自己赛前预期的好很多。这一趟下来,我最大的感受是:科学类Kaggle竞赛没那么玄乎,关键是把数据处理、特征表达和模型验证这几个环节想清楚。这篇文章就把我踩过的坑和提分的细节一次性写出来,适合正在打类似比赛、或者想靠Kaggle项目练手找工作的朋友参考。
1. 赛题拆解:不是简单的"预测化学结构"
1.1 比赛背景与任务定义
NeurIPS是机器学习的顶级学术会议,每年都会联合Kaggle出一些高质量竞赛题。2025年的"Open Polymer Prediction"全名是开放聚合物性质预测,目标是给定一批聚合物的结构信息,预测它的某种关键物理性质。官方给了一大批实验数据,要求选手用机器学习方法拟合出结构和性质之间的映射关系。这类比赛在学术圈很受关注,因为传统上聚合物的性质依赖实验测定,周期长、成本高,如果能用模型快速预估,材料筛选和配方设计的效率会提高不少。
任务本身是典型的回归问题:输入是聚合物的结构描述,输出是一个连续的数值。难度在于聚合物不是小分子,它是由重复单元组成的长链大分子,结构表达方式和小分子完全不同,不能用简单的SMILES字符串直接扔进模型。这导致很多人一开始把"预测性质"当成了"图像分类"来做,结果精度一直上不去。
1.2 评价指标与分数0.069意味着什么
比赛用的评价指标是RMSE,也就是均方根误差。它的计算方式很直接:对每个样本,把预测值和真实值的差求平方,再取平均,最后开根号。RMSE对离群点比较敏感,所以如果你的模型出现极端预测,损失会被放大得非常厉害。0.069这个分数,放在这个数据集上意味着平均误差大约在0.069个单位左右,如果性质数据的量级是0到10,那么这个误差已经能让很多实验规律稳定复现了。我记得当时排行榜上最顶尖的分数大约是0.058到0.062,我的0.069大概能排在前40%左右,对第一次打这种硬核比赛的人来说,已经算是不错的起点。
拿到这个分数前后,我做了大量特征工程和融合实验。接下来我就按实际操作顺序,把每个环节的关键点拆开讲。
2. 特征工程:把聚合物变成模型能听懂的语言
2.1 原始数据到底给了什么
打开比赛提供的训练集,我第一反应是有点懵。每一行代表一种聚合物,但关键字段并不是标准的SMILES,而是一个叫"重复单元结构"的字符串,有的还带分支和连接方式。此外,官方还给了少量辅助信息,比如聚合物的组成描述、分子量范围、合成条件中的一两个参数,剩下的几乎全靠自己处理。这意味着,没有现成的描述符可以直接喂给模型,必须自己完成从"结构字符串"到"数值向量"的转换。
我测试过几种主流处理方式,效果差异很大,下面这张表记录了我最初实验的对比情况:
| 特征方案 | 说明 | 验证集RMSE |
|---|---|---|
| 直接统计字符计数 | 只用字母出现次数作为特征 | 0.142 |
| 小分子SMILES指纹 | 把重复单元当小分子算RDKit指纹 | 0.113 |
| 自定义聚合物指纹 | 拼接重复单元与端基后再计算指纹 | 0.084 |
| 指纹+手工物理特征 | 加入原子数、可旋转键、拓扑参数等 | 0.077 |
可以看到,直接统计字符完全不可用,而把重复单元当作独立小分子处理也有问题,因为它忽略了聚合物链的整体拓扑。后来我把重复单元字符串、连接片段和可能的端基拼接成一个"伪小分子",再用RDKit计算Morgan指纹,效果明显提升。这里的关键是:聚合物预测的输入不能是单一的重复单元,必须包含链之间的连接信息和端基效应。
2.2 分子指纹与序列编码
特征工程的核心是让模型理解"结构"。我最终采用了三套互补的表示方法。
第一套是Morgan指纹。我用RDKit的GetMorganGenerator,设置半径2,位数为2048。需要注意,聚合物结构往往包含星号、方括号等占位符,在把字符串转换前必须手动清洗,否则RDKit会报错或生成无效分子。清洗的方法是把占位符替换成假原子,例如把[*]替换成[C],再用RDKit的SanitizeMol检查是否能通过语义验证。这一步不复杂,但是非常影响后续所有特征的质量。
第二套是分子序列的n-gram统计。我把重复单元按原子序列拆开,统计原子类型的三元组和化学键类型组合,类似自然语言处理里的n-gram特征。别看这方法原始,但对线性模型和GBDT来说非常有效,因为它能把局部结构信息直接编码成稀疏特征,不会因为指纹折叠造成信息丢失。我用的是拆分后长度为3和4的n-gram,再用哈希技巧把维度控制在2万以内,训练效率很高。
第三套是手工物理特征。这一步靠的是材料领域的基本常识,包括:重原子数量、氢键供体和受体的数量、可旋转键比例、芳香环数量、分子量估算值、拓扑极性表面积TPSA、LogP的粗略估算。这些特征不是模型自己能从指纹里完全学到的,尤其是TPSA和可旋转键比例,对聚合物玻璃化温度这类性质影响很大。我在实验中发现,手工特征单独使用效果平庸,但和指纹特征拼接之后,验证集RMSE能下降0.006到0.009,属于成本最低的提分方式。
2.3 数据增强与无标签数据利用
NeurIPS赛题通常还会提供一批无标签的聚合物结构,官方在竞赛说明里明确鼓励利用它们。这部分数据没有实验值,但是结构信息丰富。很多人把它们扔在一边,实际上这是免费的饭,不吃可惜了。
我做了两种利用方式。第一种是伪标签法:先用有标签数据训练一个较强的模型,然后把无标签数据输入模型,得到预测值,再选取置信度最高的那部分(我用的是预测方差最小的30%)放进训练集重新训练。迭代两次以后,验证集分数提升了大概0.003。第二种是预训练表示法:无视标签,直接对无标签的聚合物字符串做掩码重建,也就是把一部分原子类型遮住,让模型根据上下文预测被遮住的内容,类似BERT的思路。预训练得到的特征再和原特征拼接,也能带来微小但稳定的提升。
不过要提醒一句,伪标签法容易造成过拟合,尤其在数据噪声较高的时候。我的经验是,伪标签样本占总样本的比例不要超过20%,而且要严格用交叉验证评估每次加入伪标签后的效果,一旦发现新模型在公开榜上的分数变差,立刻回退。
3. 模型训练:从GBDT到深度学习的组合拳
3.1 基线:LightGBM快速摸底
我不建议一上来就搞深度学习。第一次提交流程应该是:先用一个LightGBM模型跑通交叉验证,摸清数据底数,同时也是排查特征泄露的最快方法。LightGBM对表格特征非常友好,训练速度也快,一张Kaggle GPU笔记本的CPU核心就能在几分钟内训练完一组参数。
我的基线设置比较简单:学习率0.05,叶子数128,最小数据量20,特征采样0.8,迭代次数靠早停控制在600到1000轮之间。在只使用指纹加手工特征的情况下,五折交叉验证的RMSE大约在0.079到0.081之间。这个结果不算好,但至少证明特征方向没有问题。我还顺便检查了特征重要性,发现Morgan指纹中的前几十个位置贡献了大量信息,这说明结构相似性对性质预测确实有效。
跑完LightGBM基线后,我做了两个快速修正。第一个是删除严重冗余的特征。比如多个n-gram哈希桶中出现了完全重复的列,LightGBM会浪费时间在无效分裂上。我用XGBoost的feature importance筛了一遍,把重要性低于阈值的特征直接去掉,最终特征维度从2.4万降到1.1万,训练速度快了近一倍,分数没有明显波动。第二个是处理目标值分布。一开始我直接对原始数值做拟合,后来发现目标值的分布严重右偏,尝试用log1p变换后,RMSE略微下降。但这个数据集的实际情况是,变换对RMSE的帮助有限,因为RMSE本身在原始空间计算,变换后再反正变换会带来误差。最后我保留原始数值,只是在交叉验证时用了分层采样来保证每组数据分布一致。
3.2 深度模型:把所有东西一起学
GBDT虽然强,但它学不到序列上的长程依赖。后来我尝试了两种深度模型,都获得了不同程度的提升。
第一种是1D CNN加注意力的简单架构。输入不再是表格特征,而是长度为512的聚合物原子序列编码。每个原子类型映射到一个64维嵌入,加上位置编码,然后经过三层一维卷积,每层卷积后用注意力池化,最后接全连接层输出。这个模型参数量不大,训练速度也快,我在单张T4 GPU上跑了大概40分钟。和预期的一样,它能捕捉到重复单元内部的局部化学特征,最终交叉验证RMSE约0.075,比LightGBM好一点,但还不够惊艳。
第二种是图神经网络GNN。我把每个聚合物结构转成图,节点是原子,边是化学键,然后用一个两层GraphSAGE实现消息传递,再接一个全局池化读取特征。GNN理论上是表达力最强的结构编码器,但在实际训练中,我发现它对超参数非常敏感,学习率稍微调大一点就会发散,且训练时间比CNN长很多。最终在限时训练的条件下,GNN单模型的交叉验证RMSE是0.074,和CNN相差不大。考虑到集成需要多个模型,我没有把GNN作为主力,而是把它当作融合池中的多样性来源。
这里我想多说一句,深度模型在科学类竞赛里不一定碾压GBDT。此次比赛的特征是高度离散的指纹和手工特征,LightGBM已经能拟合得很好,深度学习纯粹靠自编码器无法学习到物理化学规律。因此,最佳策略不是选边站,而是让不同类型模型互相补漏,后续融合阶段才能吃到红利。
3.3 模型融合与最优权重
单模型最高分大概在0.073左右,距离我的0.069还差一截。这个差距主要靠融合补回来。
我用了三层融合思路。第一层是相同模型不同随机种子之间的融合,比如LightGBM跑5个种子、CNN跑3个种子,然后对预测结果取平均。这一层能把方差压下去大概0.001。第二层是不同模型之间的加权平均,我用线性回归在验证集上学习每个模型的权重。考虑到可能出现过拟合,我采用3折交叉验证计算权重,而不是直接在整个验证集上优化。最终权重分配大概是:LightGBM组占0.45,CNN组占0.3,GNN组占0.25。这个组合把验证集RMSE拉低到了0.071。第三层是Stacking,我用第二层得到的预测值作为新特征,再训练一个岭回归,把多个模型输出映射到最终目标值。Stacking在本次比赛中只带来0.001到0.002的提升,但依然值得做,因为它能让模型组合更加稳健。
融合时有一个容易被忽略的细节:每个模型在训练时使用的交叉验证折要完全对齐。如果LightGBM用的是按数据框行顺序切的5折,CNN用的是随机切但不同种子的5折,那么在融合前必须重新对每个模型计算同一折上的预测,否则整个验证集预测会"泄漏",导致融合权重失真。我为此专门写了一段辅助代码,在切分数据时固定同一组fold索引传给所有模型,这样融合阶段拿到的每个预测都来自同分布的验证集。这个细节让融合后的RMSE稳定下降了0.002,非常关键。
4. 实战中的踩坑与优化细节
4.1 Kaggle Notebook与TPU使用经验
参加这个比赛时,我的本地电脑只有一块老显卡,大多数训练都放在Kaggle Notebook上跑。很多人注册完Kaggle后不知道GPU/T PU那一栏在哪,其实新建Notebook时可以在右侧设置里选Accelerator,有GPU和TPU两种。我用的是TPU,因为比赛数据量不大,TPU在深度模型训练中速度比T4 GPU快不少。
但是TPU有一个坑:它和TensorFlow的兼容性最好,PyTorch虽然也能用,但需要额外配置XLA环境。我第一个CNN模型用的是PyTorch,换到TPU后,编译时间比训练时间还长。后来我把模型全部改写成了TensorFlow/Keras版本,TPU才能真正跑起来。这里我给一个建议:如果用TPU,请优先选择TensorFlow;如果更习惯PyTorch,老老实实选GPU即可,省下调试时间比什么都重要。
另一个经验是Kaggle Notebook有12小时运行限制。为了避免训练中断,我把训练脚本做成了checkpoint模式,每隔一个epoch就保存模型权重到输出目录,如果超时就从最近的checkpoint继续跑。同时还可以把训练好的模型都存到Kaggle Dataset里,下次新建Notebook时直接挂载使用,不必重复训练。这些操作虽然简单,但是能让你少熬很多夜。
4.2 交叉验证的正确打开方式
交叉验证是科学类竞赛的命根子。我在这场比赛里吃过两次亏,都是因为交叉验证设计不对。
第一次吃虧是在比赛第一周,我直接用随机5折交叉验证,结果验证集分数比公开榜好很多,导致我以为自己的模型很强,后来才发现是数据分布不均匀,某些相似结构的聚合物被分到了同一折里,模型看到了几乎一样的训练样本,验证分数虚高。解决办法是采用分组交叉验证,根据聚合物的结构骨架类型分成5组,保证同骨架构型的样本不在训练和验证中同时出现。修改之后,验证集分数和公开榜的差距从0.02缩小到0.004,可靠多了。
第二次吃亏是目标值的分层问题。聚合物的性质值范围很宽,直接从0到十几都有。随机切分时,某些极端值只出现在训练集,导致验证集无法评估模型的外推能力。最后我用pd.qcut对目标值做十分位分箱,再根据分箱结果进行StratifiedKFold,确保每一折的目标值分布相似。这个操作对回归问题非常有效,推荐所有人试试。
4.3 提交阶段的注意事项
Kaggle提交需要用csv文件,格式要求是两列:一列是聚合物ID,另一列是预测值。我最开始没仔细看官方的模板,直接把自己用的索引当成ID交上去,结果分数奇高,原来是预训练目标没对,模型预测的其实是某个排序后的结果,完全对不上。这个错误浪费了我两天时间。
另外,Kaggle默认的提交文件大小限制是100MB,如果你的预测结果包含大量浮点数,压缩成csv后通常没问题。但为了保险,我在提交前会用pd.read_csv再跑一遍,检查列名和行数是否和sample_submission完全一致。格式错误比赛工具会直接判无效,但也别太担心,只要严格按照模板来,一般一次就能过。
还有一个叫"提交后验证"的技巧:公开榜的分数只能看反馈,但如果你担心自己的本地分和公开分差距大,可以在提交后打开Kaggle的Notebook日志,查看自己最终运行环境是否有警告或错误。特别是在TPU环境下,有时会莫名其妙被重置,导致提交的模型其实没有跑完,我就遇到过一次。所以正式提交前,我会做一次全流程演练,把从加载数据到保存csv的每一步都跑通,确保没有隐藏错误。
5. 复盘:0.069背后的关键动作与避坑指南
5.1 提分最快的三个操作
如果让我重新打一次这个比赛,我会优先做三件事。
第一件事是尽快建立稳定的交叉验证协议。没有可靠的验证集,后面所有提分动作都是盲人摸象。我这次花了半天时间把分组交叉验证写好,虽然第一天没交出好看的public score,但后续每天的变化都能被准确评估,这比盲目调模型重要得多。
第二件事是特征工程里把"聚合物的链连接信息"显式编码。我后来加了几个描述链连接类型的特征,例如重复单元之间的连接点数量、是否含有支链等,直接让LightGBM模型的验证集RMSE降低了0.004,比调任何超参数都有效。这说明特征里的物理含义比算法复杂度更有影响力。
第三件事是尽快做多模型融合。很多人把深度学习模型单独跑很多天,却忽略了一堆简单模型取平均也能带来提升。我的0.069本身就是一个中等偏上的集成结果,如果只靠LightGBM,最高也只能到0.075。融合不是做不做的问题,而是什么时候做的问题。越早开始融合,越有时间修正权重和补充模型。
5.2 无效尝试与教训
这个比赛我踩了不少坑,有几件事纯粹是浪费时间。
第一个坑是在特征标准化上纠结太久。我一开始以为深度学习模型必须要标准化指纹特征,于是尝试了StandardScaler、MinMaxScaler,结果验证分数基本没变化。后来读了一些文献才发现,稀疏二元特征直接输入网络反而更好。所以如果你在科学类竞赛中遇到稀疏特征,不必强行标准化。
第二个坑是盲目上大模型。我曾尝试用一个大型Transformer直接对聚合物序列做预测,参数近亿,训练TPU用了六个小时,最后验证集RMSE是0.078,反而不如我之前的CNN模型,还差点因为训练超时丢了checkpoint。小型模型在这个数据集上完全够用,不要为了炫技而牺牲效率和效果。
第三个坑是过早使用AutoML。Kaggle里有很多方便的AutoML工具,我在第一周就试了一个主流框架,跑了十几个小时,得到的结果是0.088,比我自己LightGBM还差很多。这类工具的定位是快速建立基线,不适合在特征已经高度定制化的比赛中发挥威力,自己动手才能调控关键特征。
5.3 参加Kaggle竞赛对求职的帮助
很多人问我,打Kaggle到底有没有用。以我的经历来说,有,但前提是你要能把项目讲清楚。我在面试的时候,通常会打开自己打过的竞赛页面,直接说明四件事:赛题要解决什么问题、我用什么特征表达数据、模型选型和调参过程中的关键决策、最后是如何验证结果可靠的。面试官往往对这个过程比结果更感兴趣,尤其是当我讲到交叉验证设计中的分组方法如何避免数据泄露时,对方明显眼前一亮。
Kaggle竞赛最值钱的不是你学会了某个具体模型,而是你养成了一套完整的解决思路:从理解问题、设计特征、建模验证到最终复盘。这套思路是可以迁移到任何数据岗位的。另外,0.069这个分数虽然距离金牌很远,但它是我真实付出的产物,能拿得出手。
现在我把这次所有值得注意的经验整理在下表里,方便你以后直接用:
| 环节 | 我的做法 | 核心结论 |
|---|---|---|
| 特征表达 | 指纹+n-gram+手工物理特征 | 结构信息和物理常识都要有 |
| 交叉验证 | 分组+分层五折 | 避免化学结构相似样本跨折 |
| 模型选择 | LightGBM+CNN+GNN | 不同类型模型互补最强 |
| 融合方式 | 加权平均+Stacking | 多个模型比单个大模型有效 |
| 训练环境 | Kaggle TPU+TF | 明确环境选型,别硬用PyTorch跑TPU |
| 时间管理 | checkpoint+预演 | 12小时限制并不可怕 |
最后再分享一个小技巧:在提交之前,记得把最终预测结果的分布打印出来看看。有一次我的模型预测均值明显偏离训练集目标均值,说明训练数据里某些极端值被模型当成了主要模式,这种情况即使RMSE不高,也说明模型不够稳健。我通常会在预测后对异常值做一次截断,或者用分位数缩放把预测结果轻微拉回训练集的分布范围。这种后处理有时能让公开榜分数继续下降0.001到0.002。科学类比赛最终拼的往往就是这些别人不愿做的小细节。希望这篇复盘能帮你少走一些弯路,也期待在下一个赛场上看到你的经验贴。