transformer 深度学习 在时间序列中应用 时间序列cnn_卷积


时间序列数据通常出现在不同的领域,如经济、商业、工程和许多其他领域,并且可以有不同的应用。使用机器学习进行时间序列建模已经成为新的发展趋势。主要的任务目标包括异常检测和回归预测。递归神经网络(RNNs)和卷积神经网络(CNNs)在时间序列建模问题上取得了良好结果。

CNN应用于时间序列的代表即TCN,又被称为因果卷积。TCN与RNNs相比,具有如下的优势:

(1)并行性。与 RNN 中后继时间步长的预测必须等待之前时间步完成预测不同,卷积可以并行完成,因为每一层都使用相同的滤波器。因此,在训练和评估中,TCN 可以处理一整个较长的输入序列,而不是像 RNN 中那样顺序处理。

(2)灵活的感受野大小。TCN 有多种方式更改其感受野大小。其中目前表现最好的即WaveNet使用的扩大卷积(dilated convolution)。

(3)梯度稳定。TCN的反向传播与RNN相比明显不同,而且通过引入残差连接和跳跃连接,能够有效缓解和避免梯度爆炸和梯度消失现象。

了解WaveNet,必须了解如下的三个概念(1)扩大因果卷积(2)PixelCNN门激活(3)跳跃连接和残差连接

扩大因果卷积


transformer 深度学习 在时间序列中应用 时间序列cnn_时间序列_02

图1 因果卷积示意图


因果卷积确保了模型输出不会违反数据的顺序:模型在 t 时刻输出的预测不会依赖任何一个未来时刻的数据,但是直接采用上述的因果卷积,为了使得模型能够捕捉长期依赖,模型必须拥有足够大的感受野,只能通过增加模型的深度和卷积核的大小,然而这将带来参数数量的迅速增加。WaveNet使用堆叠式扩大卷积层,这种卷积只需要几层就能获得很大的感受野,同时保留了输入分辨率和计算效率。


transformer 深度学习 在时间序列中应用 时间序列cnn_数据_03

扩大因果卷积层示意图

上图描述了WaveNet中的扩大因果卷积,dilation的值为:



扩大卷积即每卷积核在找下一个卷积位置的时候会隔dilation-1个位置,这样设计能够使得感受野成指数级别的增长。


class CausalConv1d(nn.Module):
    """
    Input and output sizes will be the same.
    """
    def __init__(self, in_size, out_size, kernel_size, dilation=1):
        super(CausalConv1d, self).__init__()
        self.pad = (kernel_size - 1) * dilation
        self.conv1 = nn.Conv1d(in_size, out_size, kernel_size, padding=pad, dilation=dilation)

    def forward(self, x):
        x = self.conv1(x)
        x = x[..., :-self.pad]  
        return x


一维卷积输出的纬度为


,输出纬度为


。且


满足:



transformer 深度学习 在时间序列中应用 时间序列cnn_pytorch增加一维_04

扩大因果卷积padding示意图

为了实现因果卷积, 需要对输入进行padding。上图演示了kernel_size 为1时左侧的padding,但是pytorch的padding是在两边同时进行的(默认两边相同大小padding)。可以看到图中单侧的padding大小为(kernel_size - 1) * dilation为,但是这样pytorch的输出结果会比原来大(kernel_size - 1) * dilation,最后多出来的数据其实是涉及到了未来时刻,直接舍弃这些值即可。这样就能在保证因果关系的同时,使得输入和输出的大小一致。

残差链接、PixelCNN、跳跃链接


transformer 深度学习 在时间序列中应用 时间序列cnn_时间序列_05

RESIDUAL AND SKIP CONNECTIONS

在因果卷积后链接的是pixelCNN中的门控单元:



*代表卷积操作,


代表点乘操作。


