二、基于TrajGRU的短临预报实现:代码实现

前言

接上篇一、基于TrajGRU的短临预报实现:模型结构,本篇文章主要是关于代码实现以及训练时的一些调优技巧和经验型的错误。

本文分为以下几个部分:

一、雷达数据预处理

二、模型代码pytorch实现

三、训练参数设置

四、模型测试验证

五、部署推理模型

本文依次根据上述章节进行展开。

chapter1 雷达数据预处理

雷达基数据以二进制存储,需要按位读取并解码成十进制数据。目前使用的数据是S波段多普勒雷达数据,雷达扫描方式是VCP11或者VCP21,二者的区别是仰角不同。美国雷达的标准是VCP-11有14个仰角,仰角范围是0.5~19.5度,VCP-21有9个仰角,仰角范围是0.5~19.5度。我国S波段多普勒雷达使用CINRAD 标准,

11:降水模式,16层仰角
21:降水模式,14层仰角
31:晴空模式,8层仰角
32:晴空模式,7层仰角

class Radar(object):
    ## path + file_name
    ## 
    def __init__(self,*args):
        path,name = args
        self.AbsolutePath = os.path.join(path,name)
        file = open(self.AbsolutePath,'rb')
        file.seek(0)
        self.RawData = np.array([int(i) for i in file.read()])
        self.Name = name[:-4]
        self.Count = int(len(self.RawData)/2432) ## 总个数
        self.RawArray = self.RawData.reshape(self.Count,2432)
        _,_,_,self.Station,self.time = self.Name.split('_')[:5]
        
        
        self.time_ms = read_data(self.AbsolutePath)
        logger.info(f'ms {self.time_ms}')
        #self.Julian = [self.RawArray[i][32] + self.RawArray[i][33]*256 for i in range(0,self.Count)]#儒略日
        self.NumberofElevation = [self.RawArray[i][44] + self.RawArray[i][45]*256 for i in range(0,self.Count)]#层数
        self.StartOfReflectivity = [self.RawArray[i][46]+self.RawArray[i][47]*256 for i in range(0,self.Count)]#起始距离
        self.StartOfSpeed = [self.RawArray[i][48] + self.RawArray[i][49]*256 for i in range(0,self.Count)]
        self.StepOfReflectivity = [self.RawArray[i][50] + self.RawArray[i][51]*256 for i in range(0,self.Count)]#库长
        self.StepOfSpeed = [self.RawArray[i][52]+self.RawArray[i][53]*256 for i in range(0,self.Count)]
        self.NumberOfReflectivity = [self.RawArray[i][54] + self.RawArray[i][55]*256 for i in range(0,self.Count)]#反射率的距离库数
        self.NumberOfSpeed = [self.RawArray[i][56] + self.RawArray[i][57]*256 for i in range(0,self.Count)]
        self.PointerOfReflectivity = [self.RawArray[i][64]+self.RawArray[i][65]*256 for i in range(0,self.Count)]#数据位置指针
        self.PointerOfSpeed = [self.RawArray[i][66]+self.RawArray[i][67]*256 for i in range(0,self.Count)]
        self.PointerOfSpectralWidth = [self.RawArray[i][68]+self.RawArray[i][69]*256 for i in range(0,self.Count)] # 谱宽指针
        self.ResolutionOfSpeed = [self.RawArray[i][70]+self.RawArray[i][71]for i in range(0,self.Count)]#多普勒速度分辨率
        self.vcp = [self.RawArray[i][72]+self.RawArray[i][73]*256 for i in range(0,self.Count)]# 11:降水,16;21:降水,14;31:晴空,8;32:晴空,7.
        self.Elevation = [(self.RawArray[i][42]+256*self.RawArray[i][43])/8*180/4096 for i in range(0,self.Count)]#仰角
        self.Azimuth = [(self.RawArray[i][36]+256*self.RawArray[i][37])/8*180/4096 for i in range(0,self.Count)]#方位角
        self.Storage = self.getStorage()
        #self.AllInfo = self.getAllInfo()
        #self.x,self.y,self.z,self.r = self.getXyzr()
        #self.space_info = self.getAllInfo()
        #self.elevation_list = self.get_elevation_list()
        #self.grid_data = self.grid()
        file.close()

