我的项目背景

以后,常见的大气污染预测模型大多是基于物理机理构建的,比方空气质量预测模型 Calpuff、AERMOD、CMAQ 等。然而,这些模型运算较为简单,对于输出数据的要求十分高,运算耗时也比拟长,适宜用于惯例固定区域的预报。当遇到突发净化事件时,就无奈无效发挥作用。

针对以上问题,本我的项目以某城区 3km*3km 范畴的固定模仿区域,依据污染物扩散模型,疾速计算任意开释点源和任意风向的污染物扩散动图,并进行精度评估。仅利用城市部分污染物扩散云图作为输出,应用深度学习模型提取图像中污染物扩散的特色,纯数据驱动,无需建设物理模型,预测耗时短,适宜作为突发净化扩散事件时的应急处理决策辅助。

我的项目需要

课题名称

基于数据驱动的污染物扩散深度学习模型案例

课题需要

内部单位提供数据集,总数据集详细描述:120 个动图数据(3 个风速5 个开释源点位 8 个风向)。选取其中任意 1 个动图的数据,基于数据驱动类模型(模型不限度)提取数据特色,失去污染物扩散模型,可对污染物扩散进行预测。

  • 我的项目地址

https://aistudio.baidu.com/aistudio/projectdetail/5663515

实现过程

数据集

咱们抉择了风速 15m/s,风向正北,Pos_0 作为污染源开释点的动图数据,数据来源于某城区 3km*3km 范畴的固定区域内污染物扩散 CFD 模仿后果(南京欧帕提亚公司提供),共 745 秒 148 张污染物浓度云图,两张图片工夫距离 5 秒。

基于飞桨 2.4.0 的开发环境,在对动图解压之后,咱们发现动图解压失去的 181 张动态图片中第 148 张之后的图片存在显著的图像抖动。咱们采纳了基于 Harris 角点检测的图像对齐算法进行解决,然而图像抖动没有失去齐全打消。为了保障模型输出数据的品质,咱们抛弃了第 148 张之后的动态图片。

图1  原始数据

U-Net 网络模型

网络模型如图 2 所示,其由 3 个 Encoder/Decoder、9 个卷积 Conv、9 个反卷积 Conv-T 组成,约 30 万个训练参数。之所以抉择 U-Net,是因为该网络在图像宰割和指标辨认中利用宽泛,污染物扩散模式学习能够看作是一种动静的指标辨认工作,只不过指标的状态比拟形象;另一个起因是 U-Net 的代码实现较简略,短时间内能够实现网络的搭建。

图2 U-Net网络图

外围代码

