Skip to content

Level4 encoder 分解与线性预测总览

Abstract

这一篇对应 00-DLinear总览与Level树Level 4

它只讲一件事:

encoder(x_enc) 这一段内部到底怎样把历史窗口变成未来预测。

1. 当前层第一性

这一层存在的第一性是:

不直接把原序列丢进一个黑盒,而是先把它拆成 seasonal + trend,再分别做最简单的线性外推,最后再相加。

为什么这层重要:

  • 这就是 DLinear 的核心创新点之一
  • 也是 DLinear 和 Informer 最本质的差异之一
  • 你如果看完这层还不知道 encoder(...) 在干什么,就等于还没真正读懂 DLinear

2. 上下文

父节点:

下一层:

当前入口接口:

python
encoder(x_enc)

当前出口接口:

python
return x.permute(0, 2, 1)
# shape = (B, pred_len, C)

3. 这层输入到底是什么、总体作用是什么

3.1 输入 x_enc

python
x_enc.shape = (B, seq_len, C)

语义:

  • B
    • batch 里有几条样本。
  • seq_len
    • 每条样本给 DLinear 的历史长度。
  • C
    • 每个时间步有多少个变量。

当前真实例子里:

text
x_enc.shape = (4, 96, 7)

这表示:

  • 4 条样本
  • 每条样本 96 个历史时间步
  • 每个时间步 7 个变量

3.2 这层总体作用

这一层不是简单“做个 encoder”。

它在整体里的作用是:

把长度为 seq_len 的历史多变量序列,映射成长度为 pred_len 的未来多变量序列。

更具体一点:

  1. 先把历史序列拆成:
    • 快变化的 seasonal
    • 慢变化的 trend
  2. 对 seasonal 做一条线性外推
  3. 对 trend 做一条线性外推
  4. 把两条预测结果相加
  5. 输出未来 pred_len

4. 顺序图

5. 抽象树

6. encoder(...) 完整代码

位置:

python
def encoder(self, x):
    seasonal_init, trend_init = self.decompsition(x)
    seasonal_init, trend_init = seasonal_init.permute(0, 2, 1), trend_init.permute(
        0, 2, 1
    )
    if self.individual:
        seasonal_output = torch.zeros(
            [seasonal_init.size(0), seasonal_init.size(1), self.pred_len],
            dtype=seasonal_init.dtype,
        ).to(seasonal_init.device)
        trend_output = torch.zeros(
            [trend_init.size(0), trend_init.size(1), self.pred_len],
            dtype=trend_init.dtype,
        ).to(trend_init.device)
        for i in range(self.channels):
            seasonal_output[:, i, :] = self.Linear_Seasonal[i](
                seasonal_init[:, i, :]
            )
            trend_output[:, i, :] = self.Linear_Trend[i](trend_init[:, i, :])
    else:
        seasonal_output = self.Linear_Seasonal(seasonal_init)
        trend_output = self.Linear_Trend(trend_init)
    x = seasonal_output + trend_output
    return x.permute(0, 2, 1)

7. 中文注释版完整代码

python
def encoder(self, x):
    # 第一步:把原序列拆成 seasonal / trend
    seasonal_init, trend_init = self.decompsition(x)

    # 第二步:把时间维换到最后,方便线性层把 seq_len 映射到 pred_len
    seasonal_init = seasonal_init.permute(0, 2, 1)
    trend_init = trend_init.permute(0, 2, 1)

    if self.individual:
        # 每个通道自己一套线性层
        seasonal_output = torch.zeros(
            [seasonal_init.size(0), seasonal_init.size(1), self.pred_len],
            dtype=seasonal_init.dtype,
        ).to(seasonal_init.device)
        trend_output = torch.zeros(
            [trend_init.size(0), trend_init.size(1), self.pred_len],
            dtype=trend_init.dtype,
        ).to(trend_init.device)
        for i in range(self.channels):
            seasonal_output[:, i, :] = self.Linear_Seasonal[i](seasonal_init[:, i, :])
            trend_output[:, i, :] = self.Linear_Trend[i](trend_init[:, i, :])
    else:
        # 所有通道共享一套 seasonal / trend 线性头
        seasonal_output = self.Linear_Seasonal(seasonal_init)
        trend_output = self.Linear_Trend(trend_init)

    # 第三步:两路预测直接相加
    x = seasonal_output + trend_output

    # 第四步:回到 [B, pred_len, C]
    return x.permute(0, 2, 1)

8. 固定 toy 例子

继续使用固定 toy 例子:

text
x_enc =
[
  [1, 10],
  [2, 11],
  [3, 12],
  [4, 13],
]
shape = (1, 4, 2)