,PixelCNN单元门之后的1*1卷积是有点迷惑的,根据我查阅github上的多个wavenet的实现,这里实际上是两个不同的1*1卷积,一个是为skip connection服务,一个为residual connection服务。

考虑到残差和跳跃连接层会多次出现,使用一个类来实现:


class ResidualLayer(nn.Module):    
    def __init__(self, residual_size, skip_size, dilation):
        super(ResidualLayer, self).__init__()
        self.conv_filter = CausalConv1d(residual_size, residual_size,
                                         kernel_size=2, dilation=dilation)
        self.conv_gate = CausalConv1d(residual_size, residual_size,
                                         kernel_size=2, dilation=dilation)        
        self.resconv1_1 = nn.Conv1d(residual_size, residual_size, kernel_size=1)
        self.skipconv1_1 = nn.Conv1d(residual_size, skip_size, kernel_size=1)
        
   
    def forward(self, x):
        conv_filter = self.conv_filter(x)
        conv_gate = self.conv_gate(x)  
        fx = F.tanh(conv_filter) * F.sigmoid(conv_gate)
        fx = self.resconv1_1(fx) 
        skip = self.skipconv1_1(fx) 
        residual = fx + x  
        #residual=[batch,residual_size,seq_len]  skip=[batch,skip_size,seq_len]
        return skip, residual


为了实现


这样的扩大卷积,只需要把它拆分为两个


即可,


这里采用DilatedStack来表示一个


堆叠层。


class DilatedStack(nn.Module):
    def __init__(self, residual_size, skip_size, dilation_depth):
        super(DilatedStack, self).__init__()
        residual_stack = [ResidualLayer(residual_size, skip_size, 2**layer)
                         for layer in range(dilation_depth)]
        self.residual_stack = nn.ModuleList(residual_stack)
        
    def forward(self, x):
        skips = []
        for layer in self.residual_stack:
            skip, x = layer(x)
            skips.append(skip.unsqueeze(0))
            #skip =[1,batch,skip_size,seq_len]
        return torch.cat(skips, dim=0), x  # [layers,batch,skip_size,seq_len]


WaveNet的组装

在pytorch中,输入时间序列数据纬度为


为了匹conv1d在最后一个纬度即序列长度方向进行卷积,首先需要交换输入的纬度为


,按照waveNet原文一开始就需要一个因果卷积。


依次经过两层


的卷积,每层的skip都会输出用于后面的计算,最后把skip加和作为结果输出,此后即可见进入decoder层,可以根据需要进行设计。


class WaveNet(nn.Module):

    def __init__(self,input_size,out_size, residual_size, skip_size, dilation_cycles, dilation_depth):

        super(WaveNet, self).__init__()

        self.input_conv = CausalConv1d(in_put_size,residual_szie, kernel_size=2)        

        self.dilated_stacks = nn.ModuleList(

            [DilatedStack(residual_size, skip_size, dilation_depth)

             for cycle in range(dilation_cycles)]

        )

        self.convout_1 = nn.Conv1d(skip_size, out_size, kernel_size=1)

        self.convout_2 = nn.Conv1d(skip_size, out_size, kernel_size=1)

    def forward(self, x):

        x = x.permute(0,2,1)# [batch,input_feature_dim, seq_len]

        x = self.input_conv(x) # [batch,residual_size, seq_len]             

        skip_connections = []

        for cycle in self.dilated_stacks:

            skips, x = cycle(x)             
            skip_connections.append(skips)

        ## skip_connection=[total_layers,batch,skip_size,seq_len]
        skip_connections = torch.cat(skip_connections, dim=0)        

        # gather all output skip connections to generate output, discard last residual output

        out = skip_connections.sum(dim=0) # [batch,skip_size,seq_len]

        out = F.relu(out)

        out = self.convout_1(out) # [batch,out_size,seq_len]
        out = F.relu(out)

        out=self.convout_2(out)

        out=out.permute(0,2,1)
        #[bacth,seq_len,out_size]
        return out