TCN时间卷积网络:让时间也能“卷”起来

一、引言

时序数据是一种常见的数据类型,它包含了时间上连续的一系列观测值。近年来,深度学习在时序数据处理方面取得了巨大进展,时序数据建模和预测也被多个领域广泛应用,如信号处理、语音识别等。然而,传统的序列建模方法(如RNN循环神经网络、LSTM长短期记忆网络等)需要串行地对每个时刻进行计算,并且具有较大的参数量,这限制了它们在并行计算和训练效率方面的表现。

时间卷积网络( TCN ,Temporal Convolutional Network) 是一种基于卷积神经网络结构的方法,由于其可以并行地计算所有时间步中的数据,并且具有强大的建模长期依赖性能力和更少的参数量,已被广泛应用于语音识别、动作检测、时间序列分类等领域。

二、TCN的基本结构和原理

TCN 有两个原则:

  • 原则一: TCN 网络的输入和输出形状必须相同,以确保模型对时序数据的处理不会丢失信息。
  • 原则二: TCN 网络中的每一时刻的输出仅由该时刻及其之前的输入卷积得到,以确保其在处理序列时具有因果约束。

2.1 一维卷积(1D-CNN)和零填充(Padding)

TCN的全称是时间卷积网络,很明显,是一种对时序数据进行卷积操作的网络,那么时序数据是怎么样的,又是基于怎样的卷积操作呢?

2.1.1 时序数据

时序数据(如自然语言文本,音频波形等)作为输入到神经网络中时,通常需要进行嵌入(Embedding)操作,将其转化为一个低维向量表示,以更好地表征输入序列的语义信息。

Embedding的主要目的是将时序数据映射到一个稠密的连续向量空间中,使得相似的语义信息在该向量空间中也能够彼此接近。这样,神经网络就可以基于这些向量表示学习到输入数据的复杂语义信息,并在相似的向量表示之间进行泛化。

2.1.2 一维卷积(1D-CNN)

TCN所依赖的卷积操作在本质上就是一维卷积(1D-CNN)。一维卷积利用多个大小固定的卷积核与输入序列进行卷积运算来生成输出序列。卷积核的形状由输入通道数 <math xmlns="http://www.w3.org/1998/Math/MathML"> i n _ c h a n n e l s in\_channels </math>in_channels和卷积核大小 <math xmlns="http://www.w3.org/1998/Math/MathML"> k e r n e l _ s i z e kernel\_size </math>kernel_size共同决定,卷积核的数量则由输出通道数 <math xmlns="http://www.w3.org/1998/Math/MathML"> o u t _ c h a n n e l s out\_channels </math>out_channels决定。

经过Embedding后的时序数据的通道数由1扩展成了 <math xmlns="http://www.w3.org/1998/Math/MathML"> e m b e d d i n g _ s i z e embedding\_size </math>embedding_size,对于有着 <math xmlns="http://www.w3.org/1998/Math/MathML"> i n _ c h a n n e l s in\_channels </math>in_channels个通道的时序数据作为输入, 一维卷积使用 <math xmlns="http://www.w3.org/1998/Math/MathML"> o u t _ c h a n n e l s out\_channels </math>out_channels个大小为( <math xmlns="http://www.w3.org/1998/Math/MathML"> i n _ c h a n n e l s in\_channels </math>in_channels, <math xmlns="http://www.w3.org/1998/Math/MathML"> k e r n e l _ s i z e kernel\_size </math>kernel_size)的卷积核进行卷积操作。

2.1.3 零填充(Padding)

注:从此处开始,1D-CNN的输入和输出通道数默认为1,卷积核大小kernel_size默认为K。

TCN网络的第一个原则是输入和输出的形状必须相同。而在进行一维卷积操作时,假设第 <math xmlns="http://www.w3.org/1998/Math/MathML"> i i </math>i层的输入长度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> L i L_i </math>Li,卷积核大小为 <math xmlns="http://www.w3.org/1998/Math/MathML"> K K </math>K,则输出长度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> L i + 1 = L i − K + 1 L_{i+1} = L_{i} - K + 1 </math>Li+1=Li−K+1。

为满足这个原则,TCN在使用卷积神经网络进行序列处理时,通常需要进行 Padding 操作。通过在输入序列的左侧添加一定数量( <math xmlns="http://www.w3.org/1998/Math/MathML"> K − 1 K-1 </math>K−1)的 0,实现信号维度的保持,使通过卷积和池化处理后的数据与输入数据的长度相同。

注意,在一维卷积中进行的Padding操作默认会在左右都进行填充,所以TCN进行了额外的裁剪操作。

2.1 因果卷积(Causal Convolutions)

TCN通过使用因果卷积满足原则二,因果卷积是一种只考虑过去时间状态的一维卷积操作。因果卷积的具体步骤如下图所示。

