本文继PyTorch深度学习实战:数据读取,基于时间序列预测 Autoformer 源码深入研习深度学习流程管道进行拆解,按照从数据获取与处理,构建模型,模型训练和预测以及其他的一些模块的顺序进行深入研习。
一般情况下,可以使用torch.nn包来构建神经网络。即使用 PyTorch 构建神经网络的一种简单方法是创建一个继承自 torch.nn.Module 的类。
这里将nn.Module子类化(它本身是一个类并且能够跟踪状态)。在这种情况下,我们要创建一个类,该类包含初始化模型和向前传播forward步骤的。nn.Module具有许多我们将要使用的属性和方法(例如.parameters()和.zero_grad())。
classNet(nn.Module):
"""

    示意函数

    """

def__init__(self, n_features):
        super(Net, self).__init__()

        self.encoder = Encoder()

        self.decoder = Decoder()

        self.enc_embedding = DataEmbedding_wo_pos()

        self.dec_embedding = DataEmbedding_wo_pos()


defforward(self, x_enc,x_dec,x_mark_enc,x_mark_dec):
        enc_out = self.enc_embedding(x_enc, x_mark_enc)

        enc_out = self.encoder(enc_out)

        dec_out = self.dec_embedding(x_dec, x_mark_dec)

        seasonal_part, trend_part = self.decoder(dec_out, enc_out)

return
 seasonal_part + trend_part

我们只需要定义 forward 函数,backward 函数会在使用 autograd 时自动定义,backward 函数用来计算导数。我们可以在 forward 函数中使用任何针对张量的操作和计算。

Autoformer.py

接下来,一步一步地深入研究Autoformer模型架构构建方法与细节。
Autoformer模型架构
上面是Autoformer模型的整体架构,首先先从宏观角度看下该架构的情况,并从那个Autoformer.py模型的完整代码中理论结合实践学习。
首先导入我们所需要的python库。
# Autoformer.py
import
 torch

import
 torch.nn 
as
 nn

import
 torch.nn.functional 
as
 F

from
 layers.Embed 
import
 DataEmbedding, DataEmbedding_wo_pos

from
 layers.AutoCorrelation 
import
 AutoCorrelation, AutoCorrelationLayer

from
 layers.Autoformer_EncDec 
import
 Encoder, Decoder, EncoderLayer, DecoderLayer, my_Layernorm, series_decomp

import
 math

import
 numpy 
as
 np

其中,以layers为首的网络层结构,是本次Autoformer模型中网络架构层,等下在后面会详细介绍。这里我们把它当作一个已知(已完成)的神经网络模型。

初始化模型

首先是初始化该模型,定义模型所需要的几本参数self.seq_lenself.label_lenself.pred_len,然后定义序列分解单元结构self.decomp。定义embedding层,使用DataEmbedding_wo_pos()方法分别定义编码嵌入层self.enc_embedding和解码嵌入层self.dec_embedding
接下来就是定义编码器 self.encoder 和解码器 self.decoder
# Autoformer.py 删减
classModel(nn.Module):
"""

    Autoformer is the first method to achieve the series-wise connection,

    with inherent O(LlogL) complexity

    """

def__init__(self, configs):
        super(Model, self).__init__()

        self.seq_len = configs.seq_len     
# 96
        self.label_len = configs.label_len 
# 48
        self.pred_len = configs.pred_len   
# 24
        self.output_attention = configs.output_attention 
# True

# Decomp
        kernel_size = configs.moving_avg    
#  default=25 'window size of moving average'
        self.decomp = series_decomp(kernel_size)


# Embedding
# The series-wise connection inherently contains the sequential information.
# Thus, we can discard the position embedding of transformers.
        self.enc_embedding = DataEmbedding_wo_pos(...)

        self.dec_embedding = DataEmbedding_wo_pos(...)


# Encoder
        self.encoder = Encoder(...)

# Decoder
        self.decoder = Decoder(...)

向前传播 forward

接下来就是定义向前传播函数forward()
编写向前传播函数,需要大致了解一下 Autoformer 架构
# Autoformer.py
classModel(nn.Module):
defforward
(self, x_enc,   
# [32, 96, 7]
                x_mark_enc,    
# [32, 96, 4]
                x_dec,         
# [32, 72, 7] --> cat[0:lable_len , -pred_len:] dim=1   48 + 24
                x_mark_dec,    
# [32, 72, 4]
                enc_self_mask=None, dec_self_mask=None, dec_enc_mask=None)
