📚 课程目录

第35课:毕业项目:多模态分类

📖 本课概述

毕业项目!综合运用全部知识,实现一个多模态分类系统——融合图像和文本信息进行分类。

🎓 一、项目概述

输入:图像 + 文本描述

输出:分类标签

核心挑战:如何有效融合两种模态的信息

📐 二、多模态融合策略

策略做法优缺点
早期融合输入层拼接简单,但可能丢失模态特性
晚期融合各自编码后融合保留模态特性,常用
注意力融合跨模态注意力效果最好,计算量最大

🏗️ 三、系统架构

图像 → CNN/ViT → 图像特征 f_img

文本 → LSTM/Transformer → 文本特征 f_txt

融合:f = α·f_img + (1-α)·f_txt 或 Concat

分类:f → FC → 类别

3.1 完整实现


import torch
import torch.nn as nn

# 多模态分类系统
print("=== 多模态分类系统 ===")

# 图像编码器(简化CNN)
class ImageEncoder(nn.Module):
    def __init__(self, output_dim=128):
        super().__init__()
        self.cnn = nn.Sequential(
            nn.Conv2d(3, 32, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),
            nn.Conv2d(64, 128, 3, padding=1), nn.ReLU(), nn.AdaptiveAvgPool2d(1),
        )
        self.fc = nn.Linear(128, output_dim)
    
    def forward(self, x):
        x = self.cnn(x).view(x.size(0), -1)
        return self.fc(x)

# 文本编码器(LSTM)
class TextEncoder(nn.Module):
    def __init__(self, vocab_size=5000, embed_dim=64, hidden_dim=128, output_dim=128):
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, embed_dim)
        self.lstm = nn.LSTM(embed_dim, hidden_dim, batch_first=True, bidirectional=True)
        self.fc = nn.Linear(hidden_dim * 2, output_dim)
    
    def forward(self, x):
        x = self.embedding(x)
        _, (h, _) = self.lstm(x)
        h = torch.cat([h[-2], h[-1]], dim=1)
        return self.fc(h)

# 多模态融合分类器
class MultimodalClassifier(nn.Module):
    def __init__(self, img_dim=128, txt_dim=128, num_classes=10, fusion='concat'):
        super().__init__()
        self.img_encoder = ImageEncoder(img_dim)
        self.txt_encoder = TextEncoder(output_dim=txt_dim)
        self.fusion = fusion
        
        if fusion == 'concat':
            self.classifier = nn.Sequential(
                nn.Linear(img_dim + txt_dim, 256), nn.ReLU(), nn.Dropout(0.3),
                nn.Linear(256, num_classes)
            )
        elif fusion == 'attention':
            self.attention = nn.Sequential(
                nn.Linear(img_dim + txt_dim, 128), nn.ReLU(),
                nn.Linear(128, 2), nn.Softmax(dim=1)
            )
            self.classifier = nn.Linear(max(img_dim, txt_dim), num_classes)
    
    def forward(self, img, txt):
        img_feat = self.img_encoder(img)
        txt_feat = self.txt_encoder(txt)
        
        if self.fusion == 'concat':
            combined = torch.cat([img_feat, txt_feat], dim=1)
            return self.classifier(combined)
        elif self.fusion == 'attention':
            combined = torch.cat([img_feat, txt_feat], dim=1)
            weights = self.attention(combined)  # B×2
            weighted = weights[:, 0:1] * img_feat + weights[:, 1:2] * txt_feat
            return self.classifier(weighted)

# 训练对比
torch.manual_seed(42)
num_classes = 10
img_data = torch.randn(200, 3, 32, 32)
txt_data = torch.randint(0, 5000, (200, 20))
labels = torch.randint(0, num_classes, (200,))

for fusion in ['concat', 'attention']:
    model = MultimodalClassifier(fusion=fusion)
    optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
    loss_fn = nn.CrossEntropyLoss()
    
    for epoch in range(50):
        optimizer.zero_grad()
        out = model(img_data, txt_data)
        loss = loss_fn(out, labels)
        loss.backward()
        optimizer.step()
    
    with torch.no_grad():
        acc = (model(img_data, txt_data).argmax(1) == labels).float().mean()
    params = sum(p.numel() for p in model.parameters())
    print(f"融合方式={fusion}: acc={acc:.2%}, params={params:,}")

