Skip to content

4B DataEmbedding

Abstract

这一篇是:

04-Level4-short_forecast五段总览4B DataEmbedding 这个子块的下钻文档。

只讲:

DataEmbedding 怎样把“数值张量 + 时间特征张量”变成 (B, L, d_model) 的隐藏表示。

1. 上下文

上一层:

下一层:

这一层的入口代码是:

python
enc_out = self.enc_embedding(x_enc, x_mark_enc)
dec_out = self.dec_embedding(x_dec, x_mark_dec)

这一层的输出是:

python
x_embed.shape = (B, L, d_model)

2. 当前层第一性

这一层存在的第一性是:

把原始数值序列和时间特征序列,变成统一 d_model 维的隐藏表示,供后续 attention 使用。

3. 本层入口参数与输出含义

3.1 输入

  • x
    • 数值输入,形状 (B, L, C)
  • x_mark
    • 时间特征输入,形状 (B, L, T)
  • c_in
    • 数值输入通道数 C
  • d_model
    • embedding 输出隐藏维
  • embed
    • 当前真实例子是 timeF
  • freq
    • 当前真实例子是 h

3.2 输出

  • x_embed
    • 统一隐藏表示,形状 (B, L, d_model)

4. 顺序图

5. 抽象树

6. 当前真实例子与 toy 例子

6.1 真实运行例子

当前真实例子里:

  • enc_in = dec_in = 7
  • d_model = 32
  • embed = "timeF"
  • freq = "h"

所以真实分支不是 TemporalEmbedding(fixed),而是:

python
TimeFeatureEmbedding(d_model=32, embed_type="timeF", freq="h")

6.2 固定 toy 例子

  • B = 1
  • L = 4
  • c_in = 2
  • d_model = 4
  • freq = "h"
  • embed = "timeF"
python
x = [
    [1, 10],
    [2, 11],
    [3, 12],
    [4, 13],
]  # (1, 4, 2)

x_mark = [
    [0.10, 0.20, 0.30, 0.40],
    [0.20, 0.20, 0.30, 0.50],
    [0.30, 0.20, 0.30, 0.60],
    [0.40, 0.20, 0.30, 0.70],
]  # (1, 4, 4)

7. 代码块 1:DataEmbedding.forward(...)

位置:

完整代码:

python
class DataEmbedding(nn.Module):
    def __init__(self, c_in, d_model, embed_type="fixed", freq="h", dropout=0.1):
        super(DataEmbedding, self).__init__()

        self.value_embedding = TokenEmbedding(c_in=c_in, d_model=d_model)
        self.position_embedding = PositionalEmbedding(d_model=d_model)
        self.temporal_embedding = (
            TemporalEmbedding(d_model=d_model, embed_type=embed_type, freq=freq)
            if embed_type != "timeF"
            else TimeFeatureEmbedding(d_model=d_model, embed_type=embed_type, freq=freq)
        )
        self.dropout = nn.Dropout(p=dropout)

    def forward(self, x, x_mark):
        if x_mark is None:
            x = self.value_embedding(x) + self.position_embedding(x)
        else:
            x = (
                self.value_embedding(x)
                + self.temporal_embedding(x_mark)
                + self.position_embedding(x)
            )
        return self.dropout(x)

7.1 toy 张量演变图

text
输入:
  x      = (1, 4, 2)
  x_mark = (1, 4, 4)

步骤 1: value_embedding(x)
  -> value = (1, 4, 4)

步骤 2: temporal_embedding(x_mark)
  -> temporal = (1, 4, 4)

步骤 3: position_embedding(x)
  -> position = (1, 4, 4)

步骤 4: 三路逐元素相加
  -> x_embed = value + temporal + position = (1, 4, 4)

步骤 5: dropout
  -> 输出仍是 (1, 4, 4)

7.2 这一段的 input / output 语义

  • 输入 x
    • 原始数值时间序列
  • 输入 x_mark
    • 当前时间步的连续时间特征
  • 输出 x_embed
    • 已融合数值信息、时间特征和位置信息的统一隐藏表示

8. 代码块 2:TokenEmbedding.forward(...)

完整代码:

python
class TokenEmbedding(nn.Module):
    def __init__(self, c_in, d_model):
        super(TokenEmbedding, self).__init__()
        padding = 1 if torch.__version__ >= "1.5.0" else 2
        self.tokenConv = nn.Conv1d(
            in_channels=c_in,
            out_channels=d_model,
            kernel_size=3,
            padding=padding,
            padding_mode="circular",
            bias=False,
        )

    def forward(self, x):
        x = self.tokenConv(x.permute(0, 2, 1)).transpose(1, 2)
        return x

8.1 toy 张量演变图

text
输入 x:
[
  [1, 10],
  [2, 11],
  [3, 12],
  [4, 13],
]  (1, 4, 2)

步骤 1: permute -> (B, C, L)
[
  [1, 2, 3, 4],
  [10, 11, 12, 13],
]  (1, 2, 4)

步骤 2: Conv1d(kernel_size=3, in_channels=2, out_channels=4)
  中间位置会看到长度为 3 的局部窗口
  例如位置 2 周围:
    通道1窗口 [1, 2, 3]
    通道2窗口 [10, 11, 12]

  为了理解卷积核,固定第 1 个输出通道的 toy 卷积核:
    通道1权重 = [1, 0, -1]
    通道2权重 = [0.5, 0, -0.5]

  再看位置 1(用 circular padding,左边会补最后一个时间步):
    通道1窗口 = [4, 1, 2]
    通道2窗口 = [13, 10, 11]

  则该输出通道在位置 1 的值为:
    1*4 + 0*1 + (-1)*2 + 0.5*13 + 0*10 + (-0.5)*11
    = (4 - 2) + (6.5 - 5.5)
    = 3

  其余输出通道也是同样道理,只是卷积核不同。
  所以卷积后得到 4 个输出通道
  -> (1, 4, 4)

