赛题介绍
比赛链接:上海科学智能研究院
AI新能源功率预报:根据历史发电功率数据和对应时段多类别气象预测数据,实现次日零时起到未来24小时逐15分钟级新能源场站发电功率预测。
数据介绍
数据集下载:上海科学智能研究院(注册登录后即可下载数据集)
比赛提供两个数据集文件,初赛训练集和初赛测试集。
初赛训练集:
- nwp_data_train:包含10个新能源场站的气象预报数据,每个新能源场站中包含3个气象源(NWP_1、NWP_2、NWP_3),每个气象源包含20240101.nc~20241230.nc文件,每个.nc文件是第二天北京时间0点开始的未来24小时气象预报,时间间隔1小时,文件名的日期表示预报发布日期,如20240101.nc是2024年1月1日发布的,对1月2日的预报。气象预报数据是在场站周边 11x11 个格点上观测得到的。
- fact_data:包含10个新能源场站的归一化处理后的实发功率,编号1-5为风电场,6-10为光伏电场。数据时间为北京时间,数据时间间隔为15分钟。
| 气象变量说明 | ||
|---|---|---|
| 变量 | 描述 | 单位 |
u100 | 100米高度纬向风 | m/s(米/秒) |
v100 | 100米高度经向风 | m/s(米/秒) |
t2m | 2米气温 | K(开尔文) |
tp | 总降水量 | m(米) |
tcc | 总云量 | (0 - 1) |
sp | 地面气压 | Pa(帕斯卡) |
poai | 光伏面板辐照度 | W/m²(瓦/平方米) |
ghi | 水平面总辐照度 | W/m²(瓦/平方米) |
msl | 海平面气压 | Pa(帕斯卡) |
气象源1(NWP_1)、气象源3(NWP_3):[u100, v100, t2m, tp, tcc, sp, poai, ghi] | ||
气象源2(NWP_2):[u100, v100, t2m, tp, tcc,msl,poai,ghi] | ||
注:数据详细点击比赛链接进行了解。
Baseline代码理解
注:baseline只使用了气象源1(NWP_1)的气象数据
安装导入包
%pip install numpy==2.2.4 pandas==2.2.3 netCDF4==1.7.2 lightgbm==4.6.0 scikit-learn==1.6.1
from netCDF4 import Dataset
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import KFold
from sklearn.metrics import mean_squared_error
import lightgbm as lgb
from tqdm import tqdm
netCDF4:用于读写 netCDF 文件。netCDF文件是一种广泛应用于科学数据的文件格式,尤其在地球科学(气象、海洋、气候、环境等)、物理、工程等领域常见。这里用来读取气象预报数据。
数据探索
拿到一个数据集后,首要任务就是对其中的数据进行探索、分析,帮助后期更好地进行特征工程和建立模型。数据探索的常见方法如下(内容来自Datawhale):

