乐趣区

关于人工智能:Kaggle冠军解读风电场短期风况预测任务方案

赛题背景

近年来,随着陆上风电机组装机厂址的扩大,在天气渐变较多的地区装置的风力发电机组受到气象变动的影响愈发显著。在风况渐变时,因为控制系统的滞后性,容易导致机组呈现载荷过大,甚至是倒机的状况,造成重大经济损失。同时,现有超短期风功率预测的准确性较差,导致风功率预测系统对电网调度的参考价值不大,并且会导致业主产生大量的发电量打算考核。因为常见的激光雷达等风速测量产品单价昂扬、受天气影响较大,难以实现批量化的利用部署,且在大工夫空间尺度下仍难以具备牢靠的前瞻性。因而,牢靠的超短期风况预测火烧眉毛。

超短期风况预测是一个世界性难题,如果能通过大数据、人工智能技术预测出每台机组在将来短时间内的风速和风向数据,能够晋升风电机组的管制前瞻性、进步风电机组的载荷安全性;同时,现有超短期风功率预测能力的晋升,将带来显著的平安价值和经济效益。

本次较量由深圳保安区人民政府与中国信息通信研究院联结主办,提供了来自工业生产中的实在数据与场景,心愿联合工业与 AI 大数据,解决理论生产工作中面临的挑战。

数据分析

训练集阐明

两个风场各两年的训练数据:

  1. 每个风场 25 台风电机组,提供各台机组的机舱风速、风向、温度、功率和对应的小时级气象数据;2. 风场 1 的风机编号为:x26-x50,训练集数据范畴为 2018、2019 年;3. 风场 2 的风机编号为:x25-x49,训练集数据范畴为 2017、2018 年;4. 各机组的数据文件按 / 训练集 /[风场]/[机组]/[日期].csv 的形式存储;
  2. 气象数据存储在 / 训练集 /[风场] 文件夹下。

测试集阐明

  1. 测试集分为两个文件夹:测试集初赛、测试集决赛,初赛和决赛的文件夹组织模式统一;
  2. 初赛和决赛文件夹各包含 80 个时段的数据,每个时段 1 小时数据(30S 分辨率,工夫以秒数表白),春夏秋冬各 20 个时段,初赛编号 1 -20,决赛编号 21-40;即初赛的时段编号为春_01- 冬_20 共 80 个;决赛的时段编号为春_21- 冬_40 共 80 个;
  3. 各机组的数据文件依照 / 测试集_**/[风场]/[机组]/[时段].csv 的形式存储;
  4. 气象数据存储在 / 测试集_**/[风场]/ 文件夹下,共 80 个时段风场所在地的风速风向数据,每个时段提供过来 12 小时和将来 1 小时的风速和风向数据。时段编码同上,工夫编码为 -11~2,其中 0~1 这个小时正好对应的是机舱的 1 小时数据。

上图为测试汇合数据划分形式,-11~1 时间段之内积攒的数据作为模型的输出,用于预测将来 10 分钟内(1~2 之间)的风速和风向。

缺失值

数据中的缺失值次要来自两个方面,一种是 当天的数据记录存在缺失 ,另一种是 某些时间段的数据存在缺失 。多种缺失状况导致在填充数据时存在脱漏问题,仅应用一种形式填充缺失值会导致缺失值的填充呈现缺漏,因而较量中咱们同时应用了forward fillbackward fill均值填充 以保障填充覆盖率。这样解决可能会引入乐音,然而神经网络对于乐音有肯定的容忍度,因此最终的训练成果影响并不大。思考到训练数据、将来的测试数据中都可能存在缺失数据,而且它们的记录形式是雷同的,因而咱们没有去掉存在缺失值的数据,同时对它们应用了雷同的填充形式,防止因为预处理不同导致数据分布不统一问题的呈现。

模型思路介绍

模型构造