多普勒雷达每个仰角的扫描方式是PPI,每个仰角扫描得到的数据长度都是固定的,存储长度是2432个字节,数据格式如下:

字节顺序        双字节顺序        数据类型        说明
1-14        1-7                保留        雷达信息头
(28字节)
15-16        8        2字节        1-表示雷达数据        
17-28        9-14                保留        
29-32        15-16        4字节        径向数据收集时间(毫秒,自00:00开始)
33-34        17        2字节        儒略日(Julian)表示,自1970年1月1日开始
35-36        18        2字节        不模糊距离(表示:数值/10.=千米)
37-38        19        2字节        方位角(编码方式:[数值/8.]*[180./4096.]=度)
39-40        20        2字节        当前仰角内径向数据序号
41-42        21        2字节        径向数据状态 0:该仰角的第一条径向数据
1:该仰角中间的径向数据
2:该仰角的最后一条径向数据
3:体扫开始的第一条径向数据
4:体扫结束的最后一条径向数据
43-44        22        2字节        仰角 (编码方式:[数值/8.]*[180./4096.]=度)
45-46        23        2字节        体扫内的仰角数
47-48        24        2字节        反射率数据的第一个距离库的实际距离(单位:米)
49-50        25        2字节        多普勒数据的第一个距离库的实际距离(单位:米)
51-52        26        2字节        反射率数据的距离库长(单位:米)
53-54        27        2字节        多普勒数据的距离库长(单位:米)
55-56        28        2字节        反射率的距离库数
57-58        29        2字节        多普勒的距离库数
59-60        30        2字节        扇区号
61-64        31-32        4字节        系统订正常数
65-66        33        2字节        反射率数据指针(偏离雷达数据信息头的字节数)
表示第一个反射率数据的位置
67-68        34        2字节        速度数据指针(偏离雷达数据信息头的字节数)
表示第一个速度数据的位置
69-70        35        2字节        谱宽数据指针(偏离雷达数据信息头的字节数)
表示第一个谱宽数据的位置
71-72        36        2字节        多普勒速度分辨率。 2:表示0.5米/秒
                   4:表示1.0米/秒
73-74        37        2字节        体扫(VCP)模式   11:降水模式,16层仰角
                   21:降水模式,14层仰角
                   31:晴空模式,8层仰角
                   32:晴空模式,7层仰角
75-82        38-41                保留
83-84        42        2字节        用于回放的反射率数据指针,同33
85-86        43        2字节        用于回放的速度数据指针,同34
87-88        44        2字节        用于回放的谱宽数据指针,同35
89-90        45        2字节        Nyquist速度(表示:数值/100. = 米/秒)
91-128        46-64                保留
129-588        65-294        1字节        反射率
距离库数:0-460
编码方式:(数值-2)/2.-32 = DBZ
当数值为0时,表示无回波数据(低于信噪比阀值)
当数值为1时,表示距离模糊        基数据
部分
(2300字节)
129-1508        65-754        1字节        速度
距离库数:0-920
编码方式:
分辨率为0.5米/秒时
(数值-2)/2.-63.5 = 米/秒
分辨率为1.0米/秒时
(数值-2)-127 = 米/秒
当数值为0或1时,意义同上        
129-2428        65-1214        1字节        谱宽
距离库数:0-920
编码方式:
(数值-2)/2.-63.5 = 米/秒
当数值为0或1时,意义同上        
2429-2432        1215-1216                保留


说明:
1.        数据的存储方式
每个体扫存储为一个单独的文件
2.        数据的排列方式
按照径向数据的方式顺序排列,对于CINRAD SA/SB雷达,体扫数据排列自低仰角开始到高仰角结束。
3.        径向数据的长度
径向数据的长度固定,为2432字节。
4.        距离库长和库数
反射率距离库长为1000米,最大距离库数为460;
速度和谱宽距离库长为250米,最大距离库数为920。

