Skip to content

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.pyDataEmbedding_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

与标准嵌入的区别:

嵌入方式时间特征注入方式结果
标准 DataEmbeddingvalue_emb(x) + temporal_emb(x_mark)两者相加,同一 token 混合
DataEmbedding_invertedcat([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 = 5
python
x = self.value_embedding(torch.cat([...], 1))
  • 输入 (3, 9, 12)Linear(12, 8) → 输出 (3, 9, 8)
  • nn.Linear 作用在最后一维:对每个 token 的 12 维时序向量做线性变换 → 8 维 d_model 表示

公式:

tokenn=Wxn+b,WRdmodel×seq_len,xnRseq_len

每个 token(变量或时间特征)的 12 步历史序列通过一个共享的 W 压缩成 8 维向量。

为什么 c_in = seq_len 而非 enc_in?

标准 DataEmbeddingc_in = enc_in = N,因为每个时间步 token 的特征维是 N(变量数)。 DataEmbedding_invertedc_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_inenc_in = Nseq_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] 裁掉

§8 轴翻转与时间标记注入图

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