较量中,咱们采纳了 Encoder-Decoder 模式的模型,通过序列模型开掘输出序列中的信息,再通过 Decoder 进行预测。这里的 Encoder、Decoder 有多种抉择,例如常见的序列模型 LSTM,或者近几年衰亡的 Transformer。较量中咱们在 Decoder 侧重叠了多层 LSTM,Encoder 侧只应用了一层 LSTM。模型中没有退出 dropout 进行正则化,这是思考到数据中自身就存在大量的高频乐音,再退出 dropout 会导致模型收敛迟缓,影响模型的训练效率。咱们应用飞桨框架搭建模型构造,起初发现飞桨官网的自然语言解决模型库 PaddleNLP(https://github.com/PaddlePaddle/PaddleNLP)提供了不便的数据处理 API、丰盛的网络结构和预训练模型以及分类、生成等各种 NLP 利用示例,很适宜打较量,后续会思考用起来。

应用飞桨框架构建的最终模型构造的代码如下:

class network(nn.Layer):
    def __init__(self, name_scope='baseline'):
        super(network, self).__init__(name_scope)
        name_scope = self.full_name()

        self.lstm1 = paddle.nn.LSTM(128, 128, direction =  'bidirectional', dropout=0.0)
        self.lstm2 = paddle.nn.LSTM(25, 128, direction =  'bidirectional', dropout=0.0)

        self.embedding_layer1= paddle.nn.Embedding(100, 4)
        self.embedding_layer2 = paddle.nn.Embedding(100, 16)

        self.mlp1 = paddle.nn.Linear(29, 128)
        self.mlp_bn1 = paddle.nn.BatchNorm(120)

        self.bn2 = paddle.nn.BatchNorm(14)

        self.mlp2 = paddle.nn.Linear(1536, 256)
        self.mlp_bn2 = paddle.nn.BatchNorm(256)

        self.lstm_out1 = paddle.nn.LSTM(256, 256, direction =  'bidirectional', dropout=0.0)
        self.lstm_out2 = paddle.nn.LSTM(512, 128, direction =  'bidirectional', dropout=0.0)
        self.lstm_out3 = paddle.nn.LSTM(256, 64, direction =  'bidirectional', dropout=0.0)
        self.lstm_out4 = paddle.nn.LSTM(128, 64, direction =  'bidirectional', dropout=0.0)


        self.output = paddle.nn.Linear(128, 2,)

        self.sigmoid = paddle.nn.Sigmoid()

    # 网络的前向计算函数
    def forward(self, input1, input2):

        embedded1 = self.embedding_layer1(paddle.cast(input1[:,:,0], dtype='int64'))
        embedded2 = self.embedding_layer2(paddle.cast(input1[:,:,1]+input1[:,:,0] # * 30
                                            , dtype='int64'))

        x1 = paddle.concat([
                                            embedded1, 
                                            embedded2, 
                                            input1[:,:,2:], 
                                            input1[:,:,-2:-1] * paddle.sin(np.pi * 2 *input1[:,:,-1:]), 
                                            input1[:,:,-2:-1] * paddle.cos(np.pi * 2 *input1[:,:,-1:]), 
                                            paddle.sin(np.pi * 2 *input1[:,:,-1:]),
                                            paddle.cos(np.pi * 2 *input1[:,:,-1:]),
                                        ], axis=-1)     # 4+16+5+2+2 = 29

        x1 = self.mlp1(x1)
        x1 = self.mlp_bn1(x1)
        x1 = paddle.nn.ReLU()(x1)

        x2 = paddle.concat([embedded1[:,:14], 
                                            embedded2[:,:14], 
                                            input2[:,:,:-1], 
                                            input2[:,:,-2:-1] * paddle.sin(np.pi * 2 * input2[:,:,-1:]/360.), 
                                            input2[:,:,-2:-1] * paddle.cos(np.pi * 2 * input2[:,:,-1:]/360.), 
                                            paddle.sin(np.pi * 2 * input2[:,:,-1:]/360.),
                                            paddle.cos(np.pi * 2 * input2[:,:,-1:]/360.),
                                        ], axis=-1) # 4+16+1+2+2 = 25
        x2 = self.bn2(x2)

        x1_lstm_out, (hidden, _) = self.lstm1(x1) 
        x1 = paddle.concat([hidden[-2, :, :], hidden[-1, :, :],
                                            paddle.max(x1_lstm_out, axis=1),
                                            paddle.mean(x1_lstm_out, axis=1)
                                        ], axis=-1)

        x2_lstm_out, (hidden, _) = self.lstm2(x2) 
        x2 = paddle.concat([hidden[-2, :, :], hidden[-1, :, :],
                                            paddle.max(x2_lstm_out, axis=1),
                                            paddle.mean(x2_lstm_out, axis=1)
                                        ], axis=-1)

        x = paddle.concat([x1, x2], axis=-1)
        x = self.mlp2(x)
        x = self.mlp_bn2(x)
        x = paddle.nn.ReLU()(x)

        # decoder

        x = paddle.stack([x]*20, axis=1)

        x = self.lstm_out1(x)[0]
        x = self.lstm_out2(x)[0]
        x = self.lstm_out3(x)[0]
        x = self.lstm_out4(x)[0]

        x = self.output(x)
        output = self.sigmoid(x)*2-1
        output = paddle.cast(output, dtype='float32')

        return output

飞桨框架在训练模型时有多种形式,能够像其余深度学习框架一样,通过梯度回传进行训练,也能够利用高度封装后的 API 进行训练。在应用高层 API 训练时,咱们须要筹备好数据的 generator 和模型构造。飞桨框架中 generator 的封装形式如下,应用效率很高:

class TrainDataset(Dataset):
    def __init__(self, x_train_array, x_train_array2, y_train_array=None, mode='train'):
        # 样本数量
        self.training_data = x_train_array.astype('float32')
        self.training_data2 = x_train_array2.astype('float32')
        self.mode = mode
        if self.mode=='train':
            self.training_label = y_train_array.astype('float32')

        self.num_samples = self.training_data.shape[0] 

    def __getitem__(self, idx):
        data = self.training_data[idx]
        data2 = self.training_data2[idx]
        if self.mode=='train':
            label = self.training_label[idx]

            return [data, data2],  label
        else:
            return [data, data2]

    def __len__(self):
        # 返回样本总数量
        return self.num_samples

筹备好 generator 后,便能够间接应用 fit 接口进行训练:

model = paddle.Model(network(), inputs=inputs)
model.prepare(optimizer=paddle.optimizer.Adam(learning_rate=0.002, 
                parameters=model.parameters()),
                loss=paddle.nn.L1Loss(),)
model.fit(
        train_data=train_loader, 
        eval_data=valid_loader,
        epochs=10, 
        verbose=1,
    )

优化 pipeline

对于不同风机的数据,咱们提取特色的形式是雷同的,因而咱们能够利用 python 的 Parallel 库进一步优化代码的性能,晋升迭代的效率。外围代码如下:

# 生成训练数据
def generate_train_data(station, id):
  df = read_data(station, id, 'train').values
  return extract_train_data(df)

# 通过并行运算生成训练汇合
train_data = []
for station in [1, 2]:
    train_data_tmp = Parallel(n_jobs = -1, verbose = 1)(delayed(lambda x: generate_train_data(station, x))(id) for id in tqdm(range(25)))
    train_data = train_data + train_data_tmp

这里晋升的效率与 CPU 的外围个数成正比,较量中咱们应用了 8 核 CPU,因而能够在数据生成上晋升 8 倍的效率。

拟合风向的问题

本次较量的预测标签蕴含 风速 风向,其中对于风向,因为角度是循环的,咱们有

评估函数为 MAE。在训练阶段,间接预测风向会存在问题,因为 0 与 1 代表着雷同的意义,模型在遇到风向为 0 / 1 的状况时预测为它们的均值 0.5,导致误差。这里咱们通过将 风向 角度 转化为 风向在垂直方向上的重量,来防止间接预测风向,同时能够防止拟合风向带来的问题。

解决乐音

在获得 A 榜第一名的问题后,咱们尝试对数据中存在的乐音进行解决。因为对输出侧进行解决的危险比拟大,容易抹除输出特色中的无效信号,于是咱们抉择对标签进行平滑解决。咱们将模型预测后的值与原标签做加权均匀,接着应用平滑后的新标签进行训练,实现了在 A 榜上 0.1 分的晋升。

试验后果

较量的分数由如下公式计算得出:

其中,

为均匀绝对误差。试验后果如下表所示。不难发现,较量问题的晋升次要来自于对数据与标签的解决,这也是咱们在建模时最应该器重的两个因素。

赛后感想

这一次工业大数据较量中,咱们在 风况预测赛道 重型配件需求预测赛道 中均获得了二等奖的好问题。通过这一次较量,咱们发现工业场景下的数据品质可能并不现实,对缺失值、乐音都须要进行仔细解决。在解决工夫序列预测工作时,历史数据的积攒中可能并不包含将来遇到的突发状况,仅仅依赖模型可能会存在较大的偏差,这也是咱们在建模时须要分外关注的问题。

studio 我的项目链接:https://aistudio.baidu.com/aistudio/projectdetail/3260925Paddle 地址: https://github.com/PaddlePaddle/PaddlePaddleNLP 地址:https://github.com/PaddlePaddle/PaddleNLP

参考文献

[1] 工业大数据产业翻新平台 https://www.industrial-bigdata.com/

退出移动版