目录
- 摘要
- ABSTRACT
- 1 论文信息
- 1.1 论文标题
- 1.2 论文摘要
- 1.3 论文引言
- 1.4 论文贡献
- 2 论文模型
- 2.1 问题定义
- 2.2 可逆实例归一化(Reversible Instance Normalization)
- 2.3 混合通道块 (Mixed-Channels Block)
- 2.4 编码器(Decoder)
- 3 相关代码
摘要
本周阅读了一篇关于多变量时间序列的论文。论文模型 MCformer 结合了 CI 策略的数据扩展优势,同时减轻了通道间相关性遗忘的问题。MCformer 使用了一种称为混合通道模块的方法,以增强多变量时间序列数据集的表示能力,同时使用原生的 Transformer 编码器来建模序列的长期和跨变量特征。实验结果表明,在多变量时间序列预测任务中,混合通道策略优于纯 CI 策略。
ABSTRACT
This week, We read a paper on multivariate time series. The paper’s model, MCformer, combines the data expansion advantages of the CI strategy while alleviating the issue of inter-channel correlation forgetting. MCformer employs a method called the Mixed Channels module to enhance the representational capacity of multivariate time series datasets, and it uses a native Transformer encoder to model both long-term and cross-variable features of the sequence. Experimental results demonstrate that the Mixed Channels strategy outperforms the pure CI strategy in multivariate time series forecasting tasks.
1 论文信息
1.1 论文标题
MCformer: Multivariate Time Series Forecasting with Mixed-Channels Transformer
1.2 论文摘要
大规模物联网(IoT)设备会生成大量时间序列数据,迫使人们探索更有效的多变量时间序列预测模型。在以前的模型中,主要采用的是通道依赖(CD)策略(其中每个通道代表一个单变量序列)。当前最先进的(SOTA)模型主要依赖于通道独立(CI)策略。CI 策略将多通道序列视为独立的单通道序列,从而扩展数据集以提高泛化性能,并避免了破坏长期特征的通道间相关性。然而,CI 策略面临着通道间相关性遗忘的问题。为了解决这个问题,我们提出了一种创新的混合通道策略,该策略结合了 CI 策略的数据扩展优势,同时减轻了通道间相关性遗忘的问题。基于这种策略,论文引入了 MCformer,这是一种具有混合通道特征的多变量时间序列预测模型。该模型融合了特定数量的通道,利用注意力机制在建模长期特征时有效捕捉通道间的相关信息。实验结果表明,在多变量时间序列预测任务中,混合通道策略优于纯 CI 策略。
1.3 论文引言
随着 IoT 设备在气象学、交通和电力等领域的广泛应用,设备数量的增加也导致大量时间序列数据的生成。这些数据可以用于决策、资源分配以及预测未来趋势,从而提高物联网系统的效率和可靠性。源自 IoT 设备的时间序列预测任务旨在根据历史数据预测未来状态。鉴于物联网数据通常具有非线性、快速采样和多通道等特性,这一任务存在一定的挑战。
由于物联网设备生成的时间序列数据通常具有较长的采样间隔和多采样通道,在处理这些设备的多变量时间序列时,必须考虑长序列建模和多个通道之间的复杂相互关系。鉴于 Transformer 在自然语言处理领域中展示的长序列建模的杰出能力,这种能力在时间序列预测任务中也至关重要,促使了诸如 LogTrans、Informer、Reformer、Autoformer、FEDformer、ScaleFormer、Pyraformer、FPPformer等模型的出现。这些模型在长时间序列建模方面取得了显著进展。
近年来,一些研究将重点转向多变量时间序列的挑战,例如 Crossformer 和 SageFormer 等模型。这些模型在所有通道上进行学习,特别关注捕捉多个变量之间的依赖关系。所有这些模型都可以被视为通道依赖(CD)策略模型,其中将单变量序列视为一个通道。这种方法将多变量数据视为一个整体输入,并允许模型学习通道之间的相关性,如下图1所示。
然而,这些 CD 策略模型也存在局限性。DLinear 以其简单的架构超过了现有模型。PatchTST 引入了一种通道独立(CI)策略模型,进一步提升了最先进的水平(SOTA)。CI 策略将多变量时间序列数据视为独立的单变量序列,如下图1所示。CI策略的成功引起了人们对 CD 和 CI 策略对模型影响的关注,可见于 PRReg、PETformer、CSformer、itransformer 等模型。随后,TiDE(一种基于MLP的CI模型) 不仅表现与PatchTST相似,而且在时空效率上表现出色。PETformer 的研究发现,通道独立优于通道依赖,可能是因为多变量特征会干扰长序列特征的提取。这一结果与直觉相悖,因为在深度学习中,更多的信息通常会提高模型的泛化能力。
总的来说,现有 SOTA 模型主要依赖 CI 策略有两个主要原因:首先,CI 策略可以扩展数据集以提高模型的泛化性能;其次,CI 策略可以避免因通道间相关信息而破坏长期特征信息,正如PETformer 所展示的。然而,CI 策略也有缺点,因为它倾向于忽略通道间的特征信息。在通道数量较多的情况下,可能会出现通道间相关性遗忘的问题,类似于 RNN 中的长期信息遗忘。在这种背景下,论文提出了一种混合通道策略。该策略保留了 CI 策略在数据集扩展方面的优势,同时通过选择性地混合有限数量的通道,有效地缓解了长期特征信息的破坏和过拟合问题,也解决了通道间相关性遗忘的问题。
上图1展示了 混合通道策略与通道独立(CI) 和 通道依赖(CD) 策略的区别。CD 策略通过考虑所有通道的历史数据来预测未来值,而 CI 策略则在预测未来值时不考虑其他通道的历史数据,且所有通道共享模型参数。与通道依赖策略和通道独立策略相比,混合通道策略保持了通道独立策略的优势,同时混合了有限数量的随机选择的通道,以增强模型捕捉通道间相关信息的能力。
基于上述策略,论文提出了一种具有混合通道特征的多通道时间序列预测模型。图2提供了该模型的概述。具体来说,论文的模型首先使用 CI 策略扩展数据,然后混合特定数量的通道,并允许注意力机制在建模长期特征信息时有效捕捉通道间的相关信息。最后,将编码器的结果还原以获得所有通道的预测值。
1.4 论文贡献
① 提出了一种混合通道策略,旨在最小化通道特征在 CD 策略下破坏长期信息的缺点,同时保留CI 策略下扩展数据集的优势。这使模型能够更有效地学习通道间的依赖信息。
② 基于混合通道策略,提出了一种具有混合通道特征的多变量时间序列预测模型。通过使用混合通道块,模型扩展了数据集,并通过混合方法整合了通道间的依赖信息。
2 论文模型
2.1 问题定义
在多变量时间序列预测任务中,历史观测值表示为
X
=
x
1
,
x
2
,
.
.
.
,
x
t
∈
R
t
×
M
X={x_{1}, x_{2}, ..., x_{t}} \in \R^{t \times M}
X=x1,x2,...,xt∈Rt×M,其中 t 是时间步,M 是变量的数量。每个时间步 t 的观测值表示为一个 M 维向量:
x
t
=
[
x
t
1
,
x
t
2
,
.
.
.
,
x
t
M
]
⊤
x_{t}=[x_{t}^{1},x_{t}^{2},...,x_{t}^{M}]^{\top}
xt=[xt1,xt2,...,xtM]⊤。论文的目标是基于过去的观测值预测未来时间步的多变量观测值。这个问题可以形式化为:给定时间步 t 之前的观测序列
{
x
1
,
x
2
,
.
.
.
,
x
t
}
\{x_{1},x_{2},...,x_{t}\}
{x1,x2,...,xt},预测时间步 t+1 到 t+h 的多变量观测序列
{
x
t
+
1
,
.
.
.
,
x
t
+
h
}
\{x_{t+1},...,x_{t+h}\}
{xt+1,...,xt+h},其中 h 是要预测的时间步数。论文将混合通道模块 (Mixed-Channels Block) 集成到基础 Transformer 编码器中,以扩展数据集并融合通道间的依赖信息。论文的 MCformer 模型的架构如下图3所示。
2.2 可逆实例归一化(Reversible Instance Normalization)
引入 可逆实例归一化(Reversible Instance Normalization,RevIN) 旨在解决训练和测试数据之间非均匀时间分布的问题,这通常被称为分布偏移。在混合通道模块之前,应用实例归一化来归一化每个通道的数据。对于每个实例 x t i x_{t}^{i} xti 的单个通道 x i = [ x 1 i , x 2 i , . . . , x t i ] x^{i}=[x_{1}^{i},x_{2}^{i},...,x_{t}^{i}] xi=[x1i,x2i,...,xti],计算其均值和标准差。在获得预测结果后,这些非平稳性信息组件会被加回到预测值中。
R e v I N ( x i ) = { γ i x i − M e a n ( x i ) V a r ( x i ) + ε } , i = 1 , 2 , . . . , M ( 1 ) RevIN(x^{i})=\{\gamma_{i} \frac {x^{i}-Mean(x^{i}) }{\sqrt{Var}(x^{i})+\varepsilon} \}, i=1,2,...,M\ \ \ \ \ \ (1) RevIN(xi)={γiVar(xi)+εxi−Mean(xi)},i=1,2,...,M (1)
2.3 混合通道块 (Mixed-Channels Block)
论文引入了一种称为混合通道模块的方法,以增强多变量时间序列数据集的表示能力。
Flatten: 论文采用通道独立(CI)策略,将来自 M 个通道的数据展平。对于给定的样本 X,展平后得到 X F = F l a t t e n ( X ) ∈ R t M × 1 X_F=Flatten(X) \in \R ^{tM \times1} XF=Flatten(X)∈RtM×1。然后将展平后的 X F X_F XF 视为M个独立的样本。
Mixed Channels: Mixed-Channels Block是在 Flatten 之后将不同通道的数据进行组合,通过以下步骤执行混合通道操作:
-
计算区间大小: 首先计算区间大小 ⌊ M m ⌋ \lfloor \frac M m \rfloor ⌊mM⌋,其中 m 是要混合的通道数。
-
混合通道操作: 对于给定的时间步 t,从目标通道开始,以区间步长堆叠其他所有通道来形成 U i ∈ R t × m U^i \in \R^{t \times m} Ui∈Rt×m。具体而言,混合通道模块的输出定义为:
U i = M i x e d C h a n n e l s ( x i , m ) = [ s t a c k ( x i , C 1 , C 2 , . . . , C m ) ] ( 2 ) U^{i}=MixedChannels(x^{i},m)=[stack(x^{i},C^{1},C^{2},...,C^{m})]\ \ \ \ \ \ (2) Ui=MixedChannels(xi,m)=[stack(xi,C1,C2,...,Cm)] (2)
其中, C i C^{i} Ci 表示在第 i 个间隔处获取的第 i 个通道,1 ≤ i ≤ m。通过引入混合通道模块,其目的是增加输入数据的表示能力,加入多通道信息,以更好地捕捉时间序列数据的特征。
补丁和投影: 在当前的研究中,已经观察到,相比于使用时间点数据作为输入,使用补丁可以更好地捕捉局部信息,并且捕捉到更丰富的变量间依赖关系。因此,利用补丁在混合通道后聚合序列,并采用单层 MLP 来投影通道依赖性以及相邻的时间依赖性。表示为:
P i = P r o j e c t i o n ( P a t c h ( U i ) ) ( 3 ) P^{i}=Projection(Patch(U^{i}))\ \ \ \ \ \ (3) Pi=Projection(Patch(Ui)) (3)
这里
P
i
∈
R
P
×
N
P^{i}\in\R^{P \times N}
Pi∈RP×N ,其中P表示投影后的长度,补丁的数量为 N,
N
=
⌊
(
L
−
p
)
S
⌋
+
2
N=\lfloor \frac{(L-p)}{S} \rfloor+ 2
N=⌊S(L−p)⌋+2,p 表示补丁的长度,S 表示步长。使用补丁的方法可以保留时间依赖性和通道间依赖性。它不仅保留了 Transformer 输入的标记,还进一步增加了预测窗口的大小,同时保持了时间依赖性。图 4 说明了在时间序列任务中使用补丁的方法。
2.4 编码器(Decoder)
论文采用原生的 Transformer 编码器来建模序列的长期和跨变量特征,这类似于 PatchTST 中使用的方法。由于 Transformer 没有明确建模序列的顺序,为了提供位置信息,论文使用了可学习的加权位置编码 W p o s ∈ R P × N W_{pos} \in \R^{P \times N} Wpos∈RP×N。位置编码被添加到输入序列的嵌入表示 x i n i = P i + W p o s x_{in}^{i}=P^{i}+W_{pos} xini=Pi+Wpos 上。通过这种方式,模型可以区分不同位置的元素。
编码器由多个具有相同结构的层组成,每一层包括两个子层:多头自注意力层和前馈神经网络层。多头自注意力层是编码器的第一个子层。在这个层中,输入序列中的每个元素都可以与其他所有元素进行交互,而不仅仅是与其相邻的元素。这里使用三个权重矩阵 W Q W^{Q} WQ、 W K W^{K} WK 和 W V W^{V} WV 来计算 Q i = ( x i n i ) T W Q Q^{i}=(x_{in}^{i})^{T}W^{Q} Qi=(xini)TWQ、 K i = ( x i n i ) T W K K^{i}=(x_{in}^{i})^{T}W^{K} Ki=(xini)TWK 和 V i = ( x i n i ) T W V V^{i}=(x_{in}^{i})^{T}W^{V} Vi=(xini)TWV。这是通过计算注意力分数实现的,每个元素会接收到一组权重,指示其对其他元素的重要性。通过使用多头注意力机制,模型可以学习不同方面的注意力,从而更好地捕捉序列中的信息:
A t t e n t i o n ( Q i , K i , V i ) = s o f t m a x ( Q i ( K i ) T d k ) V i ( 4 ) Attention(Q^{i},K^{i},V^{i})=softmax(\frac{Q^{i}(K^{i})^{T}}{\sqrt {d_{k}}})V^{i}\ \ \ \ \ \ (4) Attention(Qi,Ki,Vi)=softmax(dkQi(Ki)T)Vi (4)
在多头自注意力层之后是前馈神经网络层。每个位置的表示通过一个全连接的前馈神经网络进一步处理。这个网络通常包括两个全连接层,其输出被添加到输入中,形成一个残差连接。这有助于在训练过程中缓解梯度消失问题。
在每个子层的输入和输出之间,使用残差连接和层归一化。残差连接的引入使得模型更容易学习恒等映射,从而有助于深度网络的训练。此外,每个子层的输出还经过层归一化,以稳定训练过程。
3 相关代码
Transformer:
class PositionalEncoding(nn.Module):
def __init__(self, outfea, max_len):
super(PositionalEncoding, self).__init__()
# 计算位置编码
pe = torch.zeros(max_len, outfea)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
div_term = torch.exp(torch.arange(0, outfea, 2).float() * (-torch.log(
torch.tensor(10000.0)) / outfea))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0)
self.register_buffer('pe', pe)
def forward(self, x):
x = x + x + self.pe[:, :x.size(1)]
return x
class MultiHeadAttention(nn.Module):
def __init__(self, outfea, num_heads):
super(MultiHeadAttention, self).__init__()
self.num_heads = num_heads
self.outfea = outfea
assert outfea % num_heads == 0
self.depth = outfea // num_heads
# 查询、键和值的线性投影
self.query_linear = nn.Linear(outfea, outfea)
self.key_linear = nn.Linear(outfea, outfea)
self.value_linear = nn.Linear(outfea, outfea)
# 输出线性投影
self.output_linear = nn.Linear(outfea, outfea)
def split_heads(self, x):
batch_size, seq_length, outfea = x.size()
return x.view(batch_size, seq_length, self.num_heads, self.depth).transpose(1, 2)
def forward(self, query, key, value, mask=None):
# 线性投影
query = self.query_linear(query) # [batch_size, seq_length, outfea]
key = self.key_linear(key)
value = self.value_linear(value)
# 分割头部
query = self.split_heads(query) # [batch_size, num_heads, seq_length, depth]
key = self.split_heads(key)
value = self.split_heads(value)
# 缩放点积注意力
scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(
self.depth) # [batch_size, num_heads, seq_length, seq_length]
# 如果提供了掩码,则应用掩码
if mask is not None:
scores += scores.masked_fill(mask == 0, -1e9)
# 计算注意力权重并应用softmax
attention_weights = torch.softmax(scores, dim=-1)
# 应用注意力到值
attention_output = torch.matmul(attention_weights, value)
# 合并头部
batch_size, _, seq_length, d_k = attention_output.size()
attention_output = attention_output.transpose(1, 2).contiguous().view(batch_size,
seq_length, self.outfea)
# 线性投影
attention_output = self.output_linear(attention_output)
return attention_output
class FeedForward(nn.Module):
def __init__(self, outfea, hiddenfea):
super(FeedForward, self).__init__()
self.linear1 = nn.Linear(outfea, hiddenfea)
self.linear2 = nn.Linear(hiddenfea, outfea)
self.relu = nn.ReLU()
def forward(self, x):
# 线性变换1
x = self.linear1(x)
# 非线性激活
x = self.relu(x)
# 线性变换2
x = self.linear2(x)
return x
class EncoderLayer(nn.Module):
def __init__(self, outfea, num_heads, hiddenfea, dropout):
super(EncoderLayer, self).__init__()
self.self_attention = MultiHeadAttention(outfea, num_heads)
self.feed_forward = FeedForward(outfea, hiddenfea)
self.norm1 = nn.LayerNorm(outfea)
self.norm2 = nn.LayerNorm(outfea)
self.dropout = nn.Dropout(dropout)
def forward(self, x, mask):
# 自注意力层
attention_output = self.self_attention(x, x,
x, mask)
attention_output = self.dropout(attention_output)
x = x + attention_output
x = self.norm1(x)
# 前馈层
feed_forward_output = self.feed_forward(x)
feed_forward_output = self.dropout(feed_forward_output)
x = x + feed_forward_output
x = self.norm2(x)
return x
class DecoderLayer(nn.Module):
def __init__(self, outfea, num_heads, hiddenfea, dropout):
super(DecoderLayer, self).__init__()
self.masked_self_attention = MultiHeadAttention(outfea, num_heads)
self.enc_dec_attention = MultiHeadAttention(outfea, num_heads)
self.feed_forward = FeedForward(outfea, hiddenfea)
self.norm1 = nn.LayerNorm(outfea)
self.norm2 = nn.LayerNorm(outfea)
self.norm3 = nn.LayerNorm(outfea)
self.dropout = nn.Dropout(dropout)
def forward(self, x, encoder_output, src_mask, tgt_mask):
# 掩码的自注意力层
self_attention_output = self.masked_self_attention(x, x, x, tgt_mask)
self_attention_output = self.dropout(self_attention_output)
x = x + self_attention_output
x = self.norm1(x)
# 编码器-解码器注意力层
enc_dec_attention_output = self.enc_dec_attention(x, encoder_output,
encoder_output, src_mask)
enc_dec_attention_output = self.dropout(enc_dec_attention_output)
x = x + enc_dec_attention_output
x = self.norm2(x)
# 前馈层
feed_forward_output = self.feed_forward(x)
feed_forward_output = self.dropout(feed_forward_output)
x = x + feed_forward_output
x = self.norm3(x)
return x
class Transformer(nn.Module):
def __init__(self, outfea, num_heads, num_layers, hiddenfea,
max_len, dropout):
super(Transformer, self).__init__()
# 定义位置编码层
self.positional_encoding = PositionalEncoding(outfea, max_len)
# 定义编码器和解码器的多层堆叠
self.encoder_layers = nn.ModuleList([EncoderLayer(outfea, num_heads, hiddenfea, dropout)
for i in range(num_layers)])
self.decoder_layers = nn.ModuleList([DecoderLayer(outfea, num_heads, hiddenfea, dropout)
for i in range(num_layers)])
# 定义线性层
self.linear = nn.Linear(outfea, outfea)
self.dropout = nn.Dropout(dropout)
# 生成掩码
def generate_mask(self, src, tgt):
src_mask = (src != 0).unsqueeze(1).unsqueeze(2)
tgt_mask = (tgt != 0).unsqueeze(1).unsqueeze(3)
seq_length = tgt.size(1)
nopeak_mask = (1 - torch.triu(torch.ones(1, seq_length, seq_length), diagonal=1)).bool()
tgt_mask = tgt_mask & nopeak_mask
return src_mask, tgt_mask
# 前向传播
def forward(self, src, tgt):
src_mask, tgt_mask = self.generate_mask(src, tgt)
# 编码器输入的词嵌入和位置编码
encoder_embedding = self.encoder_embedding(src)
en_positional_encoding = self.positional_encoding(encoder_embedding)
src_embedded = self.dropout(en_positional_encoding)
# 解码器输入的词嵌入和位置编码
decoder_embedding = self.decoder_embedding(tgt)
de_positional_encoding = self.positional_encoding(decoder_embedding)
tgt_embedded = self.dropout(de_positional_encoding)
enc_output = src_embedded
for enc_layer in self.encoder_layers:
enc_output = enc_layer(enc_output, src_mask)
dec_output = tgt_embedded
for dec_layer in self.decoder_layers:
dec_output = dec_layer(dec_output, enc_output, src_mask, tgt_mask)
output = self.linear(dec_output)
return output