Transformer的理解与代码实现—Autoformer文献阅读

摘要

本周一是对Transformer的相关学习,如手推transformer模型内部运算机制,进一步加深对transformer的理解;与详细的代码复现了Transformer,从代码实现角度再次理解整体框架与如何具体的简单实现,对编码层,解码层,输出层分别分模块实现。
二是阅读文献Autoformer: 基于深度分解架构和自相关机制的长期序列预测模型,Autoformer革新了Transformer模型,其有两大独特创新点,一个是深度分解架构(Decomposition Architecture),另一个是自相关机制(Auto-Correlation Mechanism);该文旨在高效准确的进行长时间时序预测(O>>I)。

一. 关于Transformer的相关学习

最近VIT / Swin Transformer 的论文引用与应用都十分广泛,所以复习一下一切的源头Transformer的原理,其中编码器与解码器的详细理解与代码实现又十分重要;对以下一步理解与掌握Swin Transformer等是关键。

1.1 手推transformer

如今transformer的家族成员众多,极大的丰富了NLP领域。首先在transformer出现之前最好的机器翻译模型是带注意力机制的seq2seq模型,但是对于seq2seq模型来说,其中最大的问题是它是循环运算的,这样便不利于保持太长的句子信息,而且也不利于平行计算;然而归根原由,其实是encode与decode中的RNN造成的。既然如此,不如便扔掉RNN,而只保留attention部分。如下图:

在这里插入图片描述
下面我们便开始对Attention is all you need来展开Transformer的具体过程计算。
在这里插入图片描述
在执行翻译任务时,Attention应该做到对“我”做最大的注意力,而今天也会反复出现的self-attention,其实是对input内部大家之间的关系,比如在上面句子中,“小蛮”与“她”指的是同一个人,所以其间关系注意线最粗(代表权重最大),“成都”与“火锅”之间的权重也会比较大。这些便解决了RNN系列模型都不太擅于30个词长以上的句子,而对于Attention 来说,不管中间的句子有多长,都能得到“小蛮”与“她”其之间的注意力关系。

正式开始过一遍Transformer的运算

首先假想一个翻译任务,源语言中文的词汇量只有这五个,source vocab:{“我”,“很”,“好”,“不”,“你”},目标词汇量只有这七个,target vocab:{“bos”,“I”,“am”,“fine”,“you”,“you”,“very”,“EOS”}

目标训练一个Transformer实现翻译 :Source:“我很好” ——Target : “I am fine”.

下图中整体上左边是encode,右边部是decode。但是与原论文中模型有点不一样,比如此处运算中encode和decode只算一层,没有像论文中运算多层叠加(不同的层数是解读不同维度的信息)。

在这里插入图片描述

1.1.1 Encoder部分

第一步进入 positional embeeding :

首先encoder下方(Input Embedding)从鲜红色阴影部分来计算,先是随机生成词向量(但维度只有四维,而论文中是512维),从而得到input embedding ,然后再加上position embedding ,这里之所以要加上位置编码,原因是RNN是循环的,是一个词一个词进入的;但是Transformer是一句话一起进入,所以transformer没有办法知道词序信息。position embedding 的生成是利用cos与sin去区分奇数位与偶数位(具体原理涉及傅里叶变换),简单的说,其实就是需要加入编码以区分序位同时又不会影响词向量空间。

下一步进入Mulit-Head Attention:

在论文中是运用8个Head,此处仅用2个Head,不同的head表示从不同的维度去提取特征(多头处理,分为多组的Q,K,V,每组单独计算得到一个attention;最后把每组的attention向量拼接起来,并进入一个不带bias的FFN,从而得到一个最终的向量)。
在这里插入图片描述
将positional embedding 分拆成红蓝两部分,再分别以value,key,query的三种,分别输入Head中,下面就进一步打开Head的黑盒子:
在这里插入图片描述在Head 中,可以发现V,K,Q,先是分别经过自己的linear层,再到Scaled dot-product Attention :

