Part1 精读赛题——确定隐含条件和提交数据格式
任务和主题
AI极端降水预报:提供历史时段伏羲气象大模型未来72小时逐小时的多个气象要素以及对应时段的ERA5降水数据,输出给定时段的基于伏羲气象大模型要素作为输入的AI极端降水预测。
比赛数据
数据为原始数据的标准分数Zscore,Zscore = (x - μ)/ σ 。ERA5降水, μ: 0.19359495,σ:0.6458292; FuXi降水, μ: 0.13014507,σ: 0.4056911。
1.ERA5
比赛目标数据来自欧洲中期天气预报中心(ECMWF)的再分析数据ERA5,使用了ERA5的子集,1个地面变量,时间间隔1小时,空间分辨率0.25度,覆盖区域(N18 ~ N32, E103 ~ E123)。
2.FuXi
比赛输入数据来自伏羲气象大模型的输出,伏羲大模型是复旦大学发布的数据驱动的气象大模型,数据分辨率0.25度,时间间隔1小时,包含地面和高空气压层多个气象要素,数据的输入格式为(1, 72, 24, H, W), 维度分别表示(batch, 时间, 特征或者通道, 纵向网格数, 横向网格数);我们需要的输出格式为(1, 72, H, W), 所以在定义网络的时候要特别注意这里
下载数据后, 我们会发现数据都是.nc格式的cdf数据, 需要我们使用xarray包来进行读取, (上文TASK2有提到过,根据baseline中的数据加载函数), 并且根据feature数据的时间戳来取对应的数据,需要注意的是为了避免在gt数据中找不到某些时间戳,我们需要对数据进行清洗,在改进后的baseline中, dataset加载数据时, 会捕获这个异常并返回batch中的上一条数据(batch为1时即返回上一条batch).
Part2 速通新baseline——与原始baseline对比发现实验提升细节
-
首先在数据集加载中, 如同上文提到的, 如果尝试加载全部数据以后肯定会遇到报feature中的时间戳在gt上找不到的异常, dataset中的捕获异常就可以避免这个问题, 同时发现是哪部分数据缺失了
-
加载数据时, 原baseline中只定义了训练的dataloader, 新的baseline中定义了验证集的dataloader, 方便我们在每个epoch训练时查看当前epoch模型在验证集的表现, 判断模型效果
-
from torch.utils.data import Dataset, DataLoader, random_split
# Split the dataset into training and validation sets
train_size = int(0.8 * len(my_data))
val_size = len(my_data) - train_size
train_dataset, val_dataset = torch.utils.data.random_split(my_data, [train_size, val_size])
# Create data loaders for training and validation sets
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=1, shuffle=True)
val_loader = torch.utils.data.DataLoader(val_dataset, batch_size=1, shuffle=False)
-
模型训练时, 是首先从一个随机的数据通过逐步训练,更新参数,逐步变成拟合程度最好的模型, 因此如果模型的初始化合适的话, 模型会很快收敛, 变成拟合程度好的新模型. 本次新增baseline中, 增加了xvaier初始化 https://paperswithcode.com/method/xavier-initialization
-
新增了查看验证集loss的过程, 帮助我们判断哪些模型会是拟合程度更好的模型
-
一些超参数的调整,如学习率,权重衰减,训练周期epoch等
并且原始baseline只是使用了单层的卷积神经网络,进阶baseline对模型架构进行了加深处理,使用了4个卷积层,并使用了BatchNormalization,和Relu激活函数。
import torch
import torch.nn as nn
# 实验1 加深模型
class EnhancedModel(nn.Module):
def __init__(self, num_in_ch, num_out_ch):
super(EnhancedModel, self).__init__()
self.conv1 = nn.Conv2d(num_in_ch, 64, kernel_size=3, padding=1)
self.batchnorm = nn.BatchNorm2d(64)
self.activation = nn.ReLU()
self.conv2 = nn.Conv2d(64, 64, kernel_size=3, padding=1)
self.conv3 = nn.Conv2d(64, 64, kernel_size=3, padding=1)
self.conv4 = nn.Conv2d(64, num_out_ch, kernel_size=3, padding=1)
def forward(self, x):
B, S, C, W, H = tuple(x.shape)
x = x.reshape(B, -1, W, H)
out = self.conv1(x)
out = self.activation(out)
out = self.conv2(out)
out = self.activation(out)
out = self.conv3(out)
out = self.activation(out)
out = self.conv4(out)
out = self.activation(out)
out = out.reshape(B, S, W, H)
return out
# define model
in_varibales = 24
in_times = len(fcst_steps)
out_varibales = 1
out_times = len(fcst_steps)
input_size = in_times * in_varibales
output_size = out_times * out_varibales
model = EnhancedModel(input_size, output_size).cuda()
Part3 发散思路——简单谈谈上分的技巧
1.从数据角度
本次数据的大部分是接近于0的值, 小部分会出现极值, 但是就是这部分极值反而是评分的关键。因此, 我们在使用数据时, 要想办法提高极值数据对于模型的贡献, 降低接近于0的值这部分数据对于模型的贡献。此外,越多的训练数据往往对于我们的模型会有更理想的拟合能力, 数据决定了模型的上限。
2.从模型角度
本次baseline主要使用的是卷积神经网络的模型架构,在这个模型架构的基础上,我们可以对模型进行加深。详细参考上图代码
3.从损失函数角度
刚开始的baseline使用的是MSELoss,进阶baseline提供了另一种损失函数SmoothL1Loss。
我查阅相关数据后,有如下说明
MSELoss 是一种非常直观的损失函数,它计算的是预测值和真实值之间差的平方的均值。具体公式如下:
[
\text{MSE} = \frac{1}{N}\sum_{i=1}^{N}(y_i - \hat{y}_i)^2
]
其中:
- \( y_i \) 是第 \( i \) 个样本的真实值。
- \( \hat{y}_i \) 是第 \( i \) 个样本的预测值。
- \( N \) 是样本的数量。
MSELoss 的优点在于它对大的误差非常敏感,因为误差被平方了。这使得模型更加倾向于减小较大的预测误差。然而,这也意味着如果数据集中存在一些极端值(即异常值),MSELoss 可能会导致模型过分关注这些异常值,而忽略其他样本。
Smooth L1 Loss,也称为 Huber Loss,是一种结合了 MSE 和 MAE (Mean Absolute Error) 的损失函数。它在误差较小时使用 MSE 的形式,而在误差较大时则使用 MAE 的形式。这种损失函数的设计目的是为了减少异常值的影响,同时保持对误差的敏感性。
Smooth L1 Loss 的定义如下:
\[
\text{SmoothL1Loss}(\delta) = \begin{cases}
0.5 (y - \hat{y})^2 & \text{if } |y - \hat{y}| < \delta \\
\delta (|y - \hat{y}| - 0.5 \delta) & \text{otherwise}
\end{cases}
\]
其中:
- \( \delta \) 是一个超参数,用于控制损失函数从 MSE 到 MAE 的转变点。
Smooth L1 Loss 的优点在于它对异常值的敏感度较低,因为它在误差较大时使用绝对值而不是平方。这有助于模型更加稳健,不过它也可能导致模型对较大的误差不够敏感。
比较
1. 对大误差的敏感度:
- MSELoss 对大误差非常敏感,这意味着它会强烈惩罚模型的大误差。
- Smooth L1 Loss 对大误差的惩罚较小,这使得模型更不容易受到异常值的影响。
2. 对异常值的鲁棒性:
- MSELoss 对异常值非常敏感,可能会导致模型过分关注这些值。
- Smooth L1 Loss 更加鲁棒,因为它在误差较大时采用 MAE 形式,降低了异常值的影响。
3. 适用场景:
- 如果数据集中存在异常值,或者你希望模型对异常值不太敏感,那么 Smooth L1 Loss 可能更适合。
- 如果数据集相对干净,没有太多异常值,或者你希望模型能够更好地捕捉较大的误差,那么 MSELoss 可能更为合适。
而在我们的具体需求情况下,由于数据包含大量接近零的值和少量极值,而这些极值又是评分的关键,因此可能需要一种损失函数来降低对接近零值的敏感度,同时对高值敏感, 加更大的惩罚项,对极值保持较高的敏感度。在这种情况下,Smooth L1 Loss 可能是一个更好的选择,因为它在一定程度上减少了对异常值的敏感度,同时仍然保持了对大误差的一定敏感度。
4.从epoch角度
进阶baseline训练了多个epochs,并打印了每个epoch验证集上的Validation Loss
5.其他
学习率(比如使用学习率衰减或Cosine Annealing等策略)、优化器、优化器的schedual、训练的周期(在验证集上的性能不再提升时停止训练,防止过拟合)、验证集和训练集划分比例等等...超参数的调整(使用网格搜索、随机搜索或贝叶斯优化等方法进行超参数调优)只能通过大量的实验才能找到合适的部分