返回列表

Private LB 0.083 | GNN Embeddings + Stacking Ensemble

667. NeurIPS - Open Polymer Prediction 2025 | neurips-open-polymer-prediction-2025

开始: 2025-06-16 结束: 2025-09-15 化学与材料 数据算法赛
Private LB 0.083 | GNN Embeddings + Stacking Ensemble

私有榜单 0.083 | GNN 嵌入 + Stacking 集成

副标题:一个用于学习分子嵌入的 GNN,并在这些嵌入及其他手工特征之上堆叠多个梯度提升模型

作者:Quang Dũng (Expert)

发布时间:2025-09-16

竞赛排名:12

背景

概述

Overview

细节

GINEConv

GINEConv 是一个 PyTorch Geometric 层。GINEConv 很好,因为它允许使用丰富的边特征,而 vanilla GCNConv 只允许节点特征。

准备 GNN 训练数据

  • 总节点特征 = 40,边特征 = 23。
  • 我添加了一个全局节点,连接到其他节点。想法是在图中创建捷径,以便信息更容易流动。
  • 我应用了 masking,没有使用增强。

GNN 模型

class GINENet(torch.nn.Module):
    def __init__(self, node_dim=NODE_DIM, edge_dim=EDGE_DIM, first_conv_dim=107, second_conv_dim=107):
        super().__init__()

        self.conv1 = GINEConv(
            nn.Sequential(
                nn.Linear(node_dim, first_conv_dim),
                nn.ReLU(),
                nn.Linear(first_conv_dim, first_conv_dim)
            ),
            edge_dim=edge_dim,
            train_eps=True
        )
        self.ln1 = nn.LayerNorm(first_conv_dim)
        
        self.conv2 = GINEConv(
            nn.Sequential(
                nn.Linear(first_conv_dim, second_conv_dim),
                nn.ReLU(),
                nn.Linear(second_conv_dim, second_conv_dim)
            ),
            edge_dim=edge_dim,
            train_eps=True
        )
        self.ln2 = nn.LayerNorm(second_conv_dim)

        self.lin1 = nn.Linear(second_conv_dim, first_conv_dim)
        self.lin2 = nn.Linear(first_conv_dim, 5)
        self.dropout = nn.Dropout(0.5)

    def forward(self, data):
        x, edge_index, edge_attr, batch = data.x, data.edge_index, data.edge_attr, data.batch

        x1 = self.conv1(x, edge_index, edge_attr)
        x1 = self.ln1(x1)
        x1 = F.relu(x1)
        
        x2 = self.conv2(x1, edge_index, edge_attr)
        x2 = self.ln2(x2)
        x2 = F.leaky_relu(x2, negative_slope=0.005)

        x = global_mean_pool(x2, batch)
    
        x = self.lin1(x)
        x = F.relu(x)
        x = self.dropout(x)
        x = self.lin2(x)

        return x

我在最后一个卷积层后使用了 LeakyReLU 而不是 ReLU,这样 stacking 模型就不会得到全零嵌入。
我使用 Huber loss 训练了这个模型,根据竞争数据中 3500 个样本的验证 MAE 选择了最佳检查点。我调整了这个架构的超参数,并最终确定了在公共榜单上得分最好的那个 (0.069)。

堆叠梯度提升模型 (Stacking Gradient Boosting Models)

现在除了描述符和指纹数据外,我们还有丰富的 GNN 嵌入和 GNN 预测值可以使用 😋
我丢弃了低方差和高 NaN 的列

特征组 维度
描述符 (Descriptors) 220
指纹 (Fingerprints) 162 (从 1024 位截断 SVD)
GNN 嵌入 107
图特征 10
GNN 预测 5
总计 ~500

对于每个目标,我训练了 5 折模型,使用 optuna 进行优化。
最后我使用随机森林作为元模型。

特征重要性 (总计,非平均)

XGB

目标 描述符 指纹 GNN GNN_预测
Tg 23.49 22.09 51.20 0.63 2.58
FFV 27.55 5.15 66.45 0.12 0.73
Tc 23.68 27.09 46.38 0.60 2.24
Density 19.15 4.42 75.78 0.07 0.57
Rg 29.23 19.42 50.33 0.41 0.61

LGB

目标 描述符 指纹 GNN GNN_预测
Tg 14.77 24.75 57.74 1.22 1.51
FFV 12.77 18.79 66.74 1.36 0.34
Tc 12.73 26.12 56.92 2.44 1.78
Density 20.28 13.78 63.95 1.69 0.30
Rg 16.42 32.41 46.49 3.91 0.78

Cat

目标 GNN 描述符 指纹 GNN_预测
Tg 86.91 5.59 5.49 1.97 0.06
FFV 93.97 3.73 2.09 0.15 0.07
Tc 82.56 10.11 6.02 1.27 0.03
Density 90.23 6.46 3.13 0.14 0.05
Rg 85.11 9.07 5.45 0.26 0.11

目标 Tc 的前 30 个特征重要性

XGB

XGB Feature Importance

LGB

LGB Feature Importance

Cat

Cat Feature Importance

有趣的小技巧

我做的事情 (可能重要也可能不重要)

  • 我使用了外部数据:Tc_SMILESsmiles_extra_data,但优先加载竞赛数据。
  • 我丢弃了一些异常值。
  • 起初我尝试了 MAEMSE 作为 GNN 损失,两者效果都不错,我无法决定,所以选择了 Huber。所有 boosting 模型也使用了 Huber

我尝试过但没用的方法

  • 多个全局节点 → 过拟合 (1 个就够了)。
  • 将描述符注入全局节点或 GNN 层内部 → 过拟合。
  • 使用对比损失在子图上预训练 → 没有太大帮助。

我想尝试的事情

我刚刚在讨论区发现了 TabPFN,相信它可以进一步帮助我的 stacking,但没能及时完成 😐

感谢坚持到最后

同比赛其他方案