545. IceCube - Neutrinos in Deep Ice | icecube-neutrinos-in-deep-ice
首先,我要感谢组织者和工作人员举办了如此精彩的比赛。也感谢我所有的队友!@anjum48、@allvor、@remekkinas、@wrrosa。
祝贺@anjum48!晋升GM!
我们的代码将在此处提供。
这个数据集非常大,如果每个epoch都重复进行pandas操作会浪费大量CPU周期(polars似乎还不能与PyTorch数据加载器的多工作进程一起使用)。为了解决这个问题,我创建了PyTorch Geometric的`Data`对象用于每个事件,并将它们保存为`.pt`文件,可以在训练期间加载。使用32个线程创建这些文件大约需要8小时,占用约1TB空间。
这样做的问题是1TB空间分散在1.3亿多个小文件中。Linux分区有有限的"索引节点"或`inodes`,即特定文件的索引。由于这些文件非常小,我在2TB驱动器空间用完之前就遇到了inode限制。作为解决方法,我不得不将这些文件分散到两个驱动器上,所以如果有人想复现此方法或运行我的代码,请注意这一点。
更高效的方法可能是存储已预批处理的`.pt`文件,这样需要的文件更少,但你会失去每个epoch打乱数据的能力,这可能对这么多数据来说影响不大。我没有尝试GraphNet团队建议的sqlite方法。
在GNN的上下文中,每个DOM被视为一个节点。每个节点具有以下11个特征:
许多归一化方法来自GraphNet作为起点,但我改变了时间的尺度,因为时间在这里是一个非常重要的特征。
对于具有大量命中的事件,为防止OOM错误,我采样了256次命中。这会使过程略微非确定性。
QE是DOM中光电倍增管的量子效率。DeepCore DOM的QE比常规DOM高35%(本文图1paper),因此QE everywhere设为1,DeepCore中较低的50个DOM设为1.35。最终的QE特征使用(QE - 1.25) / 0.25进行缩放。
散射和吸收长度对于表征冰的透明度差异非常重要。此数据发布在本文第31页paper。使用1920米的数据深度,使得z = (深度 - 1920) / 500。使用`scipy.interpolate.interp1d`将数据重采样到z值。我发现经过`RobustScaler`处理后,散射和吸收数据几乎相同,因此我只使用了散射长度。
两种主要事件类型是径迹事件和级联事件。查看一些出色的可视化工具,例如edguy99的,我想到如果节点能了解最近前一次命中的位置和时间,可能有助于模型区分这两组。为计算此特征,对每个事件将命中按时间排序,计算所有命中的两两距离,屏蔽未来的命中,并计算到最近前一次命中的距离d。使用(d - 0.5) / 0.5进行缩放。前一次命中的时间差也使用相同方法计算,并使用(t - 0.1) / 0.1缩放。
我尝试创建一个标志来识别命中是来自径迹直接产生,还是某些次级散射,灵感来自本文第2.1节paper。添加此标志的一个副作用是训练更加稳定。标志生成如下:
我使用了90%-10%的训练-验证分割,由于数据集规模,没有使用交叉验证。分割通过创建log10(n_hits)的10个bin,然后使用`StratifiedKFold`进行10次分割完成。
我直接使用GraphNet的`DirectionReconstructionWithKappa`任务,意味着嵌入(例如128的形状)将被投影到4维(x, y, z, kappa)
我使用了以下3种架构。所有验证分数都应用了6x TTA:
GraphNet/DynEdge - Val = 0.98501
GPS - Val = 0.98945*
GravNet - Val = 0.98519
这3个模型的平均值得到了0.982的LB分数。
GraphNet/DynEdge模型几乎没有修改,只是改用了GELU激活。
GPS和GravNet使用了8个块,你可以在此处的代码中找到两种架构的确切结构。
*GPS是最强大的模型,但训练也最慢,属于transformer类型模型(我的机器上大约11小时/epoch)。我成功训练了一个达到0.98XX验证分数的模型,但太晚了未能包含在最终提交中。
我使用VonMisesFisher3DLoss + (1 - CosineSimilarity)作为最终损失函数,因为余弦相似度是平均角度误差的一个很好的代理指标。对于CosineSimilarity,我将目标方位角和天顶角值转换为笛卡尔坐标。
这比分别对方位角(VMF2D)和天顶角(MSE)使用损失函数要好得多,这是我最初采用的方法。
我将数据以string 35(DeepCore string)为中心,并围绕z轴以60度为步长旋转。这实际上并没有改善验证性能,但好处是使模型具有旋转不变性,从而可以利用探测器对称性并应用6倍测试时增强(TTA)。这通常能将分数提高0.002-0.003。
循环平均
待定
我的所有代码将很快在此提供:https://github.com/Anjum48/icecube-neutrinos-in-deep-ice
在团队合并之前,我创建了LSTM和GraphNet模型。团队合并后,我专注于GraphNet,因为Remek的LSTM模型优于我的。我使用graphnet(https://github.com/graphnet-team/graphnet)作为基线,并进行了多项改进以提高其准确性。以下是我进行的一些有效实验(有很多无效的尝试):
上述模型(第一阶段和第二阶段)的集成给出了public LB 0.995669,private LB 0.996550
对于LSTM训练,我们采用了Robin Smits(@rsmits)提出的方法并进行了一些改进:
训练分为三个LR调度阶段:
未能提升我们分数的方法:
额外工具:
对于我的(Remek)实验,我最初使用ZbyHP Z4配备2xA5000,后来HP给我寄了一台ZbyHP Z8工作站,配备2x Intel Xeon CPU和Nvidia A6000 GPU。我个人可以说这帮助我建立了快速的实验流程。我能够非常快速地处理数据集文件。拥有A6000让我能够使用更大的batch size训练模型。感谢HP支持我的工作。
最后的话 - 我认为我们组建了一个伟大的团队 - 我们每个人都负责解决方案的一部分,我们讨论了很多,但没有"我的更好"这种想法。虽然这是我们第一次合作,但我感觉我们好像认识了一辈子。伟大的团队,伟大的成绩!感谢大家让我有机会向伟大的AI专家学习。
我在本次竞赛中的主要贡献是开发了融合队友解决方案的方法。分析不同类型的解决方案(GraphNet、LSTM等)表明,它们在不同预测天顶角和方位角值(主要是天顶角)下的效率不同。因此,我将最初的"恒定权重"方法改为"分箱"方法。
该方法包括将预测的天顶角值分成10个等宽度的箱。因此,当组合两个解决方案时,我们总共得到100个箱。值10是可配置的,但实验表明它接近最优。
然后,在每个箱中找到天顶角的融合权重,并找到方位角的融合权重。预测天顶角的融合通过简单的线性组合完成。由于可能通过2π的过渡,方位角的融合稍微复杂一些。因此,首先计算两个解决方案中预测方位角之间的差异,以及第二个值相对于第一个值的方向。
权重拟合在大小接近测试数据的训练数据样本(5个batch,包含100万事件)上进行。
还测试了几种改进此融合方法的方法,特别是使用GBDT。
其中一种方法是尝试将事件分类为"简单"和"复杂"(受此kernel启发)。
实验表明,某些类型的神经网络更擅长处理简单事件,而其他类型更擅长处理复杂事件。如果我们能准确确定每个事件的类型,这将极大改善竞赛指标。构建的模型在事件分类方面的效率达到了公共kernel的水平(AUC 0.93),但这还不足以获得融合质量的显著提升。
还训练了几个分类模型(二元变量 - 哪个神经网络对给定事件的预测更好)和回归模型(目标变量 - 两个神经网络对给定事件的竞赛指标差异)。不幸的是,所有这些方法仅显示了结果的轻微改进(第四位小数),但它们非常容易过拟合。
后来,我的方法被Wojtek Rosa显著改进。因此,他的方法被用于最终提交,他在自己的部分详细描述了该方法。特别地,我想指出他用决策树模型构建融合箱的绝妙想法。
感谢竞赛主办方举办如此激动人心的IceCube竞赛!
也感谢我的队友们 - 再次祝贺他们的奖项、GM头衔和出色的解决方案。
我的部分是关于融合的。
我使用batch_ids 1-5进行评估和调整参数,后来仅将batch_ids 655+用于评估目的。
当我加入Remek&Alvor团队时,我们有出色的LSTM解决方案、公共Graphnet和令人惊叹的融合技术:
def alvor_blend(s):
s['diff'] = np.abs(s['azimuth_2'] - s['azimuth_1'])
s['direction'] = np.where(
s['diff'] < np.pi,
np.sign(s['azimuth_2'] - s['azimuth_1']),
-np.sign(s['azimuth_2'] - s['azimuth_1'])
)
N = 10
s['bin'] = (N*np.floor(N*s['zenith_1']/np.pi) + np.floor(N*s['zenith_2']/np.pi)).astype(int)
并最小化每个箱的分数,找到最佳的qu, alpha,例如:
s0['azimuth_pred'] = s0['azimuth_1'] + alpha * s0['diff'] * s0['direction']
s0['zenith_pred'] = (1-qu) * s0['zenith_1'] + qu * s0['zenith_2']
我意识到,与简单/公共向量权重集成相比,这种方法要好得多。
我尝试用更大的N值来提高分数,但这导致过拟合。
之后,我通过创建使用回归树的箱来改进分数,目标 = score_1 - score_2,特征:
cls = ['azimuth_1','zenith_1','azimuth_2','zenith_2','diff','direction', 'n_pulses','zenith_1_2','direction_x','direction_y','direction_z','direction_kappa',
'zenith_3','azimuth_3','score_1_3','score_2_3']
其中direction_%来自Isamu提交,zenith_3来自Robert的1.183线性解决方案。我尝试了许多来自事件的直接特征,但决策树回归器的简单性给了我们(几乎)完全控制N箱的能力,用于调整allvors的参数qu和alpha:
regr_1 = DecisionTreeRegressor(max_depth=11, min_samples_leaf=500, min_samples_split=500)
s['bin_raw'] = regr_1.predict(s[cls])
s['bin'] = round(s['bin_raw'],5).astype(str)
使用决策树使我们的本地分数和LB都提升了0.0027。我尝试了其他融合方法,如MLP或LGBM,但没有成功。
后来,我还稍微修改了`adjusting`循环,以找到zenith_1和zenith_2的完整线性组合:
s0['azimuth_pred'] = s0['azimuth_1'] + alpha * s0['diff'] * s0['direction']
s0['zenith_pred'] = ru * s0['zenith_1'] + qu * s0['zenith_2']
找到ru和qu需要更多计算,但这给了我们额外的0.001提升。
我们的最终融合包括:
Isamu解决方案 .995 -> Remek LSTM 1.002 -> datasaurus Graphnet .982 -> Robert 1.183
本地分数 0.9774692 Public LB 0.975545, Private LB 0.97658

真实天顶角(x轴) vs 分数(y轴) - "错误三角形"可见:

