文章目录
- 摘要
- Abstract
- 1 TCN通用架构
- 1.1 序列建模任务描述
- 1.2 因果卷积(Causal Convolutions)
- 1.3 扩张卷积(Dilated Convolutions)
- 1.4 残差连接(Residual Connections)
- 2 TCN vs RNN
- 3 TCN缺点
- 4 代码
- 4.1 TCN通用代码
- 4.2 torch.nn.Conv1d
- 总结
摘要
TCN(Temporal Convolutional Networks,时间卷积网络)是一种专门用于处理时间序列数据的深度学习模型。它结合了卷积神经网络(CNN)的并行处理能力和循环神经网络(RNN)的长期依赖建模能力,成为序列建模任务中的强大工具。TCN主要使用因果卷积来确保网络在预测时只能使用当前和过去的信息,而不能看到未来的数据;使用扩张卷积跳过部分输入来使卷积核可以应用于大于卷积核本身长度的区域,从而增加网络的感受野;同时利用残差连接来增加网络的深度,这有助于训练深层网络并减少梯度消失的问题。但是存在评估期间的数据存储和域迁移的潜在参数变化等问题。
Abstract
TCN (Temporal Convolutional Networks) is a deep learning model specifically designed for processing time series data. It combines the parallel processing capabilities of Convolutional Neural Networks (CNNs) with the long-term dependency modeling capabilities of Recurrent Neural Networks (RNNs), making it a powerful tool for sequence modeling tasks. TCN primarily uses causal convolution to ensure that the network can only use current and past information during prediction, without access to future data; it employs dilated convolution to skip over parts of the input, allowing the convolutional kernel to be applied to regions larger than the length of the kernel itself, thereby increasing the network’s receptive field; and it utilizes residual connections to increase the depth of the network, which helps in training deep networks and reducing the problem of vanishing gradients. However, there are potential issues such as data storage during evaluation and parameter changes in domain migration.
论文链接:https://arxiv.org/pdf/1803.01271
1 TCN通用架构
TCN的显著特征
- 因果卷积(Causal Convolution):TCN中的卷积操作是因果的,这意味着在预测当前时刻的值时,模型只使用当前时刻及之前的数据,而不使用未来数据。这种设计保证了模型的因果性,避免了信息泄露的问题
- 能够处理可变长度的输入序列,并将其映射到相同长度的输出序列。(输入和输出序列长度一致)。
- 扩张卷积(Dilated Convolution):TCN通过使用扩张卷积来扩大卷积层的感受野,使网络能够在不增加参数数量或计算复杂性的情况下捕捉长范围的序列依赖关系。扩张卷积通过在卷积核之间插入空洞来实现,有效地提高了计算效率,并缓解了梯度消失问题
1.1 序列建模任务描述
我们有一个输入序列: x 0 , x 1 , . . . . . x t x_0,x_1,.....x_t x0,x1,.....xt,我们希望预测出一些对应的输出: y 0 , y 1 , . . . . . y t y_0,y_1,.....y_t y0,y1,.....yt。但是有一个限制:如预测某一时间t的输出 y t y_t yt,只能观察时间t及时间t以前的输入( x 0 , x 1 , . . . . . x t x_0,x_1,.....x_t x0,x1,.....xt)。序列建模网络是任何函数: f ( x t + 1 ) = y t + 1 f(x_{t+1})=y_{t+1} f(xt+1)=yt+1,生成的映射是:
y ^ 0 , y ^ 1 . . . . y ^ T = f ( x 0 , x 1 . . . . x T ) \hat{y}_0,\hat{y}_1....\hat{y}_T=f({x}_0,{x}_1....{x}_T) y^0,y^1....y^T=f(x0,x1....xT)
若 y t 仅依赖于 x 0 , x 1 . . . . x t y_t仅依赖于x_0,x_1....x_t yt仅依赖于x0,x1....xt,而不依赖于未来的输入 x t + 1 , . . . . . x T x_{t+1},.....x_T xt+1,.....xT,则在序列建模设置中学习的任务是,找到网络f,使实际输出和预测值之间的预期最小, L ( y 0 , . . . . y T , f ( x 0 , . . . x T ) ) L(y_0,....y_T,f(x_0,...x_T)) L(y0,....yT,f(x0,...xT)),其中序列和输出根据某一概率分布抽取。
1.2 因果卷积(Causal Convolutions)
TCN使用因果卷积(Causal Convolution)来确保模型不会违反时间顺序。因果卷积的输出只依赖于当前时刻及其之前的输入,而不依赖于未来的输入。在标准的卷积操作中,每个输出值都基于其周围的输入值,包括未来的时间点。但在因果卷积中,权重仅应用于当前和过去的输入值,确保了信息流的方向性,避免了未来信息泄露到当前输出中。为了实现这一点,通常会在卷积核的右侧填充零(称为因果填充),这样只有当前和过去的信息被用于计算输出。
图1:卷积核为2
公式: y t = ∑ i = 0 k − 1 f ( i ) X t − i y_t=\sum^{k-1}_{i=0}f(i)X_{t-i} yt=i=0∑k−1f(i)Xt−i
d表示扩张率(dilation factor),k表示卷积核大小(filter size)
因果卷积存在问题: 需要一个非常深的网络或非常大的filters来增加卷积的感受野
通过使用扩张卷积来实现非常深的网络和增加卷积的感受野大小
1.3 扩张卷积(Dilated Convolutions)
一个简单的因果卷积只能回顾网络深度上有限大小的历史信息,在面对较长的历史的任务时,要如何解决?
使用扩张卷积来实现指数级大的感受野,使用扩张卷积,随着网络的深度以指数方式增加d(即 d = O ( 2 i ) d=O(2^i) d=O(2i), i i i 表示网络为第几层),这使卷积核可以命中有效历史中的每个输入,同时还允许使用更深的网络来处理非常大的有效历史。
下图为:扩张因果卷积图,其中扩张因子d=1,2,4;卷积核k大小=3
如图所示, 每一层中一个单元要回顾上一层中(k-1)×d个有效历史信息(扩张因子决定了卷积核中元素之间的间距,例如,如果扩张因子为2,则卷积核中的元素会间隔一个输入单元)。
公式:
y t = ∑ i = 0 k − 1 f ( i ) X t − d ⋅ i y_t=\sum^{k-1}_{i=0}f(i)X_{t-d·i} yt=i=0∑k−1f(i)Xt−d⋅i
d表示扩张率(dilation factor),k表示卷积核大小(filter size)
1.4 残差连接(Residual Connections)
TCN的感受野取决于
- 网络深度n;
- 卷积核大小k;
- 扩张因子d;
在实际预测中,可能取决于大小为 2 12 2^{12} 212的历史和高维输入序列,就需要多达12层的网络,就是每一层由多个用于特征提取的卷积核组合。在通用TCN模型中,采用了通用残差模块代替卷积层。
如图 TCN 结构图。 一个残差块包含两层的卷积和非线性映射,在每层中还加入了 WeightNorm 和 Dropout 来正则化网络。为什么要 1×1 卷积呢?1×1 卷积是可以用来降维的 。作者直接把较下层的特征图跳层连接到上层,对应的每个 Cell 的特征图数量(也就是通道数 channel)不一致,导致不能直接做类似 Resnet 的跳层特征图加和操作,于是,为了两个层加和时特征图数量吻合,用 1×1 卷积做了一个降维的操作。
TCN使用残差连接来缓解梯度消失问题并促进更深层网络的训练。残差连接是残差网络(ResNets)的关键组成部分,由何凯明等人提出。它的主要目的是解决深层神经网络训练中的梯度消失/爆炸问题,以及提高网络的训练效率和性能。在残差连接中,网络的某一层的输出直接加到几层之后的另一层上,形成所谓的“跳跃连接”。
具体来说,假设有一个输入 x x x,经过几层后得 F ( x ) F(x) F(x),那么最终的输出不是 F ( x ) F(x) F(x),而是 x + F ( x ) x+F(x) x+F(x),也就是输入+输出。这种结构允许梯度在反向传播时可以直接流回更早的层,减少了梯度消失的问题,并且使得网络能够有效地训练更深的架构。残差块的输出可以表示为:
o u t p u t = A c t i v a t i o n ( x + F ( x ) ) output=Activation(x+F(x)) output=Activation(x+F(x))
其中 F F F是卷积层和激活函数的组合。
残差连接如下图所示:
2 TCN vs RNN
相比较RNN,TCN有如下优势:
- 并行计算: 卷积可以并行进行。(在训练和评估中,在TCN中可以将长输入序列作为一个整体来处理,而不是像在RNN中那样顺序地处理。)
- 灵活的感受野大小: 通过堆叠更多的因果卷积层、增大扩张率和卷积核,可增大感受野。
- 稳定的梯度: 与递归体系结构不同,TCN具有与序列的时间方向不同的反向传播路径;故TCN没有梯度爆炸的问题。
3 TCN缺点
- 评估期间的数据存储: 在评估/测试中,TCN需要接收到有效历史长度的原始序列,因此在评估期间可能需要更多内存。(RNN只需要保持隐藏状态并接受当前输入 x t x_t xt即可生成预测)
- 域迁移的潜在参数变化: 不同领域对模型预测所需的历史数量可能有不同的要求。因此,当将模型从只需要很少内存的域转移到需要更长内存的域时,TCN可能会因为没有足够大的感受野而表现不佳。
4 代码
4.1 TCN通用代码
- Chomp1d 类
用于去除卷积输出中的最后几个元素,在应用了填充(padding)的卷积之后,保持输出序列的长度跟输入序列长度相同。
class Chomp1d(nn.Module):def __init__(self, chomp_size):super(Chomp1d, self).__init__()self.chomp_size = chomp_sizedef forward(self, x):return x[:, :, :-self.chomp_size].contiguous()
chomp_size
:指定要去除的元素数量。
- TemporalBlock 类
TemporalBlock
是TCN中的基本构建块,它包含两个卷积层,每个卷积层后面跟着ReLU激活函数和dropout层,以减少过拟合。
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.chomp1 = Chomp1d(padding)self.relu1 = nn.ReLU()self.dropout1 = nn.Dropout(dropout)self.conv2 = weight_norm(nn.Conv1d(n_outputs, n_outputs, kernel_size,stride=stride, padding=padding, dilation=dilation))self.chomp2 = Chomp1d(padding)self.relu2 = nn.ReLU()self.dropout2 = nn.Dropout(dropout)self.net = nn.Sequential(self.conv1, self.chomp1, self.relu1, self.dropout1,self.conv2, self.chomp2, self.relu2, self.dropout2)self.downsample = nn.Conv1d(n_inputs, n_outputs, 1) if n_inputs != n_outputs else Noneself.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)res = x if self.downsample is None else self.downsample(x)return self.relu(out + res)
- TemporalConvNet 类
TemporalConvNet 是整个TCN模型,它由多个TemporalBlock
堆叠而成。
class TemporalConvNet(nn.Module):def __init__(self, num_inputs, num_channels, kernel_size=2, dropout=0.2):super(TemporalConvNet, self).__init__()layers = []num_levels = len(num_channels)for i in range(num_levels):dilation_size = 2 ** iin_channels = num_inputs if i == 0 else num_channels[i-1]out_channels = num_channels[i]layers += [TemporalBlock(in_channels, out_channels, 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)
参数:
num_inputs:
输入特征的数量。num_channels:
一个列表,指定了每个 TemporalBlock 的输出通道数。kernel_size:
卷积核的大小。dropout:
dropout层的丢弃概率,用于正则化以减少过拟合。stride:
卷积的步长,这里设置为1。dilation:
卷积的扩张率,用于控制卷积核的覆盖范围。padding:
卷积的填充,确保输出序列长度不变。
TCN的网络结构是通过堆叠多个 TemporalBlock
来构建的。每个块的扩张率是前一个块的两倍,这样可以增加模型的感受野,同时保持时间序列数据的局部性。
4.2 torch.nn.Conv1d
torch.nn.Conv1d
一维卷积nn.Conv1d主要用于文本数据,只对宽度进行卷积,对高度不卷积。通常,输入大小为word_embedding_dim * max_length,其中,word_embedding_dim为词向量的维度,max_length为句子的最大长度。卷积核窗口在句子长度的方向上滑动,进行卷积操作。
torch.nn.Conv1d(in_channels, out_channels, kernel_size, stride=1, padding=0,
dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)
原理示意图
- 参数说明:
- in_channels:输入通道数;在文本应用中,即为词向量的维度;
- out_channels:输出通道数,等价于卷积核个数;
- kernel_size :卷积核大小;卷积核的第二个维度由in_channels决定,所以实际上卷积核的大小为kernel_size * in_channels
- stride:卷积步长,默认为 1;
- padding: 默认为 0;对输入的每一条边,补充0的层数;
- dilation:空洞卷积尺寸,默认为 1;
输入输出格式:
- Input: ( N , C i n , L i n ) (N,C_{in},L_{in}) (N,Cin,Lin)
- Output: ( N , C o u t , L o u t ) (N,C_{out},L_{out}) (N,Cout,Lout)
其中 L o u t = ⌊ L i n + 2 × p a d d i n g − d i l a t i o n × ( k e r n e l s i z e − 1 ) − 1 s t r i d e + 1 ⌋ L_{out}=\lfloor \frac{L_{in}+2×padding-dilation×(kernel_size-1)-1}{stride} +1\rfloor Lout=⌊strideLin+2×padding−dilation×(kernelsize−1)−1+1⌋
其中:
- N表示batch_size; L i n L_{in} Lin表示输入的长度; L o u t L_{out} Lout表示输出的长度
- C i n C_{in} Cin必须与Conv1中参数 in_channels 值相同
- C o u t C_{out} Cout必须与Conv1中参数 out_channels 值相同
代码:
import torch
import torch.nn as nn# 创建一个一维卷积层,输入通道数为1,输出通道数为2,卷积核大小为3
conv1d = nn.Conv1d(in_channels=1, out_channels=2, kernel_size=3)# 创建一个随机输入数据,形状为[batch_size, in_channels, sequence_length]
input_data = torch.randn(2, 1, 10) # batch_size=2, in_channels=1, sequence_length=10
# 应用卷积操作
output_data = conv1d(input_data)# 输出数据的形状
# batch_size=2, out_channels=2, new_sequence_length=8
print(output_data.shape) # 将输出形状,例如:torch.Size([2, 2, 8])
输出:
torch.Size([2, 2, 8])
其中输出长度为= L o u t = ⌊ L i n + 2 × p a d d i n g − d i l a t i o n × ( k e r n e l s i z e − 1 ) − 1 s t r i d e + 1 ⌋ L_{out}=\lfloor \frac{L_{in}+2×padding-dilation×(kernel_size-1)-1}{stride} +1\rfloor Lout=⌊strideLin+2×padding−dilation×(kernelsize−1)−1+1⌋
= ⌊ 10 + 2 × 0 − 1 × ( 3 − 1 ) − 1 1 + 1 ⌋ \lfloor \frac{10+2×0-1×(3-1)-1}{1} +1\rfloor ⌊110+2×0−1×(3−1)−1+1⌋=8
- 扩张率d为2:
# 创建一个一维卷积层,输入通道数为1,输出通道数为2,卷积核大小为3,扩张率d=2
conv1d = nn.Conv1d(in_channels=1, out_channels=2, kernel_size=3, dilation=2)
print(output_data.shape)
输出:
torch.Size([2, 2, 6])
其中输出长度为= L o u t = ⌊ L i n + 2 × p a d d i n g − d i l a t i o n × ( k e r n e l s i z e − 1 ) − 1 s t r i d e + 1 ⌋ L_{out}=\lfloor \frac{L_{in}+2×padding-dilation×(kernel_size-1)-1}{stride} +1\rfloor Lout=⌊strideLin+2×padding−dilation×(kernelsize−1)−1+1⌋
= ⌊ 10 + 2 × 0 − 2 × ( 3 − 1 ) − 1 1 + 1 ⌋ \lfloor \frac{10+2×0-2×(3-1)-1}{1} +1\rfloor ⌊110+2×0−2×(3−1)−1+1⌋=6
总结
本周阅读论文学习了TCN模型的相关原理、结构和代码;TCN能够并行处理多个时间步的输入,提高模型的训练和推理速度,同时保持对长期依赖关系的捕捉能力。