:

# 首先初始化序列分解 [32, 7] -> [32,1,7] -> [32,24,7]
        mean = torch.mean(x_enc, dim=
1
).unsqueeze(
1
).repeat(
1
, self.pred_len, 
1
)

# unsqueeze()返回一个插入到指定位置的尺寸为 1 的新张量。
        zeros = torch.zeros([x_dec.shape[
0
], self.pred_len, x_dec.shape[
2
]],

                            device=x_enc.device) 
# [32,24,7]
        seasonal_init, trend_init = self.decomp(x_enc) 
# [32,96,7], [32,96,7]
# decoder input
# start token length
        trend_init = torch.cat([trend_init[:, -self.label_len:, :], mean], dim=
1
# [32,48+24,7]
        seasonal_init = torch.cat([seasonal_init[:, -self.label_len:, :], zeros], dim=
1
# [32,48+24,7]
# enc
        enc_out = self.enc_embedding(x_enc, x_mark_enc)

        enc_out, attns = self.encoder(enc_out, attn_mask=enc_self_mask)

# dec
        dec_out = self.dec_embedding(seasonal_init, x_mark_dec)

        seasonal_part, trend_part = self.decoder(dec_out, enc_out, 

                                                 x_mask=dec_self_mask, 

                                                 cross_mask=dec_enc_mask,

                                                 trend=trend_init)

# final
        dec_out = trend_part + seasonal_part


if
 self.output_attention:

return
 dec_out[:, -self.pred_len:, :], attns

else
:

return
 dec_out[:, -self.pred_len:, :]  
# [B, L, D]

模型输入

研究完Autoformer模型(初始化模型__init__、forward两个函数)的源码后,继续深入。
模型输入分为两个部分:
  • 编码器Encoder输入
    输入历史时间序列,即过去
    时间段的  
  • 解码器Decoder输入
    其输入包括趋势项和季节项两个部分。
其中趋势项和季节项均是由两部分组成,第一部分都是历史时间序列后半部分     经过序列分解单元分解出来的趋势项和季节项作为初始化,这样做的目的是获取最近的信息,它是抽取编码器输入  中倒数长度为 的信息。
序列分解单元输出包括季节性和趋势性两个部分。除此之外,趋势项的另一部分是用标量0 填充占位,即目前尚不知道的未来时间序列趋势项。季节项的另一部分是用历史时间序列均值 填充。
用代码实现即为向前传播函数forward()中的。
# Autoformer.py forward 稍作修改
seasonal_init, trend_init = self.decomp(x_enc) 
# x_enc: X_en   [32,96,7], [32,96,7]
X_ent = trend_init[:, -self.label_len:, :]

X_ens = seasonal_init[:, -self.label_len:, :]

X_0 = torch.zeros([x_dec.shape[
0
], self.pred_len, x_dec.shape[
2
]], device=x_enc.device)

X_mean = torch.mean(x_enc, dim=
1
).unsqueeze(
1
).repeat(
1
, self.pred_len, 
1
)

Embedding

数据经过输入层后,需要对其进行编码。而Autoformer使用的embedding方法是TokenEmbeddingTemporalEmbedding,由于序列中已经包含了顺序信息,因此位置编码可以省略。
# Embed.py
classDataEmbedding_wo_pos(nn.Module):
def__init__(self, c_in, d_model, embed_type='fixed', freq='h', dropout=0.1):
        super(DataEmbedding_wo_pos, 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)


defforward(self, x, x_mark):
        x = self.value_embedding(x) + self.temporal_embedding(x_mark)

return
 self.dropout(x)

接下来,我们一起看下TokenEmbedding及TemporalEmbedding两种embedding的源码,看看其实现逻辑。

TokenEmbedding

从源码可以看出TokenEmbedding是使用一个3*3的卷积核进行卷积计算。
# Embed.py
classTokenEmbedding(nn.Module):
def__init__(self, c_in, d_model):
        super(TokenEmbedding, self).__init__()

        padding = 
1if
 torch.__version__ >= 
'1.5.0'else2
        self.tokenConv = nn.Conv1d(in_channels=c_in, out_channels=d_model,

                                   kernel_size=
3
, padding=padding, 

                                   padding_mode=
'circular'
, bias=
False
)

for
 m 
in
 self.modules():

if
 isinstance(m, nn.Conv1d):

                nn.init.kaiming_normal_(m.weight, mode=
'fan_in'

                                        nonlinearity=
'leaky_relu'
)


defforward(self, x):
        x = self.tokenConv(x.permute(
0
2
1
)).transpose(
1
2
)

return
 x

TemporalEmbedding

从源码可以看出针对于时间序列数据,月日周小时四个特征分别进行embedding,最后将其使用加法组合。其中如果embedding方法选用预设混合模式,则使用类似于transformer的位置编码,使用正余弦值作为embedding的权重。
# Embed.py
classTemporalEmbedding(nn.Module):
def__init__(self, d_model, embed_type='fixed', freq='h'):
        super(TemporalEmbedding, self).__init__()


        minute_size = 
4
        hour_size = 
24
        weekday_size = 
7
        day_size = 
32
        month_size = 
13

        Embed = FixedEmbedding 
if
 embed_type == 
'fixed'else
 nn.Embedding

if
 freq == 
't'
:

            self.minute_embed = Embed(minute_size, d_model)

        self.hour_embed = Embed(hour_size, d_model)

        self.weekday_embed = Embed(weekday_size, d_model)

        self.day_embed = Embed(day_size, d_model)

        self.month_embed = Embed(month_size, d_model)


defforward(self, x):
        x = x.long()


        minute_x = self.minute_embed(x[:, :, 
4
]) 
if
 hasattr(self, 
'minute_embed'
else0.
        hour_x = self.hour_embed(x[:, :, 
3
])

        weekday_x = self.weekday_embed(x[:, :, 
2
])

        day_x = self.day_embed(x[:, :, 
1
])

        month_x = self.month_embed(x[:, :, 
0
])


return
 hour_x + weekday_x + day_x + month_x + minute_x


classFixedEmbedding(nn.Module):
def__init__(self, c_in, d_model):
        super(FixedEmbedding, self).__init__()


        w = torch.zeros(c_in, d_model).float()

        w.require_grad = 
False

        position = torch.arange(
0
, c_in).float().unsqueeze(
1
)

        div_term = (torch.arange(
0
, d_model, 
2
).float() * -(math.log(
10000.0
) / d_model)).exp()


        w[:, 
0
::
2
] = torch.sin(position * div_term)

        w[:, 
1
::
2
] = torch.cos(position * div_term)


        self.emb = nn.Embedding(c_in, d_model)

        self.emb.weight = nn.Parameter(w, requires_grad=
False
)


defforward(self, x):
return
 self.emb(x).detach()

到这里,我们对于embedding层的细节源码已经熟悉了,现在回到宏观层面查看源码 Autoformer.py 中是如和使用的:
# Autoformer.py 提取
def__init__(self):
   super(Model, self).__init__()

    self.enc_embedding = DataEmbedding_wo_pos(configs.enc_in, configs.d_model, 

                                                  configs.embed, configs.freq, configs.dropout)

    self.dec_embedding = DataEmbedding_wo_pos(configs.dec_in, configs.d_model, 

                                                  configs.embed, configs.freq, configs.dropout)

defforward(self, x_enc, x_mark_enc, x_dec, x_mark_dec)
enc_out
 = 
self
.
enc_embedding(x_enc, x_mark_enc)
 # [32,96,512]

dec_out
 = 
self
.
dec_embedding(seasonal_init, x_mark_dec)
[未完待续...]
构建神经网络模型下部分主要包括模型架构架构部分详细介绍 --> series_decomp 编码器 解码器 自相关机制。敬请期待!

参考资料

[1]
https://zhuanlan.zhihu.com/p/385066440
[2]
https://arxiv.org/abs/2106.13008
🏴‍☠️宝藏级🏴‍☠️原创公众号『机器学习研习院』,公众号专注分享机器学习深度学习领域的原创文章,一起研习,共同进步!
长按👇关注- 机器学习研习院 -设为星标,干货速递
继续阅读
阅读原文