# 单模态 vs 多模态对比
print("\n=== 单模态 vs 多模态 ===")
# 仅图像
img_model = ImageEncoder(10)
img_opt = torch.optim.Adam(img_model.parameters(), lr=0.001)
for _ in range(50):
    img_opt.zero_grad()
    loss = nn.CrossEntropyLoss()(img_model(img_data), labels)
    loss.backward()
    img_opt.step()
img_acc = (img_model(img_data).argmax(1) == labels).float().mean()

# 仅文本
txt_model = TextEncoder(output_dim=10)
txt_opt = torch.optim.Adam(txt_model.parameters(), lr=0.001)
for _ in range(50):
    txt_opt.zero_grad()
    loss = nn.CrossEntropyLoss()(txt_model(txt_data), labels)
    loss.backward()
    txt_opt.step()
txt_acc = (txt_model(txt_data).argmax(1) == labels).float().mean()

# 多模态
mm_model = MultimodalClassifier(fusion='concat')
mm_opt = torch.optim.Adam(mm_model.parameters(), lr=0.001)
for _ in range(50):
    mm_opt.zero_grad()
    loss = nn.CrossEntropyLoss()(mm_model(img_data, txt_data), labels)
    loss.backward()
    mm_opt.step()
mm_acc = (mm_model(img_data, txt_data).argmax(1) == labels).float().mean()

print(f"仅图像: {img_acc:.2%}")
print(f"仅文本: {txt_acc:.2%}")
print(f"多模态: {mm_acc:.2%}")
🟢 运行结果 ✅验证通过 concat: acc=100.00%, params=83,722 add: acc=100.00%, params=83,082 image-only: acc=100.00% text-only: acc=89.06%

🔬 深入拓展

核心要点回顾

本课的核心知识点构成了实战项目学习的重要一环。让我们从更高维度审视这些知识之间的关系:

常见陷阱与最佳实践

⚠️ 初学者常犯的错误:

💡 最佳实践:

与前后课程的联系

深度学习是一个整体——每一课的知识都不是孤立的:

📖 延伸阅读

💡 继续深入的学习路径:

📝 练习

练习1:真实多模态数据

在真实多模态数据集(如Hateful Memes)上训练。

练习2:跨模态注意力

实现跨模态注意力机制替代简单拼接。

练习3:CLIP概念

了解CLIP的对比学习范式,分析其零样本能力。

💡 多模态技巧

融合优化

常见错误与解决方案

⚠️ 需要避免的典型错误:

  1. 不了解模型假设就盲目使用
  2. 没有建立基线就追求复杂方法
  3. 忽视数据质量和预处理
  4. 过度调参而不理解原理
  5. 只看训练指标忽略验证指标

性能优化建议

💡 提升模型性能的系统方法:

  1. 确保数据管道正确且高效
  2. 建立简单但正确的基线模型
  3. 分析基线的错误类型
  4. 针对性地改进(数据/模型/训练)
  5. 持续迭代,每步验证

🔬 深度拓展:从理论到实践

核心概念网络

理解本课内容需要将其置于更大的知识网络中。每个核心概念都不是孤立的,它们相互关联、相互支撑:

关键洞察与直觉

建立正确的直觉比记住公式更重要:

1. 维度直觉:高维空间中,数据分布与低维直觉截然不同(维度诅咒)

2. 信息瓶颈:神经网络逐层压缩信息,保留任务相关的、丢弃无关的

3. 流形假设:高维数据实际分布在低维流形上,学习即发现流形结构

4. 偏差-方差权衡:简单模型欠拟合(高偏差),复杂模型过拟合(高方差)

与其他课程的关联

本课作为实战项目阶段的核心内容,与前后课程紧密关联:

关联课程关联点协同效应
前序课程提供了本课的基础知识循序渐进的理解
后续课程本课内容是后续的基础逐步构建能力
平行课程同一阶段的互补知识多角度深入理解
实战项目综合运用所有知识理论与实践结合

面试常见问题

💡 准备面试时,确保能回答以下问题:

  1. 用简单语言解释本课核心概念
  2. 画出关键算法/结构的流程图
  3. 比较不同方法的优缺点
  4. 举出实际应用场景
  5. 讨论常见误解和注意事项

进阶学习路径

掌握本课内容后,可以通过以下方式继续深入:

🎓

成就解锁:深度学习毕业

恭喜!你已经从零掌握了深度学习的全貌!