
简介这是面向深度学习的乳腺癌症图像分类数据集属于典型的医学图像二分类任务共包含两个类别具体标签可参考配套JSON类别文件适合医学影像分类入门、算法效果对比以及相关课程设计。数据已按照目录清晰整理训练集约480张、验证集约140张、测试集约70张可直接输入分类模型完成训练、验证与测试全流程。资源包内共692个文件主体为689张JPG分类图像另附1个JSON类别文件、1个Python脚本和1张PNG示意图Python脚本便于快速读取样本与划分数据整体大小仅17.85MB下载和解压都非常轻量。目前已有284人学习使用对于需要快速开展二分类实验的开发者而言能够省去数据收集、清洗和标注环节拿到即可复现训练流程。同时目录采用分文件夹方式保存不同类别样本配合JSON标签文件可显著降低前期处理成本也适合作为迁移学习或数据增强方法的验证基准。1. 所有的乳腺癌分类模型都是先输在数据集上乳腺癌症图像分类这两年看起来谁都能跑U-Net、EfficientNet甚至Transformer都能上但真正让模型拉开差距的往往是数据集本身。同样一个二分类问题用CBIS-DDSM切出来的patch和用整张钼靶图分类模型结构完全不是一回事同一份病理图像按患者划分和按图片划分验证指标能差出十几个百分点。下面会从公开数据集选型、标签组织、基线CNN训练、类别不均衡处理到迁移学习微调走一遍深度学习数据集与图像分类的完整操作链路最后给出验证和调试技巧。这套流程适合正在准备医学图像分类实验的工程师和研究生也适合被准确率虚高问题困扰的从业者。2. 怎么选乳腺癌图像数据集主数据集与结构2.1 公开数据集怎么选没有万能数据只有适配任务的数据乳腺图像分类公开数据集分为两大类X线影像和病理图像。CBIS-DDSM是乳腺钼靶影像提供良恶性标注和病变区域ROI掩膜适合做二分类BreakHis是乳腺组织病理图像包含良性和恶性两大类每个类别又细分为多种亚型放大倍率有40x、100x、200x、400x四档适合做精细分类。此外INBreast包含全视野乳腺X线影像样本数量少但标注质量高常用于测试而不是训练。选数据集时先确认问题粒度。如果只是想跑通一个深度学习CNN流程BreakHis的patch大小固定处理成本低如果要模拟真实临床筛查场景CBIS-DDSM的病灶通常小、背景占比高分类难度更大也更容易暴露模型泛化能力弱的毛病。我一般会把原始数据描述和标注文件先解压到本地统计文件大小、格式和类别数量再决定要不要做重采样。2.2 目录组织按患者维度分割避免标签泄漏乳腺癌数据有一个天然陷阱同一个病人的多张图像或者同一张切片的不同patch在内容上高度相似。如果按图片随机划分模型会在训练时见过同一个病人的类似图像验证时相当于开卷考试得到的高分没有意义。正确做法是把patient_id作为分组键按患者划分数据集。比如BreakHis从病人组织样本中生成多个patch目录通常按patient编号组织。下面用GroupShuffleSplit按患者完成分割from sklearn.model_selection import GroupShuffleSplit import pandas as pd df pd.read_csv(metadata.csv) # 包含 image_path、label、patient_id split GroupShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, val_idx next(split.split(df, groupsdf[patient_id])) train_df, val_df df.iloc[train_idx], df.iloc[val_idx]groups参数传入patient_id列表使同一患者的所有图像被放到同一个集合中。如果没有groupssklearn会退化成随机划分这是很多复现问题出现时查不出来的原因。测试集建议也按患者单独隔离开不要和验证集混在一起调参。注意按患者分割是乳腺图像分类的红线宁可少用训练样本也不要让同一患者同时出现在多个集合中。2.3 标签一致性检查先看数据再写网络很多人拿到数据后直接训练结果训练到一半发现类别标签反了白跑几个小时。这里建议在训练之前做一个快速一致性检查确认每个患者的标签是否唯一。grouped df.groupby(patient_id)[label].nunique() print(grouped[grouped 1])如果发现同一个patient_id下既有良性又有恶性不能简单用patient_id进行整体分割需要确认是否属于同一病灶。这种情况下可以选择按更细的“病灶块”划分或者去读数据说明确认各病灶之间是否独立。另一个容易忽略的问题是图像格式。病理图像有时是PNG但实际是RGB有时是灰度图单通道。用PIL打开后检查mode统一转换成目标格式比在模型里做分支判断更省心。3. 从零搭一个乳腺病理图像分类CNN3.1 最小PyTorch数据管道先跑通再调参配置好深度学习环境后建议先用PyTorch写一个极简的数据管道跑通一个batch再考虑换网络。乳腺图像分类的错误很多出在数据读取和尺寸不一致上。下面是一个自定义Dataset的例子import pandas as pd from PIL import Image from torch.utils.data import Dataset class BreastDataset(Dataset): def __init__(self, df, img_dir, transformNone): self.df df self.img_dir img_dir self.transform transform def __len__(self): return len(self.df) def __getitem__(self, idx): row self.df.iloc[idx] img Image.open(f{self.img_dir}/{row[image_path]}) img img.convert(RGB) label int(row[label]) if self.transform: img self.transform(img) return img, label这里convert(RGB)会把灰度病理图复制成三通道这样可以直接用ImageNet预训练模型。如果数据集本身是RGB也不会失真。__getitem__里不需要自己转Tensor交给transform处理更高效。构造训练集和验证集的不同transformfrom torchvision import transforms transform_train transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) transform_val transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ])验证集不要加随机翻转否则每个epoch的预测结果会抖动影响early stopping判断。Normalize的均值方差用的是ImageNet统计量如果后续要加载预训练权重这个数值不要改。3.2 CNN结构与参数先别上大模型用3层卷积看数据量在迁移学习之前先用一个三层卷积的小CNN做基线。它的参数量很小CPU也能几分钟跑完一个epoch可以快速判断数据本身有没有问题。import torch.nn as nn import torch.nn.functional as F class SimpleCNN(nn.Module): def __init__(self, num_classes2): super().__init__() self.conv1 nn.Conv2d(3, 32, 3, padding1) self.conv2 nn.Conv2d(32, 64, 3, padding1) self.conv3 nn.Conv2d(64, 128, 3, padding1) self.pool nn.MaxPool2d(2, 2) self.fc nn.Linear(128 * 28 * 28, num_classes) def forward(self, x): x self.pool(F.relu(self.conv1(x))) x self.pool(F.relu(self.conv2(x))) x self.pool(F.relu(self.conv3(x))) x torch.flatten(x, 1) return self.fc(x)输入224x224经过三次池化后特征图边长变成28全连接层输入维度是128x28x28。如果改了输入分辨率这里必须同步改否则会报维度错误。先跑这个模型如果训练loss降低但验证loss不降说明模型容量或数据增强不够如果训练loss也不降应该检查标签是否反了、输入数据有没有被正确归一化。3.3 训练循环与epoch设置loss不再下降就停乳腺图像分类的深度学习epoch设置没有固定值公开数据集一般50到100个epoch就足够。不要盲目增加epoch重点看验证集loss。import torch optimizer torch.optim.Adam(model.parameters(), lr1e-4) criterion nn.CrossEntropyLoss() best_val_loss float(inf) for epoch in range(100): model.train() train_loss 0 for images, labels in train_loader: optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() train_loss loss.item() train_loss / len(train_loader) model.eval() val_loss 0 with torch.no_grad(): for images, labels in val_loader: outputs model(images) loss criterion(outputs, labels) val_loss loss.item() val_loss / len(val_loader) print(fepoch {epoch}, train_loss {train_loss:.4f}, val_loss {val_loss:.4f}) if val_loss best_val_loss: best_val_loss val_loss torch.save(model.state_dict(), best_model.pth)Adam在医学小数据集上通常比SGD好调初始学习率1e-4是相对安全的起点。best_model.pth保存的是验证loss最低的模型而不是最后一个epoch因为最后一轮可能已经过拟合。注意model.eval()必须放在验证循环前否则BatchNorm和Dropout行为不一致验证loss会出现周期性抖动。4. 乳腺癌类别不均衡三种可落地的处理方法4.1 为什么乳腺数据容易出现类别不均衡乳腺图像集中恶性样本通常比良性少。CBIS-DDSM中恶性病变的比例并不高真实临床数据更是阴性样本占大多数。如果直接用准确率评价模型把所有样本都判为良性也能拿到很高的准确率但这个模型在筛查场景中毫无价值。对于乳腺癌症图像分类必须优先关注恶性类别的召回率也就是多少个真正的癌症样本被找出来了。另一个被忽视的不均衡是“难易不均衡”。恶性病变在不同染色条件下外观差异很大少量难样本主导了loss清晰简单样本占了大多数。单纯过采样不一定能解决难样本问题还需要配合有效的增强或损失函数。4.2 重采样用WeightedRandomSampler调整训练分布采样层面的处理就是让恶性样本在训练时更常被看到。PyTorch内置WeightedRandomSampler只需要传入每个样本的权重。from torch.utils.data import WeightedRandomSampler labels train_df[label].values labels_tensor torch.tensor(labels) class_counts torch.bincount(labels_tensor).float() weights 1.0 / class_counts[labels_tensor] sampler WeightedRandomSampler(weights, num_sampleslen(weights), replacementTrue) train_loader DataLoader(train_dataset, batch_size32, samplersampler)class_counts计算每个类别的样本数weights是每个样本所在类别的样本数的倒数。良性样本如果特别多它的权重就小恶性样本权重就大。replacementTrue表示有放回采样能保证恶性样本以较高概率出现在每个batch里。需要提醒的是过采样容易让模型对少数类过拟合。使用加权采样时验证集必须保持原始分布否则评估结果无法代表真实场景。4.3 数据增强和加权损失怎么选数据增强的主要目的是缓解染色差异和位置差异。乳腺病理图像中同一组织在不同扫描仪下颜色差异明显所以我建议一定要加ColorJitter。transform_train transforms.Compose([ transforms.RandomResizedCrop((224, 224), scale(0.8, 1.0)), transforms.RandomRotation(20), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2, hue0.05), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ])RandomResizedCrop模拟不同放大倍率下的局部视野ColorJitter的取值不要太大hue0.05已经是极限否则组织颜色会失真到影响病理判断。除了增强还可以用加权CE Lossclass_weights torch.tensor([1.0, 3.0]) criterion nn.CrossEntropyLoss(weightclass_weights)1.0对应良性3.0对应恶性。权重的具体数值可以按样本比例反比设置但不必完全精确。Focal Loss在难样本多的数据集上更有效但多了一个gamma超参需要额外调参。下面是一个快速选型对照表方便你决定先试哪个。方法实现成本主要风险适用场景WeightedRandomSampler低少数类过拟合恶性样本占比低于20%数据增强中破坏病理结构染色、倍率差异明显加权CE Loss低权重不好定需要快速迭代Focal Loss中gamma难调难样本多、类别重叠大实际项目中我一般先做数据增强再用加权采样最后才考虑Focal Loss因为每多一个超参就多一分调试成本。5. 用迁移学习微调EfficientNet来提升乳腺分类5.1 为什么医学小数据集适合迁移学习前面用SimpleCNN是拿来看数据状态的真要获得能用的乳腺图像分类模型迁移学习是性价比最高的路线。乳腺癌公开数据集通常只有数千到数万张patch从头训练CNN很难学到鲁棒的高层语义特征。ImageNet预训练模型已经具备颜色、纹理、边缘等低层能力这些能力对病理图像同样有效。微调只需要让高层特征适配乳腺组织的形态差异能显著降低对数据量的需求。5.2 微调的冻结与解冻策略常见做法分两步。第一步冻结所有骨干参数只训练新分类头让模型先适应目标类别第二步解冻所有参数用很小的学习率整体微调。用timm加载EfficientNet-B0import timm import torch.nn as nn model timm.create_model(efficientnet_b0, pretrainedTrue, num_classes2) # 记住原始分类头输入维度 in_features model.classifier.in_features # 冻结全部骨干参数 for param in model.parameters(): param.requires_grad False # 替换分类头 model.classifier nn.Sequential( nn.Dropout(0.3), nn.Linear(in_features, 2) ) optimizer torch.optim.Adam( [p for p in model.parameters() if p.requires_grad], lr1e-3 )timm.create_model会自动下载预训练权重num_classes2会替换默认的1000类输出。in_features从原分类头读取不硬编码方便切换EfficientNet-B1/B2。第一阶段只训练分类头学习率可以用1e-3。第一阶段训练10个epoch左右等分类头的loss下降后再解冻全部参数for param in model.parameters(): param.requires_grad True optimizer torch.optim.Adam(model.parameters(), lr1e-5)第二阶段学习率必须降到1e-5这个量级。如果学习率太大预训练权重会被迅速破坏验证loss反而升高。5.3 调参学习率、dropout、early stopping微调阶段最容易被忽略的是BatchNorm层。如果之前冻结了所有参数解冻后BatchNorm的running_mean和running_var会开始更新但这需要合适的batch size否则统计量抖动。我通常用batch size 16或32如果显存不够就降分辨率而不是降batch size。dropout也可以加大一点。EfficientNet的默认drop_rate是0.2对于几千张的小数据集我会在分类头里额外加一层Dropout(0.3)也就是上面代码里nn.Sequential中间那层。如果训练集很小还可以把分类头的dropout加到0.5代价是需要更多epoch才能收敛。计算资源允许的情况下用验证集AUC而不是准确率来筛选模型。因为乳腺癌症图像分类的类别不均衡准确率这条曲线会把模型引向“全部判良性”的陷阱。迁移学习的epoch数建议控制在20-50如果验证loss在10个epoch内不下降说明学习率或dropout需要调整不要硬撑到100个epoch。6. 验证不只看准确率乳腺癌分类的进阶技巧6.1 用混淆矩阵和ROC定位坏样本乳腺癌症图像分类要关注的指标不是准确率而是恶性类别的召回率和AUC。训练完成后在测试集上输出混淆矩阵和ROCfrom sklearn.metrics import confusion_matrix, roc_auc_score import numpy as np all_labels [] all_probs [] model.eval() with torch.no_grad(): for images, labels in test_loader: outputs model(images) probs torch.softmax(outputs, dim1)[:, 1] all_labels.extend(labels.cpu().numpy()) all_probs.extend(probs.cpu().numpy()) preds (np.array(all_probs) 0.5).astype(int) print(confusion_matrix(all_labels, preds)) print(AUC:, roc_auc_score(all_labels, all_probs))混淆矩阵会告诉你模型是把良性误判成恶性还是把恶性漏判。AUC不依赖0.5这个阈值更接近模型对两类样本的排序能力。6.2 Grad-CAM检查模型关注的区域如果模型在高置信度下关注了背景而不是病变区域说明模型学到的是假捷径。常见做法是拿Grad-CAM可视化最后一层卷积的热力图人工确认模型看到了哪里。我习惯用pytorch-grad-cam这个库安装后直接调用from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.model_targets import ClassifierOutputTarget cam GradCAM(modelmodel, target_layers[model.blocks[-1]]) targets [ClassifierOutputTarget(1)] grayscale_cam cam(input_tensorinput_tensor, targetstargets)[0, :]target_layers选择的是EfficientNet最后一个blockClassifierOutputTarget(1)指定要解释恶性类别。热力图应该落在细胞团或者病变组织区域而不是玻璃片边缘。如果热力图集中在空白背景就要检查训练数据里是否存在背景偏移造成的误导。6.3 一个容易踩的坑数据顺序和随机种子最后一个容易忽略的调试点数据加载顺序。如果DataLoader的shuffleTrue但没有固定随机种子每次训练得到的验证集顺序和增强顺序都会变化AUC会有小抖动。复现实验时在所有随机模块前统一设置一次import random import numpy as np import torch random.seed(42) np.random.seed(42) torch.manual_seed(42) torch.backends.cudnn.deterministic True固定种子之后配合早停保存的best_model.pth你可以在不同机器上得到一致的分类结果。把数据分割、数据加载和模型初始化三个部分的随机源分别固定调试效率会好很多。本文还有配套的精品资源点击获取