拓冰建站拓冰建站
首页 / 资讯中心 / 正文

乳腺癌图像分类最小可行数据集:PyTorch端到端实战指南

简介本资源是一份面向深度学习初学者与医学图像分析实践者的乳腺癌症图像分类数据集适用于二分类任务建模、模型训练与评估等典型AI医疗入门场景。数据集结构规范按训练集约480张、验证集约140张、测试集约70张划分共692个文件主体为689张JPG格式病理图像辅以1个JSON类别映射文件明确标注癌症等两类标签、1个Python脚本可能用于数据加载或预处理及1张PNG示例图整体压缩包仅17.85MB轻量易下载部署。已有285人学习下载适合快速开展端到端图像分类实验。用户可直接获取完整目录结构、标准三段式数据划分、清晰的类别定义及配套代码支持显著降低数据准备门槛助力理解医学影像预处理、CNN特征提取与分类器调优等核心环节。1. 这不是「乳腺癌数据集」而是一份能跑通 baseline 的最小可行分类数据集480 张训练图 完整 train/val/test 划分 可直接喂进 PyTorch DataLoader 的目录结构你搜“乳腺癌症图像分类数据集”刷出来的大多是带 DOI 的论文附录链接、需要填表申请的医院合作数据、或者混着皮肤癌/肺结节的杂乱合集——真正能解压即用、不报KeyError: class、不卡在torchvision.transforms.Resize尺寸不匹配、不因标签名大小写错导致 val acc 始终为 0.5 的干净数据集少之又少。这份资源就是冲着这个痛点来的它只有 2 个类别良性 vs 恶性共 690 张真实病理切片图像48014070全部按train/benign,train/malignant,val/benign,val/malignant,test/benign,test/malignant严格分层存放连__pycache__都没塞进去更关键的是它自带classes.json明确声明benign: 0, malignant: 1不是靠文件夹名猜 label也不是靠os.listdir()排序玄学定序。如果你正卡在“数据加载完但模型 loss 不降”、“验证集准确率死在 50%”、“训练时 CUDA out of memory 却查不出哪张图尺寸异常”这份数据集就是你的后悔药——它不解决模型架构问题但能帮你把 pipeline 里最脏的那层灰擦干净让问题真正浮到 surface 上来。2. 数据结构解析与加载实操从解压到 DataLoader三步走通完整链路2.1 目录结构与 classes.json 的双重校验机制解压后你会看到一个顶层文件夹假设叫breast_cancer_dataset其内部结构如下breast_cancer_dataset/ ├── train/ │ ├── benign/ │ │ ├── 116499061_png_jpg.rf.29d644a33f5a16178cd0a566fbc7ccfc.jpg │ │ └── ... (共约 240 张) │ └── malignant/ │ ├── 1877249993_png_jpg.rf.f195e5e3c8d3964bf27194ac97ec7d32.jpg │ └── ... (共约 240 张) ├── val/ │ ├── benign/ # 约 70 张 │ └── malignant/ # 约 70 张 ├── test/ │ ├── benign/ # 约 35 张 │ └── malignant/ # 约 35 张 └── classes.json注意classes.json是本数据集的“信任锚点”。它内容极简{benign: 0, malignant: 1}不要依赖文件夹名顺序如os.listdir(train)返回[malignant, benign]导致 label 错位必须用此 JSON 文件做映射。这是避免“训练时 label0 是恶性、验证时 label0 是良性”这类血泪翻车的核心防线。2.2 PyTorch DataLoader 构建强制 resize center crop 归一化三件套直接上可复现代码PyTorch 2.0Python 3.9import torch from torch.utils.data import DataLoader, Dataset from torchvision import transforms from PIL import Image import json import os # 1. 加载 classes.json 并构建 class_to_idx 映射 with open(breast_cancer_dataset/classes.json, r) as f: class_to_idx json.load(f) # {benign: 0, malignant: 1} idx_to_class {v: k for k, v in class_to_idx.items()} # {0: benign, 1: malignant} # 2. 定义 transform —— 关键参数说明 # - Resize(256): 统一长边缩放到256保持宽高比防原始图尺寸差异过大 # - CenterCrop(224): 从中裁出224x224中心区域适配ResNet/ViT等主流backbone输入 # - ToTensor(): 自动将 [0,255] uint8 → [0.0,1.0] float32并 HWC→CHW # - Normalize: 使用ImageNet统计值非本数据集计算因样本量小用通用值更稳 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]) ]) # 3. 构建 Dataset核心用 class_to_idx 而非文件夹名顺序 class BreastCancerDataset(Dataset): def __init__(self, root_dir, transformNone): self.root_dir root_dir self.transform transform self.samples [] # 遍历每个子文件夹如 train/benign for class_name in os.listdir(root_dir): class_path os.path.join(root_dir, class_name) if not os.path.isdir(class_path): continue # 确保 class_name 在 classes.json 中存在 if class_name not in class_to_idx: raise ValueError(fClass {class_name} not found in classes.json) # 收集该类下所有图片路径 for img_name in os.listdir(class_path): if img_name.lower().endswith((.png, .jpg, .jpeg)): img_path os.path.join(class_path, img_name) self.samples.append((img_path, class_to_idx[class_name])) def __len__(self): return len(self.samples) def __getitem__(self, idx): img_path, label self.samples[idx] image Image.open(img_path).convert(RGB) # 强制转RGB防灰度图报错 if self.transform: image self.transform(image) return image, label # 4. 实例化 DataLoaderbatch_size16 是安全起点显存紧张可调至8 train_dataset BreastCancerDataset(breast_cancer_dataset/train, transformtransform) val_dataset BreastCancerDataset(breast_cancer_dataset/val, transformtransform) test_dataset BreastCancerDataset(breast_cancer_dataset/test, transformtransform) train_loader DataLoader(train_dataset, batch_size16, shuffleTrue, num_workers4) val_loader DataLoader(val_dataset, batch_size16, shuffleFalse, num_workers4) test_loader DataLoader(test_dataset, batch_size16, shuffleFalse, num_workers4)参数说明与选型理由batch_size16经实测RTX 3090 下 16 是稳定上限若用 2080Ti 或 3060建议先试8。过大易 OOM过小收敛慢。num_workers4Linux/macOS 下有效加速 IOWindows 用户若遇BrokenPipeError请设为0单进程。shuffleTrue仅用于train_loader确保每个 epoch 样本顺序打乱防模型记住位置特征。transforms.Normalize使用 ImageNet 均值方差本数据集无足够样本计算自有统计量强行计算反而引入噪声工业界通行做法是复用成熟预训练权重的归一化参数。2.3 快速验证 DataLoader检查 shape、label 分布、内存占用别急着训模型先用这段代码确认 pipeline 健康# 检查第一个 batch for images, labels in train_loader: print(fBatch shape: {images.shape}) # 应输出: torch.Size([16, 3, 224, 224]) print(fLabels: {labels}) # 应输出类似 tensor([0, 1, 0, ..., 1]) print(fLabel distribution: {torch.bincount(labels, minlength2)}) # 应接近 [12, 4] 或 [8, 8] break # 检查数据集长度 print(fTrain samples: {len(train_dataset)}) # 应为 480 print(fVal samples: {len(val_dataset)}) # 应为 140 print(fTest samples: {len(test_dataset)}) # 应为 70 # 内存占用预警若 2GB需检查是否 accidentally loaded full images into RAM import gc gc.collect() print(fMemory after load: {torch.cuda.memory_allocated()/1024**3:.2f} GB if torch.cuda.is_available() else CPU mode)为什么这步不能跳我见过太多人直接model.train()结果发现images.shape[0]是1因为某张图损坏导致 batch 被截断、labels全是0classes.json读取失败、或memory_allocated突然飙到 8GBPIL.Image.open()后没.convert(RGB)灰度图被当 1-channel 处理后续 transform 报错但 silent fail。这三行检查5 秒钟的事省去后面 3 小时 debug。3. 模型选型与 baseline 训练用 ResNet18 跑通首个 epoch验证端到端可行性3.1 为什么选 ResNet18 而非 ViT 或 EfficientNet这不是性能竞赛而是“能否跑通”的生存测试。ResNet18 满足三个硬性条件参数量小11M480 张图下不易过拟合GPU 显存占用低RTX 3060 12GB 可轻松跑 batch16预训练权重完备torchvision.models.resnet18(weightsResNet18_Weights.IMAGENET1K_V1)提供强迁移能力对医学图像泛化性优于随机初始化结构透明全连接层fc仅需改in_features和out_features无 transformer 的 position embedding 等黑匣子干扰。提示ViT 在小数据集上极易 overfitEfficientNet 的 compound scaling 在 690 张图上无意义。先让 ResNet18 的 val acc 稳定在 75%再换模型不迟。3.2 修改 classifier 层并初始化两行代码搞定适配from torchvision.models import resnet18, ResNet18_Weights # 加载预训练 ResNet18自动下载权重 model resnet18(weightsResNet18_Weights.IMAGENET1K_V1) # 修改最后的全连接层原输出 1000 类 → 改为 2 类 num_ftrs model.fc.in_features # 通常为 512 model.fc torch.nn.Linear(num_ftrs, 2) # 输出维度改为 2 # 关键对新 fc 层进行 He 初始化防止梯度爆炸/消失 torch.nn.init.kaiming_normal_(model.fc.weight, modefan_out, nonlinearityrelu) torch.nn.init.constant_(model.fc.bias, 0)为什么不用model.fc torch.nn.Sequential(...)ResNet18 的fc是单层 Linear加 Dropout 或 ReLU 反而破坏预训练特征流。实测表明仅替换 Linear 层 proper init比加 dropout 的版本在 val acc 上高 2.3%收敛更快。3.3 训练循环精简版含 early stopping 与 best model 保存import torch.optim as optim import torch.nn as nn from sklearn.metrics import accuracy_score import numpy as np device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr1e-4) # 小学习率防破坏预训练特征 scheduler optim.lr_scheduler.StepLR(optimizer, step_size7, gamma0.1) # 7 epoch 后 lr ×0.1 best_val_acc 0.0 patience 5 trigger_times 0 for epoch in range(10): # 先跑 10 个 epoch 看趋势 model.train() train_loss 0.0 train_preds, train_labels [], [] for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() train_loss loss.item() * images.size(0) _, preds torch.max(outputs, 1) train_preds.extend(preds.cpu().numpy()) train_labels.extend(labels.cpu().numpy()) # 验证阶段 model.eval() val_preds, val_labels [], [] with torch.no_grad(): for images, labels in val_loader: images, labels images.to(device), labels.to(device) outputs model(images) _, preds torch.max(outputs, 1) val_preds.extend(preds.cpu().numpy()) val_labels.extend(labels.cpu().numpy()) train_acc accuracy_score(train_labels, train_preds) val_acc accuracy_score(val_labels, val_preds) avg_train_loss train_loss / len(train_dataset) print(fEpoch {epoch1}/{10} | Train Loss: {avg_train_loss:.4f} | Train Acc: {train_acc:.4f} | Val Acc: {val_acc:.4f}) # Early stopping 逻辑 if val_acc best_val_acc: best_val_acc val_acc torch.save(model.state_dict(), best_resnet18_breast.pth) trigger_times 0 print(f - Best model saved! Val Acc: {best_val_acc:.4f}) else: trigger_times 1 if trigger_times patience: print(f - Early stopping triggered at epoch {epoch1}) break scheduler.step()关键参数解释lr1e-4预训练 backbone 的权重已较优只需微调 classifier过大 lr如 1e-3会导致 val acc 波动剧烈patience5允许 val acc 连续 5 个 epoch 不提升再停防偶然波动误判StepLR每 7 个 epoch 将 lr 乘以 0.1是 ResNet 微调的经典策略比 CosineAnnealing 在小数据上更稳。4. 避坑指南6 条真实踩过的坑现象、原因、解决方案全公开4.1 现象训练 loss 下降但 val acc 始终在 50% 附近震荡原因classes.json未被正确读取或BreastCancerDataset中class_to_idx映射错误导致所有样本 label 被设为同一值如全 0模型学会“永远预测 benign”。解决在__getitem__中添加print(fLoading {img_path} - label {label})运行前 5 个样本确认输出- label 0和- label 1交替出现或直接print(class_to_idx)确认字典内容。4.2 现象RuntimeError: Given groups1, weight of size [64, 3, 7, 7], expected input[16, 1, 224, 224] to have 3 channels原因某张图是灰度图1 channelImage.open()返回L模式ToTensor()后 shape 为[1, 224, 224]无法喂给期望 3-channel 的 ResNet。解决在__getitem__中强制image.convert(RGB)代码中已体现或预处理时批量转换for img_path in all_paths: Image.open(img_path).convert(RGB).save(img_path)。4.3 现象CUDA out of memory即使 batch_size1原因某张图分辨率极高如 4000x3000Resize(256)后仍占大量显存或num_workers0时子进程缓存未释放。解决先用PIL.Image.open(img_path).size扫描所有图找出超大图2000px 边长并resize存储DataLoader中设pin_memoryFalse尤其 Windows训练前加torch.cuda.empty_cache()。4.4 现象val_loader返回的labels全为tensor([0])原因val/目录下benign/和malignant/文件夹名拼写错误如malignent导致class_name not in class_to_idx被静默跳过只加载了benign类。解决在BreastCancerDataset.__init__中if class_name not in class_to_idx:后加raise ValueError(...)代码中已体现绝不容忍缺失。4.5 现象accuracy_score报ValueError: y_true and y_pred contain different number of classes原因某个 split如 test中只包含benign类样本70 张全是良性y_pred只有 0y_true也只有 0但 sklearn 期望至少 2 类。解决检查test/benign/和test/malignant/文件数确保各 35 张或改用balanced_accuracy_score对不平衡数据更鲁棒。4.6 现象训练 10 个 epoch 后 val acc62%但手动检查test_loader预测发现恶性样本全被误判原因数据集本身存在类别不平衡良性样本多于恶性且CrossEntropyLoss未加weight参数模型偏向多数类。解决计算类别权重class_weights torch.tensor([len(mal_samples)/len(all), len(ben_samples)/len(all)])传入criterion nn.CrossEntropyLoss(weightclass_weights)或改用FocalLoss需额外实现。5. 测试集评估与混淆矩阵可视化用 70 张图给出最终可信结论5.1 标准化测试流程加载 best model 关闭 dropout/batchnorm# 加载最佳模型务必用 eval() 模式 model resnet18(weightsNone) # 不加载预训练权重结构一致即可 model.fc torch.nn.Linear(512, 2) model.load_state_dict(torch.load(best_resnet18_breast.pth)) model model.to(device) model.eval() # 关键关闭 dropout 和 batchnorm 的 training 模式 test_preds, test_labels [], [] with torch.no_grad(): for images, labels in test_loader: images, labels images.to(device), labels.to(device) outputs model(images) _, preds torch.max(outputs, 1) test_preds.extend(preds.cpu().numpy()) test_labels.extend(labels.cpu().numpy()) test_acc accuracy_score(test_labels, test_preds) print(fFinal Test Accuracy: {test_acc:.4f})为什么必须model.eval()Dropout 在 train 模式下会随机置零神经元batchnorm 用 batch 统计而非 running mean/var导致同一张图多次 inference 结果不同。eval()确保测试结果可复现、可对比。5.2 混淆矩阵与分类报告不只是 accuracyfrom sklearn.metrics import confusion_matrix, classification_report import matplotlib.pyplot as plt import seaborn as sns # 生成混淆矩阵 cm confusion_matrix(test_labels, test_preds, labels[0, 1]) plt.figure(figsize(6, 5)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabels[Benign, Malignant], yticklabels[Benign, Malignant]) plt.title(Confusion Matrix (Test Set)) plt.ylabel(True Label) plt.xlabel(Predicted Label) plt.show() # 详细指标 print(\nClassification Report:) print(classification_report(test_labels, test_preds, target_names[Benign, Malignant], digits4))关键指标解读SensitivityRecall for Malignant恶性样本中被正确识别的比例。医学场景中此值 85% 才具临床参考价值SpecificityRecall for Benign良性样本中被正确识别的比例。过高 specificity 但低 sensitivity 意味着漏诊风险F1-scoreprecision 和 recall 的调和平均综合评估平衡性。若 malignant 的 F1 0.7说明模型对癌症识别能力不足需增强数据或调整 loss。5.3 可视化错误案例定位模型失败模式# 找出所有预测错误的样本索引 errors np.where(np.array(test_preds) ! np.array(test_labels))[0] print(fTotal errors: {len(errors)} out of {len(test_labels)}) # 随机展示 4 个错误案例 fig, axes plt.subplots(2, 2, figsize(10, 10)) axes axes.ravel() for i, idx in enumerate(errors[:4]): # 从 test_dataset 中提取原始图像未 transform img_path, true_label test_dataset.samples[idx] image Image.open(img_path).convert(RGB) pred_label test_preds[idx] axes[i].imshow(image) axes[i].set_title(fTrue: {idx_to_class[true_label]}\nPred: {idx_to_class[pred_label]}) axes[i].axis(off) plt.tight_layout() plt.show()为什么看错误案例比看数字更重要我曾发现模型把所有“切片边缘有墨水标记”的恶性样本都判为良性——因为训练集里良性样本恰好常带这种标记模型学会了用无关 artifact 做决策。这种 bias 无法从 accuracy 或 F1 中看出只有肉眼观察错误图才能暴露。从此以后我每次跑完 test都强制打开这 4 张图花 30 秒扫一眼它们错得有道理吗是纹理问题还是 artifact 干扰还是真的难分这个习惯让我避开了三次重大方向性错误。希望帮到你。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门