乐趣区

关于paddle:又一开发者公布高分方案源代码助力软件杯选手高效解题

本文作者:艾宏峰

  • 算法工程师
  • M6 Global 赛道总排名 4th
  • KDD Cup 2022 风电功率预测飞桨赛道 5th

“中国软件杯”大学生软件设计大赛——龙源风电赛道, 5 月 31 日 预选赛截止,80% 选手将升级区域赛,欢送大家 放松报名

赛题背景

随着清洁能源的疾速倒退,风力发电曾经成为可再生能源的重要组成部分,然而风具备随机性特点,惯例天气预报无奈精确反映出风电场所在区域的实在风速,从而造成发电功率预测准确率低下,影响电力供需平衡。因而,进步风电功率预测的准确性,为电网调度提供迷信撑持,对我国能源产业有非常重要的价值。

此次赛题由百度飞桨和龙源电力设置,数据集由寰球最大风电经营企业龙源电力提供,采集自实在风力发电数据,要求选手基于百度飞桨 PaddlePaddle 设计一个能够通过深度学习技术实现对风力发电进行功率预测及治理的软件系统。

时序预测技术介绍

工夫序列预测技术是指基于历史数据和工夫变化规律,通过数学模型和算法对将来发展趋势进行预测的一种技术。工夫序列预测技术广泛应用于经济、金融、交通、气象等畛域,以帮忙人们做出更加精确的决策。
时序预测从不同角度看有不同分类:

  • 实现原理 的角度,能够分为:传统统计学,机器学习(又分非深度学习和深度学习);
  • 预测步长 辨别,能够分为单步预测和多步预测;
  • 输出变量 辨别,能够分为自回归预测和应用协变量进行预测;
  • 输入后果 辨别,能够分为点预测和概率预测;
  • 指标个数 辨别,能够分为一元、多元、多重工夫序列预测

这些分类是不同角度下的分类,同一种算法往往只能是分类中的一种,例如传统的统计学算法只适宜做自回归预测而不适宜协变量预测。

工夫序列预测技术的钻研历史能够追溯到 20 世纪初期。最早的工夫序列预测办法是基于工夫平均法和线性趋势法,起初倒退出了指数平滑法、ARIMA 模型、神经网络模型等预测办法。随着机器学习和深度学习的倒退,工夫序列预测技术也失去了一直的拓展和翻新,比方 Transformer 与时序的联合。

风电功率预测钻研意义与价值

工夫序列预测技术有着宽泛的利用场景。例如,

  • 经济畛域,工夫序列预测技术能够用于股票市场预测、经济增长预测、通货膨胀预测等。
  • 交通畛域,工夫序列预测技术能够用于公交车到站工夫预测、交通拥堵预测等。
  • 气象畛域,工夫序列预测技术能够用于气象灾祸预警、天气变动预测等。

而在风电功率预测上,其钻研意义和价值更不容忽视,它们包含:

  • 进步风电发电效率:通过精准预测风电功率,能够合理安排风电机组的运行,进步风电发电效率,缩小能源节约。
  • 保障电网稳固运行:风电功率预测能够帮忙电网运营商及时调整电网负荷,防止电网过载或供电有余等问题,保障电网稳固运行。
  • 促成可再生能源倒退:风电功率预测能够进步风电发电的可靠性和经济性,促成可再生能源倒退,升高对传统能源的依赖。
  • 升高能源老本:通过精准预测风电功率,能够无效防止风电机组的过剩和有余,升高能源老本,进步能源利用率。
  • 推动智能电网建设:风电功率预测是智能电网建设的重要组成部分,能够实现对风电发电的精准监控和治理,推动智能电网建设。

赛制赛段

  • 预选赛

5 月 31 日截止,算法赛,80% 选手升级区域赛;

  • 区域赛

6- 7 月,算法赛 60%+ 软件赛 40%,颁发省级奖项;

  • 总决赛

8 月,软件赛,颁发国赛奖项。

赛题数据

