Appearance
Layer2A DataEmbedding_inverted
覆盖
forecast()中的self.enc_embedding(x_enc, x_mark_enc)调用。这是 iTransformer 最核心的创新组件:通过一次permute把时间轴和变量轴互换,再用Linear(seq_len → d_model)把整条时序压成一个 token,最后将时间标记 concat 成额外的变量 token。
§1 在父层中的位置
forecast() 第二步:
python
enc_out = self.enc_embedding(x_enc, x_mark_enc)- 输入:
x_enc (3,12,5),x_mark_enc (3,12,4) - 输出:
enc_out (3,9,8)
§2 I/O 接口定义
| 参数 | shape(toy) | 含义 |
|---|---|---|
x (x_enc) | (3, 12, 5) | B=3,seq_len=12,N=5 变量 |
x_mark (x_mark_enc) | (3, 12, 4) | B=3,seq_len=12,time_dims=4 |
| 输出 | (3, 9, 8) | B=3,token_count=9(5+4),d_model=8 |
§3 顺序图
§4 语义分组图
§5 逐步精读
§5.0 完整原始代码
Embed.py — DataEmbedding_inverted:
python
class DataEmbedding_inverted(nn.Module):
def __init__(self, c_in, d_model, embed_type="fixed", freq="h", dropout=0.1):
super(DataEmbedding_inverted, self).__init__()
self.value_embedding = nn.Linear(c_in, d_model)
self.dropout = nn.Dropout(p=dropout)
def forward(self, x, x_mark):
x = x.permute(0, 2, 1)
# x: [Batch Variate Time]
if x_mark is None:
x = self.value_embedding(x)
else:
x = self.value_embedding(torch.cat([x, x_mark.permute(0, 2, 1)], 1))
# x: [Batch Variate d_model]
return self.dropout(x)iTransformer.__init__() 中实例化:
python
self.enc_embedding = DataEmbedding_inverted(
configs.seq_len, # c_in = seq_len = 12,不是 enc_in = N = 5
configs.d_model,
configs.embed,
configs.freq,
configs.dropout,
)§5.1 宏观逻辑:为什么叫 "inverted"
用最小例子(B=1, N=2, seq_len=4, time_dims=1, d_model=3)对比标准嵌入和 inverted 嵌入:
标准 DataEmbedding(Informer/Autoformer/PatchTST 使用):
输入 x: (1, 4, 2) B=1, L=4 时间步, N=2 变量
每个时间步 → 一个 token
token[0] = [v0_t0, v1_t0] ← t=0 时刻,两个变量的值
token[1] = [v0_t1, v1_t1] ← t=1 时刻
token[2] = [v0_t2, v1_t2] ← t=2 时刻
token[3] = [v0_t3, v1_t3] ← t=3 时刻
经过 TokenEmbedding: (1, 4, 2) → (1, 4, d_model=3)
输出序列长度 = 4(时间步数),注意力在时间步之间计算DataEmbedding_inverted(iTransformer):
输入 x: (1, 4, 2) B=1, L=4 时间步, N=2 变量
permute(0,2,1): (1, 2, 4) 轴翻转:B=1, Variate=2, Time=4
每个变量 → 一个 token(整条时序作为特征向量)
token[0] = [v0_t0, v0_t1, v0_t2, v0_t3] ← var_0 的 4 个时间步
token[1] = [v1_t0, v1_t1, v1_t2, v1_t3] ← var_1 的 4 个时间步
cat(x_mark_perm, dim=1): (1, 3, 4) 拼入 1 个时间 token
经过 Linear(4→3): (1, 3, 3)
输出序列长度 = 3(变量数 + 时间 token 数),注意力在变量之间计算"inversion" = 把时间轴从序列维(L)变成特征维,把变量轴从特征维变成序列维。
§5.2 步骤一:x.permute(0,2,1) — 轴翻转
python
x = x.permute(0, 2, 1)
# x: [Batch Variate Time]- 输入
x_enc (3, 12, 5)→ 输出(3, 5, 12) - 轴含义:
(B, seq_len, N)→(B, N, seq_len) - 原来的"时间轴"(seq_len=12)现在在最后一维,作为
Linear的输入特征;原来的"变量轴"(N=5)现在在第一序列维,成为 token 的计数维度
toy 数值追踪(batch 0):
permute 前 x_enc[0]: shape (12, 5)
x_enc[0, t, n] = 变量 n 在时刻 t 的值
permute 后 x[0]: shape (5, 12)
x[0, n, t] = 同一个数值,只是行列互换
x[0, 0, :] = [v0_t0, v0_t1, ..., v0_t11] ← var_0 的 12 步历史
x[0, 1, :] = [v1_t0, v1_t1, ..., v1_t11] ← var_1 的 12 步历史
...
x[0, 4, :] = [v4_t0, v4_t1, ..., v4_t11] ← var_4 的 12 步历史§5.3 步骤二:torch.cat — 拼接时间标记 token
python
x = self.value_embedding(torch.cat([x, x_mark.permute(0, 2, 1)], 1))先处理 x_mark.permute(0, 2, 1):
x_mark_enc (3, 12, 4)→ permute →(3, 4, 12)- 每行含义:
x_mark_perm[0, k, :] = [feat_k_t0, feat_k_t1, ..., feat_k_t11](第 k 个时间特征的 12 步序列)
然后 torch.cat([x, x_mark_perm], dim=1):
x (3, 5, 12)+x_mark_perm (3, 4, 12)→(3, 9, 12)- 沿变量轴 dim=1 拼接(不是加法),在 5 个变量 token 后面追加 4 个时间特征 token
与标准嵌入的区别:
| 嵌入方式 | 时间特征注入方式 | 结果 |
|---|---|---|
| 标准 DataEmbedding | value_emb(x) + temporal_emb(x_mark) | 两者相加,同一 token 混合 |
| DataEmbedding_inverted | cat([x_perm, x_mark_perm], dim=1) | 各自成为独立 token,不混合 |
标准嵌入把时间信息"加"到每个时间步 token 上,iTransformer 把时间信息"并列"为独立的伪变量 token,让注意力机制自行学习时间特征和变量特征之间的关系。
toy 数值追踪:
cat 前:
x (3, 5, 12) — 5行 = 5个变量 token,每行 12 个时间步值
x_mark_perm (3, 4, 12) — 4行 = 4个时间 token(hour/weekday/day/month 各一行)
cat 后: (3, 9, 12)
row 0: var_0 的 12 步历史
row 1: var_1 的 12 步历史
row 2: var_2 的 12 步历史
row 3: var_3 的 12 步历史
row 4: var_4 的 12 步历史
row 5: hour 特征的 12 步值(如 [8,9,10,11,12,13,14,15,16,17,18,19])
row 6: weekday 特征的 12 步值(如 [1,1,1,1,1,1,1,2,2,2,2,2])
row 7: monthday 特征的 12 步值
row 8: month 特征的 12 步值§5.4 步骤三:Linear(seq_len → d_model) — 整条时序压缩为 token
python
self.value_embedding = nn.Linear(c_in, d_model)
# c_in = configs.seq_len = 12,不是 configs.enc_in = N = 5python
x = self.value_embedding(torch.cat([...], 1))- 输入
(3, 9, 12)→Linear(12, 8)→ 输出(3, 9, 8) nn.Linear作用在最后一维:对每个 token 的 12 维时序向量做线性变换 → 8 维 d_model 表示
公式:
每个 token(变量或时间特征)的 12 步历史序列通过一个共享的
为什么 c_in = seq_len 而非 enc_in?
标准
DataEmbedding中c_in = enc_in = N,因为每个时间步 token 的特征维是 N(变量数)。DataEmbedding_inverted中c_in = seq_len = 12,因为 permute 后每个变量 token 的特征维是 seq_len(时间步数)。这是 "inverted" 的直接体现:特征维和序列维完全互换,c_in的语义也随之翻转。
toy 数值追踪(token_0 = var_0):
输入 x[0, 0, :] = [v0_t0, v0_t1, ..., v0_t11] ← 12 维向量
Linear(12→8):
output[0, 0, :] = W @ [v0_t0, ..., v0_t11] + b
= 8 维向量,编码了 var_0 的整段历史特征
(其余 8 个 token 各自独立经过同一个 W 和 b)§5.5 步骤四:Dropout + 输出
python
return self.dropout(x)- 训练时随机置零部分元素;推理时恒等变换
- 输出
(3, 9, 8)即 enc_out,进入 Encoder
§6 与标准 DataEmbedding 全对比
| 维度 | DataEmbedding(标准) | DataEmbedding_inverted |
|---|---|---|
| 输入 | (B, L, N) | (B, L, N) |
| permute | 无 | (B, N, L) |
| 时间特征处理 | + TemporalEmbedding(x_mark) | cat(x_mark_perm, dim=1) → 独立 token |
| 核心变换 | TokenEmbedding Conv1d(N→d_model) | Linear(L→d_model) |
| 输出 | (B, L, d_model) | (B, N+time_dims, d_model) |
| 输出序列维语义 | 时间步(L 个 token) | 变量(N+time_dims 个 token) |
| c_in | enc_in = N | seq_len = L |
| 位置嵌入 | ✅ PositionalEmbedding | ❌ 无(无需位置信息,token 顺序=变量顺序) |
§7 [:, :, :N] 裁剪的来源(与 §5.5 投影步骤的配合)
DataEmbedding_inverted 在 token 序列末尾拼入了 4 个时间 token(row 5-8),这些 token 参与 Encoder 的注意力计算,贡献时间先验信息。但最终预测阶段只需要 5 个变量的输出,因此 projection 步骤后用 [:, :, :N] 裁掉最后 4 个时间 token 的预测结果(详见 [[02-Layer1-forecast主链]] §5.5)。
enc_out (3, 9, 8)
row 0-4: var_0 ~ var_4 的 token ← 用于最终输出
row 5-8: time feature token ← 只参与注意力,最后被 [:,:,:5] 裁掉