ARTICLE DETAIL

资讯详情

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

眼睛疾病四分类数据集:ImageFolder与YOLOv5开箱即用训练指南

眼睛疾病四分类数据集:ImageFolder与YOLOv5开箱即用训练指南 简介眼睛疾病分类数据集是一份面向医学图像分类任务的开箱即用资源尤其适合入门或快速验证图像分类、YOLOv5分类训练的开发者使用。压缩包内含604个文件以601张JPEG眼底图像为主体并附带1个Python可视化脚本、1个类别字典JSON与1张示意图片整体约61.34MB目录按train/test组织train共481张、test共120张覆盖白内障、青光眼、正常、视网膜疾病四个类别。数据按文件夹存放可直接用ImageFolder加载无需额外预处理可视化脚本可一次随机展示4张图像并自动保存结果方便快速核对标签与图像内容。目前已有413人学习适合需要可复现分类数据集或开展眼疾识别实验的学生、研究人员与算法工程师。无论是快速搭建分类基线还是用于模型对比实验这份数据都能节省大量整理时间。1. 眼睛疾病分类数据集一个能直接喂给 ImageFolder 的 70MB 四分类资源如果你手头正在做图像分类却总卡在「先预处理再编排目录」这一步眼睛疾病分类数据集能帮你把这道工序省掉。它按 train 和 test 两个文件夹整理好子目录就是白内障、青光眼、正常、视网膜疾病四类torchvision 的 ImageFolder 解压即用不用自己写数据清洗代码。70MB 体积、481 张训练图、120 张测试图对跑通 YOLOv5 分类或 PyTorch 分类实验来说规模正合适。更难得的是它附带 JSON 类别字典和可视化脚本动手训练之前就能先确认每张图的内容与类别映射对不对。适合医学图像分类入门也适合需要快速验证分类流程的熟手复用是一份不用二次加工就能落地的数据集训练集、测试集边界清楚拿来即用。2. 拆开数据集看结构train/test 目录、JSON 字典与 ImageFolder 的加载逻辑2.1 目录结构与类别命名拿到的压缩包解压后主体是一个 data 目录。内部结构是典型的 PyTorch ImageFolder 布局data 下分 train 和 test各自下面再按类别建子文件夹。常见的类别目录名是 Cataract、Glaucoma、Normal、Retina 四个对应白内障、青光眼、正常、视网膜疾病。这种把标签写在文件夹路径里的做法是图像分类数据集最通用的组织方式torchvision、YOLOv5 classify、fastai 都能直接识别不需要额外维护一份图片路径与标签的 CSV。data/ ├── class_dict.json ├── train/ │ ├── Cataract/ │ ├── Glaucoma/ │ ├── Normal/ │ └── Retina/ └── test/ ├── Cataract/ ├── Glaucoma/ ├── Normal/ └── Retina/从文件名单上也能看出规律像是 Retina_052.jpeg、Glaucoma_077.jpeg、NL_030.jpeg 这种前缀就是图片的类别缩写。NL 大概率是 Normal 的缩写。文件名前缀在分类流程里只起辅助作用真正决定标签的是所在子文件夹名后面做任何重命名都不要破坏「图片放在对应类别文件夹下」这条铁律。train 目录下总共 481 张、test 目录下 120 张合计 601 张。这个体量放在自然图像分类里偏小但在眼疾这种医学场景下是合理规模——医学标注本身费人力能稳定提供 600 张已经可以支撑一个完整的分类实验流程验证。如果目标是上线临床辅助工具后续需要继续扩标本如果只是验证分类链路能不能走通、评估模型选型这个体量刚刚好训练一轮用不着等半天。2.2 JSON 字典文件与类别映射数据集里附带一个 class_dict.json作用是保存 4 个类别名称与索引的对应关系。在实际工程里这个 JSON 的价值有两个一是代码里写死类别列表容易出错从 JSON 读能保证单一数据源二是训练脚本和推理脚本共用同一个文件避免训练时和预测时的类别顺序不一致。import json with open(data/class_dict.json, r, encodingutf-8) as fp: cls_dict json.load(fp) print(cls_dict)常见的内容形如{Cataract: 0, Glaucoma: 1, Normal: 2, Retina: 3}。这里有个细节容易踩ImageFolder 并不是按你 JSON 里的顺序去读取类别而是按文件夹名的 ASCII 码排序生成索引。恰好 Cataract、Glaucoma、Normal、Retina 这几个英文名的首字母顺序就是 C、G、N、R和上面这个 JSON 一致但这是巧合不是保证。加载之后一定要打印dataset.class_to_idx实际值和 JSON 比对别偷懒第 5 章的坑一就是从这里来的。2.3 用 ImageFolder 加载并配置 DataLoader图像分类里加载这张数据集的标准姿势就是 torchvision 的 ImageFolder。只要数据根目录下是「类别文件夹套图片」这种结构它会自动扫描全部子目录为每个类别分配索引并把每张图片的路径和标签一一对应好。对这份数据集来说不需要额外写map函数或自定义 Dataset。from torchvision import datasets, transforms from torch.utils.data import DataLoader transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) train_ds datasets.ImageFolder(data/train, transformtransform) test_ds datasets.ImageFolder(data/test, transformtransform) print(训练集样本数:, len(train_ds)) print(测试集样本数:, len(test_ds)) print(ImageFolder类别映射:, train_ds.class_to_idx)逻辑说明与参数Resize 到 224×224 是分类模型最常用的输入尺寸yolov5s-cls 预训练权重默认也按这个尺寸设计后面换 YOLOv5 时不需要改图。Normalize 用的 mean、std 是 ImageNet 统计值。只要后面计划用 ImageNet 预训练权重做迁移学习就必须用这套值因为特征提取阶段期望看到的是标准化后的输入分布。如果是从零训练用mean[0.5]*3, std[0.5]*3也行但既然这份数据后面大概率要接 YOLOv5 预训练权重直接用 ImageNet 参数最省事。train_ds.class_to_idx打印出来的字典要和 2.2 节 cls_dict 比对。不一致时以谁为准我一般以 ImageFolder 实际生成的为准因为训练循环、评估循环读的是 train_ds.targets。DataLoader 里shuffleTrue只在训练集开测试集评估时保持顺序就好。提示如果显存紧张把 batch_size 从 32 降到 16并把 num_workers 改成 2如果显卡吃不满优先调高 num_workers别盲目加 batch。2.4 类别分布统计先看清类不平衡再训练医学数据集的通病是类别不均衡这份数据能不能逃过这个规律不能靠文件名猜跑一遍统计脚本才有答案。481 张训练图按 4 类均分每类应该是 120 左右但实际往往不是这个数。动手训练前先跑一遍数量统计把每类真实数量记下来后面调损失函数或采样策略全指望这张表。from pathlib import Path data_root Path(data/train) for cls_dir in sorted(data_root.iterdir()): if cls_dir.is_dir(): img_count len(list(cls_dir.glob(*.jpeg))) print(f{cls_dir.name}: {img_count} 张)这段脚本用 pathlib 遍历 train 下每个子目录统计 jpeg 文件数。逻辑不复杂但有两个细节值得说sorted(data_root.iterdir())按目录名字母序输出输出顺序稳定方便和 class_to_idx 对应。glob(*.jpeg)只匹配扩展名是 jpeg 的文件。如果压缩包里混进了 jpg 或 png 格式这行统计会漏数所以更稳妥的做法是glob(*)再过滤后缀或在统计前先看一眼文件后缀分布。我用过太多标注好的数据解压后总有几张损坏或奇怪扩展名的图片统计脚本写得宽松一点能提前暴露问题。如果发现某类明显少于其他类别急着改网络结构。优先考虑两种处理一是用带类别权重的 CrossEntropyLoss把少数类的梯度放大二是用 WeightedRandomSampler 做有放回采样让每个 batch 里各类比例大致均衡。这两个方案在第 4 章后面会给出具体实现。3. 用 YOLOv5 分类跑通这个数据集环境、命令与超参数一次性说清3.1 环境准备数据集目录怎么放 YOLOv5 才认YOLOv5 仓库里有 detect、classify、segment 三套入口眼睛疾病四分类走的是 classify 分支。先把 YOLOv5 仓库克隆下来再把这份数据集放进去。常见做法是把 data 整个目录拷贝到 YOLOv5 的 datasets 目录下并重命名为一个语义化名字比如 eye_cls。git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt mkdir -p datasets/eye_cls cp -r data/train datasets/eye_cls/train cp -r data/test datasets/eye_cls/val这里有一个关键点需要注意YOLOv5 classify 训练接口的大多数版本读取的是数据集根目录下的 train 和 val 两个子目录而不是 train 和 test。原始数据只给了 test所以复制一份改名成 val 是最快的处理方式不影响原始数据。如果你复现时发现某个版本支持直接读 test那就不需要这个改名步骤但多准备一个 val 目录永远没有坏处。顺带说下YOLOv5 classify 不需要单独写一个描述类别数的 yaml 配置它直接从子目录数量和名称推断类别。这和 YOLOv5 detect 任务必须先写 data.yaml 的流程完全不同刚从检测切过来的同学容易在这个地方绕弯子。我第一次用这个数据集跑 YOLOv5 分类时花了一晚上找 yaml 文件该怎么写结果人家根本不读。3.2 classify/train.py 训练命令与超参数详解准备好目录后训练命令集中在 classify/train.py 一个脚本里。最精简的启动方式python classify/train.py \ --model yolov5s-cls.pt \ --data datasets/eye_cls \ --epochs 30 \ --img 224 \ --batch-size 32 \ --save-period 5逐项说明--model指定分类预训练路径yolov5s-cls.pt 是 YOLOv5 自带的最轻量分类权重约 20MB 上下适合这个量级的数据集。显存大想提精度就换成 yolov5m-cls.pt 或 yolov5l-cls.pt。--data数据集根目录脚本会在里面自动找 train 子目录做训练、val 子目录做验证。--epochs30 轮在这个数据规模上是比较合理的起点。医学四分类相似度比较高动辄 100 轮在训练集上很容易过拟合验证集准确率从第 20 轮后开始震荡。--img输入尺寸 224。想榨取更多细节可以试 320训练时间大约多 50%但对眼底这类病灶不明显的图像收益不一定值回时间。--batch-size8G 左右显存用 32 没问题用 16 更保守。batch 越大训练越稳但医学小数据集上大 batch 容易让 BN 层统计量失真32 是一个权衡值。--save-period每 5 轮保存一次 checkpoint防止训练中断后从零重来。小数据集单次训练时间短很多人会省掉这个参数但我习惯开着跑实验本来就图一个省心。训练过程中终端会打印 train_cls_loss 和 val_cls_loss 两个核心指标。初次跑完如果看到 val_cls_loss 在第 25 轮左右出现回升同时 train_cls_loss 还在下降那就是过拟合信号后面第 6 章的交叉验证技巧能在小数据上缓解这个问题。3.3 训练日志怎么看从 loss 曲线和指标判断模型状态你可能会遇到这种情况训练 loss 降到 0.1但 val accuracy 只有 75% 左右再跑几轮也不涨。这个现象在四分类眼疾数据上很典型原因有两层。第一层是类别混淆。白内障和正常眼睛在眼底图像上边界模糊模型会把一小部分白内障样本判定为正常。从经验看这类边界模糊的类别对在混淆矩阵里通常误判最多不是调参能解决的是数据本身决定的。第二层是样本少481 张图分 4 类每类一百来张CNN 可学到的判别特征有限。遇到这种情况优先尝试把输入尺寸从 224 提到 320再给分类头加一层 Dropout。YOLOv5 classify 脚本里没有直接的 dropout 开关可以通过在 --model 后换成自己改过的 yaml 配置来实现不过更省事的方案是先换预训练权重做迁移学习别从零训练。看验证集结果YOLOv5 classify 在训练结束后会自动在 val 集上评估 top1 和 top5 准确率。四分类场景下 top5 几乎没有意义直接盯 top1 和每一类的 precision、recall。如果发现 Glaucoma 这一类 recall 特别低说明模型把青光眼误判成了其他类这时候单纯提准确率没用要针对混淆矩阵里出错最多的两个类别去补充训练数据。3.4 classify/val.py 验证与混淆矩阵分析训练结束后用 best.pt 在 val 目录上单独验证一遍并把结果可视化。这一步不能省因为 train.py 日志里打的是最后一个 epoch 的指标而 best.pt 是按 val 准确率保存的最优权重两者可能差好几个点。python classify/val.py \ --weights runs/train-cls/exp/weights/best.pt \ --data datasets/eye_cls \ --img 224运行完会在 runs/val-cls/exp 下生成混淆矩阵图、每类的 precision 和 recall 曲线。对医学分类来说混淆矩阵远比总准确率重要。举个例子如果总准确率 80%但 Normal 类把 20 张 Retina 误判成了自己说明模型在漏报视网膜疾病这在医学场景里比分数降低更值得警惕。拿到混淆矩阵后我建议把出错最多的那一对类别截图留档后续做数据增强或故障分析都用得上。YOLOv5 classify 在验证阶段是按文件名顺序遍历 val 目录的没有 shuffle所以结果可复现。如果你的数据集后续扩充了重跑 val 时要清掉旧的 runs/val-cls 目录避免新旧结果混在一起。4. 可视化脚本探底随机抽 4 张图把数据质量看清楚再训练4.1 数据集附带可视化脚本的工作机制这份数据压缩包里带了一个可直接运行的可视化 py 文件功能是随机从数据集里取 4 张图拼成 2×2 画布展示并保存到当前目录。它的价值不在写得多精巧而是让你在训练前用最少的操作确认三件事图片内容与文件夹名是否一致、图片是否损坏、类别之间长什么样。脚本的等价实现如下实际跑的时候直接执行自带的 py 文件即可不需要改任何路径import random from pathlib import Path import matplotlib.pyplot as plt from PIL import Image data_root Path(data/train) all_images [] for class_dir in data_root.iterdir(): if class_dir.is_dir(): all_images.extend(class_dir.iterdir()) sample random.sample(all_images, 4) fig, axes plt.subplots(2, 2, figsize(8, 8)) for ax, img_path in zip(axes.ravel(), sample): img Image.open(img_path).convert(L) ax.imshow(img, cmapgray) ax.set_title(f{img_path.parent.name} - {img_path.name}, fontsize10) ax.axis(off) plt.tight_layout() plt.savefig(visual_check.png, dpi150) plt.show()逻辑说明.convert(L)把图片转成灰度。眼底照相机的输出大多本质是单通道灰度图个别以三通道 jpg 存储但三个通道值一样强制转灰度可以避免 matplotlib 把单通道数据用伪彩色渲染。random.sample(all_images, 4)是无放回随机抽 4 张每次运行结果不同。想看固定结果就加random.seed(42)。标题里同时打印父目录名和文件名方便定位某张具体图片在哪里。如果发现图片内容和目录名明显不符说明数据标注有问题需要返工这是可视化脚本最重要的产出。4.2 从可视化结果检查图像质量的三个维度第一看亮度分布。眼底图像经常有大片黑色背景病灶区域集中在圆形视野内部如果抽样图整体偏黑matplotlib 默认显示会把细节压暗。这不是数据坏了是显示映射问题。用np.percentile(img, 95)看亮度上限再决定是否要做对比度增强。第二看分辨率差异。不同来源的眼底图分辨率可能从几百到上千像素不等训练时 Resize 到 224 会把差异抹平但如果某张图原始分辨率过低放大后会出现严重的马赛克这类低质图会在训练时持续制造噪声。可视化脚本抽样比较随机多跑几轮抽到模糊图就能发现。第三看类间相似度。把四类图各抽一张拼在一起人眼就能判断这个分类任务的难度。白内障和正常类别的眼底图在亮度、血管分布上高度接近经验不深的标注员都可能标错。这份数据集的分类难度主要就集中在这两类的边界上不是模型问题是数据本身决定的别把时间浪费在无效调参上。4.3 少数类样本不均衡的处理第 2 章统计出来的类别数量如果差异明显可视化脚本又多跑了几轮会更直观地看到少数类图片在形态上的特殊性。处理不均衡我习惯先试 WeightedRandomSampler不动网络结构、不改损失函数只改变采样概率是最轻量的方案。import torch from torch.utils.data import WeightedRandomSampler, DataLoader labels train_ds.targets class_counts torch.bincount(torch.tensor(labels)) sample_weights 1.0 / class_counts[labels].double() sampler WeightedRandomSampler( weightssample_weights, num_sampleslen(labels), replacementTrue ) train_loader DataLoader(train_ds, batch_size32, samplersampler)参数说明class_counts[labels]把每张图自己类别的数量取出来取倒数就是权重。类别数量越多权重越小采样到它的概率越低。num_samples设置为len(labels)保证每个 epoch 采样总数和原始训练集一致。replacementTrue表示有放回采样少数类会被重复抽到多数类可能会被漏采这正是我们要的效果。用了这个采样器之后每次 epoch 里各类别出现次数会被拉平能缓解少数类 recall 低的问题。它也有副作用训练集的有效信息量下降因为多数类某些样本一个 epoch 里根本见不到。所以我的习惯是先用普通 DataLoader 跑一版 baseline再看混淆矩阵决定要不要上加权采样。一上来就加权效果好坏你都说不清楚是采样带来的还是模型带来的。5. 避坑指南眼睛疾病分类数据集从加载到训练翻过的五个车这个数据集虽然开箱即用但具体到每个人的环境还是有几个坑高频出现。以下 5 条都是我在实际用它的时候翻过车的地方每条按「现象 → 原因 → 解决」写清楚你碰到类似报错可以直接对号入座。5.1 加载阶段的坑ImageFolder 排序与 YOLOv5 val 目录坑一ImageFolder 的类别顺序和 JSON 字典对不上。现象打印 train_ds.class_to_idx发现顺序是 Cataract、Glaucoma、Normal、Retina而 JSON 字典里定义的顺序可能是 Retina、Glaucoma、Cataract、Normal训练时 loss 在降但混淆矩阵里标签全部错位。原因ImageFolder 按文件夹名的 ASCII 码排序分配索引跟你写 JSON 时的顺序没有任何关系。很多教程代码里类别索引写死了 0、1、2、3一旦目录名字顺序变了标签就全乱了。解决加载后用 assert 强制比对assert train_ds.class_to_idx cls_dict, 类别映射不一致请先修正目录名或JSON如果断言失败以 train_ds 的 class_to_idx 为准重新生成一份 JSON别手动改目录名凑顺序因为后续还要给 YOLOv5 用目录名保持稳定更重要。坑二YOLOv5 classify 训练时找不到 val 目录。现象执行 classify/train.py 后直接报AssertionError或提示找不到数据集查了路径也没错就是启动不了。原因这份数据集解压后只有 train 和 test而 YOLOv5 classify 多数版本默认在数据集根目录下找 train 和 val 两个子目录名字对不上就直接报错。解决在拷贝阶段顺手把 test 复制一份命名成 val一行命令的事。cp -r data/test datasets/eye_cls/val以后凡是拿到 train/test 结构的数据要喂给 YOLOv5 classify我都是先看一眼它源码里默认读的目录名再决定怎么迁移目录。翻车一次之后这个习惯就长在肌肉记忆里了。5.2 训练阶段的坑图像显示、收敛困难和标签错位坑三可视化脚本保存的图片黑成一团病灶结构看不清。现象样例图上大面积的黑色背景中间圆形视野区域也很暗血管细节肉眼不可辨以为是图片损坏。原因眼底相机原始图像的像素值大量集中在低亮度区间matplotlib 默认的线性映射让暗部细节丢失。这是显示问题不代表数据文件损坏直接拿 Otsu 阈值法判断图片有没有问题会误杀很多好样本。解决先打印图像像素的 min、max、均值再决定做不做增强。最稳妥的做法是在可视化脚本里加一句ImageOps.autocontrast(img)见过一次正常显示效果后心里就有底了。坑四训练 loss 降到很低验证准确率卡在 75%再加 epoch 完全不涨。现象30 轮之后 train_cls_loss 已经到 0.1 以下val 准确率却稳定在 75% 上下输出混淆矩阵发现白内障和正常这一类边界模糊的样本互相误判比例最高。原因这是典型的样本量与类间相似度共同导致的过拟合。眼疾四分类里白内障与正常图的边界本来就模糊几百张训练图撑不起高判别力特征。最直接的证据就是混淆矩阵里错得最多的恰好是人眼也最难分的两个类别。解决换预训练权重做迁移学习把 --img 提到 320并且不要盲目加 epoch。如果还想压榨性能在分类头前加 dropout或者用第 6 章的分层交叉验证评估模型稳定性而不是追求单次训练的指标上限。坑五JSON 字典文件的 key 和实际文件夹名不一致导致标签错位。现象val 混淆矩阵对角线上的数值不是最高整体看起来像随机猜测但训练 loss 又是正常的。原因有人手动改过某个子目录名比如把 Retina 改成 RetinalJSON 里的 key 没同步更新。ImageFolder 读的是新目录名json 文件里还是旧 key训练脚本如果拿 JSON 做标签索引就会出现错位。Windows 解压工具偶尔也会把大小写悄悄改掉这类问题最隐蔽排查半天都未必能发现。解决数据集解压后先执行一遍目录名列表与 JSON key 的比对发现问题就用小脚本批量重命名目录并同步生成新的 JSON。这一类问题靠人工盯是盯不过来的写进数据校验流程里一劳永逸。从那以后我拿到任何带 JSON 映射的数据集第一步永远是自动比对目录和 JSON而不是直接打开训练脚本。6. 进阶用五折分层验证榨干 601 张图把评估结果从碰运气变成可复现train 与 test 加在一起有 601 张直接把 test 当验证集用120 张的评估波动其实不小。想在报告里给出更稳的准确率我一般会把 train 和 test 合并用 StratifiedKFold 做五折交叉验证。四类样本量都不大分层抽样保证每一折里各类占比和总体一致避免某折恰好缺了青光眼样本这类尴尬情况。from pathlib import Path import shutil from sklearn.model_selection import StratifiedKFold from torchvision import datasets merge_root Path(data_merged) for split in [train, test]: for cls_dir in Path(data, split).iterdir(): dst merge_root / cls_dir.name dst.mkdir(parentsTrue, exist_okTrue) for img in cls_dir.iterdir(): shutil.copy(img, dst / f{split}_{img.name}) ds datasets.ImageFolder(str(merge_root)) skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) for fold, (train_idx, val_idx) in enumerate(skf.split(ds.imgs, ds.targets), 1): for subset, idx_list in [(train, train_idx), (val, val_idx)]: out_dir Path(crossval) / ffold{fold} / subset out_dir.mkdir(parentsTrue, exist_okTrue) for i in idx_list: img_path, target ds.imgs[i] cls_name ds.classes[target] shutil.copy(img_path, out_dir / cls_name / f{Path(img_path).name})这段代码做了两件事第一步把 train 和 test 下的图片合并到 data_merged复制时加了split_前缀防止 train 和 test 里出现同名文件互相覆盖第二步按 StratifiedKFold 划分索引并复制到 fold 目录。关键参数是random_state42固定随机种子才能保证五折结果可复现别人拿同一份数据能跑出同样结果。n_splits5在小数据集上是平衡点折数越多每折训练集越小模型越不稳。每个 fold 都跑一遍训练与验证最后报告五折均值和标准差。79%±2.1% 和 79%±5.8% 的可信度完全不同后者说明模型对数据划分敏感需要检查数据分布是否有问题。如果你也想复现这套流程把这份眼睛疾病分类数据集解压之后按照第 2 章的目录核对和可视化检查走一遍再到第 3 章跑 YOLOv5 分类训练最后用这个五折分层验证兜底基本不会翻车。从那以后我每次拿到这种几十 MB 的小型医学分类数据集都会先做类别统计、跑通 baseline再强制用分层验证评估一轮三个步骤缺一不可。读图的人可能只关心最终准确率但只有用分层验证确认过模型稳定性那个数字才敢写进报告里。希望帮到你。本文还有配套的精品资源点击获取
返回列表