本赛题数据集由寰球最大风电经营企业龙源电力提供,采集自实在风力发电数据。预选赛训练数据和区域赛训练数据别离为不同 10 个风电场近一年的运行数据共 30 万余条,每 15 分钟采集一次,包含风速、风向、温度、湿度、气压和实在功率等,具体的数据字段中英文对应如下:

  • WINDSPEED 预测风速
  • WINDDIRECTION 风向
  • TEMPERATURE 温度
  • HUMIDITY 湿度
  • PRESSURE 气压
  • PREPOWER 预测功率(系统生成)
  • ROUND(A.WS,1)理论风速
  • ROUND(A.POWER,0)理论功率(计量口径一)
  • YD15 理论功率(预测指标,计量口径二)

注:“预测风速”字段,指的是由权威的气象机构,像是中央气象台、欧洲国家气象中心等公布的商业气象数据源。从工夫线来说,理论功率预测须要提前 36 个小时、72 个小时、240 的小时等取得数值天气预报,从而进行功率的预测。

数据注意事项

  • 原始数据集存在不同格局的风机数据,须要额定的数据拼接解决工作;
  • 每个风机的最初一天的 ROUND(A.POWER,0)和 YD15 两个字段数据根本为空,这是出题方心愿咱们预测填空的数据;
  • csv 内的工夫戳未必有序,须要自行排序;
  • 数据存在缺失和离群值;
  • 数据存在反复样本(即同一时间戳有多条样本)。以风机 04 为例,反复样本数量: 34764, 占比: 38.68340232340766%。

以风机 4 为例的 EDA 后果展现

官网对数据的一些回复

  • 因为测量设施和网络传输问题,YD15 可能呈现数据异样(包含用于评测的输出数据)。
  • 实际上因为一些脏数据的存在,YD15 有时候会缺失或异样,这个时候 ROUND(A.POWER,0)如果有正常值的话,能够被视为 YD15 的代替。
  • YD15 的异样值解决规定是,当 YD15 为空时,依照逻辑顺次用 ROUND(A.POWER,0)、PREPOWER 进行替换。
  • 如何定义 YD15 存在异样?在本赛题中,YD15 异样包含两种状况:(1) 空值,(2) 在一段时间内、其它字段失常变动时,YD15 继续齐全不变。除以上两种状况之外,YD15 的数值变动都可认为是失常景象,如为 0 或负值。

集体补充

  • 当理论风速为 0 时,存在功率 >0 有些异样,而后有些风速过大 >12.5,存在功率为 0 的异样。
  • 指标列 ROUND(A.POWER,0)和 YD15,与风速 WINDSPEED、PREPOWER、PRESSURE 和 ROUND(A.WS,1)强相干;
  • 指标列 ROUND(A.POWER,0)和 YD15 之间就有很强的相关性;

评测阐明

算法局部

本次较量要求选手将算法模型提交至人工智能学习与实训社区 AI Studio 进行主动评测,预选赛凋谢 10 个风场数据,区域赛凋谢新的 10 个风场数据,预选赛和区域赛算法问题各占 30%。

要求选手基于飞桨 PaddlePaddle 依据官网提供的数据集,设计一种利用当日 05:00 之前的数据,预测次日 00:00 至 23:45 理论功率的办法。准确率按日统计,依据 10 个风电场均匀准确率进行排名;准确率雷同的情景下,依据每日单点的均匀最大偏差绝对值排名。

def calc_acc(y_true, y_pred):
    rmse = np.sqrt(np.mean((y_true - y_pred)**2))
    return 1 - rmse/201000

软件局部

本次较量要求选手基于 Web 技术实现:

  • 数据可视化

将预测后果以图表等模式展现进去,便于用户进行察看和剖析;

  • 实时更新与滚动预测

可能基于提供的数据实时模仿实在功率、预测功率及其之间的差别,通过调节过来不同长度的时间段,以更新将来预测后果,且预测的时间段可调节;

  • 响应式设计

反对多种终端,包含 PC 端、挪动端等,以适应不同设施的屏幕尺寸和分辨率;

  • 其余翻新附加性能

提交阐明

测评数据的格局如下:

| --- ./infile(内置于测评零碎中,参赛选手不可见)| --- 0001in.csv
     | --- 0002in.csv
     | ---  ...

参赛选手须要提交一个命名为 submission.zip 的压缩包,并且压缩包内应蕴含:

| --- ./model # 寄存模型的目录,并且大小不超过 200M(必选)| --- ./env # 寄存依赖库的目录(可选)| --- predict.py # 评估代码(必选)| --- pip-requirements.txt # 寄存依赖库的文件(可选)| --- …

