简介这份资源是面向计算机、人工智能、自动化等专业学生与从业者的深度学习毕业设计项目包主题为阿兹海默症早期诊断辅助系统适合作为毕业设计、课程大作业或进阶练手项目。压缩包共约2000个文件整体14.12MB以680个Python源码文件为核心辅以218个JavaScript、62个CSS、40个HTML等前端资源以及119个Tcl脚本、92个C头文件、36个msg消息文件、11个Markdown说明文档和若干JSON、XML配置覆盖模型训练、界面交互与文档说明等模块。项目代码经过调试测试附带文档说明答辩评审分达98分已有66人学习下载。读者可据此获得一套完整的深度学习医学辅助诊断实现方案理解数据预处理、模型构建与前端展示的衔接方式并在此基础上修改调整实现不同功能具备较高的学习借鉴价值。1. 阿兹海默症早期诊断辅助系统从 MRI 影像到可解释分级家里老人开始反复问同一句话、把钥匙放进冰箱多数家庭会先当成年纪大了。但神经内科的共识是阿兹海默症AD在临床确诊前 510 年海马体和内嗅皮层就已经出现可测量的萎缩。等到 MMSE 量表明显掉分能干预的窗口已经很窄。这个毕业设计题目要做的就是拿公开的 AD 结构核磁数据集用 Python 和深度学习搭一套能对 MRI 切片做早期分级、并给出可解释热力图的辅助诊断系统。它面向的是做毕设的本科生、刚转医学影像方向的算法同学以及想验证深度学习到底能不能在 AD 早期筛查里跑通的工程实践者。核心链路只有四步数据整理、模型训练、评估、可视化推理。听起来简单但真正卡人的从来不是网络结构而是数据划分和标签泄漏。2. 数据准备与标签体系ADNI 与 OASIS 怎么选、怎么切2.1 先搞清楚 AD / MCI / CN 三分类到底在分什么阿兹海默症辅助诊断的标签体系主流是三类CN认知正常、MCI轻度认知障碍、AD确诊。MCI 是这套系统真正的价值所在——它是最可能逆转或延缓的阶段也是模型最容易翻车的地方因为 MCI 的影像特征介于 CN 和 AD 之间类间差异小。常见做法是先用二分类CN vs AD把 pipeline 跑通确认准确率能到 90% 以上再上三分类。别一上来就三分类否则你会分不清是模型不行还是数据不行。公开数据集里ADNI 规模大、纵向随访全但需要申请权限OASIS 系列可以直接下载适合毕设快速起步。OASIS-1 是横断面OASIS-2 有纵向随访做早期诊断优先用带随访的。注意不同数据集的扫描协议、层厚、场强不一致混用之前必须做重采样和强度归一化否则模型学到的可能是哪台机器拍的而不是脑萎缩。2.2 用 Python 把 DICOM/NIfTI 整理成可训练的切片拿到原始数据后第一步是转成统一格式。NIfTI.nii/.nii.gz比 DICOM 好用因为一个文件就是一个 3D 体积。下面这段是常见的整理脚本骨架import os import numpy as np import nibabel as nib from skimage.transform import resize def load_and_normalize(nii_path, target_shape(128, 128, 128)): 读取 NIfTI做 z-score 归一化并重采样到统一尺寸 img nib.load(nii_path) data img.get_fdata().astype(np.float32) # 强度归一化按非零体素做 z-score避免背景拉低均值 mask data 0 if mask.sum() 0: data[mask] (data[mask] - data[mask].mean()) / (data[mask].std() 1e-8) # 重采样到统一尺寸三线性插值 data resize(data, target_shape, order1, preserve_rangeTrue) return data def extract_slices(volume, axis2, skip_edges10): 沿轴切 2D 切片跳过首尾空白层 n volume.shape[axis] slices [] for i in range(skip_edges, n - skip_edges): sl np.take(volume, i, axisaxis) if sl.std() 0.01: # 过滤几乎全黑的层 slices.append(sl) return np.stack(slices)逻辑说明load_and_normalize先做 z-score再做空间重采样顺序不能反——先重采样会让插值引入的零值污染统计量。extract_slices里skip_edges10是经验值跳过顶部和底部各 10 层因为那里基本是颅骨和空气。sl.std() 0.01这个阈值用来剔除无效切片具体数值要看你数据的强度分布可以先打印几十个切片的 std 分布再定。参数上target_shape设 128³ 是显存和精度的折中如果你用 3D 网络且显存够可以上 160³ 或 192³。切片方向axis2是轴状位AD 的海马体萎缩在轴状位和冠状位都明显可以两个方向都切做多视角融合。2.3 划分数据集时最容易犯的错按切片随机划分这是血泪经验里排第一的坑。如果你把同一个受试者的切片随机分到训练集和测试集模型会在测试时见过这个人的其他切片准确率虚高十几个点。正确做法是按受试者 ID 划分同一受试者的所有切片只能进同一个集合。常见比例是 7:1:2 或 8:1:1MCI 样本少的话用分层抽样保证每类都有。from sklearn.model_selection import GroupShuffleSplit # groups 是每个切片对应的受试者 ID gss GroupShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, test_idx next(gss.split(slices, labels, groupssubject_ids))GroupShuffleSplit保证同一 group 不跨集合这是医学影像毕设必须写进代码的防线。3. 模型选型与训练CNN 基线到迁移学习怎么落地3.1 从 2D CNN 基线开始别直接上 3D新手最容易冲动上 3D ResNet结果显存爆掉、训练一轮半小时、调参调到怀疑人生。稳妥路径是先用 2D CNN 在切片上跑通确认数据 pipeline 和评估逻辑没问题再考虑 3D 或 2.5D多切片堆叠当通道。2D 基线用 ResNet18 或 EfficientNet-B0 就够ImageNet 预训练权重能显著加速收敛因为底层边缘和纹理特征是可迁移的。import torch import torch.nn as nn from torchvision import models def build_model(num_classes3, pretrainedTrue): model models.resnet18(weightsIMAGENET1K_V1 if pretrained else None) # 单通道 MRI 输入把第一层卷积改成 1 通道权重按通道求平均 old_conv model.conv1 model.conv1 nn.Conv2d(1, 64, kernel_size7, stride2, padding3, biasFalse) if pretrained: model.conv1.weight.data old_conv.weight.data.mean(dim1, keepdimTrue) model.fc nn.Linear(model.fc.in_features, num_classes) return model逻辑说明把 RGB 三通道卷积核在通道维度求平均得到单通道初始化比随机初始化收敛快很多。num_classes3对应 CN/MCI/AD如果先做二分类就改成 2。损失函数用带类别权重的交叉熵因为 MCI 通常样本最少class_weights torch.tensor([1.0, 2.5, 1.2]) # 按类别频率倒数调整 criterion nn.CrossEntropyLoss(weightclass_weights)权重怎么定先统计训练集每类样本数取频率倒数再归一化。MCI 给 2.5 是常见起点具体要看你数据的不平衡程度。3.2 训练循环里必须记录的三个量很多人训练只看 loss结果模型过拟合了都不知道。必须同时记录训练 loss、验证 loss、验证集每类的召回率。AD 早期诊断里MCI 的召回率比整体准确率重要得多——漏掉一个 MCI 比误判一个 CN 代价大。下面是一个精简训练循环def train_one_epoch(model, loader, optimizer, criterion, device): model.train() total_loss 0 for imgs, labels in loader: imgs, labels imgs.to(device), labels.to(device) optimizer.zero_grad() outputs model(imgs) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() * imgs.size(0) return total_loss / len(loader.dataset) def evaluate(model, loader, device): model.eval() preds, trues [], [] with torch.no_grad(): for imgs, labels in loader: imgs imgs.to(device) out model(imgs) preds.extend(out.argmax(1).cpu().numpy()) trues.extend(labels.numpy()) return np.array(preds), np.array(trues)评估时用classification_report打印每类 precision/recall/f1重点看 MCI 那一行。如果 MCI 召回率低于 0.6说明模型没学到类间差异要么加数据增强要么换更强的特征提取器。3.3 数据增强在 MRI 上的边界翻转、旋转、轻微缩放都可以用但要注意左右翻转会改变脑的左右半球海马体萎缩通常有偏侧性翻转可能引入噪声。常见做法是只做小角度旋转±10°、平移±5%和亮度扰动不做水平翻转。另外弹性形变虽然能模拟解剖变异但幅度过大会破坏萎缩特征慎用。4. 评估、可解释性与推理接口让系统能说出理由4.1 别只看准确率AUC 和混淆矩阵才是诊断系统的门面医学辅助诊断里准确率会被类别不平衡带偏。必须报告每类 AUC、混淆矩阵、敏感度/特异度。二分类 CN vs AD 的 AUC 通常能到 0.95 以上三分类会掉到 0.85 左右这是正常范围。如果三分类 AUC 低于 0.75先回去查数据划分有没有泄漏。from sklearn.metrics import roc_auc_score, confusion_matrix from sklearn.preprocessing import label_binarize y_bin label_binarize(trues, classes[0, 1, 2]) for i in range(3): auc roc_auc_score(y_bin[:, i], probs[:, i]) print(fClass {i} AUC: {auc:.3f}) print(confusion_matrix(trues, preds))probs是 softmax 后的概率不是 argmax 结果。AUC 用概率算混淆矩阵用硬标签算两者配合看。4.2 Grad-CAM 热力图让医生看到模型在看哪里辅助诊断系统如果只给一个标签医生不会信。Grad-CAM 能把模型关注的区域叠加回原图如果热力图落在海马体、内嗅皮层、颞叶内侧说明模型学到了合理的解剖特征如果落在颅骨或背景说明模型在走捷径。实现上用pytorch-grad-cam或手写钩子都行from pytorch_grad_cam import GradCAM from pytorch_grad_cam.utils.image import show_cam_on_image target_layers [model.layer4[-1]] # ResNet 最后一层卷积 cam GradCAM(modelmodel, target_layerstarget_layers) grayscale_cam cam(input_tensorimg_tensor, targetsNone) visualization show_cam_on_image(img_np, grayscale_cam[0], use_rgbFalse)target_layers选最后一层卷积分辨率低但语义强想要更细的定位可以选 layer3。热力图不是装饰它是你验证模型是否学到正确特征的唯一手段。如果热力图长期落在非脑区别急着调参先检查预处理有没有把颅骨剥掉。4.3 推理接口把模型包成一个能调用的函数毕设系统通常要有个 demo 界面或 API。最小可用接口就是一个函数输入 NIfTI 路径输出类别、概率、热力图路径。def predict(nii_path, model, device, target_shape(128, 128, 128)): volume load_and_normalize(nii_path, target_shape) slices extract_slices(volume) model.eval() all_probs [] with torch.no_grad(): for sl in slices: tensor torch.from_numpy(sl).unsqueeze(0).unsqueeze(0).float().to(device) prob torch.softmax(model(tensor), dim1).cpu().numpy()[0] all_probs.append(prob) # 切片级概率取平均得到受试者级预测 subject_prob np.mean(all_probs, axis0) pred_class subject_prob.argmax() return pred_class, subject_prob逻辑说明切片级预测取平均是最简单的聚合方式也可以用投票或取最大概率切片。平均更稳但会稀释局部强信号如果某个切片热力图特别明显可以加权。参数上target_shape必须和训练时一致否则模型输入分布不匹配预测会乱。5. 避坑与排查那些让毕设卡住一周的问题5.1 现象训练准确率 99%测试准确率 60%原因按切片随机划分导致同一受试者跨集合标签泄漏。解决改用GroupShuffleSplit按受试者 ID 划分重新训练。这是最常见也最致命的问题改完准确率通常会回落到合理区间。5.2 现象验证 loss 震荡剧烈不收敛原因学习率太大或 batch size 太小导致梯度噪声大。解决学习率先设 1e-4用余弦退火或 ReduceLROnPlateaubatch size 至少 16显存不够就降分辨率而不是降 batch。另外检查归一化是不是在切片级别又做了一次重复归一化会让输入分布漂移。5.3 现象MCI 类召回率始终低于 0.5原因MCI 样本少且类间差异小模型倾向于把它判成 CN 或 AD。解决加类别权重、对 MCI 做过采样、或者先用二分类预训练再微调三分类。另一个思路是引入临床量表特征如 MMSE 分数做多模态融合但毕设如果只做影像就老老实实调权重和增强。5.4 现象Grad-CAM 热力图全图均匀看不出重点原因模型没收敛或者 target layer 选得太靠后导致分辨率太低。解决确认模型在验证集上 AUC 正常后再看热力图换 layer3 或 layer2 试试检查输入图像有没有做颅骨剥离没剥离的话模型可能在看颅骨。5.5 现象推理时单张 NIfTI 预测结果和训练时同一样本不一致原因推理时的预处理和训练时不一致比如训练用了 z-score推理只做了除以最大值。解决把预处理封装成一个函数训练和推理共用同一份代码别写两套。这个坑很隐蔽因为不会报错只是结果悄悄偏了。6. 进阶技巧用 2.5D 多切片融合把 MCI 召回率提上去如果基线三分类的 MCI 召回率卡在 0.6 上不去可以试 2.5D 方案不做 3D 卷积而是把相邻的 3 个切片堆成 3 通道输入让模型看到层间上下文。海马体萎缩是 3D 结构变化单切片信息有限2.5D 是显存和性能的甜点区。具体做法在extract_slices之后对每个切片索引 i取 i-1、i、i1 三层堆成 (3, H, W)。第一层卷积改成 3 通道可以直接用 ImageNet 预训练权重不用求平均。训练时注意切片索引要连续边界处做 padding 或跳过。def stack_25d(slices, gap1): 把相邻切片堆成 3 通道模拟 2.5D 输入 stacked [] for i in range(gap, len(slices) - gap): trio np.stack([slices[i-gap], slices[i], slices[igap]], axis0) stacked.append(trio) return np.stack(stacked)gap1是相邻层gap2跨度更大适合层厚较大的数据。改完之后 MCI 召回率通常能提 510 个点但训练时间会增加因为样本数没变但每样本计算量大了。另一个验证技巧把测试集按 MMSE 分数分层看模型在 MMSE 2730接近正常和 2426轻度两档上的表现差异。如果模型只在低分档准说明它学的是病得重不重而不是早不早期这跟辅助诊断的初衷是反的。我一般会在论文里专门放这张分层表比整体准确率有说服力得多。最后说个习惯每次改完预处理或划分逻辑先跑一个 5 分钟的小实验少量样本、几个 epoch确认 pipeline 通了再上全量。我吃过太多次训练三小时才发现标签对错了的亏这个后悔药没有只能靠小步验证。希望帮到你。本文还有配套的精品资源点击获取