Appearance
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的未来多变量序列。
更具体一点:
- 先把历史序列拆成:
- 快变化的 seasonal
- 慢变化的 trend
- 对 seasonal 做一条线性外推
- 对 trend 做一条线性外推
- 把两条预测结果相加
- 输出未来
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 = 4pred_len = 2moving_avg = 3individual = 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_outputtrend_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. 这一层最该固定什么
- DLinear 的主体不是注意力,不是 MLP 堆叠,而是:
- 先分解
- 再两路线性外推
- 最后相加
encoder(...)在 DLinear 里就是主运算体。- 读这层时,核心不是记住名字,而是记住:
text
x_enc
-> series_decomp
-> seasonal / trend
-> 两条线性头
-> 相加
-> (B, pred_len, C)15. 下一步
继续看分解细节:
继续看线性头细节:
看完整体收束: