论文地址:https://arxiv.org/abs/2310.06625
代码地址:https://github.com/thuml/Time-Series-Library
本篇论文的背景是研究人员发现Transformer虽然在CV、NLP领域大展风采,然而在时间序列预测方面,效果反而没有一般的线性要好。论文的作者认为,Transformer的自注意力机制并非不适用于时间序列预测任务,实际上是Transformer的没有被“正确使用”。论文在不推翻或大改Transformer模型的基础上,简单地对Embedding步骤进行转置,使得预测效果变得更好了。
一、模型架构
iTransformer的模型架构只用了传统Transformer的Encoding部分。唯一与原始Transformer不一致的地方,就是Embedding层。
class DataEmbedding(nn.Module):
"""
原本Transformer部分的Embedding
"""
def __init__(self, c_in, d_model, embed_type='fixed', freq='h', dropout=0.1):
super(DataEmbedding, self).__init__()
self.value_embedding = TokenEmbedding(c_in=c_in, d_model=d_model)
self.position_embedding = PositionalEmbedding(d_model=d_model)
self.temporal_embedding = TemporalEmbedding(d_model=d_model, embed_type=embed_type,
freq=freq) if embed_type != 'timeF' else TimeFeatureEmbedding(
d_model=d_model, embed_type=embed_type, freq=freq)
self.dropout = nn.Dropout(p=dropout)
def forward(self, x, x_mark):
if x_mark is None:
x = self.value_embedding(x) + self.position_embedding(x)
else:
x = self.value_embedding(
x) + self.temporal_embedding(x_mark) + self.position_embedding(x)
return self.dropout(x)
class DataEmbedding_inverted(nn.Module):
"""
转置之后的Embedding
"""
def __init__(self, c_in, d_model, embed_type='fixed', freq='h', dropout=0.1):
super(DataEmbedding_inverted, self).__init__()
self.value_embedding = nn.Linear(c_in, d_model)
self.dropout = nn.Dropout(p=dropout)
def forward(self, x, x_mark):
x = x.permute(0, 2, 1)
# x: [Batch Variate Time]
if x_mark is None:
x = self.value_embedding(x)
else:
x = self.value_embedding(torch.cat([x, x_mark.permute(0, 2, 1)], 1))
# x: [Batch Variate d_model]
return self.dropout(x)
原本的Transformer是将时间序列上的各个节点做Embedding,无论是value Embedding还是Positional Embedding都是将每个位置上的值统一Embedding在一起。但是,无论是NLP还是CV任务,每个位置(行)上都没有“不同特征相关性”的问题,无论图片向量化还是将单词数字化,本质上都是“只有1个特征”。而论文作者认为,单一时间点上往往会包含多个具有不同物理意义的特征,讲这些特征Embedding到同一个Token,会消除这些不同特征的多元变量相关性。iTransformer的Embedding则是将整个输入都做转置,将每个特征的时间序列整个作为一个Embedding的Token,故而没有了原本的Positional Embedding。哪怕是将时间序列根据不同粒度分解(年月日小时分秒)作x_mark,也是将各个维度的变量作整个输入变为1个Embedding Token,不再使用Temporal Embedding。
class AttentionLayer(nn.Module):
def __init__(self, attention, d_model, n_heads, d_keys=None,
d_values=None):
super(AttentionLayer, self).__init__()
d_keys = d_keys or (d_model // n_heads)
d_values = d_values or (d_model // n_heads)
self.inner_attention = attention
self.query_projection = nn.Linear(d_model, d_keys * n_heads)
self.key_projection = nn.Linear(d_model, d_keys * n_heads)
self.value_projection = nn.Linear(d_model, d_values * n_heads)
self.out_projection = nn.Linear(d_values * n_heads, d_model)
self.n_heads = n_heads
def forward(self, queries, keys, values, attn_mask, tau=None, delta=None):
B, L, _ = queries.shape
_, S, _ = keys.shape
H = self.n_heads
queries = self.query_projection(queries).view(B, L, H, -1)
keys = self.key_projection(keys).view(B, S, H, -1)
values = self.value_projection(values).view(B, S, H, -1)
out, attn = self.inner_attention(
queries,
keys,
values,
attn_mask,
tau=tau,
delta=delta
)
out = out.view(B, L, -1)
return self.out_projection(out), attn
class FullAttention(nn.Module):
def __init__(self, mask_flag=True, factor=5, scale=None, attention_dropout=0.1, output_attention=False):
super(FullAttention, self).__init__()
self.scale = scale
self.mask_flag = mask_flag
self.output_attention = output_attention
self.dropout = nn.Dropout(attention_dropout)
def forward(self, queries, keys, values, attn_mask, tau=None, delta=None):
B, L, H, E = queries.shape
_, S, _, D = values.shape
scale = self.scale or 1. / sqrt(E)
scores = torch.einsum("blhe,bshe->bhls", queries, keys)
if self.mask_flag:
if attn_mask is None:
attn_mask = TriangularCausalMask(B, L, device=queries.device)
scores.masked_fill_(attn_mask.mask, -np.inf)
A = self.dropout(torch.softmax(scale * scores, dim=-1))
V = torch.einsum("bhls,bshd->blhd", A, values)
if self.output_attention:
return V.contiguous(), A
else:
return V.contiguous(), None
class EncoderLayer(nn.Module):
def __init__(self, attention, d_model, d_ff=None, dropout=0.1, activation="relu"):
super(EncoderLayer, self).__init__()
d_ff = d_ff or 4 * d_model
self.attention = attention
self.conv1 = nn.Conv1d(in_channels=d_model, out_channels=d_ff, kernel_size=1)
self.conv2 = nn.Conv1d(in_channels=d_ff, out_channels=d_model, kernel_size=1)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.dropout = nn.Dropout(dropout)
self.activation = F.relu if activation == "relu" else F.gelu
def forward(self, x, attn_mask=None, tau=None, delta=None):
new_x, attn = self.attention(
x, x, x,
attn_mask=attn_mask,
tau=tau, delta=delta
)
x = x + self.dropout(new_x)
y = x = self.norm1(x)
y = self.dropout(self.activation(self.conv1(y.transpose(-1, 1))))
y = self.dropout(self.conv2(y).transpose(-1, 1))
return self.norm2(x + y), attn
而iTransformer剩下的部分就完全和Transformer中的Encoder部分一样了。将X一式三份经过不同的线性层作为QKV,形状变成了[Batch Variate n_head d_model],将QK相乘缩放以当做相关性的度量,Softmax化后乘以Values。此处在论文的实验中有说明“解释性增强”。最后再接入一个线性层作为decoder,得到未来n个时间段的预测值。
二、转置对其他结构的影响
1、LayerNorm
在没有转置之前,原本Transforer中的LayerNorm会对相同时间戳的变量作归一化,使得变量之间的区分度下降。同时当各个变量的时间点没有对齐时,还会产生“交互噪声”。这种每个变量的归一化还会让模型拟合过于“平滑”,使得模型无法有效地区分不同的特征或模式从而造成过拟合。但是转置过后的LayerNorm中,归一化的是相同变量的整个序列。这种处理方式可以更好地解决非平稳问题,并且减少不同度量造成的差异。
2、前向网络
在未转置的Transformer中,提供的信息过于局部且可能有定位错误,而经过转置之后,可以提取时间序列的复杂表示,原文作者还认为,MLP神经元还会被教会表达各个时间序列的固有属性:如振幅,周期甚至频谱等。
3、自注意力机制
在未转置的Transformer中会将同一时间戳的不同变量Tokenize,于是自注意力机制中就是用来促进时间之间变量的依赖的。而转置过后,整个序列进行了Tokenize,相反地自注意力机制就开始促进不同变量之间的依赖了,有着高相关性的变量之间的V的权重更大,文中认为这一机制能够增强多元时间序列预测的可解释性。
三、相关实验
1、消融实验
论文作者将模型不同的部分替换或删除以此作实验。虽然本身并没有给出这里实验的代码,但是按照前文的理解,此处的Variate和Temporal应当是用输入的转置实现的。
注意第三行(传统Transformer的时序作Attention,变量之间用FFN)的效果显得最差,被论文作者认为是最为“结构使用失误”的一个证据。
2、序列表示分析
本文引入了2019年一篇论文中提出的CKA指标,用以表示神经网络层与层之间的相似性,CKA越高表示层与层之间越相似;同时表示对于时间序列任务而言,CKA相似度高能够获得更好的性能(https://arxiv.org/abs/2302.00861 ) 从实验结果来看,转置过后的Transformer系列的模型显示出了更好的预测结果与更高的CKA相似度(偏右下那块)。
3、多元变量相关性分析
论文作者的这张图认为神经网络浅层的score map(左下)和原始输入的时序的多元相关性(左上)相似,而训练到后来的深层的score map(右下)和未来序列(右上,如果我没理解错的话是并没有作为训练输入的验证集)类似了。这里我并没有看出来他所说的结果,而且没怎么看懂这张图……
4、能耗优化
文章最后还提出了一个“iTransformer在变量增多时会导致性能下降”的问题。对应这个问题的解决方法也给出了:每个batch随机选择其中的部分特征进行训练。随着每次选择特征的减少,途中左侧的MSE并没有明显下降,但是右侧的内存占用却明显减少了。
四、总结
本文反思了为什么Transformer模型在时间序列预测的问题上没有传统的线性模型效果好。文章作者认为,Transformer并非不适合于时间序列预测任务,而是以往的研究者没有“正确使用”Transformer。在没有大改Transformer模型的情况下,通过将输入Embedding的进行转置,使得自注意力层与前向层所提取的特征进行了互换,经过试验发现无论是预测效果还是模型可解释性都大大提高了,论文具有一定指导意义,这个转置的技巧也可以用在以往的模型之中。