经过上述方法解析,可以得到雷达在各个仰角的基本反射率数据。解析后的雷达反射率数据经过变换可以得到0-255范围内的可见光图像。

常用的转化公式

pixel = 255 * \frac{dbz+10}{70}+0.5

雷达组合反射率拼图

 

得到连续的雷达反射率拼图后,还需要对数据做进一步的处理。在晴天云量较少时,雷达反射率较小甚至没有,这一部分数据对于模型来说等同于噪声,所以需要剔除。

 

 

雷达组合反射率噪声

 

另外,在剔除数据值一定要注意保持数据时空连续性,比如十分钟一次的雷达反射率组合拼图,剔除噪声数据之后需要保证剩下的图像仍然是以十分钟为间隔的。

chapter2 模型代码pytorch实现

模型采用的是Encoder-Decoder架构,论文中叫Encoder-Forecaster,是相同的意思。前文已经提到过这个架构,这里主要是代码实现。

在论文中,Encoder阶段是通过卷积+trajGRU实现图像降采样和光流以及时序特征提取,Forecaster阶段相当于Encoder的镜像,即通过trajGRU+卷机实现光流以及时序特征提取和图像上采样,最终输出预测结果。

Encoder

Encoder由三层Conv+trajGRU堆叠而成。

conv -> rnn -> conv -> rnn -> conv -> rnn ->states

在卷积阶段通过设置卷积核尺寸步长和padding大小进行特征提取和图像降采样。

三层卷机的设置分别如下:

stage1

1,8,7,5,1 -> inputchannels,outputchannels,kernel-size,stride,padding

(N + 2*1 - 7)/5 + 1 = N' 

N' = N/5


stage2 

64,192,5,3,1

(N + 2*1 - 5)/3 + 1 = N' 

N' = N/3

stage3

192,192,3,2,1
(N + 2*1 - 3)/2 + 1 = N' 

N' = N/2

可以看到,经过第一次卷积之后,输出尺寸是输入的1/5,经过第二次卷积之后,输出尺寸是输入的1/3,经过第三次卷积之后,输出尺寸是输入的1/2。

基于torch的实现如下:

stage1

conv2d = nn.Conv2d(1,8,7,5,1)
relu = nn.LeakyReLU(negative_slope=0.2, inplace=True)

stage2

conv2d = nn.Conv2d(64,192,5,3,1)
relu = nn.LeakyReLU(negative_slope=0.2, inplace=True)

stage3 

conv2d = nn.Conv2d(192,192,3,2,1)
relu = nn.LeakyReLU(negative_slope=0.2, inplace=True)

 

rnn结构(TrajGRU)

这一部分可以说是整个模型架构的核心部分,也是能够成功实现外推的关键。

上篇已经分析过TrajGRU的网络结构,本文在这里结合代码实现进一步说明。

TrajGRU和GRU的区别主要有两点:一、前者使用卷积结构实现状态转移,后者使用全连接结构;二、前者比后者多了一个光流运动捕捉结构,即traj结构部分。

先来说一下traj结构,traj结构通过引入一层卷积结构,同时限制输出的维度,再经过不断的迭代调整卷积参数达到捕捉复杂光流运动的效果。需要注意的是,作者通过限制输出的维度是2维其实是模拟了经典光流算法的输出设置,所以traj结构的输出其实是前一帧加上光流矢量之后的效果,也即是某点在下一时刻图像中的位置。由于位置通常是离散不可微分,所以利用了一个插值方法取所在位置索引对应的特征值作为反馈学习的对象。

结构组成

1、input2hidden

2、inputs2flow

3、hidden2flow

4、flows_conv -->> wrap function -->> pixel value

5、ret(reset gate)

 trajGRU网络结构由上述5个部分组成,实现从输入到光流运动学习以及状态转移等。最终利用3个trajGRU的堆叠实现雷达回波外推。

