Appearance
Layer 2C — future_multi_mixing()(多尺度预测求和)
1. 在父层中的位置
forecast() 中 PDM 块处理完 enc_out_list 后调用 self.future_multi_mixing(B, enc_out_list, x_list) 生成最终预测,返回值再经 stack+sum 和 denorm 后作为模型输出。
2. I/O 接口定义
| 参数 | Shape | 说明 |
|---|---|---|
B | 2 | 原始 batch size(CI reshape 之前) |
enc_out_list | [(6,24,8),(6,12,8),(6,6,8)] | PDM 输出, |
x_list | (list, None) | CI 模式下 x_list[0] = 原始 x_list;x_list[1] = None |
| 返回 | [(2,6,3),(2,6,3),(2,6,3)] | 各尺度预测, |
3. 顺序图
4. 语义分组图
5. 逐步骤精读
§5.0 完整原始代码
python
class TimeMixer(nn.Module):
def __init__(self, configs):
# ... (前略) ...
if (
self.task_name == "long_term_forecast"
or self.task_name == "short_term_forecast"
):
self.predict_layers = torch.nn.ModuleList(
[
torch.nn.Linear(
configs.seq_len // (configs.down_sampling_window**i),
configs.pred_len,
)
for i in range(configs.down_sampling_layers + 1)
]
)
if self.channel_independence == 1:
self.projection_layer = nn.Linear(configs.d_model, 1, bias=True)
else:
self.projection_layer = nn.Linear(
configs.d_model, configs.c_out, bias=True
)
def future_multi_mixing(self, B, enc_out_list, x_list):
dec_out_list = []
if self.channel_independence == 1:
x_list = x_list[0]
for i, enc_out in zip(range(len(x_list)), enc_out_list):
dec_out = self.predict_layers[i](enc_out.permute(0, 2, 1)).permute(
0, 2, 1
) # align temporal dimension
dec_out = self.projection_layer(dec_out)
dec_out = (
dec_out.reshape(B, self.configs.c_out, self.pred_len)
.permute(0, 2, 1)
.contiguous()
)
dec_out_list.append(dec_out)
else:
for i, enc_out, out_res in zip(
range(len(x_list[0])), enc_out_list, x_list[1]
):
dec_out = self.predict_layers[i](enc_out.permute(0, 2, 1)).permute(
0, 2, 1
) # align temporal dimension
dec_out = self.out_projection(dec_out, i, out_res)
dec_out_list.append(dec_out)
return dec_out_list§5.1 宏观逻辑
设计直觉:不同时间分辨率对未来预测能力各有侧重——细粒度(
用小例子(
三个 enc_out shape 分别为 (6,24,8) / (6,12,8) / (6,6,8)。每个尺度先把时间维换到末尾(permute),让 predict_layers[i] 在时间轴上做线性映射(projection_layer 把 reshape(2, 3, 6) 把 permute(0,2,1) 换成
shape 变化全链(以尺度0为例):(6,24,8) → permute → (6,8,24) → Linear(24→6) → (6,8,6) → permute → (6,6,8) → Linear(8→1) → (6,6,1) → reshape(2,3,6) → (2,3,6) → permute(0,2,1) → (2,6,3)。
| 论文/原理描述 | 代码实现 | 关键原因 |
|---|---|---|
| 各尺度独立预测 | predict_layers[i](3 个独立 Linear) | 不同分辨率对不同频率成分各有优势 |
| 时间维→预测步 | enc_out.permute(0,2,1) → predict_layers[i] | nn.Linear 只作用于最后维,须将 |
| CI reshape 还原 | .reshape(B, c_out, pred_len).permute(0,2,1) | 逆向还原 forecast 步骤 2 中的 CI reshape |
| 多尺度等权组合 | torch.stack(...).sum(-1) | 无额外参数;每个 predict_layer 的梯度会自动调整贡献 |
§5.2 步骤 1 — predict_layers 初始化
python
self.predict_layers = torch.nn.ModuleList(
[
torch.nn.Linear(
configs.seq_len // (configs.down_sampling_window**i),
configs.pred_len,
)
for i in range(configs.down_sampling_layers + 1)
]
)形状注解: 列表长度为 down_sampling_layers + 1 = 3。每个 Linear 的输入维度是对应尺度的时间长度 pred_len。Linear 作用于最后维,所以 enc_out 须先 permute 成
toy 数值: seq_len=24, window=2, layers=2, pred_len=6:
predict_layers[0] 由 predict_layers[1] 由 predict_layers[2] 由
CI 模式下 projection_layer = Linear(d_model=8, 1, bias=True):把每个时间步的
§5.3 步骤 2 — CI 分支逐尺度预测
python
if self.channel_independence == 1:
x_list = x_list[0]
for i, enc_out in zip(range(len(x_list)), enc_out_list):
dec_out = self.predict_layers[i](enc_out.permute(0, 2, 1)).permute(
0, 2, 1
) # align temporal dimension
dec_out = self.projection_layer(dec_out)
dec_out = (
dec_out.reshape(B, self.configs.c_out, self.pred_len)
.permute(0, 2, 1)
.contiguous()
)
dec_out_list.append(dec_out)形状注解: 以尺度0为例,enc_out shape (6, 24, 8)。enc_out.permute(0, 2, 1) 将轴顺序 (6, 8, 24),使 Linear 能在 predict_layers[0](Linear (6, 8, 6),再 .permute(0, 2, 1) 还原为 (6, 6, 8) 即 projection_layer(Linear (6, 6, 1) = .reshape(2, 3, 6) 把 .permute(0, 2, 1) 把轴顺序变为
toy 数值追踪(尺度0,完整链):
输入 enc_out shape (6, 24, 8)(
enc_out.permute(0, 2, 1) → shape (6, 8, 24)(时间轴移末尾,
predict_layers[0](Linear out[b_n, d_i, t'] = (6, 8, 6)。再 .permute(0, 2, 1) → shape (6, 6, 8) 即
projection_layer(Linear (6, 6, 1)。
.reshape(B=2, c\_out=3, pred\_len=6):pred_len=1 的 pred_len=6 → reshape 成
reshape 还原 CI reshape 的关键
CI reshape 路径:原始
(B, T, N)→permute(0,2,1)→(B, N, T)→reshape(B*N, T, 1)。还原路径:(B*N, pred_len, 1)→reshape(B, N, pred_len)→permute(0,2,1)→(B, pred_len, N)。唯一要求:reshape 时能被正确拆成 ,这由 B参数(从forecast()传入,就是原始 batch size)保证。
.permute(0, 2, 1) → shape (2, 6, 3) = .contiguous() 保证内存连续(permute 后内存可能不连续,后续 stack 需要连续 tensor)。
尺度1(enc_out=(6, 12, 8) → permute → (6, 8, 12) → predict_layers[1](Linear (6, 8, 6) → permute → (6, 6, 8) → projection_layer → (6, 6, 1) → reshape(2, 3, 6) → permute → (2, 6, 3)。
尺度2(enc_out=(6, 6, 8) → permute → (6, 8, 6) → predict_layers[2](Linear (6, 8, 6) → permute → (6, 6, 8) → projection_layer → (6, 6, 1) → reshape(2, 3, 6) → permute → (2, 6, 3)。
返回: dec_out_list = [(2,6,3), (2,6,3), (2,6,3)],三个尺度均为
§5.4 步骤 3 — stack + sum(在 forecast() 中执行)
python
dec_out = torch.stack(dec_out_list, dim=-1).sum(-1)形状注解: torch.stack 在 dim=-1(最后维)拼接三个 (2, 6, 3) 张量,在末尾新增一个"尺度维"。.sum(-1) 沿尺度维求和,将三个尺度的预测等权合并。
toy 数值: torch.stack([(2,6,3), (2,6,3), (2,6,3)], dim=-1) → shape (2, 6, 3, 3)(最后一维的 3 分别对应 scale0、scale1、scale2 的预测)。.sum(-1) 沿 scale 维求和 → shape (2, 6, 3)。每个位置的值为三个尺度预测之和:
为什么是简单求和而非加权平均?
等权求和(而非 softmax 加权)的设计动机:三个尺度预测的是同一目标的不同"频率视角",求和等价于集成(ensemble)。若某个尺度的预测完全无信息,其对应
predict_layers的权重在训练中会通过梯度自动收缩(输出趋近于零),不需要额外的门控机制。相比加权平均,简单求和避免引入混合参数,保持模型简洁。
dim=-1 而非 dim=0 的原因
dim=-1 而非 dim=0 的原因
torch.stack(list, dim=0)会把 batch 维合并,得到(3, 2, 6, 3),再sum(0)也能得到(2, 6, 3)。但dim=-1把尺度维加在末尾,语义更清晰:"每个时间步每个变量有来自 3 个尺度的预测",.sum(-1)沿尺度维折叠,方向一目了然。