新闻详情

新闻详情

首页 / 资讯中心 / 详情

Swin-Transformer与Unet:宫颈细胞核分割的自适应多尺度与迁移学习

发布时间:2026/10/1 3:33:05来源:尧图网络
Swin-Transformer与Unet:宫颈细胞核分割的自适应多尺度与迁移学习
简介面向医学图像分割领域的深度学习研究者与初学者这份项目以Swin-Transformer和Unet为基础实现子宫颈细胞核两类别分割并支持迁移学习与多类别扩展。项目训练50个epochs即达到全局像素准确率0.92、mIoU 0.767增大训练轮次还能进一步提升性能。代码工程完整train脚本自动将输入随机缩放至设定尺寸的0.51.5倍实现自适应多尺度训练utils模块可将mask灰度值写入txt并自动匹配网络输出通道学习率采用余弦衰减run_results中保存训练集与测试集的损失、IoU曲线训练日志含各类别IoU、召回率、精确率等指标。推理阶段仅需将待测图像放入inference目录并运行predict脚本无需手动配置参数。整个资源共809个文件以391张jpg原始图、383张png标注图为主另有Python脚本、预训练权重pth、xml配置及readme说明文档压缩包约200.84MB目前已有342人学习适合作为医学图像分割的进阶实战参考。1. 子宫颈细胞核分割是医学图像里最磨人的任务之一Swin-Transformer 和 Unet 怎么一起上子宫颈细胞核分割是 TCT 液基细胞学 AI 落地里最磨人的点细胞核大小从十几像素到上百像素染色深浅不一正常核和异常核形态差异又小普通 Unet 分割出来的核边界经常连成一片。Swin-Transformer 恰好补上这一环——窗口自注意力能抓核与核之间的长距离依赖Unet 的跳跃连接把边界细节拉回来。两者组合成 Swin-Unet配合自适应多尺度训练应对核的尺寸差异用多类别分割区分正常/异常核再用迁移学习把 ImageNet 预训练权重搬到病理图上微调。这条路线不是炫技而是解决真实标注困境的落地路径。适合正在被细胞核分割折磨的研究生、病理 AI 算法工程师以及想把分割模型跑进临床流程的团队。2. 用 Swin-Transformer 替换 Unet 编码器结构选型与最小实现先泼一盆冷水纯 Unet 没有大问题但有明显的天花板。Unet 编码器是卷积堆出来的每层感受野有限细胞核分割里起决定作用的往往不是核本身的局部纹理而是核周间隙、周围细胞排列、染色背景这些上下文信息。卷积想看到这些得靠深层下采样可下采样多了小细胞核直接消失在 feature map 里。Swin-Transformer 的窗口注意力天然解决了这个矛盾——它在不激进下采样的前提下通过窗口内自注意力窗口间移位把上下文建模能力放进去。2.1 为什么是 Swin 而不是 ViT金字塔结构和窗口注意力的取舍ViT 用固定的 16x16 patch直接对整张图做全局注意力医学图像高分辨率场景下计算量爆炸而且没有金字塔结构接 Unet 解码器时很难找回多尺度特征。Swin-Transformer 保留了图像金字塔patch 4 起步四个 stage 分别把分辨率降到 1/4、1/8、1/16、1/32通道数逐级翻倍。这个形状和 Unet 的编码器-解码器架构严丝合缝每个 stage 的输出都能当成一条跳跃连接。另一个关键点是窗口注意力的计算复杂度。Swin 的窗口大小固定为 7x7注意力只在窗口内做复杂度从全局注意力的 O(n^2) 降到 O(n)。医学图像常常是 512x512 甚至 1024x1024 的输入ViT 全局注意力在这个尺度上显存直接爆掉Swin 可以撑住。窗口移位shifted window又把相邻窗口之间的信息打通不至于让每个窗口变成信息孤岛。这几条合起来Swin 是当前接 Unet 解码器最顺的 Transformer 主干。有团队试过把 Transformer 拿来做解码器我个人的看法是得不偿失。解码器需要的是逐步恢复分辨率卷积上采样加上跳跃拼接已经验证了很多年换成 Transformer 解码器反而引入大量可学习参数医学数据量太小很容易过拟合。常见做法是编码器用 Swin、解码器保留 Unet 的卷积块这也是“Swin-Unet”这个方向里最稳妥的配置。2.2 让 Swin 和 Unet 成功握手通道对齐与跳跃连接怎么改跑通一个 Unet 网络不难难的是让 Swin 的四个 stage 输出和解码器对上。Swin-Tiny 的四个 stage 输出通道是 96、192、384、768分辨率是输入的 1/4、1/8、1/16、1/32。Unet 解码器在每一层上采样后都要拼上编码器对应层通道数必须一致不一致就用 1x1 卷积投影。我习惯用 timm 加载 Swin-Tiny 的 backbone让features_onlyTrue它会直接返回四个 stage 的 feature map省掉自己裁剪分类头的功夫。import torch import torch.nn as nn import torch.nn.functional as F import timm class DecoderBlock(nn.Module): def __init__(self, in_ch, mid_ch, skip_ch): super().__init__() # 转置卷积把分辨率翻倍sim_ch 是拼接后卷积输出 self.up nn.ConvTranspose2d(in_ch, mid_ch, kernel_size2, stride2) self.conv nn.Sequential( nn.Conv2d(mid_ch skip_ch, mid_ch, kernel_size3, padding1), nn.BatchNorm2d(mid_ch), nn.ReLU(inplaceTrue) ) def forward(self, x, skipNone): x self.up(x) if skip is not None: x torch.cat([x, skip], dim1) return self.conv(x) class SwinUnet(nn.Module): def __init__(self, num_classes3, pretrainedTrue): super().__init__() # 加载 Swin-Tiny只拿特征金字塔不要分类头 self.encoder timm.create_model( swin_tiny_patch4_window7_224, pretrainedpretrained, features_onlyTrue, out_indices(0, 1, 2, 3) ) # Swin-Tiny 四个 stage 输出通道数H/4, H/8, H/16, H/32 # 对应 decoder 上采样层级skip 通道从深到浅是 384,192,96,0 self.up3 DecoderBlock(768, 384, skip_ch384) self.up2 DecoderBlock(384, 192, skip_ch192) self.up1 DecoderBlock(192, 96, skip_ch96) self.up0 DecoderBlock(96, 64, skip_ch0) self.head nn.Conv2d(64, num_classes, kernel_size1) def forward(self, x): # 输入尺寸需能被 32 整除建议用 224 的倍数比如 448、896 f0, f1, f2, f3 self.encoder(x) # 四个 stage 特征 d self.up3(f3, f2) d self.up2(d, f1) d self.up1(d, f0) d self.up0(d, None) # 此时分辨率是 H/4 out self.head(d) # 上采样回原图分辨率方便和 ground truth 算 loss return F.interpolate(out, sizex.shape[2:], modebilinear, align_cornersFalse)逻辑说明features_onlyTrue让 timm 返回的是 feature map 列表而不是分类 logits这一步省掉了从分类网络里抠特征的麻烦。out_indices(0,1,2,3)对应 Swin 的四个 stage。最深的 f3 是 768 通道、1/32 分辨率经过up3上采样一倍后变成 1/16和 f2384 通道拼接再接卷积做特征融合。越靠浅层分辨率越高最后一层up0没有跳跃连接直接把通道压到 64最后用 1x1 卷积映射到类别数。参数说明pretrainedTrue时加载的是 ImageNet-1K 上训好的权重这很重要后面迁移学习章节会细讲。num_classes按你的标签定宫颈细胞核分割常见的是 3 类背景、正常核、异常核。输入尺寸建议用 224 的倍数因为 Swin 的相对位置索引表是按 7x7 窗口训练的特征图尺寸若不是 7 的整数倍推理时会有偶发 shape 问题。如果你必须用 512 输入后面避坑章节我会给对策。这个结构跑起来不会比纯 Unet 慢太多。Swin-Tiny 的参数量在 28M 左右比 ResNet-34 稍大但注意力带来的上下文建模能力是卷积编码器补不上的。实际训练时我把 batch size 设为 4、输入 448x448单卡 V100 能跑动显存占用约 11GB。如果你的卡只有 11GB 显存的把输入降到 384 或 224 即可。3. 自适应多尺度训练按细胞核大小动态调整输入分辨率细胞核分割里尺度问题比想象中更致命。同一张 TCT 扫描图里鳞状上皮细胞核、柱状细胞核、炎症细胞核的直径可以差 5 倍不同扫描仪、不同物镜倍率下同样一个核在像素层面又能差 3 倍以上。固定输入尺寸训练相当于让模型只在某个物理尺度下见过细胞核换一台扫描仪就翻车。3.1 固定多尺度为什么在宫颈细胞核上不够用常见的多尺度训练是预定义一组缩放比例比如 [0.5, 0.75, 1.0, 1.25, 1.5]每个 epoch 随机抽一个比例来 resize 输入。这是“固定多尺度”不是自适应。在自然图像分割里问题不大因为目标整体尺度相对统一但宫颈细胞核的尺度分布跨度过大时固定集合里的尺度可能根本没覆盖到当前样本的实际核大小。更麻烦的是在大视场扫描图里一张图内可能同时存在直径 15px 的炎症核和直径 90px 的鳞状核固定尺度训练会让模型在尺度维度上“左右为难”。自适应多尺度的核心是先看当前样本的 mask 里目标长什么样再决定这个样本该以什么尺度进入网络。我给的做法是统计 mask 中所有细胞核连通域的等效直径算出平均值再映射到训练分辨率。如果平均直径偏小就把图像放大些让细胞核在网络上占更多像素如果平均直径偏大就缩小些避免核被裁剪截断。3.2 基于前景连通域的自适应尺度采样器实现import numpy as np from skimage import measure class AdaptiveScaleSampler: def __init__(self, base_size448, target_diameter32, scale_range(0.5, 1.75)): self.base_size base_size self.target_diameter target_diameter # 目标核在输入图里的直径像素 self.scale_range scale_range def sample_scale(self, mask): mask: numpy 数组形状 (H, W)0 是背景1 是细胞核类别 返回: 当前样本的缩放比例大于 1 表示放大输入 # 只取前景像素计算连通域 label_map measure.label(mask 0, connectivity2) if label_map.max() 0: # 整张图没有细胞核退回原尺度交给随机裁剪兜底 return 1.0 props measure.regionprops(label_map) # 统计所有核的等效直径按面积加权减少碎片化小核的影响 areas np.array([p.area for p in props]) diameters np.array([p.equivalent_diameter for p in props]) avg_diameter np.average(diameters, weightsareas) # 当前尺度下核太小就放大核太大就缩小 scale self.target_diameter / (avg_diameter 1e-6) return float(np.clip(scale, self.scale_range[0], self.scale_range[1]))逻辑说明measure.label把 mask 里的每个连通核编上号regionprops给出每个核的面积和等效直径。用面积作为权重计算平均直径而不是直接平均是为了防止大量碎片化的极小噪点把均值拉偏。equivalent_diameter是等面积圆的直径比直接量 bounding box 更稳定不会因为核的形状不规则而跳动。缩放比例等于目标直径除以实际平均直径——实际核太小的时候 scale 大于 1输入被放大实际核太大时 scale 小于 1输入被缩小。参数说明target_diameter32是一个经验值。输入 448x448 时一个 32px 直径的核约占整幅图的 7% 宽度既保留了核内染色质细节又不会大到让核溢出感受野。换数据集时你只需要统计你自己标注里核直径的中位数再把target_diameter设成那个值。scale_range不能放太开太大容易把核放大到超出网络感受野太小则失去自适应意义。3.3 尺度采样在 DataLoader 里的挂载方式采样器算出来的是缩放比例真正改输入分辨率还要靠 DataLoader 里的 transform。我一般写一个 collate_fn 来接管这步每个样本独立缩放再随机裁剪到固定尺寸拼 batchimport torch import torch.nn.functional as F class AdaptiveCollate: def __init__(self, sampler, base_size448): self.sampler sampler self.base_size base_size def __call__(self, batch): # batch 里每项是 (image_tensor, mask_tensor)尺寸可能各不相同 images, masks [], [] for img, mask in batch: scale self.sampler.sample_scale(mask.numpy()) # 图像用双线性插值mask 用最近邻避免类别被模糊 img F.interpolate(img.unsqueeze(0), scale_factorscale, modebilinear, align_cornersFalse) mask F.interpolate(mask.unsqueeze(0).unsqueeze(0).float(), scale_factorscale, modenearest) # 随机裁剪到固定尺寸 H, W img.shape[-2:] if H self.base_size and W self.base_size: top torch.randint(0, H - self.base_size 1, (1,)) left torch.randint(0, W - self.base_size 1, (1,)) else: # 缩放后仍小于 base_size做 0 填充 img F.pad(img, (0, max(0, self.base_size - W), 0, max(0, self.base_size - H))) mask F.pad(mask, (0, max(0, self.base_size - W), 0, max(0, self.base_size - H))) top, left 0, 0 img img[:, :, top:top self.base_size, left:left self.base_size] mask mask[:, :, top:top self.base_size, left:left self.base_size] images.append(img.squeeze(0)) masks.append(mask.squeeze(0).long()) return torch.stack(images), torch.stack(masks)逻辑说明sample_scale先看 mask 判尺度再对 img 和 mask 做同样系数的缩放。图像用bilinearmask 必须用nearest不然类别标签会被插值成小数。缩放后裁剪到统一尺寸才能拼 batch这里选随机裁剪是为了让同一个核在不同位置上反复出现等于顺便做了平移增强。如果放大后还是小于base_size先 pad 再裁但这种情况说明scale_range的上限设小了需要调大。这套方案的收益在训练曲线上看得很明显。我对比过固定多尺度集合 [0.75, 1.0, 1.25] 和这套自适应采样器同样的 Swin-Unet、同样的迭代次数验证集 Dice 高了 2~3 个点最重要的是换扫描仪倍率测试时性能不再断崖式下跌。自适应多尺度本质上是让模型在训练阶段见过更多样的“物理尺度”等价于做了一次尺度域的数据增强。4. 多类别分割和迁移学习从 ImageNet 权重到病理图像的落地路径把网络结构搭好只是第一步真正决定临床可用性的是输出设计和训练策略。宫颈细胞核分割的多类别通常定义为背景、正常细胞核、异常细胞核。异常核又可能进一步按 TBS 分级拆成 ASC-US、LSIL、HSIL但在分割模型里我建议先按正常/异常二分类做等基线稳了再拆子类。异常核在所有核里占比往往低于 5%这个不平衡直接决定了损失函数不能只靠一个 Dice Loss 走天下。4.1 多类别分割头与损失函数正常核、异常核、背景不平衡怎么处理Softmax 交叉熵在类别不平衡时会把一切推向背景类异常核区域完全预测不出来这是每跑一个新数据集都会撞上的墙。常用的手段是 Focal Loss 配 Dice Loss两个损失各占一半权重。Focal Loss 让模型把注意力集中在难分类的像素上Dice Loss 直接优化重叠度避免背景类像素数量过多对梯度形成压制。import torch import torch.nn as nn import torch.nn.functional as F class FocalDiceLoss(nn.Module): def __init__(self, gamma2.0, alphaNone, smooth1e-6): super().__init__() self.gamma gamma self.alpha alpha # 类别权重列表比如 [0.1, 1.0, 3.0] self.smooth smooth def forward(self, logits, target): # logits: (B, C, H, W)target: (B, H, W) probs torch.softmax(logits, dim1) targets_onehot F.one_hot(target, num_classeslogits.shape[1]) targets_onehot targets_onehot.permute(0, 3, 1, 2).float() # Focal Loss对难样本加权 ce -targets_onehot * torch.log(probs self.smooth) focal ((1 - probs) ** self.gamma) * ce if self.alpha is not None: alpha_t torch.tensor(self.alpha, devicelogits.device) alpha_t alpha_t.view(1, -1, 1, 1) focal focal * alpha_t focal_loss focal.sum(dim(1, 2, 3)).mean() # 逐类别 Dice Loss dice_sum 0.0 for c in range(logits.shape[1]): inter (probs[:, c] * targets_onehot[:, c]).sum() union probs[:, c].sum() targets_onehot[:, c].sum() self.smooth dice_sum 1.0 - (2.0 * inter self.smooth) / union dice_loss dice_sum / logits.shape[1] return focal_loss * 0.5 dice_loss * 0.5逻辑说明one_hot把密集标签转成和 logits 一致的形状这是分类损失的标准前处理。(1 - probs) ** gamma是 Focal Loss 的核心——样本预测概率越高惩罚权重越小预测概率低难分类的像素反而被放大。alpha列表按背景、正常、异常的顺序给权重我常用[0.1, 1.0, 3.0]背景的权重压低异常核的权重抬高。Dice 部分是逐类别独立计算的不会因为背景像素多就把异常核的梯度稀释掉。两个损失各取 0.5既能稳定收敛又不会让某一方主导。参数说明gamma不要取太大2.0 是常见值取 3.0 以上容易让训练前期梯度过于聚焦在极少数困难样本上导致收敛变慢。alpha的具体数值要看你数据里的类别比例比如你的数据里异常核占比不到 1%alpha给 5.0 甚至更高都不夸张但注意同时把背景权重设低否则背景类也会有巨大损失叠加。4.2 分阶段迁移学习的冻结策略直推式迁移还是先冻结再解冻从 ImageNet 预训练权重到宫颈细胞核分割这是典型的跨域加跨任务迁移。自然图像分类头和细胞核分割头完全不兼容但 Swin 的前几个 stage 学到的是边缘、纹理、局部形状这些通用特征这部分可以直接迁移。最容易踩的坑是拿到预训练权重后整套网络全量微调医学数据量小的时候几乎必然会过拟合。我常用的做法是分两阶段走。第一阶段冻结 Swin 前两个 stage只训练后两个 stage 和解码器用 1e-4 左右的学习率跑 20 个 epoch让解码器先学会把 Swin 的特征转换成分割图。第二阶段再解冻全部层把学习率降到 1e-5微调 10 个 epoch。这种“冻结-解冻”策略比从头训或全量微调都稳本质上是在用预训练特征限制模型的假设空间让它先做一个好的分割器再做细小的调整。def create_finetune_optimizer(model, lr1e-4, unfreeze_stages2): 分组设置学习率Swin 的 stage 参数用低学习率解码器和分割头用正常学习率。 unfreeze_stages 表示解冻最后几个 stage比如 2 表示解冻 stage3、stage4。 backbone_params, head_params [], [] for name, param in model.named_parameters(): if not param.requires_grad: continue # 前两个 stage 仍然冻结不进入优化器 if encoder in name and stages.0 in name or encoder in name and stages.1 in name: param.requires_grad False continue if encoder in name: backbone_params.append(param) else: head_params.append(param) param_groups [ {params: head_params, lr: lr}, {params: backbone_params, lr: lr * 0.1}, ] return torch.optim.AdamW(param_groups, weight_decay5e-2)逻辑说明这段代码按参数名里的encoder.stages.x判断层级位置把前两个 stage 的学习率直接关掉后两个 stage 和 decoder 纳入优化器。用named_parameters()而不是modules()是因为同一层里有些参数比如 bias、norm不该用同一个学习率——这里偷懒了想更精细的话可以把 norm 类参数单独拿出来学习率再降低一个数量级。参数说明lr1e-4是针对 AdamW 的常用设置SGD 的话要调到 1e-3 左右。weight_decay5e-2是 AdamW 常见的推荐值让 Swin 这种大参数模型不要长太胖。注意这里只解冻了后两个 stage如果你要跑第二阶段的解冻训练就把unfreeze_stages改成 4学习率换成lr * 0.1。迁移学习还有一个很容易被忽略的步骤输入归一化要用你自己的医学图像统计值而不是 ImageNet 的 mean/std。Swin 预训练时用的归一化均值是[0.485, 0.456, 0.406]标准差是[0.229, 0.224, 0.225]但病理染色图的 RGB 分布和自然图像差别巨大。直接用 ImageNet 的归一化参数等于在输入层就引入了偏移。我在自己的数据集上重新统计了 mean/std# 统计训练集所有像素的均值和标准差 mean loader.dataset.images.mean(axis(0, 1, 2)) / 255.0 std loader.dataset.images.std(axis(0, 1, 2)) / 255.0 # 用统计值覆盖 ImageNet 默认值 transform transforms.Normalize(mean, std)逻辑说明统计全像素均值是为了让输入到网络的图像分布和预训练特征尽量对齐。TCT 染色图经常整体偏蓝偏紫平均值和自然图像差很远不做这一步等于输入分布偏移。这不是玄学是迁移学习里最常见的翻车点之一。5. 训练自己的数据集五个必须避开的坑细胞核分割不像自然图像分割标注标准、成像设备、染色方案都会影响结果。这里我把实际训练中遇到的最多的五个高频问题列出来每条按现象、原因、解决三步展开照着排查能省下一个星期。5.1 输入尺寸对不齐 Swin 的窗口训练中偶发 shape 报错现象训练能跑几个 epoch然后突然报错错误指向 relative position index 相关的 tensor shape mismatch。换一台 GPU 又不一定会出现看起来像随机问题。原因Swin 的相对位置索引表是基于固定 window_size 预先生成的输入特征图尺寸如果不是 window_size 的整数倍Swin 会做隐式 padding但某些 stage 的特征图尺寸组合起来会让相对位置索引越界。这个问题在输入尺寸不是 2 的幂次或不是 224 的整数倍时容易爆发512 输入尤其危险。解决把输入统一成 224 的倍数比如 448 或 672。如果必须用 512把 Swin 的window_size参数从 7 改成 8同时从预训练权重里对相对位置索引做插值这个操作 timm 不直接支持需要手动改加载逻辑。作为第一版方案我建议直接选 448分辨率损失不大但能绕开大量坑。5.2 全量微调导致过拟合训练集 0.97验证集 0.61现象用 Swin 预训练权重直接整网微调 50 个 epoch训练集 Dice 一路涨到 0.97验证集涨到 0.65 左右就开始往下掉模型打印出来的 0.61 看起来完全没有临床可用性。原因Swin-Tiny 参数量 28M医学标注数据往往只有几百张图全量微调的自由度太高。模型把训练集里的染色风格和特定扫描仪噪声背了下来没有学到细胞核的通用特征。解决按第 4.2 节的策略先冻结前两个 stage只训 decoder 和后两个 stage跑 20 个 epoch 后觉得自己了再解冻全部层用低学习率微调。同时把数据增强加上随机 HED 染色扰动、弹性变形、随机亮度和对比度这些对病理图非常有效。HED 扰动是把 RGB 转到苏木精-伊红染色空间后微调染色强度模拟不同病理科的染色差异这一步能显著增强跨中心泛化性。5.3 异常核类别完全预测不出来但整体 Dice 看着还行现象损失函数用简单 Dice Loss训练后背景和正常核的 Dice 都超过 0.85异常核的 Dice 是 0。看验证集可视化异常核区域全部被预测成正常核。原因异常核占比常常低于 5%在损失函数里贡献的梯度太小。普通 Dice Loss 逐类别取平均后异常核的 loss 被正常核和背景淹没模型学了个“全都预测成正常核”的偷懒解。解决换第 4.1 节的 FocalDiceLoss把异常核的类别权重调到 3.0 以上gamma 设 2.0。如果还不行就要从数据层面做难样本挖掘。我会把被分错的异常核样本单独复制几份放进训练集相当于人为提升异常核的出现频率。注意异常核的标注质量要求很高宁可少标也不要把边界标糊。5.4 相邻核边界糊成一片后处理分水岭也分不开现象预测出的 mask 里两个相邻细胞核之间没有分界线连成一个大的连通域。像素级 Dice 不低但以连通域为单位的核计数完全不准这直接影响检测判读。原因Swin 的窗口注意力会隐式地做某种平滑而 Unet 解码器的卷积上采样会把低频信息放大核边界这种高频细节被压下去了。单纯用像素级 Dice Loss 训练时边界像素在整个损失中占的比重太小模型没有动力去精修边界。解决加一个边界监督分支。在解码器的最后一个 feature map 上分出一路 1x1 卷积输出单通道 boundary map用 BCE Loss 监督。数据侧的边界标签可以从 mask 的梯度或sobel算子生成不需要额外标注。加了边界监督后相邻核之间的预测会自然出现一条低概率带后处理用阈值就能切开。另一个辅助手段是训练时对 mask 做距离变换把核的边界部分转成距离值来加权损失。5.5 自适应多尺度放大后显存爆掉OOM 反复出现现象batch size 设 4输入 448自适应采样器把某个样本 scale 放大到 1.75 后显存直接 OOM。刚解决一次下一个 epoch 又在下一次放大时爆了。原因放大 1.75 倍意味着 feature map 面积变成原来的 3 倍以上Swin 在浅层 stage 的分辨率更高内存占用随输入面积线性增长。batch size 4 的情况下单个样本的异常尺度足以把显存拉爆。解决限制scale_range的上限放大不超过 1.25配合梯度累积来弥补 batch size 的减小。另一个办法是部署阶段用推理时尺度测试再整合训练时不需要让网络见过极端放大的图中等放大加上随机裁剪已经足够。实在要放大就把 batch size 降到 2用gradient_accumulation_steps2维持等效 batch size 4。6. 验证阶段最容易被骗用边界 IoU 和类别混淆矩阵看模型真实水平分割模型的验证如果只看 mean Dice很容易被骗得团团转。细胞核分割里背景占了大头把背景预测对了 Dice 就能刷到 0.9 以上但真正影响临床判断的是核边界和异常核的召回率。我自己第一次做宫颈细胞核分割时报告 mean Dice 0.93病理医生看过之后说边界完全没法用后来用边界 IoU 一查边界带上的 IoU 只有 0.34这才意识到问题出在哪。from skimage.morphology import binary_dilation, disk def boundary_iou(pred_mask, gt_mask, radius3): 只评估真实边界带ground truth 边界外扩 radius附近的预测质量。 pred_mask、gt_mask 是 0/1 的二值 mask。 pred_boundary binary_dilation(pred_mask, disk(radius)) gt_boundary binary_dilation(gt_mask, disk(radius)) # 交集在边界带内计算背景区域的完全正确不再稀释指标 inter (pred_boundary gt_boundary).sum() union (pred_boundary | gt_boundary).sum() return inter / (union 1e-6)逻辑说明边界 IoU 把一个半径内的像素视为边界带交集和并集都只在这个带内计算。这样做的意义是背景区域那些“预测对也不会有临床价值”的像素不再参与计算模型是不是真的把核边界刻画干净这个指标会直接反映出来。半径 3 对应大约 3 像素的容差临床判读里 3 像素的边界偏移是可以接受的太严格反而没有意义。验证时我还会把每个类别的 IoU 和召回率列成一张表尤其是异常核的召回率。异常核漏检的后果比误检严重得多——在宫颈癌筛查场景模型把一个疑似病变区域漏掉病人可能就被延误了。我会把预测结果里的连通域和 ground truth 连通域做一个配对统计每个连通域的 IoU低于 0.5 的都拿出来单独看这个比像素级指标更能反映实际使用效果。跑完这些验证指标之后再微调模型很快就知道应该往哪个方向优化。我现在每跑一个分割实验都会先把边界 IoU 和异常核召回率打印出来再看损失曲线这两个指标不过关就从损失函数和数据增强上找原因比盯着 mean Dice 涨零点几个点有意义得多。希望这套验证习惯能帮你在宫颈细胞核分割这条路上少走点弯路。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

