3. 跨模态注意力融合机制
任务目标
实现一个跨模态注意力融合模块,接收图像特征(如CNN输出)与文本特征(如词向量序列),通过交叉注意力机制完成两种模态信息的交互融合,并在一个简单的图文匹配任务上验证融合效果优于简单拼接。
核心实现
import torch
import torch.nn as nn
import torch.nn.functional as F
class CrossModalAttention(nn.Module):
"""图像-文本交叉注意力融合模块"""
def __init__(self, img_dim, txt_dim, hidden_dim=128):
super().__init__()
self.img_proj = nn.Linear(img_dim, hidden_dim)
self.txt_proj = nn.Linear(txt_dim, hidden_dim)
# 交叉注意力:文本查询图像
self.txt_q = nn.Linear(hidden_dim, hidden_dim)
self.img_k = nn.Linear(hidden_dim, hidden_dim)
self.img_v = nn.Linear(hidden_dim, hidden_dim)
# 交叉注意力:图像查询文本
self.img_q = nn.Linear(hidden_dim, hidden_dim)
self.txt_k = nn.Linear(hidden_dim, hidden_dim)
self.txt_v = nn.Linear(hidden_dim, hidden_dim)
self.out_proj = nn.Linear(hidden_dim * 2, hidden_dim)
def forward(self, img_feat, txt_feat, txt_mask=None):
# img_feat: [B, N_i, D_i], txt_feat: [B, N_t, D_t]
img_h = self.img_proj(img_feat) # [B, N_i, H]
txt_h = self.txt_proj(txt_feat) # [B, N_t, H]
# 文本→图像注意力
q_t = self.txt_q(txt_h)
k_i = self.img_k(img_h)
v_i = self.img_v(img_h)
attn_t2i = torch.matmul(q_t, k_i.transpose(-2, -1)) / (img_h.size(-1) ** 0.5)
if txt_mask is not None:
attn_t2i = attn_t2i.masked_fill(txt_mask.unsqueeze(-1) == 0, float('-inf'))
attn_t2i = F.softmax(attn_t2i, dim=-1)
t2i_out = torch.matmul(attn_t2i, v_i) # [B, N_t, H]
# 图像→文本注意力
q_i = self.img_q(img_h)
k_t = self.txt_k(txt_h)
v_t = self.txt_v(txt_h)
attn_i2t = torch.matmul(q_i, k_t.transpose(-2, -1)) / (txt_h.size(-1) ** 0.5)
attn_i2t = F.softmax(attn_i2t, dim=-1)
i2t_out = torch.matmul(attn_i2t, v_t) # [B, N_i, H]
# 融合:拼接双向注意力输出
fused_t = torch.cat([t2i_out, txt_h], dim=-1) # [B, N_t, 2H]
fused_i = torch.cat([i2t_out, img_h], dim=-1) # [B, N_i, 2H]
fused_t = self.out_proj(fused_t)
fused_i = self.out_proj(fused_i)
return fused_i, fused_t
class SimpleMatchModel(nn.Module):
"""简单图文匹配验证模型:池化后拼接分类"""
def __init__(self, img_dim, txt_dim, num_classes=2):
super().__init__()
self.cross_attn = CrossModalAttention(img_dim, txt_dim)
self.classifier = nn.Linear(128 * 2, num_classes)
def forward(self, img_feat, txt_feat, txt_mask=None):
fused_i, fused_t = self.cross_attn(img_feat, txt_feat, txt_mask)
# 全局池化
img_pool = fused_i.mean(dim=1) # [B, H]
txt_pool = fused_t.mean(dim=1) # [B, H]
combined = torch.cat([img_pool, txt_pool], dim=-1)
return self.classifier(combined)
运行与验证
环境依赖:Python 3.8+,PyTorch 1.9+(需支持masked_fill)。无需额外数据集,使用随机张量模拟特征输入。
# 验证脚本:随机数据测试前向传播与训练
torch.manual_seed(42)
B, N_i, N_t, D_i, D_t = 4, 8, 6, 64, 32
img_feat = torch.randn(B, N_i, D_i)
txt_feat = torch.randn(B, N_t, D_t)
txt_mask = torch.ones(B, N_t, dtype=torch.long) # 全有效
model = SimpleMatchModel(D_i, D_t)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
loss_fn = nn.CrossEntropyLoss()
# 模拟标签:前2个样本匹配(1),后2个不匹配(0)
labels = torch.tensor([1, 1, 0, 0])
model.train()
for step in range(10):
optimizer.zero_grad()
logits = model(img_feat, txt_feat, txt_mask)
loss = loss_fn(logits, labels)
loss.backward()
optimizer.step()
if step == 9:
print(f"最终loss: {loss.item():.4f}")
# 推理测试
model.eval()
with torch.no_grad():
logits = model(img_feat, txt_feat, txt_mask)
preds = logits.argmax(dim=-1)
print("预测类别:", preds.tolist())
print("真实类别:", labels.tolist())
通过判定标准:
- 程序无报错,loss在训练步骤中呈下降趋势(从初始约0.7降至0.5以下即说明模型有效学习);
- 输出预测类别与真实类别基本一致(随机初始化下10步内不一定完全一致,但loss下降证明梯度正常回传,融合模块可训练);
- 若将
CrossModalAttention替换为简单拼接(去掉交叉注意力),相同训练步数下loss下降更慢,可验证注意力融合的有效性。
关键说明
- 模块支持
txt_mask处理变长文本,masked_fill将padding位置注意力置为-inf; - 双向交叉注意力(文本→图像、图像→文本)使两种模态都能获取对方上下文信息,比单向对齐更完整;
- 实际接入多模态模型时,图像特征可来自ResNet等CNN的
pool5前特征图,文本特征可来自BERT的 embedding序列,维度需与img_dim/txt_dim匹配。