import paddleimport paddle.nn as nnimport paddle.nn.functional as Ffrom paddle.nn.utils import weight_norm# 创立根底卷积层def create_layer(in_channels, out_channels, kernel_size, wn=True, bn=True,                 activation=nn.ReLU, convolution=nn.Conv2D):    assert kernel_size % 2 == 1    layer = [ ]    conv = convolution(in_channels, out_channels, kernel_size, padding=kernel_size // 2)    if wn:        conv = weight_norm(conv)    layer.append(conv)    if activation is not None:        layer.append(activation())    if bn:        layer.append(nn.BatchNorm2D(out_channels))    return nn.Sequential(*layer)# 创立Encoder中的单个块def create_encoder_block(in_channels, out_channels, kernel_size, wn=True, bn=True,                         activation=nn.ReLU, layers=2):    encoder = [ ]    for i in range(layers):        _in = out_channels        _out = out_channels        if i == 0:            _in = in_channels        encoder.append(create_layer(_in, _out, kernel_size, wn, bn, activation, nn.Conv2D))    return nn.Sequential(*encoder)# 创立Decoder中的单个块def create_decoder_block(in_channels, out_channels, kernel_size, wn=True, bn=True,                         activation=nn.ReLU, layers=2, final_layer=False):    decoder = [ ]    for i in range(layers):        _in = in_channels        _out = in_channels        _bn = bn        _activation = activation        if i == 0:            _in = in_channels * 2        if i == layers - 1:            _out = out_channels            if final_layer:                _bn = False                _activation = None        decoder.append(create_layer(_in, _out, kernel_size, wn, _bn, _activation, nn.Conv2DTranspose))    return nn.Sequential(*decoder)# 创立Encoderdef create_encoder(in_channels, filters, kernel_size, wn=True, bn=True, activation=nn.ReLU, layers=2):    encoder = [ ]    for i in range(len(filters)):        if i == 0:            encoder_layer = create_encoder_block(in_channels, filters[i], kernel_size, wn, bn, activation, layers)        else:            encoder_layer = create_encoder_block(filters[i - 1], filters[i], kernel_size, wn, bn, activation, layers)        encoder = encoder + [encoder_layer]    return nn.Sequential(*encoder)# 创立Decoderdef create_decoder(out_channels, filters, kernel_size, wn=True, bn=True, activation=nn.ReLU, layers=2):    decoder = []    for i in range(len(filters)):        if i == 0:            decoder_layer = create_decoder_block(filters[i], out_channels, kernel_size, wn, bn, activation, layers,                                                 final_layer=True)        else:            decoder_layer = create_decoder_block(filters[i], filters[i - 1], kernel_size, wn, bn, activation, layers,                                                 final_layer=False)        decoder = [decoder_layer] + decoder    return nn.Sequential(*decoder)# 创立网络class UNetEx(nn.Layer):    def __init__(self, in_channels, out_channels, kernel_size=3, filters=[16, 32, 64], layers=3,                 weight_norm=True, batch_norm=True, activation=nn.ReLU, final_activation=None):        super().__init__()        assert len(filters) > 0        self.final_activation = final_activation        self.encoder = create_encoder(in_channels, filters, kernel_size, weight_norm, batch_norm, activation, layers)        decoders = [ ]        # for i in range(out_channels):        decoders.append(create_decoder(out_channels, filters, kernel_size, weight_norm, batch_norm, activation, layers))        self.decoders = nn.Sequential(*decoders)    def encode(self, x):        tensors = [ ]        indices = [ ]        sizes = [ ]        for encoder in self.encoder:            x = encoder(x)            sizes.append(x.shape)            tensors.append(x)            x, ind = F.max_pool2d(x, 2, 2, return_mask=True)            indices.append(ind)        return x, tensors, indices, sizes    def decode(self, _x, _tensors, _indices, _sizes):        y = [ ]        for _decoder in self.decoders:            x = _x            tensors = _tensors[:]            indices = _indices[:]            sizes = _sizes[:]            for decoder in _decoder:                tensor = tensors.pop()                size = sizes.pop()                ind = indices.pop()                # 反池化操作,为上采样                x = F.max_unpool2d(x, ind, 2, 2, output_size=size)                x = paddle.concat([tensor, x], axis=1)                x = decoder(x)            y.append(x)        return paddle.concat(y, axis=1)    def forward(self, x):        x, tensors, indices, sizes = self.encode(x)        x = self.decode(x, tensors, indices, sizes)        if self.final_activation is not None:            x = self.final_activation(x)        return x

训练

训练时输出数据为上一时刻的污染物云图,输入为预测的下一时刻的污染物云图。以后的训练 batch-size 为 1,即只预测下一时刻的污染物扩散状况。训练时,每 10 个 epoch 保留一次模型,避免训练意外中断时模型参数失落。

# 训练方法def train(model, train_dataset, criterion, optimizer, device, num_epochs):    loss_history = [ ]    epoch_loss = 0# 遍历批次    for epoch in range(num_epochs):        optimizer.clear_grad()        for batch_id in range(len(train_dataset)-1):            inputs = train_dataset[batch_id]            outputs_true = train_dataset[batch_id+1]            inputs = T.ToTensor()(inputs)            inputs = paddle.unsqueeze(inputs, 0)            outputs_true = T.ToTensor()(outputs_true)            outputs_true = paddle.unsqueeze(outputs_true, 0)# 训练            outputs = model(inputs)# 计算损失值            loss = criterion(outputs, outputs_true)            if batch_id % 10 ==0:                print('epoch:',epoch,'batch_id:',batch_id,'loss:',loss.numpy())            loss.backward()            epoch_loss += loss.item()        optimizer.step()        epoch_loss /= len(train_dataset)        loss_history.append(epoch_loss)        print("Epoch [{}/{}], Loss: {:.8f}".format(epoch + 1, num_epochs, loss.numpy()[0]))    # 保留模型        if epoch % 10 == 0:            save_model(model, '/home/aistudio/pollution_model.pdparams')    print("Training complete.")return loss_history

预测

预测时,输出测试数据某时刻的污染物扩散云图,预测下一时刻的污染物扩散状况。测试函数中 supervise 这个 flag 为后续间断预测多个时刻的数据预置了接口。目前 supervise 置为 true,当模型准备间断预测多个时刻数据时,测试时将 supervise 置为 false。