在这里插入图片描述
在上面一步中是将query’,key’ 相乘得到一个33的矩阵,再经过sclae(这里除于根号Dk,即512维被分成多少个Head,每一个head里有k维),sofmax,便得到一个绿色的attention了,再将其与value相乘,得到head1的最终self-attention 32 的矩阵;

同理Head2也是如此。

下一步就是将两个head 得到的self-attention,再经过concat(拼接—), linear,最终multi head attention output 输出。

在这里插入图片描述
继续下一步便是ADD&Norm层了(残差连接以保持位置信息流入深层,也有利于训练):
在这里插入图片描述
下面继续来到Feed Forward(FFN) ,与再次有一个ADD&Norm :
在这里插入图片描述
最终便是得到了encoder output,也是decode的输入。

1.1.2 Decoder部分

(训练阶段)进入decoded的output Embeeding ,其过程如下图所示:

在这里插入图片描述
下一步就是position Embeeding 进入Masked Mulit-Head attention ,这一步大致上与前面的Mulit Head attention 相同,只有如下图中query*key的结果Mask一部分(填上很小的数值0):
在这里插入图片描述
其中mask 掉一部分的原因是在执行任务的时候Transformer是平行计算的,在翻译过程中不能把下一个词是什么,需要把下一个才会生成的词遮掉(需要考虑因果关系)。

Head2与之同理计算;继续执行 Concat ,linear;得到矩阵 ,也就是Mulit-Head attention output 。如下图:

在这里插入图片描述
再次将经过ADD& Norm 得到一个结果紫色矩阵。如下图:
在这里插入图片描述
下面进入与encoder 部分相似的阶段模块(除了输入不同):

value与key是来自encoder的output的,而query是来自Decoder的
其中真正发生中英文matching 的地方就是在这里的Attention,最后得到decoder output 。

在这里插入图片描述

最后是经过最终的Linear与Softmax,从而得到最终的输出是概率维度,与词汇表是一致的结果。
在这里插入图片描述
理论上经过训练的模型是会表现出词表对应得最高概率的。

参考:手推过程来自B站Transformer手推

1.2 Transformer的理解与实现(Pytorch)

1.2.1 seq2seq模型

encoder+attention+decoder

如下基础结构模型有CNN与RNN与Transformer三类,各自的特点:
CNN:1.权重共享:平移不变性,可并行计算;2. 滑动窗口,局部关联性建模,依靠多层堆积来获取长期建模;3.对相对位置敏感,对绝对位置不敏感(图片颠倒输入不影响)。

RNN:是依次有序递归建模,对顺序敏感,串行计算耗时,长期建模能力不强,对位置十分敏感。

Transformer:无有序假设,需要通过位置编码来反映位置变化对于特征的影响;对绝对位置不敏感。

1.2.2 Transformer的简单实现

首先是从整体到局部的实现思路,然后再去细分每一步的代码,其次明确数据流的形状与走向过程是尤为重要的。下面就是transformer的整体网络结构,分为三个部分:编码层,解码层,输出层。

1.2.2.1 整体结构
##1.Transformer的整体网络结构
class Transformer(nn.Module):
    def __init__(self):
        super(Transformer, self).__init__()
        self.encoder = Encoder()  ## 编码层
        self.decoder = Decoder()  ## 解码层
        self.projection = nn.Linear(d_model, tgt_vocab_size, bias=False)
        ## 输出层 d_model(512) 是我们解码层每个token输出的维度大小,之后会做一个 tgt_vocab_size 大小的softmax,去看当前时刻词表出现的概率最大
    def forward(self, enc_inputs, dec_inputs):
        ## 这里有两个数据进行输入,一个是enc_inputs 形状为[batch_size, src_len],主要是作为编码段的输入,一个dec_inputs,形状为[batch_size, tgt_len],主要是作为解码端的输入

        ## enc_inputs作为输入 形状为[batch_size, src_len],输出由自己的函数内部指定,想要什么指定输出什么,可以是全部tokens的输出,可以是特定每一层的输出;也可以是中间某些参数的输出;
        ## enc_outputs就是主要的输出,enc_self_attns是QK转置相乘之后softmax之后的矩阵值,代表的是每个单词和其他单词相关性(也就是attention的可视化过程输出);
        enc_outputs, enc_self_attns = self.encoder(enc_inputs)

        ## dec_outputs 是decoder主要输出,用于后续的linear映射; dec_self_attns类比于enc_self_attns 是查看每个单词对decoder中输入的其余单词的相关性;dec_enc_attns是decoder中每个单词对encoder中每个单词的相关性;
        dec_outputs, dec_self_attns, dec_enc_attns = self.decoder(dec_inputs, enc_inputs, enc_outputs)

        ## dec_outputs做映射到词表大小(dec_outputs再流经这个projection层,做了一个映射词表的操作。)
        dec_logits = self.projection(dec_outputs) # dec_logits : [batch_size x src_vocab_size x tgt_vocab_size]
        return dec_logits.view(-1, dec_logits.size(-1)), enc_self_attns, dec_self_attns, dec_enc_attns
