基于ResNet优化模型的阿尔茨海默症识别课程设计实战 简介这份资源是深度学习课程设计的完整实践项目围绕基于ResNet优化模型的阿尔茨海默症识别展开面向希望入门医学影像分析的小白学习者也可作为进阶者的课程设计、大作业或毕设参考。项目提供2D与3D两条技术路线2D方法将79张不同位置的切片转为79通道图像输入ResNet503D方法采用三维卷积直接处理体数据并引入MedicalNet医疗影像预训练模型便于对比不同建模思路的识别效果。资源包共13个文件包含6个py脚本、3个ipynb笔记本、2份pdf论文资料、1个csv预测结果和1个md说明压缩包约2.06MB脚本与笔记本均可直接运行方便读者边调试边理解网络结构。目前已有90人学习适合作为医学影像分类任务的入门跳板帮助读者掌握数据加载、模型搭建、预训练迁移与结果分析等关键环节。1. 阿尔茨海默症识别从 ResNet 到优化模型的课程设计落地路径阿尔茨海默症Alzheimers Disease, AD的早期识别在医学影像领域一直是个硬骨头。结构核磁共振sMRI能清晰反映海马体萎缩、脑室扩大等结构性变化但人眼判读依赖放射科医生的经验不同中心、不同年资的医生给出的 MCI 转化风险评估可能相差很大。深度学习课程设计选这个题目好处是数据公开、任务定义清晰、评价指标成熟坏处是——如果只拿一个 torchvision 的 resnet18 直接套上去准确率往往卡在 70% 上下连自己都说服不了。这篇笔记围绕「基于 ResNet 优化模型的阿尔茨海默症识别」这个课程设计题目把数据准备、ResNet 选型、优化点设计、训练调参和排错串成一条能复现的路径。适合正在做深度学习课程设计、想拿 ADNI 或 OASIS 数据集跑出一个像样结果的同学也适合已经跑通 baseline、想搞清楚「优化模型到底优化在哪」的熟手。2. 数据与任务定义ADNI/OASIS 的标签体系与切片策略2.1 先搞清楚三分类还是二分类AD 识别最常见的任务设定有两类。第一类是二分类AD vs 认知正常CN这个任务相对简单baseline 就能到 85% 以上课程设计如果只做这个创新点很难写。第二类是三分类AD vs 轻度认知障碍MCI vs CN难点全在 MCI——它和 CN 的脑结构差异细微和 AD 又有重叠模型很容易把 MCI 判成 CN导致 MCI 召回率偏低。我一般建议课程设计直接做三分类然后在报告里单独分析 MCI 的混淆矩阵这样优化点才有落脚处。标签来源上ADNI 自带诊断标签DXSUM 表里的 DIAGNOSISOASIS 则提供 CDR 评分通常按 CDR0 为 CN、CDR0.5 为 MCI、CDR1 为 AD 来映射。这里有个坑同一个受试者多次随访的标签可能变化如果按图像切分而不按受试者切分训练集和验证集会泄漏指标虚高。正确做法是按 subject ID 做 GroupShuffleSplit。2.2 切片方向与预处理为什么用轴状位而不是矢状位sMRI 是三维体数据直接上 3D CNN 显存吃不消课程设计阶段更常见的是取二维切片。轴状位axial切片在海马体层面能同时看到左右海马信息密度高矢状位sagittal虽然能看到海马长轴但单张切片左右不对称模型学起来更吃力。常见做法是取轴状位中间若干层比如从第 60 层到第 120 层每隔 5 层取一张每个受试者得到 12 张左右切片。预处理流程我一般固定成四步N4 偏置场校正用 ANTs 或 SimpleITK、配准到 MNI 模板、颅骨剥离、灰度归一化到 [0,1]。如果课程设计时间紧可以跳过配准但偏置场校正和颅骨剥离建议保留否则同一台机器不同扫描的亮度差异会让模型学偏。import numpy as np import nibabel as nib from sklearn.model_selection import GroupShuffleSplit def load_slices(path, z_start60, z_end120, step5): 读取3D sMRI按轴状位抽层返回切片数组和对应层号 img nib.load(path).get_fdata() # 灰度归一化到[0,1]避免不同扫描亮度差异 img (img - img.min()) / (img.max() - img.min() 1e-8) slices [] for z in range(z_start, z_end, step): slices.append(img[:, :, z]) return np.stack(slices, axis0) # shape: (N, H, W) # 按受试者切分防止同一人多次随访泄漏 subjects np.array([...]) # 每个样本对应的subject ID labels np.array([...]) # 0CN, 1MCI, 2AD gss GroupShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, val_idx next(gss.split(np.zeros(len(labels)), labels, groupssubjects))这段代码的关键在GroupShuffleSplit的groups参数它保证同一个受试者的所有切片只出现在训练集或验证集之一。z_start、z_end、step三个参数控制切片数量和覆盖范围如果显存紧张可以把 step 调到 8 或 10但别低于 5否则相邻切片几乎一样信息冗余。归一化那行加了1e-8是防止全黑切片除零。2.3 数据增强别把海马体翻转没了医学影像增强和自然图像不一样。水平翻转在自然图像里没问题但在脑影像里会改变左右半脑的解剖语义海马体左右不对称是 AD 的重要特征翻转等于把这个信号抹掉。我一般只用小角度旋转±10°、平移±5%、缩放±5%和轻微高斯噪声。颜色抖动、Cutout 这类增强在灰度 MRI 上意义不大反而可能遮住关键区域。3. ResNet 选型与优化点设计从 baseline 到改进模型3.1 为什么是 ResNet 而不是 VGG 或纯 CNNResNet 的核心是残差连接它让梯度能绕过若干卷积层直接回传缓解了深层网络的退化问题。在 AD 识别任务上海马体萎缩是弥散性变化需要较大的感受野浅层 CNN 感受野不够VGG 堆到 16 层以上又容易梯度消失。ResNet18 或 ResNet34 在参数量和感受野之间比较平衡课程设计阶段用 ResNet18 做 baseline 最稳妥。如果要用预训练权重注意 ImageNet 是 RGB 三通道而 MRI 是单通道常见做法是把第一层卷积的权重在通道维度求平均再复制成单通道或者直接改成nn.Conv2d(1, 64, ...)并随机初始化第一层。3.2 优化点一把 SE 注意力嵌进残差块ResNet 的残差块对所有通道一视同仁但 AD 切片里海马体区域和背景的贡献显然不同。SENet 的 squeeze-and-excitation 模块能自适应学习通道权重嵌进 BasicBlock 后模型会更关注有判别力的通道。实现上就是在残差相加之前对out做一次全局平均池化、两层全连接、Sigmoid再乘回out。import torch import torch.nn as nn class SEBasicBlock(nn.Module): def __init__(self, inplanes, planes, stride1, reduction16): super().__init__() self.conv1 nn.Conv2d(inplanes, planes, 3, stride, 1, biasFalse) self.bn1 nn.BatchNorm2d(planes) self.conv2 nn.Conv2d(planes, planes, 3, 1, 1, biasFalse) self.bn2 nn.BatchNorm2d(planes) self.relu nn.ReLU(inplaceTrue) # SE模块通道注意力 self.se nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Conv2d(planes, planes // reduction, 1), nn.ReLU(inplaceTrue), nn.Conv2d(planes // reduction, planes, 1), nn.Sigmoid() ) self.downsample None if stride ! 1 or inplanes ! planes: self.downsample nn.Sequential( nn.Conv2d(inplanes, planes, 1, stride, biasFalse), nn.BatchNorm2d(planes) ) def forward(self, x): identity x out self.relu(self.bn1(self.conv1(x))) out self.bn2(self.conv2(out)) out out * self.se(out) # 通道加权 if self.downsample is not None: identity self.downsample(x) return self.relu(out identity)reduction16是 SE 模块的压缩比通道数少的时候可以调到 8否则中间层太窄。AdaptiveAvgPool2d(1)把每个通道压成一个标量两层 1x1 卷积相当于全连接Sigmoid 保证权重在 0 到 1 之间。注意 SE 模块放在残差相加之前这样注意力只调制主分支恒等映射保持干净。3.3 优化点二多切片特征融合单张切片信息有限同一个受试者的多张切片其实可以融合。常见做法有两种一是把多张切片堆成多通道输入比如 12 张切片当成 12 通道第一层卷积改成nn.Conv2d(12, 64, ...)二是每张切片单独过 CNN再对特征做池化或注意力融合。第一种实现简单但通道间没有显式交互第二种更灵活但代码量大。课程设计里我一般推荐第一种把in_channels改成切片数即可改动最小效果通常能涨 2 到 3 个百分点。3.4 优化点三损失函数加类别权重和 L2 正则三分类里 MCI 样本通常最少交叉熵会被 CN 和 AD 主导。给nn.CrossEntropyLoss传weight参数按类别频率的倒数设置能让模型更关注 MCI。另外优化器里加 weight_decay比如Adam(model.parameters(), lr1e-4, weight_decay1e-4)这就是 L2 正则的常见实现方式能压住过拟合。如果验证集准确率震荡大可以把 weight_decay 调到 5e-4 试试。from collections import Counter counts Counter(labels[train_idx]) total sum(counts.values()) # 类别权重 总数 / (类别数 * 该类样本数) weights torch.tensor([total / (len(counts) * counts[i]) for i in range(3)], dtypetorch.float32) criterion nn.CrossEntropyLoss(weightweights) optimizer torch.optim.Adam(model.parameters(), lr1e-4, weight_decay1e-4)weights的计算保证少数类获得更大梯度。注意权重别设得太极端如果 MCI 只有 CN 的十分之一权重差十倍以上会让训练不稳定可以先开方缓和一下。4. 训练流程与参数配置能复现的最小闭环4.1 训练循环与学习率调度课程设计的训练循环不需要太复杂但有几个点必须做对每个 epoch 打乱训练集、验证集不增强、保存验证集最优模型而不是最后一个 epoch。学习率调度用余弦退火或 StepLR 都行我一般用CosineAnnealingLR初始 lr 设 1e-4T_max 设总 epoch 数。from torch.optim.lr_scheduler import CosineAnnealingLR device torch.device(cuda if torch.cuda.is_available() else cpu) model SEBasicBlock # 此处指代完整网络实际需组装 model model.to(device) scheduler CosineAnnealingLR(optimizer, T_max50) best_acc 0.0 for epoch in range(50): model.train() for imgs, labs in train_loader: imgs, labs imgs.to(device), labs.to(device) optimizer.zero_grad() loss criterion(model(imgs), labs) loss.backward() optimizer.step() scheduler.step() # 验证 model.eval() correct total 0 with torch.no_grad(): for imgs, labs in val_loader: imgs, labs imgs.to(device), labs.to(device) pred model(imgs).argmax(dim1) correct (pred labs).sum().item() total labs.size(0) acc correct / total if acc best_acc: best_acc acc torch.save(model.state_dict(), best_ad_resnet.pth)T_max50要和总 epoch 数一致否则余弦周期走不完。保存最优模型而不是最后一个是因为验证集准确率通常在 30 到 40 epoch 之间达到峰值之后可能过拟合。model.eval()和torch.no_grad()在验证阶段必须加否则 BatchNorm 的 running mean 会被验证集更新结果不可信。4.2 关键参数表参数推荐值说明输入尺寸224x224与 ImageNet 预训练一致便于迁移batch size16 或 32显存 8G 以下用 16初始学习率1e-4预训练模型微调不宜过大weight_decay1e-4L2 正则强度过拟合时调到 5e-4epoch50配合余弦退火优化器Adam课程设计够用SGD 调起来更麻烦切片数12z 从 60 到 120step54.3 评价指标别只看准确率三分类任务里准确率会被多数类拉高必须同时看混淆矩阵、MCI 的召回率和 F1。我一般会在报告里放一张混淆矩阵热力图重点看 MCI 有多少被误判成 CN。如果 MCI 召回率低于 50%说明模型没学到 MCI 的细微特征可以考虑加大 MCI 的类别权重或者单独对海马体区域做裁剪再训练。5. 避坑与排查课程设计里最容易翻车的五个点5.1 现象验证集准确率比训练集还高原因数据泄漏。同一受试者的多次随访切片被分到了训练集和验证集模型在验证集上见到了「熟人」。解决用GroupShuffleSplit按 subject ID 切分切分前先确认每个受试者的所有切片都归到同一侧。5.2 现象loss 变成 NaN原因学习率太大或者输入没归一化导致梯度爆炸。解决先把 lr 降到 1e-5 跑几个 epoch 看 loss 是否正常下降检查输入像素值是否在 [0,1] 或标准化到均值 0 方差 1如果用了预训练权重确认第一层单通道改造后没有引入异常值。5.3 现象MCI 召回率极低几乎全判成 CN原因类别不平衡加上 MCI 和 CN 特征差异小。解决给 CrossEntropyLoss 传类别权重对 MCI 样本做重采样或者把三分类改成层次分类先分 AD vs 非 AD再在非 AD 里分 MCI vs CN。5.4 现象训练时显存溢出原因batch size 太大或者输入切片数太多。解决把 batch size 降到 8 或 16把切片数从 12 降到 8如果还不行把输入尺寸从 224 降到 128但注意预训练权重的位置编码会失配需要重新微调。5.5 现象换了随机种子结果波动很大原因数据量小初始化敏感。解决固定所有随机种子torch、numpy、random跑 3 到 5 个种子取平均报告里写均值±标准差。课程设计里如果只跑一次就下结论答辩时容易被问住。6. 进阶技巧用 Grad-CAM 验证模型到底在看哪里模型准确率上去了但你怎么知道它看的是海马体而不是扫描伪影Grad-CAM 能生成热力图把模型关注区域叠加回原切片。具体做法是取最后一个卷积层的输出和对应梯度做全局平均池化得到通道权重再加权求和、ReLU、上采样到原图尺寸。import cv2 def grad_cam(model, img_tensor, target_layer, class_idxNone): img_tensor: (1, C, H, W)target_layer: 模型中的卷积层 features [] grads [] def forward_hook(module, inp, out): features.append(out) def backward_hook(module, grad_in, grad_out): grads.append(grad_out[0]) handle_f target_layer.register_forward_hook(forward_hook) handle_b target_layer.register_backward_hook(backward_hook) output model(img_tensor) if class_idx is None: class_idx output.argmax(dim1).item() model.zero_grad() output[0, class_idx].backward() fmap features[0].detach().cpu().numpy()[0] # (C, h, w) grad grads[0].detach().cpu().numpy()[0] # (C, h, w) weights grad.mean(axis(1, 2)) # (C,) cam np.tensordot(weights, fmap, axes([0], [0])) cam np.maximum(cam, 0) cam cv2.resize(cam, (img_tensor.shape[3], img_tensor.shape[2])) cam (cam - cam.min()) / (cam.max() - cam.min() 1e-8) handle_f.remove() handle_b.remove() return camtarget_layer一般选最后一个残差块的conv2那里感受野最大语义信息最强。weights是每个通道的梯度均值代表该通道对目标类别的重要性。np.maximum(cam, 0)是 ReLU只保留正贡献区域。生成的 cam 可以叠加到原切片上如果高亮区域集中在海马体附近说明模型学到了有解剖意义的特征如果高亮在颅骨边缘或背景说明模型可能走了捷径需要检查预处理是否到位。我自己的习惯是每跑完一个模型至少抽 10 个验证样本看 Grad-CAM如果超过 3 个样本的高亮区域明显偏离海马体这个模型即使准确率高我也不会写进报告。课程设计答辩时一张能解释的热力图比多两个百分点的准确率更有说服力。希望帮到你。本文还有配套的精品资源点击获取