参赛选手在代码提交页面提交压缩包后,测评零碎会解压选手提交的压缩包,并执行如下命令:

python predict.py

测评文件 predict.py 应该实现的性能是:读取 infile 文件夹下的测评数据,并将预测后果保留到 pred 文件夹中。

| --- ./pred(须要选手生成)| --- 0001out.csv
     | --- 0002out.csv
     | --- …

基线模型流程

基于飞桨 PaddlePaddle 的多任务 LSTM 时序预测基线模型 pipeline 如下:

以工夫序列举例,因为个别测试集也会是将来数据,所以咱们也要保障训练集是历史数据,而划分出的验证集是将来数据,不然会产生“工夫穿梭”的数据泄露问题,导致模型过拟合(例如用将来预测历史数据),这个时候就有两种验证划分形式可参考应用:

  • TimeSeriesSplit:Sklearn 提供的 TimeSeriesSplit;
  • 固定窗口滑动划分法:固定工夫窗口,一直在数据集上滑动,取得训练集和验证集。(集体举荐这种)

在时序工作中,有 2 类数据源,如下图所示:

(1)动态变量(Static Covariates):不会随工夫变动的变量,例如风机 id、风机地位;

(2)时变变量(Time-dependent Inputs):随工夫变动的变量;

  • 过来观测的时变变量(Past-observed Inputs):过来可知,但将来不可知,例如历史风速、温度、气压等
  • 先验已知将来的时变变量(Apriori-known Future Inputs):过来和将来都可知,例如天气预报将来风速、温度、气压等;

数据加载器的代码如下:

# unix 工夫戳转换
def to_unix_time(dt):
    # timestamp to unix
    epoch = datetime.datetime.utcfromtimestamp(0)
    return int((dt - epoch).total_seconds())

def from_unix_time(unix_time):
    # unix to timestamp
    return datetime.datetime.utcfromtimestamp(unix_time)

class TSDataset(paddle.io.Dataset):
    """ 时序 DataSet
    划分数据集、适配 dataloader 所需的 dataset 格局
    ref: https://github.com/thuml/Autoformer/blob/main/data_provider/data_loader.py
    """
    def __init__(self, data, 
                 ts_col='DATATIME',
                 use_cols =['WINDSPEED', 'PREPOWER', 'WINDDIRECTION', 'TEMPERATURE', 'HUMIDITY', 
                 'PRESSURE', 'ROUND(A.WS,1)', 'ROUND(A.POWER,0)', 'YD15',
                 'month', 'day', 'weekday', 'hour', 'minute'],
                 labels = ['ROUND(A.POWER,0)', 'YD15'], 
                 input_len = 24*4*5, pred_len = 24*4, stride=19*4, data_type='train',
                 train_ratio = 0.7, val_ratio = 0.15):
        super(TSDataset, self).__init__()
        self.ts_col = ts_col        # 工夫戳列
        self.use_cols = use_cols    # 训练时应用的特色列
        self.labels = labels        # 待预测的标签列
        self.input_len = input_len  # 模型输出数据的样本点长度,15 分钟距离,一个小时 14 个点,近 5 天的数据就是 24*4*5
        self.pred_len = pred_len    # 预测长度,预测次日 00:00 至 23:45 理论功率,即 1 天:24*4
        self.data_type = data_type  # 须要加载的数据类型
        self.scale = True           # 是否须要标准化
        self.train_ratio = train_ratio # 训练集划分比例
        self.val_ratio = val_ratio  # 验证集划分比例
        # 因为赛题要求利用当日 05:00 之前的数据,预测次日 00:00 至 23:45 理论功率
        # 所以 x 和 label 要距离 19* 4 个点
        self.stride = stride
        assert data_type in ['train', 'val', 'test']    # 确保 data_type 输出符合要求
        type_map = {'train': 0, 'val': 1, 'test': 2}
        self.set_type = type_map[self.data_type]

        self.transform(data)

    def transform(self, df):
        # 获取 unix 工夫戳、输出特色和预测标签
        time_stamps, x_values, y_values = df[self.ts_col].apply(lambda x:to_unix_time(x)).values, df[self.use_cols].values, df[self.labels].values
        # 划分数据集
        # 这里能够按需设置划分比例
        num_train = int(len(df) * self.train_ratio)
        num_vali = int(len(df) * self.val_ratio)
        num_test = len(df) - num_train - num_vali
        border1s = [0, num_train-self.input_len-self.stride, len(df)-num_test-self.input_len-self.stride]
        border2s = [num_train, num_train + num_vali, len(df)]
        # 获取 data_type 下的左右数据截取边界
        border1 = border1s[self.set_type]
        border2 = border2s[self.set_type]    

        # 标准化
        self.scaler = StandardScaler()
        if self.scale:
            # 应用训练集失去 scaler 对象
            train_data = x_values[border1s[0]:border2s[0]]
            self.scaler.fit(train_data)
            data = self.scaler.transform(x_values)
            # 保留 scaler
            pickle.dump(self.scaler, open('/home/aistudio/submission/model/scaler.pkl', 'wb'))
        else:
            data = x_values

        # array to paddle tensor
        self.time_stamps = paddle.to_tensor(time_stamps[border1:border2], dtype='int64')
        self.data_x = paddle.to_tensor(data[border1:border2], dtype='float32')
        self.data_y = paddle.to_tensor(y_values[border1:border2], dtype='float32')  

    def __getitem__(self, index):
        """实现__getitem__办法,定义指定 index 时如何获取数据,并返回单条数据(训练数据)"""
        # 因为赛题要求利用当日 05:00 之前的数据,预测次日 00:00 至 23:45 理论功率
        # 所以 x 和 label 要距离 19* 4 个点
        s_begin = index
        s_end = s_begin + self.input_len
        r_begin = s_end + self.stride
        r_end = r_begin + self.pred_len

        # TODO 能够减少对将来可见数据的获取
        seq_x = self.data_x[s_begin:s_end]
        seq_y = self.data_y[r_begin:r_end]
        ts_x = self.time_stamps[s_begin:s_end]
        ts_y = self.time_stamps[r_begin:r_end]
        return seq_x, seq_y, ts_x, ts_y

    def __len__(self):
        """实现__len__办法,返回数据集总数目"""
        return len(self.data_x) - self.input_len - self.stride - self.pred_len  + 1


