直接进入正题。上个季度我给某家医疗影像方向的合作方做了一套医学图像分类与诊断数据集,总共5040张,标注格式同时给了VOC和YOLO两版,覆盖了病灶检测、器官分割和疾病分级三块典型的诊断任务。做完之后不少朋友来问具体怎么组织、怎么转格式、怎么避坑,我干脆把整套流程整理出来,顺着数据构建、格式转换、训练配置到问题排查这条线完整写一遍。
先说一个基本判断:医学图像数据集和自然场景数据集有本质差异。自然场景里“猫”和“狗”的边界再模糊,人眼都能分个大概;但医学图像里“正常”和“早期病变”往往只差几个像素的灰度或纹理变化,标注质量直接决定模型能不能用。5040张这个规模,放在自然图像分类里属于“练手级”,但在医学影像场景里已经是一个相当讲究的配置,前提是你的类别设计、标注一致性和格式转换全部做到位。
1. 数据集整体设计思路拆解
1.1 为什么是5040张:数据规模背后的计算逻辑
先说这个数量是怎么定下来的。医学图像数据集不像自然图像那样可以从网上大规模爬取,来源基本就是合作医院、公开数据集加上一部分合成数据。我在定方案的时候直接算了一笔账:
- 计划覆盖5个分类类别(后面细讲),每个类别至少需要800张以上,这样训练集、验证集、测试集按8:1:1划分后,每个类别的测试集还有80张左右,能撑起一个统计上相对稳定的评估。
- 考虑医学图像中常见的类别不平衡,少数类需要额外补充,所以每个类别800张打底,个别类别追加到1000张左右,加起来就是5040张。
- 预留了大约10%的冗余量,用于清洗不合格样本之后的回填。
这个规模对应的模型参数量级,大概是YOLOv8s这种轻量级版本,或者ResNet50级别的分类网络。如果上YOLOv8x或者更大规模的Transformer模型,5040张就有些吃紧,需要配合更强的前处理和数据增强策略。
1.2 VOC格式与YOLO格式:为什么两套格式都要给
很多做算法的人有个错误认识,觉得数据集只要给一种格式、跑通训练就行。实际情况远没有这么简单。
VOC格式(这里指Pascal VOC标注体系)是XML文件形态,每个图像对应一个同名的XML标注文件,内部用<object>节点描述目标类别和边界框坐标,坐标是绝对的像素值。它的优势是人可读性强,打开XML就能直观看到目标在哪、类别是什么,配合标注工具做二次校审非常方便。VOC格式也是很多公开医学数据集(比如某些肺部X光数据集)的原始发布格式,直接兼容。
YOLO格式则完全不同。每个图像对应一个同名TXT文件,每行是一条标注记录,格式为“类别ID x_center y_center width height”,所有坐标值都被归一化到0到1之间的相对值。这种格式有几个直接好处:归一化坐标天然跟图像分辨率解耦,训练时无论输入尺寸怎么resize都不需要再做坐标换算;纯文本存储占用极小;读取逻辑简单,能支撑更高的数据加载吞吐。
实际项目里,标注团队用VOC格式做标注和审核,算法组训练时用YOLO格式加载,这两者之间必须有一条稳定可靠的转换链路。这就是“VOC+YOLO”这个命名的核心含义——不是二选一,而是同一份标注资产的双投影。
1.3 医学图像分类诊断场景的特殊性
标题里的“分类与诊断”这两个词,决定了这5040张数据不能像自然图像那样粗暴处理。我对数据集内容做了三层设计:
第一层是粗分类,解决“图像里有什么”的问题,比如判断一张病理切片属于哪个组织部位,或者一张内镜图像属于哪个检查部位。这一层的类别之间差异大,模型容易学。
第二层是病灶检测,解决“病灶在哪”的问题,对应YOLO格式中的边界框标注。这一层最考验标注一致性,因为医学病灶的边界往往不清晰,不同医生勾画的结果可能相差不少。
第三层是诊断分级,解决“病情多重”的问题,对应分类标签中的级别字段,比如某个病变的轻、中、重度。这一层最难,因为分级标准存在主观判断成分,我用了多人在标注后交叉复核的流程来控制分歧。
一套数据集同时承载这三层信息,价值就完全不一样了。只做粗分类的数据集,模型能回答“是什么”;同时做了检测和分级的数据集,模型才能回答临床上真正需要的“在哪里、有多严重”。
2. 数据构建核心细节与标注规范
2.1 目录结构与文件组织规范
整套数据集的目录结构我按下面的形式组织,这是经过多个项目验证的稳健方案:
medical_dataset_5040/ ├── VOC/ │ ├── JPEGImages/ # 所有原始图像,jpg格式 │ ├── Annotations/ # 所有VOC格式XML标注文件 │ ├── ImageSets/ │ │ ├── Main/ │ │ │ ├── train.txt │ │ │ ├── val.txt │ │ │ └── test.txt │ └── labels.txt # 类别名与ID映射 ├── YOLO/ │ ├── images/ │ │ ├── train/ │ │ ├── val/ │ │ └── test/ │ ├── labels/ │ │ ├── train/ │ │ ├── val/ │ │ └── test/ │ └── dataset.yaml # YOLO训练配置 ├── scripts/ │ ├── voc_to_yolo.py │ ├── split_dataset.py │ ├── verify_annotations.py │ └── data_augment.py └── README.md这里有个很重要的设计决策:VOC目录下不做train/test拆分,只在ImageSets/Main里通过txt文件指定划分,而YOLO目录下直接物理拆分成独立的文件夹。原因是VOC的标准加载方式就是通过ImageSets里的文件列表来索引训练和验证样本,而YOLO的加载逻辑期望直接从images/train这种路径读图。两种格式的目录哲学不同,强行统一反而增加转换代码的复杂度。
图像文件的命名也有讲究。我采用的是“来源_类型_编号.jpg”的格式,比如CXR_pneumonia_0047.jpg表示胸部X光、肺炎类别、第47张。这样在标注和调试过程中,单看文件名就能快速定位样本来源,不用每次去翻元数据。
2.2 类别定义与诊断标签设计
类别设计是数据集构建中最需要提前想清楚的环节,后期改类别几乎等于重做。我这套数据的类别体系长这样:
| 类别ID | 类别名称 | 标注类型 | 样本量 | 诊断说明 |
|---|---|---|---|---|
| 0 | normal | 分类 | 920 | 正常样本 |
| 1 | benign | 检测+分类 | 1080 | 良性病灶,标注边界框 |
| 2 | malignant | 检测+分类 | 1180 | 恶性病灶,标注边界框 |
| 3 | inflammation | 检测+分类 | 1040 | 炎症区域,标注边界框 |
| 4 | early_stage | 分类(分级) | 820 | 早期病变,仅作为分类标签 |
看到这里可能有朋友会问:为什么benign和malignant既做检测又做分类,而early_stage只做分类?这是从临床诊断逻辑出发的。良恶性判定必须基于病灶位置,医生需要先圈出病灶区域再判断性质,所以模型也要同时学习定位和分类。而早期病变的分级更多依赖全局影像特征(比如纹理分布、灰度变化),不一定要有明确的单一病灶边界框,强制标注反而会引入大量噪声。
在多标签的情况下,同一张图允许同时存在多个类别的边界框。比如一张CT图像里既有炎症区域,又有一个良性结节,XML里就会出现两个<object>节点,YOLO TXT里会有两行记录。设计类别体系时就要明确允许这种多实例、多类别共存,否则后面写数据加载逻辑时会发现很多样本“对不上”。
2.3 图像采集与预处理要点
医学图像的来源比较杂,有DICOM格式的CT,有JPG/PNG的X光,还有显微镜下拍的病理图像。统一转成JPG格式用于训练时,有几个点必须处理干净:
色深与位宽:DICOM原始数据通常是12位或16位的灰度图,直接转成8位JPG会截断灰度范围。正确做法是先做窗宽窗位调节,把诊断上有价值的灰度区间映射到0到255之间,而不是天真地做等比例压缩。举个例子,CT图像的HU值范围通常在负1000到正3000之间,直接线性映射到8位会导致软组织和病灶区域挤在很窄的灰度区间里,对比度极差。
分辨率归一化:不同设备的输出分辨率差异很大,有的X光是2000x2500,有的CT重建图是512x512。我没有把所有图像都resize到固定尺寸,而是保留了原始分辨率,只在送入模型时由数据加载器统一resize。训练时YOLO系列模型本身就支持多种输入尺寸,配合mosaic增强,效果比提前压缩掉信息要好。
格式统一性:所有图像统一转为RGB三通道JPG,灰度图也复制成三通道。这样做避免了数据加载器里写一堆分支逻辑去处理不同通道数的情况,代价只是略微增加存储,收益是训练链路清爽很多。
注意:医学图像预处理省掉任何一步“看似无伤大雅”的简化操作,都可能让模型学到错误特征。特别是灰度映射这一步,一旦把诊断关键区间的对比度压没了,后面的模型训练基本白做。
2.4 标注工具选型与标注规范
标注阶段我比较过好几款工具,最后选定了基于开源图像标注工具的方案。选择标准有三个:支持VOC格式的XML导出、支持多人协作审核、操作门槛低到标注员半天培训能上手。
实用的标注工具整理如下,方便大家按需选择:
- 开源桌面标注工具:适合几十万张以下的中小规模标注,支持矩形框、多边形、标签属性,直接导出VOC XML。部署简单,标注员在本地安装就能用,适合团队规模不大的项目。
- 浏览器端标注平台:适合跨团队协作场景,能在线指派任务、实时同步进度,标注质量有第三方审核机制。缺点是数据要上传,医学数据出域需要谨慎评估。
- 半自动标注:先用已有模型做预标注,标注员只需要修正和确认。这个方法能把标注效率提升2到3倍,但前提是你已经有一个效果尚可的初版模型。
标注规范方面,我踩过不少坑,整理出来几条硬性要求:
首先是边界框贴合度标准。我要求标注员框选病灶时,边界框边缘与病灶视觉边界的间距不超过5个像素。太紧会截掉模糊过渡区域,太松会把大量背景包进来干扰分类。这个标准听起来简单,实际执行时不同标注员的理解偏差很大,需要反复看样例对齐。
其次是分类标签的决策路径。我给标注员提供了一张明确的决策流程图:先看图像质量是否合格,不合格直接打回;再看是否存在病灶,无病灶进normal;有病灶先圈边界框,再根据影像特征判断良恶性;拿不准的标记为待审核,由高年资成员复核。这套流程让标注员面对模糊样本时不再凭感觉硬猜。
最后是多人一致性控制。我抽了10%的样本做双人标注,计算同一张图两个标注员边界框的IoU,低于0.7的样本要重新讨论统一标准。分类标签的一致性用Kappa系数衡量,低于0.6说明标注标准不清晰,需要重新培训。
3. 从VOC到YOLO的完整转换实操
3.1 标注格式转换原理
理解了两种格式的数据结构,转换逻辑就非常清晰了。VOC XML里存储的边界框是xmin, ymin, xmax, ymax,这四个值分别是左上角x坐标、左上角y坐标、右下角x坐标、右下角y坐标,单位是像素。YOLO TXT需要的是x_center, y_center, width, height,并且全部除以图像宽度和高度做归一化。
翻译成数学公式就是:
x_center = ((xmin + xmax) / 2) / image_width y_center = ((ymin + ymax) / 2) / image_height box_width = (xmax - xmin) / image_width box_height = (ymax - ymin) / image_height这个转换过程有一个容易出错的细节:VOC的坐标值是像素坐标,从0开始计数;而YOLO的归一化坐标是浮点数,理论上闭区间0到1。当边界框恰好贴着图像边缘时,转换出来可能出现0.0或1.0的边界值。多数YOLO实现认为目标必须完全在图像内部,有些版本在损失计算时会过滤掉这些边缘框。我在转换代码里做了边界约束,将坐标限制在[0.000001, 0.999999]区间内,避免踩这个坑。
3.2 Python转换脚本的实现
下面是我在实际项目中使用的转换脚本核心逻辑,去掉了工程外壳保留主干:
import os import xml.etree.ElementTree as ET from pathlib import Path def convert_voc_to_yolo(voc_xml_path, image_dir, class_map, output_txt_path): """ 将单个VOC XML标注转换为YOLO格式TXT标注 """ tree = ET.parse(voc_xml_path) root = tree.getroot() # 获取图像尺寸 size = root.find('size') img_width = int(size.find('width').text) img_height = int(size.find('height').text) lines = [] for obj in root.findall('object'): class_name = obj.find('name').text.strip() if class_name not in class_map: print(f"警告: 未知类别 {class_name},跳过该目标") continue class_id = class_map[class_name] bndbox = obj.find('bndbox') xmin = float(bndbox.find('xmin').text) ymin = float(bndbox.find('ymin').text) xmax = float(bndbox.find('xmax').text) ymax = float(bndbox.find('ymax').text) # 坐标合法性检查 if xmin >= xmax or ymin >= ymax: print(f"错误: 非法边界框 {voc_xml_path} - [{xmin},{ymin},{xmax},{ymax}]") continue # 根据原始图像尺寸归一化 x_center = ((xmin + xmax) / 2) / img_width y_center = ((ymin + ymax) / 2) / img_height box_width = (xmax - xmin) / img_width box_height = (ymax - ymin) / img_height # 边缘约束,防止出现0.0或1.0 eps = 1e-6 x_center = min(max(x_center, eps), 1.0 - eps) y_center = min(max(y_center, eps), 1.0 - eps) box_width = min(max(box_width, eps), 1.0 - eps) box_height = min(max(box_height, eps), 1.0 - eps) lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {box_width:.6f} {box_height:.6f}") # 写入TXT文件 with open(output_txt_path, 'w', encoding='utf-8') as f: f.write("\n".join(lines)) def batch_convert(voc_dir, image_dir, yolo_label_dir, class_map): """ 批量转换VOC目录下所有XML文件 """ voc_dir = Path(voc_dir) yolo_label_dir = Path(yolo_label_dir) yolo_label_dir.mkdir(parents=True, exist_ok=True) xml_files = list(voc_dir.glob("*.xml")) for idx, xml_file in enumerate(xml_files): output_txt = yolo_label_dir / f"{xml_file.stem}.txt" try: convert_voc_to_yolo(str(xml_file), str(image_dir), class_map, str(output_txt)) except Exception as e: print(f"转换失败 {xml_file.name}: {e}") if (idx + 1) % 500 == 0: print(f"进度: {idx + 1}/{len(xml_files)}")class_map的定义格式如下:
class_map = { 'normal': 0, 'benign': 1, 'malignant': 2, 'inflammation': 3, 'early_stage': 4 }这里我故意没有用字典推导式动态生成映射,而是手写固定映射,因为类别的ID一旦固定下来就在训练配置、评估脚本、模型导出等多个环节复用,必须在项目初期就锁定。
转换脚本里写了几道保险丝:未知类别跳过并告警、非法边界框直接报告、边界值做eps约束。这些在实际批量转换中真的能捞到不少问题数据,值得保留。
3.3 数据划分与验证集设计
数据划分策略直接关系到模型评估的可信度。我的做法分两步。
第一步是按类别做分层采样生成train/val/test三个集合。先按类别分组,分别从每个组里随机抽取80%、10%、10%作为训练集、验证集、测试集,然后再合并。这样做的好处是每个类别在三个集合中的分布基本一致,不会出现某个类别在测试集中只有零星几张的情况。
第二步是检查划分结果中是否存在“近重复样本”。医学图像采集时经常会对同一个病例连续拍多张,这些图像之间高度相似,如果它们同时出现在训练集和测试集中,评估结果会虚高。我用感知哈希加人工抽查的方式找出了18组近重复图像,确保同一组的图像不会跨集合分配。
这里分享一个实操细节:划分文件列表时用固定的随机种子。在random.shuffle之前显式设置random.seed(42),这样整个项目组所有人跑出来的划分结果完全一致。否则每次运行脚本都会得到不同的划分,模型对比时根本无法确定性能差异来自模型还是来自数据划分。
3.4 数据增强与样本平衡
5040张原始图像直接训练,分类模型容易过拟合,检测模型对小目标类别的泛化效果也不会太好。我用了两种增强策略来弥补数据规模上的不足,但要提醒一句:医学图像增强必须遵循“变换后仍是合理医学影像”的原则。
基础几何增强:随机水平翻转、小幅随机旋转(±10度以内)、随机缩放和裁剪。这些变换不改变病灶的形态学特征,对医学图像来说是安全的。垂直翻转我特意关掉了,因为多数医学影像存在解剖学方位约定,比如心脏偏左、肝脏偏右,垂直翻转会破坏这种空间先验,让模型学到错误的位置关联。
色彩空间增强:调整亮度、对比度、饱和度。医学图像对灰度分布极其敏感,直接学习在灰度图上的亮度变化等同于在教模型“更白就是更健康”。我把亮度扰动幅度限制在非常小的范围内,并且只在HSV空间做微调,不用GAN回来再改颜色。这类增强主要是模拟不同设备间轻微的成像差异,而不是给图像做“美容”。
离线增强扩张:对于样本量偏少的类别,我用上述变换做了3倍离线增强,将有效训练样本从5040张扩到大约9000张。为什么不建议只依赖在线增强?因为在线增强每次epoch随机变化,模型看到的是“同一张图的无数个变体”,对过拟合的抑制作用比离线增强弱一些。我两种都用,小类别额外多做离线增强,其他类别靠在线增强。
4. 模型选型与训练配置经验
4.1 分类网络与检测网络的分工
承接标题“医学图像分类与诊断”的双重需求,我实际跑了两个模型链路:
分类链路用轻量级卷积神经网络做整体图像级别的诊断。输入尺寸224x224,用预训练权重初始化,冻结前几个block的骨干参数,只训练高层特征。经过5000多张数据微调后,在测试集上的分类准确率大约能到92%上下,正常与异常的二分类准确率还能再上浮两个点。
检测链路用YOLO系列模型(具体用的是YOLOv8s)做病灶定位与分类。输入尺寸640x640,同样加载预训练COCO权重迁移学习。这个选择不是随便拍的——YOLOv8s的参数量在6000多万,对5000张级别的数据量来说正好处在“够用不过拟合”的甜点区间。如果换成YOLOv8x,参数翻了几倍,很容易把医学图像里那些微小的纹理噪声当特征记住。
两个模型的分工逻辑是这样的:分类模型给整体图像一个快速初筛判断,相当于“预检分诊”;检测模型在初筛异常的图像上定位具体病灶并给出更细的类别判断,相当于“专科诊断”。两个链路串联使用,在推理效率上比单独跑一个大模型更灵活,后续接入不同科室流程时也更好拆分。
4.2 训练参数设置与调整记录
这批数据的训练参数,我记录了一份完整的配置清单,直接贴出来供参考:
| 参数项 | 分类模型 | YOLO检测模型 |
|---|---|---|
| 输入尺寸 | 224x224 | 640x640 |
| 优化器 | AdamW | SGD |
| 初始学习率 | 0.0001 | 0.01 |
| 学习率策略 | 余弦退火 | 余弦退火 |
| 批大小 | 64 | 16 |
| 训练轮数 | 60 | 120 |
| 权重衰减 | 0.0005 | 0.0005 |
| 数据加载线程 | 8 | 8 |
YOLO检测模型我特意选了SGD而不是AdamW,这是基于一个经验判断:检测任务中SGD配合warmup往往能收敛到更平坦的极值点,泛化能力更好。AdamW早期收敛快,但在小数据集上更容易过拟合。
分类模型的微调策略也值得一提。我用了“先冻结骨干训练头,再解冻微调全网络”的两阶段方案。第一阶段训练20轮,骨干参数全部冻结,只更新分类头,让分类头先适应当前数据的特征分布;第二阶段解冻骨干的后半部分,用较小的学习率继续训练40轮。对比直接全网络微调,这个方案在测试集上提升了大约3个百分点。
训练过程的损失曲线需要时刻关注。分类模型的训练损失如果在第10轮附近开始快速下降而验证损失仍然持平,说明模型开始过拟合,要提前终止或者加强正则。检测模型则要同时盯分类损失和边界框回归损失,两个损失都降不下去的时候,大概率是标注本身有问题而不是参数问题。
4.3 评估指标与诊断性能验证
医学诊断场景的评估指标不能只看一个mAP或者准确率,需要多维度衡量。
检测链路我报告了各类别的mAP@0.5(IoU阈值为0.5时的平均精度)。其中malignant类别的mAP最高,因为恶性肿瘤通常边界相对清晰;inflammation类别的mAP偏低,因为这个类别的病灶形态变化大、边界模糊。这提示后续需要给inflammation补充更多数据。
分类链路我用四类指标一起评估:准确率、召回率、精确率、F1分数。这里的重点在于召回率优先——在医学诊断场景中,漏诊的代价远高于误诊。我刻意用低分类阈值来提升召回率,宁可多“误报”几个疑似异常让医生复核,也不能把真病变放过。调阈值的过程就是平衡敏感性和特异性的过程,这个取舍一定要跟临床需求对齐。
还有一个关键验证步骤:把检测模型的输出画成可视化结果图,随机抽了一批叠加了边界框和置信度的输出图像,交给合作方的影像科医生做盲审,确认模型标注的病灶位置在医学上是否合理。这一步不能省,因为数值指标再高,如果标注的位置偏离了病灶的真实解剖结构,模型在临床上是没有价值的。
5. 常见问题与排查技巧实录
5.1 标注格式转换中的典型错误
易错点一:图像尺寸读错了。转换YOLO格式必须知道原始图像的真实宽高,但有些XML里的<size>字段记录的是缩放前的尺寸,实际图像和XML记录不一致。我在批量转换脚本里加了一个校验逻辑:读取图像文件的真实尺寸,和XML里的<size>做比对,不一致的样本单独列出来人工处理。这一步过滤出了31张标注尺寸与图像实际尺寸不符的坏样本。
易错点二:类别名大小写不一致。标注过程中不同标注员可能会把Normal写成normal,或者Benign_1写成benign。YOLO训练时这类不匹配直接导致类别ID错乱,但模型不会报错,只会默默地输出奇怪的loss曲线。我在转换前对全部XML里的类别名做了一次全量统计,发现任何异常命名当场统一修正。
易错点三:空标注文件问题。有些图像确实没有任何目标,对应TXT文件是空的。YOLO训练时加载空标注文件本身不报错,但某些数据增强操作(比如mosaic)在组合样本时可能因为空标注导致索引异常。保险做法是在数据加载器里显式过滤完全没有标注的图像,或者给它们单独标记为背景类。
5.2 医学小目标检测难点与对策
医学图像里病灶尺寸占比通常极小。一张512x512的CT图像里,早期小结节的直径可能只有10到20个像素,在YOLO的第5层特征图上只对应不到1个格子。这类小目标的检测难点是经过多次下采样后特征几乎消失殆尽。
我实际用下来有效的方法有三个:
第一是利用高分辨率特征图。YOLO系列模型的检测头本身有多个尺度,关键是训练时注意让小目标在较早的特征层被分配正样本。如果框架允许配置anchor尺寸,把最小anchor调到接近你数据集中最小病灶的像素尺寸。
第二是加大输入分辨率。把检测模型的输入从640调到了960,小目标的mAP提升了大约7个百分点。代价是训练和推理速度明显下降,但医学场景对实时的要求远低于对精度的要求,这个取舍值得做。
第三是切片式推理。推理时将大图切成若干重叠小块分别检测,再把检测结果映射回原图坐标。这个方法能进一步缓解小目标特征丢失的问题,但需要在后处理里做非极大值抑制合并,逻辑上略复杂一些。
5.3 类别不平衡的应对策略
医学数据集中“正常”样本通常远多于“病变”样本,“良性”远多于“恶性”。如果直接用原始分布训练,模型学到的策略是“全部预测为多数类”,因为这样在损失上占便宜。
我用的处理思路分两个层面。第一是重采样:对少数类做随机过采样,对多数类做随机下采样,让每个类别在一个epoch中出现的次数基本相当。实际操作时对malignant、inflammation这类关键少数类做了轻微过采样,同时没有把normal下采样太狠,因为正常样本的形态多样性也很大,压太狠会损失对正常形态的覆盖能力。
第二是损失函数调整。分类头在计算损失时,对少数类样本的损失乘上一个大于1的权重系数。权重系数的选择我建议先从1.5左右起步,在验证集上观察各类别召回率的变化再逐步调整,不要一开始就设到5甚至10,那样多数情况下会把模型训练“带偏”。
5.4 数据质量检查清单
整理一份检查清单,每次拿到数据集后按顺序过一遍,能少踩80%的坑:
- 检查每张图像是否都能正常打开,文件是否损坏
- 检查XML/TXT标注文件与图像是否一一对应
- 检查图像分辨率、通道数、色深是否统一
- 检查类别名是否全部在类别表内,有无错拼、大小写问题
- 检查边界框是否完全在图像范围内,有无负坐标或超界坐标
- 检查是否存在空标注文件,确认是否是预期行为
- 检查不同类别的样本量分布,记录不平衡比例
- 抽取5%到10%的样本人工目检标注质量
- 检查train/val/test划分是否互斥且覆盖所有样本
清单里的每一项我都吃过亏。尤其“标注文件与图像一一对应”这条,一次批处理时因为文件名编码不一致,导致500多张图加载不到标注,训练了三轮之后才发现loss一直不降。现在每逢数据交接,第一件事就是跑一遍这个检查清单。
6. 一点个人实操体会
做完这5040张的数据集,最大的感受是:做医学图像数据集,格式转换只是最后5%的工作量,前面95%的精力都在标注标准、质量控制和数据治理上。VOC转YOLO的脚本本身半小时就能写完,但为了让这5040张数据经得起反复训练和评估,光标注标准的对齐就花了一周多的时间。
最后再分享一个小技巧:所有脚本都尽量做成幂等的——也就是同一个输入跑两次,输出结果完全一致。转换脚本、划分脚本、检查脚本全部遵循这个原则。这样每次修复数据后重新跑一遍,能够清楚知道哪一步变动影响了最终结果,排查问题时省下的时间远超写脚本时多花的功夫。
能把这套流程完整走下来,后续不管数据规模翻几倍,或者换成其他医学影像模态,核心方法论都是通用的。格式会变、框架会变,但对数据质量的敬畏心不能变。