626. NeurIPS - Ariel Data Challenge 2024 | ariel-data-challenge-2024
我想感谢主办方 @gordonyip 总体上如此乐于助人且响应迅速(尽管他仍然没有回答我 这里 和 这里 的问题,拜托!)。
同样,像往常一样感谢 Kaggle,为我们提供了这个绝佳的平台和机会。
我主要依赖多项式拟合,这与 @sergeifironov 在这里 所做的工作类似,但做了一些修改:我通过拟合两个相连的 2 次多项式来估计入凌(ingress)的开始/结束,前半部分信号如此,后半部分信号也如此(后面会更深入解释)。此外,我没有使用 scipy.optimize.minimize,而是使用了二分搜索,并且我没有在所有信号上拟合多项式,而是分别在开始 + 中间和中间 + 结束部分进行拟合,即我分别获得了入凌和出凌(egress)部分的预测。
我发现入凌计算更准确(特别是在拟合 2 次多项式时——当拟合 3 次多项式时,它们在准确性方面更相似,但总体上不如 2 次选项准确)。所以,我给了它们更大的权重。对于 sigma 估计,我拟合了一个 2D 多项式,利用了 mean(sigma(pred)) 和 std(pred) 之间的强相关性,以及 mean(sigma(pred)) 与入凌部分和出凌部分预测之间的差异之间的弱相关性(即,如果它们更相似,则 sigma 更小)。我还利用了几种后处理方法,包括在更宽的波长窗口上对低 std 预测进行平均,对低 std 预测使用 mean(pred)+pred*small_factor(即添加波动),以及用 mean(pred) 替换高波长(噪声较大的)预测。有了所有这些,我得到了 0.688/0.692 的分数。
然后是跳跃到 0.703/0.715:我使用 TensorFlow 执行二维多项式回归,想想 Sergei 的方法,但在波长轴上也有多项式。这个模型得分为 0.691/0.703,我将它的两个变体与第一个模型 (0.688/0.692) 集成在一起,作为我的最终分数。
我通过找到信号一阶导数的最大值/最小值来估计入凌/出凌的中间点,对所有波长的信号进行平均,并在时间上使用大小为 20 的滚动窗口进行平均。然后,我通过每个中间点之前/之后一阶导数变为 0 的第一个点来估计入凌/出凌的开始/结束。这非常粗略,但足以作为基线。然后我在入凌/出凌开始之前和结束之后的窗口(大小为 50)中取平均值,并使用差值来计算信号的减少量,即预测值。
对于星体 1/2,我使用了预测值和 targets 之间的 RMSE;对于星体 3/4,我使用了 0.00016(我不记得是从公共 notebook 中拿的还是自己探测的)。这导致了公共/私有榜单 0.524/0.561 的分数。
我注意到预测的平均值低于目标平均值,所以我开始探测最佳修正因子。结果如下:
pred = np.where((np.expand_dims(star, axis = -1)+pred*0) == 0, pred+0.000083053, pred)
pred = np.where((np.expand_dims(star, axis = -1)+pred*0) == 2, pred+0.00013, pred)
pred = np.where((np.expand_dims(star, axis = -1)+pred*0) == 3, pred+0.00011, pred)
这个修正将我的分数提高到了 0.543/0.578。这已经是铜牌范围了。
后来,我发现 mean(preds) 和 mean(preds-targets) 之间存在线性相关性,即我需要用乘性因子每平面进行修正。比赛结束后,事实证明这是由于前景造成的;见 这里。
我对导数方法不满意,担心它会遗漏噪声信号,特别是在私有测试中可能噪声更大。此外,它需要在时间上平均信号,导致精度损失。最后,我开发了以下方法:
我首先将信号分成两半。然后,对于信号的前半部分,我定义了两个点 p1 和 p2,并拟合了以下多项式:
然后,我用以下内容连接函数 1 和 2:
3. 一条连接第一个多项式结束和第二个多项式开始的直线。
然后,我在信号索引上变化 p1 和 p2,并搜索能使函数 1+2+3 与信号之间的 RMSE 最低的组合。首先我以 100 的步长变化,然后在第一次迭代的最佳部分以 20 的步长变化,最后在第二次迭代的最佳部分以 1 的步长变化,使我能够专注于入凌的最佳开始/结束 (p1/p2),利用高效的多项式拟合(在 Numba 中)和多处理,所有行星大约需要半小时左右。然后,我对信号的后半部分对 p3/p4 做同样的操作,以确定出凌的开始/结束。
我在平均信号上执行此拟合,假设不同波长的入凌/出凌是相同的。通过观察低端和高端波长的平均信号,支持了这一假设。
这将我的分数提高到了 0.545/0.583。这是一个很小的改进,但我非常满意,因为我认为它更 robust。此外,它在时间上是精确的(不需要在时间上平均信号以获得平滑的导数,虽然我为了更安全在一个大小为 3 的窗口中进行了平均),所以我认为当我使用更精确的预测方法时,它产生了更显著的影响(我注意到即使将入凌/出凌的开始/结束移动少量点,准确性也会下降)。拟合看起来是这样的:

