23类皮肤病图像分类数据集:从ImageFolder加载到迁移学习baseline实战
简介这份资源面向医学图像处理与深度学习入门者提供23类皮肤病图像分类数据集覆盖湿疹、肿瘤、真菌感染、带状疱疹等常见病种可直接用于图像分类模型训练与验证。数据按文件夹组织train训练集含15557张、test测试集含4002张配合ImageFolder即可加载无需额外清洗或标注也能作为YOLOv5分类任务的数据源。压缩包共2000个文件以1998张jpeg图像为主另附1个py可视化脚本和1个json类别字典整体约933.7MB7z格式打包。其中json文件给出23类标签映射py脚本随机抽取4张图片即可展示并保存到当前目录无需修改即可运行便于快速检查数据分布与类别质量。目前已有531人学习适合希望快速搭建皮肤病分类基线、验证模型效果或开展课程实验的读者使用。1. 23 类皮肤病图像分类数据集从拿到文件夹到跑通第一个 baseline如果你做过医学图像分类大概率遇到过这种局面论文里写着「在皮肤病数据集上验证」但你手里只有几百张图类别还不平衡连个像样的验证集都切不出来。这份 23 类皮肤病分类数据集解决的就是这个前置问题——它把训练集和测试集按文件夹分好train 下 15,557 张、test 下 4,002 张总共 967 MB23 个类别覆盖湿疹、肿瘤、真菌感染、疱疹等常见皮肤病变。目录结构直接对齐ImageFolder的约定不需要你写额外的标注解析脚本。适合两类人一是想快速验证分类模型ResNet、EfficientNet、ViT 都行的算法工程师二是拿它做课程设计或毕设、需要一份「能直接跑」数据的学生。下面按「数据长什么样 → 怎么接进训练管线 → 坑在哪 → 怎么验证」的顺序拆开讲。2. 数据组织与 ImageFolder 加载先看清目录再动手2.1 目录结构与类别字典拿到压缩包解压后根目录下是data里面分train和test两个子目录各自再按类别名建文件夹。这种「一类一文件夹」的结构是 PyTorchImageFolder的标准输入格式也是 yolov5 分类任务默认吃的格式。文件名本身带类别前缀比如herpes-type-1-recurrent-94.jpeg、eczema-herpeticum-3.jpeg、herpes-zoster-13.jpeg从文件名就能大致判断归属但真正决定标签的是它所在的文件夹名不是文件名——这点后面避坑章节会展开。数据集还附带一个 JSON 字典文件记录 23 个类别的映射关系。常见做法是把它读进来生成class_to_idx方便后续把预测结果还原成可读类别名。先确认一下目录层级和类别数量import os, json from pathlib import Path root Path(data) train_dir root / train test_dir root / test # 列出类别文件夹确认 23 类 classes sorted([d.name for d in train_dir.iterdir() if d.is_dir()]) print(类别数:, len(classes)) print(前 5 类:, classes[:5]) # 统计每个类别的图片数量检查是否严重不平衡 for c in classes: n_train len(list((train_dir / c).glob(*.jpeg))) len(list((train_dir / c).glob(*.jpg))) n_test len(list((test_dir / c).glob(*.jpeg))) len(list((test_dir / c).glob(*.jpg))) print(f{c:40s} train{n_train:5d} test{n_test:5d})这段脚本做三件事确认类别数是否为 23、打印类别名、逐类统计 train/test 图片数。参数上唯一要注意的是后缀——数据集里以.jpeg为主但可能混有.jpg或.png所以 glob 时把常见后缀都覆盖上避免漏统计。跑完你会得到一张类别分布表如果某些类别 train 只有几十张、test 只有几张那训练时就得考虑重采样或类别权重这是后面调参的依据。2.2 用 ImageFolder 接进 DataLoader确认目录没问题后接进 PyTorch 的标准流程就是ImageFolderDataLoader。不需要自己写Dataset子类这也是这份数据集省事的地方from torchvision import datasets, transforms from torch.utils.data import DataLoader # 训练集做增强测试集只做 resize 归一化 train_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), transforms.RandomRotation(15), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) test_tf 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_set datasets.ImageFolder(data/train, transformtrain_tf) test_set datasets.ImageFolder(data/test, transformtest_tf) train_loader DataLoader(train_set, batch_size32, shuffleTrue, num_workers4) test_loader DataLoader(test_set, batch_size32, shuffleFalse, num_workers4) print(train 样本数:, len(train_set), 类别:, train_set.classes) print(test 样本数:, len(test_set))逻辑说明ImageFolder会自动扫描子目录名作为类别、图片作为样本class_to_idx按文件夹名字母序生成。归一化用的 mean/std 是 ImageNet 的统计值如果你从零训练而非加载预训练权重可以改成自己算的均值方差但用预训练权重时保持一致更稳。batch_size32是 224 分辨率下的常见起点显存不够就降到 16 或 8。num_workers在 Windows 上如果报多进程错误先设成 0 排查。提示train 和 test 的class_to_idx必须一致。ImageFolder 各自扫描各自的目录只要两边文件夹名完全相同映射就一致一旦某类在 test 里缺失索引就会错位务必用train_set.class_to_idx test_set.class_to_idx校验一次。2.3 可视化脚本怎么用数据集自带一个可视化 py 文件随机抽 4 张图展示并保存到当前目录。这类脚本的价值在于快速肉眼确认「图和标签对不对得上」——医学图像里标签错配是常见脏数据问题训练前扫一眼能省掉后面调半天的冤枉功夫。常见做法是直接运行它会读data/train下的图片用 matplotlib 拼成 2x2 网格。如果你要改抽样数量或保存路径改脚本里的n和save_path两个变量即可。运行前确认 matplotlib 已装且当前工作目录在数据集根目录下否则相对路径会找不到图。3. 训练配置与迁移学习把 23 类跑出一个能看的准确率3.1 选 backbone 与冻结策略23 类、1.5 万张训练图这个规模不适合从零训大模型迁移学习是性价比最高的路线。常见选择是 ResNet50 或 EfficientNet-B0前者稳、社区资料多后者参数少、推理快。加载预训练权重后把最后的全连接层换成 23 维输出import torch import torch.nn as nn from torchvision import models def build_model(num_classes23, backboneresnet50, freezeTrue): if backbone resnet50: model models.resnet50(weightsmodels.ResNet50_Weights.DEFAULT) if freeze: # 冻结除 fc 外的所有层先只训分类头 for p in model.parameters(): p.requires_grad False model.fc nn.Linear(model.fc.in_features, num_classes) elif backbone efficientnet_b0: model models.efficientnet_b0(weightsmodels.EfficientNet_B0_Weights.DEFAULT) if freeze: for p in model.parameters(): p.requires_grad False model.classifier[1] nn.Linear(model.classifier[1].in_features, num_classes) return model model build_model() device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device)参数说明freezeTrue时只训练新换的分类头适合数据量偏小、怕过拟合的阶段等分类头收敛后再解冻后面几个 block 做微调学习率调小一个量级。num_classes23必须和实际类别数一致写错会在 loss 计算时直接报维度不匹配。backbone 选哪个不用纠结先跑通 ResNet50有精力再对比 EfficientNet。3.2 训练循环与关键超参一个最小可用的训练循环包含训练和验证两个阶段import torch.optim as optim criterion nn.CrossEntropyLoss() optimizer optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr1e-3) scheduler optim.lr_scheduler.StepLR(optimizer, step_size7, gamma0.1) def run_epoch(loader, trainTrue): model.train() if train else model.eval() total_loss, correct, total 0.0, 0, 0 with torch.set_grad_enabled(train): for imgs, labels in loader: imgs, labels imgs.to(device), labels.to(device) outputs model(imgs) loss criterion(outputs, labels) if train: optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() * imgs.size(0) correct (outputs.argmax(1) labels).sum().item() total imgs.size(0) return total_loss / total, correct / total for epoch in range(15): tr_loss, tr_acc run_epoch(train_loader, trainTrue) va_loss, va_acc run_epoch(test_loader, trainFalse) scheduler.step() print(fepoch {epoch1:02d} | train {tr_loss:.4f}/{tr_acc:.4f} | val {va_loss:.4f}/{va_acc:.4f})逻辑说明CrossEntropyLoss是单标签多分类的标准损失内部含 softmax所以模型输出不要自己再加 softmax。优化器只传requires_gradTrue的参数冻结阶段就只更新分类头。StepLR每 7 个 epoch 把学习率乘 0.1是迁移学习的常用节奏。验证阶段用model.eval()关掉 dropout 和 BN 的统计更新否则验证结果会飘。跑 15 个 epoch 后看 val 准确率曲线如果 train 一路涨、val 早早就平甚至下降就是过拟合回头加增强或加 dropout。3.3 类别不平衡的处理23 类皮肤病数据里湿疹、疱疹这类常见病样本多罕见病变可能只有几十张。直接训练会让模型偏向多数类。两种常见做法一是给CrossEntropyLoss传weight参数按类别频率的倒数加权二是用WeightedRandomSampler在采样阶段就平衡。前者改动小后者对少数类更友好from torch.utils.data import WeightedRandomSampler import numpy as np targets [s[1] for s in train_set.samples] class_count np.bincount(targets) class_weight 1.0 / class_count sample_weight class_weight[targets] sampler WeightedRandomSampler(sample_weight, num_sampleslen(sample_weight), replacementTrue) train_loader DataLoader(train_set, batch_size32, samplersampler, num_workers4)参数说明class_weight取频率倒数样本越少的类权重越高replacementTrue表示有放回采样保证少数类能被反复抽到。注意用了sampler就不能再设shuffleTrue两者冲突会报错。评估时别只看总体准确率少数类的 recall 才是关键建议打印每类的混淆矩阵。4. 避坑与排查这几处翻车我踩过4.1 文件名带类别前缀但标签只认文件夹现象看到herpes-zoster-13.jpeg就以为标签能从文件名解析自己写了个按文件名切分的 Dataset结果和 ImageFolder 的标签对不上。原因这份数据的标签由文件夹决定文件名前缀只是命名习惯两者在个别样本上可能不一致。解决统一用 ImageFolder别自己解析文件名如果确实要用文件名先抽样比对文件名前缀和文件夹名是否一致。4.2 train/test 类别数不一致导致索引错位现象训练时 loss 正常下降但验证准确率始终在随机水平附近。原因某个类别只在 train 有、test 没有或反过来两个 ImageFolder 的class_to_idx长度不同预测索引和真实标签错位。解决训练前强制校验train_set.class_to_idx test_set.class_to_idx不一致就手动对齐或剔除缺失类。4.3 图像尺寸和通道不统一现象DataLoader 报stack expects each tensor to be equal size或通道数错误。原因数据里混有灰度图或尺寸差异极大的图Resize只统一了空间尺寸没处理单通道。解决在 transform 里加transforms.Grayscale(num_output_channels3)把灰度转三通道或自定义 transform 做兜底Resize放在ToTensor之前。4.4 验证集当测试集反复调参现象调了很久test 准确率很高换个新数据就崩。原因把 test 当验证集用超参和模型选择都在这 4002 张上做的等于变相过拟合。解决从 train 里再切一份验证集比如 10%test 只在最后评估一次别拿它指导训练。4.5 归一化参数照搬导致收敛慢现象从零训练时 loss 下降很慢。原因直接用了 ImageNet 的 mean/std但这份数据的亮度分布和自然图像差别大。解决统计训练集的像素均值和方差替换掉默认值几行代码的事收敛会明显改善。5. 进阶验证与一个收尾习惯跑通 baseline 之后别急着上更复杂的模型先把评估做扎实。医学图像分类里总体准确率会骗人——多数类占大头时全预测成多数类也能有不错的 accuracy。我一般会强制走一遍这套验证流程先出混淆矩阵看哪些类互相混再逐类算 precision/recall/F1最后看 top-3 准确率临床上给候选诊断比只给一个更有意义。from sklearn.metrics import classification_report, confusion_matrix import numpy as np model.eval() all_preds, all_labels [], [] with torch.no_grad(): for imgs, labels in test_loader: preds model(imgs.to(device)).argmax(1).cpu().numpy() all_preds.extend(preds) all_labels.extend(labels.numpy()) print(classification_report(all_labels, all_preds, target_namestest_set.classes, digits3)) cm confusion_matrix(all_labels, all_preds) # 找出最容易混的两类 off_diag cm.copy() np.fill_diagonal(off_diag, 0) i, j np.unravel_index(off_diag.argmax(), off_diag.shape) print(f最易混淆: {test_set.classes[i]} - {test_set.classes[j]}, 次数{off_diag[i, j]})这段脚本输出逐类指标和混淆矩阵并自动定位混淆最严重的一对类别。参数上target_names直接取test_set.classes保证顺序和标签索引一致。拿到结果后如果某两类反复混先回去看可视化脚本抽出的样本确认是不是标注本身就有歧义——医学图像里湿疹和疱疹性湿疹本来就难分这种混淆未必是模型的问题。还有一个习惯每次换 backbone 或改超参固定随机种子torch.manual_seed(42)、np.random.seed(42)否则两次结果没法比。我早期图省事不设种子同一份配置跑出两个差 3 个点的准确率排查了半天才发现是随机性从那以后每次实验都强制走一遍种子固定和类别分布校验。这份数据集结构干净、开箱即用把上面这些验证做扎实它足够撑起一个可信的分类 baseline。希望帮到你。本文还有配套的精品资源点击获取