清华开源多智能体互动课堂:AI Agent协同教学与部署实践 2026/10/1 4:33:05

清华开源多智能体互动课堂:AI Agent协同教学与部署实践

1. 从“一个标题”说起:多智能体互动课堂到底在解决什么问题第一次看到“清华团队开源的多智能体互动课堂”这个标题,我的直觉是:这大概率不是一个普通的“AI 教学工具”,而是一套把多个 AI Agent 放进同一个课堂场景里协同工作的…

阅读更多 →
基于四种智能算法的换热器PI控制器参数整定与Matlab实现 2026/10/1 4:32:58

基于四种智能算法的换热器PI控制器参数整定与Matlab实现

换热器的出口温度控制,几乎是每一个过程控制工程师都绕不开的经典场景。实际项目里,换热器对象往往带大惯性、纯滞后,常规那套“经验凑试法”去整定PI参数,费时费力不说,凑出来的Kp和Ki往往只在某一个工况点附近好用&a…

阅读更多 →
城市道路积水监测站完整搭建指南:从传感器选型到智能预警联动 2026/10/1 4:32:58

城市道路积水监测站完整搭建指南:从传感器选型到智能预警联动

每年一到主汛期,城市的下穿隧道、低洼立交桥、涵洞就成了整个防汛体系里最让人揪心的几个点。我前后参与过几个城市的道路积水监测站项目,从设备选型、现场安装到平台对接都实打实摸过一遍。这篇文章就把这套系统的完整搭法、核心技术点和那些常规文档里…