1.2.2.2 Encoder编码器的代码实现

Encoder 部分包含三个部分:词向量embedding,位置编码部分,注意力层,以及后续的前馈神经网络。

class Encoder(nn.Module):
    def __init__(self):
        super(Encoder, self).__init__()
        self.src_emb = nn.Embedding(src_vocab_size, d_model)  ## 这个其实就是去定义生成一个矩阵,大小是 src_vocab_size * d_model
        self.pos_emb = PositionalEncoding(d_model) ## 位置编码情况,这里是固定的正余弦函数,也可以使用类似词向量的nn.Embedding获得一个可以更新学习的位置编码
        self.layers = nn.ModuleList([EncoderLayer() for _ in range(n_layers)]) ## 使用ModuleList对多个encoder进行堆叠,因为后续的encoder并没有使用词向量和位置编码,所以抽离出来;
        ##上面的这个layers层是由前馈神经网络与自注意力层组成的网络,并堆叠多个encoder.       
    #实现函数
    def forward(self, enc_inputs):
        ## 这里我们的 enc_inputs 形状是: [batch_size x source_len]

        ## 下面这个代码通过src_emb,进行索引定位,enc_outputs输出形状是[batch_size, src_len, d_model]
        ## src_emb,通过索引定位把把对于的数字的词向量提取出来,最后形成一个矩阵,该矩阵的形状是[batch_size, src_len, d_model]
        enc_outputs = self.src_emb(enc_inputs)

        ## 这里就是位置编码,把两者相加放入到了这个函数里面,从这里可以去看一下位置编码函数的实现;3.
        enc_outputs = self.pos_emb(enc_outputs.transpose(0, 1)).transpose(0, 1)

        ##get_attn_pad_mask是为了得到句子中pad的位置信息,给到模型后面,在计算自注意力和交互注意力的时候去掉pad符号的影响,去看一下这个函数 4.
        enc_self_attn_mask = get_attn_pad_mask(enc_inputs, enc_inputs)
        enc_self_attns = []
        for layer in self.layers:
            ## 去看EncoderLayer 层函数 5.
            enc_outputs, enc_self_attn = layer(enc_outputs, enc_self_attn_mask)
            enc_self_attns.append(enc_self_attn)
        return enc_outputs, enc_self_attns


1.2.2.3 PositionalEncoding 代码实现

在这里插入图片描述从公式角度出发就很容易实现这一种固定格式的位置编码信息:

注意pos决定着行,i变量决定着列。POS代表的是每个单词在整个句子(seq)中的索引,从0—511上的每一个位置,512是整个句子的最大长度(max_len);与2i对应的维度512(dmodel)要区分。

