Skip to content

Layer 2C — future_multi_mixing()(多尺度预测求和)

1. 在父层中的位置

forecast() 中 PDM 块处理完 enc_out_list 后调用 self.future_multi_mixing(B, enc_out_list, x_list) 生成最终预测,返回值再经 stack+sumdenorm 后作为模型输出。

2. I/O 接口定义

参数Shape说明
B2原始 batch size(CI reshape 之前)
enc_out_list[(6,24,8),(6,12,8),(6,6,8)]PDM 输出,(BN, Ti, d)
x_list(list, None)CI 模式下 x_list[0] = 原始 x_list;x_list[1] = None
返回[(2,6,3),(2,6,3),(2,6,3)]各尺度预测,(B, pred\_len, N)

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 宏观逻辑

设计直觉:不同时间分辨率对未来预测能力各有侧重——细粒度(T=24)保留了短期波动规律,粗粒度(T=6)已被 AvgPool 平滑,保留了长期趋势背景。让每个尺度通过独立线性层学习"从当前历史预测未来 pred\_len 步"的映射,最后简单等权相加,各司其职互补。

用小例子(B=2, N=3, d=8, pred=6, T0=24, T1=12, T2=6)串起 CI 路径的全链:

三个 enc_out shape 分别为 (6,24,8) / (6,12,8) / (6,6,8)。每个尺度先把时间维换到末尾(permute),让 predict_layers[i] 在时间轴上做线性映射(Ti6),再换回 (BN, pred, d),然后 projection_layerd=8 压到 1,最后 reshape(2, 3, 6)BN=6 拆回 B=2, N=3permute(0,2,1) 换成 (B,pred,N)=(2,6,3)

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 只作用于最后维,须将 T 移到末尾
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 的输入维度是对应尺度的时间长度 Ti=seq\_len/windowi,输出维度固定为 pred_len。Linear 作用于最后维,所以 enc_out 须先 permute 成 (BN, d, Ti) 才能喂给它。

toy 数值: seq_len=24, window=2, layers=2, pred_len=6

predict_layers[0]i=0 构建:Linear(24//20, 6) = Linear(24, 6),对应 scale0(T=24)。predict_layers[1]i=1 构建:Linear(24//21, 6) = Linear(12, 6),对应 scale1(T=12)。predict_layers[2]i=2 构建:Linear(24//22, 6) = Linear(6, 6),对应 scale2(T=6)。

CI 模式下 projection_layer = Linear(d_model=8, 1, bias=True):把每个时间步的 d=8 维特征投影到标量,即逐变量投影(因为 CI reshape 已把 N 并入 batch)。

§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) 将轴顺序 (BN, T, d)(BN, d, T),得 (6, 8, 24),使 Linear 能在 T 轴(最后轴)操作。predict_layers[0](Linear 246)输出 (6, 8, 6),再 .permute(0, 2, 1) 还原为 (6, 6, 8)(BN, pred\_len, d)projection_layer(Linear 81)作用于最后轴,输出 (6, 6, 1) = (BN, pred\_len, 1)。最后 .reshape(2, 3, 6)BN=6 拆回 B=2, N=3.permute(0, 2, 1) 把轴顺序变为 (B, pred\_len, N)=(2,6,3)

toy 数值追踪(尺度0,完整链):

输入 enc_out shape (6, 24, 8)BN=6T=24d=8)。

enc_out.permute(0, 2, 1) → shape (6, 8, 24)(时间轴移末尾,d 轴到中间)。

predict_layers[0](Linear 246)作用于最后轴 T=24,每条 (6,8)d 维向量对应一个线性变换:输出每个位置 out[b_n, d_i, t'] = t=023W0[t,t]enc[bn,di,t],shape → (6, 8, 6)。再 .permute(0, 2, 1) → shape (6, 6, 8)(BN, pred\_len, d)

projection_layer(Linear 81):对每个 (bn, t) 位置的 d=8 维向量做一次线性投影,输出标量,shape → (6, 6, 1)

.reshape(B=2, c\_out=3, pred\_len=6)BN=6=2×3,reshape 把前两轴 (6,6) 中的 BN=6 维拆成 (B=2, N=3),最后轴由 pred_len=1d(已投影到1)× pred_len=6 → reshape 成 (2,3,6)

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 时 BN=6 能被正确拆成 B=2, N=3,这由 B 参数(从 forecast() 传入,就是原始 batch size)保证。

.permute(0, 2, 1) → shape (2, 6, 3) = (B, pred\_len, N).contiguous() 保证内存连续(permute 后内存可能不连续,后续 stack 需要连续 tensor)。

尺度1(T=12)追踪: enc_out=(6, 12, 8) → permute → (6, 8, 12)predict_layers[1](Linear 126)→ (6, 8, 6) → permute → (6, 6, 8)projection_layer(6, 6, 1) → reshape(2, 3, 6) → permute → (2, 6, 3)

尺度2(T=6)追踪: enc_out=(6, 6, 8) → permute → (6, 8, 6)predict_layers[2](Linear 66)→ (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)],三个尺度均为 (B, pred\_len, N),形状完全一致,可直接 stack。

§5.4 步骤 3 — stack + sum(在 forecast() 中执行)

python
dec_out = torch.stack(dec_out_list, dim=-1).sum(-1)

形状注解: torch.stackdim=-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)。每个位置的值为三个尺度预测之和:y^[b,t,n]=y^(0)[b,t,n]+y^(1)[b,t,n]+y^(2)[b,t,n]

为什么是简单求和而非加权平均?

等权求和(而非 softmax 加权)的设计动机:三个尺度预测的是同一目标的不同"频率视角",求和等价于集成(ensemble)。若某个尺度的预测完全无信息,其对应 predict_layers 的权重在训练中会通过梯度自动收缩(输出趋近于零),不需要额外的门控机制。相比加权平均,简单求和避免引入混合参数,保持模型简洁。

dim=-1 而非 dim=0 的原因

torch.stack(list, dim=0) 会把 batch 维合并,得到 (3, 2, 6, 3),再 sum(0) 也能得到 (2, 6, 3)。但 dim=-1 把尺度维加在末尾,语义更清晰:"每个时间步每个变量有来自 3 个尺度的预测",.sum(-1) 沿尺度维折叠,方向一目了然。

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