简介这份资源是面向医学图像处理与深度学习入门者的23类皮肤病分类数据集适用于图像分类模型训练、迁移学习实验及YOLOv5分类任务等场景。数据按文件夹组织可直接用ImageFolder加载无需额外预处理。压缩包共约2000个文件以1998张jpeg图像为主另含1个py可视化脚本和1个json类别字典整体约933.7MB其中train训练集15557张、test测试集4002张覆盖湿疹、肿瘤、真菌感染等23个类别。可视化脚本随机抽取4张图片即可展示并保存到当前目录无需修改即可运行便于快速核验数据质量与类别分布。目前已有531人学习下载适合希望快速搭建皮肤病分类基线、验证模型效果或开展课程实验的读者参考使用。1. 23 类皮肤病图像分类数据集从拿到文件夹到跑通第一个 baseline上周帮一个做皮肤镜辅助诊断的团队看数据管线他们卡在同一个地方手里有几千张临床图但类别标签乱、目录结构散光是整理成ImageFolder能读的格式就耗了两天。这类问题在医学图像项目里太常见了——模型还没开始调数据先把你劝退。这份 23 类皮肤病分类数据集就是冲着这个痛点来的967 MBtrain 目录 15,557 张、test 目录 4,002 张按类别文件夹存放torchvision.datasets.ImageFolder直接能读不用写任何清洗脚本。类别覆盖湿疹、肿瘤、真菌感染、带状疱疹、单纯疱疹等 23 种还附带一份类别字典 JSON 和一个随机抽 4 张图做可视化的 py 脚本。适合谁想快速验证分类网络、做医学图像迁移学习、或者拿它当 YOLOv5 分类分支练手的人。下面我按「结构怎么读 → 怎么接进训练管线 → 坑在哪 → 怎么验证」的顺序拆一遍。2. 目录结构与 ImageFolder 映射先搞清楚标签是怎么来的2.1 train/test 双目录与类别字典的对应关系拿到压缩包解压后根目录下是data/里面分train/和test/两个子目录。每个子目录下再按类别名建文件夹比如herpes-type-1-recurrent/、eczema-herpeticum/、herpes-zoster/这种。文件名保留了原始采集编号像herpes-type-1-recurrent-94.jpeg、11IMG012.jpeg、3Eczema2-139.jpeg格式不统一但无所谓——ImageFolder只认文件夹名不认文件名。这里有个关键点ImageFolder的类别索引是按文件夹名的字母序自动生成的不是按你 JSON 里的顺序。也就是说class_to_idx是运行时算出来的跟字典文件里的编号可能对不上。我一般会先把dataset.class_to_idx打印出来存一份后面推理映射标签时用这个而不是用 JSON 里的原始编号。from torchvision.datasets import ImageFolder from torchvision import transforms tf transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), ]) train_ds ImageFolder(rootdata/train, transformtf) test_ds ImageFolder(rootdata/test, transformtf) print(类别数:, len(train_ds.classes)) print(class_to_idx:, train_ds.class_to_idx) print(训练样本:, len(train_ds), 测试样本:, len(test_ds))逻辑说明root指向类别文件夹的上一级ImageFolder会自动扫描所有子目录名作为类别。class_to_idx返回的是{类别名: 索引}字典索引从 0 开始按字母序排。参数上transform先做 Resize 再转 Tensor224×224 是大多数 ImageNet 预训练模型的默认输入如果你用 EfficientNet 或 ConvNeXt 可以改成 256 或 384。2.2 类别字典 JSON 的读取与校验数据集附带的 JSON 是类别编号到名称的映射。常见做法是把它读进来跟ImageFolder生成的class_to_idx做一次交叉校验确认没有类别缺失或拼写不一致。import json with open(data/class_dict.json, r, encodingutf-8) as f: class_dict json.load(f) folder_classes set(train_ds.classes) json_classes set(class_dict.values()) print(仅在文件夹中:, folder_classes - json_classes) print(仅在JSON中:, json_classes - folder_classes)逻辑说明两个集合做差集如果两边都为空说明类别完全对齐。如果有差异优先以文件夹为准因为ImageFolder实际读的是文件夹。参数上注意encodingutf-8医学类别名里可能有特殊字符。这一步花不了两分钟但能避免训练完发现标签错位的血泪经验。2.3 可视化脚本的用法与输出数据集里带了一个可视化 py 文件随机抽 4 张图展示并保存到当前目录。直接python visualize.py就能跑不需要改路径——前提是你解压后的目录结构跟脚本预期一致。如果报FileNotFoundError检查脚本里的根路径是不是写死的data/train把它改成你的实际路径即可。这个脚本的价值在于快速确认图像没有损坏、方向正确、类别文件夹没放错。我一般会在正式训练前跑一次肉眼扫一遍四张图比写一堆校验代码来得快。3. 接进训练管线从 DataLoader 到 YOLOv5 分类分支3.1 构建 DataLoader 与分层采样数据量 15,557 训练 4,002 测试不算大单卡就能跑。但 23 类之间大概率不均衡——湿疹、真菌感染这类常见病样本多某些罕见型别可能只有几十张。直接随机采样会让模型偏向多数类。常见做法是用WeightedRandomSampler按类别频率倒数加权。import torch from torch.utils.data import DataLoader, WeightedRandomSampler from collections import Counter targets [label for _, label in train_ds.samples] class_counts Counter(targets) print(各类样本数:, class_counts) weights [1.0 / class_counts[t] for t in targets] sampler WeightedRandomSampler(weights, num_sampleslen(weights), replacementTrue) train_loader DataLoader(train_ds, batch_size32, samplersampler, num_workers4) test_loader DataLoader(test_ds, batch_size32, shuffleFalse, num_workers4)逻辑说明train_ds.samples返回[(路径, 标签), ...]列表提取标签后统计每类数量。weights是每个样本的采样权重等于其类别频率的倒数这样少数类被抽中的概率更高。WeightedRandomSampler的num_samples设成总样本数replacementTrue表示有放回采样。参数上num_workers根据 CPU 核数调一般设 4 或 8batch_size32 在 224 分辨率下 8GB 显存够用显存小就降到 16。3.2 用 timm 快速搭一个 baseline不从头写网络直接用timm拉一个预训练模型改分类头。这是验证数据集可用性最快的方式。import timm import torch.nn as nn model timm.create_model(efficientnet_b0, pretrainedTrue, num_classes23) criterion nn.CrossEntropyLoss() optimizer torch.optim.AdamW(model.parameters(), lr1e-3, weight_decay1e-4) device torch.device(cuda if torch.cuda.is_available() else cpu) model.to(device) for epoch in range(5): model.train() for imgs, labels in train_loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() loss criterion(model(imgs), labels) loss.backward() optimizer.step() print(fepoch {epoch} done)逻辑说明timm.create_model的num_classes23会自动替换原分类头pretrainedTrue加载 ImageNet 权重。优化器用 AdamW学习率 1e-3 对微调来说偏大实际可以降到 3e-4 并加余弦退火。这里只跑 5 个 epoch 看 loss 是否下降确认管线通了再上完整训练。参数上weight_decay1e-4 是常规正则医学图像数据量不大时防止过拟合。3.3 转成 YOLOv5 分类格式数据集介绍里提到可以用作 YOLOv5 分类数据集。YOLOv5 的分类任务目录结构跟ImageFolder一致也是train/类别名/图片和val/类别名/图片。所以只需要把test重命名成val或者建个软链接。cd data ln -s test val cd .. python train.py --data ../data --epochs 50 --batch-size 32 --img 224 --model yolov5s-cls.pt逻辑说明YOLOv5 分类训练脚本用--data指向包含train/和val/的根目录。软链接方式不复制文件省磁盘。--model yolov5s-cls.pt是分类专用预训练权重--img 224跟前面保持一致。注意 YOLOv5 的分类脚本对类别文件夹名没有额外要求但要求train和val下的类别集合完全一致否则会报错。4. 避坑与排查23 类皮肤病数据集的五个真实翻车点4.1 类别文件夹名带空格或特殊字符导致 ImageFolder 报错现象ImageFolder初始化时抛FileNotFoundError或类别数比预期少。原因某些类别文件夹名里混入了空格、中文括号或不可见字符ImageFolder按目录名做类别索引时解析异常。解决用os.listdir列出所有文件夹名逐个repr()打印肉眼检查有没有异常字符统一重命名成小写字母加连字符的格式。4.2 train 和 test 的类别集合不一致现象训练时正常评估时test_ds.classes比train_ds.classes少几个导致预测索引对不上。原因某些类别在 test 里没有样本或者文件夹名拼写有细微差异比如herpes-zostervsherpes_zoster。解决在构建 Dataset 后立刻比较两个class_to_idx的 key 集合不一致就手动补齐空文件夹或统一命名。4.3 图像通道数不是 3 导致 ToTensor 后维度错误现象DataLoader取 batch 时报RuntimeError: expected 4D input。原因部分皮肤病图像是灰度图或带 alpha 通道的 PNGToTensor后变成 1 通道或 4 通道。解决在 transform 里加transforms.Lambda(lambda x: x.convert(RGB))强制转三通道。这个坑在医学图像里特别常见因为设备导出的格式五花八门。4.4 样本不均衡导致模型只预测多数类现象训练 loss 下降但验证准确率卡在某个值混淆矩阵显示所有预测集中在一两类。原因23 类里头部类别样本量可能是尾部的几十倍不加权采样时模型直接摆烂。解决用 3.1 节的WeightedRandomSampler或者改用 Focal Loss 降低易分类样本的权重。我一般先看class_counts的分布如果最大最小比超过 10:1 就上采样。4.5 可视化脚本保存路径写死导致覆盖或找不到现象跑可视化脚本后当前目录没生成图片或者图片被覆盖。原因脚本里保存路径用了相对路径./output.png而你运行脚本的工作目录不是数据集根目录。解决cd到脚本所在目录再运行或者打开脚本把保存路径改成绝对路径。这个不算大坑但第一次用容易懵。5. 进阶验证用混淆矩阵和 t-SNE 确认 23 类真的分得开跑完 baseline 别只看 accuracy23 类问题里 accuracy 会被头部类别拉高掩盖尾部类别的崩溃。我一般会做两件事混淆矩阵和 t-SNE 特征可视化。混淆矩阵用sklearn.metrics.confusion_matrix把test_loader跑一遍收集预测和真实标签然后归一化按行显示。重点看对角线以外哪些类别互相混淆——皮肤病里湿疹和疱疹性湿疹视觉上确实接近如果这两类互相误判多说明模型没学到细粒度特征可以考虑加大输入分辨率或换更强的 backbone。import numpy as np from sklearn.metrics import confusion_matrix import seaborn as sns import matplotlib.pyplot as plt model.eval() all_preds, all_labels [], [] with torch.no_grad(): for imgs, labels in test_loader: imgs imgs.to(device) preds model(imgs).argmax(dim1).cpu().numpy() all_preds.extend(preds) all_labels.extend(labels.numpy()) cm confusion_matrix(all_labels, all_preds, normalizetrue) plt.figure(figsize(14, 12)) sns.heatmap(cm, annotFalse, cmapBlues) plt.savefig(confusion_matrix.png, dpi150)逻辑说明normalizetrue按真实标签归一化每行加起来为 1方便看每个类别的召回率。annotFalse是因为 23×23 格子太密标数字反而看不清看颜色深浅就够了。保存成 PNG 后重点扫对角线如果某一行整体偏暗说明那个类别召回率低。t-SNE 更直接把test_ds过一遍模型取倒数第二层特征降到二维画散点。23 类如果能在二维空间里聚成相对独立的簇说明特征判别性够用如果好几类混成一团那分类头再调也白搭得回去改数据增强或换预训练权重。这个数据集 4,002 张测试图t-SNE 跑一次大概一两分钟值得做。从那以后我每次拿到新的分类数据集都强制先跑一遍class_to_idx校验加混淆矩阵确认标签没串、类别没塌再动模型结构。希望帮到你。本文还有配套的精品资源点击获取