需要注意的是,trajGRU结构不会改变输入输出的尺寸,即特征的宽和高不会被改变,只在卷积层改变特征的尺寸。一种合理的解释是类似LSTM和GRU这种含有记忆单元的结构,只是产生了状态转移,没有发生尺寸变换。

trajGRU代码实现

class TrajGRU(BaseConvRNN):
    # b_h_w: input feature map size
    def __init__(self, input_channel, num_filter,b_h_w,zoneout=0.2,L=5, 
                i2h_kernel=(3,3), i2h_stride=(1,1), i2h_pad=(1,1),
                h2h_kernel=(5,5), h2h_dilate=(1,1), prefix='BaseConvRNN'):
        super(TrajGRU,self).__init__(num_filter=num_filter,
                                b_h_w=b_h_w, 
                                h2h_kernel=h2h_kernel, 
                                h2h_dilate=h2h_dilate, 
                                i2h_kernel=i2h_kernel, 
                                i2h_stride=i2h_stride, 
                                i2h_pad=i2h_pad, 
                                prefix=prefix)
        self._L = L
        self._zoneout = zoneout
        #self._act_type = F.leaky_relu()
        # *3 according to 3 hidden states. t-1,t,t+1

        ## self.i2h -->> input2hidden
        self.i2h = nn.Conv2d(in_channels=input_channel,
                            out_channels=self._num_filter*3,
                            kernel_size=self._i2h_kernel,
                            stride=self._i2h_stride,
                            padding=self._i2h_pad,
                            dilation=self._i2h_dilate)
        
        # inputs to flow

        self.i2f_conv1 = nn.Conv2d(in_channels=input_channel,
                                out_channels=32,
                                kernel_size=(5,5),
                                stride=1,
                                padding=(2,2),
                                dilation=(1,1))

        # hidden to flow

        self.h2f_conv1 = nn.Conv2d(in_channels=self._num_filter,
                                out_channels=32,
                                kernel_size=(5,5),
                                stride=1,
                                padding=(2,2),
                                dilation=(1,1))

        # generate flow
        self.flows_conv = nn.Conv2d(in_channels=32,
                                out_channels=self._L*2,
                                kernel_size=(5,5),
                                stride=1,
                                padding=(2,2))
        # reset gate  hh,hz,hr,1*1 ks
        self.ret = nn.Conv2d(in_channels=self._num_filter*self._L,
                            out_channels=self._num_filter*3,
                            kernel_size=(1,1),
                            stride=1)
    # inputs: B C H W
    # flow comes from current inputs and forward states
    def _flow_generator(self,inputs,states):
        if inputs is not None:
            i2f_conv1  = self.i2f_conv1(inputs)

        else:
            i2f_conv1 = None 
        h2f_conv1  = self.h2f_conv1(states)
        f_conv1 = i2f_conv1 + h2f_conv1 if i2f_conv1 is not None else h2f_conv1
        f_conv1 = F.leaky_relu(f_conv1,0.2,inplace=True)

        flows = self.flows_conv(f_conv1)
        # channels L*2 ,split according to 2 
        # get L flow maps each have 2 channels
        flows = torch.split(flows,2,dim=1) 
        return flows
    
    # inputs states
    # inputs: S B C H W 
    def forward(self,inputs=None,states=None,seq_len=5):
        if states is None:
            states = torch.zeros((inputs.size(1),self._num_filter,self._state_height,
                                self._state_width),dtype= torch.float).cuda()
        if inputs is not None:
            S,B,C,H,W = inputs.size()
            i2h = self.i2h(torch.reshape(inputs,(-1,C,H,W)))
            i2h = torch.reshape(i2h,(S,B,i2h.size(1),i2h.size(2),i2h.size(3)))
            i2h_slice = torch.split(i2h,self._num_filter,dim=2)
        else:
            i2h_slice = None

        prev_h = states
        outputs = []
        for i in range(seq_len):
            if inputs is not None:
                flows = self._flow_generator(inputs[i,...],prev_h)
            else:
                flows = self._flow_generator(None,prev_h)

            wrapped_data = []

            for j in range(len(flows)):
                flow = flows[j]
                wrapped_data.append(wrap(prev_h,-flow))
            wrapped_data = torch.cat(wrapped_data,dim=1)
            h2h = self.ret(wrapped_data)
            h2h_slice = torch.split(h2h,self._num_filter,dim=1)
            if i2h_slice is not None:
                reset_gate = torch.sigmoid(i2h_slice[0][i,...]+h2h_slice[0])
                update_gate = torch.sigmoid(i2h_slice[1][i,...]+h2h_slice[1])
                new_mem = F.leaky_relu(i2h_slice[2][i, ...] + reset_gate * h2h_slice[2],0.2,inplace=True)
            
            else:
                reset_gate = torch.sigmoid(h2h_slice[0])
                update_gate = torch.sigmoid(h2h_slice[1])
                new_mem = F.leaky_relu(reset_gate * h2h_slice[2],0.2,inplace=True)
            
            next_h = update_gate * prev_h + (1-update_gate) * new_mem

            if self._zoneout > 0.0:
                mask = F.dropout2d(torch.zeros_like(prev_h),p=self._zoneout)
                next_h = torch.where(mask,next_h,prev_h)
            outputs.append(next_h)
            prev_h = next_h
        return torch.stack(outputs),next_h