并固定:

  • seq_len = 4
  • pred_len = 2
  • moving_avg = 3
  • individual = False

9. 代码块 1:series_decomp(x)

代码:

python
seasonal_init, trend_init = self.decompsition(x)

9.1 输入/输出语义

输入:

  • x
    • 原始历史序列 (B, seq_len, C)

输出:

  • seasonal_init
    • 变化较快的残差部分。
  • trend_init
    • 变化较慢的平滑部分。

9.2 toy 张量逐步演变

这一段的细算过程放在:

这里只固定结果:

text
trend_init =
[
  [4/3, 31/3],
  [2,   11],
  [3,   12],
  [11/3,38/3],
]

seasonal_init =
[
  [-1/3, -1/3],
  [0,    0],
  [0,    0],
  [1/3,  1/3],
]

这一段在总体里的作用:

先把“慢变化”和“快变化”拆开,让后面每一路都更容易预测。

10. 代码块 2:permute(0, 2, 1)

代码:

python
seasonal_init = seasonal_init.permute(0, 2, 1)
trend_init = trend_init.permute(0, 2, 1)

10.1 输入/输出语义

输入:

  • (B, seq_len, C)

输出:

  • (B, C, seq_len)

为什么这么做:

  • nn.Linear(in_features=seq_len, out_features=pred_len) 作用在最后一维
  • 所以必须把时间维挪到最后一维

10.2 toy 张量逐步演变

原来的 seasonal_init

text
[
  [-1/3, -1/3],
  [0,    0],
  [0,    0],
  [1/3,  1/3],
]

按变量拆开后变成:

text
seasonal_init(channel 1) = [-1/3, 0, 0, 1/3]
seasonal_init(channel 2) = [-1/3, 0, 0, 1/3]

trend_init(channel 1) = [4/3, 2, 3, 11/3]
trend_init(channel 2) = [31/3, 11, 12, 38/3]

这一段在总体里的作用:

把“按时间排列的序列”改写成“每个变量一条长度为 seq_len 的向量”,这样线性层才能直接做时间维外推。

11. 代码块 3:两路线性头

代码:

python
seasonal_output = self.Linear_Seasonal(seasonal_init)
trend_output = self.Linear_Trend(trend_init)

11.1 输入/输出语义

输入:

  • seasonal_init: (B, C, seq_len)
  • trend_init: (B, C, seq_len)

输出:

  • seasonal_output: (B, C, pred_len)
  • trend_output: (B, C, pred_len)

这一段在总体里的作用:

分别把 seasonal 序列和 trend 序列,从历史长度 seq_len 直接线性映射到未来长度 pred_len

11.2 toy 张量逐步演变

详细 toy 可算过程放在:

这里只固定结果:

text
seasonal_output(channel 1) = [-1/3, 1/3]
trend_output(channel 1)    = [2, 3]

12. 代码块 4:两路相加并回到时间维

代码:

python
x = seasonal_output + trend_output
return x.permute(0, 2, 1)

12.1 输入/输出语义

输入:

  • seasonal_output
  • trend_output

输出:

  • x: (B, pred_len, C)

12.2 toy 张量逐步演变

第一变量:

text
seasonal_output = [-1/3, 1/3]
trend_output    = [2, 3]

相加后 = [5/3, 10/3]

第二变量同理。

最后把 (B, C, pred_len) 再 permute 回 (B, pred_len, C),得到:

text
[
  [5/3, 32/3],
  [10/3, 35/3],
]
shape = (1, 2, 2)

这一段在总体里的作用:

把两类模式的预测重新合成一条最终多变量预测序列。

13. 当前真实例子里的参数在这层控制什么

  • seq_len = 96
    • 决定两条线性头的输入维度。
  • pred_len = 24
    • 决定两条线性头的输出维度。
  • moving_avg = 25
    • 决定 trend_init 的平滑程度。
  • enc_in = 7
    • 决定有几条变量通道并行通过这套过程。
  • individual = False
    • 决定 7 个变量共享一套线性头,而不是每个变量一套。

14. 这一层最该固定什么

  1. DLinear 的主体不是注意力,不是 MLP 堆叠,而是:
    • 先分解
    • 再两路线性外推
    • 最后相加
  2. encoder(...) 在 DLinear 里就是主运算体。
  3. 读这层时,核心不是记住名字,而是记住:
text
x_enc
-> series_decomp
-> seasonal / trend
-> 两条线性头
-> 相加
-> (B, pred_len, C)

15. 下一步

继续看分解细节:

继续看线性头细节:

看完整体收束:

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