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
注意: 图像文件很大,train2017.zip 约 18GB,val2017.zip 约 1GB。建议用 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
小技巧: 用 Hugging Face 下载时,可以设置 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 下载
# 注意:很多视频可能已被删除或设为私密
避坑指南: AudioSet 中大约有 20%-30% 的链接已经失效。下载前先检查 CSV 中的 # 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
关键点: 文本清洗时,不要过度处理。比如 Flickr30k 的文本里有人名、地名,这些对检索是有意义的。你只需要去掉明显的噪声。

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 本身只有标签,没有自然语言描述。你需要自己构建文本。我常用的方法是:

  1. 标签转描述: 把标签(如 "Dog bark")扩展成完整句子(如 "A dog is barking")。
  2. 模板填充: 用预定义的模板,比如 "The sound of {label}"。
  3. 使用预训练模型: 用 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))
性能对比: LMDB 的随机读取速度比直接从磁盘读文件快 10 倍以上。训练时,IO 瓶颈往往比计算瓶颈更严重。

4.7 避坑总结

最后,我把这些年踩过的坑总结一下:

  • 下载别用默认线程: 多线程下载虽然快,但容易被服务器封 IP。我一般用 4-8 线程,加随机延时。
  • 清洗别过度: 文本里的标点、大小写,有时候对语义理解有帮助。别一刀切。
  • 对齐要双检: 图像 ID 和文本 ID 的对应关系,一定要写脚本验证。我遇到过标注文件里 ID 不匹配的情况。
  • 备份原始数据: 清洗后的数据如果出了问题,还能回滚。别把原始数据删了。