1. Transformer核心原理:自注意力机制、多头注意力、位置编码、编码器-解码器结构详解

说实话,Transformer刚出来那会儿,我第一反应是——这玩意儿真的能用在时间序列上?毕竟它最初是为NLP设计的。但后来我在一个工业设备故障预测项目里硬着头皮试了一把,效果居然比LSTM好了不少。嗯,今天我就把这几块核心原理掰开了讲清楚。

1.1 自注意力机制:让每个点都看看全局

自注意力机制,说白了就是让序列里的每个位置,都能跟其他所有位置“打个招呼”。你想想看,传统RNN是一个接一个地看,距离远了就记不住。自注意力不一样,它一步到位,直接建立全局依赖。

具体怎么做的?我给你拆解一下:

  1. 每个输入位置生成三个向量:Query(查询)、Key(键)、Value(值)
  2. 用Query去跟所有Key做点积,算出注意力分数
  3. 分数经过softmax归一化,得到注意力权重
  4. 用权重去加权求和对应的Value

公式长这样:

Attention(Q, K, V) = softmax(Q × K^T / √d_k) × V

这里有个小细节——除以√d_k。为什么要除?我刚开始也纳闷。后来在项目里调参时发现,如果不除这个,点积结果会随着维度增大而爆炸,softmax就变成one-hot了,梯度也传不下去。所以这个缩放因子,其实是保命用的。

核心要点:自注意力的计算复杂度是O(n²),n是序列长度。时间序列如果太长(比如上万点),直接算会炸显存。我在处理高频传感器数据时,一般会先做下采样或者用窗口截断。

1.2 多头注意力:从不同角度理解数据

单头注意力就像一个人看问题,视角有限。多头注意力,就是让多个人同时看,每个人关注不同的模式。

具体实现上,我们把Q、K、V分别拆成h个头(h一般是8或16),每个头独立做自注意力,最后把结果拼起来再投影一次。

MultiHead(Q, K, V) = Concat(head_1, ..., head_h) × W_O
其中 head_i = Attention(Q × W_Q_i, K × W_K_i, V × W_V_i)

我记得有一次做电力负荷预测,单头注意力总是抓不住周期性模式。换成8头之后,有的头关注短期波动,有的头关注日周期,有的头关注周周期——效果立竿见影。

实战建议:头数不是越多越好。我试过32头,结果很多头学出来的注意力图几乎一样,白白浪费计算资源。一般8头或16头就够用了。

1.3 位置编码:给序列注入顺序信息

自注意力本身是“无序”的——你把序列打乱,它算出来的结果一模一样。但时间序列最讲究顺序,今天和昨天不一样,昨天和前天也不一样。

所以我们需要给每个位置加一个“身份证”,告诉模型:我是第几个时间步。

Transformer原文用的是正弦余弦编码:

PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))

为什么用正弦余弦?我个人的理解是:不同频率的正余弦波,能让模型学到相对位置关系。比如位置5和位置7的编码差,跟位置105和位置107的编码差,在某种频率下是相似的。

注意:对于时间序列,直接用绝对位置编码可能不够。我曾经在预测股票数据时,发现模型把“第100个时间步”当成了一个固定特征,换了数据就不灵了。后来改用可学习的位置编码,或者加上时间戳特征(比如星期几、几点钟),效果才稳定下来。

1.4 编码器-解码器结构:从输入到输出的完整流程

标准的Transformer由编码器和解码器组成。编码器负责理解输入序列,解码器负责生成输出序列。

编码器:N层堆叠,每层包含一个多头自注意力 + 一个前馈网络,都带残差连接和层归一化。

解码器:也是N层,但每层多了一个“交叉注意力”——用解码器的Query去查编码器的Key-Value。另外,解码器的自注意力还加了掩码,防止看到未来的信息。

我画了一张结构图,帮你理清关系:

Transformer 编码器-解码器结构 编码器 (Encoder) 输入序列 (x₁, x₂, ..., xₙ) + 位置编码 多头自注意力 残差连接 + 层归一化 前馈神经网络 残差连接 + 层归一化 × N 层堆叠 编码表示 解码器 (Decoder) 输出序列 (y₁, y₂, ..., yₘ) 掩码多头自注意力 防止看到未来信息 交叉注意力 Query来自解码器,Key/Value来自编码器 Key, Value 前馈神经网络 残差连接 + 层归一化 × N 层堆叠 输出概率分布

这张图里,编码器把输入序列变成一组“编码表示”,解码器一边看自己已经生成的部分,一边去编码表示里找相关信息,逐步生成输出。

对于时间序列预测,我们通常只用编码器部分(BERT风格),或者只用解码器部分(GPT风格)。我个人更常用编码器-解码器结构来做多步预测——编码器看历史窗口,解码器一步步生成未来值。

关键区别:编码器的自注意力能看到整个输入序列,解码器的自注意力只能看到当前位置及之前的位置(因为有掩码)。这个设计在时间序列里天然合理——你不能用未来的数据预测过去。

1.5 避坑指南:我踩过的几个坑

  • 序列太长怎么办? 我曾经处理过10万点的时间序列,直接算自注意力显存爆了。后来用了LogSparse注意力,只让每个位置跟对数间隔的位置做注意力,复杂度降到O(n log n)。
  • 位置编码要不要学? 对于固定长度的序列,可学习位置编码效果更好。但对于变长序列,正弦编码的泛化能力更强。我一般先试正弦编码,不行再换可学习的。
  • 多头注意力的头数怎么选? 我有个经验法则:d_model / h 最好在64左右。比如d_model=512,h=8,每个头64维,效果比较稳。

小技巧:训练时可以把注意力权重可视化出来,看看模型到底在关注哪些时间步。如果注意力图一片均匀,说明模型没学到东西;如果集中在几个点上,可能过拟合了。

好了,Transformer的核心原理就这些。下一节我们会动手实现一个完整的时间序列预测模型,到时候这些概念都会用代码跑一遍。


公众号:蓝海资料掘金营,微信deep3321