ARTICLE DETAIL

资讯详情

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

自定义image captioning数据集格式整理与清洗实战指南

自定义image captioning数据集格式整理与清洗实战指南 简介这份资源面向从事图像描述image captioning研究与开发的算法工程师、研究生及高年级本科生聚焦自定义数据集从零构建到可直接训练的全流程格式整理。内容围绕数据集结构设计、图像与caption的JSON组织方式以及训练集、验证集、测试集划分等核心环节展开并覆盖BUTD特征构建、AOA注意力机制适配、DLCT网格特征规整、224×224图像统一缩放等模型输入预处理要点帮助读者打通数据准备到模型训练的衔接链路。资源包共10个文件全部为Python脚本压缩后约11KB按编号顺序对应合并原始数据、划分数据集、生成annotations、提取特征、规整训练文件等步骤结构清晰、便于按需调用。目前已有214人学习下载适合希望系统掌握captioning数据工程细节、减少重复造轮子的读者参考借鉴。1. 自定义 image captioning 数据集到底要整理成什么样你手里可能有一批图每张图配了一句或几句中文描述想拿来训一个 image captioning 模型。真正动手时第一个卡住的地方往往不是模型结构而是数据格式图片放哪、描述写哪、编码用什么、一条样本长什么样。我见过太多人卡在这一步把时间耗在写胶水脚本上而不是调模型。这篇讲的就是自定义 image captioning 数据集的格式整理。核心目标只有一个把散落在文件夹、Excel、txt 里的图文对整理成训练框架能直接吃的标准结构并且保证后续换框架、加字段、做清洗时不用推倒重来。适合两类人一是第一次自己攒图文数据、准备微调 caption 模型的新手二是已经有数据但格式混乱、想统一成可维护结构的熟手。下面从格式设计讲到落地脚本再到踩过的坑尽量让你照着就能跑通。2. image captioning 数据集的三种主流格式与选型2.1 为什么格式选错会让后面每一步都难受image captioning 的数据本质是「一张图对应一到多条文本描述」。听起来简单但不同框架对这份对应关系的组织方式差别很大。常见的有三类一是每张图一个同名 txt图片和文本靠文件名绑定二是集中式 JSON所有图文对写在一个文件里三是类 COCO 的 annotations 结构图片、描述、划分各自成表。选型不是看哪个高级而是看你的数据规模和后续动作。数据量在几千张以内、描述只有一句同名 txt 最省事肉眼可查出问题直接打开文件看。数据上万、每图多句描述、还要做训练/验证/测试划分集中式 JSON 更好维护改一个字段不用动几千个文件。如果你打算复用现成的 COCO 评测脚本或直接对接某些训练框架的默认 loader那 COCO 风格的结构兼容性最好。我一般的判断顺序是先确认训练框架默认吃什么再确认描述条数最后确认要不要频繁做划分和清洗。三个问题答完格式基本就定了。下面这张表是我常用的对照参数都是实操里反复验证过的。格式类型适用规模每图描述数划分支持主要风险同名 txt千张以内1 句靠文件夹文件数爆炸易漏配集中式 JSON万张级1~5 句字段控制单文件大需流式读COCO 风格万张以上多句独立划分文件结构层级多易写错 id2.2 集中式 JSON 的字段设计别只存 image 和 caption很多人第一版 JSON 就两个字段image和caption。跑通没问题但一旦要做数据清洗、去重、多描述、来源追溯就得回头改结构所有下游脚本跟着改。我的习惯是一开始就把字段留够哪怕暂时用不上。一个可维护的样本结构大致是这样image存相对路径而不是绝对路径方便迁移caption存文本列表而不是单字符串天然支持多描述split存 train/val/testsource记录数据来源方便排查脏数据id用稳定唯一值别用行号因为排序一变行号就废了。下面是我常用的字段定义。{ id: sample_000001, image: images/000001.jpg, captions: [ 一只橘猫趴在窗台上晒太阳, 窗边的橘色猫咪正在休息 ], split: train, source: self_collected_2024, width: 640, height: 480 }逻辑说明id用固定前缀加序号保证跨次生成稳定image用相对路径整个数据集目录搬到别的机器不用改captions是列表训练时随机采一条或全部用都行split让划分信息内聚在样本里不用额外维护三个文件width/height可选但做分辨率过滤和分桶训练时很有用。参数说明id建议零填充到固定位数排序时不会出现 1、10、2 这种乱序image路径分隔符统一用正斜杠Windows 和 Linux 都能读captions里每条描述建议去掉首尾空白空字符串直接丢弃别留占位。2.3 从零散文件到标准 JSON 的整理流程假设你现在的原始数据是一个raw_images文件夹放图一个captions.txt每行是「文件名\t描述」或者一个 Excel 两列。整理流程分四步扫描图片、读取描述、配对校验、写出 JSON。第一步扫描图片时别只认 jpgpng、jpeg、webp 都要覆盖同时记录文件大小后面过滤损坏图用得上。第二步读描述注意编码中文 txt 常见 utf-8 和 gbk 两种读错就是乱码。第三步配对用文件名做键图片有描述没配上的、描述有图片找不到的都要单独记下来这是最常见的脏数据来源。第四步写出建议同时输出一份统计信息比如总样本数、无描述图片数、无图片描述数。import os import json import hashlib IMG_EXTS {.jpg, .jpeg, .png, .webp, .bmp} def build_id(path): # 用路径哈希生成稳定 id避免行号漂移 return sample_ hashlib.md5(path.encode(utf-8)).hexdigest()[:12] def load_captions(txt_path): pairs {} with open(txt_path, r, encodingutf-8) as f: for line in f: line line.strip() if not line: continue # 约定用制表符分隔避免描述里含空格被切错 parts line.split(\t) if len(parts) 2: continue fname, cap parts[0].strip(), parts[1].strip() if cap: pairs.setdefault(fname, []).append(cap) return pairs def collect_images(img_dir): result {} for root, _, files in os.walk(img_dir): for name in files: ext os.path.splitext(name)[1].lower() if ext in IMG_EXTS: full os.path.join(root, name) rel os.path.relpath(full, img_dir) result[name] rel.replace(os.sep, /) return result def build_dataset(img_dir, cap_txt, out_json): images collect_images(img_dir) caps load_captions(cap_txt) samples, missing_cap, missing_img [], [], [] for fname, rel in images.items(): if fname in caps: samples.append({ id: build_id(rel), image: rel, captions: caps[fname], split: train, source: raw }) else: missing_cap.append(fname) for fname in caps: if fname not in images: missing_img.append(fname) with open(out_json, w, encodingutf-8) as f: json.dump(samples, f, ensure_asciiFalse, indent2) print(f有效样本: {len(samples)}) print(f有图无描述: {len(missing_cap)}) print(f有描述无图: {len(missing_img)}) return samples if __name__ __main__: build_dataset(raw_images, captions.txt, dataset.json)逻辑说明build_id用路径哈希而不是递增序号保证同一张图多次生成 id 一致做增量更新时不会错位。load_captions用制表符分隔因为中文描述里常带空格用空格切会切错。collect_images递归扫描并统一路径分隔符跨平台不会出问题。最后把「有图无描述」和「有描述无图」分别统计这两类就是后续要人工处理的脏数据。参数说明IMG_EXTS按你实际数据增删加了新格式记得同步split这里先全给 train划分放到下一步单独做ensure_asciiFalse必须加否则中文会变成转义序列肉眼没法看indent2方便人工检查正式训练前可以压成一行减小体积。3. 划分、清洗与多描述处理让数据集真正能训3.1 训练验证测试划分的三个硬约束划分看着简单随机切一刀就行但实际有三个约束容易忽略。第一同一张图的多条描述必须落在同一个 split否则验证集里出现训练时见过的图指标虚高。第二划分比例要按图片数算不是按描述条数算否则多描述的图会稀释比例。第三划分要可复现用固定随机种子别每次跑结果都不一样。我一般按 8:1:1 切数据量小于一千时改成 7:1.5:1.5保证验证集有足够样本。划分时先按图片聚合再对图片列表打乱最后按比例切分把 split 写回每条样本。import random from collections import defaultdict def split_dataset(samples, ratios(0.8, 0.1, 0.1), seed42): random.seed(seed) # 按图片聚合保证同图多描述不跨 split by_image defaultdict(list) for s in samples: by_image[s[image]].append(s) images list(by_image.keys()) random.shuffle(images) n len(images) n_train int(n * ratios[0]) n_val int(n * ratios[1]) train_imgs set(images[:n_train]) val_imgs set(images[n_train:n_train n_val]) for img, group in by_image.items(): if img in train_imgs: sp train elif img in val_imgs: sp val else: sp test for s in group: s[split] sp return samples逻辑说明先按image聚合成组再对图片维度打乱和切分这样同一张图的所有描述自然进同一个 split。seed固定保证可复现。切分用图片数算比例不受多描述影响。参数说明ratios三个数之和必须为 1否则切分数量对不上seed建议写进配置换数据集时改一下如果某类图片特别少可以考虑分层抽样但大多数场景随机切够用。3.2 描述文本清洗中文场景下必须处理的几类脏数据中文 caption 的脏数据比英文更隐蔽。常见的有全角半角混用、多余空格、结尾标点不统一、繁体简体混杂、以及从网页复制带进来的不可见字符。这些不处理训练时 tokenizer 会多出一堆奇怪 token模型学得慢还容易输出乱码。清洗顺序建议是先去不可见字符再统一标点再处理空格最后做长度过滤。长度过滤别一刀切中文描述一般 5 到 50 字比较合理太短的可能是「图」「照片」这种无效描述太长的可能是误粘的段落。import re import unicodedata def clean_caption(text): if not text: return # 去掉零宽字符和不可见控制符 text re.sub(r[\u200b-\u200f\u2028-\u202f\ufeff], , text) # 全角转半角保留中文标点 text unicodedata.normalize(NFKC, text) # 合并多余空白 text re.sub(r\s, , text).strip() # 统一结尾标点中文描述结尾不加句号更常见 text text.rstrip(。.!?) return text def filter_captions(samples, min_len5, max_len50): kept [] for s in samples: caps [clean_caption(c) for c in s[captions]] caps [c for c in caps if min_len len(c) max_len] if caps: s[captions] caps kept.append(s) return kept逻辑说明NFKC归一化会把全角字母数字转半角同时保留中文标点是中文清洗里比较稳的一步。零宽字符用正则显式去掉这类字符肉眼看不见但会进 tokenizer。结尾标点统一去掉是因为中文 caption 数据集里带句号和不带句号混在一起很常见统一后模型输出更干净。参数说明min_len和max_len按你的数据分布调可以先统计一下长度直方图再定filter_captions里如果一张图所有描述都被过滤掉整条样本丢弃避免出现空 captions 的样本。3.3 多描述样本的两种用法与取舍一张图多条描述训练时有两种用法。一是随机采一条每个 epoch 采到的可能不同相当于数据增强实现简单大多数框架默认这么干。二是全部拼接成一条长描述信息全但长度翻倍容易超长截断而且拼接后语义不自然。我的经验是描述之间差异大比如一条讲主体、一条讲场景时用随机采样描述之间高度相似只是换词时可以只保留质量最高的一条减少冗余。判断标准很简单把同一张图的两条描述放一起读如果读起来像在说两件不同的事就保留多条如果只是同义改写留一条就够。import random def sample_caption(sample, moderandom): caps sample[captions] if not caps: return if mode random: return random.choice(caps) elif mode first: return caps[0] elif mode concat: # 拼接时用分隔符方便后续切分 return .join(caps) return caps[0]逻辑说明random模式配合固定 seed 可复现first模式适合描述已按质量排序的情况concat模式要配合长度检查超长直接截断或退回first。参数说明mode建议写进训练配置方便对比实验拼接分隔符用中文分号比逗号更不容易和描述内部标点混淆。4. 避坑与排查整理 image captioning 数据集时最容易翻车的五件事4.1 图片能打开但训练报错多半是通道或位深问题现象用 PIL 打开图片正常训练时却报 shape 或 channel 错误。原因部分 png 带 alpha 通道是四通道部分灰度图是单通道模型默认吃三通道 RGB。解决整理阶段统一转 RGB遇到四通道丢弃 alpha遇到单通道复制成三通道。这一步放在预处理脚本里别等到训练时才发现。4.2 中文描述乱码编码判断不能靠猜现象读出来的描述是「涓枃」这类乱码。原因文件实际是 gbk 或 gb18030却用 utf-8 读。解决先尝试 utf-8失败再试 gb18030还失败就报错让人工确认别静默跳过。更稳的做法是整理阶段统一转成 utf-8 存储后续只认一种编码。4.3 路径用绝对路径换台机器全废现象本地跑通换机器或换目录后所有图片找不到。原因JSON 里存了绝对路径。解决一律存相对路径读取时用数据集根目录拼接。迁移时整个目录搬走即可不用改任何配置。4.4 划分后验证集指标异常高检查是否同图跨 split现象验证 loss 低得离谱生成结果和训练集高度相似。原因同一张图的多条描述被分到不同 split等于验证集泄了训练集。解决按图片聚合后再划分划分完做一次校验确认没有图片同时出现在两个 split。4.5 描述里混入文件名或编号模型学会输出无意义串现象模型生成结果里出现「IMG_20240101」这类内容。原因原始描述文件里有些行把文件名当描述或者编号列没去掉。解决清洗阶段加规则过滤纯数字、纯文件名模式、长度过短的描述直接丢弃并统计丢弃数量数量异常大时回头查原始数据。5. 用校验脚本给数据集上保险以及一个我常留的后悔药整理完不是写完 JSON 就结束我习惯加一个校验脚本每次数据更新后跑一遍。校验项包括每条样本图片文件存在、captions 非空、split 取值合法、id 全局唯一、图片能被正常解码。这几项能挡住九成以上的低级错误比训练时报错再回头查省事得多。import json import os from PIL import Image def validate(json_path, root_dir): with open(json_path, r, encodingutf-8) as f: samples json.load(f) ids set() errors [] for s in samples: if s[id] in ids: errors.append(f重复 id: {s[id]}) ids.add(s[id]) img_path os.path.join(root_dir, s[image]) if not os.path.exists(img_path): errors.append(f图片缺失: {s[image]}) continue try: with Image.open(img_path) as im: im.verify() except Exception as e: errors.append(f图片损坏: {s[image]} - {e}) if not s.get(captions): errors.append(f空描述: {s[id]}) if s.get(split) not in {train, val, test}: errors.append(f非法 split: {s[id]}) print(f样本总数: {len(samples)}) print(f错误数: {len(errors)}) for e in errors[:20]: print( -, e) return errors if __name__ __main__: validate(dataset.json, .)逻辑说明im.verify()只检查文件头速度快适合大批量扫描重复 id 用集合查O(1)错误只打印前 20 条避免刷屏完整列表可以写文件。这个脚本我一般挂在数据更新流程末尾跑通才允许进训练。参数说明root_dir是数据集根目录和 JSON 里相对路径拼接如果图片量特别大verify可以改成只检查文件存在跳过解码速度更快但漏检损坏图。最后留一个我自己的习惯也算后悔药每次生成dataset.json时同时存一份带时间戳的副本比如dataset_20240101.json。数据清洗和划分都是不可逆操作改错了想回退有历史版本能省几小时。这个习惯帮我救过两次场一次是划分脚本写错把验证集切没了一次是清洗规则太激进删掉了一半样本。数据集整理这件事稳比快重要多留一份副本不亏。希望帮到你。本文还有配套的精品资源点击获取
返回列表