4. 数据准备与预处理:多模态数据集的下载、清洗与对齐
做跨模态检索,数据就是你的弹药库。弹药不行,枪法再好也白搭。我见过不少团队,模型结构设计得花里胡哨,结果数据没处理好,训练出来的模型连基本的图文匹配都做不好。
这一章,咱们就聊聊怎么搞定三个主流多模态数据集:MSCOCO、Flickr30k 和 AudioSet。说白了,就是教你怎么把原始数据变成模型能吃的“干净饭”。
4.1 三大数据集概览
先看看这三个数据集各自是什么来头。我习惯把它们分成两类:视觉-语言和视觉-音频。
| 数据集 | 模态 | 规模 | 典型用途 |
|---|---|---|---|
| MSCOCO | 图像 + 文本(5句/图) | 12万+图像 | 图文检索、图像描述 |
| Flickr30k | 图像 + 文本(5句/图) | 3.1万图像 | 图文检索、视觉推理 |
| AudioSet | 音频 + 标签(527类) | 200万+音频片段 | 音频事件检测、跨模态对齐 |
你想想看,MSCOCO 和 Flickr30k 都是图文对,但规模差了一个数量级。AudioSet 则是纯音频的,做跨模态检索时,通常需要自己构建音频-文本对。
4.2 数据下载:别踩这些坑
下载数据听起来简单,但实际操作起来,坑不少。我当年第一次搞 MSCOCO,直接去官网点下载,结果等了三天三夜,最后发现下错了版本。
4.2.1 MSCOCO 下载
MSCOCO 官方提供了 2014 和 2017 两个版本。我个人习惯用 2017 版,因为它的训练/验证集划分更合理。
# 下载图像
wget http://images.cocodataset.org/zips/train2017.zip
wget http://images.cocodataset.org/zips/val2017.zip
# 下载标注(包含文本描述)
wget http://images.cocodataset.org/annotations/annotations_trainval2017.zip
wget -c 支持断点续传,不然网络断了就得重来。
4.2.2 Flickr30k 下载
Flickr30k 稍微麻烦点。它需要从 Flickr 平台获取原始图像,然后配上人工标注的文本。我建议直接去 Kaggle 或者 Hugging Face Datasets 找打包好的版本,省心。
# 使用 Hugging Face Datasets
from datasets import load_dataset
dataset = load_dataset("nlphuji/flickr30k", split="train")
print(len(dataset)) # 输出: 31783
cache_dir 参数,把数据存到你的 SSD 上,能大幅提升后续读取速度。
4.2.3 AudioSet 下载
AudioSet 是 YouTube 上的音频片段,每个片段 10 秒。下载它需要调用 YouTube API,速度很慢。我曾经为了下载 10 万条音频,跑了整整一周。
# 使用官方提供的 CSV 文件
# 下载 balanced_train_segments.csv
wget http://storage.googleapis.com/us_audioset/youtube_corpus/v1/csv/balanced_train_segments.csv
# 然后用 youtube-dl 或 yt-dlp 下载
# 注意:很多视频可能已被删除或设为私密
# YT ID 是否有效,不然白白浪费时间。
4.3 数据清洗:把脏数据筛出去
下载完数据,别急着用。先做清洗。我见过有人直接拿原始数据训练,结果模型学了一堆噪声。
4.3.1 图像清洗
- 去重: 用感知哈希(pHash)检测重复图像。MSCOCO 里偶尔会有完全相同的图片。
- 过滤低质量: 分辨率低于 256x256 的图片直接扔掉。模糊检测可以用拉普拉斯算子。
- 格式统一: 全部转成 JPEG,压缩质量 95 以上。PNG 太大,训练时加载慢。
4.3.2 文本清洗
文本这块,问题更多。我遇到过文本里包含 HTML 标签、特殊符号、甚至乱码的情况。
import re
def clean_text(text):
# 去除 HTML 标签
text = re.sub(r'<[^>]+>', '', text)
# 去除特殊符号,保留字母、数字、空格和标点
text = re.sub(r'[^\w\s\.\,\!\?]', '', text)
# 合并多余空格
text = re.sub(r'\s+', ' ', text).strip()
# 统一小写(可选)
text = text.lower()
return text
4.3.3 音频清洗
AudioSet 的音频是 10 秒片段,但很多片段里只有几秒的有效声音。我建议做 静音检测,把前后静音部分切掉。
import librosa
def trim_silence(audio_path, top_db=20):
y, sr = librosa.load(audio_path, sr=16000)
y_trimmed, _ = librosa.effects.trim(y, top_db=top_db)
return y_trimmed, sr
4.4 数据对齐:让不同模态对上号
对齐是跨模态检索的核心。说白了,就是让图像和文本、音频和文本之间建立一一对应的关系。
4.4.1 图文对齐
MSCOCO 和 Flickr30k 的标注文件里,已经给出了图像 ID 和文本 ID 的对应关系。你只需要解析 JSON 文件就行。
import json
with open('annotations/captions_train2017.json', 'r') as f:
data = json.load(f)
# 构建图像ID -> 文本列表的映射
image_to_captions = {}
for ann in data['annotations']:
image_id = ann['image_id']
caption = ann['caption']
if image_id not in image_to_captions:
image_to_captions[image_id] = []
image_to_captions[image_id].append(caption)
# 检查对齐情况
print(f"图像数量: {len(image_to_captions)}")
print(f"第一张图的描述数: {len(image_to_captions[list(image_to_captions.keys())[0]])}")
4.4.2 音频-文本对齐
AudioSet 本身只有标签,没有自然语言描述。你需要自己构建文本。我常用的方法是:
- 标签转描述: 把标签(如 "Dog bark")扩展成完整句子(如 "A dog is barking")。
- 模板填充: 用预定义的模板,比如 "The sound of {label}"。
- 使用预训练模型: 用 AudioCLIP 或 CLAP 的文本编码器,把标签映射到语义空间。
4.5 知识结构图:数据准备全流程
下面这张图,把整个数据准备流程串起来了。你可以把它当作操作手册。
4.6 数据存储与索引
数据清洗对齐后,怎么存也是个学问。我推荐用 LMDB 或 HDF5 存储图像和音频,用 JSON 或 Parquet 存储文本和元数据。
import lmdb
import pickle
# 创建 LMDB 环境
env = lmdb.open('coco_images.lmdb', map_size=1099511627776) # 1TB
# 写入数据
with env.begin(write=True) as txn:
for image_id, image_data in image_dict.items():
txn.put(str(image_id).encode(), pickle.dumps(image_data))
4.7 避坑总结
最后,我把这些年踩过的坑总结一下:
- 下载别用默认线程: 多线程下载虽然快,但容易被服务器封 IP。我一般用 4-8 线程,加随机延时。
- 清洗别过度: 文本里的标点、大小写,有时候对语义理解有帮助。别一刀切。
- 对齐要双检: 图像 ID 和文本 ID 的对应关系,一定要写脚本验证。我遇到过标注文件里 ID 不匹配的情况。
- 备份原始数据: 清洗后的数据如果出了问题,还能回滚。别把原始数据删了。