Time Series Forecasting
第二届世界科学智能大赛地球科学赛道-AI极端降水预报
深度学习建模, 我的思考:
- 确定研究的问题,分析已有的数据
- 选择合适的方法,确定模型结构
- 训练过程中可能出现的问题以及对应的解决方案
我认为的难点:
- 首选确认好所需要用到的数据,哪些可以作为训练,哪些作为测试,并构建可以输入模型的数据结构:
- 深度学习有相当一部分的工作量都在数据集的处理、分割等工作上(通过Task1的Baseline我也发现很多人在学习群里问有关数据的下载、数据存放、路径等问题)
- 确定自己理想的数据集结构,定义处理函数(读取、分割、存储),转为Pytorch-DataLoader
- 相信你自己,能够完美处理好数据基本上已经超过很多人了
- 能够创建数据结构之后,选择你认为合适的方法(算法)[LSTM, CNN, Transform-Family, …],搭建完整的模型架构(不要怕搭建的结构不合理,只要你的模型能够跑起来,完整输出预测结果,再去解决出现的问题)
- 将训练数据与测试数据输入模型当中开始训练,观察Loss变化(绘图),调整模型结构或方法,针对出现的问题不断完善模型结构
本文章代码来源于Datawhale-Task2:抽丝剥茧——降水预测baseline详解
首先,本次比赛是研究天气预测,给定的数据为:气象要素+对应时段的ERA5降水数据,具体变量名如下:
变量 | 含义 | 变量 | 含义 |
---|---|---|---|
z | 位势高度 | u10m | 10m风U分量 |
t | 温度 | v10m | 10m风V分量 |
q | 绝对湿度 | lcc | 低云量 |
u | 风速U分量 | mcc | 中云量 |
v | 风速V分量 | hcc | 高云量 |
t2m | 2m温度 | tcc | 总云量 |
d2m | 2m露点温度 | tp | 累积降水 |
其中tp为target。
Task2中,前一部分工作量集中在构建数据集上(给出的数据按照年份划分,并且里面的结构对于新手来说有点复杂);
接下来重点讲解EnhancedModel(第一个,长的那个):
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
相对来说比较简单的模型结构:卷积、批量标准化、激活函数
-
卷积层:Conv2d
torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode=‘zeros’, device=None, dtype=None)
本次用到的参数:
in_channels: 输入维度
out_channels: 输出维度
kernel_size: 卷积核大小(整数或元组): 整数n代表卷积核大小为nn,元组(x,y)代表卷积核大小为xy
stride: 卷积核下一次移动的步长(步长:假如数据shape为13*13,步长为1则代表每次卷积核移动一个数据的长度大小范围)
padding: 在卷积核进行任何操作之前,数据填充的长度大小 -
BatchNorm2d: 通过调整和缩放激活来规范化小批量的输入。有助于通过减少内部协变量位移来稳定和加快训练过程。
一般输入的参数为上一层输出的维度大小 -
ReLU激活函数
引入非线性变换
ReLU(x) = max(0,x)
forward函数更为简单,作用就是用以及搭建好的层结构处理输入数据,本次搭建的模型如下:
搭建完成,输入超参数(上面模型当中涉及到的变量,给定值的大小)
定义损失函数(一堆,自己去选)
模型参数初始化
import torch.nn.init as init
def init_weights(m):
if isinstance(m, nn.Conv2d):
init.xavier_uniform_(m.weight)
if m.bias is not None:
init.constant_(m.bias, 0)
model.apply(init_weights)
这里是定义了一个初始化函数,因为Baseline模型只有Conv,这里面通过isinstance(m, nn.Conv2d)遍历上面定义的模型各个层结构(m代表当前层,nn.Conv2d代表2d卷积层)。如果当前层为nn.Conv2d则进行 init.xavier_uniform_1初始化参数(m.weight代表当前卷积层卷积核的具体数值),跳过ReLU激活层。
m.bias代表偏置(卷积层一般不要,特殊的我没见过)
- 如果你自己添加了其他的层(比如:nn.Linear线性层),也可以在第一个if后添加else或elif-else语句(结合isinstance, m.weight)。
参数初始化完成后,设置优化器(optimizer,一般Adam效果就还可以),开始训练模型
num_epochs = 20
optimizer = torch.optim.Adam(model.parameters(), lr=0.0004, weight_decay=1e-6)
# for epoch in tqdm(range(num_epochs)):
os.makedirs('./model', exist_ok=True)
for epoch in range(num_epochs):
model.train()
loss = 0.0
for index, (ft_item, gt_item) in enumerate(train_loader):
ft_item = ft_item.cuda().float()
gt_item = gt_item.cuda().float()
# print("gt", gt_item.max(), gt_item.min())
# Backward and optimize
optimizer.zero_grad()
# Forward pass
output_item = model(ft_item)
# print(output_item.max(), output_item.min())
loss = loss_func(output_item, gt_item)
loss.backward()
optimizer.step()
loss += loss.item()
# Print the loss for every 10 steps
if (index+1) % 10 == 0:
print(f"Epoch [{epoch+1}/{num_epochs}], Step [{index+1}/{len(train_loader)}], Loss: {loss.item():.6f}")
loss = 0.0
# Save the model weights
torch.save(model.state_dict(), f'./model/model_weights_{epoch}.pth')
model.eval()
val_loss = 0.0
with torch.no_grad():
for index, (ft_item, gt_item) in enumerate(val_loader):
ft_item = ft_item.cuda().float()
gt_item = gt_item.cuda().float()
output_item = model(ft_item)
val_loss = loss_func(output_item.max(), gt_item.max())
val_loss += val_loss.item()
val_loss /= len(val_loader)
print(f"[Epoch {epoch+1}/{num_epochs}], Validation Loss: {val_loss:.6f}")
print("Done!")
测试部分不再解释…
我认为本次比赛对于新手来说,最大的难点是数据集处理与模型输入输出的合理设置
- 卷积层、线性层、激活层这些挑挑选选总能替换
参数初始化的一种方法(还有其他方法,可以在bing.com
上搜索Pytorch卷积层初始化方法学习) ↩︎