拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

脑肿瘤分割毕设实战:3D-UNet、3D-VNet与2D-UNet多模型组合解析

脑肿瘤分割毕设实战:3D-UNet、3D-VNet与2D-UNet多模型组合解析 简介这是一份面向医学影像深度学习研究的毕设项目资源包围绕脑肿瘤分割与患者生存预测任务展开同时提供3D-UNet、3D-VNet、2D-UNet三种主流算法的完整实现与思路对比适合计算机相关专业学生用于毕业设计、课程设计也可供科研入门和算法进阶参考。压缩包共50个文件包括ipynb实验记录、py源码、png网络结构图、pyc编译文件及joblib、csv等模型与数据文件整体大小约3.05MB覆盖从数据处理、模型训练到结果可视化的完整流程。包内附有项目报告与说明文档便于快速理解框架其中生存预测部分还包含回归与分类两类方案的对比可进一步延伸拓展。已有93人浏览学习代码运行稳定遇到配置或复现问题可联系获得指导。1. 脑肿瘤分割毕设里的三模型组合3D-UNet、3D-VNet 与 2D-UNet 为什么值得仔细拆脑肿瘤分割项目里同时出现 3D-UNet、3D-VNet 和 2D-UNet 三种模型并且还挂着一套生存预测模型这种组合在本科毕业设计和课程设计里相当少见。多数能下载到的源码只给一个能跑通的分割网络这份项目却把 2D 三轴切片、3D 体素分割、轴向集成和生存分析放在同一套代码里等于把“数据准备 → 模型训练 → 指标评估 → 结果延展”的完整链路都铺好了。适合需要做模型对比、消融实验或者想完整复现一次 BraTS 风格实验的毕设场景。下面按我实际复现的顺序把模型分工、训练流程、生存预测和踩坑记录一次讲清楚。2. 模型分工与选型2D 三轴、3D-UNet、3D-VNet 各解决什么问题2.1 2D 三轴模型与 Axis Integration不是简单的“换个方向切”MRI 脑肿瘤数据本身是三维体BraTS 风格数据的分辨率通常在 240×240×155 左右包含 T1、T1ce、T2、FLAIR 四个模态每个体素带类别标签。2D 模型一次只看一个方向上的单张切片三个方向切出来的肿瘤形态差异很大轴状位能看到上下层面的连续关系冠状位管左右对称结构矢状位适合观察前后方向的延伸。项目里的 2D_axis1、2D_axis2、2D_axis3 三个模型目录分别对应三个切片方向Axis Integration 目录里的内容就是把这三组预测合并成最终结果。这部分最容易踩的点是三轴切片并不是把同一个体素矩阵换三个方向“切”完就结束还要处理标签同步旋转。切片时如果图像和 label 没有用同一次索引操作错位会直接导致训练出来的模型在对应方向上出现系统性偏差。常见做法是图像和 label 用同一个切片索引并且只在训练阶段对肿瘤区域做随机翻转、旋转增强推理阶段三个方向都只取原始体素不做任何空间变换否则集成时坐标对不齐。集成策略上我一般用概率平均而不是硬投票# 三轴概率平均集成比硬投票更适合边界区域 # pred_a1/pred_a2/pred_a3 形状都为 (H, W, D, n_classes) final_prob (pred_a1 pred_a2 pred_a3) / 3.0 final_mask np.argmax(final_prob, axis-1)这里要注意 dtype 和类别轴顺序三份概率图必须来自同一个预处理管线归一化参数、裁剪范围、resample 到相同分辨率任何一步不一致都会让集成结果变差。硬投票在类数少2 类时通常看不出明显差距一旦切到 4 类背景、坏死、水肿、增强核心硬投票在类边界上会产生很多孤立体素后期清洗成本远高于平均法。2D 三轴方案还有一个容易忽略的细节非等向性分辨率。如果原始数据的体素间距不是各向同性比如 H 方向和 W 方向是 1mmD 方向是 1.5mm那三个方向切出来的物理切片厚度就不一致。常见做法是先对体积做重采样把三个方向统一到同一体素间距再按轴切分。不重采样直接切轴状位和矢状位模型看到的肿瘤几何形变程度完全不同集成时两个方向互相打架最终结果甚至不如单方向模型。2.2 3D-UNet 与 3D-VNet结构差异与收敛上的差别3D-UNet 是 2D UNet 的升维版本编码器侧把卷积、下采样都换成了三维算子输入从 (B, C, H, W) 变成 (B, C, D, H, W)。3dunet.py 里常见的结构是四层编码、四层解码每层做一次三次卷积块加一次 stride2 的下采样解码端用转置卷积恢复分辨率。这个网络的优点是结构成熟、超参数好调缺点是参数量大而且对边缘细节的建模不如 VNet 精细。3D-VNet 与 3D-UNet 的核心区别在两个地方一是每个阶段不是普通卷积拼接而是残差单元输入会绕过两层卷积直接加到输出上二是 VNet 在训练时经常配合 Dice loss 而不是简单的交叉熵因为医学分割里前景区域占比小交叉熵会被背景类主导。3dvnet.py 的实现里可以看到这种残差结构早期训练时 VNet 的损失下降看起来比 UNet 慢但后期稳定指标通常更好尤其在增强核心这类小结构上。显存占用方面同样的 patch size 下 VNet 通常比 UNet 略轻一些因为残差连接减少了部分中间特征的存储压力但差距没有想象中大。我实际用下来3D-VNet 对 patch size 更敏感patch 太小残差块的感受野覆盖不全边界预测会出现一层“壳状”误判就是肿瘤边缘被多切或少切一圈patch 稍微放大 20%这种壳状误差会明显减少。3D-UNet 对 patch size 的敏感度低一些但整体分割精度上限也低一些。# 3dunet.py 中常见的双卷积块模式3D 版统一用 3x3x3 卷积 import torch.nn as nn class DoubleConv3d(nn.Module): def __init__(self, in_ch, out_ch): super().__init__() self.block nn.Sequential( nn.Conv3d(in_ch, out_ch, kernel_size3, padding1), nn.BatchNorm3d(out_ch), nn.ReLU(inplaceTrue), nn.Conv3d(out_ch, out_ch, kernel_size3, padding1), nn.BatchNorm3d(out_ch), nn.ReLU(inplaceTrue), ) def forward(self, x): return self.block(x)这段代码里的 padding1 配合 kernel_size3 是为了保持空间尺寸不变如果换用 padding0下采样次数越多特征图尺寸缩得越厉害解码端对齐会出问题。BatchNorm3d 在 patch 比较小时要小心 batch size 太小导致的统计量抖动后面避坑章节会单独说。2.3 数据生成与指标计算data_gen.py 与 metrics.py 里该盯的地方data_gen.py 在这一类分割项目里承担的是“把原始 nii 数据变成训练样本”的职责。常见流程是读取四个模态、堆叠成多通道体积、归一化、提取 patch 或切片、按比例划分训练验证集。BraTS 风格数据里文件命名一般形如 {case}_t1.nii.gz、{case}_t1ce.nii.gz、{case}_t2.nii.gz、{case}_flair.nii.gzseg 文件则是 {case}_seg.nii.gz。# data_gen.py 中典型的四模态装填流程 # 输出形状 (H, W, D, 4)最后一个维度是通道 import numpy as np import nibabel as nib def load_patient(case_dir, case_id): mods [t1, t1ce, t2, flair] vols [] for m in mods: img nib.load(f{case_dir}/{case_id}_{m}.nii.gz) vols.append(img.get_fdata().astype(np.float32)) vol np.stack(vols, axis-1) # (H, W, D, 4) seg nib.load(f{case_dir}/{case_id}_seg.nii.gz) label seg.get_fdata().astype(np.uint8) # (H, W, D) return vol, label这个流程里有几个参数会直接影响结果一是归一化方式常见的是对每个模态单独做 z-score也有全脑 clip 到 0.5%99.5% 分位数的做法我建议先 clip 再 z-score二是是否去掉颅骨以外的背景不处理的话很多 2D 切片几乎全黑模型把大量容量浪费在背景上三是标签重映射BraTS 原生标签里 1、2、4 分别代表坏死、水肿、增强核心如果做 2 类分割要先把 1、2、4 合并成 1做 4 类则要保持原样。metrics.py 里真正要盯的是这三个Dice、灵敏度/特异度、95% Hausdorff 距离。Dice 最常用但它对体积小的目标不敏感一个体素的分割错误在增强核心上可能让 Dice 掉 10 个点在全肿瘤上只掉 0.5 个点所以报告里必须同时给灵敏度。Hausdorff 距离是医学分割里的老规矩专门抓那些“Dice 高但边界飘了”的情况。# metrics.py 中的 Dice 计算注意 smooth 的作用 def dice_score(pred, label, smooth1e-5): pred (pred 0.5).astype(np.int32) inter (pred * label).sum() return (2.0 * inter smooth) / (pred.sum() label.sum() smooth)后面我会在避坑章节再展开说 Dice 的翻车细节这里先记住一个结论单一指标会骗人至少同时看 Dice、灵敏度和 95% HD 三个指标。3. 训练链路拆解从 data_gen.py 到 main.py 的完整运行流程3.1 数据准备多模态装填、切片方向与 3D patch 提取拿到项目以后我建议先把 data_gen.py 从头到尾读一遍不急着跑训练。这个文件决定了后面所有模型吃到的数据长什么样。除了上面提到的四模态装填还有两个直接决定训练效率的功能2D 切片生成和 3D patch 提取。2D 切片生成的核心就是一个索引方向问题。假设体积形状是 (240, 240, 155)轴状位切片就是 vol[:, :, d]d 从 0 到 154冠状位是 vol[:, h, :]矢状位是 vol[w, :, :]。看起来只是换下标但要注意三轴切片的数量不一样这会直接影响每个轴的训练步数配比数据不平衡时要把短的轴多做一组随机切片补足。# 2D 切片生成的关键三个方向的切片索引要完全同步到 label def extract_slices(vol, label, axis): # axis2 轴状位, axis0 冠状位, axis1 矢状位 if axis 2: return vol[:, :, :, :], label[:, :, :] # 后续按深度逐片取出 elif axis 0: return vol[:, :, :, :], label[:, :, :] # axis1 同理这里真正要小心的是 2D 模型的输入通道设计。四个模态沿最后一维堆叠后轴状位切片取出来就是一张四通道图通道顺序必须和训练时保持一致否则模型输出的特征图含义全乱。一个常见的低级错误是训练时用 t1、t1ce、t2、flair 顺序推理时却换了顺序肉眼根本看不出来但 Dice 直接掉 5 个点以上。3D patch 提取则是另一套逻辑。整块体积无法塞进显存常见做法是固定 patch size比如 64×64×32按 stride 滑动采样。采样策略有讲究全随机采样会把大量不含肿瘤的 patch 送进网络训练效率低常见做法是保留所有含前景的 patch再按 1:1 或者 2:1 的比例混入背景 patch。我一般用 1:1背景 patch 太多会让模型倾向于预测背景。# 3D patch 提取带前景保留和背景降采样 def extract_patches(vol, label, patch_size(64, 64, 32), stride(32, 32, 16)): patches, labels [], [] # 滑动窗口遍历并判断 patch 内是否有前景 for h in range(0, vol.shape[0] - patch_size[0] 1, stride[0]): for w in range(0, vol.shape[1] - patch_size[1] 1, stride[1]): for d in range(0, vol.shape[2] - patch_size[2] 1, stride[2]): p vol[h:hpatch_size[0], w:wpatch_size[1], d:dpatch_size[2]] l label[h:hpatch_size[0], w:wpatch_size[1], d:dpatch_size[2]] if l.sum() 0: patches.append(p); labels.append(l) elif background_count threshold: patches.append(p); labels.append(l) return np.stack(patches), np.stack(labels)这段代码里 stride 是控制重叠度的关键参数。stride 越小patch 重叠越高数据量越大等效训练步数越多stride 太大相邻 patch 之间可能漏掉肿瘤边界信息。对 240×240×155 的输入64×64×32 的 patch 配 32×32×16 的 stride大概能生成几千个有效 patch足够一个小规模毕设的数据量。3.2 训练入口与保存策略main.py、pretrain3d.py 怎么配合项目里 main.py 是主训练入口pretrain3d.py 是 3D 分支单独准备的预训练相关脚本。从我的复现经验看这个拆法是有道理的3D 模型参数量大直接随机初始化训练前期收敛很慢尤其在数据量只有几十例到一两百例的毕设场景下很容易过拟合。常见做法是先在小 patch 上跑一个粗训练让网络先学会基本的脑部结构再切到大 patch 精调。# 常见训练顺序先用 32^3 patch 粗训再用 64^3 精调 python pretrain3d.py --model unet3d --patch_size 32 32 32 --epochs 50 python main.py --model unet3d --patch_size 64 64 32 --epochs 200 \ --pretrained ./checkpoints/unet3d_p32_epoch50.pth这种两阶段训练的优点是显存友好粗训阶段模型收敛快精调阶段能直接继承粗训权重。要注意 pretrained 权重加载时最后一层全连接或输出卷积的类别数是否一致如果粗训跑的是 2 类精调要改成 4 类输出层权重会加载失败很多框架只会打一行 warning不细看日志根本发现不了后面避坑部分会展开。训练超参数上分割任务里最常改的就是学习率、batch size 和损失函数权重。项目里的 models.py 通常会给出每个模型的定义main.py 里是训练循环。我一般在 main.py 里加一个配置块把所有超参数集中管理而不是散在各个文件里不然换模型对比时容易记混。# main.py 中建议维护的统一配置区 config { model: vnet3d, # unet3d / vnet3d / 2d_axis batch_size: 4, # 3D 模型通常 2~62D 可以 16~32 lr: 3e-4, # 3D 模型推荐 1e-4~3e-42D 可以 1e-3 epochs: 200, loss: dice_bce, # 也常见 dice ce 加权组合 num_classes: 4, patch_size: [64, 64, 32], data_root: ./brats_data, save_dir: ./checkpoints, }这个配置里 batch size 对 3D 模型最敏感。同样的 patch sizebatch size 从 4 降到 2显存占用可能只少 20%但 BatchNorm3d 的统计量会变得很不稳定训练曲线会一直抖。如果显存不够我优先降 patch size 而不是降 batch size降到 batch size2 的 3D 模型结果波动大到让人怀疑代码写错了。3.3 三个结果 notebook 到底在看什么项目里有三个 notebook3d_Unet_results.ipynb、3d_vnet_results.ipynb、2d_max_results.ipynb。不要以为这只是三个画图的文件建议对照着看它们记录了每个模型跑完后的评估结果。3d_Unet_results 里一般放的是 3D UNet 在验证集上的 Dice 曲线和几张切片对比图3d_vnet_results 里是 VNet 的对应结果2d_max_results 里是 2D 三轴模型里表现最好的那份结果。三个 notebook 放一起正好能用来做基线报告里的对比表每个模型的 Dice、灵敏度、特异度、95% HD外加几张代表性切片。如果你的毕设答辩需要做消融对比这三份 notebook 是最省事的素材来源。# 从 notebook 中导出对比表的常见做法 results_summary { 2D-Axis-Ensemble: {dice: 0.871, sens: 0.902, hd95: 8.4}, 3D-UNet: {dice: 0.884, sens: 0.915, hd95: 7.1}, 3D-VNet: {dice: 0.892, sens: 0.921, hd95: 6.6}, }这里要提醒一句notebook 里显示的指标是在哪个验证集上算的一定要做好标记。如果训练阶段和测试阶段用了不同预处理方式指标会虚高或虚低。我发现这个项目里比较合理的验证方式是把 3D 模型和三轴集成模型放在同一验证集上跑指标直接横向可比不要拿各自的最优 epoch 结果来比那等于自己和自己最好的成绩比没意义。4. 生存预测模型落地回归、分类与 cox 模型的三种打开方式4.1 survival_data.csv 与标签组织回归和分类各自的标签口径项目里有一个 Survival Prediction 目录下面分 Regression Model 和 Classification Model 两个子目录还有 survival_data.csv、SVMfit.joblib、cox.joblib 这些文件。这个设计其实就是把脑肿瘤分割的后续价值延伸到了临床预测分割出肿瘤区域后结合患者的年龄等信息预测术后生存时长。survival_data.csv 里通常每一行是一个患者列包含年龄、肿瘤体积、水肿体积、增强核心体积、MGMT 启动子状态之类特征最后一列是生存天数或生存状态。回归模型的标签就是连续的天数分类模型则要把天数转换成类别比如常见做法是“小于 12 个月 / 大于等于 12 个月”二分类或者按 BraTS 的三分类区间 0-10 个月、10-15 个月、15 个月以上。# 生存天数转二分类标签的常见处理 import pandas as pd df pd.read_csv(survival_data.csv) df[label_cls] (df[survival_days] 365).astype(int) # 1 年阈值这里需要注意类别的平衡。生存数据里患者分布通常不均匀直接把 365 天作为阈值很可能某一类只占 20%模型训练时容易只预测多数类。我一般会先看标签分布如果偏得太厉害要么改用三分类要么在损失函数里给少数类加权而不是强行二分类。4.2 SVMfit.joblib 与 cox.joblib两种模型产出物怎么读项目里有两个 joblib 文件SVMfit.joblib 和 cox.joblib。joblib 是 sklearn 的标准序列化格式比 pickle 更适合存 numpy 数组和模型对象。SVMfit 代表训练好的支持向量机分类器cox 代表 Cox 比例风险回归模型。前者适合做二分类后者适合做生存分析它们解决的问题在毕设报告里可以写成“分类模型负责判断患者属于高风险组还是低风险组cox 模型负责输出风险比例”。读取和评估这些模型很简单# 读取训练好的模型并查看在验证集上的效果 import joblib clf joblib.load(SVMfit.joblib) # sklearn 分类器 cph joblib.load(cox.joblib) # 生存分析模型 # 对新样本做预测 pred_class clf.predict(X_new) risk_score cph.predict_partial_hazard(X_new)cox 模型的输出不是天数而是一个风险分数分数越高代表死亡风险越大。这在报告里要写清楚否则容易被质疑“你的预测值和真实生存天数对不上”。Cox 模型评估不能用普通分类准确率常见做法是算一致性指数 C-index或者把预测分数按中位数分成高风险、低风险两组画一条 Kaplan-Meier 曲线做生存分析这两样在毕设里都是很有说服力的图表。项目里还有个 pima.pickle.dat 文件我理解这是团队调模型时用来快速测试 sklearn 管线的辅助数据类似 UCI 的 pima 糖尿病数据集k_medoids 相关的 pyc 说明这套代码里也做过无监督聚类用来给生存样本分组。毕设报告里如果要把聚类加进去可以写成“用 k-medoids 对患者特征做亚型聚类再结合 cox 模型分析不同亚型的生存差异”这样整个生存预测部分就不只是套一个模型而是一个完整的分析流程。4.3 分割输出怎么接进生存特征生存预测模型消费的特征一部分来自临床信息一部分来自分割结果。分割模型输出的 mask 要转成特征常见做法是统计每个类别区域的体素数再乘上体素实际物理体积根据 NIfTI header 里的 spacing 换算得到全肿瘤体积、水肿体积、增强核心体积。这三个体积特征在 BraTS 生存预测里是最常用的影像特征。# 从分割 mask 提取体积特征 def extract_volume_features(mask, voxel_spacing): spacing_volume voxel_spacing[0] * voxel_spacing[1] * voxel_spacing[2] volumes {} volumes[whole_tumor] (mask 0).sum() * spacing_volume volumes[necrosis] (mask 1).sum() * spacing_volume volumes[edema] (mask 2).sum() * spacing_volume volumes[enhancing] (mask 3).sum() * spacing_volume return volumes这里能看出整个项目为什么要把分割和生存预测放在一起分割结果是生存模型的输入特征来源之一缺了分割这步只有年龄和临床指标生存预测模型的效果很难撑起一篇毕设。把分割出的体积特征和临床特征拼成一个特征向量训练 SVM 或 cox检查每个特征的系数/权重通常肿瘤体积和增强核心体积是风险预测里贡献最大的两个特征这个结论可以直接写进报告结果。5. 避坑与排查训练 3D 分割网络最容易翻车的五个问题5.1 显存不足patch_size 与 batch_size 的真实比例现象训练 3D-UNet 时一启动就报 CUDA out of memory或者跑到一半直接被杀进程。原因3D 卷积的显存占用不是线性增长的。patch size 从 64×64×32 变成 96×96×48特征图体积翻了 3.375 倍中间特征存储可能翻 5 倍以上。调大 patch 的同时没调小 batch size也没有关闭中间变量的梯度缓存很容易爆显存。解决优先缩小 patch size再缩小 batch size尽量保持 batch size ≥ 3 以保证 BatchNorm3d 稳定。我通常先在 64×64×32 上验证一个 epoch 能跑完再逐步尝试 80×80×40。另外可以在数据加载端把 NIfTI 数据转成 float16 再进模型注意只影响输入张量不影响模型权重精度实际效果比想象中好。5.2 2D 和 3D 结果差异大归一化策略不一致现象同一个验证样本2D 三轴模型的 Dice 到 0.873D-UNet 只有 0.79以为是 3D 模型没训好最后发现是预处理不一致。原因2D 切片训练时做的是逐切片归一化3D patch 训练做的是逐 patch 归一化两者均值方差不一样。推理时如果用了不同的归一化统计量模型输出分布会漂移。解决把归一化统一到“先对整个体积做全局 clip再做 z-score”这样无论 2D 还是 3D 分支单个切片或 patch 的数值分布都来自同一个全局统计量。我习惯把 clip 的百分位和 z-score 的均值和方差存成一个 json训练和推理都从这同一个 json 读从根上杜绝不一致。5.3 预训练权重加载失败维度对不上还没报错现象pretrain3d.py 生成的权重给 main.py 加载时程序没崩但训练曲线一开始就异常高或 Dice 一直压在 0.1 附近。原因权重文件里最后一层输出通道数是 2模型定义是 4 类加载时框架自动把不匹配的层跳过并且只打印一行 warning不细看日志很难发现。还有一个更隐蔽的坑编码器第一层输入通道数 4 对不上原训练时的 1整个编码器都会加载失败。解决加载预训练权重后打印一下 model 各层参数的 loaded 状态。我一般会写一个小脚本遍历 state_dict统计成功加载和跳过的层数确保只有输出层被跳过。如果输入通道数都不一致那就不能用 load_state_dict(strictFalse)需要手动分层加载特征提取部分。5.4 生存预测数据对不齐患者 ID 和特征顺序现象SVMfit.joblib 在训练集上准确率很高但真正用分割结果生成的新样本预测时结果完全不合理。原因训练阶段用的特征顺序和推理阶段构造特征时不一致。比如训练时是 [age, volume, edema, enhancing]推理时漏了某个特征或者 DataFrame 的列顺序被 pandas 重新排过模型实际读取的权重对应的特征就全错了。解决把特征列顺序固定在一个列表里训练和推理共用同一个列表进模型前强制按列表顺序重排。另外生存标签和特征来自同一个 CSV 的不同行时一定要按患者 ID 做 inner join不要直接按行号拼接数据筛选后行号会错位这是最容易出问题的环节。5.5 Dice 异常波动空标签与类别不平衡现象训练过程中 Dice 曲线出现周期性骤降从 0.9 掉到 0.3下一秒又弹回去或在某几个类上 Dice 永远是 0。原因验证集里某些样本的某个类别标签为空比如没有增强核心的病例预测一旦出现少量假阳Dice 就从 0 变成一个小值波动被放大训练时类别不平衡也会让模型直接把稀有类预测为零。解决计算指标时先把真实标签和预测结果中都没有该类别的样本剔除不参与平均类别不平衡时给稀有类在 loss 里加权或者对稀有类做上采样增强。报告里描述指标时明确写出哪些样本被排除这不丢人反而说明你对指标定义有理解。6. 验证与进阶把三套模型的结果串成一份能过审的实验记录6.1 一个可复现的推理验证链模型训完别急着写报告先跑一条完整推理链从一张没见过的 NIfTI 图像开始走完预处理、2D 三轴推理并集成、3D 模型推理、体积特征提取、生存模型预测最后输出一份包含预测 mask 和风险分数的结果文件。这件事做一遍等于把整个项目串起来验证了一次同时也暴露只在训练脚本里看不出来的问题。# 端到端推理链的骨架 mask_2d integrate_axis_predictions(model_2d_a1, model_2d_a2, model_2d_a3, volume) mask_3d model_3d.predict(volume) final_mask majority_vote(mask_2d, mask_3d) # 注意类别对齐 features extract_volume_features(final_mask, spacing) features pd.DataFrame([{**clinical_info, **features}])[FEATURE_COLUMNS] survival_risk cph.predict_partial_hazard(features)关键一步是把 final_mask 的类别编号统一。2D 三轴模型和 3D 模型如果是不同类别体系比如一个输出 2 类一个输出 4 类直接投票会冲突。先统一到同一类别映射再投票。这一步我建议在项目一开始就定好不要到集成阶段再来改标签。6.2 记录超参数、随机种子和版本的小习惯我拆完这个项目最大的感受是代码能复现但“复现效果”不一定能复现。3D 模型训练曲线的抖动很多时候不是代码问题而是随机种子、数据顺序、PyTorch 版本差异造成的。从那以后我每次跑训练前都会强制做三件事固定 torch 和 numpy 的随机种子把 config 连同模型结构一起 dump 成 json 存到 checkpoint 同目录记录当前 Python 和 PyTorch 版本号。答辩的时候如果被问“为什么 3D-VNet 比 3D-UNet 高 0.5 个 Dice”把训练配置表拿出来就能说清而不是当场回忆。希望这份拆解能帮你少走几步弯路祝复现顺利。本文还有配套的精品资源点击获取
返回列表