步骤 3: transpose 回来
value =
[
  [v11, v12, v13, v14],
  [v21, v22, v23, v24],
  [v31, v32, v33, v34],
  [v41, v42, v43, v44],
]  (1, 4, 4)

8.2 这一段的 input / output 语义

  • 输入 x
    • 原始数值序列
  • 输出 value
    • 已包含局部数值模式的隐藏表示

9. 代码块 3:TimeFeatureEmbedding.forward(...)

完整代码:

python
class TimeFeatureEmbedding(nn.Module):
    def __init__(self, d_model, embed_type="timeF", freq="h"):
        super(TimeFeatureEmbedding, self).__init__()

        freq_map = {"h": 4, "t": 5, "s": 6, "m": 1, "a": 1, "w": 2, "d": 3, "b": 3}
        d_inp = freq_map[freq]
        self.embed = nn.Linear(d_inp, d_model, bias=False)

    def forward(self, x):
        return self.embed(x)

9.1 toy 张量演变图

text
输入 x_mark:
[
  [0.10, 0.20, 0.30, 0.40],
  [0.20, 0.20, 0.30, 0.50],
  [0.30, 0.20, 0.30, 0.60],
  [0.40, 0.20, 0.30, 0.70],
]  (1, 4, 4)

步骤 1: 当前 freq="h",所以每个时间步有 4 个连续特征
  t1 = [0.10, 0.20, 0.30, 0.40]
  t2 = [0.20, 0.20, 0.30, 0.50]
  t3 = [0.30, 0.20, 0.30, 0.60]
  t4 = [0.40, 0.20, 0.30, 0.70]

步骤 2: Linear(4, 4)
  每一行都乘同一组权重
  -> temporal =
[
  [t11, t12, t13, t14],
  [t21, t22, t23, t24],
  [t31, t32, t33, t34],
  [t41, t42, t43, t44],
]  (1, 4, 4)

9.2 这一段的 input / output 语义

  • 输入 x_mark
    • 连续时间特征
  • 输出 temporal
    • 线性映射到 d_model 维后的时间特征表示

10. 代码块 4:PositionalEmbedding.forward(...)

完整代码:

python
class PositionalEmbedding(nn.Module):
    def __init__(self, d_model, max_len=5000):
        super(PositionalEmbedding, self).__init__()
        pe = torch.zeros(max_len, d_model).float()
        pe.require_grad = False

        position = torch.arange(0, max_len).float().unsqueeze(1)
        div_term = (
            torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model)
        ).exp()

        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)

        pe = pe.unsqueeze(0)
        self.register_buffer("pe", pe)

    def forward(self, x):
        return self.pe[:, : x.size(1)]

10.1 toy 张量演变图

text
当前序列长度 L = 4
所以只取位置 0, 1, 2, 3 的位置编码

位置 0:
  [sin(0*a), cos(0*a), sin(0*b), cos(0*b)]
= [0, 1, 0, 1]

位置 1:
  [sin(1*a), cos(1*a), sin(1*b), cos(1*b)]

位置 2:
  [sin(2*a), cos(2*a), sin(2*b), cos(2*b)]

位置 3:
  [sin(3*a), cos(3*a), sin(3*b), cos(3*b)]

position = (1, 4, 4)

10.2 这一段的 input / output 语义

  • 输入 x
    • 这里只是借 x.size(1) 读出当前序列长度
  • 输出 position
    • 表示“这是第几个时间步”的固定位置向量

11. 代码块 5:三路相加

对应代码:

python
x = (
    self.value_embedding(x)
    + self.temporal_embedding(x_mark)
    + self.position_embedding(x)
)

11.1 toy 张量演变图

text
value =
[
  [v11, v12, v13, v14],
  [v21, v22, v23, v24],
  [v31, v32, v33, v34],
  [v41, v42, v43, v44],
]

temporal =
[
  [t11, t12, t13, t14],
  [t21, t22, t23, t24],
  [t31, t32, t33, t34],
  [t41, t42, t43, t44],
]

position =
[
  [p11, p12, p13, p14],
  [p21, p22, p23, p24],
  [p31, p32, p33, p34],
  [p41, p42, p43, p44],
]

逐元素相加后:
x_embed =
[
  [v11+t11+p11, v12+t12+p12, v13+t13+p13, v14+t14+p14],
  [v21+t21+p21, v22+t22+p22, v23+t23+p23, v24+t24+p24],
  [v31+t31+p31, v32+t32+p32, v33+t33+p33, v34+t34+p34],
  [v41+t41+p41, v42+t42+p42, v43+t43+p43, v44+t44+p44],
]  (1, 4, 4)

11.2 这一段的 input / output 语义

  • 输入 value
    • 数值局部模式表示
  • 输入 temporal
    • 时间特征表示
  • 输入 position
    • 位置表示
  • 输出 x_embed
    • 三种信息对齐后得到的统一隐藏表示

12. 当前层真正要固定什么

  1. 当前真实例子里,时间分支走的是 TimeFeatureEmbedding
  2. TokenEmbedding 负责数值局部模式
  3. TimeFeatureEmbedding 负责连续时间特征线性投影
  4. PositionalEmbedding 负责位置编码
  5. 三路都对齐到 d_model,再逐元素相加

13. 下一步

继续看:

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