可以看到,因果卷积具备两个特点:

  • 只考虑过去的信息。 时刻的输出 <math xmlns="http://www.w3.org/1998/Math/MathML"> y t y_t </math>yt仅依赖于 <math xmlns="http://www.w3.org/1998/Math/MathML"> x 0 , . . . , x t x_0,...,x_t </math>x0,...,xt,而不依赖任何"未来"的输入 <math xmlns="http://www.w3.org/1998/Math/MathML"> x t + 1 , . . . , x T x_{t+1},...,x_T </math>xt+1,...,xT。
  • 追溯历史信息越久远,隐藏层越多。 图中,输出层期望采集输入层的8个时间步,则需要6个隐藏层。而业务的需求往往要求采取更多的时间步,确实是"深度"学习了。

由于因果卷积没有循环连接,它们通常比循环神经网络(RNN)训练速度更快,尤其是应用于非常长的序列。但单纯的因果卷积还是存在传统卷积神经网络的问题,即对时间的建模长度是受限于卷积核大小的,如果要想抓去更长的依赖关系,就需要线性的堆叠很多的层。为解决这个问题,TCN使用扩展卷积(Dilated Convolutions)以指数级地增加感受域,而不会大大增加计算成本。

2.3 扩展卷积(Dilated Convolutions)

扩展卷积是一种在因果卷积基础上进行的卷积操作,它将卷积核的采样间隔(跨度)扩大,使得输出的特征图具有更大的感受野和更高的表达能力。和传统卷积不同的是,扩展卷积类似于CNN中的空洞卷积,其允许卷积时的输入存在间隔采样,采样率受超参数 Dilation控制,指的是做卷积操作时kernel里面的元素之间的下标间隔。

扩展卷积的原理如图所示,其中紫色圆表示的是当前卷积的输入时刻步,黄色圆表示的是当前卷积的输出时间步,白色圆表示的是Padding,黑色箭头表示输入和输出的连接方式。

可以看到,扩展卷积具备三个特点:

  • 同因果卷积相同的特性,只考虑过去的信息。
  • 同空洞卷积相同的特性,每一层对上一层信息的提取,都是跳跃式的,且逐层dilation以2的指数增长
  • 每一层扩展卷积都需要做Padding(通常情况下补0),以此保持输入和输出的 维度 大小一致 ,Padding的大小为: <math xmlns="http://www.w3.org/1998/Math/MathML"> p a d d i n g = ( K − 1 ) ∗ d i l a t i o n padding = (K-1)*dilation </math>padding = (K−1)∗dilation。

2.4 残差连接(Residual Connections)

扩展卷积提高了因果卷积网络的扩展性,但TCN的感受野仍受网络深度 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n、卷积核大小 <math xmlns="http://www.w3.org/1998/Math/MathML"> K K </math>K和扩展因子 <math xmlns="http://www.w3.org/1998/Math/MathML"> d i l a t i o n dilation </math>dilation这三个参数的影响。即使使用扩展卷积在一定程度上优化了因果卷积的网络层数问题,但仍然需要较多的层数,这种大而深的网络模型的稳定性尤为重要。比如对于预测可能依赖于大小为 <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 12 2^{12} </math>212的历史和高维输入序列的情况,需要一个至少有12层的TCN网络,且每层都包含多个卷积核用于特征提取。虽然增加网络深度可以提取更抽象、语义化的特征,但网络深度的增加也会带来梯度问题(梯度消失或梯度爆炸)和网络退化问题。

为解决上述问题,TCN对每个权重向量都进行归一化,并使用ReLU非线性激活函数和Dropout正则化,同时在设计中加入了残差连接(每两层扩展卷积进行一次残差连接),如图所示。

  • ReLU激活函数和Dropout正则化不仅可以帮助网络在训练时更快收敛,还可以避免梯度消失的问题。
  • 残差连接能够保持信息流的一致性,并帮助网络学习恒等映射,从而进一步提高网络的性能和泛化能力,减弱网络退化的问题。

需要注意的是,各层TCN的输入和输出的通道数可能会不一致,所以需要使用一个额外的1x1卷积,并逐时间步相加 <math xmlns="http://www.w3.org/1998/Math/MathML"> ⊕ \oplus </math>⊕,以得到和输出相同形状的张量。

三、TCN模型的PyTorch实现

原论文作者代码:github.com/locuslab/TC...

Python 复制代码
# 定义一个1-D卷积层的截断模块,主要用于去除sequence右边的padding
class Crop1d(nn.Module):
    def __init__(self, crop_size):
        super(Crop1d, self).__init__()
        self.crop_size = crop_size  # 输入需要被裁剪的序列长度

    def forward(self, sequence):
        return sequence[:, :, :-self.crop_size].contiguous()  # 裁剪输入,返回截断后的序列
        