class TSPredDataset(paddle.io.Dataset):
    """ 时序 Pred DataSet
    划分数据集、适配 dataloader 所需的 dataset 格局
    ref: https://github.com/thuml/Autoformer/blob/main/data_provider/data_loader.py
    """
    def __init__(self, data, 
                 ts_col='DATATIME',
                 use_cols =['WINDSPEED', 'PREPOWER', 'WINDDIRECTION', 'TEMPERATURE', 'HUMIDITY', 
                 'PRESSURE', 'ROUND(A.WS,1)', 'ROUND(A.POWER,0)', 'YD15',
                 'month', 'day', 'weekday', 'hour', 'minute'],
                 labels = ['ROUND(A.POWER,0)', 'YD15'],  
                 input_len = 24*4*5, pred_len = 24*4, stride=19*4):
        super(TSPredDataset, self).__init__()
        self.ts_col = ts_col        # 工夫戳列
        self.use_cols = use_cols    # 训练时应用的特色列
        self.labels = labels        # 待预测的标签列
        self.input_len = input_len  # 模型输出数据的样本点长度,15 分钟距离,一个小时 14 个点,近 5 天的数据就是 24*4*5
        self.pred_len = pred_len    # 预测长度,预测次日 00:00 至 23:45 理论功率,即 1 天:24*4
        # 因为赛题要求利用当日 05:00 之前的数据,预测次日 00:00 至 23:45 理论功率
        # 所以 x 和 label 要距离 19* 4 个点
        self.stride = stride        
        self.scale = True           # 是否须要标准化

        self.transform(data)

    def transform(self, df):
        # 获取 unix 工夫戳、输出特色和预测标签
        time_stamps, x_values, y_values = df[self.ts_col].apply(lambda x:to_unix_time(x)).values, df[self.use_cols].values, df[self.labels].values
        # 截取边界
        border1 = len(df) - self.input_len - self.stride - self.pred_len
        border2 = len(df)   

        # 标准化
        self.scaler = StandardScaler()
        if self.scale:
            # 读取预训练好的 scaler
            self.scaler = pickle.load(open('/home/aistudio/submission/model/scaler.pkl', 'rb'))
            data = self.scaler.transform(x_values)
        else:
            data = x_values

        # array to paddle tensor
        self.time_stamps = paddle.to_tensor(time_stamps[border1:border2], dtype='int64')
        self.data_x = paddle.to_tensor(data[border1:border2], dtype='float32')
        self.data_y = paddle.to_tensor(y_values[border1:border2], dtype='float32')  

    def __getitem__(self, index):
        """实现__getitem__办法,定义指定 index 时如何获取数据,并返回单条数据(训练数据)"""
        # 因为赛题要求利用当日 05:00 之前的数据,预测次日 00:00 至 23:45 理论功率
        # 所以 x 和 label 要距离 19* 4 个点
        s_begin = index
        s_end = s_begin + self.input_len
        r_begin = s_end + self.stride
        r_end = r_begin + self.pred_len

        # TODO 能够减少对将来可见数据的获取
        seq_x = self.data_x[s_begin:s_end]
        seq_y = self.data_y[r_begin:r_end]
        ts_x = self.time_stamps[s_begin:s_end]
        ts_y = self.time_stamps[r_begin:r_end]
        return seq_x, seq_y, ts_x, ts_y

    def __len__(self):
        """实现__len__办法,返回数据集总数目"""
        return len(self.data_x) - self.input_len - self.stride - self.pred_len  + 1