def test():    # 初始化后果列表    results = [ ]    # 测试汇合起始点    inputs = test_dataset[0]    inputs = T.ToTensor()(inputs)    inputs = paddle.unsqueeze(inputs, 0)    # 是否supervise    flag_supervise = True    device = paddle.set_device('gpu' if paddle.is_compiled_with_cuda() else 'cpu')    # 加载模型    model = UNetEx(3,3,3)    load_model(model,'/home/aistudio/pollution_model.pdparams',device)    for num in range(1,10):        # 进行预测        outputs = model(inputs)        outputs_np = outputs.numpy()        outputs_np = np.squeeze(outputs_np, axis=0)  # 去除第一个维度(batch_size)        outputs_np = np.transpose(outputs_np, (1, 2, 0))  # 将通道维度调整为最初一个维度        outputs_np = (255 * np.clip(outputs_np, 0, 1)).astype('uint8')        #outputs_np = outputs_np.transpose([1, 2, 0])        #outputs_np_uint8 = (outputs_np * 255).astype(np.uint8)        # 将预测后果增加到后果列表中        results.append(outputs_np)        if flag_supervise == False:            # 将预测后果作为下一帧的输出            inputs = outputs        else:            # 应用实在数据预测            inputs = test_dataset[num+1]            inputs = T.ToTensor()(inputs)            inputs = paddle.unsqueeze(inputs, 0)    return resultsresults = test()

我的项目成绩

图3 计算函数损失值

图4 比照 CFD 模仿参数比照

图5 残差值比照

如图 5 所示,浓度误差次要集中在污染源左近(如图红色框),次要数值散布在-0.02~0.02 之间。不同色彩别离代表不同浓度区间误差,蓝色示意的低浓度相对误差较小,绿色红色示意的中高浓度误差平均误差较高,绿色区域表征的中等浓度区域,偏大的误差影响的面积较大。

图6 数值比照

将来倒退方向

预测能力方面

  • 基于前一时刻的污染物浓度云图,预测后十个时刻、二十个时刻,四十个时刻的污染物浓度云图;
  • 尝试用多时刻预测多时刻。

网络方面

  • 尝试引入更先进的网络架构,如 transformer;
  • 对于网络层数和每层网络的神经元个数,尝试进行敏感性剖析和误差剖析;
  • 尝试引入更多品种的激活函数如 tanh,silu 等;
  • 尝试对 learning rate、batch size 等超参数进行调整试验。

物理原理方面

  • 尝试引入物理先验常识,对修建、边界地位施加 loss 软束缚;
  • 尝试利用流体 NS 方程对模型进行修改。

模型方面

  • 尝试引入更多参数作为输出:如污染源地位、污染源初始浓度等进步模型的适应能力;
  • 减少模型参数量级,摸索大模型对简单多态问题的解决能力;
  • 尝试和传统流体求解办法进行交融。

我的项目意义与心得

本我的项目尝试用 U-Net 网络通过污染物扩散云图来学习污染物扩散的模型参数,对污染物扩散进行疾速预测,是数据驱动计算场景拓展的一次摸索。从我的项目后果来看,模型计算速度相比 CFD 模仿晋升显著,然而模型预测的成果还有待晋升,将来将通过摸索以上几个方向,一直优化模型预测成果。我的项目实现过程中,咱们破费了大量的工夫解决背景存在抖动的图像,直到起初发现有一部分数据集的品质要远远好于另一部分,咱们抉择放弃品质不好的数据,从而放慢了我的项目的停顿。

数据处理过程中有以下几个方面的心得。

第一,对我的项目的数据应该第一工夫进行全局摸索,理解数据的全貌,对数据品质进行评估;

第二,与其破费大量的工夫解决品质不好的数据,不如先应用品质较好的数据,优先做对模型获得停顿更加要害的事件;

第三,绝对于扭转模型的构造,进步输出数据的品质对模型的训练后果起到更加踊跃的作用。一些开源模型的成果无奈复现的起因在于训练数据的不公开,即使大家都用到同样构造的网络,然而训练数据不同,模型获得的成果就大不相同。从这个角度看,模型参数是训练数据在网络上留下的压缩信息,训练数据存在的瑕疵很难通过优化网络来解决。

飞桨 AI for Science 共创打算为本我的项目提供了弱小的技术支持,打造沉闷的前瞻性的 AI for Science 开源社区,通过飞桨 AI for Science 共创打算,学习到了如何在飞桨平台上应用科学计算的 AI 办法去解决 CFD 模仿预测的问题,并且大幅度提高了数据驱动计算的速度。置信将来会有越来越多的我的项目通过 AI for Science 共创打算建设产学研闭环,推动科研翻新与产业赋能。

相干地址

  • 飞桨 AI for Science 共创打算

https://www.paddlepaddle.org.cn/science


  • 飞桨 PPSIG-Science 小组

https://www.paddlepaddle.org.cn/specialgroupdetail?id=9

  • 飞桨 PaddleScience 工具组件

https://github.com/PaddlePaddle/PaddleScience