1.1 数据观测
读取并查看训练集中的气象数据
nc_path = "data/初赛训练集/nwp_data_train/1/NWP_1/20240101.nc"
dataset = Dataset(nc_path, mode='r')
dataset.variables.keys()
查看 channel 变量
channel = dataset.variables["channel"][:]
channel
# 输出:array(['ghi', 'poai', 'sp', 't2m', 'tcc', 'tp', 'u100', 'v100'], dtype=object)
查看 data 的维度
data = dataset.variables['data'][:]
data.shape
# 输出:(1,24,8,11,11)
对 data 数据的理解
1. data是什么数据?
data中包含的是气象变量对应的数据,即 'ghi', 'poai', 'sp', 't2m', 'tcc', 'tp', 'u100', 'v100' 在场站周边 11x11 个格点上的数据。
2. data的五个维度分别表示什么?
- 无numpy数组的基础
运行以下代码进行观察,先充分了解numpy多维数组的结构:
import numpy as np data1 = np.random.rand(1,2,3,4,5) # data2 = np.random.rand(2,2,3,4,5) print(data1) # print(data2) # 自行更改维度数据,多观察几组就明白了
- 有numpy数组的基础
1:没有实质意义,可以理解为在四维数组外面多加了一个中括号[],将整个数据“框起来”作为一个data。
24:24个时间步长(即24小时)。
8:对应channel中的8个气象变量。
11、11:新能源场站周边 11x11 个格点上观测得到的气象数据。
一句话概括:data中包含每一个气象变量在 11×11 格点上每一小时的气象数据。
1.2 数据处理
数据处理部分的主要目标是创建用于模型训练的训练集 train ,需要完成以下两个工作:
- 由前面数据观测的结果可知,新能源场站中每个气象变量每一小时的气象数据为 11×11 格点上的观测数据,对于构建单个时间点的单个特征只需要一个标量即可,所以需要将 11×11 的二维数据转换为一维标量数据,采取的方法为将 11×11 格点的数据取均值。
- 将发电功率数据作为 target 值合并到 train 中。气象数据时间精度为h,而发电功率精度为15min,即一天的数据有24条天气数据与96(24*4)条功率数据,因此将功率数据中每四条数据只保留一条。
# 获取2024年日期
date_range = pd.date_range(start = '2024-1-1', end = '2024-12-30')
# 将日期格式从%Y-%m-%d转换为%Y%m%d
date = [date.strftime('%Y%m%d') for date in date_range]
# 定义读取训练集/测试集的函数
def get_data(path, date):
# 读取该天数据
dataset = Dataset(path.format(date), mode='r')
# 获取 channel 中的变量
channel = dataset.variables['channel'][:]
# 获取 data 数据
data = dataset.variables['data'][:]
# 将 11×11 的二维数据转换成一维标量
data_mean = np.array([np.mean(data[0,:,i,:,:], axis=(1,2)) for i in range(8)]).T
# 将数据与对应气象变量名整合为 Dataframe
return pd.DataFrame(data_mean, columns = channel)
# 定义训练集
## 定义路径模板
train_path_template = "data/初赛训练集/nwp_data_train/1/NWP_1/{}.nc"
## 通过列表推导式获取每一天的数据
data = [get_data(train_path_template, i) for i in date]
## 将每天的数据拼接并重设index
train = pd.concat(data, axis=0).reset_index(drop=True)
## 读取目标值
target = pd.read_csv('data/初赛训练集/fact_data/1_normalization_train.csv')
target = target[96:] # .nc气象数据是对未来24小时的气象预报,所以目标值应该从1.2开始选取
## 功率数据中每四条数据去掉三条
target = target[target['时间'].str.endswith('00:00')]
target = target.reset_index(drop=True)
## 将目标值合并到训练集
train['power'] = target['功率(MW)']
# 定义测试集
test_path_template = "data/初赛测试集/nwp_data_test/1/NWP_1/{}.nc"
date_range = pd.date_range(start='2024-12-31', end='2025-02-27')
date = [date.strftime('%Y%m%d') for date in date_range]
date[:5]
data = [get_data(test_path_template, i) for i in date]
test = pd.concat(data, axis=0).reset_index(drop=True)
test.shape
重点代码理解
data_mean = np.array([np.mean(data[0,:,i,:,:], axis=(1,2)) for i in range(8)]).T
- data[0, :, i, :, :]:‘0’ 表示取第一个维度的第一个切片,将维度从 [1, 24, 8, 11, 11] 降为 [24, 8, 11, 11];‘i’ 表示遍历第三个维度的所有切片,将维度从 [24, 8 ,11, 11] 降为 [24, 11, 11]
- axis=(1, 2):按 [24, 11, 11] 的第二、第三维度 (11, 11) 执行操作
- [np.mean()]:对二维格点数据 11 × 11 求均值转换为一维标量,结果为一个列表:
- np.array:按行将列表重新组合为一个 8×24 的数组
- T:转置为 24 × 8 的数组
1.3 数据可视化
hour = range(24)
plt.figure(figsize=(20, 10))
for i in range(9):
plt.subplot(3,3,i+1)
plt.plot(hour, train.iloc[:24, i]) # 取2024.1.1 24h进行观察
plt.title(train.columns.tolist()[i])
plt.show()

对可视化结果进行分析
- ghi(水平面总辐照度)和 poai(光伏面板辐照度)都近似遵循正态分布。
- sq(地面气压)、tcc(总云量)、v100(100米高度经向风)的变化趋势较为相近,在一天之内近似存在两个极小值,在中午前后达到一天峰值。其中,tcc 和 v100 的变化趋势更为一致,考虑是风速的变化会直接对云量产生影响。
- t2m(2米气温)的变化符合一般认知,在凌晨降至最低,在14点前后达到峰值。
- tp(总降水量)是这一天中唯一一个单调递增的气象变量。这里我会考虑其对发电功率有直接影响,因为从图上可以看出,当降水量在10点左右快速增多时,发电功率也在同时段出现断崖式下降,随着降水量的不断增多,发电功率逐渐降至于0。
- u100(100米高度纬向风)和发电功率的趋势较为一致,都有一个明确且相同的时间变化点——上午10点。10点前,u100的风值大,发电功率平均较大;10点后,u100的风值快速降低,发电功率也快速降低。
以上均为看到可视化结果时的初步猜想,还需要在后续实验中进一步验证。
数据清洗
数据和特征决定了机器学习的上限。分析完数据后,特征工程前必不可少的步骤是对数据清洗。数据清洗的作用是利用有关技术如数理统计、数据挖掘或预定义的清理规则将脏数据转化为满足数据质量要求的数据。数据清洗的常见方法如下(内容来自Datawhale):

