简介:这份资源面向机器学习、深度学习方向的课程学习者,提供一套基于3D卷积神经网络完成医学图像分类的完整课程大作业方案,适合期末大作业、课程设计或新手入门实践。压缩包共48个文件,约11.48MB,以18个Python源码文件为核心,辅以pickle、h5模型与数据文件、csv结果记录、txt与md说明文档等,覆盖数据加载、模型定义、训练与测试全流程。代码注释较为完整,部署门槛低,下载后按说明即可运行。项目围绕3D医学影像数据展开,包含dataloader、models等模块划分,并给出训练脚本、测试脚本与结果文件,便于读者理解三维卷积在医学图像分类中的具体实现方式,也能据此复现实验、调整网络结构或迁移到相似任务。目前已有440人学习下载,可作为课程作业参考与深度学习实战练手素材。
1. 从一份 3D CNN 医学图像分类作业说起:它到底能跑出什么结果
如果你正在为机器学习课程大作业发愁,尤其是选题卡在「医学图像分类」这个方向上,这份基于 3D 卷积神经网络的源代码包大概率能让你少熬两个通宵。它不是那种只丢一个.ipynb让你自己猜的仓库,而是把训练、验证、测试、结果输出整条链路都铺好了:train.py、test.py、dataloader、models、utils各司其职,根目录还带着README.md和result/final_result.csv这样的产出物。换句话说,你拿到手就能先跑一遍看结果,再回头改模型、换数据、调参数。
这份资源适合三类人:一是期末大作业需要交完整代码和文档的同学,二是想入门 3D CNN 但不知道从哪下手的初学者,三是需要快速验证医学图像分类 pipeline 的从业者。它用 Python 写,依赖 PyTorch 生态,数据以.pickle和.h5两种格式出现,说明作者在数据加载上做过兼容处理。接下来我会按「先跑通、再改懂、后避坑」的顺序,把这份源码拆开讲清楚。
2. 环境搭建与数据格式:把 p3.pickle 和 p4.h5 喂进模型之前
2.1 依赖安装与目录结构确认
拿到压缩包解压后,第一件事不是急着python train.py,而是先看清楚目录里有什么。根目录下mylib是核心库,里面又分了dataloader、models、utils三个子模块;data文件夹放训练和验证数据;result放输出结果。常见做法是先用tree或ls -R把结构打印出来,确认__init__.py都在位,否则 Python 导入会直接报ModuleNotFoundError。
# 查看项目结构,确认关键文件存在 find . -maxdepth 3 -type f -name "*.py" | sort # 预期看到 train.py test.py test_pickle.py test_h5.py 以及 mylib 下的模块这段命令的作用是快速定位所有 Python 文件,避免因为解压不完整导致后续步骤白做。参数-maxdepth 3控制递归深度,防止在数据目录里翻太久。如果你看到mylib/models/下没有__init__.py,需要手动补一个空文件,这是 Python 包导入的硬性要求。
依赖方面,核心是 PyTorch、numpy、h5py、pandas。我一般会先建一个干净的环境,避免和系统里的老版本冲突:
python -m venv venv_3dcnn source venv_3dcnn/bin/activate # Windows 用 venv_3dcnn\Scripts\activate pip install torch torchvision numpy h5py pandas scikit-learn这里不写死版本号,是因为 3D CNN 对 PyTorch 版本不算挑剔,但如果你用的是比较新的 CUDA 显卡,建议装对应 CUDA 版本的 torch。装完后用python -c "import torch; print(torch.cuda.is_available())"验证一下,返回True说明 GPU 可用,训练会快很多;返回False就只能用 CPU,3D 卷积在 CPU 上跑起来会非常慢,这是第一个需要心理准备的地方。
2.2 pickle 与 h5 两种数据格式的读取差异
项目里同时出现了p3.pickle、p4.pickle、p5.pickle和p4.h5,这不是作者随意为之,而是医学图像数据常见的两种存储方式。.pickle是 Python 对象序列化,读取快、写法简单,但跨语言兼容性差;.h5是 HDF5 格式,适合存大规模数组,支持分块读取和压缩。test_pickle.py和test_h5.py就是分别用来验证这两种格式读取是否正常的脚本。
import pickle import h5py import numpy as np # 读取 pickle 文件,通常是一个字典或数组 with open('p3.pickle', 'rb') as f: data_pickle = pickle.load(f) print('pickle 类型:', type(data_pickle)) if isinstance(data_pickle, dict): for k, v in data_pickle.items(): print(k, np.array(v).shape) # 读取 h5 文件,注意 h5 内部是类似字典的层级结构 with h5py.File('p4.h5', 'r') as f: def print_h5(name, obj): if isinstance(obj, h5py.Dataset): print(name, obj.shape, obj.dtype) f.visititems(print_h5)这段代码先加载 pickle 并打印类型和形状,再用visititems递归遍历 h5 里的所有数据集。关键参数是'rb'和'r',分别表示二进制只读和只读模式,千万别用'w'去打开,否则会把原始数据覆盖掉,这种翻车现场我见过不止一次。打印形状是为了确认数据维度是否符合 3D CNN 的输入要求,通常是(样本数, 深度, 高度, 宽度)或(样本数, 通道数, 深度, 高度, 宽度)。如果形状里出现None或维度顺序明显不对,就需要在dataloader里做转置或 reshape。
提示:先跑
test_pickle.py和test_h5.py,确认两种格式都能正常读取,再动train.py。这两个脚本相当于数据层的「体检」,跳过它们直接训练,报错时你连是数据问题还是模型问题都分不清。
3. 模型定义与训练流程:3D 卷积核到底在卷什么
3.1 3D 卷积层与 2D 卷积的本质区别
医学图像分类和普通图像分类最大的不同在于「深度」这个维度。CT、MRI 这类数据是三维体数据,如果硬压成 2D 切片,就会丢失层与层之间的空间关系。3D 卷积核的尺寸是(kernel_d, kernel_h, kernel_w),它在三个方向上同时滑动,所以能捕捉到病灶在纵深方向上的连续变化。这也是这份作业选择 3D CNN 而不是 2D CNN 的核心原因。
在mylib/models/下,模型定义通常会包含几个nn.Conv3d层,后面接BatchNorm3d、ReLU和MaxPool3d。我一般会先看模型文件里的forward函数,确认输入张量的维度顺序是(N, C, D, H, W),其中 N 是 batch size,C 是通道数,D 是深度。如果数据加载出来是(N, D, H, W),就需要在进入第一层卷积前用unsqueeze(1)补一个通道维度。
import torch import torch.nn as nn class Simple3DCNN(nn.Module): def __init__(self, num_classes=2): super().__init__() # 输入通道 1,输出通道 16,卷积核 3x3x3 self.conv1 = nn.Conv3d(1, 16, kernel_size=3, padding=1) self.bn1 = nn.BatchNorm3d(16) self.pool1 = nn.MaxPool3d(2) self.conv2 = nn.Conv3d(16, 32, kernel_size=3, padding=1) self.bn2 = nn.BatchNorm3d(32) self.pool2 = nn.MaxPool3d(2) self.global_pool = nn.AdaptiveAvgPool3d(1) self.fc = nn.Linear(32, num_classes) def forward(self, x): # x: (N, 1, D, H, W) x = self.pool1(torch.relu(self.bn1(self.conv1(x)))) x = self.pool2(torch.relu(self.bn2(self.conv2(x)))) x = self.global_pool(x) # (N, 32, 1, 1, 1) x = x.view(x.size(0), -1) # (N, 32) return self.fc(x)这段代码展示了一个最小可用的 3D CNN 结构。padding=1保证卷积后空间尺寸不变,MaxPool3d(2)把每个维度减半,AdaptiveAvgPool3d(1)把任意大小的特征图压成 1x1x1,这样全连接层的输入维度就固定了。参数num_classes根据你的分类任务改,二分类就设 2,多分类就设对应类别数。实际项目里的模型会比这个深,但结构逻辑是一样的。
3.2 训练脚本的关键参数与日志观察
train.py是整条链路的入口。跑之前先打开看一眼,重点确认四个东西:数据路径、batch size、学习率、epoch 数。数据路径通常写在文件开头的常量里,或者通过argparse传入。batch size 受显存限制,3D 数据很吃显存,8 或 16 是常见起点,如果报CUDA out of memory,就往下调到 4 甚至 2。
# 典型训练启动命令,具体参数名以 train.py 里的 argparse 为准 python train.py --data_dir ./data --batch_size 8 --lr 0.001 --epochs 50 --save_dir ./result学习率0.001是 Adam 优化器的常用值,如果 loss 震荡厉害就降到0.0001。epoch 数看数据量,小数据集 50 轮足够观察趋势。训练过程中重点看两个信号:训练 loss 是否稳定下降,验证集准确率是否跟着涨。如果训练 loss 降但验证准确率不涨,说明过拟合了,常见做法是加 dropout 或数据增强。result/final_result.csv和5_avg.csv这类文件通常是训练结束后生成的预测结果或交叉验证平均结果,可以用来核对模型输出格式是否符合提交要求。
注意:3D 卷积的训练时间通常是 2D 的几倍甚至十几倍。如果只有 CPU,建议先把 epoch 设成 2 跑通流程,确认没有报错后再考虑上 GPU 或减少数据量。
4. 推理与结果输出:test.py 和 sampleSubmission.csv 怎么对上
4.1 测试脚本的加载与预测逻辑
test.py负责加载训练好的权重并对测试集做推理。它和train.py共享mylib里的模型定义和 dataloader,所以只要训练能跑通,测试一般不会出大问题。关键点是权重文件的路径要对,项目里出现的p4.h5有可能是模型权重,也有可能是数据文件,需要根据README.md或代码里的load_state_dict调用来确认。
import torch from mylib.models import Simple3DCNN # 以实际模型类名为准 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = Simple3DCNN(num_classes=2).to(device) # 加载权重,注意 map_location 防止 GPU 权重在 CPU 上加载失败 state_dict = torch.load('p4.h5', map_location=device) model.load_state_dict(state_dict) model.eval() # 推理时关闭梯度,节省显存 with torch.no_grad(): for batch in test_loader: inputs = batch['image'].to(device) outputs = model(inputs) preds = torch.argmax(outputs, dim=1)map_location=device这个参数很关键,它解决了「在 GPU 上训练、在 CPU 上推理」的兼容问题。model.eval()会把 BatchNorm 和 Dropout 切到推理模式,忘了写这句会导致预测结果不稳定。torch.no_grad()关闭梯度计算,能明显降低显存占用。最后argmax取概率最大的类别作为预测标签。
4.2 提交文件格式与结果校验
sampleSubmission.csv是提交格式的模板,final_result.csv是实际产出的结果。常见做法是先读模板看列名和行数,再把自己生成的预测结果按同样格式写进去。如果列名是id,label,那你的 DataFrame 就必须有这两列,且行数要和测试集样本数一致。
import pandas as pd submission = pd.read_csv('sampleSubmission.csv') print(submission.columns.tolist(), submission.shape) # 假设 preds 是预测结果列表,顺序与测试集一致 submission['label'] = preds submission.to_csv('result/final_result.csv', index=False)这段代码先检查模板的列名和形状,再把预测结果填进去。index=False避免多出一列索引,很多提交系统对格式很敏感,多一列就直接判错。写完后建议再读回来head()看一眼,确认没有错位。5_avg.csv如果是五折交叉验证的平均结果,可以用它和final_result.csv做对比,看单次训练和平均结果的差异有多大,差异过大说明模型不稳定。
5. 避坑与常见问题:那些让作业从满分变重交的细节
5.1 数据维度不匹配导致卷积层报错
现象:运行train.py时报RuntimeError: Expected 5D input (got 4D input)或Given groups=1, weight of size [16, 1, 3, 3, 3], expected input to have 1 channel。原因通常是 dataloader 返回的张量少了通道维度,或者数据本身是 2D 切片被误当成 3D 体数据。解决方法是先打印一个 batch 的 shape,确认是(N, C, D, H, W)还是(N, D, H, W),如果是后者就在模型 forward 里加x = x.unsqueeze(1),或者在 dataset 的__getitem__里补维度。
5.2 显存不足与 batch size 的取舍
现象:训练刚开始就报CUDA out of memory,或者跑几个 batch 后突然崩掉。原因是 3D 卷积的中间特征图占用显存远大于 2D,batch size 稍大就撑爆。解决方法是把 batch size 降到 4 或 2,同时可以用torch.cuda.empty_cache()清理缓存。如果还是不够,就减小输入体积,比如把深度或宽高从 128 降到 64,但要注意这会损失部分空间信息,准确率可能下降。
5.3 pickle 与 h5 读取时的路径和权限问题
现象:FileNotFoundError或PermissionError。原因是代码里写的是相对路径,而你在别的目录下执行脚本,或者文件被其他进程占用。解决方法是用os.path.dirname(__file__)拼绝对路径,确保无论从哪执行都能找到数据。Windows 下还要注意反斜杠转义,建议统一用os.path.join或pathlib.Path。
5.4 训练 loss 不下降或变成 NaN
现象:loss 一直是nan,或者前几轮降一点后面就卡住。原因可能是学习率太大、数据没有归一化、或者标签编码有问题。解决方法是先把学习率降到1e-4,检查输入数据是否做了ToTensor和Normalize,再确认标签是不是从 0 开始的连续整数。如果用了CrossEntropyLoss,标签必须是long类型,不能是float。
5.5 测试结果与提交格式对不上
现象:生成的 CSV 行数不对,或者列名和模板不一致。原因是测试集顺序被打乱,或者预测结果没有按原始 id 排序。解决方法是保留测试集的原始索引,在生成提交文件前按 id 排序,再和sampleSubmission.csv做一次merge校验,确保每个 id 都有对应的预测值。
6. 进阶技巧:用 5_avg.csv 反推模型稳定性与改进方向
跑通基础流程后,真正拉开差距的是对结果的分析。项目里出现的5_avg.csv很可能是一个五折交叉验证的平均结果文件,它的价值不在于提交,而在于告诉你模型在不同数据划分下的表现波动有多大。我一般会把它和final_result.csv放在一起对比,如果某几个类别的预测在两个文件里差异明显,说明模型对这些类别的特征学习不够稳定,可以考虑加数据增强或者调整类别权重。
import pandas as pd final_df = pd.read_csv('result/final_result.csv') avg_df = pd.read_csv('result/5_avg.csv') # 假设两个文件都有 id 和 label 列 merged = final_df.merge(avg_df, on='id', suffixes=('_final', '_avg')) diff = merged[merged['label_final'] != merged['label_avg']] print('预测不一致的样本数:', len(diff)) print(diff.head(10))这段代码把两次结果按 id 对齐,找出预测不一致的样本。suffixes参数给同名列加后缀,避免冲突。如果不一致的样本集中在某个类别,就去检查那个类别的数据量是不是太少,或者图像质量是不是有问题。常见做法是对少数类做重采样,或者在 loss 里加weight参数。
另一个进阶方向是可视化 3D 卷积核学到的特征。虽然这份源码不一定带可视化脚本,但你可以自己写一个 hook 把中间层的激活值导出来,用 matplotlib 画几个切片看看模型到底关注了哪个区域。如果激活值集中在图像边缘而不是病灶区域,说明模型可能学到了背景噪声,这时候就需要检查数据预处理是不是把关键区域裁掉了。
提示:改模型之前先备份原始代码,每次只改一个变量,跑完记录结果。3D CNN 的训练成本高,盲目调参很容易浪费一整天。
从那以后我每次拿到这类课程作业源码,都强制先跑一遍test_pickle.py和test_h5.py,确认数据层没问题再动模型。这个习惯帮我省下了大量排查「到底是数据错还是代码错」的时间。希望这份拆解能帮你顺利跑通这份 3D CNN 医学图像分类作业,少走几个弯路。
本文还有配套的精品资源,点击获取