模型代码如下:

class MultiTaskLSTM(paddle.nn.Layer):
    """ 多任务 LSTM 时序预测模型
    LSTM 为共享层网络,对两个预测指标别离有两个分支独立线性层网络

    TODO 其实该模型就是个 Encoder,如果后续要引入天气预测将来的变量,补充个 Decoder,而后 Encoder 负责历史变量的编码,Decoder 负责将 编码后的历史编码后果 和 它编码将来变量的编码后果 合并后,做解码预测即可
    """
    def __init__(self,feat_num=14, hidden_size=64, num_layers=2, dropout_rate=0.7, input_len=120*4, pred_len=24*4):
        super(MultiTaskLSTM, self).__init__()
        # LSTM 为共享层网络
        self.lstm_layer = paddle.nn.LSTM(feat_num, hidden_size, 
                                    num_layers=num_layers, 
                                    direction='forward', 
                                    dropout=dropout_rate)
        # 为 'ROUND(A.POWER,0)' 构建分支网络
        self.linear1_1 = paddle.nn.Linear(in_features=input_len*hidden_size, out_features=hidden_size*2)
        self.linear1_2 = paddle.nn.Linear(in_features=hidden_size*2, out_features=hidden_size)
        self.linear1_3 = paddle.nn.Linear(in_features=hidden_size, out_features=pred_len)
        # 为 'YD15' 构建分支网络 
        self.linear2_1 = paddle.nn.Linear(in_features=input_len*hidden_size, out_features=hidden_size*2)
        self.linear2_2 = paddle.nn.Linear(in_features=hidden_size*2, out_features=hidden_size)
        self.linear2_3 = paddle.nn.Linear(in_features=hidden_size, out_features=pred_len)
        self.dropout = paddle.nn.Dropout(dropout_rate)


    def forward(self, x):
        # x 形态大小为[batch_size, input_len, feature_size]
        # output 形态大小为[batch_size, input_len, hidden_size]
        # hidden 形态大小为[num_layers, batch_size, hidden_size]
        output, (hidden, cell) = self.lstm_layer(x)
        # output: [batch_size, input_len, hidden_size] -> [batch_size, input_len*hidden_size]
        output = paddle.reshape(output, [len(output), -1])

        output1 = self.linear1_1(output)
        output1 = self.dropout(output1)
        output1 = self.linear1_2(output1)
        output1 = self.dropout(output1)
        output1 = self.linear1_3(output1)

        output2 = self.linear2_1(output)
        output2 = self.dropout(output2)
        output2 = self.linear2_2(output2)
        output2 = self.dropout(output2)
        output2 = self.linear2_3(output2)

        # outputs: ([batch_size, pre_len, 1], [batch_size, pre_len, 1])
        return [output1, output2]

模型训练、验证和测试代码:

