机器学习周报-ModernTCN文献阅读
文章目录
- 摘要
- Abstract
- 0 提升有效感受野(ERF)
- 1 相关知识
- 1.1 标准卷积
- 1.2 深度分离卷积(Depthwise Convolution,DWConv)
- 1.3 逐点卷积(Pointwise Convolution,PWConv)
- 1.4 组卷积(Group Convolution)
- 2 ModernTCN
- 2.1 Modern convolution block
- 2.2 相关修改
- 2.2.1 保留变量维度
- 2.2.2 DWConv
- 2.2.3 ConvFFN
- 2.3 最终结构
- 2.4 Modern TCN代码
- 总结
摘要
本周阅读了ModernTCN:一种时间序列分析的现代纯卷积结构,其在多个主流时间序列分析任务上取得了与最先进的基于Transformer和MLP的模型相媲美的性能,同时保持了卷积模型的效率优势。ModernTCN借鉴了Transformer中的架构设计,采用了深度卷积和逐点卷积的组合,以提高模型的表示能力。同时,为了更好地处理时间序列数据,ModernTCN引入了变量独立嵌入和跨变量依赖捕获机制。ModernTCN通过使用大核心尺寸而不是堆叠更多小核心,ModernTCN显著扩大了ERF,这有助于更好地捕捉时间序列数据中的长期依赖性。
Abstract
This week, I read ModernTCN: a modern pure convolutional structure for time series analysis that has achieved performance comparable to state-of-the-art Transformer and MLP based models on multiple mainstream time series analysis tasks, while maintaining the efficiency advantage of convolutional models. ModernTCN draws inspiration from the architecture design of Transformer and adopts a combination of deep convolution and pointwise convolution to enhance the model’s representation capability. Meanwhile, in order to better handle time series data, ModernTCN introduces variable independent embedding and cross variable dependency capture mechanisms. ModernTCN significantly expands ERF by using larger core sizes instead of stacking more small cores, which helps better capture long-term dependencies in time series data.
0 提升有效感受野(ERF)
基于Transformer的模型和基于MLP的模型具有全局有效感受野(ERF),因此可以更好地捕获长期时间(跨时间)依赖性,从而显著优于传统的TCN。而SCINet和MICN主要关注的是设计更复杂的结构来处理传统的卷积,而忽略了更新卷积本身的重要性,下图给出了这两种模型的感受野对比。
其中,ModernTCN通常采用大核,来有效地提高ERF。
SCINet 和 MICN 是两个基于 TCN 的预测模型,它们的感受野都很小。作者发现 ModernTCN 中采用大的卷积核所对应的感受野要大很多。其次是充分利用卷积可以捕获跨变量依赖性,也就是多变量时间序列中变量之间的关系
1 相关知识
1.1 标准卷积
标准卷积,利用若干个多通道卷积核对输入的多通道图像进行处理,输出的feature map既提取了通道特征,又提取了空间特征。
如图所示:假设输入层为一个大小为5×5像素,3通道色彩的输入图片(shape为5×5×3),经过3×3卷积核的卷积层(假设输出通道为4,则卷积核shape为3×3×3×4),最终输出4个Feature Map。其中卷积层的参数量为:3×3×3×4=108
1.2 深度分离卷积(Depthwise Convolution,DWConv)
DWConv就是深度(channel)维度不变,改变H/W。
DWConv的一个卷积核负责一个通道,一个通道只被一个卷积核卷积。而常规卷积每个卷积核是同时操作输入图片的每个通道,这样就不会混合通道,只会混合token。
DWConv的核为单通道模式,需要对输入的每一个通道进行卷积,这样就会得到和输入特征图通道数一致的输出特征图。即有输入特征图通道数=卷积核个数=输出特征图个数。
如下图所示:同样是对于一张5×5像素、三通道彩色输入图片(shape为5×5×3),Depthwise Convolution首先经过第一次卷积运算,不同于上面的常规卷积,DW完全是在二维平面内进行。卷积核的数量与上一层的通道数相同(通道和卷积核一一对应)。所以一个三通道的图像经过运算后生成了3个Feature map(如果有same padding则尺寸与输入层相同为5×5)。
卷积层的参数量为:3×3×3=27
DWConv完成后的Feature map数量与输入层的通道数相同,无法扩展Feature map。而且这种运算对输入层的每个通道独立进行卷积运算,没有有效的利用不同通道在相同空间位置上的feature信息。因此需要PWConv来将这些Feature map进行组合生成新的Feature map。
1.3 逐点卷积(Pointwise Convolution,PWConv)
PWConv就是W/H维度不变,改变channel
由于DWConv输入特征图通道数=卷积核个数=输出特征图个数,这样会导致输出的特征图个数过少(或者说输出特征图的通道数过少,可看成是输出特征图个数为1,通道数为3),从而可能影响信息的有效性。此时,就需要进行逐点卷积。
逐点卷积实质上是用1x1的卷积核进行升维
PWConv的运算与常规卷积运算非常相似,它的卷积核的尺寸为 1×1×M(M为上一层的通道数)。所以这里的卷积运算会将上一步的map在深度方向上进行加权组合,生成新的Feature map。有几个卷积核就有几个输出Feature map。
如下图所示:从DWConv得到的3个单通道特征图,经过4个大小为:1×1×3的卷积核的卷积计算后,输出4个特征图;而输出特征图的个数取决于Filter的个数。卷积层的参数量为:1×1×3×4=12
1.4 组卷积(Group Convolution)
将输入特征图的通道分成若干组,并在每组内单独进行卷积操作,从而减少计算复杂度和参数量,同时有效利用模型的结构特性。
如下图:输入数据shape: H i n × W i n × C i n H_{in}×W_{in}×C_{in} Hin×Win×Cin,假设将输入数据的通道分为G组,即每组数据的通道数为 C i n G \frac{C_{in}}{G} GCin,对这G组数据进行单独设计卷积核,最终的输出结果将这G组的卷积结果进行拼接;其中每个卷积核shape为:k×k
卷积层的参数量为: k × k × C i n G × C o u t G × G k×k×\frac{C_{in}}{G}×\frac{C_{out}}{G}×G k×k×GCin×GCout×G
= k × k × C i n × C o u t G \frac{k×k×C_{in}×C_{out}}{G} Gk×k×Cin×Cout
而标准卷积的卷积层的参数量为: k × k × C i n × C o u t k×k×C_{in}×C_{out} k×k×Cin×Cout
即分组卷积可将参数量减小为原来的1/G。
分组卷积的用途:
- 减少参数量,分成𝐺组,则该层的参数量减少为原来的1/𝐺
- Group Convolution可以看成是structured sparse,每个卷积核的尺寸由𝐶∗𝐾∗𝐾变为𝐶/𝐺∗𝐾∗𝐾,可以将其余(𝐶−𝐶 / 𝐺)∗𝐾∗𝐾的参数视为0,有时甚至可以在减少参数量的同时获得更好的效果(相当于正则)
- 当分组数量等于输入map数量,输出map数量也等于输入map数量,即𝐺=𝑁=𝐶,𝑁个卷积核每个尺寸为1∗𝐾∗𝐾时,Group Convolution就成了DWConv
2 ModernTCN
ModernTCN采用了现代化的卷积块设计,借鉴了Transformer中的架构设计,使用了深度卷积和逐点卷积的组合,以提高模型的表示能力。
论文贡献:
- 作者深入研究了如何更好地利用卷积在时间序列中的问题,并提出了一种新的解决方案。实验结果表明所提方法在时间序列分析中比现有的基于卷积的模型更能发挥卷积的潜力。
- ModernTCN在多个主流时间序列分析任务上实现了一致的最先进性能,展示了出色的任务泛化能力。
- ModernTCN提供了效率和性能的更好平衡。它保持了基于卷积的模型的效率优势,同时在性能方面与最先进的基于Transformer的模型竞争,甚至更好。
2.1 Modern convolution block
ModernTCN模块设计,其中M,N,D分别是变量维度、时间维度和特征维度的大小
DWConv
负责在每个特征的基础上学习token之间(不同时间步之间)的时间信息,其作用与Transformer中的自关注模块相同。
ConvFFN
类似于Transformer中的FFN
模块(结果等价)。它由两个PWConv
组成,采用倒瓶颈结构(先升维后降维),其中ConvFFN
块的隐藏通道比输入通道宽 r rr 倍。该模块独立学习每个 token 新的 feature representation。
上述设计实现了时间信息和特征信息混合的分离。DWConv
和ConvFFN
中的每一个都只混合一个时间或特征维度的信息。与传统的卷积不同,传统的卷积将两个维度的信息混合在一起。这种解耦设计使对象任务更容易学习,降低了计算复杂度
但作者发现,简单地以与CV相同的方式对卷积进行现代化改造,在时间序列任务中几乎没有带来性能改进。作者注意到时间序列除了特征维和时间维之外,时间序列还有一个变量维。**其中ConvFFN可以建模通道间关系,但无法建模变量间关系。为了使现代1D卷积更适合于时间序列分析,还需要更多与时间序列相关的修改
2.2 相关修改
2.2.1 保留变量维度
时间序列变量之间存在复杂的依赖关系,简单的嵌入层,忽略了变量的维度,无法学习这种依赖性,甚至可能因为不考虑变量的不同行为而丢失变量的独立特性。
作者提出了一种“patchify variable-independent embedding”(分块变量独立嵌入)的方法。
提出了一种新的时间序列数据嵌入方法,通过分块和全卷积的方式,更好地适应时间序列数据的特性,同时保留了变量维度,为进一步的分析和建模提供了基础。
输入表示: X i n ∈ R M × L X_{in}\in R^{M×L} Xin∈RM×L 表示输入的时间序列,其长度为L,其特征数量为M。
Patchify Variable-Independent Embedding 步骤:
- 将输入 X i n ∈ R M × L X_{in}\in R^{M×L} Xin∈RM×L 解压缩为 X i n ∈ R M × 1 × L X_{in}\in R^{M×1×L} Xin∈RM×1×L
- padding operation:对原始序列 X i n X_{in} Xin采用padding操作,确保N=L//S(L为序列长度,S为步长,)(具体来说,我们重复Xin的最后一个值(P-S)次,然后将它们填充回Xin的末尾。)
- 将填充好的 X i n X_{in} Xin通过1D卷积层进行patching和embedding
其中卷积层的核大小为P,步长为S,将1个输入通道映射到D个输出通道。在这个过程中,每个单变量时间序列被独立嵌入,从而保留了变量维度。
其中分块处理:将输入的时间序列在适当的填充后,将时间序列分成N个大小为P的块,分块过程中的步长为S,S也是两个连续块之间不重叠区域的长度。
嵌入变量将这些块嵌入到D维的嵌入向量中, X e m b = E m b e d d i n g ( X i n ) X_{emb}=Embedding(X_{in}) Xemb=Embedding(Xin)得到 X e m b ∈ R M × D × L X_{emb}\in R^{M×D×L} Xemb∈RM×D×L
这里将原始的变量的隐藏维度(等于1)计入输入变量 X i n ∈ R M × L X_{in}\in R^{M×L} Xin∈RM×L ,调整为 X i n ∈ R M × 1 × L X_{in}\in R^{M×1×L} Xin∈RM×1×L,然后对每个变量单独进行 embedding ,将每个变量映射为指定的隐藏维度D,得到嵌入 X e m b ∈ R M × D × L X_{emb}\in R^{M×D×L} Xemb∈RM×D×L;
优势:
- 保留变量维度:通过独立嵌入,可以保留时间序列的变量维度,这对于捕捉时间序列数据的复杂特性至关重要。
- 信息捕获:后续的修改将使结构能够从额外的变量维度中捕获信息。
2.2.2 DWConv
DWConv最初是为学习时间信息而设计的。由于单独使用DWConv来共同学习跨时间和跨变量的依赖关系比较困难,所以不适合让DWConv同时负责跨变量维度的信息混合。因此,作者将原来的DWConv从仅特征独立修改为特征和变量独立,使其独立学习每个单变量时间序列的时间依赖性。同时,在DWConv中采用大核来增加ERF,提高时间建模能力。
2.2.3 ConvFFN
由于DWConv是特征和变量独立的,ConvFFN作为补充,应该混合跨特征和变量维度的信息。一种简单的方法是通过单个ConvFFN共同学习特征和变量之间的依赖关系。但这种方法的计算复杂度较高,性能较差。因此,我们进一步将单个ConvFFN解耦为ConvFFN1和ConvFFN2,方法是将PWConvs替换为grouped PWConvs ,并设置不同的组数。ConvFFN1负责学习每个变量的特征信息,ConvFFN2负责捕获每个特征的交叉变量依赖性。
2.3 最终结构
最终修改得到的ModernTCN block如下
其中DWConv、ConvFFN1和ConvFFN2中的每一个只在时间、特征或可变维度中的一个上混合信息,这保持了现代卷积中解耦设计的思想。现(深度可分离卷积其实也是组数等于深度数的组卷积),既简单又有效。
上图中 shape 在每一个模块的前后变化,首先,用 DWConv 来建模时间上的关系,但又不希望它参与到通道间和变量间的建模上。因此,作者将M和D这两个表示变量通道的维度 reshape 在一起,再进行深度可分离卷积。其次,希望独立建模通道和变量。因此,作者采用了两个组卷积,其中一个组卷积的 Group 数为 M(表示每 D 个通道构成一个组,因此用来建模通道间关系),另一个组卷积的 Group 数为 D(表示每 M 个变量构成一个组,因此用来建模变量间关系)。注意,两个组卷积之间存在着 reshape 和 permute 操作,这是为了正确的分组,最后会再 reshape 和 permute 回去。最后,整体再用一个残差连接,即可得到最终的 ModernTCN block。堆叠多个 block 即可得到 ModernTCN 模型。综上所述,作者将时间上、通道上、变量上的三种关系解耦建模,用三种组卷积来巧妙地进行实现(深度可分离卷积其实也是组数等于深度数的组卷积),既简单又有效。
2.4 Modern TCN代码
ModernTCN_Layer.py
import torch
from torch import nn
import math
# decompositionclass moving_avg(nn.Module):"""Moving average block to highlight the trend of time series"""def __init__(self, kernel_size, stride):super(moving_avg, self).__init__()self.kernel_size = kernel_sizeself.avg = nn.AvgPool1d(kernel_size=kernel_size, stride=stride, padding=0)def forward(self, x):# padding on the both ends of time seriesfront = x[:, 0:1, :].repeat(1, (self.kernel_size - 1) // 2, 1)end = x[:, -1:, :].repeat(1, (self.kernel_size - 1) // 2, 1)x = torch.cat([front, x, end], dim=1)x = self.avg(x.permute(0, 2, 1))x = x.permute(0, 2, 1)return xclass series_decomp(nn.Module):"""Series decomposition block"""def __init__(self, kernel_size):super(series_decomp, self).__init__()self.moving_avg = moving_avg(kernel_size, stride=1)def forward(self, x):moving_mean = self.moving_avg(x)res = x - moving_meanreturn res, moving_mean# forecast task head
class Flatten_Head(nn.Module):def __init__(self, individual, n_vars, nf, target_window, head_dropout=0):super(Flatten_Head, self).__init__()self.individual = individualself.n_vars = n_varsif self.individual:self.linears = nn.ModuleList()self.dropouts = nn.ModuleList()self.flattens = nn.ModuleList()for i in range(self.n_vars):self.flattens.append(nn.Flatten(start_dim=-2))self.linears.append(nn.Linear(nf, target_window))self.dropouts.append(nn.Dropout(head_dropout))else:self.flatten = nn.Flatten(start_dim=-2)self.linear = nn.Linear(nf, target_window)self.dropout = nn.Dropout(head_dropout)def forward(self, x): # x: [bs x nvars x d_model x patch_num]if self.individual:x_out = []for i in range(self.n_vars):z = self.flattens[i](x[:, i, :, :]) # z: [bs x d_model * patch_num]z = self.linears[i](z) # z: [bs x target_window]z = self.dropouts[i](z)x_out.append(z)x = torch.stack(x_out, dim=1) # x: [bs x nvars x target_window]else:x = self.flatten(x)x = self.linear(x)x = self.dropout(x)return x
moving_avg模块
:移动平均块,用于突出时间序列的趋势,通过在时间序列两端进行填充,然后应用一维平均池化操作实现.series_decomp模块
:序列分解块,将时间序列分解为残差和趋势两部分,其中趋势部分通过moving_avg
模块计算得到.Flatten_Head模块
:预测任务头部模块,用于将特征进行展平和线性变换,以输出预测结果.支持个体化和非个体化两种模式,个体化模式为每个变量分别进行操作,非个体化模式则对所有变量统一操作.
ModernTCN.py
文件定义了基于时间卷积网络(TCN)的现代时间序列预测模型ModernTCN及其相关组件,包括模型的构建、前向传播过程以及模型的结构重参数化等.
import torch
from torch import nn
import torch.nn.functional as F
import math
from layers.RevIN import RevIN
from models.ModernTCN_Layer import series_decomp, Flatten_Headclass LayerNorm(nn.Module):def __init__(self, channels, eps=1e-6, data_format="channels_last"):super(LayerNorm, self).__init__()self.norm = nn.Layernorm(channels)def forward(self, x):B, M, D, N = x.shapex = x.permute(0, 1, 3, 2)x = x.reshape(B * M, N, D)x = self.norm(x)x = x.reshape(B, M, N, D)x = x.permute(0, 1, 3, 2)return xdef get_conv1d(in_channels, out_channels, kernel_size, stride, padding, dilation, groups, bias):return nn.Conv1d(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size, stride=stride,padding=padding, dilation=dilation, groups=groups, bias=bias)def get_bn(channels):return nn.BatchNorm1d(channels)def conv_bn(in_channels, out_channels, kernel_size, stride, padding, groups, dilation=1,bias=False):if padding is None:padding = kernel_size // 2result = nn.Sequential()result.add_module('conv', get_conv1d(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size,stride=stride, padding=padding, dilation=dilation, groups=groups, bias=bias))result.add_module('bn', get_bn(out_channels))return resultdef fuse_bn(conv, bn):kernel = conv.weightrunning_mean = bn.running_meanrunning_var = bn.running_vargamma = bn.weightbeta = bn.biaseps = bn.epsstd = (running_var + eps).sqrt()t = (gamma / std).reshape(-1, 1, 1)return kernel * t, beta - running_mean * gamma / stdclass ReparamLargeKernelConv(nn.Module):def __init__(self, in_channels, out_channels, kernel_size,stride, groups,small_kernel,small_kernel_merged=False, nvars=7):super(ReparamLargeKernelConv, self).__init__()self.kernel_size = kernel_sizeself.small_kernel = small_kernel# We assume the conv does not change the feature map size, so padding = k//2. Otherwise, you may configure padding as you wish, and change the padding of small_conv accordingly.padding = kernel_size // 2if small_kernel_merged:self.lkb_reparam = nn.Conv1d(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size,stride=stride, padding=padding, dilation=1, groups=groups, bias=True)else:self.lkb_origin = conv_bn(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size,stride=stride, padding=padding, dilation=1, groups=groups,bias=False)if small_kernel is not None:assert small_kernel <= kernel_size, 'The kernel size for re-param cannot be larger than the large kernel!'self.small_conv = conv_bn(in_channels=in_channels, out_channels=out_channels,kernel_size=small_kernel,stride=stride, padding=small_kernel // 2, groups=groups, dilation=1,bias=False)def forward(self, inputs):if hasattr(self, 'lkb_reparam'):out = self.lkb_reparam(inputs)else:out = self.lkb_origin(inputs)if hasattr(self, 'small_conv'):out += self.small_conv(inputs)return outdef PaddingTwoEdge1d(self,x,pad_length_left,pad_length_right,pad_values=0):D_out,D_in,ks=x.shapeif pad_values ==0:pad_left = torch.zeros(D_out,D_in,pad_length_left)pad_right = torch.zeros(D_out,D_in,pad_length_right)else:pad_left = torch.ones(D_out, D_in, pad_length_left) * pad_valuespad_right = torch.ones(D_out, D_in, pad_length_right) * pad_valuesx = torch.cat([pad_left,x],dims=-1)x = torch.cat([x,pad_right],dims=-1)return xdef get_equivalent_kernel_bias(self):eq_k, eq_b = fuse_bn(self.lkb_origin.conv, self.lkb_origin.bn)if hasattr(self, 'small_conv'):small_k, small_b = fuse_bn(self.small_conv.conv, self.small_conv.bn)eq_b += small_beq_k += self.PaddingTwoEdge1d(small_k, (self.kernel_size - self.small_kernel) // 2,(self.kernel_size - self.small_kernel) // 2, 0)return eq_k, eq_bdef merge_kernel(self):eq_k, eq_b = self.get_equivalent_kernel_bias()self.lkb_reparam = nn.Conv1d(in_channels=self.lkb_origin.conv.in_channels,out_channels=self.lkb_origin.conv.out_channels,kernel_size=self.lkb_origin.conv.kernel_size, stride=self.lkb_origin.conv.stride,padding=self.lkb_origin.conv.padding, dilation=self.lkb_origin.conv.dilation,groups=self.lkb_origin.conv.groups, bias=True)self.lkb_reparam.weight.data = eq_kself.lkb_reparam.bias.data = eq_bself.__delattr__('lkb_origin')if hasattr(self, 'small_conv'):self.__delattr__('small_conv')class Block(nn.Module):def __init__(self, large_size, small_size, dmodel, dff, nvars, small_kernel_merged=False, drop=0.1):super(Block, self).__init__()self.dw = ReparamLargeKernelConv(in_channels=nvars * dmodel, out_channels=nvars * dmodel,kernel_size=large_size, stride=1, groups=nvars * dmodel,small_kernel=small_size, small_kernel_merged=small_kernel_merged, nvars=nvars)self.norm = nn.BatchNorm1d(dmodel)#convffn1self.ffn1pw1 = nn.Conv1d(in_channels=nvars * dmodel, out_channels=nvars * dff, kernel_size=1, stride=1,padding=0, dilation=1, groups=nvars)self.ffn1act = nn.GELU()self.ffn1pw2 = nn.Conv1d(in_channels=nvars * dff, out_channels=nvars * dmodel, kernel_size=1, stride=1,padding=0, dilation=1, groups=nvars)self.ffn1drop1 = nn.Dropout(drop)self.ffn1drop2 = nn.Dropout(drop)#convffn2self.ffn2pw1 = nn.Conv1d(in_channels=nvars * dmodel, out_channels=nvars * dff, kernel_size=1, stride=1,padding=0, dilation=1, groups=dmodel)self.ffn2act = nn.GELU()self.ffn2pw2 = nn.Conv1d(in_channels=nvars * dff, out_channels=nvars * dmodel, kernel_size=1, stride=1,padding=0, dilation=1, groups=dmodel)self.ffn2drop1 = nn.Dropout(drop)self.ffn2drop2 = nn.Dropout(drop)self.ffn_ratio = dff//dmodeldef forward(self,x):input = xB, M, D, N = x.shapex = x.reshape(B,M*D,N)x = self.dw(x)x = x.reshape(B,M,D,N)x = x.reshape(B*M,D,N)x = self.norm(x)x = x.reshape(B, M, D, N)x = x.reshape(B, M * D, N)x = self.ffn1drop1(self.ffn1pw1(x))x = self.ffn1act(x)x = self.ffn1drop2(self.ffn1pw2(x))x = x.reshape(B, M, D, N)x = x.permute(0, 2, 1, 3)x = x.reshape(B, D * M, N)x = self.ffn2drop1(self.ffn2pw1(x))x = self.ffn2act(x)x = self.ffn2drop2(self.ffn2pw2(x))x = x.reshape(B, D, M, N)x = x.permute(0, 2, 1, 3)x = input + xreturn xclass Stage(nn.Module):def __init__(self, ffn_ratio, num_blocks, large_size, small_size, dmodel, dw_model, nvars,small_kernel_merged=False, drop=0.1):super(Stage, self).__init__()d_ffn = dmodel * ffn_ratioblks = []for i in range(num_blocks):blk = Block(large_size=large_size, small_size=small_size, dmodel=dmodel, dff=d_ffn, nvars=nvars, small_kernel_merged=small_kernel_merged, drop=drop)blks.append(blk)self.blocks = nn.ModuleList(blks)def forward(self, x):for blk in self.blocks:x = blk(x)return xclass ModernTCN(nn.Module):def __init__(self,patch_size,patch_stride, stem_ratio, downsample_ratio, ffn_ratio, num_blocks, large_size, small_size, dims, dw_dims,nvars, small_kernel_merged=False, backbone_dropout=0.1, head_dropout=0.1, use_multi_scale=True, revin=True, affine=True,subtract_last=False, freq=None, seq_len=512, c_in=7, individual=False, target_window=96):super(ModernTCN, self).__init__()# RevINself.revin = revinif self.revin:self.revin_layer = RevIN(c_in, affine=affine, subtract_last=subtract_last)# stem layer & down sampling layers(if needed)self.downsample_layers = nn.ModuleList()stem = nn.Sequential(nn.Conv1d(1, dims[0], kernel_size=patch_size, stride=patch_stride),nn.BatchNorm1d(dims[0]))self.downsample_layers.append(stem)for i in range(3):downsample_layer = nn.Sequential(nn.BatchNorm1d(dims[i]),nn.Conv1d(dims[i], dims[i + 1], kernel_size=downsample_ratio, stride=downsample_ratio),)self.downsample_layers.append(downsample_layer)self.patch_size = patch_sizeself.patch_stride = patch_strideself.downsample_ratio = downsample_ratioif freq == 'h':time_feature_num = 4elif freq == 't':time_feature_num = 5else:raise NotImplementedError("time_feature_num should be 4 or 5")self.te_patch = nn.Sequential(nn.Conv1d(time_feature_num, time_feature_num, kernel_size=patch_size, stride=patch_stride,groups=time_feature_num),nn.Conv1d(time_feature_num, dims[0], kernel_size=1, stride=1, groups=1),nn.BatchNorm1d(dims[0]))# backboneself.num_stage = len(num_blocks)self.stages = nn.ModuleList()for stage_idx in range(self.num_stage):layer = Stage(ffn_ratio, num_blocks[stage_idx], large_size[stage_idx], small_size[stage_idx], dmodel=dims[stage_idx],dw_model=dw_dims[stage_idx], nvars=nvars, small_kernel_merged=small_kernel_merged, drop=backbone_dropout)self.stages.append(layer)# Multi scale fusing (if needed)self.use_multi_scale = use_multi_scaleself.up_sample_ratio = downsample_ratioself.lat_layer = nn.ModuleList()self.smooth_layer = nn.ModuleList()self.up_sample_conv = nn.ModuleList()for i in range(self.num_stage):align_dim = dims[-1]lat = nn.Conv1d(dims[i], align_dim, kernel_size=1,stride=1)self.lat_layer.append(lat)smooth = nn.Conv1d(align_dim, align_dim, kernel_size=3, stride=1, padding=1)self.smooth_layer.append(smooth)up_conv = nn.Sequential(nn.ConvTranspose1d(align_dim, align_dim, kernel_size=self.up_sample_ratio, stride=self.up_sample_ratio),nn.BatchNorm1d(align_dim))self.up_sample_conv.append(up_conv)# headpatch_num = seq_len // patch_strideself.n_vars = c_inself.individual = individuald_model = dims[-1]if use_multi_scale:self.head_nf = d_model * patch_numself.head = Flatten_Head(self.individual, self.n_vars, self.head_nf, target_window,head_dropout=head_dropout)else:if patch_num % pow(downsample_ratio,(self.num_stage - 1)) == 0:self.head_nf = d_model * patch_num // pow(downsample_ratio,(self.num_stage - 1))else:self.head_nf = d_model * (patch_num // pow(downsample_ratio, (self.num_stage - 1))+1)self.head = Flatten_Head(self.individual, self.n_vars, self.head_nf, target_window,head_dropout=head_dropout)def up_sample(self, x, upsample_ratio):_, _, _, N = x.shapereturn F.upsample(x, size=N, scale_factor=upsample_ratio, mode='bilinear')def forward_feature(self, x, te=None):B,M,L=x.shapex = x.unsqueeze(-2)for i in range(self.num_stage):B, M, D, N = x.shapex = x.reshape(B * M, D, N)if i==0:if self.patch_size != self.patch_stride:# stem layer paddingpad_len = self.patch_size - self.patch_stridepad = x[:,:,-1:].repeat(1,1,pad_len)x = torch.cat([x,pad],dim=-1)else:if N % self.downsample_ratio != 0:pad_len = self.downsample_ratio - (N % self.downsample_ratio)x = torch.cat([x, x[:, :, -pad_len:]],dim=-1)x = self.downsample_layers[i](x)_, D_, N_ = x.shapex = x.reshape(B, M, D_, N_)x = self.stages[i](x)return xdef forward(self, x, te=None):# instance normif self.revin:x = x.permute(0, 2, 1)x = self.revin_layer(x, 'norm')x = x.permute(0, 2, 1)x = self.forward_feature(x,te)x = self.head(x)# de-instance normif self.revin:x = x.permute(0, 2, 1)x = self.revin_layer(x, 'denorm')x = x.permute(0, 2, 1)return xdef structural_reparam(self):for m in self.modules():if hasattr(m, 'merge_kernel'):m.merge_kernel()class Model(nn.Module):def __init__(self, configs):super(Model, self).__init__()# hyper paramself.stem_ratio = configs.stem_ratioself.downsample_ratio = configs.downsample_ratioself.ffn_ratio = configs.ffn_ratioself.num_blocks = configs.num_blocksself.large_size = configs.large_sizeself.small_size = configs.small_sizeself.dims = configs.dimsself.dw_dims = configs.dw_dimsself.nvars = configs.enc_inself.small_kernel_merged = configs.small_kernel_mergedself.drop_backbone = configs.dropoutself.drop_head = configs.head_dropoutself.use_multi_scale = configs.use_multi_scaleself.revin = configs.revinself.affine = configs.affineself.subtract_last = configs.subtract_lastself.freq = configs.freqself.seq_len = configs.seq_lenself.c_in = self.nvars,self.individual = configs.individualself.target_window = configs.pred_lenself.kernel_size = configs.kernel_sizeself.patch_size = configs.patch_sizeself.patch_stride = configs.patch_stride# decompself.decomposition = configs.decompositionif self.decomposition:self.decomp_module = series_decomp(self.kernel_size)self.model_res = ModernTCN(patch_size=self.patch_size,patch_stride=self.patch_stride,stem_ratio=self.stem_ratio, downsample_ratio=self.downsample_ratio, ffn_ratio=self.ffn_ratio, num_blocks=self.num_blocks, large_size=self.large_size, small_size=self.small_size, dims=self.dims, dw_dims=self.dw_dims,nvars=self.nvars, small_kernel_merged=self.small_kernel_merged, backbone_dropout=self.drop_backbone, head_dropout=self.drop_head, use_multi_scale=self.use_multi_scale, revin=self.revin, affine=self.affine,subtract_last=self.subtract_last, freq=self.freq, seq_len=self.seq_len, c_in=self.c_in, individual=self.individual, target_window=self.target_window)self.model_trend = ModernTCN(patch_size=self.patch_size,patch_stride=self.patch_stride,stem_ratio=self.stem_ratio, downsample_ratio=self.downsample_ratio, ffn_ratio=self.ffn_ratio, num_blocks=self.num_blocks, large_size=self.large_size, small_size=self.small_size, dims=self.dims, dw_dims=self.dw_dims,nvars=self.nvars, small_kernel_merged=self.small_kernel_merged, backbone_dropout=self.drop_backbone, head_dropout=self.drop_head, use_multi_scale=self.use_multi_scale, revin=self.revin, affine=self.affine,subtract_last=self.subtract_last, freq=self.freq, seq_len=self.seq_len, c_in=self.c_in, individual=self.individual, target_window=self.target_window)else:self.model = ModernTCN(patch_size=self.patch_size,patch_stride=self.patch_stride,stem_ratio=self.stem_ratio, downsample_ratio=self.downsample_ratio, ffn_ratio=self.ffn_ratio, num_blocks=self.num_blocks, large_size=self.large_size, small_size=self.small_size, dims=self.dims, dw_dims=self.dw_dims,nvars=self.nvars, small_kernel_merged=self.small_kernel_merged, backbone_dropout=self.drop_backbone, head_dropout=self.drop_head, use_multi_scale=self.use_multi_scale, revin=self.revin, affine=self.affine,subtract_last=self.subtract_last, freq=self.freq, seq_len=self.seq_len, c_in=self.c_in, individual=self.individual, target_window=self.target_window)def forward(self, x, te=None):if self.decomposition:res_init, trend_init = self.decomp_module(x)res_init, trend_init = res_init.permute(0, 2, 1), trend_init.permute(0, 2, 1)if te is not None:te = te.permute(0, 2, 1)res = self.model_res(res_init, te)trend = self.model_trend(trend_init, te)x = res + trendx = x.permute(0, 2, 1)else:x = x.permute(0, 2, 1)if te is not None:te = te.permute(0, 2, 1)x = self.model(x, te)x = x.permute(0, 2, 1)return x
- LayerNorm:层归一化模块,对输入特征进行归一化处理,支持不同的数据格式.
- get_conv1d、get_bn、conv_bn、fuse_bn:卷积层、批量归一化层、卷积+批量归一化组合模块以及批量归一化融合函数,用于构建和优化卷积神经网络.
- ReparamLargeKernelConv:可重参数化的大核卷积模块,通过将大核卷积分解为小核卷积和大核卷积的组合,实现模型的结构重参数化,以提高模型的表达能力和效率.
- Block:模型的基本构建块,包含深度可分离卷积、归一化、前馈网络等组件,用于提取特征和进行特征变换.
- Stage:由多个Block组成的一个阶段,用于构建模型的不同层次结构.
- ModernTCN:主模型类,定义了模型的整体架构和前向传播过程,包括RevIN(可逆归一化)层、茎层、下采样层、多尺度融合层以及预测头部等组件.
总结
ModernTCN是一种现代化的纯卷积结构,它通过以下几个关键创新点,成功地将卷积技术重新带回时间序列分析的舞台:
- 现代化的卷积块设计:借鉴了Transformer中的架构设计,ModernTCN采用了深度卷积和逐点卷积的组合,以提高模型的表示能力。
- 时间序列相关的修改:为了更好地处理时间序列数据,ModernTCN引入了变量独立嵌入和跨变量依赖捕获机制。
- 扩大有效感受野(ERF):通过使用大核心尺寸而不是堆叠更多小核心,ModernTCN显著扩大了ERF,这有助于更好地捕捉时间序列数据中的长期依赖性。
相关文章:

