ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Flickr30k跨模态检索实战:从数据预处理到QueryApp测试界面全流程解析

Flickr30k跨模态检索实战:从数据预处理到QueryApp测试界面全流程解析 简介本资源为基于Flickr30k数据集的图像—文本跨模态检索课程设计项目面向计算机、人工智能、通信工程等专业的在校学生与教师可用于媒体计算实践作业、毕业设计或项目初期立项演示。项目包含完整Python源码、测试界面与项目说明覆盖数据预处理、模型训练与评估全流程并配有QueryApp交互查询界面方便直观验证检索效果。压缩包共35个文件以22个py源码为主辅以6个pyc编译文件、3个md说明文档、1个sh脚本及少量图片与许可证文件整体约1.13MB结构清晰、便于按模块阅读。目前已有586人学习关注。代码经测试运行成功答辩评审平均分达96分读者可据此掌握跨模态检索的数据划分、短边缩放、分阶段训练与指标计算等关键环节也可在现有基础上修改扩展用于课设、毕设或功能演示。1. 从一份能跑通的 Flickr30k 跨模态检索作业说起如果你手头正好有一份图像文本跨模态搜索的课程设计或者正在为毕设找一个能跑通、有界面、有数据流程的 Python 项目那这份基于 Flickr30k 的跨模态检索源码值得花时间拆一遍。它不是那种只丢几个模型文件让你自己猜怎么用的仓库而是把数据预处理、训练、评估、测试界面都串起来了。我拿到压缩包的第一反应是看目录结构Preprocessing、Models、DataSet、Test这几个文件夹把职责分得很清楚trainStage1.py、trainStage2.py、trainMore1.py、trainMore2.py对应不同训练阶段evaluation.py和median_compute负责指标计算QueryApp是测试界面入口。适合谁计算机相关专业做课设或毕设的学生想快速理解跨模态检索完整链路的开发者以及需要一份可修改基线代码来验证自己想法的人。它解决的核心问题是让你不用从零搭数据管道和训练框架直接在一个结构清晰的工程里跑通图像到文本、文本到图像的检索流程。2. 拆开工程目录Flickr30k 数据流与模块职责2.1 从压缩包到可运行环境目录里每个文件干什么先把压缩包解压进入Cross-modal-retrieval-master。根目录下这些文件不是随便放的我按实际调用关系理一遍。config.py是全局配置入口数据集路径、模型保存路径、训练超参基本都从这里读。DataSet文件夹放的是数据加载相关的代码和Preprocessing里的脚本配合使用。Models文件夹是网络结构定义跨模态检索通常包含图像编码器和文本编码器这里就是核心模型所在。trainStage1.py和trainStage2.py是两阶段训练脚本trainMore1.py、trainMore2.py是补充训练或不同配置的训练入口。evaluation.py负责在验证集或测试集上算检索指标median_compute看名字是算中位数相关指标。QueryApp是测试界面logger.py管日志tools.py放通用工具函数chushihua.sh是初始化脚本README.md是项目说明。LICENSE不用管学习用途注意别商用就行。常见做法是先把README.md过一遍确认作者有没有写环境依赖和运行顺序。这份资源的说明里提到数据预处理在Preprocessing下data_split_1.py划分训练集、测试集、验证集resize_data_2.py做短边拉伸到 256 且保持长宽比。这意味着你下载完 Flickr30k 原始数据后不能直接丢给训练脚本必须先跑预处理。2.2 数据预处理脚本划分与尺寸调整的参数逻辑Flickr30k 原始数据包含图像和对应的句子描述通常是一个图像对应多个文本描述。跨模态检索训练需要把数据分成训练、验证、测试三部分并且图像尺寸要统一到网络能接受的输入大小。data_split_1.py干的就是划分的活resize_data_2.py干的是尺寸调整的活。我一般会先看data_split_1.py里的划分比例和随机种子。常见做法是按 8:1:1 或 7:1:2 划分但具体要看代码里写死的比例。如果代码里没有固定随机种子每次运行划分结果会变这对复现实验不友好。你可以在划分前加一行固定种子比如random.seed(42)保证每次划分一致。resize_data_2.py里“短边拉伸为256”这个操作要注意它不是直接 resize 到 256x256而是保持长宽比把短边变成 256长边按比例缩放。这样做的好处是图像不会变形但长边可能超过 256后续可能需要中心裁剪或填充。代码里如果只做了拉伸没做裁剪那网络输入尺寸就得适配动态尺寸或者你在数据加载时再处理。# 以 data_split_1.py 常见写法为例固定随机种子保证可复现 import random import os random.seed(42) # 固定种子避免每次划分结果不同 # 假设所有图像文件名列表已经读入 all_images # 按 8:1:1 划分 random.shuffle(all_images) n len(all_images) train_images all_images[:int(0.8 * n)] val_images all_images[int(0.8 * n):int(0.9 * n)] test_images all_images[int(0.9 * n):] # 写入划分文件供后续 DataSet 加载 with open(train_split.txt, w) as f: f.write(\n.join(train_images))上面这段逻辑说明先固定随机种子再打乱全部图像列表然后按比例切片。参数上0.8、0.9是划分边界你可以根据数据量调整。如果数据量小验证集可以少一点但测试集不能省否则评估指标波动大。resize_data_2.py里通常用 PIL 或 OpenCV 读图计算缩放比例然后resize。注意保存路径要和config.py里的数据根目录一致否则训练时找不到文件。2.3 两阶段训练脚本trainStage1 与 trainStage2 的衔接trainStage1.py和trainStage2.py不是随便拆的跨模态检索里常见两阶段训练策略第一阶段先分别预训练图像编码器和文本编码器或者用对比学习拉近匹配的图像文本对第二阶段再联合微调让两个模态的特征空间对齐。trainMore1.py和trainMore2.py可能是更多轮次或不同超参的版本。你跑的时候要先确认config.py里第一阶段和第二阶段的模型保存路径是否分开。如果两个阶段都往同一个文件写第二阶段会覆盖第一阶段的权重导致无法衔接。常见做法是第一阶段保存为model_stage1.pth第二阶段加载它再训练保存为model_stage2.pth。训练命令一般是python trainStage1.py但有些项目需要传参数比如--data_root、--batch_size。先看代码里argparse部分有没有默认值没有的话你得在命令行补上。# 常见运行顺序先确认 config.py 里的路径 python Preprocessing/data_split_1.py python Preprocessing/resize_data_2.py python trainStage1.py python trainStage2.py python evaluation.py这段命令的逻辑是先划分数据再调整图像尺寸然后第一阶段训练接着第二阶段训练最后评估。参数说明如果config.py里数据路径写的是相对路径你必须在项目根目录下运行这些命令否则路径会错。如果报FileNotFoundError先检查config.py里的data_root和实际解压后的 Flickr30k 文件夹名是否一致。3. 跑通训练与评估从 config.py 到 evaluation.py 的实操链路3.1 config.py 里必须改的几处路径与超参config.py是整条链路的控制中心不改它基本跑不起来。我拿到任何项目先看这个文件。通常需要改的有数据集根目录、图像特征或文本特征的保存目录、模型保存目录、日志目录。有些项目还会在这里定义batch_size、learning_rate、num_epochs、margin等超参。Flickr30k 数据集下载后一般是一个文件夹里面是flickr30k-images和对应的文本文件。你要把data_root指向包含这些内容的父目录。超参方面batch_size受显存限制常见从 32 或 64 开始试。如果显存不够先降到 16 或 8但太小会影响对比学习的效果因为负样本数量变少。learning_rate一般第一阶段可以大一点比如 1e-3 或 1e-4第二阶段微调时降到 1e-5 或 1e-6。margin是三元组损失里的边界值常见 0.2 或 0.5具体看代码用的是哪种损失函数。如果你不确定先按代码默认值跑一遍看 loss 是否下降再调。# config.py 常见需要修改的字段示例 data_root /path/to/flickr30k # 改成你实际解压的路径 image_dir os.path.join(data_root, flickr30k-images) text_file os.path.join(data_root, results_20130124.token) # 具体文件名看数据集版本 model_save_dir ./checkpoints log_dir ./logs batch_size 32 learning_rate_stage1 1e-3 learning_rate_stage2 1e-5 num_epochs_stage1 30 num_epochs_stage2 20逻辑说明data_root是根image_dir和text_file从它派生。model_save_dir和log_dir如果不存在代码里通常有os.makedirs创建但有些项目没写你得手动建。参数上num_epochs_stage1和num_epochs_stage2根据你的耐心和显卡时间定跑太少 loss 没降下去跑太多可能过拟合。建议先各跑 10 轮看趋势。3.2 训练阶段一图像文本对比学习的 loss 观察trainStage1.py跑起来后终端会打印每个 epoch 的 loss。跨模态检索第一阶段通常用对比损失或三元组损失目标是让匹配的图像文本对在特征空间里距离近不匹配的远。你要盯的是 loss 是否稳定下降。如果 loss 震荡很大可能是学习率太高或 batch_size 太小。如果 loss 不降先检查数据加载是否正确比如图像和文本是否对应上了。常见做法是在训练循环里每隔几个 batch 打印一次 loss每个 epoch 结束后在验证集上算一次检索召回率。evaluation.py可能被trainStage1.py调用也可能单独跑。如果训练脚本里没有验证步骤你就得等训练完再跑evaluation.py。我一般会在第一阶段训练时加一个简单的验证比如每 5 个 epoch 算一次 R1、R5、R10这样能早点发现模型是不是没学进去。# 训练循环里观察 loss 的常见写法 for epoch in range(num_epochs_stage1): model.train() total_loss 0 for batch_idx, (images, texts) in enumerate(train_loader): images images.to(device) texts texts.to(device) optimizer.zero_grad() loss model(images, texts) # 具体损失函数看 Models 里的定义 loss.backward() optimizer.step() total_loss loss.item() if batch_idx % 50 0: print(fEpoch {epoch}, Batch {batch_idx}, Loss {loss.item():.4f}) avg_loss total_loss / len(train_loader) print(fEpoch {epoch} finished, Avg Loss {avg_loss:.4f}) # 保存 checkpoint torch.save(model.state_dict(), os.path.join(model_save_dir, fstage1_epoch{epoch}.pth))逻辑说明每个 batch 做前向、算损失、反向、更新。loss.item()取标量值用于打印。参数上batch_idx % 50控制打印频率太频繁刷屏太少看不到趋势。保存 checkpoint 时带上 epoch 号方便回退。注意model(images, texts)这行具体模型 forward 返回什么要看Models里的实现有的返回 loss有的返回特征向量需要你自己算 loss。3.3 评估指标evaluation.py 与 median_compute 怎么读evaluation.py跑完后会输出检索指标常见的是 R1、R5、R10分别表示正确结果排在前 1、前 5、前 10 的比例。median_compute看名字是算中位数排名即正确结果排名的中位数越小越好。Flickr30k 上图像到文本和文本到图像两个方向的指标都要看。如果 R1 只有个位数说明模型没学好如果 R1 能到 20% 以上对于课程设计来说已经能看了。读指标时注意区分image to text和text to image。有些代码只算一个方向你要看evaluation.py里是不是两个方向都算了。如果只算了一个你可以自己补另一个方向的循环。另外评估时用的距离度量是余弦相似度还是欧氏距离会影响结果。常见做法是特征归一化后用余弦相似度或者直接算内积。如果代码里没归一化指标可能偏低。# evaluation.py 里常见的检索指标计算逻辑 def compute_recall(sim_matrix, labels, k_list[1, 5, 10]): # sim_matrix: (num_images, num_texts) 相似度矩阵 # labels: 每个图像对应的正确文本索引 recalls {} for k in k_list: correct 0 for i in range(sim_matrix.shape[0]): topk_indices sim_matrix[i].argsort(descendingTrue)[:k] if labels[i] in topk_indices: correct 1 recalls[fR{k}] correct / sim_matrix.shape[0] return recalls逻辑说明对每个图像取相似度最高的 k 个文本看正确文本是否在里面。参数上k_list可以按需改比如加 R20。sim_matrix的维度要和图像、文本数量对应。如果labels不是索引而是多热编码需要先转成索引。注意argsort(descendingTrue)在 PyTorch 里是torch.sort(descendingTrue)NumPy 里是np.argsort(-sim)别写错。4. 测试界面 QueryApp 与常见报错排查4.1 QueryApp 启动方式与界面交互逻辑QueryApp是这份资源里比较实用的部分它让你不用写代码就能测试检索效果。启动方式通常是python QueryApp/main.py或python QueryApp/app.py具体看文件夹里的入口文件名。启动后一般是一个简单的图形界面你可以输入文本查图像或者上传图像查文本。背后调用的就是训练好的模型和特征库。我一般会先确认QueryApp里加载的模型路径和config.py里第二阶段保存的模型路径是否一致。如果不一致界面会报加载失败。另外界面查询时需要预先计算好所有测试图像的图像特征和所有文本的文本特征存成矩阵或文件。如果QueryApp里没有预计算步骤第一次查询会很慢因为它要现场编码。常见做法是在evaluation.py跑完后把特征保存下来QueryApp直接读。# 启动测试界面的常见命令 cd QueryApp python main.py # 如果报模块找不到回到项目根目录用 python -m QueryApp.main逻辑说明cd进目录再跑或者用-m模块方式跑取决于代码里的相对导入写法。如果报ModuleNotFoundError: No module named Models说明你不在项目根目录或者sys.path没包含根目录。可以在入口文件开头加sys.path.append(..)解决。4.2 避坑与常见问题排查现象运行trainStage1.py报FileNotFoundError: [Errno 2] No such file or directory: flickr30k-images/xxx.jpg。原因config.py里的image_dir路径不对或者预处理脚本没有把图像放到指定目录。 解决打开config.py确认data_root指向的文件夹里确实有flickr30k-images子文件夹。如果数据集解压后多了一层目录比如Flickr30K/flickr30k-images那data_root要相应调整。另外检查resize_data_2.py的输出目录是否和image_dir一致。现象训练时 loss 一直是nan。原因学习率太大或者数据里有损坏图像导致特征出现异常值也可能是损失函数里除了零。 解决先把学习率降到 1e-4 或 1e-5 试。然后在数据加载时加异常捕获跳过读不出来的图像。如果用的是三元组损失检查margin是否设得太大。另外确认输入图像是否归一化到 [0,1] 或 [-1,1]没归一化容易出 nan。现象evaluation.py跑完 R1 只有 0.1% 左右。原因模型根本没学好或者评估时图像和文本的对应关系错了。 解决先检查训练 loss 有没有下降。如果 loss 正常下降但指标极低大概率是评估时labels对错了比如图像索引和文本索引没对齐。打开evaluation.py看它怎么构建相似度矩阵和标签的。另外确认测试集划分文件是否和训练集有重叠有重叠会导致指标虚高但这里是极低更可能是标签错位。现象QueryApp启动后界面空白或闪退。原因缺少 GUI 依赖或者模型加载失败但异常没打印出来。 解决先看终端有没有报错。如果是tkinter相关错误Linux 下需要装python3-tk。如果是模型加载失败检查QueryApp里写的模型路径是否存在以及模型结构是否和保存时一致。常见坑是训练时用了DataParallel保存的权重带module.前缀加载时没去掉。现象显存不足CUDA out of memory。原因batch_size太大或者图像输入尺寸太大。 解决先把batch_size减半。如果还不行检查resize_data_2.py输出的图像短边是不是 256长边可能到 400 多可以改成短边 224 或 128。另外在训练循环里用torch.cuda.empty_cache()清理缓存但根本办法还是减小 batch 或输入尺寸。5. 进阶技巧用 median_compute 验证检索排名分布5.1 中位数排名比 R1 更能反映模型稳定性median_compute这个文件容易被忽略但它算的中位数排名很有参考价值。R1 只看第一名对不对如果模型把正确结果排在第 2 或第 3R1 就是 0但中位数排名能告诉你模型大概把正确结果放在什么位置。我一般会同时看 R1 和中位数排名。如果 R1 不高但中位数排名很小比如中位数是 3说明模型其实学到了只是没排到第一这时候可以调调损失函数的 margin 或者加更多负样本。具体操作上你可以在evaluation.py里把每个查询的正确结果排名记录下来然后传给median_compute算中位数。如果median_compute是独立脚本看它读什么格式的输入一般是排名列表或相似度矩阵。常见做法是把排名存成.npy或.txt然后python median_compute.py --input ranks.npy。# 记录每个查询的正确排名并算中位数 import numpy as np def compute_median_rank(sim_matrix, labels): ranks [] for i in range(sim_matrix.shape[0]): sorted_indices np.argsort(-sim_matrix[i]) rank np.where(sorted_indices labels[i])[0][0] 1 # 排名从1开始 ranks.append(rank) ranks np.array(ranks) median_rank np.median(ranks) print(fMedian rank: {median_rank}) return median_rank逻辑说明对每个查询把相似度从高到低排序找到正确标签的位置加 1 得到排名。np.median算中位数。参数上sim_matrix是图像到文本或文本到图像的相似度矩阵labels是对应的正确索引。如果中位数排名是 1说明一半以上的查询第一名就是对的模型很好如果是 10 以上说明模型还有很大提升空间。5.2 从课程设计到可修改基线换数据集与调模型结构这份代码的价值不止于跑通 Flickr30k。你可以把它当成一个跨模态检索的基线换其他数据集试试。比如把Preprocessing里的划分脚本改成读你自己的图像文本对把DataSet里的加载逻辑改成适配你的文件格式然后重新训练。模型结构方面Models文件夹里的图像编码器可能是 CNN你可以换成 ResNet 或 ViT文本编码器可能是 LSTM 或 BERT你也可以换。换的时候注意特征维度要对齐否则相似度计算会报错。我自己的习惯是每次改完模型结构或数据管道先跑一个极小的子集比如 100 张图像看能不能过拟合。如果在小数据集上 loss 能降到接近 0说明代码逻辑没问题再上全量数据。这个习惯帮我省了很多时间不用等几个小时才发现维度对不上。从那以后我每次拿到新项目都强制走一遍小数据过拟合测试希望这个习惯也能帮到你。本文还有配套的精品资源点击获取
返回列表