class PositionalEncoding(nn.Module):
    def __init__(self, d_model, dropout=0.1, max_len=5000):
        super(PositionalEncoding, self).__init__()

        ## 位置编码的实现其实很简单,直接对照着公式去敲代码就可以,下面这个代码只是其中一种实现方式;
        ## 从理解来讲,需要注意的就是偶数和奇数在公式上有一个共同部分,我们使用log函数把次方拿下来,方便计算;
        ## pos代表的是单词在句子中的索引,这点需要注意;比如max_len是128个,那么索引就是从012...,127(pos决定着行,i变量决定着列)
        ##假设我的demodel是5122i那个符号中i从0取到了255,那么2i对应取值就是0,2,4...510
        self.dropout = nn.Dropout(p=dropout)

        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
        pe[:, 0::2] = torch.sin(position * div_term)## 这里需要注意的是pe[:, 0::2]这个用法,就是从0开始到最后面,补长为2,其实代表的就是偶数位置
        pe[:, 1::2] = torch.cos(position * div_term)##这里需要注意的是pe[:, 1::2]这个用法,就是从1开始到最后面,补长为2,其实代表的就是奇数位置
        ## 上面代码获取之后得到的pe:[max_len*d_model]

        ## 下面这个代码之后,我们得到的pe形状是:[max_len*1*d_model]
        pe = pe.unsqueeze(0).transpose(0, 1)

        self.register_buffer('pe', pe)  ## 定一个缓冲区,因为pe是一个常规参数,简单理解为这个参数不更新就可以。

    def forward(self, x):
        """
        x: [seq_len, batch_size, d_model]
        """
        x = x + self.pe[:x.size(0), :]
        return self.dropout(x)

1.2.2.4 get_attn_pad_mask的细节

encoder中的get_attn_pad_mask的细节与实现,这里实现的是对自身的pad,此处在上篇的手推transformer中并没有提及对自身的pad,以及为什么需要去做这样一个自身的pad,由于实际中每一个句子的真实长度并不一致,所以要统一一个max_len ,就会需要pad。

那又为什么需要告诉后面的模型那些位置被pad,这里又涉及到在做Attention中softmax时,它不应该包含被pad的那一部分,那样会对原来的结果产生误差。

## 4. get_attn_pad_mask

## 比如说,我现在的句子长度是5,在后面注意力机制的部分,我们在计算出来QK转置除以根号之后,softmax之前,我们得到的形状
## len_input * len*input  代表每个单词对其余包含自己的单词的影响力

## 所以这里我需要有一个同等大小形状的矩阵,告诉我哪个位置是PAD部分,之后在计算计算softmax之前会把这里置为无穷大;

## 一定需要注意的是这里得到的矩阵形状是batch_size x len_q x len_k,我们是对k中的pad符号进行标识,并没有对k中的做标识,因为没必要

## seq_q 和 seq_k 不一定一致,(在自注意力层时是一致的)在交互注意力,query来自解码端,key来自编码端,所以告诉模型编码这边pad符号信息就可以,解码端的pad信息在交互注意力层是没有用到的;

def get_attn_pad_mask(seq_q, seq_k):
    batch_size, len_q = seq_q.size()
    batch_size, len_k = seq_k.size()
    # eq(zero) is PAD token
    pad_attn_mask = seq_k.data.eq(0).unsqueeze(1)  # batch_size x 1 x len_k, one is masking
    return pad_attn_mask.expand(batch_size, len_q, len_k)  # batch_size x len_q x len_k

1.2.2.5 EncoderLayer的代码实现
## 5. EncoderLayer :包含两个部分,多头注意力机制和前馈神经网络
class EncoderLayer(nn.Module):
    def __init__(self):
        super(EncoderLayer, self).__init__()
        self.enc_self_attn = MultiHeadAttention()
        self.pos_ffn = PoswiseFeedForwardNet()

    def forward(self, enc_inputs, enc_self_attn_mask):
        ## 下面这个就是做自注意力层,输入是enc_inputs,形状是[batch_size x seq_len_q x d_model] 需要注意的是最初始的QKV矩阵是等同于这个输入的,去看一下enc_self_attn函数 6.
        enc_outputs, attn = self.enc_self_attn(enc_inputs, enc_inputs, enc_inputs, enc_self_attn_mask) # enc_inputs to same Q,K,V,复制了三份enc_outputs去计算.
        enc_outputs = self.pos_ffn(enc_outputs) # enc_outputs: [batch_size x len_q x d_model]
        return enc_outputs, attn