def train(df, turbine_id):
    # 设置数据集
    train_dataset = TSDataset(df, input_len = input_len, pred_len = pred_len, data_type='train')
    val_dataset = TSDataset(df, input_len = input_len, pred_len = pred_len, data_type='val')
    test_dataset = TSDataset(df, input_len = input_len, pred_len = pred_len, data_type='test')
    print(f'LEN | train_dataset:{len(train_dataset)}, val_dataset:{len(val_dataset)}, test_dataset:{len(test_dataset)}')

    # 设置数据读取器
    train_loader = paddle.io.DataLoader(train_dataset, shuffle=True, batch_size=batch_size, drop_last=True)
    val_loader = paddle.io.DataLoader(val_dataset, shuffle=False, batch_size=batch_size, drop_last=True)
    test_loader = paddle.io.DataLoader(test_dataset, shuffle=False, batch_size=1, drop_last=False)

    # 设置模型
    model = MultiTaskLSTM()

    # 设置优化器
    scheduler = paddle.optimizer.lr.ReduceOnPlateau(learning_rate=learning_rate, factor=0.5, patience=3, verbose=True)
    opt = paddle.optimizer.Adam(learning_rate=scheduler, parameters=model.parameters())

    # 设置损失
    mse_loss = MultiTaskMSELoss()

    train_loss = []
    valid_loss = []
    train_epochs_loss = []
    valid_epochs_loss = []
    early_stopping = EarlyStopping(patience=patience, verbose=True, ckp_save_path=f'/home/aistudio/submission/model/model_checkpoint_windid_{turbine_id}.pdparams')

    for epoch in tqdm(range(epoch_num)):
        # =====================train============================
        train_epoch_loss, train_epoch_mse1,  train_epoch_mse2 = [], [], []
        model.train() # 开启训练
        for batch_id, data in enumerate(train_loader()):             
            x = data[0]
            y = data[1]
            # 预测
            outputs = model(x)
            # 计算损失
            mse1, mse2, avg_loss = mse_loss(outputs, y)
            # 反向流传
            avg_loss.backward()
            # 梯度降落
            opt.step()
            # 清空梯度
            opt.clear_grad()
            train_epoch_loss.append(avg_loss.numpy()[0])
            train_loss.append(avg_loss.item())
            train_epoch_mse1.append(mse1.item())
            train_epoch_mse2.append(mse2.item())
        train_epochs_loss.append(np.average(train_epoch_loss))
        print("epoch={}/{} of train | loss={}, MSE of ROUND(A.POWER,0):{}, MSE of YD15:{}".format(epoch, epoch_num,
        np.average(train_epoch_loss), np.average(train_epoch_mse1), np.average(train_epoch_mse2)))

        # =====================valid============================
        model.eval() # 开启评估 / 预测
        valid_epoch_loss, valid_epochs_mse1,  valid_epochs_mse2 = [], [], []
        for batch_id, data in enumerate(val_loader()): 
            x = data[0]
            y = data[1]
            outputs = model(x)
            mse1, mse2, avg_loss = mse_loss(outputs, y)
            valid_epoch_loss.append(avg_loss.numpy()[0])
            valid_loss.append(avg_loss.numpy()[0])
            valid_epochs_mse1.append(mse1.item())
            valid_epochs_mse2.append(mse2.item())
        valid_epochs_loss.append(np.average(valid_epoch_loss))
        print('Valid: MSE of ROUND(A.POWER,0):{}, MSE of YD15:{}'.format(np.average(train_epoch_mse1), np.average(train_epoch_mse2)))

        # ==================early stopping======================
        early_stopping(valid_epochs_loss[-1], model=model)
        if early_stopping.early_stop:
            print(f"Early stopping at Epoch {epoch-patience}")
            break

    print('Train & Valid:')
    plt.figure(figsize=(12,3))
    plt.subplot(121)
    plt.plot(train_loss[:],label="train")
    plt.title("train_loss")
    plt.xlabel('iteration')
    plt.subplot(122)
    plt.plot(train_epochs_loss[1:],'-o',label="train")
    plt.plot(valid_epochs_loss[1:],'-o',label="valid")
    plt.title("epochs_loss")
    plt.xlabel('epoch')
    plt.legend()
    plt.tight_layout()
    plt.show()

    # =====================test============================
    # 加载最优 epoch 节点下的模型
    model = MultiTaskLSTM()
    model.set_state_dict(paddle.load(f'/home/aistudio/submission/model/model_checkpoint_windid_{turbine_id}.pdparams'))

    model.eval() # 开启评估 / 预测
    test_loss, test_epoch_mse1, test_epoch_mse2 = [], [], []
    test_accs1, test_accs2 = [], [] 
    for batch_id, data in tqdm(enumerate(test_loader())): 
        x = data[0]
        y = data[1]
        ts_y = [from_unix_time(x) for x in data[3].numpy().squeeze(0)]
        outputs = model(x)
        mse1, mse2, avg_loss = mse_loss(outputs, y)
        acc1 = calc_acc(y.numpy().squeeze(0)[:,0], outputs[0].numpy().squeeze(0))
        acc2 = calc_acc(y.numpy().squeeze(0)[:,1], outputs[1].numpy().squeeze(0))
        test_loss.append(avg_loss.numpy()[0])
        test_epoch_mse1.append(mse1.numpy()[0])
        test_epoch_mse2.append(mse2.numpy()[0])
        test_accs1.append(acc1)
        test_accs2.append(acc2)

    print('Test:')
    print('MSE of ROUND(A.POWER,0):{}, MSE of YD15:{}'.format(np.average(test_epoch_mse1), np.average(test_epoch_mse2)))
    print('Mean MSE:', np.mean(test_loss))
    print('ACC of ROUND(A.POWER,0):{}, ACC of YD15:{}'.format(np.average(test_accs1), np.average(test_accs2)))