在baseline中仅进行简单的数据处理,删除数据集中包含空值的行。
train = train.dropna().reset_index(drop=True)
特征工程
特征工程是将原始数据转化为更能代表预测目标的特征的过程,在机器学习和数据挖掘领域中至关重要。通过特征工程,可以让数据更好地适应模型,增强模型的泛化能力,最终提高预测的准确性和可靠性。特征工程的常见方法如下(内容来自Datawhale):

baseline中的特征工程:生成两个新的特征,一个是将经向风和纬向风合并成总风速特征;另一个是每一小时的时间特征。
def feature_combine(df):
# 复制一份数据
df_copy = df.copy()
# 新增列风速 将两个方向的风速合并为总风速
df_copy["wind_speed"] = np.sqrt(df_copy['u100']**2 + df_copy['v100']**2)
# 添加小时的特征
df_copy["h"] = df_copy.index % 24
return df_copy
train = feature_combine(train)
思考:还有哪些特征工程可以做?
模型训练与验证
特征工程也好,数据清洗也罢,都是为最终的模型来服务的,模型的建立和调参决定了最终的结果。模型的选择决定结果的上限, 如何更好的去达到模型上限取决于模型的调参。建模过程需要对常见的线性模型、非线性模型有基础的了解。模型构建完成后,需要掌握一定的模型性能验证的方法和技巧。建模调参的常见方法如下(内容来自Datawhale):

使用LightGBM模型
baseline中使用 lightgbm 模型,并选择经典的K折交叉验证方法进行离线评估,流程如下:
- K折交叉验证将样本数据随机的分成 K 份;
- 每次随机的选择 K-1 份作为训练集,剩下的1份作为验证集;
- 当这一轮完成后,重新随机选择 K-1 份来训练数据;
- 最后将 K 折预测结果取平均作为最终提交结果。

from sklearn.model_selection import KFold
from sklearn.metrics import mean_squared_error
import lightgbm as lgb
def cv_model(clf, train_x, train_y, test_x, seed=2024):
# 5折交叉验证
folds = 5
# 生成训练集和验证集的索引
kf = KFold(n_splits=folds, shuffle=True, random_state=seed)
# 存储验证结果
oof = np.zeros(train_x.shape[0])
# 存储测试集预测结果
test_predict = np.zeros(test_x.shape[0])
# 存储每折评分
cv_scores = []
# kf.split(train_x, train_y)返回一个生成器 每次调用返回新的train_index, valid_index
# 循环五次
for i, (train_index, valid_index) in enumerate(kf.split(train_x, train_y)):
print('************************************ {} ************************************'.format(str(i+1)))
# 获取当前折的训练集及验证集
trn_x, trn_y, val_x, val_y = train_x.iloc[train_index], train_y[train_index], train_x.iloc[valid_index], train_y[valid_index]
# 使用Lightgbm的Dataset构建训练及验证数据集
train_matrix = clf.Dataset(trn_x, label=trn_y)
valid_matrix = clf.Dataset(val_x, label=val_y)
# 模型参数
params = {
# 提升类型
'boosting_type': 'gbdt',
# 回归任务
'objective': 'regression',
# 评估指标
'metric': 'rmse',
# 子节点最小权重
'min_child_weight': 5,
# 最大叶子结点数
'num_leaves': 2 ** 8,
# L2正则化权重
'lambda_l2': 10,
# 每次迭代随机选择80%的特征
'feature_fraction': 0.8,
# 表示每次迭代随机选择80%的样本
'bagging_fraction': 0.8,
# 4次迭代执行一次bagging
'bagging_freq': 4,
# 学习率,即模型更新的幅度
'learning_rate': 0.1,
# 随机种子
'seed': 2023,
# 线程数
'nthread' : 16,
# 设置模型训练过程中不输出信息
'verbose' : -1,
}
# 使用参数训练模型
model = clf.train(params, train_matrix, 3000, valid_sets=[train_matrix, valid_matrix])
# 对验证集进行预测
val_pred = model.predict(val_x, num_iteration=model.best_iteration)
# 对测试集进行预测
test_pred = model.predict(test_x, num_iteration=model.best_iteration)
# 更新模型在验证集上的结果
oof[valid_index] = val_pred
# 将每个模型结果加权并相加
test_predict += test_pred / kf.n_splits
# 对rmse取倒数 得分越高性能越好
score = 1/(1+np.sqrt(mean_squared_error(val_pred, val_y)))
# 存储成绩
cv_scores.append(score)
# 打印成绩
print(cv_scores)
# 返回验证集及测试集结果
return oof, test_predict
# 获取训练集中除了power的其他列
cols = [f for f in train.columns if f not in ['power']]
# 使用函数
lgb_oof, lgb_test = cv_model(lgb, train[cols], train["power"], test)
# 将数据重复4次
lgb_test = [item for item in lgb_test for _ in range(4)]


1234

被折叠的 条评论
为什么被折叠?



