第二章:核心技术基础
在第一章中,我们从宏观层面了解了多模态学习的概念、发展历程和应用场景。从本章开始,我们将深入技术核心,详细讲解多模态大模型背后的核心技术原理。这些知识是理解后续章节(如CLIP、GPT-4V等模型架构)的基础。
虽然这一章会涉及一些技术细节,但我们会继续坚持费曼教学法,用丰富的类比和直观的解释帮助你理解这些概念。我们的目标是:即使你不具备深厚的数学背景,也能理解这些技术的工作原理和设计思想。
2.1 Transformer架构回顾
为什么我们需要Transformer?
在深入Transformer之前,让我们先理解为什么这个架构如此重要,它解决了什么问题。
回到2017年。那时候,处理序列数据(如文字、语音)的主流方法是循环神经网络(RNN,Recurrent Neural Network)及其各种变体,如LSTM(长短期记忆网络)、GRU(门控循环单元)。这些方法有一个共同的特点:它们按照顺序一个元素一个元素地处理数据。
用一个读书的例子来类比。想象你是一个RNN,你在读一本小说:
注释:RNN的工作方式就像我们人类读书——从第一页开始,逐字逐句地阅读。当你读到第100页时,你需要把第1页到第99页的内容都"记在脑子里",才能理解第100页在讲什么。
但这里有个问题:你的脑子(记忆容量)是有限的。当你读到第500页时,你可能已经忘记了第1页的很多细节。RNN也有这个问题——早期输入的信息在经过多次传递后,会逐渐"稀释"甚至丢失。
注释:这就是为什么早期的RNN模型很难处理很长的句子或文章。当句子长度超过几十个词时,模型往往难以建立词与词之间的长距离关联关系。比如,如果有一个句子是"我在法国长大,...(中间隔了20个词)...我会说法语",RNN可能无法记住"法国"和"法语"之间的关联。
另一个问题是计算效率。RNN必须按顺序处理,无法并行计算。假设一个句子有100个词,RNN需要一步步地处理完第1个词才能处理第2个,处理完第2个才能处理第3个...这就像一条流水线,每个工位必须等前一个工位完成才能开始工作。
注释:而现代GPU擅长的是并行计算——同时处理大量数据。RNN的串行特性无法充分利用GPU的计算能力,限制了模型的规模和训练效率。
Transformer的革命性突破
2017年,Google Brain团队发表了论文《Attention Is All You Need》,提出了Transformer架构,彻底改变了序列建模的方式。
这里有个有趣的语言学问题:为什么叫"Transformer"?它和《变形金刚》电影有关系吗?
注释:答案是没有关系。Transformer这个词来自单词"transform",意思是"转换"、"变换"。这个架构之所以叫Transformer,是因为它的核心功能是转换(transform)序列数据——把输入的序列转换成更有用的表示。中文有时会翻译为"变换器架构"或直接音译为"Transformer架构"。
Transformer的核心创新是:它完全抛弃了循环结构,只使用注意力机制来建模序列数据。
注释:这就像从"流水线式阅读"变成了"一次性全体扫描"。在Transformer中,模型在处理任何一个词时,都能"一眼看到"整个句子的所有其他词,并根据它们的相关性决定应该关注哪些词。
这解决了RNN的两个核心问题:
- 长程依赖问题:模型可以直接访问序列中的任何位置,不需要像RNN那样逐层传递信息
- 并行计算问题:所有词可以同时处理,不需要等待前一个词的结果
Transformer的核心组件
Transformer架构由两个主要部分组成:编码器(Encoder)和解码器(Decoder)。编码器负责理解输入序列,解码器负责生成输出序列。根据任务的不同,可以使用编码器-解码器结构(如机器翻译),或者只用编码器(如文本分类)、只用解码器(如语言建模)。
编码器-解码器架构图解:
┌─────────────────────────────────────────────────────────────────────────────┐
│ Transformer编码器-解码器架构 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 输入序列 │
│ │ │
│ ▼ │
│ ┌───────────────────────────────┐ │
│ │ 输入嵌入 + 位置编码 │ │
│ └───────────────┬───────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ 编码器堆栈(Encoder Stack) │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────┐│ │
│ │ │ 编码器层 1 ││ │
│ │ │ ├── 多头自注意力层(Multi-Head Self-Attention) ││ │
│ │ │ ├── 残差连接 + 层归一化 ││ │
│ │ │ ├── 前馈神经网络(FFN) ││ │
│ │ │ └── 残差连接 + 层归一化 ││ │
│ │ └─────────────────────────────────────────────────────────────┘│ │
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────────────────────────────────────────────────────┐│ │
│ │ │ 编码器层 2(结构相同) ││ │
│ │ └─────────────────────────────────────────────────────────────┘│ │
│ │ │ │
│ │ ... │
│ │ ▼ │
│ │ ┌─────────────────────────────────────────────────────────────┐│ │
│ │ │ 编码器层 N(通常6层) ││ │
│ │ └─────────────────────────────────────────────────────────────┘│ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────┐ │
│ │ 编码器输出(内存向量) │ │
│ └───────────────┬───────────────┘ │
│ │ │
│ ┌─────────────────────┼─────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ 解码器层1 │ │ 解码器层2 │ │ 解码器层N │ │
│ │ (含交叉 │ ────▶ │ (含交叉 │ ────▶ │ (含交叉 │ │
│ │ 注意力) │ │ 注意力) │ │ 注意力) │ │
│ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │
│ │ │ │ │
│ └────────────────────┴────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────┐ │
│ │ 输出概率分布 │ │
│ └───────────────┬───────────────┘ │
│ │ │
│ ▼ │
│ 输出序列 │
│ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ 关键组件说明: │ │
│ │ • 多头自注意力:序列内部的信息交互 │ │
│ │ • 交叉注意力:解码器"查看"编码器输出 │ │
│ │ • 前馈网络:非线性变换,增加模型容量 │ │
│ │ • 残差连接:防止梯度消失,加速训练 │ │
│ │ • 层归一化:稳定训练过程 │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
注释:编码器和解码器的内部结构类似,都由多层"注意力层"和"前馈神经网络"组成。不同之处在于解码器多了一层"交叉注意力"(Cross-Attention),用来在生成时"查看"编码器的输出。
Transformer层内部结构详图:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 单层Transformer内部结构 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 输入向量 X │
│ │ │
│ ├──────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────┐ ┌───────────────┐ │
│ │ 多头自注意力层 │ │ 残差连接 │ │
│ │ │ │ LayerNorm │ │
│ │ Q, K, V ← X │ │ X + Attention│ │
│ │ Attention(Q,K,V) │ └───────────────┘ │
│ │ MultiHead(...) │ │ │
│ └──────────┬──────────┘ │ │
│ │ │ │
│ └─────────────────────┐ │ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────────────┐ ┌───────────────┐ │ │
│ │ 残差连接 + 层归一化 │◀─────│ Add & Norm │◀─────────────────────┘ │
│ │ X + Attention(X) │ │ │ │
│ └──────────┬──────────┘ └───────────────┘ │
│ │ │
│ ├──────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────┐ ┌───────────────┐ │
│ │ 前馈神经网络 │ │ 残差连接 │ │
│ │ │ │ LayerNorm │ │
│ │ FFN(X) = W2(ReLU( │ │ X + FFN(X) │ │
│ │ W1(X) + b1)│ └───────────────┘ │
│ │ + b2) │ │ │
│ │ │ │ │
│ └──────────┬──────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 输出向量 Y │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
PyTorch实现:Transformer编码器层:
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
class MultiHeadAttention(nn.Module):
"""多头注意力机制实现"""
def __init__(self, d_model, num_heads, dropout=0.1):
"""
参数:
d_model: 模型维度(词嵌入维度)
num_heads: 注意力头数量
dropout: Dropout比率
"""
super().__init__()
assert d_model % num_heads == 0, "d_model必须能被num_heads整除"
self.d_model = d_model
self.num_heads = num_heads
self.d_k = d_model // num_heads # 每个头的维度
# 定义三个线性变换层,将输入投影到Q, K, V
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)
self.w_o = nn.Linear(d_model, d_model)
self.dropout = nn.Dropout(dropout)
self.scale = math.sqrt(self.d_k)
def forward(self, query, key, value, mask=None):
"""
前向传播
参数:
query: 查询张量 [batch_size, seq_len, d_model]
key: 键张量 [batch_size, seq_len, d_model]
value: 值张量 [batch_size, seq_len, d_model]
mask: 注意力掩码(可选)
返回:
输出张量 [batch_size, seq_len, d_model]
"""
batch_size = query.size(0)
# 1. 线性变换并分拆成多个头
query = self.w_q(query).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
key = self.w_k(key).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
value = self.w_v(value).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
# 2. 计算注意力分数
# [batch_size, num_heads, seq_len, seq_len]
scores = torch.matmul(query, key.transpose(-2, -1)) / self.scale
# 3. 应用掩码(如果有)
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
# 4. Softmax得到注意力权重
attention_weights = F.softmax(scores, dim=-1)
attention_weights = self.dropout(attention_weights)
# 5. 加权求和得到输出
context = torch.matmul(attention_weights, value)
# 6. 拼接多个头的输出
context = context.transpose(1, 2).contiguous().view(
batch_size, -1, self.d_model
)
# 7. 最后的线性变换
output = self.w_o(context)
return output
class PositionWiseFeedForward(nn.Module):
"""位置-wise前馈神经网络"""
def __init__(self, d_model, d_ff, dropout=0.1):
"""
参数:
d_model: 模型维度
d_ff: 前馈网络中间层维度(通常是d_model的4倍)
dropout: Dropout比率
"""
super().__init__()
self.linear1 = nn.Linear(d_model, d_ff)
self.linear2 = nn.Linear(d_ff, d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
"""
前向传播: [batch_size, seq_len, d_model]
-> [batch_size, seq_len, d_ff]
-> [batch_size, seq_len, d_model]
"""
return self.linear2(F.relu(self.linear1(x)))
class TransformerEncoderLayer(nn.Module):
"""Transformer编码器层"""
def __init__(self, d_model, num_heads, d_ff, dropout=0.1):
super().__init__()
self.self_attn = MultiHeadAttention(d_model, num_heads, dropout)
self.feed_forward = PositionWiseFeedForward(d_model, d_ff, dropout)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.dropout1 = nn.Dropout(dropout)
self.dropout2 = nn.Dropout(dropout)
def forward(self, x, mask=None):
"""
前向传播
"""
# 残差连接 + 层归一化(自注意力)
attn_output = self.self_attn(x, x, x, mask)
x = self.norm1(x + self.dropout1(attn_output))
# 残差连接 + 层归一化(前馈网络)
ff_output = self.feed_forward(x)
x = self.norm2(x + self.dropout2(ff_output))
return x
class TransformerEncoder(nn.Module):
"""Transformer编码器"""
def __init__(self, num_layers, d_model, num_heads, d_ff, dropout=0.1):
super().__init__()
self.layers = nn.ModuleList([
TransformerEncoderLayer(d_model, num_heads, d_ff, dropout)
for _ in range(num_layers)
])
self.norm = nn.LayerNorm(d_model)
def forward(self, x, mask=None):
for layer in self.layers:
x = layer(x, mask)
return self.norm(x)
# 使用示例
if __name__ == "__main__":
# 参数设置
d_model = 512 # 模型维度
num_heads = 8 # 注意力头数量
d_ff = 2048 # 前馈网络维度
num_layers = 6 # 编码器层数
seq_len = 100 # 序列长度
batch_size = 32 # 批次大小
# 创建编码器
encoder = TransformerEncoder(
num_layers=num_layers,
d_model=d_model,
num_heads=num_heads,
d_ff=d_ff
)
# 创建输入数据 [batch_size, seq_len, d_model]
x = torch.randn(batch_size, seq_len, d_model)
# 前向传播
output = encoder(x)
print(f"输入形状: {x.shape}")
print(f"输出形状: {output.shape}")
print("Transformer编码器前向传播测试通过!")
代码详解:
- MultiHeadAttention类:实现多头注意力机制,包括Q、K、V的线性变换、注意力计算、多头拼接
- PositionWiseFeedForward类:实现位置-wise前馈神经网络,两层全连接网络
- TransformerEncoderLayer类:单个编码器层,包含自注意力和前馈网络两个子层
- TransformerEncoder类:编码器堆叠,由多个编码器层组成
注意力分数计算可视化:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 注意力分数计算详细过程 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 假设句子: "我 喜欢 学习 人工智能" │
│ │
│ 步骤1: 计算Q·K^T(相似度矩阵) │
│ │
│ Q=我 Q=喜欢 Q=学习 Q=AI │
│ ┌──────┬──────┬──────┬──────┐ │
│ K=我 │ 0.9 │ 0.3 │ 0.2 │ 0.1 │ │
│ ├──────┼──────┼──────┼──────┤ │
│ K=喜欢 │ 0.2 │ 0.8 │ 0.4 │ 0.1 │ │
│ ├──────┼──────┼──────┼──────┤ │
│ K=学习 │ 0.1 │ 0.4 │ 0.9 │ 0.3 │ │
│ ├──────┼──────┼──────┼──────┤ │
│ K=AI │ 0.1 │ 0.1 │ 0.3 │ 0.8 │ │
│ └──────┴──────┴──────┴──────┘ │
│ │
│ 步骤2: 除以√d_k进行缩放(假设d_k=64) │
│ 相似度矩阵 / 8 │
│ │
│ 步骤3: Softmax归一化(以"学习"为例) │
│ │
│ 原始分数: [0.1, 0.4, 0.9, 0.3] │
│ exp值: [1.1, 1.5, 2.5, 1.4] │
│ 归一化后: [0.13, 0.22, 0.43, 0.22] │
│ │
│ 权重和 = 1.0 │
│ │
│ 步骤4: 加权求和(输出"学习"的上下文表示) │
│ │
│ 输出 = 0.13×V(我) + 0.22×V(喜欢) + 0.43×V(学习) + 0.22×V(AI) │
│ │
│ 解读: "学习"这个词在上下文中与自身关联最强(0.43), │
│ 其次是"喜欢"和"AI"(各0.22),最后是"我"(0.13) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
现在让我们详细了解Transformer的三个核心组件:自注意力机制、位置编码和前馈网络。
自注意力机制:Transformer的核心
自注意力(Self-Attention)是Transformer的核心创新,也是它能够有效处理序列数据的关键。
什么是自注意力?
简单来说,自注意力机制让序列中的每个位置都能"关注"序列中的所有其他位置,并根据相关性分配不同的权重。
注释:用一个会议讨论的场景来类比。想象一个项目会议,有5个人在讨论一个问题。在RNN模式下,每个人必须等前一个人说完才能发言(A说完B说,B说完C说...)。而在Transformer模式下,每个人可以同时听到所有人的发言,并根据发言内容的相关性决定重点听谁的。
在处理一个句子时,自注意力机制允许模型:
- 当处理"银行"这个词时,同时"看到"句子中的"存款"、"利息"、"行长"等相关词
- 根据上下文,正确理解"银行"在这个句子中的具体含义(是"河边的银行"还是"存钱的银行")
注释:这个能力对于理解自然语言至关重要。同一个词在不同上下文中有不同的含义。比如"意思"这个词:
- "这篇文章很有意思"(有趣)
- "我懂你的意思"(意图)
- "小费的意思"(红包)
只有根据上下文(其他词)才能正确理解每个"意思"的具体含义。自注意力机制正是提供了这种"看上下文"的能力。
注意力分数的计算过程:
在数学上,自注意力通过以下公式计算:
Attention(Q, K, V) = softmax(QK^T / √d_k) × V
注释:这个公式看起来很吓人,但它背后的思想可以用搜索引擎的类比来解释:
- Q(Query,查询):你搜索框里输入的内容,代表"我在找什么"
- K(Key,键):数据库中每个条目的标签,代表"这是什么"
- V(Value,值):搜索结果的实际内容,代表"我能给你什么"
计算过程是:
- 计算相似度:用Q和每个K做点积,得到相似度分数
- 缩放处理:除以√d_k(缩放因子,防止数值过大)
- Softmax归一化:把分数转换成概率分布(所有分数加起来等于1)
- 加权求和:用这些权重对V进行加权平均
注释:用找餐厅的类比来说明:
- 假设你想找"安静的咖啡馆"(Q = "安静的咖啡馆")
- 餐厅数据库里有很多餐厅,每个餐厅有一个标签(K = 餐厅特征)
- 餐厅A标签:["安静", "咖啡馆", "适合工作"] - 餐厅B标签:["热闹", "酒吧", "适合聚会"] - 餐厅C标签:["安静", "中餐", "商务"]
- 你搜索词和餐厅A标签的相似度最高(都是"安静"和"咖啡馆")
- 所以餐厅A的权重最高,会优先推荐给你
多头注意力:让模型学习多种关联模式:
在实际应用中,Transformer使用多头注意力(Multi-Head Attention),即同时进行多组注意力计算,每组使用不同的Q、K、V投影。
注释:「多头」的意思是"多组"。这就像在会议中,你可以同时关注多个方面:
- 第一组注意力:关注语法结构(谁和什么动词相关)
- 第二组注意力:关注语义关联(哪些词表达的是同一个概念)
- 第三组注意力:关注位置关系(词和词之间的距离)
每组注意力可以专注于不同类型的关联模式,然后所有头的输出会被拼接起来,形成最终的表示。
多头注意力图解:
输入向量
│
├──→ 分成h个头(每个头是一个Q,K,V投影)
│
├──→ 头1:计算注意力1 → 输出1
├──→ 头2:计算注意力2 → 输出2
├──→ ...(h个头)
└──→ 头h:计算注意力h → 输出h
│
├──→ 拼接所有头的输出
└──→ 线性变换得到最终输出
注释:h通常是8或16(代表8头或16头注意力)。通过多头注意力,模型可以同时学习多种不同类型的关联模式,比如语法层面的关联、语义层面的关联、位置层面的关联等。
位置编码:给序列加上顺序信息
Transformer没有循环结构,它"天生"不知道序列中元素的顺序。比如,如果没有位置信息,模型无法区分"狗咬人"和"人咬狗"这两个意思完全相反的句子。
注释:位置编码(Positional Encoding)的目的就是给每个位置加上一个"位置标签",让模型知道每个元素在序列中的位置。
论文中使用的是正弦-余弦位置编码:
- 对于位置pos和维度i,位置编码的值由sin(pos/10000^(2i/dmodel))和cos(pos/10000^(2i/dmodel))计算得到
注释:为什么用正弦和余弦函数?因为它们有两个很好的性质:
- 唯一性:每个位置都有一个唯一的位置编码
- 相对位置可学习:两个位置编码的差值包含了它们的相对距离信息,模型可以学习利用这种信息
位置编码可视化与实现:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 正弦-余弦位置编码原理 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 公式: │
│ PE(pos, 2i) = sin(pos / 10000^(2i/d_model)) (偶数维度) │
│ PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model)) (奇数维度) │
│ │
│ 参数说明: │
│ • pos: 序列中的位置(0, 1, 2, ...) │
│ • i: 维度索引(0, 1, 2, ..., d_model/2-1) │
│ • d_model: 模型维度 │
│ │
│ 示例(d_model=8, 序列长度=5): │
│ ┌────────┬──────────┬──────────┬──────────┬──────────┐ │
│ │ 位置 │ PE(pos,0)│ PE(pos,1)│ PE(pos,2)│ PE(pos,3)│ │
│ │ │ (sin) │ (cos) │ (sin) │ (cos) │ │
│ ├────────┼──────────┼──────────┼──────────┼──────────┤ │
│ │ pos=0 │ sin(0) │ cos(0) │ sin(0) │ cos(0) │ │
│ │ │ = 0.00 │ = 1.00 │ = 0.00 │ = 1.00 │ │
│ ├────────┼──────────┼──────────┼──────────┼──────────┤ │
│ │ pos=1 │ sin(0.1) │ cos(0.1) │ sin(0.01)│ cos(0.01)│ │
│ │ │ = 0.10 │ = 0.99 │ = 0.01 │ = 1.00 │ │
│ ├────────┼──────────┼──────────┼──────────┼──────────┤ │
│ │ pos=2 │ sin(0.2) │ cos(0.2) │ sin(0.02)│ cos(0.02)│ │
│ │ │ = 0.20 │ = 0.98 │ = 0.02 │ = 1.00 │ │
│ ├────────┼──────────┼──────────┼──────────┼──────────┤ │
│ │ pos=3 │ sin(0.3) │ cos(0.3) │ sin(0.03)│ cos(0.03)│ │
│ │ │ = 0.30 │ = 0.95 │ = 0.03 │ = 1.00 │ │
│ └────────┴──────────┴──────────┴──────────┴──────────┘ │
│ │
│ 位置编码矩阵可视化(d_model=512, seq_len=100): │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 热图: │ │
│ │ │ │
│ │ 维度 0 ████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ │
│ │ 维度 1 ████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ │
│ │ 维度 2 ████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ │
│ │ ... │ │
│ │ 维度 255 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░████ │ │
│ │ 维度 256 ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░████ │ │
│ │ ... │ │
│ │ 维度 511 ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ │
│ │ │ │
│ │ └────────────────── 序列位置 ──────────────────▶ │ │
│ │ │ │
│ │ 观察:高频维度变化快,低频维度变化慢 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
PyTorch实现:位置编码:
import torch
import math
class PositionalEncoding(nn.Module):
"""正弦-余弦位置编码实现"""
def __init__(self, d_model, max_seq_len=5000, dropout=0.1):
"""
参数:
d_model: 模型维度
max_seq_len: 最大序列长度
dropout: Dropout比率
"""
super().__init__()
self.dropout = nn.Dropout(p=dropout)
# 初始化位置编码矩阵 [max_seq_len, d_model]
pe = torch.zeros(max_seq_len, d_model)
position = torch.arange(0, max_seq_len, dtype=torch.float).unsqueeze(1)
# 计算除数: 10000^(2i/d_model)
div_term = torch.exp(
torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)
)
# 填充位置编码
pe[:, 0::2] = torch.sin(position * div_term) # 偶数维度
pe[:, 1::2] = torch.cos(position * div_term) # 奇数维度
pe = pe.unsqueeze(0) # [1, max_seq_len, d_model]
self.register_buffer('pe', pe)
def forward(self, x):
"""
前向传播
参数:
x: 输入张量 [batch_size, seq_len, d_model]
返回:
添加位置编码后的张量 [batch_size, seq_len, d_model]
"""
seq_len = x.size(1)
# 将位置编码添加到输入中(只取前seq_len个位置)
x = x + self.pe[:, :seq_len, :]
return self.dropout(x)
class RelativePositionalEncoding(nn.Module):
"""相对位置编码(更现代的方法)"""
def __init__(self, d_model, max_len=5000):
super().__init__()
self.d_model = d_model
self.max_len = max_len
# 可学习的相对位置嵌入
self.relative_attention_bias = nn.Embedding(2 * max_len, num_heads)
def forward(self, seq_len):
"""
生成相对位置偏差矩阵
参数:
seq_len: 序列长度
返回:
相对位置偏差矩阵 [num_heads, seq_len, seq_len]
"""
# 生成位置索引
query_position = torch.arange(seq_len).unsqueeze(1)
key_position = torch.arange(seq_len).unsqueeze(0)
relative_position = key_position - query_position
# 偏移到非负索引 [0, 2*seq_len-1]
relative_position += self.max_len - 1
# 获取位置偏差
position_bias = self.relative_attention_bias(relative_position)
return position_bias # [seq_len, seq_len, num_heads]
# 使用示例
if __name__ == "__main__":
# 参数设置
d_model = 512
max_seq_len = 100
batch_size = 32
# 创建位置编码层
pos_encoder = PositionalEncoding(d_model, max_seq_len)
# 创建输入数据 [batch_size, seq_len, d_model]
x = torch.randn(batch_size, max_seq_len, d_model)
# 前向传播
output = pos_encoder(x)
print(f"输入形状: {x.shape}")
print(f"输出形状: {output.shape}")
print("位置编码测试通过!")
# 验证位置编码的性质
pe = pos_encoder.pe[0] # [max_seq_len, d_model]
print(f"\n位置编码矩阵形状: {pe.shape}")
# 检查相邻位置的相似度
similarity_0_1 = torch.cosine_similarity(pe[0], pe[1], dim=0)
similarity_0_50 = torch.cosine_similarity(pe[0], pe[50], dim=0)
print(f"位置0和位置1的余弦相似度: {similarity_0_1:.4f}")
print(f"位置0和位置50的余弦相似度: {similarity_0_50:.4f}")
print("相邻位置更相似,符合预期!")
位置编码的性质验证:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 位置编码性质验证 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 性质1: 唯一性 - 每个位置有唯一的编码 │
│ ───────────────────────────────────────────────────────────────────────── │
│ 测试: 计算所有位置编码之间的汉明距离(或余弦相似度) │
│ 结果: 不同位置的编码不完全相同 │
│ │
│ 性质2: 相对位置可学习 │
│ ───────────────────────────────────────────────────────────────────────── │
│ 测试: 比较位置i和位置j的编码与位置i+1和位置j+1的编码 │
│ 预期: 差值相似(反映相对距离) │
│ │
│ 实际观察: │
│ • 位置0 vs 位置1: 相似度 ≈ 0.999 │
│ • 位置0 vs 位置10: 相似度 ≈ 0.97 │
│ • 位置0 vs 位置50: 相似度 ≈ 0.85 │
│ • 位置0 vs 位置99: 相似度 ≈ 0.60 │
│ │
│ 结论: 距离越近的位置编码越相似,模型可以学习这种关系 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
注释:每个位置都有一条独特的"波浪",代表它的位置编码。相邻位置的编码比较相似,远距离位置的编码差异较大,这正好反映了位置关系。
前馈网络:非线性变换
在每个Transformer层中,除了注意力层,还有一个前馈神经网络(Feed-Forward Network,FFN)。
前馈网络的结构很简单:两层全连接网络,中间有一个ReLU激活函数。
输入 → 线性变换 → ReLU → 线性变换 → 输出
(第一层) (第二层)
注释:虽然结构简单,但前馈网络在Transformer中扮演着重要的角色:
- 引入非线性:注意力机制本质上是线性的(矩阵乘法),需要非线性的激活函数来增加模型的表达能力
- 特征变换:对注意力层的输出进行进一步的非线性变换,提取更复杂的特征
- 容量补充:每个位置独立进行变换,增加了模型的参数量和容量
前馈网络结构详图:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 前馈神经网络内部结构 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 输入向量 X [d_model] │
│ │ │
│ ▼ │
│ ┌───────────────────┐ │
│ │ 线性变换 W1 │ │
│ │ [d_model → d_ff] │ │
│ │ 输入: X │ │
│ │ 输出: H = W1X + b1│ │
│ └─────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────┐ │
│ │ ReLU激活函数 │ │
│ │ H' = max(0, H) │ │
│ └─────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────┐ │
│ │ 线性变换 W2 │ │
│ │ [d_ff → d_model] │ │
│ │ 输入: H' │ │
│ │ 输出: Y = W2H' + b2│ │
│ └─────────┬─────────┘ │
│ │ │
│ ▼ │
│ 输出向量 Y [d_model] │
│ │
│ 典型参数: │
│ • d_model = 512 │
│ • d_ff = 2048 (= 4 × d_model) │
│ • 参数量: 512×2048 + 2048 + 2048×512 + 512 ≈ 200万 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
注释:可以把前馈网络想象成"思考深化器"。注意力层负责"看到"所有相关位置并进行信息汇聚,前馈网络则负责对这些信息进行"深度思考"——进一步加工、提炼、转化。
多模态场景的扩展:Cross-Attention
标准的自注意力是在同一模态内部进行的(文本-文本或图像-图像)。在多模态场景中,我们需要跨模态的注意力,让不同模态之间能够交互。
交叉注意力(Cross-Attention)就是解决这个问题的。它和自注意力的结构类似,但有一个关键区别:Q来自一个模态,而K和V来自另一个模态。
交叉注意力图解:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 交叉注意力机制 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 模态A(如文本) 模态B(如图像) │
│ │ │ │
│ ▼ ▼ │
│ Q向量 K和V向量 │
│ │ │ │
│ └──→ 注意力计算 ────────┘ │
│ │ │
│ ▼ │
│ 输出向量 │
│ │
│ 计算公式: │
│ CrossAttention(Q_A, K_B, V_B) = softmax(Q_A × K_B^T / √d_k) × V_B │
│ │
│ 多模态应用示例: │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 应用场景 │ Q来源 │ K,V来源 │ 作用 │ │
│ │ ───────────────────────────────────────────────────────────────── │ │
│ │ 图文理解 │ 文本特征 │ 图像特征 │ 用文本查询图像 │ │
│ │ 视觉问答 │ 问题向量 │ 图像特征 │ 根据问题找答案 │ │
│ │ 语音-文本对齐 │ 语音特征 │ 文本特征 │ 语音-文本对应 │ │
│ │ 视频描述 │ 文本前缀 │ 视频特征 │ 根据描述生成 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
PyTorch实现:交叉注意力:
class CrossAttention(nn.Module):
"""交叉注意力机制 - 多模态融合核心组件"""
def __init__(self, d_model, num_heads, dropout=0.1):
super().__init__()
assert d_model % num_heads == 0
self.d_model = d_model
self.num_heads = num_heads
self.d_k = d_model // num_heads
# Q来自模态A
self.w_q = nn.Linear(d_model, d_model)
# K和V来自模态B
self.w_k = nn.Linear(d_model, d_model)
self.w_v = nn.Linear(d_model, d_model)
self.w_o = nn.Linear(d_model, d_model)
self.dropout = nn.Dropout(dropout)
self.scale = math.sqrt(self.d_k)
def forward(self, query_modal_a, key_modal_b, value_modal_b, mask=None):
"""
前向传播
参数:
query_modal_a: 模态A的查询 [batch_size, seq_a, d_model]
key_modal_b: 模态B的键 [batch_size, seq_b, d_model]
value_modal_b: 模态B的值 [batch_size, seq_b, d_model]
mask: 掩码(可选)
返回:
输出张量 [batch_size, seq_a, d_model]
"""
batch_size = query_modal_a.size(0)
# 线性变换并分拆成多个头
query = self.w_q(query_modal_a).view(
batch_size, -1, self.num_heads, self.d_k
).transpose(1, 2)
key = self.w_k(key_modal_b).view(
batch_size, -1, self.num_heads, self.d_k
).transpose(1, 2)
value = self.w_v(value_modal_b).view(
batch_size, -1, self.num_heads, self.d_k
).transpose(1, 2)
# 计算注意力分数 [batch_size, num_heads, seq_a, seq_b]
scores = torch.matmul(query, key.transpose(-2, -1)) / self.scale
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
# Softmax得到权重
attention_weights = F.softmax(scores, dim=-1)
attention_weights = self.dropout(attention_weights)
# 加权求和
context = torch.matmul(attention_weights, value)
# 拼接多头的输出
context = context.transpose(1, 2).contiguous().view(
batch_size, -1, self.d_model
)
return self.w_o(context)
class MultimodalFusionBlock(nn.Module):
"""多模态融合模块 - 结合文本和图像"""
def __init__(self, d_model, num_heads, d_ff, dropout=0.1):
super().__init__()
self.cross_attn_text_to_image = CrossAttention(d_model, num_heads, dropout)
self.cross_attn_image_to_text = CrossAttention(d_model, num_heads, dropout)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.ffn = PositionWiseFeedForward(d_model, d_ff, dropout)
self.dropout1 = nn.Dropout(dropout)
self.dropout2 = nn.Dropout(dropout)
def forward(self, text_features, image_features):
"""
前向传播
参数:
text_features: 文本特征 [batch_size, seq_len_t, d_model]
image_features: 图像特征 [batch_size, seq_len_i, d_model]
返回:
融合后的文本特征和图像特征
"""
# 文本到图像的交叉注意力
text_enhanced = self.cross_attn_text_to_image(
text_features, image_features, image_features
)
text_features = self.norm1(text_features + self.dropout1(text_enhanced))
# 图像到文本的交叉注意力
image_enhanced = self.cross_attn_image_to_text(
image_features, text_features, text_features
)
image_features = self.norm2(image_features + self.dropout2(image_enhanced))
# 前馈网络处理
text_features = self.ffn(text_features)
image_features = self.ffn(image_features)
return text_features, image_features
# 使用示例
if __name__ == "__main__":
# 参数设置
d_model = 512
num_heads = 8
d_ff = 2048
# 创建模块
cross_attn = CrossAttention(d_model, num_heads)
fusion_block = MultimodalFusionBlock(d_model, num_heads, d_ff)
# 模拟数据
batch_size = 2
seq_len_text = 20
seq_len_image = 49 # 7x7图像patch
text_features = torch.randn(batch_size, seq_len_text, d_model)
image_features = torch.randn(batch_size, seq_len_image, d_model)
# 测试交叉注意力
output = cross_attn(text_features, image_features, image_features)
print(f"交叉注意力输入形状: {text_features.shape}")
print(f"交叉注意力输出形状: {output.shape}")
# 测试融合模块
fused_text, fused_image = fusion_block(text_features, image_features)
print(f"\n融合后文本特征形状: {fused_text.shape}")
print(f"融合后图像特征形状: {fused_image.shape}")
print("多模态融合模块测试通过!")
代码详解:
- CrossAttention类:实现交叉注意力机制,Q来自模态A,K和V来自模态B
- MultimodalFusionBlock类:完整的双向多模态融合模块,包含文本到图像和图像到文本的双向交叉注意力
注释:用图文理解的例子来说明:
- Q来自文本:"图片里有什么?"
- K和V来自图像:图像各个区域的特征向量
- 计算结果是:文本问题"映射"到图像的对应区域,找出答案所在的视觉位置
注释:交叉注意力是多模态融合的关键技术之一。它允许一种模态的信息去"查询"另一种模态的信息,实现真正的跨模态理解。
本节小结
让我们用简洁的总结回顾这一节的核心内容。
Transformer架构的核心组件:
- 自注意力机制:让序列中每个位置都能关注所有其他位置,根据相关性分配权重
- 多头注意力:同时进行多组注意力计算,学习多种关联模式
- 位置编码:给序列元素加上位置信息,让模型知道顺序
- 前馈网络:对注意力输出进行非线性变换,增加模型容量
- 交叉注意力:跨模态的注意力机制,实现不同模态之间的信息交互
Transformer相比RNN的优势:
- 直接建立长距离依赖,不存在信息稀释问题
- 支持并行计算,训练效率高
- 注意力机制提供了更好的可解释性
思考题:如果让你设计一个能同时处理文本和图像的模型,你会如何使用这些组件?请画出你的设计草图。
2.2 注意力机制详解
注意力机制的本质
在上一节中,我们初步了解了注意力机制的基本概念。在这一节中,我们将更深入地探讨注意力机制的工作原理,特别是在多模态融合中的应用。
首先,让我们重新审视一个问题:为什么叫"注意力"机制?
注释:这个名字来源于人类注意力的类比。当你在一幅复杂的画作中寻找某个物体时,你的眼睛会自动聚焦在相关的区域,而忽略不相关的信息。注意力机制正是模拟了这种认知过程——让模型能够"有选择地"关注输入的不同部分,而不是平等地处理所有信息。
注意力机制的核心思想可以概括为:在处理任何一个元素时,不是把所有信息同等对待,而是根据当前任务的需求,动态地决定哪些信息更重要,应该分配更多的"注意力"。
注释:用阅读理解来类比。当你做阅读理解题时,你会仔细阅读和问题相关的段落,而快速浏览不相关的段落。注意力机制让AI模型也能做类似的事情——在回答问题时,重点关注与问题相关的上下文。
Q、K、V的三角关系
在Transformer中,注意力机制的核心是Q、K、V三个矩阵。让我们深入理解它们之间的关系。
Q(Query,查询):代表"我想知道什么",是当前正在处理的位置的表示。它就像你在搜索引擎中输入的查询词。
注释:Q是从当前输入经过线性变换得到的。线性变换可以理解为"投影"——把原始的向量"投射"到一个新的空间,这个新空间是专门为计算注意力设计的。
K(Key,键):代表"我有什么",是序列中所有可能位置的表示。它就像搜索引擎数据库中每个网页的关键词标签。
注释:K和Q在同一个空间中进行比较,计算相似度。每个K对应一个位置的信息,用于判断这个位置是否与当前的Q相关。
V(Value,值):代表"我能提供什么",是序列中每个位置的原始信息。它就像搜索引擎返回的网页内容。
注释:V是实际携带信息的向量。最终的注意力输出是V的加权平均,权重由Q和K的相似度决定。
Q、K、V的关系可以用"面试"来类比:
注释:想象一个公司招聘:
- Q(查询):面试官问的问题,代表"我们需要什么样的人"
- K(键):每个候选人的简历,代表"我有什么技能和经验"
- V(值):候选人的详细信息,代表"我的具体经历"
面试过程就是计算Q和每个K的匹配程度,然后根据匹配程度决定关注哪个候选人的V(详细信息)。
注意力分数的计算
现在让我们详细了解注意力分数的计算过程。虽然数学公式看起来复杂,但背后的思想很简单。
步骤一:计算相似度
首先,计算Q和每个K的相似度。论文中使用的方法是点积:
score_i = Q · K_i
注释:点积是衡量两个向量相似度的常用方法。当两个向量的方向越相似(夹角越小),点积越大;方向越不相似,点积越小。
步骤二:缩放处理
为了防止点积的值过大(特别是当向量维度较高时),除以一个缩放因子√d_k:
scaled_score_i = score_i / √d_k
注释:d_k是K向量的维度。为什么要缩放?因为当维度较高时,向量的点积可能会变得很大,导致softmax函数进入梯度很小的区域,影响训练稳定性。缩放可以把方差控制在合理范围内。
步骤三:Softmax归一化
使用softmax函数把分数转换成概率分布:
weight_i = softmax(scaled_score_i) = exp(scaled_score_i) / Σexp(scaled_score_j)
注释:Softmax函数把所有分数转换成0到1之间的值,且所有值的和等于1。这使得这些值可以解释为"概率"或"权重"。
步骤四:加权求和
最后,用这些权重对V进行加权平均:
output = Σ(weight_i × V_i)
注意力计算可视化详图:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 注意力分数计算详细流程 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 输入: │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Q(查询向量): [0.5, 0.8, 0.2] │ │
│ │ K(键矩阵): │ │
│ │ K1: [0.9, 0.1, 0.3] ← 位置1 │ │
│ │ K2: [0.4, 0.7, 0.5] ← 位置2 │ │
│ │ K3: [0.2, 0.6, 0.8] ← 位置3 │ │
│ │ V(值矩阵): │ │
│ │ V1: [1.0, 2.0, 3.0] ← 位置1 │ │
│ │ V2: [4.0, 5.0, 6.0] ← 位置2 │ │
│ │ V3: [7.0, 8.0, 9.0] ← 位置3 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ 步骤1: 计算Q·K^T(点积相似度) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ score1 = Q·K1 = 0.5×0.9 + 0.8×0.1 + 0.2×0.3 = 0.51 │ │
│ │ score2 = Q·K2 = 0.5×0.4 + 0.8×0.7 + 0.2×0.5 = 0.82 │ │
│ │ score3 = Q·K3 = 0.5×0.2 + 0.8×0.6 + 0.2×0.8 = 0.74 │ │
│ │ │ │
│ │ 分数矩阵: [0.51, 0.82, 0.74] │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 步骤2: 缩放处理(除以√d_k,假设d_k=3) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ scaled_scores = scores / √3 ≈ scores / 1.732 │ │
│ │ [0.29, 0.47, 0.43] │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 步骤3: Softmax归一化 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ exp_values = [e^0.29, e^0.47, e^0.43] ≈ [1.34, 1.60, 1.54] │ │
│ │ sum_exp = 1.34 + 1.60 + 1.54 = 4.48 │ │
│ │ weights = exp / sum_exp │ │
│ │ weight1 ≈ 1.34 / 4.48 = 0.30 │ │
│ │ weight2 ≈ 1.60 / 4.48 = 0.36 │ │
│ │ weight3 ≈ 1.54 / 4.48 = 0.34 │ │
│ │ sum_weights = 0.30 + 0.36 + 0.34 = 1.00 ✓ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 步骤4: 加权求和(输出) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ output = 0.30×V1 + 0.36×V2 + 0.34×V3 │ │
│ │ = 0.30×[1.0,2.0,3.0] + 0.36×[4.0,5.0,6.0] + 0.34×[7.0,8.0,9.0]│ │
│ │ = [0.30+1.44+2.38, 0.60+1.80+2.72, 0.90+2.16+3.06] │ │
│ │ = [4.12, 5.12, 6.12] │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ 解读:Q与K2的相似度最高(0.47),因此V2被赋予最高的权重(0.36) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
不同注意力机制对比表:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 注意力机制类型对比 │
├─────────────────┬─────────────────┬─────────────────┬───────────────────────┤
│ 类型 │ Q来源 │ K,V来源 │ 典型应用 │
├─────────────────┼─────────────────┼─────────────────┼───────────────────────┤
│ 自注意力 │ 模态A │ 模态A │ 序列内部建模 │
│ (Self-Attention)│ │ │ (Transformer编码器) │
├─────────────────┼─────────────────┼─────────────────┼───────────────────────┤
│ 交叉注意力 │ 模态A │ 模态B │ 多模态融合 │
│ (Cross-Attention)│ │ │ (Transformer解码器) │
├─────────────────┼─────────────────┼─────────────────┼───────────────────────┤
│ 掩码自注意力 │ 模态A │ 模态A │ 因果语言建模 │
│ (Masked Self) │ (仅左侧) │ (仅左侧) │ (GPT等生成模型) │
├─────────────────┼─────────────────┼─────────────────┼───────────────────────┤
│ 稀疏注意力 │ 模态A │ 模态A │ 长序列处理 │
│ (Sparse) │ (局部+全局) │ (局部+全局) │ (Longformer) │
├─────────────────┼─────────────────┼─────────────────┼───────────────────────┤
│ 线性注意力 │ 模态A │ 模态A │ 高效长序列 │
│ (Linear) │ (核函数映射) │ (核函数映射) │ (Linformer) │
├─────────────────┼─────────────────┼─────────────────┼───────────────────────┤
│ 多查询注意力 │ 模态A │ 模态B │ 内存高效推理 │
│ (Multi-Query) │ (多个头) │ (共享K,V) │ (FlashAttention) │
└─────────────────┴─────────────────┴─────────────────┴───────────────────────┘
多模态融合中的注意力注释:这就是注意力的最终输出——一个综合了所有位置信息的向量,其中与当前查询更相关的信息被赋予了更高的权重。
注意力计算图解:
输入序列:["我", "喜欢", "学习", "人工智能"]
为"学习"这个词计算注意力:
┌─────────────────────────────────────────────────────────┐
│ Q("学习"的查询向量) │
│ │ │
│ ├──→ 与"我"的K计算相似度 → 权重0.1 │
│ ├──→ 与"喜欢"的K计算相似度 → 权重0.15 │
│ ├──→ 与"学习"的K计算相似度 → 权重0.65(自己,重要) │
│ └──→ 与"人工智能"的K计算相似度 → 权重0.1 │
│ │
│ 加权求和输出 = 0.1×V("我") + 0.15×V("喜欢") │
│ + 0.65×V("学习") + 0.1×V("人工智能") │
└─────────────────────────────────────────────────────────┘
多模态融合中的注意力
在多模态场景中,注意力机制有一个非常重要的应用:跨模态注意力(Cross-Modal Attention)。这是让不同模态能够"对话"的关键技术。
图文理解的跨模态注意力:
注释:假设我们要让AI理解一张图片和对应的文字描述。图片被分成多个区域,每个区域有一个视觉特征向量;文字被分成多个词,每个词有一个词向量。
跨模态注意力允许:
- 用文本的特征(Q)去"查询"图像的特征(K, V)
- 或者用图像的特征(Q)去"查询"文本的特征(K, V)
例子:视觉问答(VQA)
假设用户问:"图里有几只猫?"
注释:处理流程如下:
- 图像编码:把图片分成多个区域(如9宫格),每个区域提取视觉特征
- 文本编码:把问题编码为向量
- 跨模态注意力:
- Q = 问题向量("图里有几只猫?") - K = 各个图像区域的视觉特征 - V = 各个图像区域的视觉特征
- 注意力计算:找出问题与哪些图像区域最相关(可能是猫所在的区域)
- 答案生成:基于注意力结果,生成答案"一只"
跨模态注意力图解:
问题:"图里有几只猫?"
│
▼
问题编码 → Q(查询向量)
│
│ 跨模态注意力计算
▼
┌─────────────────────────────────────────┐
│ │
│ 图像区域1(天空) K1 → 低权重 │
│ 图像区域2(草地) K2 → 低权重 │
│ 图像区域3(猫) K3 → 高权重 ★ │
│ 图像区域4(猫) K4 → 高权重 ★ │
│ │
└─────────────────────────────────────────┘
│
▼
加权求和 → 聚焦于猫区域的特征 → 答案:"两只"
多模态融合的其他方式:
除了跨模态注意力,还有其他几种常见的多模态融合方式:
早期融合(Early Fusion):在输入层就把不同模态的数据拼接起来,然后统一处理。
注释:优点是模态间的交互在最早的阶段就开始了;缺点是不同模态的数据形式可能差异很大,直接拼接效果不好。
晚期融合(Late Fusion):分别用不同的编码器处理各个模态,在决策层(最后一步)再把各模态的表示拼接或融合。
注释:优点是各模态可以独立优化;缺点是模态间的交互不够深入,可能错过模态间的细粒度关联。
中间融合(Intermediate Fusion):在多个层级上进行模态间的交互和融合。
注释:这是目前最常用的方法,结合了早期融合和晚期融合的优点。Transformer的交叉注意力机制就是一种典型的中间融合方式。
自注意力和交叉注意力的对比
为了更清晰地理解,让我们对比一下自注意力和交叉注意力:
| 对比维度 | 自注意力 | 交叉注意力 |
|---|---|---|
| Q的来源 | 模态A | 模态A |
| K的来源 | 模态A | 模态B |
| V的来源 | 模态A | 模态B |
| 作用 | 模态内部的信息交互 | 跨模态的信息交互 |
| 应用场景 | 序列内部建模 | 多模态融合 |
| Transformer中的位置 | 编码器内部 | 解码器查询编码器 |
注释:自注意力让同一模态内的各个位置能够相互"交流",比如一句话中各个词之间的关联。交叉注意力让不同模态之间能够"交流",比如文本和图像之间、文本和音频之间。
本节小结
让我们用简洁的总结回顾这一节的核心内容。
注意力机制的本质:
- 动态分配权重,让模型能够"有选择地"关注输入的不同部分
- 核心计算是Q(查询)和K(键)的匹配,然后用权重对V(值)进行加权平均
Q、K、V的关系:
- Q = "我在找什么"(当前任务的需求)
- K = "我有什么"(候选信息的内容标签)
- V = "实际内容"(候选信息的具体内容)
多模态融合中的应用:
- 跨模态注意力允许用一种模态的Q去查询另一种模态的K和V
- 这是实现图文理解、视觉问答等任务的关键技术
思考题:如果让你设计一个能同时理解文本、图像和音频的三模态模型,你会如何使用注意力机制来实现它们之间的两两交互?
2.3 特征表示学习
什么是特征表示?
在理解了Transformer和注意力机制之后,我们需要了解另一个核心概念:特征表示学习(Feature Representation Learning)。
什么是特征表示?
简单来说,特征表示就是把复杂的数据(如图像、文本、音频)转换成计算机能够处理的数值向量的过程。
注释:计算机只能处理数字,不能直接理解一张图片里有什么、一段文字是什么意思。特征表示的任务就是找到一种方法,把这些"人类能理解"的信息转换成"计算机能处理"的数值形式,同时保持信息的完整性——转换后的向量应该保留原始数据的关键信息。
用"简历"来类比特征表示:
注释:想象你要向别人介绍你自己。你有丰富的人生经历——你上过什么学校、做过什么工作、有什么技能、得过什么奖...这些信息是复杂的、立体的、难以用一句话概括的。
现在,你需要用一份简历来概括自己。简历的设计很巧妙——它用有限的篇幅(几个板块、几十行文字)就抓住了你最重要的信息。看过你简历的人,虽然没有真正了解你的全部人生,但已经对你有了基本的了解。
注释:特征表示就像是给数据"写简历"的过程:
- 原始数据(你的人生经历)很复杂、维度很高
- 特征向量(你的简历)很简洁、维度较低
- 但特征向量保留了原始数据的关键信息
- 看过特征向量,就能对原始数据有基本的了解
不同模态的原始数据形式:
让我们看看不同模态的原始数据是什么样的,以及它们如何被转换成特征向量:
文本:文本是由字符、词或词组组成的序列。每个词都可以被转换成一个向量(词嵌入),一个句子可以表示为多个词的向量的组合或聚合。
注释:早期的词表示方法是独热编码(One-Hot Encoding)——假设你有10000个词,每个词表示为一个10000维的向量,只有一个位置是1,其他都是0。这种方法的问题是完全忽略了词与词之间的语义关系——"猫"和"狗"的独热编码没有任何相似度,尽管它们都是动物。
注释:现代的方法是词嵌入(Word Embedding)——把每个词表示为一个稠密的低维向量(如300维)。在这个向量空间中,语义相似的词距离较近:"猫"和"狗"的向量距离近,"猫"和"汽车"的向量距离远。Word2Vec、GloVe是代表性的词嵌入方法。
图像:图像是由像素组成的二维矩阵。每个像素有颜色值(RGB三个通道)。一张224×224的彩色图片有224×224×3 = 150528个数值。
注释:直接用原始像素作为特征有两个问题:
- 维度太高:150528维的向量太大,计算和存储成本高
- 语义不明:像素值只代表颜色,不包含"这是猫还是狗"这样的语义信息
注释:图像特征提取使用卷积神经网络(CNN,Convolutional Neural Network)或Vision Transformer(ViT)。这些方法能够:
- 把高维的像素信息"压缩"成低维的特征向量(如512维或1024维)
- 提取语义信息——特征向量包含了"图中有什么物体、什么场景"的语义信息
ViT的工作原理(Vision Transformer):
- 把图像切成若干个"patch"(如16×16像素的小块)
- 把每个patch展平并通过线性变换成向量
- 加入位置编码(告诉模型每个patch的位置)
- 输入Transformer编码器进行处理
- 用第一个位置的输出(或所有patch输出的聚合)作为图像特征
音频:音频是随时间变化的波形信号。原始音频数据是时间序列,每个时间点有一个振幅值。一段10秒、采样率44.1kHz的音频有441000个样本点。
注释:直接用原始波形作为特征同样面临维度太高、语义不明的问题。音频特征提取的常用方法是:
- 频谱特征(Mel Spectrogram):把时域信号转换成频域表示,然后按时间排列成二维"图像"
- 预训练音频编码器:如Whisper、AudioCLIP等,在大规模音频数据上预训练,能够提取语义丰富的音频特征
频谱特征的工作原理:
- 对音频进行短时傅里叶变换(STFT),得到频谱
- 把频率轴转换成Mel尺度(更符合人类听觉感知)
- 对幅度取对数,得到对数梅尔频谱(Log Mel Spectrogram)
- 结果是一个二维矩阵,可以像图像一样处理
注释:处理后的频谱图就像一张"声音的图片",水平轴是时间,垂直轴是频率,颜色深浅代表能量强弱。人类语音的频谱图有明显的条纹结构(共振峰),不同乐器的声音有不同的频谱特征。
多模态特征提取架构对比:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 多模态特征提取架构对比 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ 统一编码器架构 (如Gemini) │ │
│ │ ──────────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ 文本 ──┐ │ │
│ │ ├──→ 统一嵌入层 ──▶ 统一Transformer ──▶ 多模态表示 │ │
│ │ 图像 ──┤ │ │
│ │ 音频 ──┘ │ │
│ │ │ │
│ │ 优点:深度模态交互,端到端优化 │ │
│ │ 缺点:训练复杂,需要大量多模态数据 │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ 双编码器架构 (如CLIP) │ │
│ │ ──────────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ 文本 ──▶ 文本编码器 ──▶ 文本特征 ──┐ │ │
│ │ ├──→ 对比学习 ──▶ 统一空间 │ │
│ │ 图像 ──▶ 图像编码器 ──▶ 图像特征 ──┘ │ │
│ │ │ │
│ │ 优点:简单高效,易于训练 │ │
│ │ 缺点:模态交互较浅 │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ 融合编码器架构 (如Flamingo) │ │
│ │ ──────────────────────────────────────────────────────────────── │ │
│ │ │ │
│ │ 文本 ──▶ LLM ──▶ ┌─────────────────────────────────────────┐ │ │
│ │ │ Cross-Attention Layers (插入到LLM中) │ │ │
│ │ 图像 ──▶ ViT ──▶ └─────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 优点:强大多模态能力,支持复杂推理 │ │
│ │ 缺点:计算开销大 │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
统一特征空间的概念
理解了不同模态的特征表示后,我们来看多模态学习中最关键的概念之一:统一特征空间(Unified Feature Space)。
为什么需要统一特征空间?
在多模态学习中,我们需要比较、关联不同模态的内容。比如:
- 给定一段文字描述,找到最匹配的图像
- 给定一张图片,回答关于图片的问题
- 给定一段语音,转录成对应的文字
注释:这些任务的前提是:不同模态的内容必须在同一个"空间"里才能比较。就像比较两个人的身高,必须用同一个单位(厘米或英寸),否则无法直接比较。
统一特征空间就是这样一个"共同语言"空间——所有模态的数据都被映射到这个空间中的向量,语义相似的内容在这个空间中距离较近。
统一特征空间图解:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 统一特征空间详细架构 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ 原始数据层 │ │
│ │ │ │
│ │ 文本: "一只橘猫坐在窗台上" 图像: 🖼️ 橘猫在窗台上的照片 │ │
│ │ 音频: 🐱 猫叫声 视频: 🎬 猫跳来跳去 │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────┼────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ 文本编码器 │ │ 图像编码器 │ │ 音频编码器 │ │
│ │ (Transformer) │ │ (ViT) │ │ (Audio Spectrogram│ │
│ │ │ │ │ │ Transformer) │ │
│ └────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ 文本特征向量 │ │ 图像特征向量 │ │ 音频特征向量 │ │
│ │ [0.1, 0.3, ...] │ │ [0.1, 0.3, ...] │ │ [0.1, 0.3, ...] │ │
│ │ dim=512 │ │ dim=512 │ │ dim=512 │ │
│ └────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘ │
│ │ │ │ │
│ └─────────────────────┼─────────────────────┘ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ 统一特征空间 (Shared Embedding Space) │ │
│ │ │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ 归一化处理 │ │ │
│ │ │ (L2 Normalization) │ │ │
│ │ └───────────┬─────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ ● 猫 [0.15, 0.42, 0.31, ..., 0.08] ●────────────● 猫图片 │ │ │
│ │ │ ●───────────● │ │ │
│ │ │ │ 正样本对 │ (相似度: 0.89) │ │ │
│ │ │ ●───────────● │ │ │
│ │ │ ●────────────● 猫音频 │ │ │
│ │ │ │ │ │
│ │ │ ● 狗 [0.72, 0.15, 0.08, ..., 0.21] ●──────────● 狗图片 │ │ │
│ │ │ │ │ │
│ │ │ ● 车 [0.31, 0.08, 0.65, ..., 0.12] ●──────────● 汽车图片 │ │ │
│ │ │ │ │ │
│ │ │ (语义相似的实体在空间中距离较近) │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │
│ 下游任务: │
│ • 图文检索: 给定文本,搜索最相似的图像 │
│ • 零样本分类: 给定文本类别描述,识别图像中是否有该物体 │
│ • 跨模态检索: 文本↔图像↔音频 之间的相互检索 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
注释:在这个统一空间中:
- "猫"这个文本向量和猫的图片向量距离很近
- "狗"这个文本向量和狗的图片向量距离很近
- 即使"猫"和"狗"的文本向量也有一定距离,因为它们是不同的概念
对比学习详解与实现:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 对比学习(Contrastive Learning)详解 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 对比学习核心思想: │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 目标: 让正样本对在特征空间中接近,让负样本对远离 │ │
│ │ │ │
│ │ 正样本对 (Positive Pairs): │ │
│ │ • 匹配的文本-图像对(如"猫"的描述 + 猫的图片) │ │
│ │ • 匹配的图像-文本对(如猫的图片 + "猫"的描述) │ │
│ │ │ │
│ │ 负样本对 (Negative Pairs): │ │
│ │ • 不匹配的文本-图像对(如"猫"的描述 + 狗的图片) │ │
│ │ • 不匹配的图像-文本对(如猫的图片 + "狗"的描述) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ InfoNCE Loss (对比学习常用损失函数): │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ L = -log[exp(sim(I_i, T_i)/τ) / Σexp(sim(I_i, T_j)/τ)] │ │
│ │ │ │
│ │ 其中: │ │
│ │ • I_i: 第i个图像的特征向量 │ │
│ │ • T_i: 第i个文本的特征向量 │ │
│ │ • sim(I, T): 图像和文本的余弦相似度 │ │
│ │ • τ: 温度参数(控制分布的锐利程度) │ │
│ │ • Σ: 对所有负样本求和 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
PyTorch实现:对比学习:
import torch
import torch.nn.functional as F
class ContrastiveLoss(nn.Module):
"""对比学习损失函数 (InfoNCE Loss)"""
def __init__(self, temperature=0.07):
"""
参数:
temperature: 温度参数,控制注意力分布的锐利程度
"""
super().__init__()
self.temperature = temperature
def forward(self, image_features, text_features):
"""
计算对比学习损失
参数:
image_features: 图像特征 [batch_size, dim]
text_features: 文本特征 [batch_size, dim]
返回:
loss: 对比学习损失
"""
# 1. L2归一化
image_features = F.normalize(image_features, dim=1)
text_features = F.normalize(text_features, dim=1)
# 2. 计算相似度矩阵 [batch_size, batch_size]
logits = torch.matmul(image_features, text_features.t()) / self.temperature
# 3. 创建标签(对角线位置为正样本)
labels = torch.arange(image_features.size(0), device=image_features.device)
# 4. 计算交叉熵损失
loss = F.cross_entropy(logits, labels)
# 5. 计算准确率
with torch.no_grad():
predictions = logits.argmax(dim=1)
accuracy = (predictions == labels).float().mean()
return loss, accuracy.item()
class CLIPModel(nn.Module):
"""简化的CLIP模型实现"""
def __init__(self, vision_model, text_model, embed_dim=512):
super().__init__()
self.vision_model = vision_model
self.text_model = text_model
self.vision_projection = nn.Linear(vision_model.embed_dim, embed_dim)
self.text_projection = nn.Linear(text_model.embed_dim, embed_dim)
self.temperature = nn.Parameter(torch.ones([]) * math.log(1 / 0.07))
def forward(self, images, texts):
"""
前向传播
参数:
images: 图像批次 [batch_size, C, H, W]
texts: 文本批次 [batch_size, seq_len]
返回:
image_features: 投影后的图像特征 [batch_size, embed_dim]
text_features: 投影后的文本特征 [batch_size, embed_dim]
"""
# 获取图像和文本特征
image_embeds = self.vision_model(images)
text_embeds = self.text_model(texts)
# 投影到统一空间
image_features = self.vision_projection(image_embeds)
text_features = self.text_projection(text_embeds)
# L2归一化
image_features = F.normalize(image_features, dim=1)
text_features = F.normalize(text_features, dim=1)
# 可学习的温度参数
logits = torch.matmul(image_features, text_features.t()) * self.temperature.exp()
return logits
# 使用示例
if __name__ == "__main__":
# 创建模型
vision_encoder = VisionEncoder(embed_dim=512)
text_encoder = TextEncoder(embed_dim=512)
clip_model = CLIPModel(vision_encoder, text_encoder, embed_dim=512)
# 模拟数据
batch_size = 4
images = torch.randn(batch_size, 3, 224, 224)
texts = torch.randint(0, 1000, (batch_size, 77))
# 前向传播
logits = clip_model(images, texts)
print(f"相似度矩阵形状: {logits.shape}") # [4, 4]
# 计算损失
criterion = ContrastiveLoss(temperature=0.07)
image_features = F.normalize(clip_model.vision_projection(vision_encoder(images)), dim=1)
text_features = F.normalize(clip_model.text_projection(text_encoder(texts)), dim=1)
loss, accuracy = criterion(image_features, text_features)
print(f"对比学习损失: {loss.item():.4f}")
print(f"匹配准确率: {accuracy:.2%}")
print("对比学习模型测试通过!")
代码详解:
- ContrastiveLoss类:实现InfoNCE损失函数,包含相似度计算、标签创建、交叉熵损失计算
- CLIPModel类:简化的CLIP模型,包含视觉编码器、文本编码器和投影层
- temperature参数:可学习的温度参数,控制分布的锐利程度
注释:这就是CLIP等模型学习统一特征空间的基本方法。通过大规模的对比学习,模型学会了把语义相似的跨模态数据映射到特征空间中相近的位置。
本节小结
让我们用简洁的总结回顾这一节的核心内容。
特征表示的本质:
- 把复杂的数据(图像、文本、音频)转换成计算机能处理的数值向量
- 同时保持原始数据的关键语义信息
- 不同模态有不同的特征提取方法(CNN/ViT提取图像特征,词嵌入提取文本特征)
统一特征空间:
- 让不同模态能够在同一个空间中进行比较和关联
- 是多模态学习的核心技术
- 通过对比学习来训练,让匹配的跨模态数据在空间中接近
关键类比:
- 特征表示 = 给数据写简历(保留关键信息,压缩冗余)
- 统一特征空间 = 共同语言(让不同模态能够"对话")
思考题:如果你想让模型学会理解"声音"和"文字"的对应关系(比如语音识别),你会如何设计统一特征空间的学习方法?
2.4 本章小结与练习
核心概念回顾
让我们用简洁的关键词回顾第二章学到的核心概念:
Transformer架构:2017年提出的革命性序列建模架构,基于注意力机制而非循环结构。它由编码器和解码器组成,包含自注意力、多头注意力、前馈网络、位置编码等核心组件。
自注意力机制:Transformer的核心创新,允许序列中每个位置关注所有其他位置,根据相关性分配权重。核心计算是Q(查询)与K(键)的相似度匹配,然后用权重对V(值)加权求和。
多头注意力:同时进行多组注意力计算,每组使用不同的Q、K、V投影。允许模型学习多种不同类型的关联模式。
位置编码:给序列元素加上位置信息的编码,解决Transformer无法感知顺序的问题。论文使用正弦-余弦函数生成位置编码。
交叉注意力:跨模态的注意力机制,Q来自一个模态,K和V来自另一个模态。是多模态融合的关键技术。
特征表示学习:把复杂数据(图像、文本、音频)转换成数值向量的过程。不同模态有不同的特征提取方法(CNN/ViT、词嵌入、频谱特征)。
统一特征空间:让不同模态的数据能够映射到同一个空间中进行比较和关联。是多模态学习的核心技术基础。
对比学习:通过拉近正样本对(匹配的跨模态数据)、推远负样本对(不匹配的跨模态数据),学习统一特征空间的训练方法。
知识关系图
核心技术基础
│
├── Transformer架构
│ │
│ ├── 编码器-解码器结构
│ ├── 自注意力机制(核心创新)
│ │ │
│ │ ├── Q(查询)= 我在找什么
│ │ ├── K(键)= 我有什么
│ │ └── V(值)= 实际内容
│ │
│ ├── 多头注意力(多组Q,K,V)
│ ├── 位置编码(序列顺序)
│ └── 前馈网络(非线性变换)
│
├── 注意力机制详解
│ │
│ ├── 相似度计算(Q·K)
│ ├── Softmax归一化
│ ├── 加权求和输出
│ └── 跨模态注意力(Cross-Attention)
│
└── 特征表示学习
│
├── 文本特征(词嵌入)
├── 图像特征(CNN/ViT)
├── 音频特征(频谱特征)
└── 统一特征空间(对比学习)
实践任务
任务一:手动计算注意力分数
假设我们有3个词的嵌入向量(简化示例,维度为2),手动计算"喜欢"这个词对其他词的注意力分数。
词嵌入矩阵:
- "我":[1, 0]
- "喜欢":[0, 1]
- "学习":[1, 1]
简化假设Q、K、V都直接使用词嵌入(实际应用中会有不同的线性变换)。
计算"喜欢"(Q=[0,1])对"我"(K=[1,0])的注意力分数:
- 点积:0×1 + 1×0 = 0
计算"喜欢"对"喜欢"的注意力分数:
- 点积:0×0 + 1×1 = 1
计算"喜欢"对"学习"的注意力分数:
- 点积:0×1 + 1×1 = 1
请继续完成Softmax归一化,计算最终权重。
任务二:调研ViT架构
Vision Transformer(ViT)是把Transformer应用于图像的里程碑工作。请查阅ViT论文或相关资料,回答以下问题:
- ViT如何处理图像数据?(图像分块 + 位置嵌入)
- ViT和标准Transformer在结构上有什么异同?
- ViT为什么需要更多的训练数据才能达到较好的效果?
思考题参考答案提示
2.1节思考题:如果让你设计一个能同时处理文本和图像的模型,你会如何使用这些组件?
参考思路:
- 使用双编码器:文本编码器(基于Transformer编码器)和图像编码器(基于ViT)
- 两个编码器分别处理各自的模态,输出特征向量
- 使用交叉注意力层让两种模态交互
- 在统一特征空间中进行对比学习或目标任务训练
2.2节思考题:如果让你设计一个三模态(文本、图像、音频)模型,你会如何使用注意力机制实现两两交互?
参考思路:
- 可以使用两级交叉注意力
- 第一级:两两模态之间的交叉注意力(文本-图像、文本-音频、图像-音频)
- 第二级:在跨模态表示上的自注意力或进一步的交叉注意力
- 或者使用图注意力网络,把三种模态看作图中的节点
2.3节思考题:如果你想让模型学会理解"声音"和"文字"的对应关系(比如语音识别),你会如何设计统一特征空间的学习方法?
参考思路:
- 使用双编码器:音频编码器(处理频谱特征)和文本编码器(处理词嵌入)
- 训练数据是成对的(语音音频,对应的文字转录)
- 使用对比学习,让匹配的语音-文本对在特征空间中接近
- 负样本是不匹配的语音-文本对
预告:下一章
在第二章中,我们深入学习了多模态大模型的核心技术基础——Transformer架构、注意力机制和特征表示学习。这些知识是理解后续章节(如CLIP、GPT-4V等模型架构)的前提。
第三章预告:主流模型架构解析
- CLIP:连接视觉与语言的里程碑
- GPT-4V:视觉理解的新高度
- Gemini:原生多模态架构
- 三大模型的架构对比与演进
从第三章开始,我们将具体分析几个代表性的多模态大模型,理解它们的架构设计、训练方法和能力特点。这些模型包括:让图文统一的CLIP、增加视觉能力的GPT-4V,以及原生多模态的Gemini。
本章作者:智柴网(zhichai.net) 发布日期:2026年1月 版权声明:© 2026 智柴网 版权所有