整体的实现还是严格按照网络结构进行的,继承了一个BaseConvRNN类,用来计算网络特征转移中的特征尺寸以及padding设置等。

下一篇按照trajGRU的5个结构拆解开逐个分析。

 

 

 

 

 

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
TrajGRU是一种用于序列建模的神经网络,它包含一个门控循环单元(GRU)和一个轨迹编码器。以下是用PyTorch实现TrajGRU的示例代码: ```python import torch import torch.nn as nn class TrajGRU(nn.Module): def __init__(self, input_size, hidden_size, num_layers, output_size): super(TrajGRU, self).__init__() self.input_size = input_size self.hidden_size = hidden_size self.num_layers = num_layers self.output_size = output_size self.gru = nn.GRU(input_size, hidden_size, num_layers, batch_first=True) self.linear = nn.Linear(hidden_size, output_size) def forward(self, x): # x: (batch_size, seq_len, input_size) h_0 = self._init_hidden(x.size(0)) # output: (batch_size, seq_len, hidden_size) output, h_n = self.gru(x, h_0) # output: (batch_size, hidden_size) output = output[:, -1, :] # output: (batch_size, output_size) output = self.linear(output) return output def _init_hidden(self, batch_size): hidden = torch.zeros(self.num_layers, batch_size, self.hidden_size) return hidden ``` 在上面的代码中,我们首先定义了TrajGRU类,并初始化了GRU和线性层。在前向传递中,我们首先使用GRU对输入序列进行编码,然后使用线性层将最后一个时间步的隐藏状态转换为输出。我们还定义了一个_init_hidden方法来初始化隐藏状态。 使用TrajGRU进行序列建模的示例代码如下: ```python # 构造一个TrajGRU模型 model = TrajGRU(input_size=10, hidden_size=32, num_layers=2, output_size=2) # 定义损失函数和优化器 criterion = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 训练模型 for epoch in range(num_epochs): for i, (inputs, labels) in enumerate(train_loader): # 前向传播 outputs = model(inputs) loss = criterion(outputs, labels) # 反向传播和优化 optimizer.zero_grad() loss.backward() optimizer.step() if (i+1) % 100 == 0: print('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'.format(epoch+1, num_epochs, i+1, total_step, loss.item())) ``` 在这个示例中,我们首先构建了一个TrajGRU模型,并定义了损失函数和优化器。然后我们使用训练集的数据迭代训练模型,并在每个epoch的末尾打印出当前的损失。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nobrody

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值