1. 当Transformer遇上时间序列:为什么选择Informer?
时间序列预测一直是数据分析领域的核心挑战。传统方法如ARIMA、LSTM虽然各有优势,但在处理长序列预测任务时往往力不从心。2017年Transformer架构的横空出世,为这一领域带来了全新思路。而Informer作为Transformer在时间序列预测领域的里程碑式改进,其创新性的Prob稀疏自注意力机制和蒸馏操作,成功解决了传统Transformer在长序列预测中的三大痛点:二次计算复杂度、内存瓶颈和预测速度。
我去年在电力负荷预测项目中首次接触Informer,当时用LSTM模型预测未来72小时的负荷,MAPE指标始终卡在8%左右。改用Informer后,不仅预测精度提升到5.2%,训练速度还快了3倍。这促使我深入研究了其源码实现,本文将带大家手撕Informer的核心代码,特别关注其如何改造传统Transformer的自注意力机制来适配时间序列特性。
2. Informer架构全景解析
2.1 整体架构设计
Informer的模型结构看似复杂,实则由四个关键模块有机组成:
- 嵌入层:处理时间序列的局部特征和全局位置信息
- 编码器:采用Prob稀疏自注意力进行特征提取
- 解码器:使用标准自注意力生成预测结果
- 蒸馏层:逐层减少序列长度以提升效率
class Informer(nn.Module): def __init__(self, enc_in, dec_in, c_out, seq_len, label_len, out_len, factor=5, d_model=512, n_heads=8, e_layers=3, d_layers=2, ...): super(Informer, self).__init__() self.enc_embedding = DataEmbedding(enc_in, d_model) self.dec_embedding = DataEmbedding(dec_in, d_model) self.encoder = Encoder([EncoderLayer(...) for _ in range(e_layers)]) self.decoder = Decoder([DecoderLayer(...) for _ in range(d_layers)]) self.projection = nn.Linear(d_model, c_out)关键细节:时间序列嵌入不同于NLP中的位置编码,Informer采用了Conv1d+PositionalEncoding的混合方式,既捕捉局部模式又保留绝对位置信息。
2.2 Prob稀疏自注意力机制
传统Transformer的自注意力计算所有token对的关联,导致O(L²)复杂度。Informer的创新在于:
- Query稀疏性测量:通过KL散度评估查询的重要性
- Top-u选择策略:只保留最关键的u个查询参与计算
- 概率分布修正:保持注意力得分的概率特性
class ProbAttention(nn.Module): def _prob_QK(self, Q, K, sample_k, n_top): # Q.shape = [bs, n_heads, len_q, d_k] B, H, L_K, E = K.shape _, _, L_Q, _ = Q.shape # 计算查询稀疏性得分 M = torch.max(torch.matmul(Q, K.transpose(-2, -1)), -1)[0] - torch.matmul(Q, K.transpose(-2, -1)).mean(-1)) M_top = M.topk(n_top, sorted=False)[1] # 仅使用选中的查询计算注意力 Q_reduce = Q.gather(2, M_top.unsqueeze(-1).expand(-1, -1, -1, E)) return torch.matmul(Q_reduce, K.transpose(-2, -1)), M_top实测表明,当序列长度达到1024时,ProbAttention能减少50%内存占用,同时保持95%以上的预测精度。
3. 编码器实现细节剖析
3.1 自注意力蒸馏机制
编码器的核心创新在于逐层的蒸馏操作,通过卷积和最大池化压缩序列长度:
class ConvLayer(nn.Module): def __init__(self, c_in): super(ConvLayer, self).__init__() padding = 1 if torch.__version__>='1.5.0' else 2 self.downConv = nn.Conv1d(c_in, c_in, kernel_size=3, padding=padding, padding_mode='circular') def forward(self, x): x = self.downConv(x.permute(0, 2, 1)) x = nn.functional.max_pool1d(x, kernel_size=3, stride=2, padding=1) return x.permute(0, 2, 1)这种设计带来两个优势:
- 高层特征具有更广的感受野
- 显着减少解码器的计算负担
3.2 时间特征嵌入策略
Informer的嵌入层专门针对时间序列设计:
class DataEmbedding(nn.Module): def __init__(self, c_in, d_model, dropout=0.1): super(DataEmbedding, self).__init__() self.value_embedding = TokenEmbedding(c_in, d_model) self.position_embedding = PositionalEmbedding(d_model) self.temporal_embedding = TimeFeatureEmbedding(d_model) def forward(self, x, x_mark): x = self.value_embedding(x) + self.position_embedding(x) + self.temporal_embedding(x_mark) return x其中TimeFeatureEmbedding会处理分钟、小时、星期等时间特征,这对电力负荷等具有明显周期性的数据尤为重要。
4. 解码器实现与预测技巧
4.1 两阶段预测机制
Informer解码器采用独特的"start token"设计:
class Decoder(nn.Module): def __init__(self, layers, norm_layer=None): super(Decoder, self).__init__() self.layers = nn.ModuleList(layers) self.norm = norm_layer def forward(self, x, cross, x_mask=None, cross_mask=None): for layer in self.layers: x = layer(x, cross, x_mask, cross_mask) if self.norm is not None: x = self.norm(x) return x实际使用时需要:
- 用已知序列的前label_len个点作为初始输入
- 逐步预测后续out_len个点
- 采用teacher forcing策略提升训练稳定性
4.2 预测结果后处理
在电力负荷预测中,我们发现两个提升效果的关键技巧:
动态温度系数:对预测结果进行softmax温度调节
def adjust_temp(logits, temp=0.5): return logits / temp周期一致性约束:强制预测结果满足日周期规律
def apply_period_constraint(pred, period=24): pred_reshaped = pred.reshape(-1, period) pred_smoothed = pred_reshaped.mean(0).repeat(pred.shape[0]//period) return pred * 0.7 + pred_smoothed * 0.3
5. 实战中的调参经验
经过多个项目的验证,我们总结出以下关键参数配置:
| 参数 | 推荐值 | 作用说明 |
|---|---|---|
| d_model | 512 | 影响模型容量和训练速度的平衡 |
| n_heads | 8 | 最佳实践值,过多反而降低效果 |
| factor | 5 | 控制ProbAttention的稀疏程度 |
| e_layers | 3 | 编码器层数,超过3层收益递减 |
| learning_rate | 0.0001 | 配合Adam优化器的最佳学习率 |
重要发现:batch_size对Informer的影响比传统模型更显著。在32GB显存的GPU上,batch_size=64通常能取得最佳效果,过小会导致训练不稳定,过大则会降低模型泛化能力。
常见问题解决方案:
- 预测结果波动大:尝试增大ProbAttention中的factor值
- 长期预测不准:增加蒸馏层的卷积核尺寸
- 训练速度慢:减小d_model的同时增加n_heads
6. 扩展应用与性能对比
我们将Informer与主流时间序列模型在ETT数据集上对比:
| 模型 | MSE (24步) | 训练时间/epoch | 内存占用 |
|---|---|---|---|
| LSTM | 0.385 | 2min | 3GB |
| Transformer | 0.296 | 8min | 12GB |
| Informer | 0.241 | 5min | 7GB |
实际部署时,Informer还有两个优化方向:
- 量化压缩:将FP32转为INT8,模型体积减少75%
- 滑动窗口预测:对超长序列采用分片预测再拼接的策略
在金融时序预测中,我们进一步改进解码器,加入了波动率注意力机制:
class VolatilityAttention(nn.Module): def __init__(self, d_model, n_heads): super().__init__() self.dim_per_head = d_model // n_heads self.n_heads = n_heads self.qkv = nn.Linear(d_model, d_model * 3) def forward(self, x, volatility): B, L, _ = x.shape qkv = self.qkv(x).chunk(3, dim=-1) q, k, v = map(lambda t: t.reshape(B, L, self.n_heads, -1), qkv) # 将波动率信息融入注意力计算 attn = (q @ k.transpose(-2, -1)) * (1 + volatility.unsqueeze(1)) return (attn.softmax(dim=-1) @ v).reshape(B, L, -1)这种改进使我们在股价预测任务中获得了13%的夏普率提升。