Appearance
Layer 2A — DataEmbedding_wo_pos 精读
由
forecast()主链([[02-Layer1-forecast主链]])调用两次:enc_embedding(x_enc, x_mark_enc)和dec_embedding(seasonal_init, x_mark_dec)。
1. 在父层中的位置
forecast()
├─ self.enc_embedding(x_enc, x_mark_enc) ← DataEmbedding_wo_pos
└─ self.dec_embedding(seasonal_init, x_mark_dec) ← DataEmbedding_wo_pos与 Informer 的 DataEmbedding 唯一区别:不加 PositionalEmbedding。
2. I/O 接口定义
以 encoder embedding 为例(toy):
| shape | 含义 | |
|---|---|---|
输入 x | (2, 12, 5) | encoder 窗口 |
输入 x_mark | (2, 12, 4) | 时间特征(月/日/时/分) |
| 输出 | (2, 12, 8) | 嵌入后的 token 序列 |
decoder embedding 同理:(2,10,5)+(2,10,4) → (2,10,8)。
3. 顺序图(具体层)
4. 语义分组图(索引层)
5. 逐步精读
5.0 完整原始代码
python
class DataEmbedding_wo_pos(nn.Module):
def __init__(self, c_in, d_model, embed_type="fixed", freq="h", dropout=0.1):
super(DataEmbedding_wo_pos, 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)
else:
x = self.value_embedding(x) + self.temporal_embedding(x_mark)
return self.dropout(x)⚠️ position_embedding 在 __init__ 中定义但 forward 从未使用
position_embedding 在 __init__ 中定义但 forward 从未使用
DataEmbedding_wo_pos名字中的 "wo_pos" 即 "without position"。虽然__init__里初始化了self.position_embedding,但forward()里完全没有调用它(对比DataEmbedding的forward会加self.position_embedding(x))。
这个冗余的position_embedding属性占用了少量显存,但不影响正确性。
5.1 TokenEmbedding:值嵌入
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 xConv1d 要求输入 (B, C_in, L),原始 x 是 (B, L, C_in)=(2,12,5) → permute(0,2,1) → (2,5,12) → Conv1d(5→8, k=3, pad=1, circular) → (2,8,12) → transpose(1,2) → (2,12,8)。
为什么必须 transpose:
Conv1d 需要: (B, Channels, Length)
↑ ↑
输入通道数 序列长度
原始 x: (2, 12, 5) ← C_in=5 在最后一维,不对
permute(0,2,1): (2, 5, 12) ← Channels=5 ✓ Length=12 ✓
Conv1d 输出: (2, 8, 12)
transpose(1,2): (2, 12, 8) ← 还原到 (B, L, d_model) 格式toy 数值 — 输出通道 k=0,时间位置 t=0 的完整计算:
t=0 的 Conv1d 窗口覆盖 j=−1,0,+1 三个位置;circular padding 令 j=−1 循环至 t=11:
| t(实际位置) | x[0,t,0] | x[0,t,1] | x[0,t,2] | x[0,t,3] | x[0,t,4] |
|---|---|---|---|---|---|
| 11(j=−1,circular pad) | 0.2 | 0.5 | −0.3 | 0.8 | −0.1 |
| 0 (j=0) | 1.0 | −0.5 | 0.4 | −0.2 | 0.7 |
| 1 (j=+1) | 0.3 | 0.6 | −0.8 | 0.1 | −0.4 |
设权重 W[k=0],shape (c_in=5, kernel_size=3):
| c | j=−1 | j=0 | j=+1 |
|---|---|---|---|
| 0 | 0.1 | 0.2 | −0.1 |
| 1 | 0.3 | −0.2 | 0.4 |
| 2 | −0.1 | 0.3 | 0.2 |
| 3 | 0.2 | −0.3 | 0.1 |
| 4 | 0.4 | 0.1 | −0.2 |
按 c 分组展开:
完整输出 (2,12,8):B=2 个样本、L=12 个时间步、k=0..7 共 8 个输出通道各用独立权重做相同计算,并行生成所有 2×12×8=192 个输出值。
5.2 TemporalEmbedding:时间嵌入
TFB 的 embed_type="timeF",因此使用 TimeFeatureEmbedding(而非 TemporalEmbedding):
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)freq="h" → d_inp=4(对应小时频率的 4 个时间特征)。x_mark (2,12,4) → Linear(4,8) → (2,12,8)。
5.3 双路嵌入结构总览
| 论文/原理描述 | 代码实现 | 关键原因 |
|---|---|---|
| 值嵌入:原始时序 → token 向量 | TokenEmbedding Conv1d(c_in→d_model, k=3, circular) | 时序值用 Conv1d 捕捉局部相关 |
| 时间嵌入:日历特征 → 时间向量 | TimeFeatureEmbedding Linear(4→8) | freq="h",4维时间特征线性投影 |
| 无位置编码(Auto-Correlation 不需要) | position_embedding 仅在 __init__ 定义,forward 从不调用 | AutoCorrelation 通过 FFT lag 捕获周期,位置编码反而引入冗余 |
| 两路相加 | value_embedding(x) + temporal_embedding(x_mark) | 向量空间叠加,保持 d_model 维度 |
5.4 相加与 Dropout
python
x = self.value_embedding(x) + self.temporal_embedding(x_mark)
return self.dropout(x)两路 (2,12,8) 逐元素相加 → (2,12,8)。Dropout(p=0.1) 随机置零 10% 元素(训练时),测试时无效果。
toy 数值(batch=0, t=0, feature=0):设 token_emb[0,0,0]=0.43,time_emb[0,0,0]=-0.12,则 output[0,0,0]=0.31。