2、Transformer核心原理回顾:Self-Attention机制详解,Multi-Head Attention,Positional Encoding,Encoder-Decoder架构
好,咱们直接进入正题。Transformer 这个东西,现在做自动驾驶的没人不知道。但说实话,很多人只是调包调习惯了,真让你手撕一个 Self-Attention,可能还得想半天。这一章,我就带你把这几个核心概念彻底捋一遍。
我个人习惯是,先理解原理,再看代码。不然你光看公式,很容易晕。咱们从最基础的 Self-Attention 开始。
2.1 Self-Attention 机制详解
Self-Attention,说白了就是让模型自己学会“该看哪里”。你想想看,一句话里每个词的重要性是不一样的。比如“前方有行人横穿马路”,模型得知道“行人”和“横穿”才是关键信息。
它的核心公式就一个:
Attention(Q, K, V) = softmax(Q * K^T / sqrt(d_k)) * V
嗯,这里要注意,d_k 是 Key 的维度。为什么要除以 sqrt(d_k)?因为点积的结果会随着维度增大而变大,softmax 之后梯度容易消失。我在项目中遇到过这个问题,当时训练一个 BEV 感知模型,注意力权重全变成 one-hot 了,排查了半天才发现是没做缩放。
咱们拆开来看:
- Q (Query):相当于“我要查什么”
- K (Key):相当于“我有什么”
- V (Value):相当于“实际的信息内容”
计算过程其实就三步:
- Q 和 K 做点积,得到注意力分数
- 除以
sqrt(d_k)做缩放,然后 softmax 归一化 - 用归一化后的权重去加权 V
举个例子,假设你有一串序列 [x1, x2, x3],每个 token 都会跟所有 token(包括自己)计算注意力。这样每个位置都能看到全局信息。这就是 Self-Attention 最牛的地方——长距离依赖捕捉能力。
关键点:Self-Attention 的计算复杂度是 O(n²),n 是序列长度。在自动驾驶中,如果处理的是 BEV 特征图,256x256 就是 65536 个 token,直接算会炸显存。所以实际工程中会用局部注意力或者稀疏注意力来优化。
2.2 Multi-Head Attention
一个头不够,那就多个头。Multi-Head Attention 的本质,就是把 Q、K、V 分别投影到多个子空间,每个头独立做 Self-Attention,最后再拼起来。
公式长这样:
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 头注意力之后,效果立竿见影。说白了,多头给了模型更多的“视角”。
工程建议:头数一般取 8 或 16,但要注意每个头的维度 d_k = d_model / h。如果 d_k 太小,每个头表达能力会下降。我一般保持 d_k 在 64 以上。
2.3 Positional Encoding
Self-Attention 本身是“无序”的。你想想看,如果把一句话里的词打乱顺序,注意力计算出来的结果是一样的。这显然不合理,因为“我打你”和“你打我”完全是两码事。
所以我们需要 Positional Encoding,给每个位置注入位置信息。
Transformer 原文用的是正弦余弦编码:
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
为什么用正弦余弦?因为这样可以让模型学到相对位置关系。比如 PE(pos+k) 可以表示为 PE(pos) 的线性变换。嗯,这个性质在实际中挺有用的。
但在自动驾驶场景里,我其实更常用可学习的 Positional Encoding。因为我们的输入往往是 BEV 特征图,有明确的 2D 空间结构。直接用可学习的 embedding,效果往往更好。
避坑指南:我曾经在训练一个时序融合模型时,忘了给不同时间步的特征加位置编码。结果模型把“当前帧”和“上一帧”的特征混在一起,输出全是重影。后来加上时间维度的位置编码,问题就解决了。
2.4 Encoder-Decoder 架构
Transformer 最初是为机器翻译设计的,所以有 Encoder 和 Decoder 两部分。
- Encoder:把输入序列编码成一组特征表示。每个 Encoder 层包含 Self-Attention + Feed Forward Network。
- Decoder:根据 Encoder 的输出,一步步生成目标序列。每个 Decoder 层包含 Masked Self-Attention + Cross-Attention + FFN。
Masked Self-Attention 是啥?就是让 Decoder 在生成第 t 个词时,只能看到前 t-1 个词,不能“偷看”未来的信息。这个在自动驾驶的轨迹预测里特别重要——你不能用未来的轨迹来预测当前的位置。
Cross-Attention 则是 Decoder 去“看” Encoder 的输出。比如在端到端自动驾驶中,Encoder 处理图像和雷达数据,Decoder 生成规划轨迹,Cross-Attention 就是连接感知和规划的桥梁。
不过说实话,在自动驾驶领域,我们更多用的是 Encoder-only 或者 Decoder-only 的变体。比如 BEVFormer 就是 Encoder-only,直接用 Transformer 编码多视角图像到 BEV 空间。而像 UniAD 这种端到端模型,则用了完整的 Encoder-Decoder 结构。
总结一下:Encoder 负责“理解”,Decoder 负责“生成”。在自动驾驶里,Encoder 做感知,Decoder 做预测和规划,这个思路非常自然。
2.5 代码实战:手写一个简化版 Self-Attention
光说不练假把式。咱们用 PyTorch 写一个最简的 Self-Attention,方便你理解内部流程。
import torch
import torch.nn as nn
import torch.nn.functional as F
class SimpleSelfAttention(nn.Module):
def __init__(self, d_model=256):
super().__init__()
self.d_model = d_model
self.W_q = nn.Linear(d_model, d_model)
self.W_k = nn.Linear(d_model, d_model)
self.W_v = nn.Linear(d_model, d_model)
def forward(self, x):
# x: [batch, seq_len, d_model]
B, L, D = x.shape
Q = self.W_q(x) # [B, L, D]
K = self.W_k(x) # [B, L, D]
V = self.W_v(x) # [B, L, D]
# 计算注意力分数
scores = torch.matmul(Q, K.transpose(-2, -1)) / (D ** 0.5)
# scores: [B, L, L]
attn_weights = F.softmax(scores, dim=-1)
out = torch.matmul(attn_weights, V)
return out
这段代码虽然简单,但核心逻辑都在了。实际工程中你还会用到 Dropout、LayerNorm、残差连接等。不过先把主干搞明白,其他的都是锦上添花。
小技巧:调试注意力模型时,我习惯把 attn_weights 可视化出来。如果看到某一行全是均匀分布,说明这个 token 没学到有用的注意力,可能是特征没对齐。
好了,这一章的内容就到这。Transformer 的核心原理,说白了就是“注意力机制 + 位置编码 + 编解码结构”。下一章咱们会深入讲它在自动驾驶感知中的具体应用,比如怎么用 Transformer 做 BEV 特征提取。
记住,理解原理是第一步,能动手实现才是真本事。