尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

猴痘皮肤病变二分类实战:从数据到模型的可复现流程

猴痘皮肤病变二分类实战:从数据到模型的可复现流程 简介这份资源面向计算机视觉入门与医学图像分析方向的开发者、学生及研究人员提供猴痘皮肤病变的二分类图像数据集可直接用于深度学习模型训练与分类实验省去繁琐的数据采集与清洗环节。压缩包内共约2000个文件以1999张jpg图像为主另附1个json类别字典文件整体约24.6MB体积轻便易于下载与本地部署。数据按训练集与测试集分文件夹组织训练集含2555张图像测试集含637张图像每个子文件夹对应一个类别涵盖猴痘与非猴痘两类目录结构清晰便于直接接入PyTorch、TensorFlow等框架的ImageFolder读取方式。json字典文件可用于类别索引映射方便标签转换与结果解析。目前已有149人学习关注适合作为医学图像二分类任务的练手数据帮助读者快速验证网络结构、数据增强与迁移学习效果积累从数据加载到模型评估的完整实践思路。1. 猴痘皮肤病变二分类从临床痛点到可复现的图像分类任务猴痘Mpox皮肤病变的识别在 2022 年全球多国暴发之后从皮肤科的小众议题变成了公共卫生监测里的高频需求。临床上一个真实的痛点是猴痘、水痘、麻疹、梅毒二期疹、手足口病的皮损在早期照片里高度相似基层医生靠肉眼判断的准确率并不稳定而 PCR 确诊又受限于采样时机和实验室覆盖。于是「用图像分类模型做初筛」成了一个被反复讨论的方向——这正是「猴痘皮肤病变的二分类数据集」要解决的问题把皮损图像组织成「猴痘 / 非猴痘」两类让计算机视觉里的图像分类流程能直接跑起来。它适合三类人做计算机视觉大作业的学生、想练手医学图像二分类的算法工程师、以及需要快速验证「皮损初筛」可行性的医疗信息化团队。二分类是图像分类里最朴素的形态但医学数据的坑恰恰最多——类别不平衡、图像来源混杂、标注口径不统一这些都会在后面几章里逐个拆开。这一章先把任务边界立住后面才好动手。2. 数据集拿到手先做什么结构、标签与最小可视化2.1 二分类数据集的典型目录结构与标签约定猴痘皮损数据集在公开渠道上通常以「按类别分文件夹」的形式发布这是图像分类里最省事的组织方式因为ImageFolder这类接口可以直接读取。常见结构是根目录下两个子目录分别对应两个类别每个子目录里放该类别的图像文件。命名上有的用Monkeypox/Others有的用monkeypox/normal还有的用positive/negative。不要假设名字统一第一步永远是先ls看清楚。# 先看清目录结构别急着写训练脚本 find ./dataset -maxdepth 2 -type d | sort # 统计每个类别的图像数量判断是否类别不平衡 for d in ./dataset/*/; do echo -n $d : find $d -type f \( -iname *.jpg -o -iname *.png -o -iname *.jpeg \) | wc -l done上面第一条命令列出两层目录确认类别文件夹的真实名字第二条循环统计每个类别的文件数。参数上没什么可调的重点是看两个数字的比值。如果「猴痘」类比「非猴痘」类少一个数量级那后面训练时必须处理类别不平衡否则模型会倾向于全预测成多数类准确率看着高召回率却惨不忍睹。这是医学二分类里最常见的翻车点没有之一。2.2 用几十行代码把数据「看」一遍在写任何模型之前先把图像读进来、显示出来、看看尺寸和通道分布。医学图像经常混着灰度图、RGBA 图、不同分辨率直接喂给网络会在归一化那一步报错或者悄悄出错。import os from PIL import Image import matplotlib.pyplot as plt root ./dataset classes sorted(os.listdir(root)) fig, axes plt.subplots(2, 5, figsize(15, 6)) for row, cls in enumerate(classes[:2]): cls_dir os.path.join(root, cls) files [f for f in os.listdir(cls_dir) if f.lower().endswith((.jpg, .png, .jpeg))][:5] for col, fname in enumerate(files): img Image.open(os.path.join(cls_dir, fname)) axes[row, col].imshow(img) axes[row, col].set_title(f{cls}\n{img.size} {img.mode}, fontsize8) axes[row, col].axis(off) plt.tight_layout() plt.savefig(sample_grid.png, dpi120) print(类别:, classes)这段代码做三件事按类别取前五张图、显示图像同时把尺寸和色彩模式打在标题上、存成一张网格图。img.size给出宽高img.mode给出是RGB还是L灰度还是RGBA。如果发现同一类别里既有RGB又有L那在 Dataset 里必须统一转成三通道否则ToTensor之后维度对不上训练直接崩。这一步花五分钟能省掉后面半小时的调试。提示如果样本网格里出现大量重复或近乎相同的图说明数据可能来自同一批爬取或同一患者的连续拍摄训练集和验证集之间会存在泄漏后面划分时要按来源分组而不是随机切。3. 从零搭一条可复现的二分类训练流水线3.1 数据划分与增强别让验证集「偷看」训练集图像分类的标准做法是训练 / 验证 / 测试三划分比例常见 7:1.5:1.5 或 8:1:1。医学数据样本量往往不大所以划分要固定随机种子保证每次复现结果一致。增强方面皮损图像对翻转、小角度旋转、亮度微调是鲁棒的但不建议做大幅度的裁剪或弹性形变因为皮损的形态和边缘本身是判别信息裁掉可能把病灶切没。import random from torchvision import datasets, transforms from torch.utils.data import DataLoader, Subset random.seed(42) train_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p0.5), transforms.RandomRotation(degrees15), transforms.ColorJitter(brightness0.1, contrast0.1), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) val_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]), ]) full datasets.ImageFolder(./dataset, transformtrain_tf) n len(full) idx list(range(n)) random.shuffle(idx) train_idx idx[:int(0.7 * n)] val_idx idx[int(0.7 * n):int(0.85 * n)] test_idx idx[int(0.85 * n):] train_ds Subset(full, train_idx) val_ds Subset(datasets.ImageFolder(./dataset, transformval_tf), val_idx) test_ds Subset(datasets.ImageFolder(./dataset, transformval_tf), test_idx) train_loader DataLoader(train_ds, batch_size32, shuffleTrue, num_workers4) val_loader DataLoader(val_ds, batch_size32, shuffleFalse, num_workers4)关键点在Normalize用的均值和方差是 ImageNet 的统计值因为后面要用预训练权重输入分布必须对齐。RandomRotation(15)是经验值皮损照片通常不会有大角度旋转15 度足够覆盖拍摄时的轻微倾斜。batch_size32在 224 分辨率下对显存要求不高8G 显存也能跑。如果样本量只有几百张batch_size可以降到 16配合更小的学习率。3.2 迁移学习建模为什么二分类也别从零训猴痘皮损数据集规模通常不大从零训练卷积网络几乎必然过拟合。常见做法是用 ImageNet 预训练的 ResNet 或 EfficientNet把最后的全连接层换成二输出然后分两阶段微调先冻结主干只训分类头再解冻后几层用更小的学习率整体微调。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) for p in model.parameters(): p.requires_grad False # 第一阶段冻结主干 model.fc nn.Linear(model.fc.in_features, 2) model model.to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.fc.parameters(), lr1e-3) for epoch in range(5): model.train() for x, y in train_loader: x, y x.to(device), y.to(device) optimizer.zero_grad() loss criterion(model(x), y) loss.backward() optimizer.step() print(fepoch {epoch} done)第一阶段只训model.fc学习率 1e-3 是分类头的常用起点。5 个 epoch 之后如果验证集准确率还在涨就进入第二阶段把layer4和fc解冻学习率降到 1e-4再训 10 到 15 个 epoch。CrossEntropyLoss对二分类同样适用不需要换成BCELoss因为输出是两个 logits。如果类别严重不平衡给CrossEntropyLoss传weight参数权重按类别频率的倒数设置这是比盲目上采样更稳的做法。3.3 评估指标准确率会骗人看召回和 AUC医学二分类里准确率是最容易让人误判的指标。如果非猴痘样本占 90%模型全预测成非猴痘也有 90% 准确率但猴痘一个都抓不到。真正要看的是猴痘类正类的召回率、精确率以及整体 AUC。from sklearn.metrics import classification_report, roc_auc_score import numpy as np model.eval() y_true, y_prob [], [] with torch.no_grad(): for x, y in val_loader: x x.to(device) logits model(x) prob torch.softmax(logits, dim1)[:, 1] y_prob.extend(prob.cpu().numpy()) y_true.extend(y.numpy()) y_pred (np.array(y_prob) 0.5).astype(int) print(classification_report(y_true, y_pred, digits4)) print(AUC:, roc_auc_score(y_true, y_prob))classification_report会给出每个类别的精确率、召回率和 F1。重点看猴痘类的召回率它代表「真正是猴痘的样本里有多少被找出来」。阈值 0.5 不是金科玉律如果漏检代价高可以把阈值降到 0.3 换取更高召回代价是误报增多。AUC 衡量的是排序能力不受阈值影响适合用来比较不同模型。这套指标组合比单看准确率靠谱得多。4. 避坑与排查医学图像二分类最容易栽的五个地方4.1 现象验证准确率 95%测试集一塌糊涂原因通常是数据泄漏。同一患者的多次拍摄、同一来源的近似图被随机分到了训练和验证两侧模型记住了「这张图」而不是「这个特征」。解决方式是按来源或按图像哈希分组划分同一组只进一个集合。如果数据里没有来源信息至少用感知哈希去重把近似图归到同一组。4.2 现象训练 loss 不降或者降了但输出全是同一类先查标签映射。ImageFolder按文件夹名的字母序分配标签Monkeypox和Others谁对应 0 谁对应 1 要打印确认。再看归一化如果验证集忘了做同样的Normalize输入分布和训练不一致模型表现会断崖式下跌。最后查学习率分类头用 1e-3 没问题但整体微调时还用 1e-3 会把预训练权重冲垮必须降到 1e-4 量级。4.3 现象图像读取报错cannot identify image file多半是数据集里混了损坏文件或非图像文件比如.DS_Store、缩略图缓存。解决方式是在 Dataset 的__getitem__里加异常捕获跳过坏样本或者提前用脚本过滤一遍。医学数据集从多个渠道汇总时这类脏文件非常常见别指望它干净。4.4 现象显存溢出batch 调到 1 还是 OOM检查图像原始分辨率。有些皮损照片是几千万像素的临床相机原图Resize之前如果先做了ToTensor或者别的操作内存会瞬间爆掉。正确顺序是先Resize再ToTensortransforms.Compose里顺序写反是新手高频错误。另外num_workers设太大也会占额外内存4 到 8 之间够用。4.5 现象模型在验证集上表现很好换一批新图就崩这是域偏移。训练数据可能来自单一来源、单一设备、单一肤色分布换到真实场景就不适用。缓解手段包括增强里加入更强的颜色抖动模拟不同设备训练时用多个来源的数据混合以及在新域上做少量样本的微调。指望一个在小数据集上训出来的模型直接上临床是不现实的这一点必须提前跟需求方说清楚。5. 把二分类做扎实阈值调优、可解释性与小样本技巧模型训完只是起点。真正决定这个方案能不能用的是阈值怎么定、模型凭什么判断、以及样本少的时候怎么稳住。先说阈值默认 0.5 是数学上的中点不是业务上的最优点。做法是在验证集上扫一遍阈值画出召回率和精确率的权衡曲线根据实际场景选点。如果用于初筛、宁可误报不可漏报就选高召回对应的低阈值如果用于辅助确诊、误报代价高就往高精确率方向调。import numpy as np from sklearn.metrics import precision_recall_curve prec, rec, thr precision_recall_curve(y_true, y_prob) # 找召回 0.90 的前提下精确率最高的阈值 mask rec[:-1] 0.90 best_idx np.argmax(prec[:-1][mask]) print(推荐阈值:, thr[mask][best_idx], 精确率:, prec[:-1][mask][best_idx], 召回率:, rec[:-1][mask][best_idx])这段代码先算精确率-召回率曲线再在召回不低于 0.9 的候选里挑精确率最高的阈值。rec[:-1]是因为precision_recall_curve返回的精确率比召回率多一个元素索引要对齐。这个阈值应该固化到推理脚本里而不是每次拍脑袋。再说可解释性。医学场景里「模型说是猴痘」不够医生想知道它看的是哪块区域。Grad-CAM 是最省事的做法把最后一层卷积的特征图和梯度加权叠回原图就能看到模型关注的位置。如果热力图落在背景、衣物、水印上而不是皮损本身说明模型学偏了这时候要么裁掉无关区域要么在增强里加入随机裁剪逼它关注病灶。这一步在写论文或做汇报时几乎是必需的也是排查「模型到底学了什么」的最直接手段。小样本是这个数据集绕不开的现实。几百张图训深度模型除了迁移学习还有几个实用技巧一是用更强的增强比如 RandAugment但幅度要控制二是用余弦退火学习率配合较多 epoch让模型慢慢收敛三是做 k 折交叉验证把每一折的验证结果平均比单次划分的结论可靠得多四是如果实在样本太少考虑先用自监督预训练或者用更大的医学图像数据集做中间预训练再迁移过来。这些手段不保证一定涨点但能显著降低「一次划分运气好」带来的误判。我自己在这个方向上踩过最深的坑是早期太相信验证集上的数字没做交叉验证也没看热力图结果换了一批新来源的图召回率从 0.88 掉到 0.5 出头。后来养成习惯任何医学二分类结果先看混淆矩阵再看 Grad-CAM最后做一次跨来源测试三关都过了才敢说这个模型能用。数据集本身不会告诉你它的边界在哪得自己一遍遍去撞。希望帮到你。本文还有配套的精品资源点击获取
返回列表