# 定义用于时间序列预测的1-D卷积神经网络
class TemporalBlock(nn.Module):
    def __init__(self, n_inputs, n_outputs, kernel_size, stride, dilation, padding, dropout=0.2):
        super(TemporalBlock, self).__init__()

        # 定义两个卷积层,对权重进行规范化
        self.conv1 = weight_norm(nn.Conv1d(n_inputs, n_outputs, kernel_size,
                                           stride=stride, padding=padding, dilation=dilation))  # 第一层卷积层
        self.crop1 = Crop1d(padding)  # 第一层卷积层后面的截取模块
        self.relu1 = nn.ReLU()  # 第一层卷积的激活函数
        self.dropout1 = nn.Dropout(dropout)  # 第一层卷积的dropout层

        self.conv2 = weight_norm(nn.Conv1d(n_outputs, n_outputs, kernel_size,
                                           stride=stride, padding=padding, dilation=dilation))  # 第二层卷积层
        self.crop2 = Crop1d(padding)  # 第二层卷积层后面的截取模块
        self.relu2 = nn.ReLU()  # 第二层卷积的激活函数
        self.dropout2 = nn.Dropout(dropout)  # 第二层卷积的dropout层

        # 将两个卷积层、截取模块、激活函数和dropout层按照顺序放入一个新的模型中,并作为TimeBlock的网络模型
        self.net = nn.Sequential(self.conv1, self.crop1, self.relu1, self.dropout1,
                                 self.conv2, self.crop2, self.relu2, self.dropout2)

        # 如果输入和输出的特征通道数不同,定义1x1的卷积层进行通道数的转换
        self.downsample = nn.Conv1d(n_inputs, n_outputs, 1) if n_inputs != n_outputs else None
        self.relu = nn.ReLU()
        self.init_weights()  # 初始化模型参数

    # 对模型的参数进行初始化
    def init_weights(self):
        self.conv1.weight.data.normal_(0, 0.01)
        self.conv2.weight.data.normal_(0, 0.01)
        if self.downsample is not None:
            self.downsample.weight.data.normal_(0, 0.01)

    # 前向传播计算
    def forward(self, x):
        out = self.net(x)  # 对输入的x进行卷积操作,并通过relu和dropout层进行处理
        res_part = x if self.downsample is None else self.downsample(
            x)  # 如果输出的通道数和输入的通道数不一致,则需要定义一个1x1的卷积层进行通道数转换,这里将res_part指定为输入x或转换后的输出结果。
        res = out + res_part  # 使用相加的方式进行残差连接
        return self.relu(res)  # 通过激活函数处理后返回
        
# 定义用于时间序列预测的卷积神经网络模型
class TemporalConvNet(nn.Module):
    # num_inputs是一个列表,其长度代表TemporalBlock的层数,值表示输入序列的通道数(特征维度)。
    # out_channels是一个列表,其长度代表TemporalBlock的层数,值表示每个TemporalBlock中输出的通道数(特征维度)。
    # kernel_size是卷积核的大小,默认为2。
    def __init__(self, in_channels, out_channels, kernel_size=2, dropout=0.2):
        super(TemporalConvNet, self).__init__()
        layers = []
        num_levels = len(out_channels)
        # 使用for循环,定义多个TemporalBlock模块,并按序添加到网络结构中
        for i in range(num_levels):
            dilation_size = 2 ** i  # 逐层dilation以2的指数增长
            in_channel = in_channels[i]
            out_channel = out_channels[i]
            layers += [TemporalBlock(in_channel, out_channel, kernel_size, stride=1, dilation=dilation_size,
                                     padding=(kernel_size - 1) * dilation_size, dropout=dropout)]
        self.network = nn.Sequential(*layers)

    # 模型前向计算,按顺序执行各模块的前向计算
    def forward(self, x):
        return self.network(x)

四、总结

TCN在时序数据建模方面具有许多优点和应用前景。它采用了因果卷积和扩展卷积来处理序列化数据,同时利用残差连接提升模型的准确性和训练速度。相对于传统的循环神经网络和长短时记忆网络,TCN具有并行计算能力、参数较少以及无门限结构等优点。在实际应用中,TCN在金融、信号处理、文本分类、视频分类等领域都有着非常广泛的应用。

五、参考

相关推荐
因特麦克斯8 分钟前
每日一题&智能指针
数据结构·算法·leetcode
蹉跎x17 分钟前
力扣104. 二叉树的最大深度
算法·leetcode·职场和发展
gaogao_jack19 分钟前
[Leetcode小记] 3233. 统计不是特殊数字的数字数量
java·算法·leetcode
zzzhpzhpzzz33 分钟前
设计模式——解释器模式
算法·设计模式·解释器模式
一只鸡某43 分钟前
实习冲刺第二十九天
数据结构·c++·算法·leetcode
ZZZ_O^O1 小时前
【贪心算法入门第一题——860.柠檬水找零】
学习·算法·leetcode·贪心算法
Easy数模2 小时前
竞赛思享会 | 2024年第十届数维杯国际数学建模挑战赛D题【代码+演示】
python·算法·数学建模
向宇it2 小时前
【unity小技巧】Unity 四叉树算法实现空间分割、物体存储并进行查询和碰撞检测
开发语言·算法·游戏·unity·游戏引擎
无限大.2 小时前
冒泡排序(结合动画进行可视化分析)
算法·排序算法
走向自由3 小时前
Leetcode 最长回文子串
数据结构·算法·leetcode·回文·最长回文