Appearance
PatchTST 总览与 Level 树
一句话定位
PatchTST = 把时间序列切成 patch + channel-independent Transformer。 核心洞见:把连续时间步视为 token(patch),attention 建模 patch 间依赖;同时每个变量独立送进模型,变量间完全不交互。
1. 论文问题:时序 Transformer 的三个困境
PatchTST(2023, ICLR)在一批时序 Transformer 之后提出,它要解决的不是"没有 Transformer",而是"已有的 Transformer 用法有问题"。
困境 1:O(L²) 的 attention 复杂度
传统 Transformer 把每个时间步当一个 token。序列长 512 → 512 个 token → attention 矩阵 512×512。序列变长,计算量平方增长。
传统做法(每步一个 token):
时序: [t1][t2][t3][t4][t5][t6][t7][t8][t9]
token: T1 T2 T3 T4 T5 T6 T7 T8 T9 → 9 个 token,attention O(9²)=O(81)困境 2:单点 token 语义太弱
一个时间步(比如"17:00 的温度")本身携带的信息极少,attention 学到的是点与点之间的相关,而不是更有意义的局部时间模式。
困境 3:channel-mixing 引入跨变量噪声
多变量时序里,不同变量(温度、湿度、风速)并不总是强相关。如果 attention 让变量之间互相影响,经常引入噪声反而使预测变差。
2. 两大核心创新
创新 1:Patch Token(时间片段作为 token)
类比:NLP 里的 BPE 把字符合并成词;ViT 把图片切成 16×16 的图块当 token。PatchTST 把时间序列切成重叠的时间片段(patch)当 token。
PatchTST 做法(patch_len=4,stride=2):
原始时序(长度 9,右端补 2):
[1][3][5][2][4][6][3][5][7][7][7]
0 1 2 3 4 5 6 7 8 9 10 ← 索引
滑窗切 patch(窗口=4,步长=2):
Patch 0: [1, 3, 5, 2] ← 索引 0~3
Patch 1: [5, 2, 4, 6] ← 索引 2~5
Patch 2: [4, 6, 3, 5] ← 索引 4~7
Patch 3: [3, 5, 7, 7] ← 索引 6~9
9 个时间步 → 4 个 patch token,attention O(4²)=O(16)效果:
- token 数量大幅减少(9→4),attention 更轻量
- 每个 token 包含局部时间结构(4 步的连续信息),语义更丰富
- 相邻 patch 有重叠(stride < patch_len),边界信息不丢失
创新 2:Channel-Independent(变量独立建模)
Channel-Mixing(传统做法):
变量 A: [a1, a2, a3, ...]
变量 B: [b1, b2, b3, ...] → 三条时序一起送进 Transformer
变量 C: [c1, c2, c3, ...] 变量之间 attention 可以互相影响
Channel-Independent(PatchTST):
变量 A: [a1, a2, a3, ...] ─→ 独立 Transformer ─→ 预测 A
变量 B: [b1, b2, b3, ...] ─→ 独立 Transformer ─→ 预测 B
变量 C: [c1, c2, c3, ...] ─→ 独立 Transformer ─→ 预测 C
(参数共享,同一套权重)实现方式:把 batch 维和 channel 维合并,(B=2, enc_in=3, time) → (B×enc_in=6, time),让 6 条时序以"独立样本"的身份同时通过同一个 Transformer。
论文实证:在大多数数据集上,channel-independent 比 channel-mixing 效果更好。作者认为:跨变量相关性是噪声信号,强行建模反而有害。
3. 论文架构图(原理层)
4. 论文组件 → 代码文件对应
| 论文组件 | 实现代码 | 对应精读文档 |
|---|---|---|
| Instance Normalization(RevIN 思路) | PatchTST.py:forecast() 第 97-100 行 | 03-Level3-forward主链 §3.1 |
| Channel-Independent 拆分 | PatchTST.py:forecast() 第 103 行 permute | 03-Level3-forward主链 §3.2 |
| Patch 切割 | Embed.py:PatchEmbedding.forward() 第 201-203 行 | 04A-PatchEmbedding精读 §B~D |
| Patch 投影 + 位置编码 | Embed.py:PatchEmbedding.forward() 第 205 行 | 04A-PatchEmbedding精读 §E~F |
| Transformer Encoder | Transformer_EncDec.py:Encoder / EncoderLayer | 04B-Encoder精读 §一~四 |
| Multi-Head Self-Attention | SelfAttention_Family.py:AttentionLayer / FullAttention | 04B-Encoder精读 §三~四 |
| Prediction Head | PatchTST.py:FlattenHead | 04B-Encoder精读 §五 |
| Denormalize | PatchTST.py:forecast() 第 122-123 行 | 03-Level3-forward主链 §3.9 |
5. 模型对比
| 对比项 | Informer | DLinear | PatchTST |
|---|---|---|---|
| token 是什么 | 单个时间步 | 无(直接线性) | 一段时间步(patch) |
| attention 复杂度 | O(L log L)(稀疏) | 无 | O(patch_num²),远小于 O(L²) |
| 多变量处理 | channel-mixing | 共享或独立线性 | channel-independent |
| Decoder | 有(生成式) | 无 | 无 |
| 归一化 | 无 | 无 | RevIN 思路(实例归一化) |
6. PatchTST 在完整 benchmark 中的坐标
6.1 顺序图(执行主线)
从外层 benchmark 到 PatchTST.forward 的完整调用链,和 DLinear / Informer 的外层完全相同,只在最后一步进入不同的模型。
和 DLinear / Informer 的区别只在 Level 8 之后:外层 benchmark 主线完全不变,从 _process(...) 往下才是 PatchTST 自己的逻辑。
6.2 抽象索引树
A~F 和 DLinear、Informer 的坐标树完全相同。PatchTST 独有的内容全在 G 节点展开。
7. 文档 Level 树
7. 全局 Toy 参数
本套文档所有文件统一使用以下参数,不得单独修改。
| 参数 | 值 | 含义 |
|---|---|---|
B | 2 | batch size |
seq_len | 9 | 输入序列长度 |
enc_in | 3 | 变量数(channels) |
d_model | 8 | embedding 维度 |
patch_len | 4 | 每个 patch 的长度(时间步数) |
stride | 2 | patch 滑动步长 |
padding | 2 | 右端填充(= stride,硬编码) |
pred_len | 7 | 预测步长 |
n_heads | 2 | attention 头数 |
派生量:
| 派生量 | 计算 | 值 | 出现位置 |
|---|---|---|---|
| 填充后长度 | 9 + 2 | 11 | PatchEmbedding padding 步 |
patch_num | int((9-4)/2 + 2) | 4 | unfold 输出 |
B×enc_in | 2 × 3 | 6 | channel-independent 虚拟 batch |
d_k | 8 // 2 | 4 | attention 每头维度 |
head_nf | 8 × 4 | 32 | FlattenHead 输入维度 |
为什么这组参数好:
B×enc_in(6) ≠ patch_num(4) ≠ d_model(8),Encoder 内部各 transpose 操作的轴变化都可见,不存在"两轴相等导致变化隐藏"的问题。
8. 文件索引
| 文件 | Level | 覆盖内容 | 关键 tensor 变化 |
|---|---|---|---|
01-Level1-配置进入PatchTST.md | L1 | 命令行 → PatchTST(config) | 无 tensor,参数流 |
02-Level2-数据进入PatchTST.md | L2 | DataLoader → forward() 入参 | (2,9,3) (2,11,3) |
03-Level3-forward主链.md | L3 | forecast() 9步骨架 | (2,9,3)→...→(2,7,3) |
04-Level4-精读总览.md | L4 | 精读索引 | - |
04A-PatchEmbedding精读.md | L4A | unfold + embed | (2,3,9)→(6,4,8) |
04B-Encoder精读.md | L4B | Attention + FFN + FlattenHead | (6,4,8)→(2,7,3) |
09-PatchTST全览流程图收束.md | 收束 | 端到端流程图 + tensor 汇总 | 全链 |
9. 推荐阅读路径
只想理解原理和直觉(不读代码):
00(§1-4 原理部分)→ 03(只看骨架)→ 09完整精读(从原理到每行代码):
00 → 01 → 02 → 03 → 04A → 04B → 0910. 代码层全局形状链(速览)
x_enc 输入: (2, 9, 3)
↓ normalize
↓ permute(0,2,1) → (2, 3, 9) ← channel-independent 起点
↓ PatchEmbedding → (6, 4, 8) ← B×enc_in=6 是虚拟 batch
↓ Encoder → (6, 4, 8)
↓ reshape → (2, 3, 4, 8)
↓ permute(0,1,3,2) → (2, 3, 8, 4)
↓ FlattenHead → (2, 3, 7)
↓ permute(0,2,1) → (2, 7, 3)
↓ denormalize
输出: (2, 7, 3) = (B, pred_len, enc_in)