1.2.2.6 MultiHeadAttention多头注意力的代码实现
class MultiHeadAttention(nn.Module):
    def __init__(self):
        super(MultiHeadAttention, self).__init__()
        ## 输入进来的QKV是相等的,我们会使用映射linear做一个映射得到参数矩阵Wq, Wk,Wv
        self.W_Q = nn.Linear(d_model, d_k * n_heads)
        self.W_K = nn.Linear(d_model, d_k * n_heads)
        self.W_V = nn.Linear(d_model, d_v * n_heads)
        self.linear = nn.Linear(n_heads * d_v, d_model)
        self.layer_norm = nn.LayerNorm(d_model)

    def forward(self, Q, K, V, attn_mask):

        ## 这个多头分为这几个步骤,首先映射分头,然后计算atten_scores,然后计算atten_value;
        ##输入进来的数据形状: Q: [batch_size x len_q x d_model], K: [batch_size x len_k x d_model], V: [batch_size x len_k x d_model]
        residual, batch_size = Q, Q.size(0)
        # (B, S, D) -proj-> (B, S, D) -split-> (B, S, H, W) -trans-> (B, H, S, W)

        ##下面这个就是先映射,后分头;一定要注意的是q和k分头之后维度是一致额,所以一看这里都是dk
        q_s = self.W_Q(Q).view(batch_size, -1, n_heads, d_k).transpose(1,2)  # q_s: [batch_size x n_heads x len_q x d_k]
        k_s = self.W_K(K).view(batch_size, -1, n_heads, d_k).transpose(1,2)  # k_s: [batch_size x n_heads x len_k x d_k]
        v_s = self.W_V(V).view(batch_size, -1, n_heads, d_v).transpose(1,2)  # v_s: [batch_size x n_heads x len_k x d_v]

        ## 输入进行的attn_mask形状是 batch_size x len_q x len_k,然后经过下面这个代码得到 新的attn_mask : [batch_size x n_heads x len_q x len_k],就是把pad信息重复了n个头上
        attn_mask = attn_mask.unsqueeze(1).repeat(1, n_heads, 1, 1)


        ##然后我们计算 ScaledDotProductAttention 这个函数,去7.看一下
        ## 得到的结果有两个:context: [batch_size x n_heads x len_q x d_v], attn: [batch_size x n_heads x len_q x len_k]
        context, attn = ScaledDotProductAttention()(q_s, k_s, v_s, attn_mask) ## 7. 可查看ScaledDotProductAttention函数
        context = context.transpose(1, 2).contiguous().view(batch_size, -1, n_heads * d_v) # context: [batch_size x len_q x n_heads * d_v]
        output = self.linear(context)
        return self.layer_norm(output + residual), attn # output: [batch_size x len_q x d_model]

1.2.2.7 Decoder解码器的代码实现
class Decoder(nn.Module):
    def __init__(self):
        super(Decoder, self).__init__()
        self.tgt_emb = nn.Embedding(tgt_vocab_size, d_model)
        self.pos_emb = PositionalEncoding(d_model)
        self.layers = nn.ModuleList([DecoderLayer() for _ in range(n_layers)])

    def forward(self, dec_inputs, enc_inputs, enc_outputs): # dec_inputs : [batch_size x target_len]
        dec_outputs = self.tgt_emb(dec_inputs)  # [batch_size, tgt_len, d_model]
        dec_outputs = self.pos_emb(dec_outputs.transpose(0, 1)).transpose(0, 1) # [batch_size, tgt_len, d_model]

        ## get_attn_pad_mask 自注意力层的时候的pad 部分
        dec_self_attn_pad_mask = get_attn_pad_mask(dec_inputs, dec_inputs)

        ## get_attn_subsequent_mask 这个做的是自注意层的mask部分,就是当前单词之后看不到,使用一个上三角为1的矩阵
        dec_self_attn_subsequent_mask = get_attn_subsequent_mask(dec_inputs)

        ## 两个矩阵相加,大于0的为1,不大于0的为0,为1的在之后就会被fill到无限小
        dec_self_attn_mask = torch.gt((dec_self_attn_pad_mask + dec_self_attn_subsequent_mask), 0)


        ## 这个做的是交互注意力机制中的mask矩阵,enc的输入是k,我去看这个k里面哪些是pad符号,给到后面的模型;注意哦,我q肯定也是有pad符号,但是这里我不在意的,之前说了好多次了哈
        dec_enc_attn_mask = get_attn_pad_mask(dec_inputs, enc_inputs)

        dec_self_attns, dec_enc_attns = [], []
        for layer in self.layers:
            dec_outputs, dec_self_attn, dec_enc_attn = layer(dec_outputs, enc_outputs, dec_self_attn_mask, dec_enc_attn_mask)
            dec_self_attns.append(dec_self_attn)
            dec_enc_attns.append(dec_enc_attn)
        return dec_outputs, dec_self_attns, dec_enc_attns

由于篇幅过大,下面另做一篇为整洁一点,从而下面再展示完整 Transformer的完整代码实现

复现代码⼼得体会

  1. 从整体到局部
  2. 搞清楚数据流动形状,⾮常关键

参考来源: NLP-Transformer的从零实现代码详解

二.文献阅读-Autoformer: 基于深度分解架构和自相关机制的长期序列预测模型

单位:清华大学软件学院机器学习研究组
作者:吴海旭、徐洁慧、王建民、龙明生

文章链接地址:Autoformer论文地址
代码地址:https://github.com/thuml/Autoformer

2.1 摘要

延长预测时间是极端天气预警和长期能源消耗规划等实际应用的关键需求。本文研究时间序列的长期预测问题。先前的基于 Transformer 的模型采用各种自我注意机制来发现长期依赖关系。然而,长期未来的复杂时间模式使模型无法找到可靠的依赖关系。此外,Transformers 必须采用稀疏版本的 point-wise self-attentions 以获得长序列效率,从而导致信息利用瓶颈。除了 Transformers,我们将 Autoformer 设计为一种具有自相关机制的新型分解架构。我们打破了序列分解的预处理惯例,并将其更新为深度模型的基本内部块。这种设计为 Autoformer 赋予了复杂时间序列的渐进分解能力。此外,受随机过程理论的启发,我们设计了基于序列周期性的自相关机制,在子序列级别进行依赖关系发现和表示聚合。自相关在效率和准确性方面都优于自我注意。在长期预测中,Autoformer 产生了最先进的准确性,在六个基准上相对提高了 38%,涵盖了五个实际应用:能源、交通、经济、天气和疾病。

2.2 分析

基于Transformer的时间序列预测模型,通过自注意力机制(self-attention)来捕捉时刻间的依赖,在时序预测上取得了一些进展。但是在长期序列预测中,仍存在不足:

  • 长序列中的复杂时间模式使得注意力机制难以发现可靠的时序依赖
  • 基于Transformer的模型不得不使用稀疏形式的注意力机制来应对二次复杂度的问题,但造成了信息利用的瓶颈

