第2章:Transformer基础回顾:自注意力机制、多头注意力、位置编码、Transformer Encoder-Decoder架构

好,咱们正式开始。在动手做跨模态对齐之前,我强烈建议先把Transformer的老底儿翻出来晒一晒。你可能会说:“这玩意儿我早看烂了。” 但说实话,很多人在做多模态融合时翻车,恰恰是因为对这几个基础模块的理解还停留在“调包侠”阶段。

我个人习惯是,每接触一个新领域,先花半小时把核心组件的手推逻辑过一遍。嗯,咱们今天就这么干。

2.1 自注意力机制:让每个词都“看见”所有词

自注意力(Self-Attention)说白了就是:让序列里的每个元素,都能跟其他所有元素做一次“亲密度打分”。这个打分结果决定了在生成当前词的表征时,应该从其他词那里“借”多少信息过来。

公式长这样:

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

我来拆解一下:

  • Q(Query):当前词想问的问题。比如“我”想知道跟谁关系近。
  • K(Key):其他词给出的“标签”。比如“爱”的标签是“动词、情感”。
  • V(Value):其他词的实际内容。比如“爱”这个向量本身。
  • QK^T:算相似度。点积越大,说明越相关。
  • 除以√d_k:防止内积过大把softmax推向极端。d_k是每个头的维度,我一般设64或128。
  • softmax:归一化成概率,让注意力权重加起来等于1。

核心理解: 自注意力就是让模型学会“关注哪里”。在跨模态任务里,文本的“猫”应该去关注图像里的猫区域,而不是背景的草地。这就是对齐的雏形。

我在项目中遇到过一个问题:直接用原始点积做注意力,训练初期梯度特别不稳定。后来发现是没做缩放(除以√d_k)。你想想看,如果d_k=512,点积的方差大约是512,softmax几乎变成one-hot,梯度直接消失。所以这个除法,不是数学游戏,是保命操作。

2.2 多头注意力:从不同角度“偷看”信息

单头注意力有个毛病——它只能捕捉一种关系模式。比如在句子“他打碎了花瓶,然后捡起了碎片”里,“他”和“打碎”是主谓关系,“花瓶”和“碎片”是物与物的关系。一个头很难同时搞定这两种。

多头注意力(Multi-Head Attention)的解法很简单:复制多份Q、K、V,各自学不同的关注模式

MultiHead(Q, K, V) = Concat(head_1, ..., head_h) W_O
其中 head_i = Attention(QW_Q_i, KW_K_i, VW_V_i)

具体流程:

  1. 把Q、K、V分别通过8个(或12个、16个)不同的线性投影。
  2. 每个投影后的子空间维度是原来的1/h。比如原始512维,8个头的话每个头64维。
  3. 每个头独立做自注意力。
  4. 把8个头的输出拼起来,再经过一个线性层融合。

我的经验: 头数不是越多越好。我试过32个头,结果每个头学到的模式高度重复,反而浪费计算资源。一般8或12个头就够用。在跨模态任务里,我习惯让不同头分别关注“文本-图像”、“文本-音频”、“位置-语义”等不同对齐维度。

我曾经踩过一个坑:在实现时忘了对每个头的输出做残差连接和层归一化。结果训练到一半loss直接飞了。嗯,这里要注意,多头注意力的标准实现是:输出 = LayerNorm(x + MultiHead(x)),千万别漏了残差。

2.3 位置编码:给Transformer装上“顺序感”

自注意力本身是置换不变的。什么意思?你把“我打你”换成“你打我”,自注意力算出来的结果一模一样。这显然不合理,因为词序变了,语义完全相反。

位置编码(Positional Encoding)就是用来打破这种对称性的。Transformer原文用的是正弦余弦函数:

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

为什么用sin/cos?我个人理解有两点:

  • 相对位置信息:sin(α+β)可以展开成sinα和cosα的组合,模型能学到位置之间的相对关系。
  • 外推能力:训练时没见过更长的序列,但sin/cos函数本身是连续的,理论上可以处理任意长度。

避坑指南: 我曾经在做一个视频-文本对齐任务时,直接把位置编码加到视觉特征上。结果发现模型根本学不动。后来才意识到,视觉特征本身已经包含空间位置信息(比如CNN特征图里的坐标),再加位置编码反而造成干扰。正确的做法是:只在文本侧加位置编码,视觉侧用空间坐标编码

现在很多新模型改用可学习的位置编码(Learned Positional Embedding)。效果差不多,但可学习的编码在序列长度超过训练范围时表现很差。我个人习惯:如果任务序列长度固定(比如224x224的图像patch),用可学习的;如果长度可变(比如文本),用sin/cos。

2.4 Transformer Encoder-Decoder架构:编码与解码的“双人舞”

标准的Transformer由Encoder和Decoder两部分组成。咱们先看整体结构:

组件 输入 输出 核心操作
Encoder 源序列(如英文句子) 上下文表征(每个位置一个向量) 自注意力 + FFN
Decoder 目标序列(如中文翻译) 预测的下一个词 掩码自注意力 + 交叉注意力 + FFN

Encoder 负责把输入序列“看懂”。它由N层(通常是6层或12层)堆叠,每层包含:

  • 多头自注意力(每个位置都能看到所有位置)
  • 前馈神经网络(FFN,两层线性层+ReLU)
  • 残差连接 + 层归一化

Decoder 负责“生成”。它比Encoder多了两个关键设计:

  1. 掩码自注意力(Masked Self-Attention):生成第t个词时,只能看到前t-1个词,不能偷看未来的词。实现方式是把未来位置的注意力权重设为负无穷。
  2. 交叉注意力(Cross-Attention):Decoder的Query来自自身,Key和Value来自Encoder的输出。这就是“跨模态对齐”的雏形——文本生成时去“看”图像特征。

跨模态视角: 在图像描述任务里,Encoder处理图像特征(比如用CNN提取的patch序列),Decoder生成文本。交叉注意力层就是文本词去“关注”图像区域的桥梁。说白了,这就是最朴素的跨模态对齐。

我记得第一次手写Transformer时,在Decoder的掩码实现上卡了两天。如果你也遇到类似问题,记住一个口诀:“上三角矩阵,对角线及以下保留,以上置为负无穷”。用PyTorch的话就是 attn_mask = torch.triu(torch.ones(L, L), diagonal=1).bool(),然后把注意力分数里对应位置加上 -1e9

2.5 小结:从单模态到跨模态的“跳板”

回顾一下今天的内容:

  • 自注意力:让每个元素都能“看见”全局,是跨模态对齐的基础操作。
  • 多头注意力:从多个子空间并行学习,不同头可以关注不同模态的交互。
  • 位置编码:给无序的注意力机制注入顺序信息,在跨模态任务中要谨慎使用。
  • Encoder-Decoder架构:交叉注意力层天然适合做模态间的信息传递。

下一章,咱们会把这些组件组装起来,做一个真正的跨模态对齐模型。到时候你会发现,今天这些基础概念,每一个都会在实战中反复出现。嗯,打好地基,后面盖楼才稳。