ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

基于ISIC2018的皮肤病判别:CNN、ResNet与SVM实战解析

基于ISIC2018的皮肤病判别:CNN、ResNet与SVM实战解析 简介本资源为基于CNN、ResNet与SVM对皮肤病数据进行判别的Python项目源码面向计算机、人工智能、通信工程等专业的在校学生与教师也适合作为课程设计、毕业设计或项目立项演示的参考方案帮助读者理解深度学习与传统机器学习在医学图像分类任务中的结合方式。压缩包共1996个文件以1963张png图像数据为主另含10个py源码、8个pyc编译文件、4个xml配置、2个csv标签文件及pth模型权重等整体约34.47MB目录结构清晰便于按数据、模型与训练模块检索。目前已有119人学习下载。项目代码均经测试运行成功答辩评审平均分达96分读者可获取完整的皮肤病判别流程包括数据预处理、CNN与ResNet特征提取、SVM分类对比及模型保存等环节并可在现有代码基础上修改以扩展其他功能。1. 从一份 ISIC2018 皮肤病判别源码说起CNN、ResNet、SVM 到底怎么分工如果你手头正压着一个医学图像分类的课程设计或者毕设开题选了「皮肤病智能判别」这个方向大概率绕不开 ISIC2018 这个数据集。这份资源就是围绕它做的一套 Python 源码把 CNN、ResNet、SVM 三条技术路线揉进同一个判别任务里还带了 Flask 的注册登录页面。我第一次拿到类似结构的时候最想搞清楚的不是「准确率多少」而是这三个模型到底谁在干活、谁在打辅助。因为很多人一上来就把 ResNet 当万能钥匙结果训练半天 loss 不降回头才发现数据预处理和标签对齐根本没做对。这份源码的价值在于它把「卷积特征提取」和「传统分类器」串成了一条完整链路适合想一次性看懂 CNN 做特征、SVM 做决策这套组合拳的人也适合需要快速搭出可演示系统的同学。下面我按实际拆包顺序把运行、改参、避坑一条条讲清楚。2. 环境搭建与数据准备把 ISIC2018 的 CSV 和图像对齐2.1 先看清目录里有什么再决定装什么这份源码的根目录里躺着几个关键文件ISIC2018_Task3_Training_GroundTruth.csv、ISIC2018_Task3_Test_GroundTruth.csv还有baseCSS.css、user_register.html、success.html这些前端资源以及一张resnet.png结构图。CSV 是标签文件图像本身需要你另外去 ISIC 官方渠道下载对应编号的图片。很多人翻车就翻在这里CSV 里有图像 ID 和七类皮肤病标签但图片文件夹是空的代码跑起来直接报FileNotFoundError。我一般会先写个脚本核对 CSV 里的 ID 和实际图片文件名是否一一对应差一个都别急着往下走。环境方面这份代码依赖 PyTorch 或 TensorFlow 中的一套看具体实现加上 scikit-learn 做 SVMFlask 做 Web 展示。Python 版本建议 3.8 到 3.10太新的版本某些旧版 torchvision 会出兼容问题。装包之前先确认显卡驱动和 CUDA 版本如果只用 CPU 跑 ResNet一张图推理可能要几百毫秒训练基本不用想。# 建议先建虚拟环境避免和系统里的包打架 python -m venv venv_isic source venv_isic/bin/activate # Windows 用 venv_isic\Scripts\activate # 核心依赖版本按你实际 CUDA 调整 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install scikit-learn pandas numpy pillow flask这段命令的逻辑是先隔离环境再装深度学习框架和传统机器学习库。--index-url那行是 PyTorch 官方源CUDA 版本要和你nvidia-smi里看到的驱动匹配不匹配会装成 CPU 版训练速度差几十倍。Flask 和 Pillow 分别负责 Web 展示和图像读取缺一个都会在导入阶段就报错。2.2 标签 CSV 的读取与图像路径拼接ISIC2018 Task3 的标签是 one-hot 格式七列分别对应 AKIEC、BCC、BKL、DF、MEL、NV、VASC。代码里通常用 pandas 读进来再把 one-hot 转成类别索引。这里有个容易忽略的点CSV 第一列是image字段存的是图像 ID不带.jpg后缀。你拼接路径的时候必须手动补扩展名而且要注意有些图像是.png格式混在一起会漏读。import pandas as pd import os from PIL import Image # 读取训练标签 df pd.read_csv(ISIC2018_Task3_Training_GroundTruth.csv) # one-hot 列名 label_cols [AKIEC, BCC, BKL, DF, MEL, NV, VASC] # 转成类别索引 df[label] df[label_cols].values.argmax(axis1) # 图像目录假设图片都放在 images/ 下 img_dir images valid_rows [] for _, row in df.iterrows(): img_id row[image] # 先试 jpg 再试 png避免格式混用漏图 for ext in [.jpg, .png]: path os.path.join(img_dir, img_id ext) if os.path.exists(path): valid_rows.append((path, row[label])) break print(f有效图像数: {len(valid_rows)} / 总标签数: {len(df)})这段代码的核心是「先校验再训练」。argmax(axis1)把 one-hot 转成 0 到 6 的整数后面 CrossEntropyLoss 直接能用。路径拼接用循环试两种扩展名是因为 ISIC 官方图片格式并不统一只写死.jpg会静默丢掉一批样本。最后打印有效数量如果明显少于标签总数说明图片没下全或者命名对不上这时候继续跑训练就是自欺欺人。注意ISIC2018 的测试集标签文件里部分图像在公开下载包里可能缺失遇到对不上的 ID 先查官方说明别硬凑。3. CNN 与 ResNet 特征提取从卷积核到预训练权重怎么选3.1 自己搭 CNN 还是直接上 ResNet 预训练源码里同时出现了 CNN 和 ResNet这不是重复劳动而是两条对比路线。自己搭的 CNN 通常三到四个卷积块每块接 ReLU 和 MaxPool最后全连接输出七类。这种结构参数量小训练快但准确率上限有限适合用来理解卷积到底在干什么。ResNet 则是拿 ImageNet 预训练权重做迁移学习把最后的全连接层换成七输出冻结前面几层或者全部微调。我一般会先跑一遍自建 CNN 确认数据管道没问题再切 ResNet 看准确率能涨多少这样出问题容易定位。选 ResNet 版本也有讲究。源码里如果用的是resnet18或resnet50前者参数量约 1100 万后者约 2500 万。皮肤病图像分辨率通常不高resnet18 往往够用resnet50 在小数据集上反而容易过拟合。如果你看到代码里写了pretrainedTrue记得确认 torchvision 版本新版里这个参数改成了weightsResNet18_Weights.IMAGENET1K_V1写错直接报 TypeError。import torch import torch.nn as nn from torchvision import models # 方案一自建轻量 CNN class SimpleCNN(nn.Module): def __init__(self, num_classes7): super().__init__() self.features nn.Sequential( nn.Conv2d(3, 32, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(32, 64, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(64, 128, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), ) self.classifier nn.Linear(128 * 28 * 28, num_classes) def forward(self, x): x self.features(x) x x.view(x.size(0), -1) return self.classifier(x) # 方案二ResNet18 迁移学习 def build_resnet18(num_classes7, freeze_backboneTrue): model models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) if freeze_backbone: for p in model.parameters(): p.requires_grad False model.fc nn.Linear(model.fc.in_features, num_classes) return model自建 CNN 里128 * 28 * 28这个维度取决于输入图像大小如果你把输入改成 224×224经过三次池化后特征图是 28×28这个数就得跟着变写死很容易在换分辨率时崩掉。ResNet 部分freeze_backboneTrue表示只训练最后的全连接层适合数据量小的情况如果数据够多把它设成 False 做全量微调准确率通常更高但显存占用和训练时间都会上去。3.2 数据增强与归一化参数别照搬 ImageNet皮肤病图像和自然图像分布差别很大直接用 ImageNet 的均值和方差做归一化不是最优但作为起点可以接受。真正要小心的是数据增强随机翻转、旋转对皮肤病判别是合理的但颜色抖动要谨慎因为颜色本身可能是判别特征。源码里如果用了ColorJitter建议把强度调小或者去掉否则模型可能学到无关的颜色偏差。from torchvision import transforms train_tf transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(), transforms.RandomVerticalFlip(), transforms.RandomRotation(15), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])Resize到 224 是为了匹配 ResNet 的输入要求自建 CNN 可以改成更小的尺寸省显存。翻转和旋转 15 度以内是医学图像里比较安全的增强方式。Normalize那三个均值方差是 ImageNet 统计值如果你从头训练自建 CNN可以改成自己数据集的统计值但差别通常不大不必纠结。提示增强只加在训练集上验证集和测试集只用 Resize ToTensor Normalize否则评估结果会虚高。4. SVM 接在 CNN 后面特征提取与分类器替换的实操4.1 为什么要把 CNN 特征喂给 SVM这套源码里 SVM 不是独立跑的而是接在 CNN 或 ResNet 提取的特征后面。做法通常是去掉网络最后的分类层把倒数第二层的输出比如 ResNet 的 512 维向量拿出来作为 SVM 的输入。这么做的理由是CNN 擅长提取高层语义特征SVM 在小样本上做分类边界往往比直接接 Softmax 更稳。尤其是皮肤病数据类别不平衡时SVM 的类别权重参数能帮你手动调节比改损失函数直观。但这里有个坑特征提取阶段必须把模型切到eval()模式并且用torch.no_grad()包住否则 BatchNorm 和 Dropout 会在推理时乱动提取出来的特征每次都不一样。我见过有人忘了切 evalSVM 训练准确率忽高忽低查了半天以为是数据问题。import numpy as np from sklearn.svm import SVC from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline from torch.utils.data import DataLoader def extract_features(model, dataloader, devicecuda): model.eval() model.to(device) feats, labels [], [] with torch.no_grad(): for imgs, lbls in dataloader: imgs imgs.to(device) # 去掉最后的 fc 层取全局池化后的特征 x model.conv1(imgs) x model.bn1(x); x model.relu(x); x model.maxpool(x) x model.layer1(x); x model.layer2(x) x model.layer3(x); x model.layer4(x) x model.avgpool(x) x torch.flatten(x, 1) feats.append(x.cpu().numpy()) labels.append(lbls.numpy()) return np.concatenate(feats), np.concatenate(labels) # 提取后训练 SVM X_train, y_train extract_features(model, train_loader) clf make_pipeline(StandardScaler(), SVC(kernelrbf, C1.0, gammascale, class_weightbalanced)) clf.fit(X_train, y_train)extract_features里手动走了一遍 ResNet 的前向层直到avgpool和flatten这样拿到的就是 512 维特征向量。StandardScaler做标准化很关键SVM 对特征尺度敏感不标准化收敛慢且效果差。class_weightbalanced自动按类别频率反比加权缓解皮肤病数据里 NV 类样本过多的问题。C和gamma是两个核心超参C 越大边界越硬gamma 越大影响范围越小建议先用网格搜索粗调一遍。4.2 训练集测试集划分要按图像 ID 来别随机打乱医学图像有个隐蔽的坑同一个病人可能有多张不同角度的照片如果随机划分训练集和测试集同一病人的图可能同时出现在两边导致测试准确率虚高。ISIC2018 的 CSV 里没有直接给病人 ID但图像命名有一定规律常见做法是按图像 ID 排序后取前 80% 做训练。源码里如果用了train_test_split(shuffleTrue)你得留个心眼评估结果可能偏乐观。from sklearn.model_selection import train_test_split # 按索引顺序切分避免同一来源图像泄漏 indices np.arange(len(X_train)) train_idx, test_idx train_test_split(indices, test_size0.2, shuffleFalse) X_tr, X_te X_train[train_idx], X_train[test_idx] y_tr, y_te y_train[train_idx], y_train[test_idx]shuffleFalse是这里的关键它保证切分是按原始顺序来的减少同源泄漏。代价是如果数据本身按类别排序可能导致训练集类别不全所以切分前最好先确认 CSV 里的顺序是不是随机的。如果不是就得先手动打乱一次再切但打乱要以病人为单位这个需要额外信息没有的话只能接受一定偏差。注意SVM 在特征维度高于样本数时容易过拟合如果提取的特征是 512 维而训练样本只有几百建议先做 PCA 降维再喂给 SVM。5. 避坑与排查跑不起来、准确率异常、Web 端报错怎么查5.1 现象训练 loss 一直不降准确率停在 1/7 左右原因通常是标签没对齐或者输入数据全是零。先检查 CSV 读取后的label列分布如果某一类占了 90% 以上模型会直接摆烂全预测那一类。再检查图像读取路径PIL 读进来如果是单通道灰度图而模型第一层是Conv2d(3, ...)就会报维度错误或者被静默转成三通道但数值异常。解决办法是打印一个 batch 的imgs.mean()和lbls看看均值接近 0 或者标签全一样就往数据管道上查。5.2 现象ResNet 加载预训练权重时报 URL 错误或下载超时原因是 torchvision 的权重文件存在境外服务器网络不稳时下载会中断。解决办法是手动下载对应的.pth文件放到~/.cache/torch/hub/checkpoints/目录下文件名要和 torchvision 期望的一致。或者改用weightsNone从头训练但准确率会明显下降。这个坑在校园网环境下尤其常见提前下好权重能省很多时间。5.3 现象SVM 训练报「特征维度不一致」原因是 CNN 特征提取时输入图像尺寸不统一有的经过 Resize 有的没经过导致flatten后的维度对不上。检查 DataLoader 的 transform 是否所有数据集都用了同一个Resize尺寸。另外如果模型在 CPU 上提取特征而 DataLoader 用了pin_memoryTrue也可能出奇怪的维度错误把pin_memory关掉试试。5.4 现象Flask 页面能打开但注册登录报 500源码里的user_register.html和success.html是前端模板后端通常用 SQLite 或内存字典存用户。报 500 多半是数据库文件路径不对或者表没建。先看 Flask 启动日志里的 traceback如果是no such table就找到建表脚本先跑一遍。另外baseCSS.css路径如果写的是绝对路径换机器就会 404改成url_for(static, filenamebaseCSS.css)更稳。5.5 现象测试集准确率很高但实际预测全是同一类这是类别不平衡加评估指标选错的典型翻车。ISIC2018 里 NV 类占比很高如果只看 accuracy全预测 NV 也能有六七十分。解决办法是看混淆矩阵和每类的 F1SVM 那边把class_weightbalanced加上CNN 那边用带权重的 CrossEntropyLoss。别被一个漂亮的 accuracy 数字骗了医学场景里漏诊的代价远大于误诊。6. 把 ResNet 特征和 SVM 串成一条推理流水线一个可复用的验证技巧前面几章把数据、模型、SVM 都拆开了最后一章说一个我实际用下来最省事的验证习惯把「特征提取 SVM 预测」封装成一个独立函数每次改完模型或预处理先拿几十张图跑一遍端到端确认输出维度和类别映射没错再上全量训练。这个习惯帮我省过至少两次通宵排查——一次是 ResNet 的fc层换掉后忘了同步改特征维度一次是 SVM 的classes_顺序和 CSV 列顺序不一致导致预测标签整体错位。def predict_pipeline(img_path, cnn_model, svm_clf, devicecuda): 端到端单张图 - CNN 特征 - SVM 类别 cnn_model.eval() 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]) ]) img Image.open(img_path).convert(RGB) x tf(img).unsqueeze(0).to(device) with torch.no_grad(): f cnn_model.conv1(x) f cnn_model.bn1(f); f cnn_model.relu(f); f cnn_model.maxpool(f) f cnn_model.layer1(f); f cnn_model.layer2(f) f cnn_model.layer3(f); f cnn_model.layer4(f) f cnn_model.avgpool(f) f torch.flatten(f, 1).cpu().numpy() pred svm_clf.predict(f)[0] return pred # 快速抽检 20 张看预测分布是否合理 import glob, collections sample_paths glob.glob(images/*.jpg)[:20] preds [predict_pipeline(p, model, clf) for p in sample_paths] print(collections.Counter(preds))这个函数把预处理、特征提取、SVM 预测串成一条线unsqueeze(0)是给单张图补上 batch 维度少了这步会报维度错误。最后用Counter看预测分布如果 20 张全预测成同一类说明 SVM 的class_weight或者特征标准化有问题不用等全量评估就能发现。我一般还会把svm_clf.classes_打印出来和 CSV 的label_cols顺序对一遍确认没有错位。参数上Resize尺寸必须和训练时一致Normalize的均值方差也要一致否则特征分布偏移SVM 直接失效。如果你换了 ResNet 版本比如从 18 换成 50特征维度从 512 变成 2048SVM 得重新训练不能复用旧模型。这个流水线跑通之后再接到 Flask 的预测接口里前端上传图片就能直接返回类别整个课程设计的演示闭环就完成了。从那以后我每次改完特征提取部分都强制先跑一遍这个 20 张的抽检确认预测分布正常再往下走。希望帮到你。本文还有配套的精品资源点击获取
返回列表