更多代码,详见:

  • 龙源风电赛 Baseline – 多任务 LSTM 深度网络模型 (Paddle)

https://aistudio.baidu.com/aistudio/projectdetail/5911966?con…

提分技巧

开掘风机间关联信息

尽管赛方没给出不同风机的地理位置,且不同风机的数据分布时间段不齐全不统一,但能够从风场维度,联结多个风机发展数据分析,看是否能挖掘出风机之间的关联信息,以缩小单风机的数据噪声、帮忙填补缺失或解决异样等。

还有例如求功率 / 温度相关性,Kmeans 聚类获取风机 cluster,把近邻风机的特色们求均值退出特色,或者分 cluster 建模预测。

<p align=right>ST-Tree module:ST-Tree:Spatio-Partitioned Time-Phased Tree Model。它算每个风机之间的皮尔逊系数,再用 K-means 聚类,而后针对聚类的风机训练 LightGBM。</p>
<p align=right>–KDD 第 1 名计划(海康)</p>

  • 在图网络表征上,选手是把与风机有 Top-K 高相关性的风机们,确认连贯边,构建图关系。
  • 用 k-shape 算法将风机聚类成 39 类,每类风机用一个 LightGBM 预测。
  • 从属风机可被看做是一类风机们,它们之间有最类似的发电法则。通过均匀从属风机的功率,当做特色退出模型,能缓解数据噪声。

<p align=right>–KDD 第 4 名计划(清华、浙江工商、多伦多大学)</p>

分段建模预测

风机的发电功率预测难度是挺大的,模型个别偏好均值预测,所以咱们要争取把可预测性强的短期预测局部预测好,再思考如何晋升模型的中长期预测能力。其实短期预测能够思考递归预测,因为它强调时序前后依赖,对短期信息依赖水平会更高些,有余在于中后期会误差累积。所以在中长期则能够思考多步生成式预测。

论文《DeepSpatio-TemporalWind Power Forecasting》反馈到:

  • 随着工夫滞后项的减少,自相关系数迅速衰减,阐明短期预测可能性更大,中长期就比拟艰难了。
  • 随机抽取几个不同的风速时序,结果显示风速时序没有展现出极其的长期依赖。而且 GRU 用更少的参数反而防止了过拟合问题。

下图展现了头几个小时下的 MAE,也反映出雷同的论断。所以风电功率预测时大多用户抉择了分段建模预测。

其余

  • 异样值解决,尤其是标签改正: 例如风速过大但功率为 0 的异样,在特定风速下的离群功率等;
  • 标签交融: 交融两个标签,以 YD15 为主,A.Power 为辅;
  • 利用天气预报的数据: 加 decoder 局部退出即可; 
  • 开掘更多特色: 差分序列、同时刻风场 / 邻近风机的特色均值 / 标准差等;
  • 尝试树模型: XGB、LGB 等;
  • 模型参数调优: optuna;
  • 模型交融: 人工加权或配合寻参算法。

官网交换 QQ 群

QQ 搜寻 479266219 退出官网交换 QQ 群~👇

退出移动版