前言
ok,这是datawhale夏令营的一部分,这个赛题来源于第二届世界科学智能大赛地球科学赛道:AI极端降水预报。
本次大赛考察选手基于业界领先的伏羲气象大模型输出,进一步优化和提升未来三天的极端降水预测。赛事提供历史时段伏羲气象大模型未来72小时逐小时的多个气象要素以及对应时段的ERA5降水数据,输出给定时段的基于伏羲气象大模型要素作为输入的AI极端降水预测。
赛事数据
赛事提供的数据如下,除此之外不允许使用其他数据。
我们可以对提供的数据进行简单的划分。1:gt数据,也就是标签,也就是比赛的目标数据(数据来自欧洲中期天气预报中心(ECMWF)的再分析数据ERA5),赛事组织方提供了2019-2021三年的gt数据,用来训练;2.ft数据,训练数据,这是伏羲大模型生成的特征数据,同样提供了2019-2021三年的数据,数据量比较大,解压前就有42G,这些数据可以供我们来训练模型,ft和gt的数据是按照时间戳一一对应的;3.test数据,这部分是没有标签的,是供我们来进行推理的,推理的结果我们存成一个zip文件,用来提供。
赛题简单分析
参考赛事方提供的背景,初步研读下来,我们可以得出,这是一个时间序列预测问题,是一个回归问题。我们需要使用历史的天气数据来预测未来72小时,是否会发生极端降水。通俗来说, 时间序列预测模型目的在构建一个全时段可用的时间序列函数, 根据输入可以精准的得到目的的输出, 比如本次赛题中的降水预测模型。
baseline搭建
对赛题初步分析后,我们已经明确了这个赛事的目标是什么,这个问题应该如何去解决。
首先我们需要搭建一个baseline出来,对数据进行简单的梳理,把竞赛的整个流程跑通。baseline有助于我们更好的了解问题和数据,可以检验我们流程,检查数据,以便后续在baseline的基础上进行进一步的挖掘,提升优化上分。这是baseline的作用,也是我们进行竞赛,一般拿到赛题后,要尽快完成的第一件事。baseline也是后续我们比较模型性能的一个基准。通常baseline都会相对简单一些,方便我们快速跑通。
Datawhale已经帮我们搭建好了baseline了,那么接下来我就在此基础上,简单的分享一下baseline的思路。
数据集准备和处理
本次赛事,我们使用的魔搭社区的Studio,我们可以开一个GPU机器,新手是有100小时的免费时长的,当然我们也可以用本地机器来跑,如果是游戏本的话,建议配置RTX4060以上。
开一个新机器,首先我们需要安装必备的库。这里我们需要使用xarray库。
xarray是一个 Python 库,用于处理和分析多维数组数据。它构建在 NumPy 和 pandas 之上,并为多维数据提供了更高级别的接口。xarray特别适用于气象学、海洋学以及其他涉及多维数据集的领域,比如那些通常存储在 NetCDF 文件中的数据。
!pip install xarray[complete]
!apt update&&apt install axel
我们需要下载所需要的数据集,因为是baseline,所以我们只选用了一部分数据。我们将数据进行解压到对应的文件,加上Feature(ft)数据,那么我们所需要的feature数据,groundtruth数据,test数据就齐活了。baseline中,我们只使用了2021年的部分数据。
!axel -n 5 -o weather.round1.train.gt.2019-2021.zip 'https://tianchi-race-prod-sh.oss-cn-shanghai.aliyuncs.com/file/race/documents/532234/bigFile/weather.round1.train.gt.2019-2021.zip?Expires=1721991780&OSSAccessKeyId=LTAI5t7fj2oKqzKgLGz6kGQc&Signature=B14IJ7j7JAzy2i2K1XGSVNtwGvs%3D&response-content-disposition=attachment%3B%20'
!axel -n 5 -o weather.round1.test.zip 'https://tianchi-race-prod-sh.oss-cn-shanghai.aliyuncs.com/file/race/documents/532234/bigFile/weather.round1.test.zip?Expires=1721991827&OSSAccessKeyId=LTAI5t7fj2oKqzKgLGz6kGQc&Signature=1XtjX3ljMwfDFiPKLKIkm7FCMRM%3D&response-content-disposition=attachment%3B%20'
!unzip -q -n weather.round1.train.gt.2019-2021.zip -d groundtruth
!unzip -q -n weather.round1.test.zip -d test
比赛的数据分为数据特征(feature数据)和数据真值(groundtruth),这两者就是我们需要的训练数据和标签。他们分别存在feature和groundtruth文件夹中,我们导入所需的库,并配置好数据集的路径。
import os
import pandas as pd
import xarray as xr
from torch.utils.data import Dataset, DataLoader
# path config
feature_path = 'feature' #自定义路径并修改为自己的路径
gt_path = 'groundtruth' #自定义路径并修改为自己的路径
years = ['2021']
fcst_steps = list(range(1, 73, 1))
我们需要定义Feature类和GT类(groundtruth) ,定义好数据集,以方便后续的操作。
# Feature部分
class Feature:
def __init__(self):
self.path = feature_path
self.years = years
self.fcst_steps = fcst_steps
self.features_paths_dict = self.get_features_paths()
def get_features_paths(self):
init_time_path_dict = {}
for year in self.years:
init_time_dir_year = os.listdir(os.path.join(self.path, year))
for init_time in sorted(init_time_dir_year):
init_time_path_dict[pd.to_datetime(init_time)] = os.path.join(self.path, year, init_time)
return init_time_path_dict
def get_fts(self, init_time):
return xr.open_mfdataset(self.features_paths_dict.get(init_time) + '/*').sel(lead_time=self.fcst_steps).isel(
time=0)
# GroundTruth部分
class GT:
def __init__(self):
self.path = gt_path
self.years = years
self.fcst_steps = fcst_steps
self.gt_paths = [os.path.join(self.path, f'{year}.nc') for year in self.years]
self.gts = xr.open_mfdataset(self.gt_paths)
def parser_gt_timestamps(self, init_time):
return [init_time + pd.Timedelta(f'{fcst_step}h') for fcst_step in self.fcst_steps]
def get_gts(self, init_time):
return self.gts.sel(time=self.parser_gt_timestamps(init_time))
接下来我们定义数据加载器,我们使用PyTorch框架。
# 构建Dataset部分
class mydataset(Dataset):
def __init__(self):
self.ft = Feature()
self.gt = GT()
self.features_paths_dict = self.ft.features_paths_dict
self.init_times = list(self.features_paths_dict.keys())
def __getitem__(self, index):
init_time = self.init_times[index]
ft_item = self.ft.get_fts(init_time).to_array().isel(variable=0).values
print(type(ft_item))
gt_item = self.gt.get_gts(init_time).to_array().isel(variable=0).values
print(type(gt_item))
return ft_item, gt_item
def __len__(self):
return len(list(self.init_times))
我们可以打印查看我们的数据数量。然后dataload.
# 可以查看一下已经构建的dataset
# define dataset
my_data = mydataset()
print('sample num:', mydataset().__len__())
train_loader = DataLoader(my_data, batch_size=1, shuffle=True)
这样数据的加载和预处理部分就完成了,baseline其实没有对这一块进行过多的深度探讨,这一块是有很多的信息供我们后续挖掘的,有人说,一个好的数据科学家80%的时间都是在处理数据,可以看出数据的价值,数据对一个深度学习模型的重要性,这是大厦的地基,是我们进行一切炼丹的基础。
模型定义
数据部分处理完了,我们开始定义我们的模型,作为一个时间序列问题,我们是有很多模型来进行选择的,baseline采用了一维卷积来处理,而且只是一层的卷积网络。我们可以利用PyTorch框架来搭建模型,定义好Class Model。
直接上代码:
# 模型构建部分
import torch.nn as nn
class Model(nn.Module):
def __init__(self, num_in_ch, num_out_ch):
super(Model, self).__init__()
self.conv1 = nn.Conv2d(num_in_ch, num_out_ch, 3, 1, 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 = 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 = Model(input_size, output_size).cuda()
模型训练
首先定义损失函数:
回归问题,我们可以使用MSE来评估结果。MSE(Mean Squared Error,均方误差)是一种常用的损失函数,尤其在回归任务中被广泛应用。MSE 的计算方式是将预测值与真实值之间的差的平方求和,然后除以样本数量,得到的均值就是 MSE 的值。
# define loss
loss_func = nn.MSELoss()
接下来训练模型,我们需要定义好num_epochs,lr,优化器等等,然后train吧,train完保存成.pth格式。.pth格式在PyTorch里面通常用于保存神经网络模型的权重和偏置等参数。这个我们后续推理的时候要用到。
import numpy as np
import torch
# from tqdm import tqdm
# Train the model
num_epochs = 1
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# for epoch in tqdm(range(num_epochs)):
for epoch in range(num_epochs):
for index, (ft_item, gt_item) in enumerate(train_loader):
ft_item = ft_item.cuda().float()
gt_item = gt_item.cuda().float()
print(type(ft_item))
print(type(gt_item))
# Forward pass
output_item = model(ft_item)
loss = loss_func(output_item, gt_item)
# Backward and optimize
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 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():.4f}")
# Save the model weights
torch.save(model.state_dict(), 'model_weights.pth')
模型推理(预测)
接下来就是模型推理环节啦,这里我们需要用到test数据作为输入,加载之前训练好的模型,进行预测,来上代码:
# Inference
# Load the model weights
model.load_state_dict(torch.load('model_weights.pth'))
model.eval()
import os
test_data_path = "test/weather.round1.test"
os.makedirs("./output", exist_ok=True)
for index, test_data_file in enumerate(os.listdir(test_data_path)):
test_data = torch.load(os.path.join(test_data_path, test_data_file))
test_data = test_data.cuda().float()
# Forward pass
output_item = model(test_data)
# Print the output shape
print(f"Output shape for sample {test_data_file.split('.')[0]}: {output_item.shape}")
# Save the output
output_path = f"output/{test_data_file}"
torch.save(output_item.cpu(), output_path)
# Load the model weights
model.load_state_dict(torch.load("model_weights.pth"))
好了,到这里,我们的baseline就跑完啦。把输出结果打包压缩,就可以上传啦,会得到我们的第一个分数哦。撒花。。。
!zip -r output.zip output
小结
做一个小结吧,一般拿到一个竞赛题,我们需要进行赛题分析,然后搭建baseline,通常baseline包括1)数据集准备和处理;2)模型定义;3)模型训练;4)模型预测,这几个流程。baseline相对比较简单,后续我们有很大的优化空间。优化的思路也可以从baseline的这几个流程着手。
一些优化思路:
使用更多的数据;使用更深的模型;使用不同的模型架构;调整超参数;特征工程;模型融合等。
跑baseline过程中,遇到的一些问题以及解决:
1)第一次跑完baseline,得分为0,程序没有任何报错。没有深究原因。第二次我用完整的数据跑了一遍,分数可以达到0.1左右;
2)用完整数据跑的过程中,train的时候有一个KeyError报错,原因是因为部分gt数据缺失,没有对应的值,后来参考了baseline进阶版,修改了这一块的程序,解决;
3)我用本地机器跑的,是WINDOWS系统,一开始程序压缩后的结果,最后提交文件的时候,提交失败,"bad error file",我就自己手动把output文件压缩完,再提交,解决。
看完了的小伙伴,觉得有用的话,就请点赞关注收藏吧。