机器学习周报-ModernTCN文献阅读
文章目录 摘要Abstract 0 提升有效感受野(ERF)1 相关知识1.1 标准卷积1.2 深度分离卷积(Depthwise Convolution,DWConv)1.3 逐点卷积(Pointwise Convolution,PWConv)1.4 组卷积(Grou…...

什么是网关路由
1.认识网关 网关(Gateway)和路由(Router)是两个相关但不同的概念。 一、网关(Gateway) 定义 网关是一个网络节点,它充当了不同网络之间的连接点。可以将其看作是一个网络的 “大门”…...

信号的产生、处理
一、信号的概念 信号是linux系统提供的一种,向指定进程发送特定事件的方式。收到信号的进程,要对信号做识别和处理。信号的产生是异步的,进程在工作过程中随时可能收到信号。 信号的种类分为以下这么多种(用指令kill -l查看&…...
在Linux中,zabbix如何监控脑裂?
在Linux中,zabbix监控脑裂主要涉及对高可用(HA)系统中可能发生的节点间通信中断或不一致状态的监控。脑裂问题通常发生在具有冗余节点的高可用系统中,如集群、HA系统或分布式数据库系统,当节点之间失去通信时ÿ…...

C++基础概念复习
前言 本篇文章作基础复习用,主要是在C学习中遇到的概念总结,后续会继续补充。如有不足,请前辈指出,万分感谢。 1、什么是封装,有何优点,在C中如何体现封装这一特性? 封装是面向对象编程&…...

Earth靶场
打开靶机后使用 arp-scan -l 查询靶机 ip 我们使用 nmap 进行 dns 解析 把这两条解析添加到hosts文件中去,这样我们才可以访问页面 这样网站就可以正常打开 扫描ip时候我们发现443是打开的,扫描第二个dns解析的443端口能扫描出来一个 txt 文件 dirsear…...
JavaScript 日期格式
在 JavaScript 中,日期格式可以通过 Date 对象进行操作和格式化。下面是一些常见的 JavaScript 日期格式及其示例: 1. ISO 8601 格式 ISO 8601 是一种标准的日期和时间表示方法,格式为 YYYY-MM-DDTHH:mm:ss.sssZ,例如: let date = new Date(); console.log(date.toISOS…...

django vue3实现大文件分段续传(断点续传)
前端环境准备及目录结构: npm create vue 并取名为big-file-upload-fontend 通过 npm i 安装以下内容"dependencies": {"axios": "^1.7.9","element-plus": "^2.9.1","js-sha256": "^0.11.0&quo…...

xiaoya小雅超集使用夸克网盘缓存教程
距离上一次小白写到关于小雅的教程已经过去了一周的时间,这段时间里,有很多小伙伴都想知道怎么用夸克网盘作为小雅的缓存。 今天这不就来了吗? 这段时间确实是比较忙,毕竟快过年了嘛,辛辛苦苦一整年,至少…...
计算机基础知识复习1.4
数据库事务 #开启一个事务 start transaction #执行SQL语句 SQL1 SQL2 .. #提交事务 commit 类加载器 启动类加载器:负责加载Java的核心库 用C编写,是JVM的一部分,启动类加载器无法被Java程序直接引用 扩展类加载器:是Java语…...

SpringMVC(三)请求
目录 一、RequestMapping注解 1.RequestMapping的属性 实例 1.在这里创建文件,命名为Test: 2.复现-返回一个页面: 创建test界面(随便写点什么): Test文件中编写: 编辑 运行: 3.不返回…...

Node.js应用程序遇到了内存溢出的问题
vue 项目 跑起来,一直报错,内存溢出 在 文件node_modules 里 .bin > vue-cli-service.cmd 在依赖包这个文件第一行加上这个 node --max-old-space-size102400 "%~dp0\..\vue\cli-service\bin\vue-cli-service.js" %* node --max-old-s…...

如何构建云原生时空大数据平台?
在现代企业中,随着对技术的依赖日益加深,空间数据的重要性愈发显著。它通过结合地理成分(如纬度、经度、地址、邮编等)与业务数据,成为解决复杂问题的重要工具。地理空间数据可从多种来源获取,例如卫星影像…...

二极管钳位电路分享
二极管钳位(I/O的过压/浪涌保护等) 如果我们的电路环境接收外部输入信号容易受到噪声影响,那我们必须采取过压和浪涌保护措施,其中一个方式就是二极管钳位保护。 像上图,从INPUT输入的电压被钳位在-Vf与VCCVf之间&…...

腾讯云智能结构化 OCR:驱动多行业数字化转型的核心引擎
在当今数字化时代的汹涌浪潮中,数据已跃升为企业发展的关键要素,其高效、精准的处理成为企业在激烈市场竞争中脱颖而出的核心竞争力。腾讯云智能结构化 OCR 技术凭借其前沿的科技架构与卓越的功能特性,宛如一颗璀璨的明星,在交通、…...
19.3、Unix Linux安全分析与防护
目录 UNIX/Linux系统的安全分析与防护UNIX/Linux系统安全增强技术UNIX/Linux安全模块应用参考国产操作系统安全分析与防护 UNIX/Linux系统的安全分析与防护 unix和linux操作系统分成三层,分别是硬件层,系统内核层以及应用层。Windows系统也是分成三层&a…...

JVM对象内存结构
1对象内存结构说明 注意: 如果对象为数组对象,在对象头后面有4字节存储数组长度; 1.1对象头 对象头分为Mark Word和Class Pointer两部分; Mark Word:对象基础信息 32位操作系统中占4字节,64位操作系统中占8…...
联邦学习和大模型相结合: 数据隐私,提升训练效率,架构优化
联邦学习和大模型相结合: 数据隐私,提升训练效率,架构优化 数据隐私保护方面 增强隐私保护机制:大模型通常需要大量的数据进行训练,而联邦学习可以在不共享原始数据的情况下进行模型训练。结合两者,可以设计出更强大的隐私保护机制,如利用联邦学习的加密技术和差分隐私…...

命令别名和命令历史
1.1命名别名 别名是命令的快捷方式。对于需要经常执行,并需要很长时间输入的长命令创建快捷方式很有用。 语法: alias 别名 ’ 原命令 [ 选项 ]…… [ 参数 ]……’ 1.2命令历史...
打造三甲医院人工智能矩阵新引擎(二):医学影像大模型篇--“火眼金睛”TransUNet
一、引言 1.1 研究背景与意义 在现代医疗领域,医学影像作为疾病诊断与治疗的关键依据,发挥着不可替代的作用。从传统的X射线、CT(计算机断层扫描)到MRI(磁共振成像)等先进技术,医学影像能够直观呈现人体内部结构,为医生提供丰富的诊断信息,涵盖疾病识别、病灶定位、…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...

微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...

DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
拉力测试cuda pytorch 把 4070显卡拉满
import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试,通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小,增大可提高计算复杂度duration: 测试持续时间(秒&…...

k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...

TSN交换机正在重构工业网络,PROFINET和EtherCAT会被取代吗?
在工业自动化持续演进的今天,通信网络的角色正变得愈发关键。 2025年6月6日,为期三天的华南国际工业博览会在深圳国际会展中心(宝安)圆满落幕。作为国内工业通信领域的技术型企业,光路科技(Fiberroad&…...

wpf在image控件上快速显示内存图像
wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像(比如分辨率3000*3000的图像)的办法,尤其是想把内存中的裸数据(只有图像的数据,不包…...
华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)
题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...
LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)
在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...
Python实现简单音频数据压缩与解压算法
Python实现简单音频数据压缩与解压算法 引言 在音频数据处理中,压缩算法是降低存储成本和传输效率的关键技术。Python作为一门灵活且功能强大的编程语言,提供了丰富的库和工具来实现音频数据的压缩与解压。本文将通过一个简单的音频数据压缩与解压算法…...