医学深度学习毕设最常听到的一句话就是“没思路”。模型选哪个、数据集去哪找、训练环境怎么配、结果怎么评估,每一步都可能卡住人。这篇文章直接把ResNet、UNet、DeepLabV3+、YOLOv5四个经典模型串成一个医学图像多任务实战基线,用PyTorch完整走一遍“数据准备 → 环境搭建 → 模型训练 → 效果评估”的流程,覆盖图像分类、语义分割、目标检测三类最常见任务。如果你正在做医学影像相关毕业设计,这篇可以当一份能直接照着操作的底稿。
先给结论:ResNet 管分类,UNet 管分割,DeepLabV3+ 管语义分割,YOLOv5 管目标检测。四个模型在 PyTorch 生态里都有成熟实现,不需要从零写网络结构,真正花时间的是数据格式、训练循环、评估指标和显存控制。标题里写的 DeepLabV3++ 其实准确命名是DeepLabV3+,实际动手时不要搜 V3++,搜 DeepLabV3+ 才能找到模型代码和预训练权重。
本文会依次完成四件事:搭建可复用的 PyTorch 医学深度学习环境,组织三类数据的目录结构,分别微调四个模型,最后讲清楚怎么用nvidia-smi观察资源占用、怎么在毕设答辩前把实验记录整理成体系。
1. 核心能力速览
| 模型 | 任务类型 | 典型医学场景 | 输入数据 | 输出结果 |
|---|---|---|---|---|
| ResNet | 图像分类 | 肺炎 X 光分类、病理图像分级、视网膜病变分级 | 2D 图像 | 类别概率 |
| UNet | 图像分割 | CT 器官分割、皮肤病变分割、细胞膜分割 | 图像 + Mask | 像素级前景/背景 |
| DeepLabV3+ | 语义分割 | 大范围结构分割、多类别组织分割 | 图像 + Mask | 多类别像素标签 |
| YOLOv5 | 目标检测 | 肺结节检测、细胞检测、骨折定位 | 图像 + 标注框 | 边界框 + 类别 + 置信度 |
这套组合覆盖了医学影像论文里最常见的三个问题:它是什么、病灶在哪一块区域、图像里有几个目标实例。毕设如果用单一模型做不出工作量和对比维度,用多任务思想串起四个模型,天然就有横向对比、消融实验和不同任务间的效果讨论空间。
2. 模型选型思路与医学场景适配
2.1 ResNet:从分类任务切入
ResNet 是卷积神经网络里使用频率最高的主干网络之一。它通过残差连接解决了深层网络梯度消失问题,在医学图像分类场景中,通常直接使用在 ImageNet 上预训练的 ResNet50 或 ResNet101,然后把最后一层全连接替换成自己的类别数做微调。医学图像和自然图像有明显差异,但预训练权重迁移后在中小规模医学数据集上通常比随机初始化收敛更快、准确率更高。
适用数据示例:胸部 X 光肺炎分类、皮肤镜图像良恶性分类、病理切片图像分级。这类任务的数据集往往只有数千张图像,完全从头训练 ResNet 容易过拟合,微调预训练模型是更稳妥的做法。
2.2 UNet:小样本分割的最稳选择
UNet 是医学图像分割领域绕不开的结构,编码器逐级提取特征,解码器通过跳跃连接恢复空间细节,在标注样本有限的情况下表现稳定。医学图像分割标注成本很高,UNet 这种 U 型结构加上数据增强,往往用几十张到几百张 Mask 就能训练出可接受的基线。
适用数据示例:BraTS 脑肿瘤分割、ISIC 皮肤病变分割、腹部 CT 器官分割。标注格式通常是 PNG 二值图或者多类别 PNG,训练时 Dice Loss 与交叉熵损失组合使用。
2.3 DeepLabV3+:多类别语义分割的强基线
DeepLabV3+ 通过空洞卷积在不损失分辨率的情况下扩大感受野,配合编码器-解码器结构恢复物体边界细节。相比 UNet,DeepLabV3+ 在多类别语义分割任务上通常有更强的上下文建模能力。torchvision 直接内置了deeplabv3_resnet50和deeplabv3_resnet101,加载预训练权重后替换分类头即可。
适用数据示例:多器官分割、心脏结构分割、包含多种组织类型的病理图像分割。当 Mask 的类别数超过 3 类且区域边界复杂时,优先考虑 DeepLabV3+。
2.4 YOLOv5:检测任务的工程化标杆
YOLOv5 是 Ultralytics 维护的目标检测框架,训练、验证、导出、推理链路非常完整。医学场景里如果任务是“在图像中找出所有肺结节并标出位置”,分类模型做不到,分割模型做太细,检测模型是更合适的中间粒度。YOLOv5 的数据格式是每个图片对应一个 txt 标签文件,每一行表示class x_center y_center width height,坐标值归一化到 0~1。
适用数据示例:LIDC-IDRI 肺结节检测、胸片中的异常目标检测、病理图像中的细胞核检测。
3. 医学数据合规与使用边界
医学深度学习毕设最容易忽略的不是模型,而是数据合规。
优先使用公开数据集。常见选择包括:
- 肺炎分类:ChestX-ray2017、RSNA Pneumonia Detection Challenge
- 皮肤病变分割:ISIC 2018 Challenge
- 脑肿瘤分割:BraTS 数据集
- 肺结节检测:LIDC-IDRI
- 病理图像:Camelyon16、Camelyon17
不要直接用未经授权的医院临床数据做训练和演示。医学图像涉及患者隐私,即使是脱敏数据,也要确认是否允许用于学术研究。毕设答辩时导师和评委大概率会问“数据来源是否合规、是否涉及隐私”,提前准备好数据授权截图和预处理说明,这个问题能顺利回答。
另一个边界是成果定性。毕设训练的模型无论准确率多高,都不能写成“可用于临床诊断”,只能表述为“在公开数据集上的学术研究基线”“辅助诊断方法的可行性验证”。医学AI落地需要临床验证和注册审批,这是原则问题,不能模糊处理。
如果论文里需要展示真实患者影像,必须对面部、姓名、病案号等信息完成脱敏,或者直接使用公开数据集的图。涉及生成、分割、检测结果的展示,也要确认是否包含可识别个人的特征。
4. PyTorch 环境搭建
4.1 环境版本选择
医学图像任务推荐使用 Anaconda 管理 Python 环境。PyTorch 安装命令和版本对应关系变化较快,建议以 PyTorch 官网的安装命令为准。这里给出一套通用流程,Python 3.10 + PyTorch 2.x + CUDA 11.8 的组合在近两年多个版本中兼容性较好。
conda create -n medical_dl python=3.10 -y conda activate medical_dl先确认显卡驱动支持的最高 CUDA 版本:
nvidia-sminvidia-smi右上角会显示驱动版本及支持的 CUDA 版本。注意,驱动支持的 CUDA 版本是上限,实际安装的 PyTorch CUDA 运行时可以和它不一致,只要不高于驱动支持版本即可。例如驱动支持 12.1,就可以安装 cu118 或 cu121 的 PyTorch。
以 CUDA 11.8 为例,安装命令是:
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118如果只有 CPU 环境,或者电脑没有 NVIDIA 显卡:
pip install torch torchvision安装完成后验证环境:
import torch import torchvision print("PyTorch 版本:", torch.__version__) print("torchvision 版本:", torchvision.__version__) print("CUDA 是否可用:", torch.cuda.is_available()) print("GPU 名称:", torch.cuda.get_device_name(0) if torch.cuda.is_available() else "未检测到 GPU")如果torch.cuda.is_available()返回False,不要急着装别的版本,先检查安装的是不是 CPU 版,再检查 NVIDIA 驱动是否正常安装。用pip list | grep torch查看当前 torch 构建版本,输出中如果包含+cpu就说明装成了 CPU 版。
4.2 安装图像处理与可视化依赖
pip install numpy opencv-python pillow matplotlib scikit-learn tqdm tensorboard albumentationsalbumentations是做医学图像增强非常好用的库,支持掩膜同步变换,UNet 和 DeepLabV3+ 训练时用起来很顺手。可视化部分用matplotlib和tensorboard足够,不需要额外安装重型前端。
如果要做 YOLOv5,单独安装 Ultralytics 生态:
pip install ultralytics也可以使用 YOLOv5 官方仓库:
git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt两种方式都能训练 YOLOv5。区别是ultralytics库的接口更新,官方仓库更贴近论文和原始训练脚本。毕设建议用官方仓库,因为训练日志、超参数配置、可视化脚本都是现成的,方便直接截图放进论文。
5. 数据集组织与预处理
多任务项目的数据目录要在一开始就设计好,否则后续写训练脚本会非常混乱。推荐结构如下:
medical_dl_project/ ├── data/ │ ├── classify/ │ │ ├── train/ │ │ │ ├── normal/ │ │ │ └── pneumonia/ │ │ ├── val/ │ │ └── test/ │ ├── segment/ │ │ ├── images/ │ │ ├── masks/ │ │ └── train_val_split.csv │ ├── detect/ │ │ ├── images/ │ │ │ ├── train/ │ │ │ └── val/ │ │ ├── labels/ │ │ │ ├── train/ │ │ │ └── val/ │ │ └── medical.yaml ├── checkpoints/ ├── runs/ └── scripts/分类任务直接用文件夹名作为类别标签,用torchvision.datasets.ImageFolder读取最简单。分割任务需要手动维护图片和 Mask 的对应关系,建议用 CSV 记录每一行的图像路径和掩膜路径。检测任务必须按照 YOLO 格式组织,图片和 txt 同名且在不同目录下。
使用公开数据集时先做一次基本的格式检查,把训练集、验证集、测试集划分固定下来并保存划分结果,后续每次训练都用同一份划分,实验结果才有可比性。千万不要在每次运行时重新随机划分。
6. ResNet 分类任务实战
6.1 加载预训练模型
用 torchvision 加载 ResNet50,官方推荐的写法是:
import torch import torch.nn as nn import torchvision from torchvision import transforms # 加载 ImageNet 预训练权重 model = torchvision.models.resnet50( weights=torchvision.models.ResNet50_Weights.IMAGENET1K_V2 ) # 替换全连接层,适配二分类任务 num_classes = 2 in_features = model.fc.in_features model.fc = nn.Linear(in_features, num_classes)如果用pretrained=True老参数,会收到 PyTorch 的弃用提示。新代码建议直接使用weights参数。
6.2 数据加载与增强
医学图像分类的数据增强不要做得太激进,否则会把重要的病理特征增强掉。常用组合是随机水平翻转、小幅旋转、归一化。
train_transform = 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]) ]) val_transform = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])注意,医学图像如果本身是灰度图,torchvision读入后通常是单通道,需要转换成三通道后再输入 ResNet。可以在__getitem__里用img.convert("RGB")处理。
6.3 训练循环
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) criterion = nn.CrossEntropyLoss() optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=30) for epoch in range(30): model.train() running_loss = 0.0 correct = 0 total = 0 for images, labels in train_loader: images, labels = images.to(device), labels.to(device) outputs = model(images) loss = criterion(outputs, labels) optimizer.zero_grad() loss.backward() optimizer.step() running_loss += loss.item() _, predicted = torch.max(outputs, 1) total += labels.size(0) correct += (predicted == labels).sum().item() scheduler.step() train_acc = 100.0 * correct / total print(f"Epoch [{epoch+1}/30] Loss: {running_loss/len(train_loader):.4f} Acc: {train_acc:.2f}%")6.4 评估与可视化
验证阶段计算准确率、精确率、召回率、F1,并输出混淆矩阵:
from sklearn.metrics import classification_report, confusion_matrix import numpy as np model.eval() all_preds = [] all_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) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) print(classification_report(all_labels, all_preds, target_names=["normal", "pneumonia"])) print(confusion_matrix(all_labels, all_preds))分类任务更容易出现的问题不是准确率不够,而是类别不均衡。医学数据集中阳性样本往往远少于阴性样本,这时不能只用准确率评估,要同时看召回率和精确率。如果发现少数类被大量误判,需要增加少数类的权重或者对少数类做针对性增强。
7. UNet 分割任务实战
7.1 模型定义
UNet 网络结构不复杂,但手写代码较长。建议直接用segmentation_models_pytorch库,它封装了 UNet 和 DeepLabV3+ 等多种分割网络,使用非常方便:
pip install segmentation-models-pytorchimport segmentation_models_pytorch as smp model = smp.Unet( encoder_name="resnet50", encoder_weights="imagenet", in_channels=3, classes=1, # 二值分割时 classes=1 activation=None # 输出 logits )如果二值分割想输出更稳定的概率,可以设置activation="sigmoid"。但通常建议网络输出 logits,在损失函数里结合BCEWithLogitsLoss,这样数值更稳定。
经典实现不建议只看库调用。毕设答辩很容易被问到“UNet 的跳跃连接在代码里怎么体现”,所以至少要能画出 UNet 结构图,并理解编码器下采样四次、解码器逐步恢复分辨率的过程。推荐把 UNet 原论文里的结构图重新画一版放进论文,并说明自己的改进点。
7.2 数据加载与掩膜处理
数据集目录里images和masks文件一一对应。读取后做尺寸统一和归一化:
import cv2 import numpy as np from torch.utils.data import Dataset class SegmentationDataset(Dataset): def __init__(self, image_paths, mask_paths, transform=None): self.image_paths = image_paths self.mask_paths = mask_paths self.transform = transform def __len__(self): return len(self.image_paths) def __getitem__(self, idx): image = cv2.imread(self.image_paths[idx]) image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) mask = cv2.imread(self.mask_paths[idx], cv2.IMREAD_GRAYSCALE) mask = (mask > 0).astype(np.float32) # 二值化 if self.transform is not None: transformed = self.transform(image=image, mask=mask) image = transformed["image"] mask = transformed["mask"] image = torch.from_numpy(image.transpose(2, 0, 1)).float() / 255.0 mask = torch.from_numpy(mask).unsqueeze(0).float() return image, mask注意:如果掩膜是多类别 PNG,灰度值不是 0 和 255,而是 0、1、2……不同类别,不能简单二值化。
7.3 损失函数与训练
UNet 二值分割经典组合是 Dice Loss + BCE Loss:
import torch import torch.nn as nn def dice_loss(pred, target, smooth=1.0): pred = torch.sigmoid(pred) pred = pred.view(pred.size(0), -1) target = target.view(target.size(0), -1) intersection = (pred * target).sum(dim=1) return 1 - ((2.0 * intersection + smooth) / (pred.sum(dim=1) + target.sum(dim=1) + smooth)) bce = nn.BCEWithLogitsLoss()每一步训练取两种损失的加权和:
for images, masks in train_loader: images, masks = images.to(device), masks.to(device) logits = model(images) loss_bce = bce(logits, masks) loss_dice = dice_loss(logits, masks) loss = loss_bce + loss_dice训练时推荐每轮验证集上计算 Dice 系数和 IoU,不要只看损失下降。分割任务常见问题是预测全部为背景、边界粗糙、小目标漏检。如果预测全为背景,先检查掩膜是否加载正确;如果边界粗糙,考虑降低学习率或增加损失函数中的边界项。
7.4 推理与结果保存
model.eval() with torch.no_grad(): for images, masks in val_loader: images = images.to(device) logits = model(images) probs = torch.sigmoid(logits) preds = (probs > 0.5).float() # 保存叠加可视化 for i in range(images.size(0)): pred_np = preds[i, 0].cpu().numpy() * 255 cv2.imwrite(f"results/unet_pred_{i}.png", pred_np)保存预测 Mask 时建议把原图、真实 Mask、预测 Mask 拼接成一张图,论文里的可视化对比图可以这样生成。
8. DeepLabV3+ 语义分割实战
8.1 加载预训练模型
torchvision 直接提供 DeepLabV3+ 的 ResNet 版本:
import torchvision model = torchvision.models.segmentation.deeplabv3_resnet50( weights=torchvision.models.segmentation.DeepLabV3_ResNet50_Weights.COCO_WITH_VOC_LABELS_V1 ) # 替换分类头,多类别分割 num_classes = 4 model.classifier[4] = torch.nn.Conv2d(256, num_classes, kernel_size=(1, 1))DeepLabV3+ 的classifier是一个 Sequential,最后一层是 1x1 卷积,把通道数变成 VOC 的 21 类,替换成自己的类别数即可。如果图像输入尺寸较大,可以只替换分类头而保留整个骨干网络。
8.2 多类别分割的训练要点
和 UNet 不同,多类别分割的掩膜是(H, W)的整数标签图,每个像素值范围是 0 到num_classes-1。数据加载时不能做二值化,要转成长整型张量:
mask = cv2.imread(self.mask_paths[idx], cv2.IMREAD_UNCHANGED) mask = torch.from_numpy(mask).long()损失函数直接用nn.CrossEntropyLoss():
criterion = nn.CrossEntropyLoss(ignore_index=255)ignore_index=255很关键。很多公开数据集的标注图边缘区域会填充 255,表示“不参与训练的未标注区域”,不忽略它会导致损失异常波动。
训练代码主体和 UNet 类似,区别是输出形状。DeepLabV3+ 原始输出是一个 OrderedDict,包含out和aux两个键,其中aux是辅助损失输出。训练时可以直接取out计算损失,简化处理。如果你的显卡显存够大,也可以同时把辅助损失加进来,辅助损失权重通常取 0.4。
8.3 评估与可视化
多类别分割的评估要分三个层面:全局像素准确率、每个类别的 IoU、平均 IoU。医学分割论文里最常用的是 mIoU 和类别 IoU。
def calculate_iou(pred, target, num_classes): ious = [] pred = pred.view(-1) target = target.view(-1) for cls in range(num_classes): pred_inds = (pred == cls) target_inds = (target == cls) intersection = (pred_inds & target_inds).sum().float() union = (pred_inds | target_inds).sum().float() if union == 0: ious.append(float("nan")) else: ious.append((intersection / union).item()) return ious多类别分割在类别不均衡时 mIoU 会被占比大的类别主导,论文里应该同时给出每个类别的 IoU 和 Dice,让读者看到少数类的真实表现。
DeepLabV3+ 相对 UNet 的优势场景是多类别复杂结构,但参数量更大、显存占用更高。实际入手时先用小分辨率测试,比如 256x256 或 320x320,跑通后再提升到 512。
9. YOLOv5 目标检测实战
9.1 数据格式准备
YOLOv5 使用 YOLO 格式标注。每个 txt 文件的每一行是:
class x_center y_center width height坐标都是归一化到 0~1 的浮点数。例如一张 1000x1000 图像中,一个边界框左上角在 (200, 300)、右下角在 (500, 700),对应的归一化值是:
- x_center = (200 + 500) / 2 / 1000 = 0.35
- y_center = (300 + 700) / 2 / 1000 = 0.5
- width = (500 - 200) / 1000 = 0.3
- height = (700 - 300) / 1000 = 0.4
最终 txt 内容是:
0 0.35 0.5 0.3 0.4如果你的原始标注是 COCO 格式或者 VOC XML 格式,YOLOv5 官方仓库里提供了转换脚本,也可以自己写一个简单的转换函数。
9.2 配置 medical.yaml
train: data/detect/images/train val: data/detect/images/val nc: 1 names: ["nodule"]路径必须是 YOLOv5 项目根目录下的相对路径,否则训练会报数据加载错误。nc是类别数,names是类别名称,要和 txt 标签中的 class id 顺序一致。
9.3 训练命令
python train.py --img 640 --batch 16 --epochs 100 \ --data medical.yaml --weights yolov5s.pt --device 0参数含义:
--img:输入图像尺寸,医学图像如果目标是小尺寸病灶,建议用 640 或 1280,尺寸越大越能检测小目标,但显存占用更高--batch:批大小,结合显存调整--weights:预训练权重,yolov5s.pt会自动下载--device:GPU 编号,CPU 训练填cpu
如果在 Colab 或本地首次运行时yolov5s.pt下载慢,可以提前手动下载后放到项目根目录,再指定路径加载。
9.4 验证与推理
python val.py --data medical.yaml --weights runs/train/exp/weights/best.ptpython detect.py --source data/detect/images/test --weights runs/train/exp/weights/best.pt --conf-thres 0.25detect.py会自动把带标注框的结果图保存到runs/detect目录。医学目标检测建议把--conf-thres设低一点,例如 0.1 或 0.15,防止低置信度的小病灶被漏检。代价是假阳性会增加,需要结合 mAP 曲线和具体医学任务来做取舍。
9.5 指标解读
YOLOv5 训练结束会输出 mAP50、mAP50-95、Precision、Recall。医学检测场景中 Recall 通常比 mAP 更重要,因为漏检一个病灶比误检更危险。答辩时如果被问“为什么准确率低”,一个合理的解释是“在设定的低置信度阈值下提升了召回率以减少漏检,后续通过 NMS 参数调整降低假阳性”。
10. 多任务训练策略与资源占用观察
10.1 批量任务与多模型管理
毕设四个模型不建议同时训练。更稳妥的策略是串行训练,先训练 ResNet 分类,再训练 UNet,再 DeepLabV3+,最后 YOLOv5。每一次训练保留独立的 checkpoint 目录、训练日志、预测结果,这样论文里的实验记录才完整。
给每个模型单独建目录:
checkpoints/ ├── resnet50/ ├── unet_resnet50/ ├── deeplabv3p_resnet50/ └── yolov5/批量推理时写一个简单的 shell 脚本遍历所有测试图像:
for img in data/classify/test/*.png; do python inference.py --model resnet50 --image "$img" done10.2 显存占用观察方法
训练过程中另开一个终端,实时查看显存占用:
watch -n 1 nvidia-sminvidia-smi中MiB一列显示的就是当前进程占用的显存。不同模型、不同分辨率、不同 batch size 下显存占用差异很大,不能直接套用别人的数字。更稳妥的判断是:发现自己训练时报CUDA out of memory,就减小 batch size 或图片尺寸,先让程序跑起来。
10.3 降低显存占用的常用手段
- batch size 从 4 或 8 开始,逐步增大
- 图片尺寸先用 256,确认逻辑正确后再提高到 512 或 640
- 使用混合精度训练,PyTorch 里使用
torch.cuda.amp.autocast和GradScaler - YOLOv5 训练时加
--amp参数 - 关闭不使用的进程,避免多个训练脚本抢占显存
10.4 训练时长预估
医学图像数据量通常不大,分类任务几千张、分割任务几百张到一千张。ResNet50 微调在消费级显卡上一个 epoch 往往几分钟到十几分钟;UNet 和 DeepLabV3+ 单 epoch 时间取决于分辨率和 batch size;YOLOv5 的训练时间更容易受图片尺寸影响。第一次训练建议把 epoch 数设小,例如先跑 5 个 epoch,确认 loss 在下降、验证指标在上升,再启动完整训练。
11. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
torch.cuda.is_available()返回 False | 安装了 CPU 版 PyTorch,或显卡驱动异常 | pip list | grep torch查看是否带+cpu | 根据显卡驱动重新安装 CUDA 版 PyTorch |
| CentOS/Ubuntu 下 PyTorch 安装后 import 报错 | CUDA 版本与 PyTorch 构建版本不匹配 | 查看完整报错堆栈,确认libcudart报错 | 改用一个兼容的 cu118 或 cu121 版本 |
训练报CUDA out of memory | batch size 过大或分辨率过高 | watch nvidia-smi观察显存占用 | 减小 batch size、降低分辨率、开启 AMP |
| UNet 预测全为背景 | Mask 读取错误或二值化错误 | 打印一张 Mask 的像素值分布 | 修正 Mask 读取逻辑 |
| 分割 loss 为负数 | 使用了torch.nn.functional.binary_cross_entropy输入了未经过 sigmoid 的 logits | 检查损失函数是否包含 Sigmoid | 改用BCEWithLogitsLoss |
| DeepLabV3+ 输出形状与 GT 不匹配 | 模型输出是OrderedDict | 打印output.keys() | 取output["out"]计算损失 |
| YOLOv5 训练时数据加载报错 | medical.yaml路径错误或标签与图像不一致 | 检查 yaml 路径是否为相对路径 | 修正为项目根目录相对路径 |
| 影集不均衡导致分类全部预测为多数类 | 类别样本数量差异过大 | 打印训练集类别分布 | 使用WeightedRandomSampler或调整类别权重 |
| 验证集 Dice 高但边界粗糙 | 训练充分但缺少边界约束 | 可视化预测边界 | 增加边界损失或提高输入分辨率 |
| 推理结果保存后图像是全黑的 | 预测 Mask 是浮点数 0~1,直接乘 255 后未转 uint8 | 打印pred_np.dtype | 保存前用astype(np.uint8) |
| TensorBoard 可视化无变化 | 训练脚本没有周期性写入 summary | 检查代码中是否调用了add_scalar | 每个 epoch 记录 loss 和指标 |
| 预训练权重下载缓慢或卡住 | 网络问题 | 查看下载日志 | 使用手动下载后放到缓存目录,再用torch.load加载 |
12. 毕设工程化与文档整理建议
实验跑通是一回事,毕设答辩是另一回事。代码能复现、结果有对比、指标有分析,这三点做好,答辩时技术问题基本不会被问倒。
先固定随机种子:
import random import numpy as np import torch def set_seed(seed=42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed)每次实验记录以下信息:
- 数据集名称和划分方式
- 图像尺寸、batch size、学习率、优化器、epoch 数
- 训练集和验证集的 loss 收敛曲线
- 每个模型最佳 epoch 的验证指标
- 推理速度和显存占用
- 失败案例截图
论文里最常用的一张图是四个模型在同一个医学数据集上的效果对比。可以设计成表格形式,横向比较 ResNet50、UNet、DeepLabV3+、YOLOv5 的参数量、输入尺寸、训练时间、核心指标。
多任务设计的论文阐述逻辑可以这样组织:
- 主任务:选择分类或分割中的一种作为核心贡献,其余作为对比实验
- 辅助任务:用另一个任务证明所提方法在相似问题上的泛化性
- 对比实验:四个模型在同数据集上的指标对比
- 消融实验:更换骨干网络、更换损失函数、是否使用数据增强等
这样即使没有提出新网络结构,也能把工作量和方法讨论写得比较充分。
13. 总结与下一步
这套 ResNet + UNet + DeepLabV3+ + YOLOv5 的多任务组合,是医学图像领域四个方向的标准基线,也覆盖了影像分类、区域分割、目标检测三类最常见的实际需求。第一个值得验证的功能是确认 PyTorch 的 CUDA 环境能正常识别 GPU;第二步先跑通 ResNet 分类的完整流程,理解数据加载、训练循环、评估指标这一套闭环;第三步再切换到分割和检测任务,你会发现大部分代码结构是通用的。
最容易踩的坑不在模型本身,而是数据格式和训练环境的匹配。分类数据必须转 RGB 三通道,分割 Mask 必须确认像素值是二值还是多类别,YOLOv5 标签必须归一化且路径正确。这些细节在每一类模型的章节里都做了重点提醒。
下一步可以做的扩展方向包括:在 UNet 中引入注意力机制改进小病灶分割,使用 ResNet34 作为分割模型 encoder 从而降低显存占用,把 YOLOv5 替换为 YOLOv8 或 YOLOv26 对比推理速度。如果所在单位服务器允许,还可以把训练好的模型导出为 ONNX,接入一个简单的 Web 界面做演示。
医学深度学习毕设的价值不只是模型准确率,而是你是否完整地走通了数据合规、实验设计、指标分析、结果可视化这一条工程链路。把这篇里的代码和流程消化完,剩下的就是把公开数据集下载好,动手跑第一个 epoch。