一系列探测导致了星体 3/4 更好的因子,结果为公共/私有 0.55/0.588。
首先,我没有通过在大小为 50 的窗口中取入凌/出凌之前/之后的平均信号来估计信号,而是拟合了一个 2 次多项式直到 p1,在 p2 到 p3 之间,以及从 p4 到信号结束,并使用它们在入凌/出凌中间的值来估计用于预测目标的信号值。我对波长轴上滚动窗口为 30 个波长的平均信号按波长进行此操作。然后,我通过波长 0-220 的预测平均值来估计目标,排除较长波长的预测,因为它们太噪声且只会损害预测。
此时,我仍然预测每颗行星的平均目标,但是每颗星的平均 sigma。我寻找每颗行星的平均 sigma 与某物之间的相关性,最终发现与直到波长 220 的 max(pred)-min(pred) 存在一些相关性,此后定义为'diff'。记住从 1.2.3 开始,此时我每个波长都有预测(在大小为 30 的波长窗口中平均),即使我的最终预测是总平均值,所以我可以计算这个'diff'。我预测 sigma 为训练集上不同 diff 阈值之间的平均 sigma(阈值 = [0, 0.0003, 0.0004, 0.0005, 0.0006, 0.0007, 0.00085, 1000])。
1.2.3+1.2.4 将我的分数提高到了 0.602/0.614。相关性看起来是这样的:

让我们回到 1.1.3。记得我给预测每颗星添加了一个常数吗?所以,在某个时候,我尝试检查 mean(preds) 和 mean(targets-preds) 之间的相关性。(平均值是在波长轴上,每颗行星)。令我惊讶的是,我发现了这个:

正如我在 1.1.3 中所说,事实证明这是由于前景;见 这里。
用从上述回归中发现的线性修正替换 1.1.3 中的修正项, resulted in a 0.619/0.627 的分数。这已经在银牌范围了。
记得 1.2.4 中的'diff'吗?所以,我发现对于小 diff,我不能每波长预测,但对于大 diff,我可以(或者更确切地说,对于大 diff,每波长预测的误差小于预测平均值的误差——想想一个在极值之间变化的信号)。无论如何:
pred = np.where(np.expand_dims(diff, axis = 1)+features*0>0.00085, features, pred_0)
这里,'features' 是每波长的'原始'预测,'pred_0' 是每颗行星的平均预测。这是公共/私有 0.626/0.632。
我添加了:
pred[:, 230:] = pred_0[:, 230:]
即,对于较长波长,即使对于大 diff 我也预测平均值。这是一个小改进,0.628/0.632。
pred_0 = pred_0+(features-np.mean(features, axis = 1, keepdims = True))*0.08
这个技巧我也在一些其他解决方案中看到过,也许是第三名?我不太记得了。这是一个小改进,0.630/0.635。
记得 1.2.4 中的'diff'=max(pred)-min(pred) 吗?所以,我用 diff=std(pred) 替换了它用于所有目的。这将我的分数提高到了 0.638/0.637。
我读了 Sergei 的 notebook 并受到启发,所以我利用了相同的方法搜索预测值,该值将使拟合在中间(通过预测移位)和两侧的多项式的 RMSE 最低。我使用了自定义二分搜索,并分别计算入凌部分(开始 + 中间)和出凌部分(中间 + 结束)。后来,我发现我的方法中拟合 2 次多项式等同于在整个信号(开始 + 中 + 结束)上拟合 3 次,我的方法中的 3 次等同于在整个信号上拟合 4 次。无论如何,我拟合了 2 次多项式——我知道它们不适合某些至少需要 3 次的信号。尽管如此,尽管我努力寻找利用更高次多项式而不损害分数的方法,但它们 consistently 给了我比 3 次多项式更好的结果。有了这个,我跳跃到了公共/私有 0.673/0.658。
在 1.2.4 中,我预测了不同 diff 阈值的不同平均 sigma。我用每两个阈值之间的线性回归替换了常数。看起来像这样:

有了这个,我的分数增加到了 0.674/0.678。
到目前为止,我预测 sigma 与 diff 的关系,其中 diff=std(pred)。然后我注意到每颗行星的平均 sigma 与入凌预测和出凌预测之间的绝对差异之间存在弱相关性。所以我切换到拟合 2D 多项式,其中 z=sigma, x=std(pred) 和 y=distance(pred1,pred2)。(如果不清楚,pred = (pred1+pred2)/2)。这是点在 XYZ 3D 空间中的样子:

这给了我 0.675/0.678。
我发现给 pred1(入凌上的那个)更多权重会导致显著改进。我加权为 1.9:1,我的分数提高到了 0.681/0.685。这在公共和私有榜单上都是金牌范围。
对于入凌/出凌两侧更相似的曲率/斜率,我给了 pred1/pred2 相对于另一个更多的权重。这给了我 0.682/0.686。
对于较小的 std(pred),我在更大的波长窗口上平均信号。此外,对于较低的 std,我向较短波长的平均值添加了更强的波动(可能在代码中更清楚)。无论如何,有了这个,我达到了 0.688/0.692。
当我观察信号时,我看到所有波长的时间漂移是相似的,但偶尔会有斜率/曲率的逐渐漂移。由于它们似乎相关,我想拟合时间和波长的 2D 多项式,这样我的拟合将依赖更多数据并更准确。(在 第一名解决方案 中,他们指出漂移被建模为 f(time)*g(wavelength),所以拟合两个相乘的 1d 多项式比 2d 更好)。
我所做的是将所有参数一起转化为回归问题:我让每个波长的信号减少量成为一个参数,我也让 2D 多项式的系数成为参数;我将损失定义为拟合多项式与中间通过每个波长对应参数移位的信号之间的 RMSE,将所有内容放入带有我所描述损失的 TensorFlow 模型中,附上 AdamW 优化器和半余弦 LR 衰减,如通常一样让它'训练'。然后,我所需要做的就是检索定义每个波长信号减少量的参数,并计算拟合多项式在入凌/出凌处的值以预测目标。
我还使用了归一化将所有波长带到同一尺度:我从每个波长中减去其入凌/出凌之前/之后的平均值(排除中间),然后将每个减去的波长除以所述平均值除以最大平均值(所以具有最大平均值的波长除以 1,具有较低平均值的波长除以小于一的因子)。我还通过减法后的 std(signal) 除以归一化损失。我在波长轴上以 30 的 binning 和在时间上以 3 的 binning 的信号上执行所有操作以加快计算速度。
此外,为了让所有这一切在可接受的时间内运行(此时,我基本上为测试集中的每颗行星'train'一个 tensorflow 模型,所以至少需要几个小时),我构建了模型以便并发执行多颗行星的回归(基本上行星对齐在'batch'维度上,每颗行星的损失和参数是分开的,但参数以向量方式定义,以便所有计算一起执行)。
好吧,得出结论——我并发'train'了 256 颗行星,它在 GPU 上非常快,而且效果很好。第一个模型是时间的 2 次多项式,时间零项的系数是波长的 2n 次多项式,t, t^2 的系数(t 代表时间)是波长的 1 次多项式。与旧方法一样,我分别对入凌和出凌部分执行回归,给 pred1(入凌预测)更大的权重,并执行上述相同的后处理技术。这给了我 0.691/0.703。
然后我'train'了第二个模型,这次出凌部分使用时间的 3 次多项式(但入凌部分仍像第一个模型一样使用 2 次多项式)。我让出凌部分所有项的系数成为波长的 2 次多项式,除了 t^3 的系数,我将其保持为波长的 1 次多项式。对于入凌,它与第一个模型相同。此时,我有三个模型:1.3.6 的旧好模型,第一个 2D 多项式模型,和第二个 2D 多项式模型。我对光谱预测平等加权,对 sigma 预测加权 0.2:1:0(我根据训练集选择权重),这是我的最终解决方案。
涵盖在 1.4 中。
我沿时间轴对坏点进行了插值(每个坏点由左右两个相邻像素的平均值替换)。我在探测确保测试集中没有两个或更多相邻坏点之后才这样做。
老实说,太多事情记不住了。在 Kaggle 竞赛中涵盖所有想法总是一个挑战,在这场竞赛中更是如此,因为它有太多可能的方向(其中大多数是错误的...)。我可能投入最多的两个失败想法是一个用于细化预测光谱的 1D DL 模型,以及一个具有许多增强的 DL 模型,用于从信号预测光谱。我还尝试了各种小事情,如仅在信号的部分上拟合多项式(排除外边缘/入凌/出凌周围的区域)或使用中值拟合而不是平均拟合(即 MAE 而不是 RMSE 损失)。
Kaggle cpu/gpu notebooks。
我想向一路上帮助我的来源致以衷心的感谢:
这个介绍性 notebook 由 @ambrosm 提供,我用于预处理管道,也用于介绍 %%writefile/exec 管道,我广泛使用了他。
这个 notebook 由 @sergeifironov 提供,向我展示了如何改进我的拟合/预测方法。还要感谢你和你的伙伴 @asimandia 成为排行榜上有趣昵称的有趣伙伴。
这个资源 教我如何在 Numba 上实现 polyfit。
我希望我没有忘记任何人;如果你在我的工作中发现你贡献的痕迹,请通知我。这是一场漫长的竞赛。
我的整个管道在 Kaggle 上;链接列在 我的 GitHub 上。