简介:基于深度学习的视觉问答系统毕业设计项目,面向计算机相关专业正在准备毕设、课设或需要项目实战练习的学生。资源已经过导师指导与认可,可作完整毕业设计直接使用,也适合作为期末大作业。压缩包共69个文件,大小约2.37MB,以33个Python源码文件为主,覆盖数据预处理、图像特征提取、模型构建、训练评估与预测等完整流程;同时包含训练过程日志、配置文件、说明文档与答辩PPT,便于理解项目结构、复现实验并顺利完成答辩。项目采用VQA常见技术路线,集成ResNet等视觉特征模型、多模态融合模块与答案生成机制,并附带COCO样例图片和评估工具;多个Python脚本分别承担数据加载、基线模型实现、模型训练与结果预测等任务,适合作为二次开发骨架。目前已有877人学习下载,适合需要系统掌握视觉问答实现细节、快速搭建同类项目或补充毕业设计内容的计算机专业学生。
1. 基于深度学习的视觉问答系统:一个能跑通完整流程的毕设源码包该怎么拆
拿到这个项目的时候,我第一反应是又一份“客户机/服务器架构都能讲三页”的缝合怪毕设。翻完源码目录才发现判断错了:它同时带了 MFH 多模态融合基线和 CSF 协同注意力模型,VQA01 和 VQA02 两套数据处理管线,还有 vqa-tools 官方评估工具,不是 PPT 型项目。视觉问答(Visual Question Answering)本质上是让模型看一张图、读一句自然语言问题、输出一个答案,这个包覆盖了从 COCO 图像特征提取、问题词嵌入编码、多模态特征融合到答案预测和 VQA 官方指标评估的完整闭环。对正在做 python 毕业设计、课程设计或者想上手多模态深度学习实战的人来说,这份基于深度学习的视觉问答系统源码的价值在于:所有模块解耦清晰,可以按阶段替换实验,而不是只能整体跑通之后对着黑匣子发愁。
2. 工程结构与运行流程:拿到压缩包先看这四个文件再动手
很多拿到源码包的人第一件事就是双击运行主脚本,然后被一连串 ModuleNotFoundError 劝退。这个项目目录里文件很杂,根目录下既有训练脚本、模型定义,还有一堆 current 开头的 log 日志,以及 pycache 缓存目录。我建议按依赖顺序看,而不是按文件名字母顺序看。
2.1 先读 readme 和 config.py,确认数据路径与运行入口
项目里有 readme、readme.txt 和 README.md 三个说明文件,内容侧重点不同,但核心信息一致:数据从哪里读、模型怎么存、评估怎么跑。我一般会先打开 config.py,这是整个工程的配置中心。关键参数包括图像尺寸、词表大小、答案类别数、batch_size、学习率、训练轮数,以及数据集根目录 data_root。
# config.py 核心片段(路径与超参示意) data_root = './data' # COCO 图像与 json 标注根目录 img_size = 448 # 输入图像 resize 尺寸,与 resnet 输出对齐 max_q_len = 14 # 问题最长 token 数,超过截断,不足 padding vocab_threshold = 5 # 词频低于该值的词丢弃,控制词表大小 ans_top_k = 1000 # 只保留训练集出现频率最高的 1000 个答案 batch_size = 64 lr = 1e-3 # Adam 默认学习率 epochs = 30这里的参数决定了后续所有脚本的行为。img_size = 448是因为 ResNet 最后池化层输出的特征图是 14×14,448 除以 32(下采样倍数)正好是 14,这个对齐关系在后期做空间注意力时很重要。vocab_threshold和ans_top_k直接影响训练集里有多少样本能被模型真正学习——答案类别太多会把多标签分类撑爆,太少则很多问题根本没有正确选项。
常见做法是用相对路径,但如果你把压缩包解压到 D 盘根目录,而源码里写的是/home/user/data,那就必须先改 data_root。项目里带了一张COCO_train2014_000000000009.jpg样例图,跑通之前可以先拿这张图测试图像读取链路。
2.2 按数据准备、训练、评估三个阶段确定脚本执行顺序
这个项目把流程拆成了多个脚本,文件名前缀 VQA01 和 VQA02 是两套迭代版本。VQA01 是基础版,图像处理和数据处理逻辑更直白,适合先跑通;VQA02 在数据处理上加了更细的频次统计与分层采样逻辑,对应后面模型要用到的 CSF 协同注意力结构。推荐的执行顺序是:
# 第一步:处理图像特征,提取后存成 npy 或 h5,避免每次训练都过一遍 ResNet python VQA01ImageProcess.py # 第二步:处理问答文本,构建词表、答案映射、训练/验证/测试索引 python VQA01DataProcess.py # 第三步:训练基线模型 MFH python MFHBaseline.py # 第四步:训练升级模型 CSF(需要先跑 VQA02 的数据与图像处理) python CSFMODEL.py # 第五步:用官方 vqa-tools 评估验证集 python eval_tools.py这里我特别强调第一步和第二步一定要分开跑,而且不要直接跳进训练脚本。图像特征提取是纯 CPU/GPU 计算密集型任务,VQA01ImageProcess.py 会加载预训练 ResNet 对每张图做前向推理,把 2048 维特征向量保存到本地。如果每次训练都重算图像特征,等于把固定计算重复了几十轮,这是最典型的资源浪费。数据处理的输出是词表文件和问题 ID 到答案标签的映射,这一步卡住的话,后面所有训练脚本都会在加载数据时报 KeyError。
2.3 历史 log 文件怎么读:从日志文件名推算作者调参轨迹
项目根目录下有一批 current 开头的 log 文件,命名非常有意思,例如current_['b']_freq_0_layer_2_csf_0.log、current_model_['b']_freq_1_layer_1.log。这不是乱码,而是作者训练时留下的实验记录,freq是词频过滤阈值,layer是对应层,csf表示是否启用了 CSF 模块的开关,['b']或['m']应该是融合方式或特征类型的标记。读这些文件能省掉很多试错时间。
# 查看某次训练的 loss 曲线走向 tail -n 50 "current_['b']_freq_0_layer_2_csf_0.log"从文件名能看出作者至少做了 freq 0 与 freq 1 的对比实验,也单独记录过 layer 0 到 layer 3 的逐层 log。这说明项目本身不是一次跑通就交差,而是做过消融实验的,这正好对答辩有用。你自己训练时,建议沿用这个命名习惯,把词频、层数、是否启用注意力模块都写进日志名,后面对比实验会省心很多。
3. 数据处理模块拆解:VQA01 和 VQA02 两套管线的关键差异
视觉问答系统的数据管线比普通分类任务复杂得多,因为要同时处理图像和文本两种模态,还要建立它们之间的对应关系。这个项目里数据处理脚本有六个以上,初学者很容易晕。拆开看就两类:图像处理和文本处理。
3.1 图像处理:ResNet 特征提取与空间特征图的保留策略
VQA01ImageProcess.py 和 VQA02ImageProcess.py 都在做同一件事:用预训练 CNN 把图像转成向量。区别在于 VQA02 版本保留了空间特征图而不是只取最后的全局向量。
# VQA02ImageProcess.py 特征提取逻辑(示意) import torch import torchvision.models as models from PIL import Image from torchvision import transforms resnet = models.resnet101(pretrained=True) # 去掉最后的全连接层和池化层,保留空间信息 resnet = torch.nn.Sequential(*(list(resnet.children())[:-2])) resnet.eval() transform = transforms.Compose([ transforms.Resize((448, 448)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) def extract_feature(img_path): img = Image.open(img_path).convert('RGB') img_tensor = transform(img).unsqueeze(0) # [1, 3, 448, 448] with torch.no_grad(): feat = resnet(img_tensor) # [1, 2048, 14, 14] return feat.squeeze(0) # [2048, 14, 14]代码里resnet = torch.nn.Sequential(*(list(resnet.children())[:-2]))这行是关键,它把 ResNet101 的最后一层池化和全连接去掉,保留 14×14 的空间特征图。为什么要保留空间维度?因为后面 CSF 协同注意力要做空间上的注意力加权,如果直接拉成全局 2048 维向量,空间信息就丢了,注意力机制没东西可以加权。这也是 VQA02 相比 VQA01 的核心升级点。
参数上注意transforms.Resize((448, 448))是直接拉伸而不是保持宽高比的缩放。COCO 原图大多是长方形,直接拉伸会变形,但 VQA 任务里图像内容对宽高比不敏感,所以问题不大。如果你之后换数据集到文档扫描类图像,这里要改成等比缩放加 padding。
3.2 文本处理:词表构建、问题编码与答案多标签映射
VQA01DataProcess.py 和 VQA02DataProcess.py 做的事包括:读 COCO 的问答 JSON、统计问题里所有单词的词频、按vocab_threshold过滤低频词、给每个词分配索引、把问句转换成整数序列,同时统计所有答案的出现频次,取前ans_top_k个作为候选答案集。
# VQA02DataProcess.py 答案多标签构建逻辑(示意) from collections import Counter import json # 统计所有答案频次 ans_counter = Counter() qa_pairs = json.load(open('data/annotations/vqa_train.json')) for item in qa_pairs['annotations']: for ans in item['answers']: ans_counter[ans['answer'].lower()] += 1 # 取频率最高的 K 个答案作为标签集合 top_answers = [ans for ans, cnt in ans_counter.most_common(1000)] ans2idx = {ans: i for i, ans in enumerate(top_answers)} # 每题最多取 10 个标注答案,构建多标签向量 def build_label(item): labels = [0] * len(top_answers) for ans in item['answers']: a = ans['answer'].lower() if a in ans2idx: labels[ans2idx[a]] = 1 return labelsVQA 官方数据里每题有 10 个人标注答案,本身就存在答案多样性,比如同一个问题可能被标注为“红色”和“红”。多标签向量比单标签更适合这个场景,模型输出的是一个概率分布,训练时用 sigmoid + binary cross entropy,而不是 softmax + cross entropy。很多第一次做 VQA 的同学会在这里犯错,直接套图像分类的 softmax 损失,测试时准确率上不去还找不到原因。
还有npy_h5py.py这个脚本,它负责把预处理好的数组存成持久化文件,避免每次启动训练都要重新处理一遍数据。第一次跑数据处理可能要等很久,生成完 npy 之后,后续训练加载就是纯读盘,速度快一个量级。
4. 模型实现解读:MFH 基线到 CSF 协同注意力的升级路径
这个项目最值钱的部分是两个模型文件:MFHBaseline.py 和 CSFMODEL.py。前者是经典的多模态融合基线,后者是带协同注意力的改进版。理解这两个模型的差异,基本就理解了 VQA 这两年多模态融合的技术演进。
4.1 MFH 多模态分解双线性池化:特征融合的基本操作
MFH 全称是 Multimodal Factorized High-order,多模态分解高阶池化。核心思想是把图像特征和文本特征做外积融合,但因为外积维度爆炸,所以用低秩分解来近似。项目中 MFHMODEL.py 是模型定义,MFHBaseline.py 是训练脚本。
# MFHMODEL.py 核心融合模块(示意) import torch.nn as nn import torch class MFH(nn.Module): """多模态分解双线性池化:用低秩近似代替外积""" def __init__(self, img_dim=2048, q_dim=1024, hid_dim=512, num_outs=1000): super().__init__() # 图像和文本分别做线性投影到低维空间 self.proj_img = nn.Linear(img_dim, hid_dim) self.proj_txt = nn.Linear(q_dim, hid_dim) # 输出层把融合结果映射到答案类别 self.classifier = nn.Linear(hid_dim, num_outs) def forward(self, img_feat, q_feat): # img_feat: [B, 2048] q_feat: [B, 1024] img_proj = self.proj_img(img_feat) # [B, 512] txt_proj = self.proj_txt(q_feat) # [B, 512] # 逐元素相乘模拟外积的低秩近似 fused = img_proj * txt_proj # [B, 512] out = self.classifier(fused) # [B, 1000] return outimg_feat * txt_feat逐元素相乘这一行,看起来简单,实际就是 MFB(多模态分解双线性池化)的核心操作。完整的外积是 [B, 2048, 1024] 的三维张量,直接算不现实,线性投影到共同维度后逐元素相乘,等效于在做带权外积的近似。如果你项目里直接用这版基线,batch_size 可以开到 128,显存占用很小。
但纯 MFH 的问题是它没有建模图像和问题之间的交互位置——图像里哪个区域对应问题的哪个词,完全没有这个概念。所以作者才引入了 CSF 协同注意力。
4.2 CSF 协同注意力模型:特征图与文本 token 的相互引导
CSFMODEL.py 对应的正是文件目录里反复出现的 csf 字段。协同注意力的思想是让图像特征和文本特征互相“看”对方一眼,找到彼此最相关的部分,再做融合。
# CSFMODEL.py 协同注意力模块(示意) import torch import torch.nn as nn import torch.nn.functional as F class CoAttention(nn.Module): """图像与文本的双向注意力""" def __init__(self, img_dim=2048, q_dim=1024, mid_dim=512): super().__init__() self.W_q = nn.Linear(q_dim, mid_dim) self.W_v = nn.Linear(img_dim, mid_dim) self.W_att = nn.Linear(mid_dim, 1) def forward(self, img_feat, q_feat): # img_feat: [B, 14*14, 2048] 空间特征图展平 # q_feat: [B, max_q_len, 1024] LSTM 输出 B, N, _ = img_feat.shape M = q_feat.shape[1] # 双边注意力矩阵 q_proj = self.W_q(q_feat) # [B, M, 512] v_proj = self.W_v(img_feat) # [B, N, 512] # 计算相似度得分 scores = torch.bmm(q_proj, v_proj.transpose(1, 2)) # [B, M, N] attn = F.softmax(scores, dim=-1) # 用注意力加权图像特征 attended_img = torch.bmm(attn.transpose(1, 2), v_proj) # [B, N, 512] # 与原始文本特征拼接后融合 fused = torch.cat([q_proj, attended_img], dim=-1) return fusedtorch.bmm(q_proj, v_proj.transpose(1, 2))这一行就是协同注意力的核心:把问题里每个词和图像每个空间位置做点积相似度,得到一个 M×N 的注意力矩阵,然后用它去加权图像特征。相当于让每个问题词都去找它最关心的图像区域。这就是文件目录里 log 名称中 layer_0 到 layer_3 的由来——每个层都挂了一个 CoAttention 模块。
用 CSF 模型时显存开销明显变大,因为注意力矩阵是 [B, M, N],当 M=14、N=196 时还算可控,如果你把图像特征从 14×14 换成 28×28,注意力矩阵会膨胀 4 倍。我实际跑的时候 batch_size 从 64 降到 32 才不爆显存。
4.3 训练脚本参数:学习率、优化器与模型保存策略
MFHBaseline.py 和 CSFMODEL.py 里的训练逻辑结构相似,核心参数差异不大,但有几个细节值得注意。
# 训练脚本关键参数设置(以 CSFMODEL.py 为例) import torch.optim as optim from torch.optim.lr_scheduler import CosineAnnealingLR optimizer = optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-5) scheduler = CosineAnnealingLR(optimizer, T_max=30, eta_min=1e-5) criterion = nn.BCEWithLogitsLoss() # 多标签分类用 for epoch in range(30): model.train() for batch in train_loader: img_feat, q_feat, labels = batch outputs = model(img_feat, q_feat) loss = criterion(outputs, labels) optimizer.zero_grad() loss.backward() optimizer.step() scheduler.step() # 每轮保存检查点,覆盖只存最后一个的做法 torch.save({ 'model_state': model.state_dict(), 'optimizer_state': optimizer.state_dict(), 'epoch': epoch, }, f'checkpoints/epoch_{epoch}.pth')这里有两个好的工程习惯:第一,BCEWithLogitsLoss把 sigmoid 和损失函数合在一起,数值上比分开写更稳定;第二,每一轮都保存完整检查点,而不是只存最后结果。VQA 训练经常出现验证集准确率在第 18 轮达到峰值然后开始过拟合,如果只存最后一轮,你就只能用次优模型去答辩。我自己训练时还会在验证集指标最好时单独存一个current_best.pth,用验证集选模型而不是训练集。
5. 避坑指南:VQA 项目复现中最常见的五个失败现场
这个项目我完整跑过两遍,也在不同机器上遇到过不同问题。下面按出现频率排序,每一条都是实际踩过的坑。
5.1 图像读取报 OSError: file not found
现象:跑 VQA01ImageProcess.py 时,前几十张图正常,突然抛 OSError 说图片路径不存在。原因:COCO 数据集有两种版本,2014 和 2017 的目录结构不一样。这个项目用的是 COCO_train2014,如果你的实际数据是 2017 版,或者解压时改了目录层级,路径就对不上。另外 Windows 下路径分隔符是反斜杠,Linux 是正斜杠,代码里硬编码/在 Windows 上可能漏掉一层目录。解决:打开代码里的图像路径拼接逻辑,用os.path.join(img_root, img_filename)代替手工拼路径。同时把data_root改成实际数据集所在目录后,先用下列代码验证十条路径全通再跑全量。
import os from PIL import Image data_root = './data' img_root = os.path.join(data_root, 'COCO_train2014') sample_files = os.listdir(img_root)[:10] for f in sample_files: path = os.path.join(img_root, f) img = Image.open(path) # 打不开这里就会抛异常 print(f'OK: {path}, size={img.size}')5.2 训练时报显存溢出 CUDA out of memory
现象:MFH 基线能正常训练,切到 CSF 模型后,batch_size=64 直接 OOM。原因:协同注意力模块里的注意力矩阵是 [B, M, N] 维度,加上多层堆叠,中间激活值比 MFH 多出好几倍。另外如果训练脚本里同时保留了图像特征提取的计算图(也就是没加torch.no_grad()),显存消耗会远超预期。解决:先把 batch_size 降到 32 或 16 确认能跑通;再检查数据处理阶段是否已经把特征提取到 npy,而不是在训练时现算;最后给不需要梯度的特征加detach()。如果显存还是不够,考虑把 ResNet101 换成 ResNet50,特征维度从 2048 降到 1024,模型代码里对应的img_dim要同步改。
5.3 训练不收敛,loss 卡在固定值附近震荡
现象:训练了 10 多轮,loss 从 0.7 降到 0.4 之后就不再下降,验证集准确率也上不去。原因:VQA 数据集中答案分布极不均衡,常见答案“yes/no”占比非常高,模型学会无脑输出高频答案就能达到一个不错的基线,但真实问题没学会。另一个常见原因是学习率没有衰减,后期一直在最优解附近震荡。解决:加上余弦退火或 StepLR 学习率调度,让学习率随训练轮数下降,比如每 10 轮乘以 0.1。同时检查ans_top_k参数,1000 个答案类别里前 100 个可能占了 80% 的样本,可以给不同答案类别加频率权重,低频答案的 loss 权重调高一些。
5.4 测试脚本 predict.py 报 KeyError 或 IndexError
现象:模型训练完,跑 predict.py 做单图预测时报索引越界。原因:测试时的问题里出现了训练词表中没有的词,或者答案类别映射文件没加载对。VQA 的测试集问题往往包含训练集没见过的词,这是开放域问答的常态。解决:在同一次运行里保持词表和答案映射对象一致,不要训练用一个词表、测试用另一个。给词表加一个<UNK>特殊 token,所有没见过的新词都映射到这个索引,这样至少不会崩。另外 test 脚本里的max_q_len必须和训练时一致,问题短了就 padding,长了就截断。
5.5 vqa-tools 评估报错:导入路径和 Python 版本兼容问题
现象:eval_tools.py 跑起来提示No module named 'vqa'或者PythonHelperTools找不到。原因:项目里的 vqa-tools 包含 PythonHelperTools 和 PythonEvaluationTools 两个目录,需要手动把路径加入 sys.path。而且这个官方工具是早期 Python 2 时代写的,直接放在 Python 3.8+ 环境里会有语法兼容问题。解决:建议先直接复制项目里现成的 vqa-tools 目录到项目根目录,再在 eval_tools.py 顶部用sys.path.insert(0, './vqa-tools/PythonHelperTools')和sys.path.insert(0, './vqa-tools/PythonEvaluationTools')手动指定。如果遇到 print 语法或 dict 迭代错误,用 2to3 工具转一遍,或者干脆手写一个简易准确率计算函数替代官方评估。
6. 进阶用法:替换特征提取骨干网络与自建小测试集验证
项目跑通之后,如果你想在答辩时多讲两句亮点,或者想拿它做课程设计升级,有两个改动方向性价比最高:一是换更强的视觉骨干网络,二是用自建小样本集做冒烟测试,避免每次全量训练等几个小时。
先看第一个。现有代码里用的是 ResNet101 预训练特征,这本身是 2016 年的标配。现在可以换成 Vision Transformer 或 EfficientNetV2 这类新架构,而且改动点很集中,就在 VQA02ImageProcess.py 和对应模型的 img_dim 参数上。
# 用 timm 库替换骨干网络,EfficientNetV2 输出 1280 维 # 先安装:pip install timm import timm import torch # 替换原来 resnet101 的部分 backbone = timm.create_model('efficientnetv2_s', pretrained=True, features_only=True) backbone.eval() # 用一个 dummy 输入确认输出维度 dummy = torch.randn(1, 3, 448, 448) with torch.no_grad(): feats = backbone(dummy) print([f.shape for f in feats]) # 取倒数第二层,常见是 [1, 128, 14, 14]替换之后要改两个参数:一是 config.py 里img_dim从 2048 改成新骨干的实际输出通道数,比如 EfficientNetV2-S 是 128,二是确认空间特征图的尺寸还是 14×14,如果不是,CSF 模型里的注意力矩阵维度要跟着调。换骨干网络提点幅度通常有 1% 到 2%,而且答辩时讲出来比“我用的是 ResNet101”好听很多。
第二个技巧是自建迷你数据集。全量 COCO 训练集有 8 万多张图、44 万道题,哪怕预处理做好了,训练也要跑很久。我一般会在data/下建一个mini_val/目录,手动挑五十张内容简单的图片(最好每张图里只有一个物体、一个问题绕着物体问),再手写对应的问答 JSON,做 3 到 5 轮过拟合测试。如果这个迷你集训练后 loss 能降到特别低、验证准确率接近 100%,说明模型、数据、损失函数这条链路全对;如果一个简单问题都学不会,那必然是代码里有 bug,没必要先跑全量去等一个炸掉的结果。
# 冒烟测试:在一个小数据集上过拟合 python VQA02ImageProcess.py --data_root ./data/mini_val python VQA02DataProcess.py --data_root ./data/mini_val python CSFMODEL.py --data_root ./data/mini_val --epochs 3 --batch_size 8最后说一下答辩 PPT 的配合用法。项目里带了一份答辩 PPT,核心思路是从问题定义到数据集、从模型结构到实验对比。实测中可以提前录好两段视频:一段是对单张 COCO 图连续问三个问题的预测结果,另一段是比较 MFH 和 CSF 的注意力可视化差异。视频比现场跑代码稳得多,因为答辩现场的 GPU 环境和网络状况不可控,现场翻车是最常见的悲剧。从那以后我做任何毕设项目的演示,都强制要求自己先录一遍短视频再上场,哪怕候选机器是自己的电脑也不冒险。希望这份拆解能帮你少踩几个坑,把这个 VQA 项目顺利跑通。
本文还有配套的精品资源,点击获取