为突破上述问题,我们全面革新了Transformer,并提出了名为Autoformer的模型,主要包含以下两大独特的本文创新

  • 突破将序列分解作为预处理的传统方法,提出深度分解架构(Decomposition Architecture),能够从复杂时间模式中分解出可预测性更强的组分。(亮点1:序列分解
  • 基于随机过程理论,提出自相关机制(Auto-Correlation Mechanism),代替点向连接的注意力机制,实现序列级(series-wise)连接和 O ( L l o g L ) O(L log L) O(LlogL) 复杂度,打破信息利用瓶颈。(亮点二:自相关机制)

2.3 Autoformer的整体框架

时间序列预测问题是根据过去的长度I序列预测未来最可能的长度O序列,表示为输入I预测O。长期预测设置是预测长期未来,即更大的O。

我们提出的Autoformer全面革新Transformer为深度分解架构,包括内部的序列分解单元、自相关机制以及对应的编-解码器。整体结构如下图1:
在这里插入图片描述
时间序列分解是指将时间序列分解为几个组分,每个组分表示一类潜在的时间模式,如季节周期项(seasonal)趋势项(trend-cyclical)。由于预测问题中未来的不可知性,通常先对过去序列进行分解,再分别预测。但这会造成预测结果受限于分解效果,并且忽视了未来各个组分之间的相互作用。

我们提出深度分解架构,将序列分解作为Autoformer的一个内部单元,嵌入到编-解码器中。在预测过程中,模型交替进行预测结果优化和序列分解,即从隐变量中逐步分离趋势项与周期项,实现渐进式分解

2.3.1 Decomposition Architecture(深度分解架构)

1. 序列分解单元(series decomposition block)

基于滑动平均思想,平滑周期项、突出趋势项:
在这里插入图片描述
其中 X X X为待分解的隐变量, X t X_t Xt X s X_s Xs分别为趋势项和季节周期项,我们将上述公式记为:
在这里插入图片描述
我们将上述序列分解单元嵌入Autoformer层间。

2. Model inputs 模型输入

encoder的输入:时间步长为I的 X e n X_{en} Xen,其维度是I*d;

decoder的输入
在这里插入图片描述
将上面的 X e n X_{en} Xen进行一个序列分解SeriesDecomp:其中是从后半部分去做的分解(作者说是为了效率才这样做),长度为 L / 2 L/2 L/2, 得到上面公式2中的 X e n s X_{ens} Xens, X e n t X_{ent} Xent,再经过Concat之后得到 X d e s , X d e t X_{des},X_{det} Xdes,Xdet,其维度为(I/2+O)*d,其中两个decoder输入部分之前concat的部分是不一样的, X d e s X_{des} Xdes部分填充的是0,而 X d e t X_{det} Xdet部分填充的是X的mean值。

3. 编码器

在encoder部分,我们逐步消除趋势项(这部分会在Deocder中通过累积得到),得到季节周期项 S e n l , 1 S^{l,1}_{en} Senl,1, S e n l , 2 S^{l,2}_{en} Senl,2,也就是encoder中的序列分解只保留seasonal,而基于这种周期性,我们设计自相关机制,聚合不同周期的相似子过程,实现信息聚合:
在这里插入图片描述
编码器的输出包含过去的季节周期信息,将作为交叉信息帮助解码器优化预测结果。假设我们有 N 个编码器层。 第 l 个编码器层的整体方程总结为:
在这里插入图片描述

4. 解码器

如上图所示,解码器采用双路处理模式,上分支处理seasonal part,下分支处理trend-cyclical part。

上分支首先使用AutoCorrelation提取未来预测状态内在的时间依赖,然后使用encoder-decoder AutoCorrelation从编码器输出的拥有高阶时间依赖的历史序列提取信息,最后过FeedForward层。

下分支则采用带权加法将上分支每一个子层的输出加合起来。如下所示:
在这里插入图片描述
每个解码器层都包含内部自相关编码器-解码器自相关,它们可以分别优化预测并利用过去的周期信息。 该模型在解码器期间从中间隐藏变量中提取潜在趋势,允许 Autoformer 逐步优化趋势预测并消除干扰信息,以便在自相关中发现基于周期的依赖关系。

基于上述渐进式分解架构,模型可以在预测过程中逐步分解隐变量,并通过自相关机制、累积的方式分别得到周期、趋势组分的预测结果,实现分解、预测结果优化的交替进行、相互促进

5. Auto-Correlation Mechanism (自相关机制)

提出自相关机制来实现高效的序列级连接,从而扩展信息效用。观察到,不同周期的相似相位之间通常表现出相似的子过程,我们利用这种序列固有的周期性来设计自相关机制,其中,包含基于周期的依赖发现(Period-based dependencies)时延信息聚合(Time delay aggregation)

1. 基于周期的依赖发现:观察到周期之间相同的相位位置自然会提供相似的子过程。基于随机过程理论,对于实离散时间过程 X t {X_t} Xt,我们可以计算其自相关系数 R x x ( τ ) R_{xx}(τ) Rxx(τ),其公式5如下:

在这里插入图片描述

2. 时延信息聚合: 基于周期的依赖关系连接估计周期之间的子系列。 因此,我们提出了时间延迟聚合块,它可以根据选定的时间延迟 τ 1 、 ⋅ ⋅ ⋅ 、 τ k τ1、···、τk τ1τk 滚动系列。 该操作可以对齐在估计周期的相同相位位置的相似子序列,这与 self-attention 系列中的逐点点积聚合不同。 最后,我们通过 softmax 归一化置信度聚合子系列。

为了实现序列级连接,我们需要将相似的子序列信息进行聚合。我们这里依据估计出的周期长度,首先使用 R O L L ( ) ROLL() ROLL()操作进行信息对齐,再进行信息聚合,我们这里依然使用query、key、value的形式,从而可以无缝替代自注意力机制。

在这里插入图片描述
从下到上对Auto-Correlation Mechanism进行分析,如上图所示,Q,K,V和Transformer一样通过映射输入得到,首先注意到对Q和K分别执行快速傅里叶变换操作 (FFT),K还执行了共轭操作,那么为什么要这样做呢?看下式:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

其中 a r g T o p k ( ⋅ ) arg Topk(·) argTopk() 是为了得到 T o p k Topk Topk 自相关的参数并且让 k = [ c × l o g L ] k = [c × log L] k=[c×logL], c 是一个超参数。 R Q , K R_{Q,K} RQ,K 是序列 Q 和 K 之间的自相关。(挑选到最有可能的K做为周期长度,用于避免挑选到无关、甚至相反的相位。)

注意: 这里的 τ τ τ 是从1取到L,然后分别算出前K个最大的TOPK,取为K个 τ 1 , . . . . . τ k τ_1,.....τ_k τ1,.....τk做周期长度!

为什么需要这样的一个 R O L L ( ) ROLL() ROLL操作,这样做的原因是什么?

作者对一个笔者对该疑问的回答:
在这里插入图片描述

自相关模块不同于之前Transformer中的自关注一类的模块,后者更关注与point-wise之间的依赖性,而前者更关注series-wise之间的依赖性。对比图如下:
在这里插入图片描述

2.3.2 实验部分

在六个真实世界基准上广泛评估了拟议的Autoformer,涵盖了五个主流时间序列预测应用:能源、交通、经济、天气和疾病。

首先看和baselines(多个基准模型,比如Informer、Reformer、LogTrans)的比较,可以看出autoformer在所有基准下均取得了极大的提升:

在这里插入图片描述
接着是Decomposition architecture的实验,可以看到对别的模型采用decomposition architecture也能极大提升模型效果,证明了框架的有效性。
在这里插入图片描述

小结

  • 作者提出Autoformer的框架,其遵循Transformer的框架设计除了新添加decomposition block提取模型中隐藏状态的内在复杂时序趋势。
  • Auto-Correlation机制替代self-attention,其不再是只考虑关注点,而形成了稀疏,造成信息利用瓶颈,所以考虑sub-series间的相似度能更好的捕捉到趋势性,不仅保证了 O ( L l o g L ) O(L log L) O(LlogL) 的复杂度,也防止了信息的丢失,做到了又快又好。
  • FFT计算自相关机制的独特创新,才保证了复杂度的降低,其ROLL()操作与之相洽合。

对论文中的Auto-Correlation Mechanism

如果需要彻底理解Auto-Correlation Mechanism ,就必须对Winer-Khinchin理论和快速傅里叶变换更进一步的学习才行。

参考优秀博文:
1. 细读好文 之 Autoformer
2.【边读边写】Autoformer
3.【时序】Autoformer

下一步是对Autoformer具体代码的复现,从整体到局部逐一理解与实现该论文的优势所在。

三. 空间计量分析领域知识

毕设:与学弟保持沟通,正在逐步完成论文撰写与项目搭建。

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值