Skip to content

Layer2B Encoder

覆盖 iTransformer.forecast() 中的 self.encoder(enc_out, attn_mask=None) 调用。Encoder 组件负责将 e_layers=2 个 EncoderLayer 串行堆叠,并在最终做 LayerNorm。注意力在 变量 token 之间计算,token 语义与 PatchTST 不同,但 Encoder 本体结构相同。


§1 在父层中的位置

forecast() 第三步:

python
enc_out, attns = self.encoder(enc_out, attn_mask=None)
  • 输入:enc_out (3, 9, 8),即 (B, token_count, d_model) = (batch, N+time_dims, d_model)
  • 输出:enc_out (3, 9, 8)(形状不变,token 内容被更新)

§2 I/O 接口定义

参数shape(toy)含义
输入 enc_out(3, 9, 8)B=3,token_count=9(5 变量 + 4 时间),d_model=8
输出 enc_out(3, 9, 8)形状不变,token 内容被 Encoder 更新
attn_maskNoneiTransformer 不使用掩码
为什么形状不变?

iTransformer 没有 ConvLayer distilling(Informer 才有)。每个 EncoderLayer 输入输出 shape 相同,e_layers=2 层堆叠后整体仍是 (3, 9, 8)


§3 顺序图


§4 语义分组图


§5 逐步精读

§5.0 完整原始代码

python
def forward(self, x, attn_mask=None, tau=None, delta=None):
    attns = []
    if self.conv_layers is not None:
        for i, (attn_layer, conv_layer) in enumerate(
            zip(self.attn_layers, self.conv_layers)
        ):
            delta = delta if i == 0 else None
            x, attn = attn_layer(x, attn_mask=attn_mask, tau=tau, delta=delta)
            x = conv_layer(x)
            attns.append(attn)
        x, attn = self.attn_layers[-1](x, tau=tau, delta=None)
        attns.append(attn)
    else:
        for attn_layer in self.attn_layers:
            x, attn = attn_layer(x, attn_mask=attn_mask, tau=tau, delta=delta)
            attns.append(attn)

    if self.norm is not None:
        x = self.norm(x)

    return x, attns

§5.1 宏观逻辑

用小例子(B=1,token_count=3,d_model=4,e_layers=2)串起来看:

x 初始 (1, 3, 4)
  ↓ EncoderLayer[0]:3个 token 互相 attend,更新各自表示
  x (1, 3, 4)
  ↓ EncoderLayer[1]:再做一轮 attention + FFN
  x (1, 3, 4)
  ↓ LayerNorm
  x (1, 3, 4)
返回 (x, [attn0, attn1])

shape 全链不变是 iTransformer 的特点:没有 Informer 的 distilling(每层把 seq_len 减半),因为 token 语义是变量,固定就是 9 个(N+time_dims),不需要压缩。


§5.2 步骤一:分支判断 — conv_layers is None

iTransformer.__init__() 中:

python
self.encoder = Encoder(
    [
        EncoderLayer(...)
        for l in range(configs.e_layers)
    ],
    norm_layer=torch.nn.LayerNorm(configs.d_model)
)

Encoder 构造时没有传 conv_layers,因此 self.conv_layers = None,代码走 else 分支。

if self.conv_layers is not None 分支(Informer 的 distilling 逻辑)在 iTransformer 中永远不会执行


§5.3 步骤二:循环 e_layers=2 个 EncoderLayer

python
for attn_layer in self.attn_layers:
    x, attn = attn_layer(x, attn_mask=attn_mask, tau=tau, delta=delta)
    attns.append(attn)
  • self.attn_layers 共 2 个 EncoderLayer 实例
  • attn_mask=None:iTransformer 不使用因果掩码(变量 token 之间全局互相关)
  • tau=None, delta=None:iTransformer 不使用这两个参数(为与 Informer 共用接口而保留)
  • 每轮返回 (x, attn),attn 累积到列表备用(调用方一般不用,但接口保留)

toy 数值(全局 toy 参数):

迭代前:
  x[0, 0, :] = [0.12, -0.34, 0.87, -0.21, 0.55, -0.68, 0.33, -0.44]
               ← var_0 token 的 8 维 embedding(DataEmbedding_inverted 输出)

迭代 0(EncoderLayer[0]):
  x 传入后,var_0 通过 self-attention 关注了 var_1~var_4 及 4 个时间 token
  并经过 FFN 做非线性变换;内容改变,shape 不变
  x[0, 0, :] = [0.03, -0.51, 1.12, 0.08, 0.44, -0.92, 0.17, -0.29]
               ← 数值已更新,但仍是 8 维,仍代表 var_0 的 token 表示
  attn0 = None

迭代 1(EncoderLayer[1]):
  在第 0 层已更新的 token 表示上再做一轮 attention + FFN
  x[0, 0, :] = [0.07, -0.48, 1.08, 0.11, 0.39, -0.87, 0.14, -0.32]
               ← 进一步精化,绝对值趋于稳定
  attn1 = None

attns = [None, None]

EncoderLayer 内部细节见 [[03B1-Layer3-EncoderLayer]]。


§5.4 步骤三:LayerNorm

python
if self.norm is not None:
    x = self.norm(x)
  • self.norm = nn.LayerNorm(configs.d_model) = LayerNorm(8)
  • 对最后一维(d_model=8)做归一化,形状 (3, 9, 8) 不变
  • 每个 token 向量独立归一化,跨 token 的统计量不混合

toy 数值:

x[0, 0, :] = [e0, e1, ..., e7]  ← var_0 的 8 维表示
norm 后:减去这 8 个数的均值,除以标准差,× weight + bias
输出仍是 8 维向量;其他 8 个 token 各自独立 norm

§6 下钻子组件

组件输入 shape输出 shape下层文档
EncoderLayer(3, 9, 8)(3, 9, 8)[[03B1-Layer3-EncoderLayer]]

§7 结构图

*记录并在线阅读我的笔记*