返回列表

1st Place Solution

545. IceCube - Neutrinos in Deep Ice | icecube-neutrinos-in-deep-ice

开始: 2023-01-19 结束: 2023-04-19 物理与天文 数据算法赛
1st Place Solution - IceCube Neutrinos 冠军解决方案

IceCube中微子探测冠军解决方案

作者:tito (Kaggle Grandmaster)
排名:第1名
发布时间:2023年4月20日

首先,我要向主办方和Kaggle团队组织如此引人入胜的比赛表示衷心感谢。此外,我还想感谢那些通过发布笔记本和参与讨论分享宝贵见解的贡献者。

特别感谢以下非常有帮助的笔记本和讨论:

模型架构

我的模型结构很简单,将EdgeConv和Transformer连接在一起。我旨在将EdgeConv和Transformer结合到模型中,以高效地收集信息。图神经网络(GNN)用于捕获局部邻域信息,而Transformer则用于收集全局上下文,从而确保对数据的全面理解。这个简单的模型作为单模型,在公开排行榜上的得分是0.9628,在私有排行榜上的得分是0.9633。

模型架构图

用于GNN的静态边选择

原始论文中,每一层都会动态地计算边,但这种边选择是不可微的,因此无法训练。在原始论文的分割任务中,这仍然效果很好,因为同一分割中的点在潜在空间中被训练得很接近。然而,在这个任务中情况不同,所以我认为动态选择边没有意义。因此,在EdgeConv中使用的边选择在输入时计算。

EdgeConv的简单改进

使用EdgeConv时,dom_i的潜在参数\\({x}_i\\)利用差值\\({x}_j-{x}_i\\)进行更新,其中\\({x}_j\\)是dom_i的第k近邻dom_j的潜在参数。这对于x、y、z和时间来说没有问题,但对于电荷和辅助信息,绝对值是有意义的,所以我让\\({x}_i\\)不仅使用\\({x}_j-{x}_i\\),还使用\\({x}_j\\)来更新。

EdgeConv改进示意图

损失函数

VMFLoss是一个很好的稳定损失函数,但θ是以余弦形式出现的:

VMFLoss = -κ*cos(θ) + C(κ)
# θ是真实值与预测值之间的夹角,κ是3D预测向量的长度

另一方面,本次比赛的指标是角度θ本身。为了最小化θ本身,我将损失函数定义如下:

MyLoss = -θ - κ*cos(θ) + C(κ)

与VMFLoss相比,这种简单的改进带来了0.005的显著提升。

序列分桶(Sequence Bucketing)

在Transformer中,计算复杂度与序列长度的平方成正比,因此减少序列长度至关重要。因此,将序列长度相似的数据分组并创建小批量是有效的。这种方法加快了计算速度,并进一步减少了GPU内存使用,从而允许使用更大的小批量大小。

序列分桶示意图

这可以通过修改DataLoader的collate_fn轻松实现:

def collate_fn(graphs, split_list=[0.8, 1.0]):
    graphs = [g for g in graphs if g.n_pulses > 1]
    graphs.sort(key=lambda x: x.n_pulses)
    batch_list = []
    for minp, maxp in zip([0]+split_list[:-1], split_list):
        min_idx = int(minp*len(graphs))
        max_idx = int(maxp*len(graphs))
        this_graphs = graphs[min_idx:max_idx]
        this_batch = Batch.from_data_list(this_graphs)
        batch_list.append(this_batch)
    return batch_list

注意:即使对batch_list中每个长度偏向的小批量单独计算梯度,也可以通过之后集体更新权重来减轻负面影响。

数据加载

由于数据量巨大,难以将所有数据放入内存,我尝试每个时期加载一个批次。伪代码如下:

class IceCubeDataset(Dataset):
    def reset_batch(self):
        # 获取下一个批次ID
        self.this_batch_id = self.get_next_batch_id()
        # 读取下一个批次的数据
        self.this_batch = pd.read_parquet(f"{BATCH_DIR}/batch_{self.this_batch_id}.parquet")
        # 过滤元数据
        self.this_meta = self.meta[self.meta.batch_id == self.this_batch_id]
        
    def __len__(self) -> int:
        return len(self.this_meta)
    
    # 其他方法...

class IceCubeModel(Model):
    def __init__(self, dataset, ...):
        ...
        self._dataset = dataset
        
    def training_epoch_end(self, outputs):
        ...
        # 每个时期重置批次
        self._dataset.reset_batch()

模型参数

以下是用于堆叠的基模型的一些信息:4层结构,600万参数,似乎比其他顶级解决方案更小。

  • EdgeConv+Transformer模块深度:3或4层
  • 嵌入维度:256
  • 使用的序列长度(训练):200到500
  • 使用的序列长度(推理):6000
  • 是否使用全局特征
  • 参数数量:600万
  • 用于GNN的kNN参数:x,y,z 或 x,y,z,time
  • 训练时间:每轮10-14小时(2块Titan RTX)
  • 推理时间:30分钟/5个批次(Kaggle笔记本)

由于使用了6000个序列,推理时间相对较慢。

模型堆叠

堆叠的框架主要基于原始基模型。主要区别在于模型被替换为一个3层MLP。通过堆叠,取得了+0.003的提升。

代码

推理笔记本可在 这里 获取。

同比赛其他方案