ARTICLE DETAIL

资讯详情

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

使用GAN进行3D肝脏分割:从CT预处理到训练避坑全解析

使用GAN进行3D肝脏分割:从CT预处理到训练避坑全解析 简介面向医疗图像分析与深度学习研究者或学生的三维肝脏分割实战资源基于生成对抗网络GAN在Python/Jupyter Notebook环境中完成模型构建、训练与评估适用于疾病诊断、治疗规划与手术导航等医学场景。压缩包共12个文件含4个Python脚本、2张网络结构图、1个Jupyter Notebook及依赖与说明文档整体仅529KB。目前已有108人学习下载。资源从数据预处理、三维卷积网络搭建到生成器与判别器的对抗训练、损失函数与优化器选择再到Dice/Jaccard等评估指标计算均有完整代码与说明支撑同时提供可直接运行的实验骨架、集群训练脚本与可视化结果便于读者快速复现GAN医学分割流程。无论用于课程设计、毕业项目还是科研实验都能借助这套轻量资源快速上手。1. 使用GAN进行3D肝脏分割先说清楚这个zip到底值不值得打开拿到一个“使用GAN进行3D肝脏分割_Python_Jupyter Notebook_下载.zip”的项目包你大概想知道三件事这东西是不是又是换个皮把U-Net跑一遍跑起来需要多大显存以及GAN到底在这里解决了什么纯分割模型解决不了的问题。先说结论GAN在这个任务里不是噱头CT影像中肝脏与周围组织灰度接近、边界模糊纯监督分割容易被模糊标注带偏而生成对抗训练等于给分割网络配了一个“挑刺的评委”逼着预测结果从形状和纹理上都更像真实标注。这个项目对医学图像算法工程师、做3D视觉的研究生以及想从2D分割转3D的从业者都值得过一遍。文章后面我会按“项目结构 → 数据预处理 → 生成器与判别器调参 → 踩坑记录 → 验证技巧”一条线讲清楚。2. 读懂项目结构从下载的zip到跑通一次训练2.1 zip里应该有什么核心目录与文件职责这类GAN分割项目zip包的内部结构通常不会长得一模一样但主干模块基本一致。我一般会用一条命令先摸清家底再决定从哪个文件开始读unzip 使用GAN进行3D肝脏分割_Python_Jupyter_Notebook_下载.zip -d liver_gan cd liver_gan find . -maxdepth 2 -type f | sort | head -50find这步是为了让你在动手之前先建立文件地图。常见结构是这样的data/放原始NIfTI和预处理产物models/放生成器和判别器定义configs/放超参数yaml或pytrain.py是训练入口inference.py或一个predict.ipynb负责推理和可视化。如果连requirements.txt都没有你需要做好手动补依赖的准备。目录/文件典型职责你要重点看什么data/raw原始CT和标签文件是NIfTI(.nii/.nii.gz)还是H5data/preprocessed裁剪后的patch或归一化数组patch大小、归一化范围是否写死models/generator.py生成器网络结构是3D U-Net变体还是VNetmodels/discriminator.py判别器结构是全局打分还是PatchGANlosses/组合损失对抗损失权重、Dice/BCE占比train.py训练主循环默认超参数和断点续训逻辑configs/参数配置batch size、patch size、学习率notebook/可视化或小规模demoJupyter Notebook里跑的推理示例注意一点很多项目的“3D”其实是假3D内部直接调用2D卷积逐层切片处理。判断标准是看models/generator.py里有没有Conv3d、ConvTranspose3d这些算子。标题既然写的是3D分割你应该优先找真正使用3D卷积的版本因为肝脏CT是体数据切片间连续性对分割精度影响很大。2.2 环境准备Python、CUDA、PyTorch与MONAI的版本搭配这类医学影像项目跑不起来的原因十有八九是环境版本互相打架。肝脏分割项目通常依赖PyTorch、NiBabel或SimpleITK做数据读写、MONAI做数据增强与评估指标。常见做法是用conda单独建环境避免把系统Python搞乱conda create -n liver_gan python3.8 -y conda activate liver_gan conda install pytorch torchvision pytorch-cuda11.8 -c pytorch -c nvidia pip install monai nibabel simpleitk scikit-image matplotlib tensorboardPython 3.8到3.10是这类项目兼容性最稳的区间太新的版本可能会踩到MONAI或旧版PyTorch的依赖坑。CUDA版本要看你的显卡驱动使用nvidia-smi确认驱动支持的最高CUDA版本再选对应的PyTorch轮子。如果只是推理不训练CPU也能跑但3D卷积加GAN的推理速度会让你失去耐心建议至少要有一张8GB以上显存的卡。python -c import torch; print(torch.__version__, torch.cuda.is_available())看到True只代表CUDA可用不代表你能跑。下一步你要确认显存是否足够这直接决定了该项目的patch size和batch size该怎么设置。很多项目默认配置是按16GB以上显存写的8GB卡跑python train.py大概率立刻OOM这是最普遍的翻车点。2.3 第一条训练命令参数含义与最小可跑配置在改任何代码之前先看一眼train.py的入口参数。这类项目的启动命令通常长这样python train.py \ --data ./data/preprocessed \ --config ./configs/lits_gan.yaml \ --batch_size 2 \ --patch_size 96 \ --lr 1e-4 \ --lambda_adv 0.1 \ --epochs 200--lambda_adv是对抗损失权重数值太小等于没有GAN太大则可能让网络陷入振荡甚至生成畸变结构。--patch_size是每次喂给网络的3D块边长不是2D图像边长它决定了显存占用的立方级增长。--batch_size 2配合--patch_size 96是8到12GB显存的一个保守起点。第一次跑通不要追求指标建议把--epochs调小到10以内确认数据加载、前向反向、权重保存整条链路能走通。等日志里出现epoch和loss稳定输出再把它调回正常规模。这一步很重要因为GAN训练本来就不稳定如果链路都没通你看到的loss数值没有任何意义。运行过程中关注这几项训练loss是否下降、判别器loss是否为零、每轮保存的验证Dice是否在动。如果Dice迭代几十步都纹丝不动说明生成器可能根本没被有效训练后面第5章的踩坑清单里会有对应的排查方法。3. 把CT体数据喂给GAN预处理与3D patch裁剪3.1 从NIfTI到numpy数组归一化、重采样与HU截断肝脏CT数据以Hounsfield UnitHU表示组织密度直接拿原始值喂网络会出问题。不同扫描设备的HU范围一致但肝脏与周围组织相对关系才是关键所以行业通行做法是先把CT值截断到一个合理区间再做归一化。肝脏区域大致在-100到400HU之间常见截断范围是[-150, 250]或[-100, 300]具体看数据集标注习惯。import SimpleITK as sitk import numpy as np def preprocess_ct(image_path, label_path, out_path, lower-150, upper250): itk_img sitk.ReadImage(image_path) itk_lab sitk.ReadImage(label_path) img sitk.GetArrayFromImage(itk_img).astype(np.float32) lab sitk.GetArrayFromImage(itk_lab).astype(np.uint8) # HU截断与归一化截断后线性映射到[0,1] img np.clip(img, lower, upper) img (img - lower) / (upper - lower) # 标签二值化非0视为肝脏 lab (lab 0).astype(np.uint8) np.savez_compressed(out_path, imgimg, lablab) print(fshape{img.shape}, 前景比例{(lab 0).mean():.4f})注释里写的前景比例是按体素算的医学解剖中肝脏在腹部CT的占比通常只有5%到15%这个数字你要记住后面设计损失函数全靠它。np.savez_compressed是这类项目常用的中间格式读写快、无需额外依赖比直接存NIfTI再反复读取更省时间。重采样这一步我单独说一下。很多原始CT的层间距是5mm或更厚而层内分辨率是0.7mm左右导致体素各向异性严重。3D卷积在特征提取时会把每个体素当成同等物理大小这一假设在厚层数据上不成立。常见处理是重采样到各向同性比如1.5mm或2.0mmdef resample_to_isotropic(itk_img, target_spacing1.5): original_spacing itk_img.GetSpacing() original_size itk_img.GetSize() new_size [ int(round(original_size[0] * original_spacing[0] / target_spacing)), int(round(original_size[1] * original_spacing[1] / target_spacing)), int(round(original_size[2] * original_spacing[2] / target_spacing)), ] resampler sitk.ResampleImageFilter() resampler.SetOutputSpacing([target_spacing] * 3) resampler.SetSize(new_size) resampler.SetInterpolator(sitk.sitkLinear) return resampler.Execute(itk_img)调用它时注意图像用线性插值标签必须用最近邻插值否则肝脏边缘会被插出中间灰度值训练时判别器会抓住这些伪影做文章。这一步你要是拿同一个重采样函数处理图像和标签分割结果边缘会出现一圈灰色晕这是明显的低级错误。3.2 用滑动窗口裁patch显存有限时的标准做法3D体数据整卷喂进3D卷积网络是不现实的一张512×512×300的CT体积远超显存容量。标准做法是训练时随机裁剪固定大小的patch推理时用滑动窗口拼回全图。裁剪策略直接影响训练效果纯随机裁剪会让大部分patch落到背景区肝脏占比过低判别器很容易学会“见背景就判假”。class RandomPatchSampler: def __init__(self, img, lab, patch_size96, foreground_fraction0.5): self.img img self.lab lab self.patch_size patch_size self.foreground_fraction foreground_fraction # 强制50% patch包含肝脏 def sample(self): p self.patch_size z, y, x self.img.shape if np.random.rand() self.foreground_fraction: # 以肝脏前景体素为中心采样 fg np.argwhere(self.lab 0) if len(fg) 0: cz, cy, cx fg[np.random.randint(len(fg))] cz min(max(cz, p // 2), z - p // 2) cy min(max(cy, p // 2), y - p // 2) cx min(max(cx, p // 2), x - p // 2) else: cz, cy, cx [np.random.randint(p//2, s-p//2) for s in (z, y, x)] else: cz, cy, cx [np.random.randint(p//2, s-p//2) for s in (z, y, x)] return (self.img[cz-p//2:czp//2, cy-p//2:cyp//2, cx-p//2:cxp//2], self.lab[cz-p//2:czp//2, cy-p//2:cyp//2, cx-p//2:cxp//2])这段代码用foreground_fraction控制采样的偏向0.5意味着每个batch至少一半的patch包含肝实质。实际训练中你会发现纯随机采样时一个epoch里大量样本都是纯背景生成器根本没机会学肝脏内部结构。加了前景引导采样后训练收敛速度能快出不少。patch大小怎么定96×96×96是通用起点。显存紧张时降到64显存充裕又能承受更慢训练时用128。注意一个隐藏陷阱patch越大GAN判别器越容易从全局形状上指出“这是假的”但这会让训练成本立方级上涨patch太小判别器只看局部纹理又会失去对抗训练对整体形状的约束能力。所以这个参数不是越大越好你的GPU预算决定上限分割目标决定下限。3.3 标签不均衡处理前景占比只有5%时怎么办肝脏在CT体数据中占比很小如果直接对每个体素做二分类交叉熵网络可以靠全部预测为背景拿到95%准确率。这时网络看似很“聪明”实际分割结果是全黑。纯监督分割任务里常用Dice损失缓解这个问题GAN路线里同样要处理但方式略有不同。def dice_loss(pred, target, smooth1e-5): pred torch.sigmoid(pred) intersection (pred * target).sum(dim(2, 3, 4)) union pred.sum(dim(2, 3, 4)) target.sum(dim(2, 3, 4)) return 1 - ((2 * intersection smooth) / (union smooth)).mean()这里的dice_loss按3D卷计算Dice直接作用在logits上配合bce一起用。训练配置里通常写成loss dice_loss bce_weight * bce lambda_adv * adversarial_loss。Dice损失对前景占比不敏感它天然关注的是重叠度而不是像素正确率。另外你可以在配置里给标签做类别权重比如背景权重0.1、前景权重1.0。但我不建议权重拉得过大因为GAN的判别器会同时看到生成结果和真实标签如果生成器被标签权重逼得过分激进生成的肝脏形状会偏向“大而全”把胆囊、下腔静脉这些相邻结构也包进来。遇到这种问题时最需要调的不是类别权重而是第4章要讲的对抗损失权重和判别器结构。4. 生成器与判别器的关键参数想让GAN稳定收敛该调什么4.1 生成器选型3D U-Net还是VNet以及损失权重肝脏分割的生成器几乎清一色是编码器-解码器结构区别在细节。3D U-Net用4到5层下采样每层特征通道翻倍跳跃连接把浅层细节传回来适合边界精细的分割任务。VNet在U-Net基础上加了残差连接前向传播更稳定梯度流动更顺滑GAN这种对抗训练环境下崩溃概率更低。如果是自己改代码我建议先ResNet风格的VNet起步。class Generator(nn.Module): def __init__(self, in_channels1, base_channels32): super().__init__() self.enc1 self._block(in_channels, base_channels, stride1) self.enc2 self._block(base_channels, base_channels * 2, stride2) self.enc3 self._block(base_channels * 2, base_channels * 4, stride2) self.dec3 self._block(base_channels * 4, base_channels * 2, stride1) self.dec2 self._block(base_channels * 2, base_channels, stride1) self.out nn.Conv3d(base_channels, 1, kernel_size1) def _block(self, in_ch, out_ch, stride): return nn.Sequential( nn.Conv3d(in_ch, out_ch, 3, padding1, stridestride), nn.InstanceNorm3d(out_ch), nn.LeakyReLU(0.2, inplaceTrue) ) def forward(self, x): e1 self.enc1(x) e2 self.enc2(e1) e3 self.enc3(e2) d3 self.dec3(e3) d2 self.dec2(d3 e2) return self.out(d2)这是压缩版结构真正跑实验时你会把跳跃连接补全。两个细节值得注意第一InstanceNorm3d比BatchNorm3d更适配GAN训练因为判别器拿到的batch里的真实样本和生成样本统计量本来就不同BN会把两组数据混在一起归一化容易震荡。第二生成器输出前不加Sigmoid让nn.BCEWithLogitsLoss在数值上更稳定也方便Dice损失直接从logits计算。损失权重方面lambda_adv从0.01到0.1之间起步是经验值。权重太小输出接近纯U-Net边界依旧模糊权重太大生成器为了骗过判别器可能牺牲结构准确性出现支离破碎的空洞。这个参数每次翻倍或减半地调观察验证集Dice的变化别靠猜。4.2 判别器与对抗损失PatchGAN感受野和梯度惩罚判别器的任务不是判断“这张图是CT还是标签图”而是判断“分割结果和真实标注像不像”。常用的判别器有两类整图打一个真/假分数的全局判别器和PatchGAN这种输出一张响应图、对每个局部块分别打分的结构。3D分割场景下我几乎不用全局判别器因为生成结果和标注的差異往往只集中在肝脏边缘这层很薄的体素上全局打分很容易被大面积背景淹没——网络直接给全部patch判真就能把loss压到很低。class PatchDiscriminator3D(nn.Module): def __init__(self, in_channels2, base_channels64): super().__init__() self.layers nn.Sequential( nn.Conv3d(in_channels, base_channels, 4, stride2, padding1), nn.LeakyReLU(0.2, inplaceTrue), nn.Conv3d(base_channels, base_channels * 2, 4, stride2, padding1), nn.InstanceNorm3d(base_channels * 2), nn.LeakyReLU(0.2, inplaceTrue), nn.Conv3d(base_channels * 2, 1, 4, stride1, padding1) ) def forward(self, x): out self.layers(x) return out.view(x.size(0), -1).mean(dim1)判别器输入是2个通道一个通道是真实或生成的二值分割图另一个通道是对应的CT图像作为条件。这个“条件”非常关键它让判别器知道当前要评判的是“这张CT下的肝脏分割图”而不是毫无上下文的随机二值形状。对抗损失用哪种形式直接决定你要写的训练代码。常见做法是用WGAN-GP取代原始的log loss因为原始BCE在判别器训练得特别好时梯度容易消失生成器学不到东西。WGAN-GP要求判别器输出的是一个实数分值并且对输入求梯度加入惩罚项。如果你看到项目里train.py有gradient_penalty相关代码说明作者已经踩过梯度消失的坑。4.3 训练节奏先跑自监督预热再开对抗GAN训练最让人头疼的是两个网络互相博弈导致的震荡。纯GAN从零开始训练时判别器往往先一步收敛生成器还在瞎猜结果对抗损失一路飙升。这种场景下全盘训练是白费力普遍的做法是分成两个阶段先关掉判别器只用Dice加BCE训练生成器几十个epoch让分割网络先学会一个像样的初版结果再打开对抗损失用一个“实力接近”的判别器促使边界变得更锐利。# 阶段一预热禁用对抗分支 python train.py --phase warmup --warmup_epochs 30 --lambda_adv 0 # 阶段二正式训练从warmup权重继续 python train.py --phase gan --resume ./checkpoints/liver_warmup.pth \ --lambda_adv 0.05 --lr 1e-4两个阶段的--lr最好都不超过1e-4。GAN里生成器更新时判别器输出的分值大致反映了生成器的真实水平如果判别器太弱生成器会拿不到有效梯度这又是另一个极端。一个实用的检查方式是打印判别器对真实样本和生成样本的平均输出分值理想状态是真实分值略高于0但因为梯度惩罚被限制在0附近生成分值从负往0爬。如果真实分值为1、生成分值为0网络已经饱和生成器已经学不到东西。训练过程中--batch_size 2或4是3D分割的常态。显存允许时适当调大batch会让判别器梯度更稳定但如果只能跑batch size 1也不是不能训只是判别器每次只看到一对样本震荡会更明显。这种情况我一般会给判别器加谱归一化把整个训练搅在一起的黑匣子问题留给第5章细说。5. 训练避坑五个让GAN分割翻车的常见问题5.1 显存溢出3D卷积的显存是立方级膨胀现象运行python train.py不到一个迭代报错CUDA out of memory。原因3D卷积的特征图体积随patch边长三次方增长96³的patch在第4层特征图已经是6×6×6但这个过程的中间变量会同时占据显存。很多人习惯了2D网络的参数习惯把batch size设成8或16在3D任务里直接爆显存。解决先减batch size再减patch size。--batch_size 1 --patch_size 64是大多数消费级显卡能跑的组合。还不够就用梯度累积例如--grad_accum 4把4个小batch的梯度累加后再更新一次参数等效于batch size 4但不吃显存。MONAI还提供CachedDataset配合pin_memory的优化选项能再省一点。如果改完仍然OOM检查是不是复用了两次输入到判别器生成和真实各一次显存占用翻倍。5.2 训练出现NaNGAN训练不稳定的一大源头现象训练日志里loss正常但某一步后突然变成NaN之后一路NaN下去。原因最常见的两类原因学习率过大导致loss爆炸判别器梯度数值失控梯度惩罚项计算过程出现除以零或无穷大。出现NaN后整段训练已经报废这时候没有后悔药只能恢复检查点重新来所以模型要养成每10个epoch保存一次的习惯。解决把生成器和判别器的学习率降到5e-5同时给生成器梯度做clip常用做法是torch.nn.utils.clip_grad_norm_(g_model.parameters(), max_norm12.0)。对WGAN-GP检查梯度惩罚项里是否对K1做了幂函数计算K值过大会让惩罚项爆炸常见范围是0.1到1.0。这类问题定位时先看log里最后一次正常迭代的loss数值如果之前已经到几百或上千那不是指标问题是训练早就偏了。5.3 肝脏边缘依旧模糊说明对抗损失没有真正作用到边界现象Dice有0.85以上但可视化切片里肝脏轮廓和背景过渡区域灰蒙蒙一片预测概率集中在0.5附近。原因对抗损失的权重太低判别器感受野过大它只关心整体形状而不关注边界局部细节。另一个可能原因是生成器输出层用了Sigmoid加MSE这类组合会让模型倾向于输出平滑概率而不是锐利的0/1边界。解决把--lambda_adv从0.01逐步提到0.1或0.2切换成带边缘项的判别器输入。我常用的做法是把判别器的输入从整张CT图替换为CT图的sobel梯度图与分割图的拼接让判别器注意力直接落在高梯度区域。梯度图计算用3D Sobel代码量不大但它让对抗信号直接作用于边界效果立竿见影。5.4 判别器loss为0且保持不动生成器已经“骗”不过去了现象判别器对真实样本输出总为1对生成样本总为0D_loss稳定在某个很低值。原因判别器训练得太强生成器一开始输出的结构差距太大判别器不需要花什么力气就能区分。这时判别器的梯度是饱和的生成器从它那里得到的反馈几乎为零训练进入停滞。解决先降低判别器学习率比如d_lr 0.25 * g_lr再给判别器加标签平滑真实标签用0.9而不是1.0最直接做法是限制判别器的更新频率生成器每更新2到3次判别器才更新1次。训练脚本里通常有n_critic参数从默认1改成3或5让生成器有更多机会追赶。5.5 Jupyter Notebook里跑训练直接卡死内存叠加与默认路径两个坑现象在Jupyter里执行训练单元格几十步之后页面转圈内核无响应或重启后模型加载不到原来保存的路径不存在。原因Notebook的内核持续占用显存和内存你反复执行单元格时旧的DataLoader和新进程叠在一起内存堆积到系统OOM。另一个很常见但容易被忽略的问题notebook默认保存路径和train.py读取的路径不一致工作目录一换相对路径全部失效模型权重没被写到你期望的地方。解决训练代码不要放在Jupyter单元格里跑用.py脚本在终端启动notebook只做数据探索和推理可视化。如果一定要在notebook里跑先执行gc.collect()释放旧变量再每个epoch用torch.cuda.empty_cache()清一次缓存。路径问题用这行代码解决import os os.chdir(os.path.dirname(os.path.abspath(__file__)))放到脚本最顶部确保所有相对路径都基于脚本所在目录解析而不是随Jupyter的工作目录漂移。其实这一点也适用于所有实验路径如果乱了比模型不收敛更让人崩溃因为前者是确定性的错误要靠重复劳动来补数据。6. 验证分割结果Dice之外还要看什么6.1 二维切片可视化在Jupyter里直接对比训练中期开始每轮结束都用Jupyter加载验证样本把预测结果画成切片图比只看Dice数字靠谱得多。肝脏分割的边界质量、过分割欠分割区域都要靠肉眼判断因为Dice只能反映重叠体积的总体比例无法体现边缘是锯齿还是光滑、有没有漏掉小的肝段。可视化代码不复杂import matplotlib.pyplot as plt import numpy as np def visualize_slice(ct, label, pred, slice_idx): fig, axes plt.subplots(1, 3, figsize(15, 5)) axes[0].imshow(ct[slice_idx], cmapgray) axes[0].set_title(CT) axes[1].imshow(label[slice_idx], cmapgray) axes[1].contour(label[slice_idx] 0, colorsr) axes[1].set_title(Ground Truth) axes[2].imshow(pred[slice_idx], cmapgray) axes[2].contour(pred[slice_idx] 0.5, colorsr) axes[2].set_title(Prediction) plt.show() # 加载验证集第一个样本的预测结果 pred np.load(./results/sample_pred.npy) ct_vol np.load(./data/preprocessed/sample_img.npy) lab_vol np.load(./data/preprocessed/sample_lab.npy) visualize_slice(ct_vol, lab_vol, pred, slice_idx40)轮廓叠加用的是0.5的二值阈值实际看结果时可以多试0.3和0.7感受概率图的不同切分对体积的影响。边缘处的弹簧状锯齿说明patch之间存在拼接痕迹需要在推理时加重叠融合。6.2 全卷推理的滑动窗口实现推理时用滑动窗口把完整CT扫描切成重叠patch逐块预测再拼回重叠区域取平均值能有效避免patch边界出现明显的接缝。代码逻辑如下def predict_volume(model, volume, patch_size96, stride48): z, y, x volume.shape output np.zeros_like(volume, dtypenp.float32) weight_map np.zeros_like(volume, dtypenp.float32) for cz in range(0, z - patch_size 1, stride): for cy in range(0, y - patch_size 1, stride): for cx in range(0, x - patch_size 1, stride): patch volume[cz:czpatch_size, cy:cypatch_size, cx:cxpatch_size] logit model(torch.tensor(patch).unsqueeze(0).unsqueeze(0).cuda()) output[cz:czpatch_size, cy:cypatch_size, cx:cxpatch_size] torch.sigmoid(logit).cpu().numpy()[0, 0] weight_map[cz:czpatch_size, cy:cypatch_size, cx:cxpatch_size] 1.0 output / np.maximum(weight_map, 1e-6) return output--stride 48表示patch之间有50%重叠重叠区域被多次预测、取平均拼接处的连续性会好很多。边界不足一个patch的地方需要补零或反射填充否则边缘区域的预测结果会缺失。这个推理代码里没有做torch.no_grad()在推理时一定要加上否则会把中间变量全部塞进显存照样OOM。最后聊聊我自己的习惯。GAN分割项目跑通到成型关键不在网络结构抄哪个模型而在验证闭环做得到不到位——我用的是“先可视化切片、再评估Dice、最后看预测体积和标注体积的差值”这套顺序因为一眼看上去奇怪的预测通常已经从体积上讲不清了。每次调参前我都会把数据路径、训练命令、hash过的配置参数记录到实验笔记里防止两周后对着一个0.9的Dice不知道这个分数是哪个版本的代码产生的。如果你现在刚打开这个zip建议先按第2章的最小命令跑通10个epoch用第6章的切片可视化看一眼输出再决定要不要往下投入调参单看loss曲线或者等待别人口中“这个项目跑出来的状态很好”是不太够的。希望帮到你。本文还有配套的精品资源点击获取
返回列表