
简介一套面向医学图像分类的19种器官细胞识别数据集适合算法学习者、科研人员及医学影像项目开发者用于分类任务验证与实验。资源已将数据划分为训练集2100张和测试集500张按类别文件夹存放涵盖肾上腺、子宫、甲状腺、食道等多种器官细胞同时提供class_indices.json类别字典文件与show.py可视化脚本便于查看类别映射并快速抽样预览。压缩包共2000个文件以1998张png图片为主搭配1个Python脚本和1个JSON字典整体大小约260.22MB目录结构清晰开箱即用。目前已有176人学习/下载。该数据集可直接用于yolov5分类训练或常见CNN分类网络既省去自行整理和划分数据的时间也可作为课程设计、算法复现或论文实验的可靠数据来源。1. 19种器官细胞图像数据集为什么说它是医学图像分类的及格线做医学图像分类的同行都懂模型选型再花心思数据不干净照样翻车。这个19种器官细胞图像识别数据集把最容易被忽略的两件事直接帮你做完了按类别划分好的文件夹和类别字典文件。拿到手不用再花一周做数据清洗和标签整理直接从train/val/test三个目录开始跑训练。它适合刚入门图像识别的同学快速跑通完整流程也适合有经验的工程师用它验证新模型在细粒度医学图像上的表现。下面我按实际动手的顺序把这个数据集怎么用、怎么训练、坑在哪讲清楚。2. 拆开数据包19类文件夹结构与类别字典文件的实际读法2.1 划分好的train/val/test目录树怎么查看拿到压缩包解压之后第一件事不是急着看图而是先把目录结构彻底摸清楚。这个数据集的目录组织方式很标准常见做法是train / val / test三个一级目录每个目录下面按19个类别分别建子文件夹子文件夹名是类别编号018或器官细胞缩写。用一条命令就能看到全貌# 解压后查看目录结构实际路径以你解压的位置为准 unzip medical_cell_dataset.zip -d ./dataset find ./dataset -maxdepth 2 -type d | sort输出的结构大致是这样dataset/ ├── train/ │ ├── 0/ │ ├── 1/ │ ├── ... │ └── 18/ ├── val/ │ ├── 0/ │ ├── 1/ │ └── ... └── test/ ├── 0/ ├── 1/ └── ...这里的find -maxdepth 2只列到二级目录目的是快速确认每个split下有哪几个类别文件夹、有没有缺类或多类的情况。如果某个split下少了几个文件夹说明这个划分不完整不能直接信任得自己把缺失部分补划分。-type d只显示目录不显示文件避免满屏图片名刷掉有效信息。提示如果你解压后发现目录层级不止两层比如train/class_0/images/xxx.png先不要急着改路径用find ./dataset -maxdepth 3 -type d | head -50往下多看一层确认从哪一级目录开始放图片。2.2 类别字典文件数字ID到细胞名的映射与反向映射类别字典文件是这个数据集里最容易被忽视、但最不能丢的文件。它通常是JSON或TXT格式内容就是把数字标签和器官细胞名称一一对应。读取方式如下import json # 以JSON格式的类别字典为例 with open(class_dict.json, r, encodingutf-8) as f: class_dict json.load(f) print(class_dict) # {0: neutrophil, 1: eosinophil, 2: lymphocyte, ...}打印出来之后需要顺手做一个反向映射因为训练时模型输出的是索引而我们最终要看的是细胞名称idx_to_name {int(k): v for k, v in class_dict.items()} name_to_idx {v: int(k) for k, v in class_dict.items()} # 验证一下假设模型预测索引为3打印对应名称 print(idx_to_name[3])这段代码的逻辑很直接idx_to_name让预测索引可解释name_to_idx在做标签替换或数据筛选时有用。比如你想只抽某个类别的图片出来看用name_to_idx[lymphocyte]就能拿到它的数字标签再去文件夹里过滤。这里有一个容易被忽略的细节字典文件里键的顺序不等于文件夹的排序。即使Python 3.7以后dict保持了插入顺序也不能假设class_dict的键顺序和os.listdir返回的文件夹顺序一致。正确做法是始终以类别字典文件为准构建标签映射而不是按文件夹在磁盘上的排序猜。2.3 类别分布统计哪些类少到需要特殊处理在写训练脚本之前我一般会先统计每个类别的样本数。医学图像数据集普遍存在类别不平衡比如中性粒细胞可能几千张某些罕见的器官细胞只有两三百张。统计脚本# 统计训练集每个类别的图片数量 for d in dataset/train/*/; do echo $(basename $d): $(find $d -type f | wc -l) done输出的数字会直接告诉你后续策略。如果所有类别样本数都在500张以上常规的CrossEntropyLoss加随机采样就能跑如果少数类不足300张而多数类超过2000张第四节的WeightedRandomSampler就要提前加进训练流程。这一步不是走过场它直接决定后面的训练策略怎么定也决定你对最终准确率的预期——19类细粒度分类任务多数类接近95%但少数类只有60%左右这在医学图像分类数据集上是常态不是模型出问题了。3. 用PyTorch加载19类医学图像数据集预处理与增强的参数细节3.1 ImageFolder直接加载划分好的目录一行代码对接由于这个数据集已经按train/0, train/1, ...的组织方式放置PyTorch内置的torchvision.datasets.ImageFolder可以无缝对接不需要手写Dataset类。深度学习图像识别的通用流程里这一步是最省心的from torchvision import datasets, transforms train_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_dataset datasets.ImageFolder( rootdataset/train, transformtrain_transform, ) print(类别数:, len(train_dataset.classes)) print(样本数:, len(train_dataset)) print(类别到索引映射:, train_dataset.class_to_idx)这段代码里ImageFolder会自动扫描root下的每个子文件夹把文件夹名当作类别标签。class_to_idx是它内部生成的类别映射——这里要重点核对一下它跟你读到的类别字典文件是否一致。我见过有人在跑完整个训练流程之后才发现标签错位白跑了几十个小时。需要注意ImageFolder的排序规则是字典序而非文件系统顺序。比如文件夹名是 0、1、10、2字典序会排成 0、1、10、2。如果你的类别字典文件把 10 定义为某个特定细胞而ImageFolder认为 10 排在第3位那么标签就全部错位了。这种错位在训练时不会报任何错误只会在验证时表现为准确率低得离谱。注意训练前打印class_to_idx和类别字典文件逐项对比是医学图像分类流程里成本最低的止损操作务必养成习惯。3.2 灰度图与RGB的通道统一Resize、Grayscale与Normalize的参数医学细胞图像和自然图像不一样常见原图是256×256、512×512甚至更大的灰度图或特殊染色图。直接把原图喂进去显存扛不住普遍做法是先Resize再CenterCroptrain_transform transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ])Resize(256)先把短边缩放到256CenterCrop(224)再从中切出224×224的区域这样能保留更多图像中心的有效信息。细胞图像的有效区域一般居中这种组合优于直接Resize((224, 224))的拉伸变形效果。如果图像长宽比差异大直接Resize会拉变形细胞形态特征会失真。如果数据是单通道灰度图但你想用ImageNet预训练的ResNet需要把灰度图复制成3通道再输入否则预训练权重的第一层卷积通道数对不上transform transforms.Compose([ transforms.Resize((224, 224)), transforms.Grayscale(num_output_channels3), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ])Grayscale(num_output_channels3)会把灰度图复制为3个相同的通道既保留灰度信息又兼容ImageNet预训练的输入要求。但要注意如果数据集中既有真彩色染色图又有灰度图统一用这个方法会把彩色图的颜色信息丢掉。这种情况建议先人工确认数据源的染色方式是否一致再做通道处理。3.3 数据增强的边界细胞图像允许动哪些变换医学图像的数据增强要做但不能照搬ImageNet竞赛那套。水平翻转、小角度旋转、轻微的亮度/对比度抖动是相对安全的但90°/180°旋转、随机裁剪太多、强颜色抖动可能会破坏细胞形态的具象特征反而让模型学到错误的模式。train_transform transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(degrees15), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ])RandomHorizontalFlip(p0.5)一半概率水平翻转对细胞图像来说基本不改变类别语义。RandomRotation(degrees15)限制在正负15度内避免大幅旋转改变细胞的朝向特征。ColorJitter只调亮度和对比度不调色相饱和度因为医学染色图像的色彩是有诊断语义的乱调颜色会让模型把染色噪声当作类别特征。这是我在跑类似的医学图像分类数据集时踩过的一个坑——用了完整版的ColorJitter(brightness, contrast, hue, saturation)结果验证集准确率比训练集低了快20个点原因就是模型把颜色偏移当成了特征。4. 训练自己的19类分类模型迁移学习、采样器与超参数配置4.1 ResNet18迁移学习起步替换最后一层与冻结backbone19类细粒度分类任务用ResNet18做backbone是稳妥的起步选择。参数量适中在医学图像这种中小规模数据集上不容易过拟合训练速度快调试成本低import torch import torch.nn as nn from torchvision import models device torch.device(cuda if torch.cuda.is_available() else cpu) model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) model.fc nn.Linear(512, 19) # 最后一层全连接换成19类输出 model model.to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.SGD(model.parameters(), lr0.001, momentum0.9, weight_decay1e-4)迁移学习的逻辑weightsIMAGENET1K_V1加载在ImageNet上预训练好的权重然后替换最后一层fc的输出维度为19。因为ImageNet预训练模型提取的是通用视觉特征细胞图像虽然和自然图像差异很大但底层的边缘、纹理特征还是相通的用预训练权重比从头训练收敛快得多尤其医学图像数据量不够时优势更明显。如果发现过拟合比如训练准确率接近100%但验证集停滞在80%出头可以冻结backbone只微调最后的全连接层for param in model.parameters(): param.requires_grad False for param in model.fc.parameters(): param.requires_grad True冻结backbone后训练参数量从1100多万骤降到2万左右基本不可能过拟合代价是模型上限会低一些。常见做法是先冻结跑10个epoch让fc层收敛再解冻全部参数用更小的学习率微调510轮效果比直接全量微调更稳。4.2 类别不平衡的解法WeightedRandomSampler与Loss加权前面统计类别分布的时候如果发现样本数差异超过3倍数据层面的采样调整比直接换Loss更直观有效from torch.utils.data.sampler import WeightedRandomSampler import numpy as np targets [train_dataset.targets[i] for i in range(len(train_dataset))] class_counts np.bincount(targets) # 统计每个类别的样本数 weights 1.0 / class_counts sample_weights np.array([weights[t] for t in targets]) sampler WeightedRandomSampler( weightssample_weights, num_sampleslen(sample_weights), replacementTrue, ) train_loader torch.utils.data.DataLoader( train_dataset, batch_size32, samplersampler, # sampler 和 shuffle 不能同时用 num_workers4, )逻辑说明WeightedRandomSampler让样本被抽中的概率与其类别样本数成反比少数类的样本在同一个epoch里被重复抽到等于从数据层面把类别不平衡拉平。replacementTrue表示允许重复采样这是必须的因为少数类样本数量太少不允许重复的话一个epoch根本覆盖不了模型对少数类学习的需求。Loss加权是采样之外的第二种手段适合不想改变数据分布的情况class_weights torch.tensor([1.0 / class_counts[i] for i in range(19)], devicedevice) criterion nn.CrossEntropyLoss(weightclass_weights)如果加了权重后少数类上来了但多数类掉得厉害说明权重给得太猛对class_weights做一次平滑比如0.5 0.5 * (weights / weights.max())让多数类不至于被完全忽略。4.3 batch size、学习率与epoch的起步参数表医学图像分类常用以下一组起步参数是我在类似规模的图像识别数据集上的经验值不是唯一答案但能帮你快速建立基线参数建议值说明batch size1632细胞图像原图较大显存不足就减半初始学习率0.001SGD/ 0.0001Adam迁移学习微调不宜太大容易冲乱预训练特征epoch3050医学小数据集50轮足够收敛多跑浪费算力weight decay1e-4正则化防过拟合学习率衰减每10轮×0.1或直接用CosineAnnealingLR学习率是这里最玄学的参数。用Adam我习惯直接lr1e-4用SGD需要配动量0.9并加weight_decay1e-4。如果loss在训练初期不降反升大概率是学习率设大了先降10倍再试。另外一个经验是batch size增加时同步增大学习率比如batch size从16翻到32学习率从0.001提到0.0015左右收敛会快一些但幅度控制不好容易震荡。5. 医学图像分类避坑指南5个高频问题与排查5.1 类别字典顺序和文件夹排序错位现象训练loss正常下降但验证准确率始终在50%以下而且各类别准确率都接近随机猜测水平。原因ImageFolder按字典序给文件夹编号而类别字典文件里数字ID的排列顺序和文件夹字典序不一致导致标签整体错位。解决训练前打印train_dataset.class_to_idx和类别字典文件逐项比对。如果错位用前面建的name_to_idx重新映射或者按类别字典文件的键顺序把文件夹重命名。千万别直接开训这种错位不会报错只会让时间白费。5.2 数据泄露验证集准确率高但实际推理拉胯现象验证集准确率达到98%但把模型用到新数据上效果很差掉到70%甚至更低。原因同一个来源的细胞图像被随机分到了train和val比如同一张病理切片截出来的多张图train和val里各自有模型等于见过答案做测试。这个数据集虽然标好了划分但如果你自己补充数据或重新划分很容易踩这个坑。解决重新划分时按切片编号或病人编号分组保证同一个来源的图像只在train或只在val不要同时出现在两边。对已经提供的划分直接信任默认的train/val/test目录即可不要因为某个类在val里表现差就手动从train搬图过去补——这会让验证集失去参考意义。5.3 灰度图被当RGB读导致通道语义混乱现象模型能训练但第一层卷积提取的特征全都乱套loss收敛极慢验证准确率卡在50%上下。原因数据集里部分是灰度图部分是彩色染色图ImageFolder统一按3通道读。灰度图被复制成三个相同通道但另一个子集的图片是真正的RGB染色图通道分布不一致模型输入的特征空间在样本间漂移。解决统一先做Grayscale(num_output_channels3)或者全部转成RGB保存保证所有输入图像的通道语义一致。处理完以后随机抽20张图检查通道数值分布再进训练循环。这个检查用不了两分钟但能省掉后面几十个小时的排查时间。5.4 batch size设大了直接显存溢出现象训练刚开始就报CUDA out of memory完全跑不起来。原因细胞图像原图可能是512×512甚至1024×1024预处理没做Resize就进模型显存直接被中间特征图打爆。解决先确认预处理里有Resize再调batch size从16开始往下减。如果batch size已经降到8还爆显存用AMP混合精度scaler torch.cuda.amp.GradScaler() with torch.cuda.amp.autocast(): outputs model(images) loss criterion(outputs, labels) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()混合精度能省约一半显存代价只是训练速度略有波动。如果显存只有8G以下还可以把输入尺寸从224降到160模型输出分辨率降了以后信息量损失在可接受范围内尤其细胞图像本身纹理不太依赖极细节。5.5 类别不平衡让少数类永远学不好现象多数类准确率95%以上少数类只有30%左右而且训练轮数增加后少数类准确率不升反降。原因CrossEntropyLoss默认对每个类一视同仁模型把有限的学习能力全部用在优化多数类上少数类的梯度被多数类淹没。解决先用4.2节的WeightedRandomSampler从数据层面拉平分布。如果效果还不够再叠加Loss权重。注意观察加权重后多数类是否掉点严重掉太多就做权重平滑。少数类如果在加了采样器和权重之后还是起不来可能是这个类本身的图像质量参差先检查少数类的图片是不是有大量标注噪声这个案例在医学图像分类数据集里很常见。6. 把19类分类精度再推一截TTA与模型集成的落地姿势模型在测试集上跑出90%的准确率之后再多做两步还能往上走。第一步是TTATest Time Augmentation。把测试时单次推理换成多次增强后取平均预测实现很便宜def predict_with_tta(model, image, transforms_list, device): model.eval() preds [] with torch.no_grad(): for t in transforms_list: x t(image).unsqueeze(0).to(device) preds.append(torch.softmax(model(x), dim1)) return torch.mean(torch.stack(preds), dim0)transforms_list里放三到五种不包含纯随机破坏的变换比如原图、水平翻转、垂直翻转、±10°旋转。TTA对细粒度分类通常能提升1到2个百分点因为不同增强下模型犯的错不完全重合取平均能抵消部分误判。提示TTA的变换列表里不要放RandomCrop这类带随机性的操作可以用五个固定位置裁剪四角加中心再水平翻转组合效果更稳定。第二步是模型集成。不要集成一大堆同结构的ResNet18参数都差不多集成的收益很小。用ResNet18和EfficientNet-B0各训一个或者ResNet18加ViT-small特征提取方式差异大集成的效果才明显final_pred 0.6 * pred_resnet 0.4 * pred_efficientnet权重分配可以根据两个模型在验证集上的表现来调也可以直接对多个模型的logits取平均再softmax。我一般先用验证集跑一遍两个模型的混淆矩阵看它们各自在哪类上犯错再决定权重——如果ResNet在淋巴细胞上明显更好而EfficientNet在上皮细胞上更准那权重分配就按类别分而治之而不是用一个全局权重。这是我做这一类医学图像分类数据集的习惯新数据集到手只用ResNet18跑通全流程准确率基准定了以后再上TTA和异构模型集成去抠那几个点的提升。医学图像分类数据集的资源和时间都有限先把底坐稳再用技巧做增量。这个19类数据集的划分和类别字典文件都已经备好省下来的时间值得花在这些能确实提升测试集结果的环节上。希望这些经验对你跑这个数据集有帮助。本文还有配套的精品资源点击获取