简介:这份资源是面向计算机相关专业学生与初学者的深度学习与计算机视觉课程设计项目,以Python实现新冠肺炎预测为核心任务,适合人工智能、通信工程、自动化等方向的学生用于课设、毕设、作业或项目立项演示。压缩包共9个文件,约7KB,包含2个Python源码文件、4个XML配置、1个Markdown说明文档及工程配置文件,源码均已测试运行成功,答辩评审平均分达96分。项目围绕图像加载与深度学习模型展开,读者可参考完整代码结构、模型实现思路与运行配置,快速理解计算机视觉任务从数据读取到预测输出的整体流程,也可在此基础上修改扩展实现其他功能。目前已有382人学习下载,下载后建议先阅读README.md了解项目说明,适合小白进阶学习与课程实践参考。
1. 从一次课程设计翻车说起:深度学习与计算机视觉到底怎么落地
带过几届课程设计,最常听到的抱怨是“代码跑不起来,数据集找不到,文档看不懂”。这次要聊的题目是深度学习与计算机视觉课程设计,基于 Python 实现新冠肺炎预测,附带源代码和文档说明。它本质上是一个典型的计算机视觉二分类任务:输入胸部 X 光片,输出正常或肺炎(含新冠)的概率。适合正在做计算机视觉大作业的学生、想补一个完整项目进简历的转行者,以及需要快速搭出医学影像分类基线的工程师。很多人一上来就堆 ResNet,结果显存爆了、准确率还上不去,问题往往出在数据划分和预处理,而不是模型本身。这一章先把任务边界划清楚,后面几章再拆数据、模型、训练和排错。
2. 数据准备与预处理:胸部 X 光片怎么变成模型能吃的张量
2.1 数据集从哪来、目录怎么组织
公开的胸部 X 光片数据集常见做法是采用 Kaggle 上的 Chest X-Ray Images (Pneumonia) 或 COVID-19 Radiography Database。前者约 5800 张,分 train/val/test,每类下再分 NORMAL 和 PNEUMONIA;后者类别更多,含 COVID、Lung_Opacity、Normal、Viral Pneumonia。课程设计里我一般选前者,因为目录结构规整,适合新手直接跑通。下载后不要急着改文件名,先按下面结构放好:
data/ train/ NORMAL/ PNEUMONIA/ val/ NORMAL/ PNEUMONIA/ test/ NORMAL/ PNEUMONIA/如果拿到的是 COVID-19 Radiography Database,类别名换成 COVID、Normal、Viral Pneumonia、Lung_Opacity 即可。注意:不要把所有图片混在一个文件夹里再用文件名前缀区分,那样 DataLoader 的 ImageFolder 会直接报错。
2.2 用 Python 做尺寸统一与归一化
X 光片原始尺寸参差不齐,常见 1024×1024 到 2000×2000 不等。直接缩到 224×224 会丢细节,但课程设计阶段这是性价比最高的选择。下面是最小可运行预处理代码:
import torch from torchvision import datasets, transforms from torch.utils.data import DataLoader # 训练集做轻微增强,验证测试集只做缩放和归一化 train_tf = transforms.Compose([ transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p=0.5), transforms.RandomRotation(10), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) 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]) ]) train_ds = datasets.ImageFolder('data/train', transform=train_tf) val_ds = datasets.ImageFolder('data/val', transform=eval_tf) test_ds = datasets.ImageFolder('data/test', transform=eval_tf) train_loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=4) val_loader = DataLoader(val_ds, batch_size=32, shuffle=False, num_workers=4) test_loader = DataLoader(test_ds, batch_size=32, shuffle=False, num_workers=4) print(train_ds.classes, len(train_ds))逻辑说明:ImageFolder 按子文件夹名自动生成标签,所以目录结构必须严格。Normalize 用的 mean/std 是 ImageNet 统计值,因为后面要用预训练权重,保持一致能避免分布偏移。参数说明:batch_size 在 8GB 显存下 32 比较稳,显存小就降到 16;num_workers 在 Windows 上如果报错就改成 0;RandomRotation 只给 10 度,医学影像旋转太大会引入不真实样本。
2.3 类别不平衡的处理
PNEUMONIA 通常比 NORMAL 多,比例可能到 3:1。不处理的话模型会偏向多数类,表现为准确率虚高但召回率低。常见做法有两种:一是 WeightedRandomSampler 过采样少数类,二是在损失函数里加 class_weight。课程设计里我倾向后者,改动小、可解释性强:
from collections import Counter import torch.nn as nn counts = Counter([label for _, label in train_ds]) total = sum(counts.values()) weights = torch.tensor([total / counts[i] for i in range(len(counts))], dtype=torch.float) criterion = nn.CrossEntropyLoss(weight=weights)这段代码先统计每类样本数,再按总数除以类频得到权重。少数类权重高,梯度更新时会被放大。注意权重别设得太极端,否则训练震荡。如果两类比例超过 5:1,建议先检查数据标注是否出错,而不是硬调权重。
3. 模型选型与训练:从 ResNet18 到迁移学习的最小闭环
3.1 为什么课程设计首选 ResNet18 而不是自己搭 CNN
自己搭三层卷积也能跑,但准确率通常卡在 70% 上下,而且调参玄学成分大。ResNet18 有 ImageNet 预训练权重,特征提取能力强,224 输入下参数量约 1100 万,单卡 8GB 完全够用。更重要的是,它的结构简单、文档多,出问题容易搜到答案。常见做法是冻结前面层、只训练 layer4 和全连接层,这样小数据集上不容易过拟合。
import torchvision.models as models import torch.nn as nn model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1) for name, param in model.named_parameters(): if 'layer4' not in name and 'fc' not in name: param.requires_grad = False model.fc = nn.Linear(model.fc.in_features, 2) model = model.cuda()逻辑说明:named_parameters 遍历所有参数,名字里不含 layer4 和 fc 的冻结梯度。这样反向传播只更新高层特征和分类头,训练快且省显存。参数说明:weights 指定预训练版本,不要用 pretrained=True 的老写法,新版本 torchvision 会警告。如果显存够,也可以解冻 layer3,但课程设计阶段没必要。
3.2 训练循环与关键超参
训练循环本身不复杂,关键是监控验证集损失和召回率。下面是最小训练脚本:
import torch.optim as optim from sklearn.metrics import recall_score optimizer = optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-4) scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=7, gamma=0.1) for epoch in range(15): model.train() for imgs, labels in train_loader: imgs, labels = imgs.cuda(), labels.cuda() optimizer.zero_grad() loss = criterion(model(imgs), labels) loss.backward() optimizer.step() scheduler.step() model.eval() preds, trues = [], [] with torch.no_grad(): for imgs, labels in val_loader: imgs = imgs.cuda() out = model(imgs).argmax(dim=1).cpu().numpy() preds.extend(out); trues.extend(labels.numpy()) print(f'epoch {epoch} recall {recall_score(trues, preds, average="macro"):.4f}')逻辑说明:Adam 只传需要梯度的参数,避免更新冻结层。StepLR 每 7 个 epoch 降一次学习率,防止后期震荡。验证阶段用 argmax 取预测类,再算 macro 召回率。参数说明:lr=1e-4 是迁移学习常用值,太大容易破坏预训练特征;epoch 15 在 5000 张图上大约 20 分钟,够收敛。如果验证召回率连续 3 轮不升,就提前停。
3.3 评估指标别只看准确率
医学影像里准确率是最容易骗人的指标。测试集上如果 NORMAL 占 30%、PNEUMONIA 占 70%,全预测 PNEUMONIA 也有 70% 准确率。必须同时看混淆矩阵、召回率和 AUC。下面这段代码输出完整报告:
from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score import numpy as np model.eval() probs, trues = [], [] with torch.no_grad(): for imgs, labels in test_loader: imgs = imgs.cuda() out = torch.softmax(model(imgs), dim=1)[:, 1].cpu().numpy() probs.extend(out); trues.extend(labels.numpy()) preds = (np.array(probs) > 0.5).astype(int) print(confusion_matrix(trues, preds)) print(classification_report(trues, preds, target_names=test_ds.classes)) print('AUC', roc_auc_score(trues, probs))逻辑说明:softmax 取第 1 类概率作为阳性得分,阈值 0.5 先跑基线。classification_report 会给出每类精确率、召回率和 F1。参数说明:阈值可以根据业务调,如果更怕漏诊就把阈值降到 0.3,召回率升但误报增。AUC 不受阈值影响,适合横向对比模型。
4. 避坑与排查:课程设计里最容易翻车的 5 个地方
4.1 现象:训练 loss 不降,准确率卡在 50%
原因:标签和输入没对齐,或者归一化用错。ImageFolder 按文件夹名排序生成标签,如果文件夹名是 COVID、Normal,标签顺序是 COVID=0、Normal=1,但你以为 Normal=0,后面算指标就全反了。解决:打印 train_ds.classes 确认顺序,再检查 classification_report 的 target_names 是否对应。
4.2 现象:验证集准确率比训练集高很多
原因:验证集太小或者和训练集重复。常见于手动复制文件时把 test 的图也拷进了 train。解决:用 md5 去重,或者直接重新下载数据集按官方划分走。另外验证集至少每类 100 张,否则指标波动大。
4.3 现象:CUDA out of memory
原因:batch_size 太大,或者没加 torch.no_grad() 导致验证阶段也建计算图。解决:先把 batch_size 降到 8 跑通,再逐步加;验证和测试循环必须包在 with torch.no_grad() 里。如果还爆,就把模型输入从 224 降到 128,但准确率会掉几个点。
4.4 现象:Windows 上 num_workers 报 BrokenPipeError
原因:Windows 的 DataLoader 多进程实现和 Linux 不同,容易在脚本没加 ifname== 'main' 时崩。解决:把 num_workers 设为 0,或者把训练代码包进 main 函数。这不是代码错,是平台差异,别在这上面耗太久。
4.5 现象:测试集 AUC 很高但临床不可用
原因:数据集本身太干净,很多 X 光片来自同一台设备、同一批患者,模型学到了设备特征而不是病灶。解决:课程设计阶段可以在报告里说明这个局限,不要宣称能直接上临床。如果想让结果更可信,做一次跨数据集测试,比如用 COVID-19 Radiography 训练、用 Chest X-Ray 测试,AUC 通常会掉 10 个点以上,这才是真实泛化能力。
5. 进阶技巧:用 Grad-CAM 让模型告诉你它在看哪里
课程设计只报准确率太单薄,加一张热力图能显著提升文档说服力。Grad-CAM 的原理是取目标层梯度对特征图的加权和,高亮模型关注区域。下面是最小实现:
import cv2 import numpy as np import torch.nn.functional as F class GradCAM: def __init__(self, model, target_layer): self.model = model self.grads = None self.acts = None target_layer.register_forward_hook(self.save_act) target_layer.register_full_backward_hook(self.save_grad) def save_act(self, module, input, output): self.acts = output.detach() def save_grad(self, module, grad_in, grad_out): self.grads = grad_out[0].detach() def __call__(self, img_tensor, class_idx): out = self.model(img_tensor) self.model.zero_grad() out[0, class_idx].backward() weights = self.grads.mean(dim=(2, 3), keepdim=True) cam = F.relu((weights * self.acts).sum(dim=1, keepdim=True)) cam = F.interpolate(cam, size=(224, 224), mode='bilinear', align_corners=False) cam = cam.squeeze().cpu().numpy() cam = (cam - cam.min()) / (cam.max() + 1e-8) return cam cam_gen = GradCAM(model, model.layer4[-1]) img, label = test_ds[0] cam = cam_gen(img.unsqueeze(0).cuda(), class_idx=1) heatmap = cv2.applyColorMap(np.uint8(255 * cam), cv2.COLORMAP_JET)逻辑说明:forward hook 存特征图,backward hook 存梯度。权重是梯度在空间维度上的均值,乘回特征图再 ReLU 去掉负响应。最后归一化并上色。参数说明:target_layer 选 layer4 最后一层,分辨率 7×7,插值回 224 后比较粗糙但够用。class_idx=1 表示看阳性类关注区域。跑完后把热力图和原图叠加,如果高亮在肺野外侧,说明模型可能学到了无关特征,这时候要回头检查数据。
我自己的习惯是每训完一个模型,先抽 10 张测试图跑 Grad-CAM,肉眼过一遍。有一次热力图全集中在图像边缘,查了半天发现是预处理时 padding 填了黑边,模型把黑边当成了信号。这个检查花不了五分钟,但能省掉后面反复调参的后悔药。希望帮到你。
本文还有配套的精品资源,点击获取