资讯动态

19种器官细胞图像识别数据集:从结构解析到迁移学习实战

发布时间:2026/10/26 16:58:00 来源:尧图企业网站定制
简介这份医学图像分类数据集面向从事医学影像识别、深度学习分类网络实践的学生与算法工程师覆盖肾上腺、子宫、甲状腺、食道等19类器官细胞图像可直接用于CNN分类网络或YOLOv5分类任务的训练与验证。资源包共约2000个文件以1998张png图像为主体另附1个json类别字典与1个Python可视化脚本压缩包整体约260MBdata目录下已按训练集与测试集分文件夹整理训练集2100张、测试集500张开箱即可投入实验。类别字典文件清晰给出19个类别的索引映射配合show脚本可快速预览样本分布与图像质量省去自行清洗与划分数据的时间成本。目前已有176人学习下载适合需要快速搭建医学图像分类基线、验证模型效果或开展课程实验的读者参考使用。1. 医学图像分类数据集19 种器官细胞图像识别数据集到底解决了什么问题做医学图像分类的团队十有八九卡在同一个地方模型结构抄得飞快数据却凑不齐。公开的病理、细胞、器官图像要么散落在各个论文附件里要么标注格式五花八门光是把「这是哪种细胞、属于哪个器官」对齐成一张能喂给网络的表就能耗掉一两周。这个 19 种器官细胞图像识别数据集的价值就在这——它把图像按类别分好文件夹配一份类别字典文件等于把「数据清洗 标签映射」这两步提前做完了。它适合三类人一是刚入门医学图像分类、想跑通第一个多分类 baseline 的学生和转行者二是要做器官/细胞识别原型、但没预算自己标注的小团队三是拿它当预训练或对比实验底座的算法工程师。核心要理解的是这类数据集本质是一个「文件夹即标签」的多分类结构类别字典负责把文件夹名翻译成可读的类别语义。下面从结构、加载、训练、避坑一路讲透让你拿到手当天就能跑起来。2. 数据集结构拆解文件夹划分与类别字典怎么对上号2.1 文件夹即标签的目录约定绝大多数「划分好的数据 类别字典」型数据集目录长这样一个训练集目录、一个验证集或测试集目录各自下面按类别建子文件夹子文件夹里放该类别的图像。这种结构的好处是标签信息直接编码在路径里不需要额外的标注文件torchvision.datasets.ImageFolder或tf.keras.utils.image_dataset_from_directory都能零配置读进来。先别急着写训练脚本第一步永远是「看清目录」。我一般会先跑一段统计脚本把每个类别的样本数、图像尺寸分布、通道数摸清楚。这一步能提前暴露类别不平衡、损坏图像、灰度图混彩色图这些后面会让你训练翻车的问题。import os from collections import defaultdict from PIL import Image DATA_ROOT dataset # 数据集根目录 SPLITS [train, val] # 划分好的子集目录名按实际改 def scan_split(root, split): split_dir os.path.join(root, split) stats defaultdict(int) sizes defaultdict(int) bad_files [] for cls in sorted(os.listdir(split_dir)): cls_dir os.path.join(split_dir, cls) if not os.path.isdir(cls_dir): continue for fn in os.listdir(cls_dir): fp os.path.join(cls_dir, fn) try: with Image.open(fp) as im: im.verify() # 校验文件是否损坏 with Image.open(fp) as im: sizes[im.size] 1 # 统计尺寸分布 stats[cls] 1 except Exception: bad_files.append(fp) # 记录打不开的文件 return stats, sizes, bad_files for s in SPLITS: stats, sizes, bad scan_split(DATA_ROOT, s) print(f[{s}] 类别数{len(stats)} 总样本{sum(stats.values())}) print(f[{s}] 尺寸分布 top5{sorted(sizes.items(), keylambda x:-x[1])[:5]}) print(f[{s}] 损坏文件{len(bad)})这段脚本做了三件事用Image.verify()逐个校验文件完整性避免训练到一半报UnidentifiedImageError统计尺寸分布决定后面 resize 的目标分辨率统计每类样本数判断要不要做重采样或加权。参数上DATA_ROOT和SPLITS按你解压后的实际目录名改有的数据集划分目录叫train/test有的叫training/validation别照抄。2.2 类别字典文件的三种常见格式与读取类别字典文件是这类数据集的灵魂它把文件夹名往往是class_0、001这种无语义编号映射成人类可读的类别名。常见格式有三种JSON{0: 肝细胞, ...}、CSV两列id,name、TXT每行id name。读取前先确认编码中文类别名用 UTF-8遇到乱码基本是 GBK 没指定。import json, csv def load_class_dict(path): if path.endswith(.json): with open(path, encodingutf-8) as f: raw json.load(f) # 统一成 {文件夹名: 可读名} return {str(k): str(v) for k, v in raw.items()} if path.endswith(.csv): with open(path, encodingutf-8) as f: return {row[0]: row[1] for row in csv.reader(f) if len(row) 2} # 默认按 txt每行 id name with open(path, encodingutf-8) as f: return {line.split()[0]: line.split()[1] for line in f if line.strip()} class_dict load_class_dict(class_dict.json) print(类别数:, len(class_dict)) print(示例:, list(class_dict.items())[:3])关键点在于「对齐」字典的 key 必须和文件夹名逐字符一致。我见过太多人栽在这里——文件夹叫0字典 key 是class_0结果标签全错位模型训练 loss 看着在降实际学的是错的映射。跑完上面这段务必打印几个 key 和os.listdir的结果肉眼比对一次。提示如果字典 key 和文件夹名对不上写一个映射修正函数统一成文件夹名别去改原始数据保留可追溯性。2.3 划分是否可信先验证 train/val 无重叠「划分好的数据」听起来省心但你要确认两件事训练集和验证集之间没有图像重复同一张图出现在两个集合里验证指标会虚高以及类别在两个集合里都有覆盖某类只在训练集出现验证时该类无法评估。用文件哈希做去重是最稳的办法。import hashlib def file_hash(fp, block65536): h hashlib.md5() with open(fp, rb) as f: while chunk : f.read(block): h.update(chunk) return h.hexdigest() def collect_hashes(root, split): hashes {} split_dir os.path.join(root, split) for cls in os.listdir(split_dir): cls_dir os.path.join(split_dir, cls) if not os.path.isdir(cls_dir): continue for fn in os.listdir(cls_dir): hashes[file_hash(os.path.join(cls_dir, fn))] f{cls}/{fn} return hashes h_train collect_hashes(DATA_ROOT, train) h_val collect_hashes(DATA_ROOT, val) overlap set(h_train) set(h_val) print(跨集合重复图像数:, len(overlap)) for h in list(overlap)[:5]: print( 重复:, h_train[h], -, h_val[h])如果overlap不为零说明划分有泄漏验证分数不可信。处理方式是从验证集里删掉重复项或者重新按哈希分组划分。这一步花五分钟能省掉后面「为什么线上效果和验证差这么多」的几天排查。3. 从零跑通一个 19 类分类 baseline3.1 用 ImageFolder 构建 DataLoader 与数据增强结构摸清后加载就顺了。ImageFolder会自动按子文件夹名生成类别索引但要注意它的索引顺序是文件夹名的字典序和你的类别字典顺序不一定一致。所以要么用class_to_idx反查要么自己写 Dataset 保证映射可控。下面用ImageFolder加标准增强适合快速验证。import torch from torch.utils.data import DataLoader from torchvision import datasets, transforms IMG_SIZE 224 mean, std [0.485, 0.456, 0.406], [0.229, 0.224, 0.225] train_tf transforms.Compose([ transforms.Resize((IMG_SIZE, IMG_SIZE)), transforms.RandomHorizontalFlip(), # 细胞/器官图像水平翻转通常安全 transforms.RandomRotation(15), # 小角度旋转模拟制片角度差异 transforms.ColorJitter(0.2, 0.2, 0.2), # 染色深浅差异大颜色抖动有用 transforms.ToTensor(), transforms.Normalize(mean, std), ]) val_tf transforms.Compose([ transforms.Resize((IMG_SIZE, IMG_SIZE)), transforms.ToTensor(), transforms.Normalize(mean, std), ]) train_ds datasets.ImageFolder(os.path.join(DATA_ROOT, train), train_tf) val_ds datasets.ImageFolder(os.path.join(DATA_ROOT, val), val_tf) train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers4, pin_memoryTrue) val_loader DataLoader(val_ds, batch_size32, shuffleFalse, num_workers4, pin_memoryTrue) print(类别索引映射:, train_ds.class_to_idx)参数说明IMG_SIZE224是 ImageNet 预训练模型的通用输入若你的细胞图像分辨率很高、细节是关键判别依据可以提到 384 甚至 448但要相应降 batch size。RandomRotation(15)别开太大病理切片有方向语义转 90 度可能把结构转没了。ColorJitter对染色差异大的数据集很有用但如果你做的是灰度细胞图这行删掉。num_workers按 CPU 核数设Windows 上如果报多进程错误就设成 0。3.2 迁移学习训练循环与关键超参19 类、样本量中等的情况下从零训练容易过拟合迁移学习是首选。用 ResNet 或 EfficientNet 的预训练权重换掉最后的全连接层先冻结主干训几轮分类头再解冻微调。下面是一个最小可用的训练循环。import torch.nn as nn from torchvision import models device cuda if torch.cuda.is_available() else cpu num_classes len(train_ds.classes) model models.resnet50(weightsmodels.ResNet50_Weights.IMAGENET1K_V2) for p in model.parameters(): p.requires_grad False # 先冻结主干 model.fc nn.Linear(model.fc.in_features, num_classes) model model.to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.AdamW(model.fc.parameters(), lr1e-3, weight_decay1e-4) def run_epoch(loader, trainTrue): model.train() if train else model.eval() total_loss, correct, n 0.0, 0, 0 torch.set_grad_enabled(train) for x, y in loader: x, y x.to(device), y.to(device) out model(x) loss criterion(out, y) if train: optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() * y.size(0) correct (out.argmax(1) y).sum().item() n y.size(0) return total_loss / n, correct / n for epoch in range(5): # 第一阶段只训分类头 tr_loss, tr_acc run_epoch(train_loader, True) va_loss, va_acc run_epoch(val_loader, False) print(fep{epoch} train {tr_loss:.3f}/{tr_acc:.3f} val {va_loss:.3f}/{va_acc:.3f})第一阶段冻结主干、只训fc学习率可以给到1e-3。跑几轮后如果验证准确率还在涨就解冻主干做微调此时学习率要降到1e-4甚至1e-5否则预训练权重会被大梯度冲垮。weight_decay1e-4是抗过拟合的常规手段。判断该不该解冻看验证 loss 是否已经平台化——平台了就解冻还在降就继续冻着训。3.3 类别不平衡时的加权与采样19 类里往往有几类样本特别少。直接训练会让模型偏向多数类少数类召回率惨不忍睹。两种处理给CrossEntropyLoss传类别权重或用WeightedRandomSampler过采样少数类。前者改动小后者效果更直接。import numpy as np from torch.utils.data import WeightedRandomSampler targets [y for _, y in train_ds.samples] counts np.bincount(targets, minlengthnum_classes) class_weights 1.0 / np.maximum(counts, 1) class_weights class_weights / class_weights.sum() * num_classes criterion nn.CrossEntropyLoss( weighttorch.tensor(class_weights, dtypetorch.float32).to(device)) # 或者用过采样每个样本被抽到的概率与其类别频次成反比 sample_weights [1.0 / counts[t] for t in targets] sampler WeightedRandomSampler(sample_weights, len(sample_weights), replacementTrue) train_loader DataLoader(train_ds, batch_size32, samplersampler, num_workers4)两种方式别叠加用否则少数类被过度补偿反而过拟合。我一般先试类别权重如果少数类召回还是上不去再换采样器。评估时别只看总体准确率一定要打印每类的 precision/recallsklearn.metrics.classification_report一行搞定。4. 避坑与排查19 类细胞识别最容易翻车的 5 个地方4.1 现象验证准确率 95%换一批图就崩原因通常是数据泄漏或过拟合到背景。细胞图像里不同类别的制片背景、染色批次可能不同模型学的其实是「背景颜色」而不是「细胞形态」。解决先跑 2.3 的哈希去重排除泄漏再做一次「背景扰动」测试把验证图做轻微颜色抖动看准确率掉多少掉得厉害说明模型依赖颜色。缓解手段是加强颜色增强、加 Cutout/RandomErasing或者把图像转灰度再训练对比。4.2 现象训练 loss 正常下降验证 loss 一路飙升典型的过拟合尤其在样本量小、类别多的时候。原因可能是解冻主干太早、学习率太大、增强太弱。解决先冻结主干多训几轮确认分类头收敛解冻后学习率降到1e-4以下加weight_decay、Dropout增强里补上RandomResizedCrop和RandomErasing。如果还压不住考虑减小模型ResNet18 换 ResNet50或做类别合并。4.3 现象报UnidentifiedImageError或OSError: image file is truncated原因是数据集里混了损坏或截断的图像文件ImageFolder遍历到就崩。解决用 2.1 的校验脚本先扫一遍把坏文件列出来。临时方案是在 Dataset 里包一层 try/except 跳过坏图但更干净的做法是直接删掉或修复。截断文件可以用ImageFile.LOAD_TRUNCATED_IMAGES True强行加载但可能得到半张图不推荐用于训练。4.4 现象类别字典和文件夹对不上标签全错前面提过但值得单独列。表现是模型训练正常、指标也不低但推理时预测的类别名驴唇不对马嘴。原因是ImageFolder的class_to_idx按文件夹名字典序生成而你的类别字典是另一套顺序。解决永远用train_ds.classes或class_to_idx反查真实类别名别用字典文件的行号去索引。写一个idx_to_name映射训练完存下来推理时加载同一份。4.5 现象多进程 DataLoader 卡死或显存爆num_workers设太大每个 worker 都复制一份数据预处理内存吃满或者pin_memoryTrue在 CPU 环境下反而拖慢。解决num_workers从 2 或 4 起步观察 GPU 利用率利用率低且 CPU 没满再加。Windows 上多进程容易出问题设num_workers0或把训练代码放进if __name__ __main__:。显存爆则降 batch size 或图像分辨率别硬扛。5. 让 19 类模型真正可用的两个进阶技巧第一个技巧是分层评估加混淆矩阵定位「难分类对」。19 类里总有几对长得像的细胞或器官总体准确率会掩盖它们。训练完别只看一个数跑一遍混淆矩阵找出互相误判最多的类别对再针对性补数据或加细粒度增强。from sklearn.metrics import confusion_matrix, classification_report import numpy as np model.eval() preds, gts [], [] with torch.no_grad(): for x, y in val_loader: out model(x.to(device)) preds.extend(out.argmax(1).cpu().numpy()) gts.extend(y.numpy()) print(classification_report(gts, preds, target_namesval_ds.classes, digits3)) cm confusion_matrix(gts, preds) # 找出误判最多的类别对 pairs [(cm[i, j], val_ds.classes[i], val_ds.classes[j]) for i in range(len(cm)) for j in range(len(cm)) if i ! j] for cnt, a, b in sorted(pairs, reverseTrue)[:5]: print(f{a} - {b}: {cnt})classification_report给出每类 precision/recall/f1target_names用val_ds.classes保证名字对得上。混淆矩阵里非对角线的最大值就是最该关注的误判方向。我一般会把 top5 误判对单独截图看往往能发现是标注歧义还是图像质量差异。第二个技巧是导出可复现的推理接口把类别字典和模型权重绑在一起存。很多人训完模型权重文件一丢过两周自己都忘了哪个索引对应哪个类别。正确做法是保存一个label_map.json内容就是idx - 类别名和权重同目录。import json, torch torch.save(model.state_dict(), best_model.pth) label_map {i: name for i, name in enumerate(train_ds.classes)} with open(label_map.json, w, encodingutf-8) as f: json.dump(label_map, f, ensure_asciiFalse, indent2) # 推理时 def predict(img_tensor, model, label_map): model.eval() with torch.no_grad(): idx model(img_tensor.unsqueeze(0).to(device)).argmax(1).item() return label_map[str(idx)]ensure_asciiFalse保证中文类别名不被转义成\uXXXX方便人工核对。推理时用str(idx)取 key因为 JSON 的 key 存下来是字符串。这套「权重 label_map」的组合是我踩过「忘了索引对应关系、重新训一遍」的坑之后养成的习惯多花十秒省下的是半天。数据集本身不会让模型变强但把结构摸清、把标签对齐、把评估做细能让你少走很多弯路。19 类不算多真正难的是让每一类都站得住。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