时间序列机器学习论文:iTransformer

论文地址: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的进行转置,使得自注意力层与前向层所提取的特征进行了互换,经过试验发现无论是预测效果还是模型可解释性都大大提高了,论文具有一定指导意义,这个转置的技巧也可以用在以往的模型之中。

  • 8
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值