2、注意力机制基础:从Seq2Seq到Attention、Bahdanau Attention与Luong Attention、自注意力与交叉注意力

好,咱们正式开始聊注意力机制。这一章是地基,我建议你认真看。因为后面所有花里胡哨的多模态融合,追根溯源,都是从这里长出来的。

很多人一上来就啃Transformer,结果被QKV绕晕。其实没必要。注意力机制最初被发明出来,就是为了解决一个非常朴素的问题:机器翻译里的长句子记不住。

2.1 从Seq2Seq说起:那个“记不住”的年代

在Attention出现之前,主流的序列建模框架是Seq2Seq。它长这样:

  • 编码器(Encoder):读入源语言句子,生成一个“上下文向量”(Context Vector),通常就是RNN最后一个时间步的隐状态。
  • 解码器(Decoder):拿着这个上下文向量,逐词生成目标语言。

听起来挺合理对吧?但问题来了——这个上下文向量是固定长度的。

你想想看,一个10个词的句子,和一个50个词的句子,最后都压缩成一个几百维的向量。信息能不丢吗?

我曾经踩过的坑: 早年做文本摘要,用纯Seq2Seq。句子一长,生成的结果就开始“胡言乱语”,后半段基本在重复前面的话。后来我才明白,这叫“长距离依赖丢失”——解码器越往后,越记不住源句开头说了啥。

这就是Seq2Seq的瓶颈:信息瓶颈。解码器每一步都只能依赖同一个固定向量,没有“选择性关注”的能力。

2.2 Attention的诞生:让解码器学会“回头看”

2015年,Bahdanau等人提出了一个想法:别把源句压缩成一个向量了,把编码器每一步的隐状态都保留下来。解码器每生成一个词,自己去源句里“翻一翻”,找最相关的信息。

这就是注意力机制的核心思想:不是记住全部,而是知道该看哪里。

它的计算流程可以拆成三步:

  1. 计算注意力分数:解码器当前隐状态与编码器每个时间步的隐状态做匹配。
  2. 归一化:用Softmax把分数变成概率分布(权重)。
  3. 加权求和:用权重对编码器隐状态做加权平均,得到上下文向量。

用公式表达就是:

e_ti = score(s_{t-1}, h_i)      # 计算匹配分数
α_ti = exp(e_ti) / Σ_j exp(e_tj) # Softmax归一化
c_t  = Σ_i α_ti * h_i            # 加权求和得到上下文向量

这个上下文向量 c_t 会跟解码器当前隐状态拼接,一起决定下一个输出词。

核心理解: Attention本质上是一个软寻址操作。解码器发出查询(Query),在源句的键值对(Key-Value)里做匹配,取出最相关的信息。这个思想,贯穿了整个注意力家族。

2.3 Bahdanau Attention vs Luong Attention

同样是注意力,实现方式有差异。业界最经典的两个版本,就是Bahdanau和Luong。我当年刚接触时也傻傻分不清,后来在项目中对比过,才摸出门道。

对比维度 Bahdanau Attention(加性注意力) Luong Attention(乘法注意力)
提出时间 2015(ICLR) 2015(EMNLP)
分数计算方式 score = v^T · tanh(W1·h + W2·s) score = s^T · W · h 或 s^T · h
对齐方式 加性(Additive) 乘法(Multiplicative / Dot-Product)
计算复杂度 较高(需要额外参数W1、W2、v) 较低(可复用矩阵乘法)
上下文向量使用 用于计算当前步的隐状态 用于预测当前步输出
工程偏好 早期翻译任务常用 后续Transformer继承了点积思路

Bahdanau Attention 用的是加性对齐。它把解码器隐状态和编码器隐状态分别做线性变换,再通过一个 tanh 激活和向量 v 做内积。说白了,它学的是一个“匹配函数”。

Luong Attention 更直接。它用点积或双线性变换来计算相似度。你想想看,两个向量做点积,方向越一致,分数越高——这不就是“相似度”的直观体现吗?

我的个人习惯: 如果模型规模不大,或者需要快速迭代,我倾向于先用Luong的点积注意力。参数少,收敛快。但如果任务需要更精细的对齐(比如语音识别里的帧对齐),Bahdanau的加性注意力往往效果更好——它多了一层非线性,表达能力更强。

2.4 自注意力与交叉注意力

理解了上面的基础,这两个概念就很好懂了。它们本质上是“查询和键值对来自哪里”的区别。

2.4.1 自注意力(Self-Attention)

查询、键、值都来自同一个序列。

说白了,就是序列内部自己做“自我匹配”。每个位置都去跟序列里所有其他位置算相似度,然后聚合信息。

举个例子:句子“它没按时完成,因为它太复杂了。”——自注意力能让第二个“它”跟前面的“任务”关联起来,解决指代消解问题。

自注意力的计算流程:

  1. 输入序列 X,通过三个线性变换得到 Q、K、V。
  2. 计算 Q 与 K 的点积,缩放后做 Softmax。
  3. 用权重对 V 做加权求和。
Attention(Q, K, V) = softmax(Q · K^T / √d_k) · V

这里的 √d_k 是缩放因子。为什么要缩放?因为维度高了以后,点积的值会很大,Softmax 的梯度会变得非常小——训练就推不动了。

我曾经犯过的错: 第一次手写自注意力时,忘了加缩放因子。结果训练 loss 死活降不下去,梯度全消失了。查了两天才发现是这个问题。嗯,从此以后我写注意力代码,第一件事就是检查 scale。

2.4.2 交叉注意力(Cross-Attention)

查询来自一个序列,键和值来自另一个序列。

这就是前面讲的Seq2Seq注意力——解码器(查询)去关注编码器(键值对)。在多模态里,交叉注意力是融合不同模态信息的核心手段。

比如:

  • 文本作为查询,去关注图像的局部区域。
  • 音频作为查询,去关注视频帧。
  • 雷达点云作为查询,去关注相机图像。

交叉注意力的计算跟自注意力一模一样,只是 Q 和 K/V 的来源不同。

2.5 一张图看懂本章知识体系

下面这张SVG图,把本章的核心逻辑串起来了。从Seq2Seq的瓶颈出发,到Attention的通用范式,再到两种经典实现,最后落到自注意力和交叉注意力的应用场景。

注意力机制知识体系 Seq2Seq 信息瓶颈 Attention核心:查询 → 键值对匹配 → 加权聚合 经典注意力:Bahdanau vs Luong 自注意力 & 交叉注意力 Bahdanau:加性对齐 Luong:点积/双线性 自注意力:Q=K=V 交叉注意力:Q≠K=V 应用:机器翻译、文本摘要、多模态融合 核心脉络:从“记不住”到“选择性关注”,再到“内部自匹配”与“跨模态交互”

2.6 本章小结与工程建议

这一章的内容,是后面所有多模态融合技术的基石。我建议你记住几个关键点:

  • Attention的本质是软寻址:Query找Key,取出Value。这个范式贯穿始终。
  • 加性 vs 点积:加性注意力参数多、表达强;点积注意力计算快、易并行。选哪个,看你的场景和资源。
  • 自注意力 vs 交叉注意力:自注意力做序列内部建模,交叉注意力做序列间交互。多模态融合里,两者缺一不可。
工程落地小贴士: 如果你在实现多模态模型,建议先单独验证自注意力模块(单模态内部对齐)和交叉注意力模块(模态间交互)的效果。我习惯的做法是:先跑一个纯文本baseline,加上自注意力看提升,再加交叉注意力看融合增益。这样能清晰定位每个模块的贡献。