ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Unet医学影像分割实战:编码器解码器结构与Dice Loss调参全攻略

Unet医学影像分割实战:编码器解码器结构与Dice Loss调参全攻略 简介一套完整的基于Unet的医学影像分割系统源码面向人工智能、计算机等专业学生及医学影像方向初学者可应用于毕业设计、课程设计或项目初期演示。工程涵盖数据预处理、模型构建、训练预测、交互界面与指标评估等环节代码经测试均运行成功答辩平均分达96分可直接学习或二次修改。压缩包内共76个文件涵盖Python源码、模型文件、json标注、png/jpg图像集、csv评估表及PDF文档等类型其中unet_model、train、predict等脚本串起完整分割流程另有labelme2seg、gen_split等辅助工具配合截图演示和安装教程可显著降低上手门槛。整体仅4.61MB却已包含ISIC样本图、分割结果图、Unet原版论文、训练模型及README说明能帮助读者理解网络结构、训练逻辑与调参思路。已有217人浏览学习尤其适合从零搭建医学影像分割系统的高年级本科生和研究生参考。1. 医学影像分割为什么绕不开Unet从一张CT切片到像素级判断一位做肺癌筛查的朋友找我帮忙说手上有几百张肺部CT切片需要把结节区域逐像素标出来给后续诊断做参考。手工标注一张切片要十几分钟而且不同医生标出来的边界还不一致。这类把影像里感兴趣区域精确框出来的需求正是医学影像分割要解决的问题。Unet从2015年提出至今在医学影像领域依然是绕不开的基线模型——它结构简洁、对少量数据友好、分割精度高医院里大量实际落地的分割系统跑的还是Unet及其变体。这篇笔记把Unet医学影像分割从原理、数据准备、训练调参到踩坑经验完整讲一遍适合正在做课题的研究生、要落地影像辅助工具的工程师以及准备拿Unet做毕业设计的本科同学。2. Unet的核心设计编码器-解码器结构为什么适合医学影像2.1 收缩路径与扩张路径Unet对小样本大病灶的结构性妥协Unet名字来源于它U形的网络结构左边是收缩路径编码器右边是扩张路径解码器。编码器通过一次次下采样把图像分辨率降下来、通道数升上去让网络逐步从像素细节抽象到语义特征解码器再通过上采样把低分辨率的高层语义特征恢复到原始分辨率。这组对称的结构让Unet天然适合医学影像——医学数据集往往只有几十到几百例网络如果一开始就用很深的残差结构很容易过拟合而Unet浅层抓细节、深层抓语义的分工正好在模型容量和可训练性之间取得平衡。编码器的每一次下采样都搭配两个连续的3×3卷积和一个ReLU激活然后接一个2×2最大池化。每下采样一次特征图尺寸减半、通道数翻倍。这种逐步加大感受野的做法保证网络在深层能看到更大范围的上下文信息。对于CT中直径几十像素的小结节浅层特征能保留边界纹理对于MRI中占据大片区域的肿瘤深层特征能感知整体轮廓和与周围组织的关系。解码器部分每次先做一个2×2上采样转置卷积或双线性插值把特征图尺寸恢复一倍、通道数减半再把编码器同层级的特征拼接过来做融合。这里要强调的是Unet的跳连拼接concatenation不是简单相加而是把编码器下采样前的特征图和解码器当前层级的特征图在通道维度上拼起来——浅层细节信息和深层语义信息真正被揉在了一起。2.2 跳连不是锦上添花梯度传递与细节恢复的两张王牌很多初学者以为Unet的跳连只是为了让解码器多拿到一些纹理信息实际上它的作用远不止于此。在深层网络中梯度从输出端向输入端回传时会经历多次卷积和池化逐层衰减。Unet的跳连为梯度提供了一条高速公路编码器浅层的梯度可以经由跳连直接流向解码器的对应层这显著缓解了深层Unet训练时的梯度消失问题。这也是为什么Unet在加深到四层、五层时依然能稳定收敛而同等深度的纯卷积网络往往很难训练。从分割任务本身来看像素级分类要求输出每个像素属于前景还是背景这个任务天然需要细节定位能力。如果把解码器想象成一张从粗到细的复原过程每经过一次上采样图像分辨率都会增大但单纯的上采样已经丢失的高频细节是无法凭空生成的。跳连把编码器在相同分辨率下还没被池化抹掉的细节直接递给解码器让网络既能理解这是什么又能说清楚它在哪。我在实际项目中会把Unet的跳连按重要性排个序第一层和第二层的跳连对分割边缘质量影响最大因为这两层保留的是最原始的像素纹理第三层和第四层的跳连主要提供语义上下文对区分容易混淆的区域比如肿瘤和炎症帮助更大。如果要做模型裁剪优先砍深层的通道数而不是浅层的跳连分割边界的损失最小。2.3 损失函数与评价指标Dice Loss和交叉熵怎么配合用医学影像分割的损失函数选择和自然图像分割有明显区别。医学影像里前景区域往往非常小一个肺结节可能只占整张切片的1%背景像素占比巨大。如果直接用像素级交叉熵损失Cross Entropy网络只需要把所有像素预测为背景就能得到非常低的损失值模型根本学不到前景。针对这个问题Dice Loss是医学影像分割的主力损失函数import torch import torch.nn as nn class DiceLoss(nn.Module): def __init__(self, smooth1.0): super().__init__() self.smooth smooth def forward(self, pred, target): # pred: [B, C, H, W] 经过softmax或sigmoid后的概率图 # target: [B, H, W] 整数标签背景为0前景为1 pred pred.contiguous().view(pred.size(0), pred.size(1), -1) target target.contiguous().view(target.size(0), -1) # 对每个类别分别计算Dice系数 dice_total 0.0 for c in range(pred.size(1)): p pred[:, c, :] # 第c类的预测概率 t (target c).float() # 第c类的真实标签 intersection (p * t).sum(dim1) dice (2.0 * intersection self.smooth) / (p.sum(dim1) t.sum(dim1) self.smooth) dice_total dice.mean() return 1.0 - dice_total / pred.size(1)这段代码的核心逻辑是逐类别计算预测概率和真实标签的重叠度。smooth参数是平滑项防止分子分母都为0导致除零错误一般取1.0即可。Dice Loss的本质是用预测区域和真实区域的重合程度直接作为优化目标前景占比再小也不影响梯度传递。实际项目中我通常把Dice Loss和交叉熵按7:3或8:2的权重混合使用——纯Dice Loss在训练初期梯度噪声偏大混合交叉熵可以让训练更稳。评价指标上医学影像分割最常用的三个指标是Dice系数、IOU交并比和Hausdorff距离。Dice和IOU都是区域重叠度指标但Dice对前景小目标更敏感Hausdorff距离衡量的是分割边界和真实边界的最大间距用于评估边界精度。发论文的时候Dice和Hausdorff都要报工程落地主要看Dice和推理速度。3. 环境搭建与数据准备从零把Unet医学影像分割系统跑起来3.1 Python环境与深度学习框架选型PyTorch还是TensorFlow拿到一个Unet分割项目第一步是把环境搭干净。Python版本建议3.8到3.10之间太新的版本有时会遇到CUDA适配问题。深度学习框架选择上医学影像分割领域现在主流是PyTorch原因有三点一是医学影像的论文代码大多用PyTorch发布复现别人的结果方便二是动态图机制在调试分割模型时能看到中间特征图的实时形状三是TorchIO、MONAI这些医学影像专用工具库对NIfTI格式、预处理、增强的支持比TensorFlow生态成熟很多。创建虚拟环境并安装核心依赖的常见做法是# 创建Python虚拟环境避免和系统Python互相污染 python -m venv unet_env source unet_env/bin/activate # Windows下用 unet_env\Scripts\activate # 安装PyTorch根据本机CUDA版本选择对应命令这里以CUDA 11.8为例 pip install torch2.1.0 torchvision0.16.0 --index-url https://download.pytorch.org/whl/cu118 # 安装医学影像处理与训练相关库 pip install numpy1.24.3 nibabel monai opencv-python matplotlib scikit-learn tqdmnibabel是读取NIfTI和DICOM影像格式的基础库monai是专门为医学影像深度学习设计的工具包提供数据加载、预处理、增强和评估的现成组件不用自己重复造轮子。这里要提醒一下PyTorch版本和CUDA版本必须匹配装完torch后马上验证torch.cuda.is_available()是否为True这一步翻车的人最多。3.2 医学影像数据的处理格式转换、窗宽窗位与归一化医学影像和自然图像最本质的区别在于数据格式。医院拿到的原始数据一般是DICOM格式CT、MRI设备直接输出的标准做科研时通常把DICOM转成NIfTI格式后缀为.nii或.nii.gz一个NIfTI文件里同时存储三维体数据和空间坐标信息。处理流程是先用dcm2niix工具把DICOM序列转成NIfTI再用nibabel加载做预处理。CT影像还有一个关键概念叫窗宽窗位。CT值Hounsfield Unit简称HU的范围是从-1000到3000左右但人类不同组织的HU值区间很窄。直接把原始HU值送进网络大部分数值区间是空的模型很难学。标准做法是用窗宽窗位把感兴趣的HU值范围映射到0-255再做归一化import nibabel as nib import numpy as np def load_and_preprocess(nii_path, window_center40, window_width400): # 读取NIfTI文件 img nib.load(nii_path) data img.get_fdata().astype(np.float32) # 窗宽窗位截断只保留HU值在[center-width/2, centerwidth/2]范围内的数据 lower window_center - window_width / 2 upper window_center window_width / 2 data np.clip(data, lower, upper) # 映射到[0,1]区间 data (data - lower) / (upper - lower) return data窗口参数的选择直接决定模型能看到什么。腹部CT软组织窗口一般取窗位40、窗宽400肺部窗口取窗位-600、窗宽1500脑部CT窗口取窗位40、窗宽80。如果你的任务是分割腹部器官却用了骨窗参数大量软组织对比度会被截断模型性能断崖式下降。做多器官分割时一个常用技巧是把不同窗宽窗位下的图像作为多通道输入网络自己学习融合。归一化方面医学影像分割通用的做法是把体素值映射到[0,1]区间如上文而不是像自然图像那样做均值方差标准化。其中的原因是NIfTI体素值本身有物理意义HU值破坏原始的线性关系会让不同设备之间的数据可比性变差。3.3 数据增强怎么做才不翻车弹性形变与随机裁切医学影像数据量少增强是提高泛化能力最关键的手段。但医学影像增强有一个特殊原则增强不能破坏解剖结构的合理性。旋转角度要控制在15度以内大幅旋转会产生器官长在奇怪位置的样本反而误导模型水平翻转可以用但垂直翻转对于某些器官比如心脏和肝脏的位置关系会造成异常。常用的医学影像增强策略包括三类几何增强随机旋转、平移、缩放、强度增强亮度对比度微调、高斯噪声、伽马变换、弹性形变模拟器官形变。MONAI库中提供了一个直接可用的增强组合import monai from monai.transforms import ( Compose, RandRotated, RandFlipd, RandZoomd, RandShiftIntensityd, RandAffined ) train_transforms Compose([ # 随机旋转限制在正负15度内保持解剖结构合理性 RandRotated(keys[image, label], range_x15.0 / 360 * 2 * 3.14159, prob0.3), # 水平翻转prob设为0.3避免过度增强 RandFlipd(keys[image, label], spatial_axis1, prob0.3), # 随机缩放到0.9~1.1倍 RandZoomd(keys[image, label], min_zoom0.9, max_zoom1.1, prob0.2), # 强度扰动模拟不同扫描设备的信号差异 RandShiftIntensityd(keys[image], offsets0.05, prob0.3), ])注意keys同时包含image和label保证图像和标签做完全相同的几何变换这个一致性错了整个训练就废了。RandAffined是做弹性形变的标准组件但弹性形变对计算资源消耗较大建议先加旋转翻转数据量不够再考虑弹性形变。增强的概率prob建议控制在0.2到0.4之间过高的增强概率会让网络长期看不到原始数据分布收敛变慢。4. 训练与调参让Unet在医学影像上收敛的实用参数4.1 学习率策略从1e-4起步用余弦退火收尾Unet训练里学习率是影响最终分割精度最大的超参数。医学影像分割任务普遍使用Adam优化器初始学习率设置在1e-4到3e-4之间。这个数值比自然图像分类常用的1e-3要低原因在于医学影像的前景区域占比小Dice Loss的梯度本身噪声偏大学习率过高会导致网络在最优解附近震荡表现为训练曲线剧烈波动、验证Dice迟迟上不去。我一般会这样配置学习率调度器前10个epoch用线性预热warmup从1e-5逐渐升到1e-4然后使用余弦退火在训练后半段逐渐把学习率降到1e-6以下。预热阶段让网络参数先稳定下来避免刚开始几步就把参数更新的方向带偏余弦退火让网络在后期可以精细地收敛到局部最优解。import torch from torch.optim.lr_scheduler import LinearLR, CosineAnnealingLR def get_scheduler(optimizer, total_epochs): # 前10%的epoch做线性预热 warmup_epochs max(1, int(total_epochs * 0.1)) warmup_scheduler LinearLR( optimizer, start_factor0.1, end_factor1.0, total_iterswarmup_epochs ) # 预热结束后切换为余弦退火 cosine_scheduler CosineAnnealingLR( optimizer, T_maxtotal_epochs - warmup_epochs, eta_min1e-6 ) return warmup_scheduler, cosine_schedulerstart_factor0.1表示预热阶段从当前学习率的10%开始逐渐升到原值eta_min1e-6是余弦退火末尾的最小学习率。实际使用时要配合torch.optim.lr_scheduler.SequentialLR把两个调度器串起来或者简单地在前10个epoch手动调整学习率。这里有一个从业者常犯的误区直接用PyTorch默认的ReduceLROnPlateau配合验证损失调整学习率——医学影像验证损失震荡大经常触发无意义的降学习率导致训练被拖慢。4.2 batch size与输入patch大小显存和精度的权衡医学影像分割的输入是三维体数据或二维切片显存占用远超自然图像任务。以2D Unet为例输入切片尺寸为512×512时单张样本的显存占用大约在3-4GBbatch size设4基本就到消费级显卡的极限了3D Unet处理96×96×96的patchbatch size设2也会吃掉10GB以上显存。这里的一个关键经验是不要为了塞下更大的batch size把输入图像缩到很小。医学影像中的病灶往往只有几十个像素图像缩小到256×256以下时小病灶的纹理信息会被插值算法抹掉分割精度显著下降。常见做法是保持输入尺寸不变把batch size调到显卡能承受的最大值一般4-8然后配合梯度累积模拟更大的batch# 梯度累积参数 accumulation_steps 4 optimizer.zero_grad() for step, (images, labels) in enumerate(train_loader): outputs model(images) loss dice_loss(outputs, labels) / accumulation_steps loss.backward() # 每累积足够的梯度后再更新参数 if (step 1) % accumulation_steps 0: optimizer.step() optimizer.zero_grad()这段代码的关键是把loss除以accumulation_steps这样累积4个batch的梯度之和才等同于一个正常batch的更新量。如果你用的是3D数据patch大小通常取96³、128³、160³这需要根据GPU显存动态调整。显存不足时优先减patch深度数据体在z轴方向的层数而不是缩小平面分辨率因为CT和MRI的层间分辨率本来就低于层内分辨率平面结构信息更宝贵。4.3 验证集划分与早停防止过拟合的底线操作医学影像数据集通常来自同一台设备、同一批病人的扫描如果直接用随机划分的方式切训练集和验证集来自同一个病人的多张切片可能同时出现在训练集和验证集中导致验证指标虚高。正确做法是按病人ID或病例编号划分数据——同一个病人的所有切片只能出现在一个集合中。import os import random from collections import defaultdict # 按病例划分训练/验证集避免数据泄漏 def split_by_patient(data_dir, patient_keyword, val_ratio0.2, seed42): patient_to_files defaultdict(list) # 假设文件名格式为 patient001_slice001.nii.gz for fname in os.listdir(data_dir): patient_id fname.split(_)[0] # 提取病例编号 patient_to_files[patient_id].append(fname) patients list(patient_to_files.keys()) random.seed(seed) random.shuffle(patients) val_count int(len(patients) * val_ratio) val_patients set(patients[:val_count]) train_files, val_files [], [] for patient, files in patient_to_files.items(): if patient in val_patients: val_files.extend(files) else: train_files.extend(files) return train_files, val_filespatient_keyword参数需要根据实际文件名格式调整比如文件名是TCGA-XX-XXXX_001.nii.gz就应该用split(_)[0]提取完整的病例编号。划分完成后需要打印训练/验证集的病人数量和切片数量确认没有病人在两个集合中重复出现。早停策略对医学影像分割尤其重要。由于数据量少训练后期验证Dice的波动幅度经常在2-3个点之间如果只用验证损失下降作为早停条件很容易过早停掉。我一般会设定patience20到30个epoch——连续这么久验证Dice没有创新高才停止训练同时保存验证Dice最高的模型权重作为最终权重也就是用保存最好而非最后的策略防止模型后期过拟合。5. 避坑排查医学影像分割最常见的5个坑5.1 训练正常但预测全黑标签和图像错位现象训练损失正常下降但推理时模型对所有输入都输出全黑的背景图分割结果完全没有前景区域。原因这是数据加载阶段最常见的问题。NIfTI文件的图像方向和标签方向不一致或者从DICOM转NIfTI时坐标系发生了翻转。比如图像的轴状位切片的序是从上到下而标签的序是从下到上模型看到的图像和标签空间上完全不匹配只能学到全部预测为背景这个捷径。解决在训练前写一个可视化脚本把图像和标签叠加显示逐病例随机抽几张检查。代码层面可以用nibabel加载后比较affine矩阵是否一致不一致时用nibabel.as_closest_canonical统一方向import nibabel as nib img nib.load(image.nii.gz) label nib.load(label.nii.gz) # 统一到标准RAS坐标系 img_canon nib.as_closest_canonical(img) label_canon nib.as_closest_canonical(label) # 检查shape是否一致 assert img_canon.shape label_canon.shape, fShape mismatch: {img_canon.shape} vs {label_canon.shape} # 重新保存为统一方向的NIfTI nib.save(img_canon, image_canon.nii.gz) nib.save(label_canon, label_canon.nii.gz)as_closest_canonical会把图像重采样到标准的RAS坐标系Right-Anterior-Superior所有NIfTI文件都经过这一步处理后图像和标签的空间对应关系就完全统一了。这是处理医学影像数据的第一步不要跳过。5.2 类别极度不平衡Dice值卡在0.6上不去现象前景区域占图像面积不到5%的小目标分割任务训练前期Dice稳步上升到0.5-0.6之后无论怎么调学习率都上不去了。原因虽然用了Dice Loss但当前景占比极低时单一Dice Loss会陷入局部最优——网络倾向于输出一个保守的、偏小的预测区域来保证和真实区域有较高重叠率但边界预测不准确。另外验证指标的波动范围大小目标预测偏差几个像素Dice就可能掉5个点以上给调参的判断带来严重干扰。解决一种做法是改用Tversky Loss或Focal Loss让网络更关注困难样本另一种做法是把前景/背景采样比例拉开——训练时把背景区域随机下采样到和前景相当的数量数据层面强行平衡类别import numpy as np import random def balanced_crop(image_3d, label_3d, crop_size(128, 128, 64), fg_ratio0.5): # 找出前景像素的位置 fg_indices np.argwhere(label_3d 0) # 一半概率在前景区域附近采样一半概率在全图随机采样 if len(fg_indices) 0 and random.random() fg_ratio: center fg_indices[random.randint(0, len(fg_indices) - 1)] else: center [random.randint(0, s - 1) for s in label_3d.shape] # 根据中心点计算裁剪范围 slices [] for c, s, cs in zip(center, label_3d.shape, crop_size): half cs // 2 start max(0, c - half) end min(s, start cs) start max(0, end - cs) slices.append(slice(start, end)) return image_3d[tuple(slices)], label_3d[tuple(slices)]这段代码的核心逻辑是以50%的概率在前景区域附近寻找裁剪中心确保每一批训练数据都包含足够多的前景样本。背景区域占比高的切片即使被采样到裁剪窗口里也会混入部分前景信息。经过这种数据层面的平衡后Dice值一般能稳定提升到0.75以上。5.3 显存溢出CUDA out of memory三维数据输入太大现象训练刚跑第一个batch就报CUDA out of memory让减小batch size后还是溢出最终只能把输入缩小到原来的四分之一分割精度随之下降。原因3D Unet的显存占用远高于2D Unet且attention模块、深层的特征图通道数都会显著推高显存消耗。特别是几年前的Unet代码实现里常把中间特征图保留在显存中用于反向传播深层的特征图数量很大时会直接占满GPU。解决优先使用梯度检查点gradient checkpointing技术在forward时丢弃中间激活值、backward时重新计算用时间换显存from torch.utils.checkpoint import checkpoint class UnetWithCheckpoint(nn.Module): def __init__(self, unet_model, use_checkpointTrue): super().__init__() self.unet unet_model self.use_checkpoint use_checkpoint def forward(self, x): if self.use_checkpoint: return checkpoint(self.unet, x, use_reentrantFalse) return self.unet(x)checkpoint函数的原理是不保存中间计算图反向传播时重新执行一次forward来重建计算图。代价是训练时间增加约20-30%但显存占用大幅下降。对于3D Unet这种显存大户这是个性价比极高的方案。另外注意use_reentrantFalse这个参数在PyTorch 2.x里是推荐写法可以避免一些边界情况的报错。5.4 推理速度太慢一个病例要跑十几秒现象训练好的Unet在GPU上单张切片推理只要几十毫秒但整个病例是几百张切片逐张推理下来一个病例要跑十几秒临床上根本等不了。原因最常见的瓶颈是逐切片推理没有利用好batch维度。PyTorch的GPU调度开销主要在kernel launchbatch_size1时大部分时间花在启动CUDA kernel而不是实际计算上。另外切片之间存在大量空间重叠重复推理了很多冗余区域。解决把整个病例的切片打包成一个batch一次推理。例如256张512×512的切片汇总成一个[256, 1, 512, 512]的输入一次性forward出结果GPU利用率能提升数倍import torch def predict_full_case(model, volume_np, device, batch_size32): # volume_np: [D, H, W] 单病例三维数据 model.eval() slices torch.from_numpy(volume_np).float().unsqueeze(1) # [D, 1, H, W] preds [] with torch.no_grad(): for i in range(0, slices.shape[0], batch_size): batch slices[i:ibatch_size].to(device) out torch.sigmoid(model(batch)) # [B, 1, H, W] preds.append(out.cpu()) return torch.cat(preds, dim0).numpy()batch_size按显存动态调整一般设为16到32之间。如果还是不够快可以配合自动混合精度AMP推理在torch.no_grad()块里加with torch.cuda.amp.autocast():fp16推理在计算密集型任务上通常能再快30-50%。5.5 训练和验证指标差距大数据泄漏还是数据增强过头现象训练集Dice达到0.95验证集Dice只有0.7差距大到不合理。网络参数没有改增强也没有加过。原因分两种情况排查。第一种是数据泄漏——同一个病人的切片同时出现在训练集和验证集中验证指标虚高这种情况差距反而是小的第二种是数据增强过度——训练时用了强随机旋转和大幅缩放而验证数据是原始形态模型在增强数据上学习到的特征无法泛化到干净数据上。解决先按前面那个按病例划分的脚本检查数据划分确认病人ID没有交叉。然后做一个消融实验把训练阶段的数据增强全部关掉只保留最基础的归一化观察训练和验证Dice的差距是否缩小。如果缩小明显说明增强参数设置过强把旋转角度从15度降到5度、增强概率从0.5降到0.2重新训练。我遇到过一个案例图像和标签都做了弹性形变但形变参数不一致导致验证集表现极差——图像形变了、标签没跟上模型学到的映射关系本身就是错的。6. 一个进阶技巧把分割结果做成交叉验证的稳定性报告训练完成后不要急着看单次实验的Dice就下结论。医学影像数据量小单次划分训练集和验证集的随机性对结果影响很大——换一个随机种子Dice可能上下浮动3-5个点。我习惯做5折交叉验证每折独立训练一个模型然后分别计算Dice、IOU和Hausdorff距离的均值和标准差。如果5折均值Dice在0.85以上且标准差小于0.03这个模型才有资格进入下一步的实际应用评估。另外一个小习惯值得分享把每折模型的错误案例可视化输出——预测错误的切片叠加在原图上保存成PNG文件。很多分割误差集中在病灶边缘区域这些区域肉眼看起来边界模糊甚至医生手动标注时也不一致。遇到这种模型错了但也说不上错得多离谱的样例我通常会把它们单独抽出来复查标注质量标注本身有歧义的样本会从训练集中剔除或重新整理。这个习惯帮我过滤过好几次因为标注质量问题导致的Dice虚高也帮我在项目答辩和论文写作时有了更扎实的案例分析素材。Unet医学影像分割从2015年发展至今依然是绝大多数医学影像分割项目最稳妥的起点。先把上文的基础实验跑通再往Gated Attention、Transformer模块或3D变体方向演进你会少走很多弯路。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进