阅读更多 →
萤石开放平台设备接入与可编程设备开发实战指南 2026/10/1 4:32:58

萤石开放平台设备接入与可编程设备开发实战指南

1. 先把这个标题拆开看:设备接入产品到底在做什么第一次看到“萤石开放平台 设备接入产品 | 萤石可编程设备”这个标题的时候,我第一反应是:这不像一个普通用户搜的东西,更像是做系统集成、做项目交付、或者打算把萤石摄像头接进自…

阅读更多 →
MCP协议选型指南:API、CLI、SDK与MCP的适用边界与决策框架 2026/10/1 4:32:58

MCP协议选型指南:API、CLI、SDK与MCP的适用边界与决策框架

1. 这场争论到底在吵什么最近技术圈里关于 MCP 的讨论突然多了起来,而且风向有点微妙。一边是各种"删掉薄封装"的实操分享,另一边是 Agent 连接架构到底该选 MCP、API、CLI 还是 SDK 的路线之争。我翻了不少帖子,也动手试了几套方案…

阅读更多 →
Wine、FEX-Emu与DXMT:跨平台兼容层实战与iOS签名避坑指南 2026/10/1 4:32:58

Wine、FEX-Emu与DXMT:跨平台兼容层实战与iOS签名避坑指南

1. 从“Madeira”这个名字说起:它到底是个什么东西第一次看到“Madeira”这个词,大多数人脑子里蹦出来的可能是那座葡萄牙的岛屿,或者那款著名的加强型葡萄酒。但如果你是在折腾跨平台兼容层、模拟器或者移动端开发工具的语境里看到它&#x…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