Appearance
Layer 2B — Encoder 精读
父层(Layer 1)
forecast()的第⑤步调用self.encoder(enc_out)。
本文档只覆盖Encoder.forward这一层的逻辑(循环调度 + 最终 LayerNorm)。
子层 EncoderLayer 及以下见 04A-Layer3-EncoderLayer。
1. 在父层中的位置
forecast()
├─ ④ patch_embedding(x_enc) → enc_out (8, 6, 16)
└─ ⑤ enc_out, attns = self.encoder(enc_out) ← 本文档
└─ for attn_layer in attn_layers:
x, attn = attn_layer(x, ...) → 详见 Layer3 EncoderLayer
└─ self.norm(x)2. I/O 接口定义
python
def forward(self, x, attn_mask=None, tau=None, delta=None):| shape(toy) | 含义 | |
|---|---|---|
输入 x | (8, 6, 16) = (B*C, patch_num, d_model) | PatchEmbedding 输出的 patch token |
输出 x | (8, 6, 16) | Transformer 处理后的 patch 表示,形状不变 |
输出 attns | [None] | 注意力权重列表;output_attention=False 时每层返回 None |
attn_mask / tau / delta接收但在 PatchTST 中全部为 None,原样透传给子层。
3. 顺序图(具体层)
4. 语义分组图(索引层)
Encoder 只做"管理",不做任何特征计算。三件事:选路径 → ==迭代调度== → 收尾归一化。
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整体结构:分支路由决定走哪条迭代路径,串联完所有 EncoderLayer 后做一次收尾 LayerNorm。
5.1 分支路由
本节的作用
根据
conv_layers是否存在,选择纯串联(PatchTST)还是交替蒸馏(Informer)两条路径。
步骤一 — if/else 分支判断
python
attns = []
if self.conv_layers is not None:
...
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 路径(distilling,Informer):
attn_layers: [EncoderLayer_0, EncoderLayer_1, EncoderLayer_2, ...]
conv_layers: [ConvLayer_0, ConvLayer_1, ...]
step 0: x → EncoderLayer_0 → ConvLayer_0 → x(seq_len 缩短)
step 1: x → EncoderLayer_1 → ConvLayer_1 → x(再缩短)
...
最后一步: x → attn_layers[-1](无 conv)→ x每个 ConvLayer 做 MaxPool 压缩序列长度,实现 Informer 的 distilling。
else 路径(纯串联,PatchTST):
attn_layers: [EncoderLayer_0] (e_layers=1 时只有一个)
step 0: x (8,6,16) → EncoderLayer_0 → x (8,6,16)只有 attn_layer,无 conv_layer,形状始终不变。PatchTST 不做序列压缩——patch 数量从头到尾保持 6。
| 分支 | 条件 | 迭代逻辑 | 适用模型 |
|---|---|---|---|
if(distilling) | conv_layers is not None | attn + conv 交替;最后再单跑 attn_layers[-1] | Informer |
else(简单串联) | conv_layers is None ← PatchTST | 只有 attn_layer,纯串联,形状不变 | PatchTST |
toy 追踪(PatchTST,e_layers=1):self.conv_layers = None → 走 else,循环体执行 1 次,attns 从 [] 变为 [None]。
5.2 循环调度
本节的作用
将输入 x 依次传过每个 EncoderLayer,前一层输出作为后一层输入,实现 Transformer 的深度堆叠。
步骤二 — 串联调度 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 是 nn.ModuleList([EncoderLayer_0]),toy 里 e_layers=1 只循环一次。每次把当前 x 传给 EncoderLayer,返回的新 x 就地覆盖,作为下一层输入(若有下一层)。
若 e_layers=2,循环两次,第二层的输入是第一层的输出,形状始终维持 (8, 6, 16) 不变。
toy 追踪:x (8,6,16) → EncoderLayer_0 → x (8,6,16),attn=None,循环后 attns = [None]。
attns 是列表而非单个 tensor
attns 是列表而非单个 tensor每层返回各自的注意力权重并 append,使调用方可以访问任意层的权重。
output_attention=False时每次 append 的是None,不占内存,但列表结构保持不变,方便切换output_attention=True时直接查任意层。
5.3 最终归一化
本节的作用
对所有 EncoderLayer 跑完后的输出做一次整体 LayerNorm,稳定深层堆叠后的 token 分布。
步骤三 — 收尾 LayerNorm + return
python
if self.norm is not None:
x = self.norm(x)
return x, attnsself.norm = LayerNorm(16),对最后一维逐 token 归一化,形状不变 (8, 6, 16)。
Encoder 级归一化 vs EncoderLayer 内部归一化
04A-Layer3-EncoderLayer 内部有
norm1、norm2两个归一化——那是每个 Transformer block 内部的 Post-norm,负责每步残差后的局部稳定。这里的
self.norm是整个 Encoder 的最终归一化,是在所有 block 串联完毕后才执行的收尾操作。两者层级不同,作用互补:block 内归一化控制每步梯度流,Encoder 级归一化在 Transformer 栈顶做最后一次分布对齐,让后续 projection 层接收到分布稳定的输入。
toy 追踪:x (8,6,16) → LayerNorm(16) → x (8,6,16),attns = [None],return (8,6,16), [None]。
6. 下钻子组件
| 子组件 | 职责 | 下层文档 |
|---|---|---|
EncoderLayer(attn_layer) | Transformer block:注意力残差 + FFN 残差 + 2×LayerNorm | 04A-Layer3-EncoderLayer |