U-Net 已经发表十余年,但它仍是语义分割中最值得先掌握的结构之一。原因不复杂:编码器负责压缩上下文,解码器负责恢复分辨率,同尺度跳跃连接把定位信息送回解码端。后来出现的 UNet++、Attention U-Net、3D U-Net、nnU-Net 以及大量 Transformer、状态空间模型变体,都没有绕开这组基本矛盾------语义需要大感受野,边界需要高分辨率。
这篇文章从 2015 年原论文出发,给出张量尺寸、损失函数、PyTorch 实现和一组可复查的小型实验。文中的训练曲线、预测结果、中间特征与消融数据均来自实际运行,不使用示意数字。
阅读路线
|---------------------|---------------------------|-------------------------|-------------------|
| 结构 编码器、解码器、Skip | 原论文 valid conv、权重图、切块 | 实现与实验 PyTorch、曲线、消融 | 进阶 论文、开源项目、视频 |
1. 语义分割在求什么
给定输入图像

网络输出每个像素的类别分数

二分类通常令
,输出前景 logit;多分类令
,每个像素对应
个类别分数。训练阶段保留 logits,推理时再做 Sigmoid 或 Softmax。
语义分割与相邻任务的区别如下。
| 任务 | 输出 | 同类实例是否区分 |
|---|---|---|
| 图像分类 | 整张图一个类别 | 不涉及 |
| 目标检测 | 类别与边界框 | 区分 |
| 语义分割 | 每个像素一个类别 | 不区分 |
| 实例分割 | 每个实例一个掩码 | 区分 |
下面的样例从左到右分别是输入、人工标注与预测。分割结果不能只看整体轮廓,还要检查边界、孔洞、小目标和相邻对象之间是否粘连。

图 1 Oxford-IIIT Pet 输入、标注与预测示例
处理数据时有一条很容易被忽略的规则:图像可以使用双线性插值,离散标签必须使用最近邻插值。如果对类别掩码使用双线性插值,标签中会出现原本不存在的中间值,损失函数看到的监督信号已经被污染。
2. U-Net 的结构与张量流

图 2 现代 same-padding U-Net 数据流
2.1 编码器
编码器由重复的卷积块和下采样组成。每下降一级,空间尺寸通常减半,通道数增加:
256×256×64
→ 128×128×128
→ 64×64×256
→ 32×32×512
→ 16×16×1024
较浅的特征保留边缘、纹理和精确坐标;较深的特征拥有更大感受野,更适合判断"这是什么"。下采样换来了语义范围,也丢掉了细边界和小目标信息。
二维卷积的输出尺寸为

当
时,输入输出尺寸相同,即常说的 same padding。
2.2 解码器
解码器逐级上采样,把低分辨率语义恢复到原图尺度。常见实现有两种:
- 转置卷积:参数可学习,但需要留意棋盘格伪影;
- 双线性插值加卷积:实现直接,尺寸控制更稳定。
上采样只能放大特征,不能凭空还原池化前的坐标,所以还需要跳跃连接。
2.3 Skip Connection
U-Net 把编码器同尺度特征与解码器特征沿通道维拼接:
x = torch.cat([skip, x], dim=1)
假设两路特征都是 B×256×64×64,拼接后得到 B×512×64×64。后续卷积负责融合浅层定位信息和深层语义。
Concat 与 Add 的差别不只是写法:
| 融合方式 | 通道变化 | 特点 |
|---|---|---|
| Concat | 相加 | 信息保留充分,但更占显存 |
| Add | 不变 | 轻量,但要求两路通道一致 |
如果输入尺寸不是
的倍数,连续下采样后可能出现奇数尺寸,导致上采样结果与 skip 对不上。工程中不要依赖"刚好整除",而应显式对齐到 skip 的尺寸:
x = F.interpolate(x, size=skip.shape[-2:], mode="bilinear", align_corners=False)
2.4 1×1 输出卷积
最后一层 1×1 卷积只改变通道数,不改变空间尺寸。它相当于在每个像素位置使用同一组线性分类器:
- 二分类:输出 1 个通道;
类互斥分割:输出
个通道;- 多标签分割:输出
个独立通道。
3. 2015 年原论文里容易被忽略的细节
原论文题为 U-Net: Convolutional Networks for Biomedical Image Segmentation,发表于 MICCAI 2015。论文结构图如下。

图 3 U-Net 2015 原论文结构图
图源:Ronneberger、Fischer、Brox,MICCAI 2015。
现代教程经常把 U-Net 画成输入输出等大,但原版并不是这样。原论文使用不填充的 3×3 卷积:输入为 572×572,输出为 388×388;编码器特征在送入解码器前需要裁剪,图中因此写的是 copy and crop。
| 项目 | 2015 原版 | 现代常见实现 |
|---|---|---|
| 3×3 卷积 | valid padding | same padding |
| 输入与输出 | 572→388 | 通常同尺寸 |
| Skip 对齐 | 裁剪后拼接 | 直接拼接或插值对齐 |
| 上采样 | 2×2 up-convolution | 转置卷积或插值加卷积 |
| 归一化 | 无 BatchNorm | BN、GN、IN 均常见 |
| 损失 | 带像素权重的交叉熵 | CE、BCE、Dice、Focal 等 |
| 数据增强 | 强调弹性形变 | 按任务选择几何与强度增强 |
这一区分很重要。复现原论文时应保留 valid convolution、crop 与 overlap-tile;做现代工程时,same padding 往往更方便。两者都是 U-Net,但不能把工程改写误认为论文原设定。
3.1 边界加权损失
论文处理细胞分割时,希望模型主动分开相互接触的细胞。作者构造了像素权重图:

其中
是像素到最近、次近细胞边界的距离。两细胞之间的窄缝会得到较大权重。

图 4 原论文边界权重图示例
这个权重图属于损失函数设计,不是注意力模块,也不是网络额外输出。它针对的是"粘连实例如何分开"这一具体问题。
3.2 Overlap-tile
原论文用镜像扩展和重叠切块处理超大显微图像。

图 5 Overlap-tile 策略
现代滑窗推理仍沿用相同思路:切出重叠 patch,分别预测,再用高斯或中心加权融合重叠区域。直接无重叠拼接容易在 patch 边缘留下接缝。
3.3 原论文真正有价值的部分
U-Net 的贡献不只是画出一个 U 形网络。更重要的是三件事:
- 把"上下文识别"和"精确定位"放在同一端到端结构里;
- 用高分辨率 skip 特征补偿下采样造成的定位损失;
- 把网络结构、数据增强、边界损失和大图推理组合成完整方案。
4. 损失函数与指标
4.1 二分类:BCEWithLogits + Dice
BCEWithLogitsLoss 内部已经包含数值稳定的 Sigmoid。训练时直接传 logits,不要提前调用 torch.sigmoid。

Soft Dice 直接优化区域重叠:

实践中常用

BCE 提供稳定的逐像素梯度,Dice 对前景稀少更友好。权重不必固定为 0.5,应在验证集上根据前景比例和边界要求调整。
4.2 多分类:CrossEntropyLoss
互斥多分类通常使用 CrossEntropyLoss:
- logits:
B×K×H×W,浮点数; - target:
B×H×W,long类型; - target 存类别索引,不做 one-hot 也不提前 Softmax。
4.3 Dice、IoU 与边界指标

Dice 和 IoU 都强调区域重叠,但不能完整描述边界质量。医学或细结构任务还应报告 HD95、Surface Dice、Precision 和 Recall。只报一个整体 Dice,可能掩盖小目标、细血管或极端失败样例。
4.4 常见接口错误
| 现象 | 常见原因 | 修正 |
|---|---|---|
| loss 不下降 | 标签值为 0/255 或形状错误 | 二分类掩码转为 0/1 float |
| CE 报 dtype 错误 | target 是 float | 改为 long 类别索引 |
| BCE 报尺寸不一致 | target 少了通道维 | 保持 B×1×H×W |
| 指标很好但图很差 | 大量背景主导像素准确率 | 改看 Dice、IoU 与错误图 |
| 预测全背景 | 类别失衡、阈值过高或监督损坏 | 检查正像素比例、logits 与掩码插值 |
5. 一个清晰可用的 PyTorch U-Net
下面的实现使用 same padding、双线性插值和 concat skip。它保留 logits,可直接接 BCE 或 CE。
from __future__ import annotations
import torch
import torch.nn as nn
import torch.nn.functional as F
class DoubleConv(nn.Module):
def __init__(self, in_ch: int, out_ch: int, norm: str = "batch"):
super().__init__()
def norm_layer(c: int):
if norm == "group":
groups = min(8, c)
while c % groups != 0:
groups -= 1
return nn.GroupNorm(groups, c)
if norm == "none":
return nn.Identity()
return nn.BatchNorm2d(c)
self.net = nn.Sequential(
nn.Conv2d(in_ch, out_ch, 3, padding=1, bias=False),
norm_layer(out_ch),
nn.ReLU(inplace=True),
nn.Conv2d(out_ch, out_ch, 3, padding=1, bias=False),
norm_layer(out_ch),
nn.ReLU(inplace=True),
)
def forward(self, x):
return self.net(x)
class Down(nn.Module):
def __init__(self, in_ch: int, out_ch: int, norm: str):
super().__init__()
self.net = nn.Sequential(
nn.MaxPool2d(2),
DoubleConv(in_ch, out_ch, norm),
)
def forward(self, x):
return self.net(x)
class Up(nn.Module):
def __init__(self, in_ch: int, skip_ch: int, out_ch: int, norm: str):
super().__init__()
self.reduce = nn.Conv2d(in_ch, out_ch, 1)
self.fuse = DoubleConv(out_ch + skip_ch, out_ch, norm)
def forward(self, x, skip):
x = F.interpolate(
x, size=skip.shape[-2:], mode="bilinear", align_corners=False
)
x = self.reduce(x)
x = torch.cat([skip, x], dim=1)
return self.fuse(x)
class UNet(nn.Module):
def __init__(
self,
in_channels: int = 3,
num_classes: int = 1,
widths=(64, 128, 256, 512),
norm: str = "batch",
):
super().__init__()
w1, w2, w3, w4 = widths
self.e1 = DoubleConv(in_channels, w1, norm)
self.e2 = Down(w1, w2, norm)
self.e3 = Down(w2, w3, norm)
self.e4 = Down(w3, w4, norm)
self.bottleneck = Down(w4, w4 * 2, norm)
self.d4 = Up(w4 * 2, w4, w4, norm)
self.d3 = Up(w4, w3, w3, norm)
self.d2 = Up(w3, w2, w2, norm)
self.d1 = Up(w2, w1, w1, norm)
self.head = nn.Conv2d(w1, num_classes, 1)
def forward(self, x, return_features: bool = False):
e1 = self.e1(x)
e2 = self.e2(e1)
e3 = self.e3(e2)
e4 = self.e4(e3)
b = self.bottleneck(e4)
d4 = self.d4(b, e4)
d3 = self.d3(d4, e3)
d2 = self.d2(d3, e2)
d1 = self.d1(d2, e1)
logits = self.head(d1)
if return_features:
features = {
"E1": e1, "E2": e2, "E3": e3, "E4": e4,
"Bottleneck": b, "D4": d4, "D3": d3,
"D2": d2, "D1": d1,
}
return logits, features
return logits
5.1 BCE + Dice 损失
class BCEDiceLoss(nn.Module):
def __init__(self, bce_weight=0.5, dice_weight=0.5, eps=1e-6):
super().__init__()
self.bce_weight = bce_weight
self.dice_weight = dice_weight
self.eps = eps
def forward(self, logits, target):
bce = F.binary_cross_entropy_with_logits(logits, target)
prob = torch.sigmoid(logits)
dims = tuple(range(1, prob.ndim))
inter = (prob * target).sum(dims)
denom = prob.sum(dims) + target.sum(dims)
dice_loss = 1 - (
(2 * inter + self.eps) / (denom + self.eps)
).mean()
return self.bce_weight * bce + self.dice_weight * dice_loss
5.2 训练与验证
def train_one_epoch(model, loader, optimizer, criterion, device):
model.train()
total = 0.0
for image, mask in loader:
image = image.to(device, non_blocking=True)
mask = mask.to(device, non_blocking=True).float()
optimizer.zero_grad(set_to_none=True)
logits = model(image)
loss = criterion(logits, mask)
loss.backward()
optimizer.step()
total += loss.item() * image.size(0)
return total / len(loader.dataset)
@torch.no_grad()
def evaluate(model, loader, criterion, device, threshold=0.5):
model.eval()
loss_sum = dice_sum = iou_sum = 0.0
for image, mask in loader:
image = image.to(device, non_blocking=True)
mask = mask.to(device, non_blocking=True).float()
logits = model(image)
loss_sum += criterion(logits, mask).item() * image.size(0)
pred = torch.sigmoid(logits) >= threshold
truth = mask >= 0.5
dims = tuple(range(1, pred.ndim))
tp = (pred & truth).sum(dims).float()
fp = (pred & ~truth).sum(dims).float()
fn = (~pred & truth).sum(dims).float()
dice_sum += ((2 * tp + 1e-7) / (2 * tp + fp + fn + 1e-7)).sum().item()
iou_sum += ((tp + 1e-7) / (tp + fp + fn + 1e-7)).sum().item()
n = len(loader.dataset)
return {
"loss": loss_sum / n,
"dice": dice_sum / n,
"iou": iou_sum / n,
}
5.3 推理
model.eval()
with torch.no_grad():
logits = model(image.to(device))
probability = torch.sigmoid(logits)
mask = (probability >= 0.5).to(torch.uint8)
数据集必须保证以下约定:
image: float32, B×3×H×W
mask : float32, B×1×H×W,值为 0 或 1
随机裁剪、翻转和旋转必须同时作用于图像与掩码。对两者分别调用随机增强,会悄悄破坏像素对齐,模型通常只会学到背景统计。
建议把项目拆成以下目录,而不是把所有逻辑塞进一个 notebook:
unet_project/
├── datasets/
│ └── dataset.py
├── models/
│ └── unet.py
├── losses/
│ └── dice.py
├── metrics/
│ └── segmentation.py
├── train.py
├── predict.py
├── config.yaml
└── requirements.txt
6. 真实训练实验
为了验证 skip、损失函数和增强的影响,下面使用 Penn-Fudan Pedestrian 数据集做一个小型受控实验。它不是追求榜单结果,而是让四组配置在同一台 CPU 环境中快速跑完。
6.1 实验设置
| 项目 | 设置 |
|---|---|
| 数据 | Penn-Fudan Pedestrian,固定取 100 张 |
| 划分 | 80 张训练,20 张验证 |
| 输入 | 96×96 RGB |
| 模型 | 3 次下采样,base channel=8 |
| 参数量 | 113,113 |
| 卷积 MACs | 约 82.58M(96×96 输入) |
| 损失 | 0.5 BCE + 0.5 Dice |
| 优化器 | Adam,初始学习率 1e-3 |
| 训练 | 12 Epoch,seed=42 |
| 设备 | CPU |
这组模型比标准 64 通道、4 次下采样的 U-Net 小得多,因此结果只用于比较同一实验内的相对变化。
6.2 曲线与最佳结果

图 6 标准配置训练曲线
标准配置在第 6 个 Epoch 取得最佳验证结果:
| 指标 | 数值 |
|---|---|
| Dice | 0.6538 |
| IoU | 0.4977 |
| Precision | 0.6064 |
| Recall | 0.7547 |
训练损失持续下降,但验证损失在第 6 个 Epoch 之后小幅波动,说明继续训练的收益已经有限。对这类小数据实验,保存最佳验证 checkpoint 比直接使用最后一轮更稳妥。
6.3 预测与错误图

图 7 验证集预测与错误分析
图中四列依次为输入、真值、前景概率和错误叠加:绿色为 TP,青色为 FN,红色为 FP。可以看到三类典型问题:
- 拥挤区域粘连:多人轮廓靠得很近,低分辨率概率图难以分开个体;
- 细结构漏检:腿、手臂和窄边界更容易出现 FN;
- 背景误检:与人体纹理相近的区域产生 FP。
只看一张二值掩码,很难区分"阈值不合适"和"模型根本没有学到"。概率图与 FP/FN 错误图应当成为固定的验证输出。
7. Skip、Loss 与增强的受控消融
四组实验使用相同数据划分、随机种子、训练轮数与评估代码,只改变一个因素。

图 8 U-Net 受控消融结果
| 配置 | Dice | IoU | 相对标准配置 |
|---|---|---|---|
| 标准:Skip + BCE/Dice + 增强 | 0.6538 | 0.4977 | --- |
| 移除 Skip | 0.6224 | 0.4579 | Dice -0.0314 |
| 仅 BCE | 0.6466 | 0.4904 | Dice -0.0072 |
| 无增强 | 0.6599 | 0.5045 | Dice +0.0060 |
这张表可以得到三个有限而明确的结论。
第一,移除 skip 的下降最明显。 在同一小模型中,解码器缺少浅层定位信息,边界和细结构首先受损。
第二,BCE + Dice 略优于仅 BCE。 前景占比不均衡时,直接优化区域重叠通常有帮助,但本次差距不大,不能把损失函数当成万能模块。
第三,无增强组在这次短实验中略高。 这不等于"增强有害"。单随机种子、12 个 Epoch 和固定验证集不足以支持该结论;合理解释是增强提高了训练难度,而短训练尚未把长期泛化优势表现出来。正式实验至少应增加随机种子、训练预算和外部测试集。
8. 参数量、计算量、显存与特征图
卷积层参数量近似为

输出特征尺寸为
时,卷积 MACs 约为

有些工具把一次乘加记作 1 MAC,有些换算成 2 FLOPs。比较论文或工具结果时,先确认统计口径。
单个激活张量的显存近似为

训练显存还包括梯度、优化器状态、临时算子缓冲以及 skip 特征。U-Net 在高分辨率阶段保留多级 skip,concat 又会临时增加通道,因此显存压力往往集中在浅层和解码融合处,而不是参数最多的瓶颈层。

图 9 训练模型的真实中间特征图
从实际激活可以观察到:
- 浅层特征响应边缘、亮度变化和局部纹理;
- 编码越深,空间分辨率越低,响应区域更连贯;
- 瓶颈层更像粗粒度语义图,不再保留精确轮廓;
- 解码器在上采样和 skip 融合后逐步恢复边界。
特征图不能只挑"看起来漂亮"的通道。更可靠的做法是固定样本、固定层、固定通道选择规则,并同时展示成功与失败案例。
9. 最有效的排错顺序
语义分割的大多数问题并不来自"模型不够新",而来自数据与评估协议。
9.1 先让模型过拟合极小样本
固定 1~4 张图,关闭随机增强,持续训练到训练 Dice 接近 1。如果做不到,优先检查:
- 图像与掩码文件是否一一对应;
- 掩码是否真的是 0/1 或合法类别索引;
- 几何增强是否同步;
- 输出和 target 的形状、dtype 是否匹配;
- 损失输入是否为 logits;
- 梯度、学习率与参数更新是否正常。
极小样本都无法过拟合时,继续换骨干或加注意力通常没有意义。
9.2 检查数据泄漏
医学、视频和连续切片数据必须按患者、病例或序列划分。把同一患者的相邻切片分到训练集和验证集,会得到漂亮但失真的验证结果。
9.3 小 batch 不要盲用 BatchNorm
3D 分割或大分辨率训练常把 batch size 压到 1~2。此时 BatchNorm 的统计量不稳定,可改用 GroupNorm、InstanceNorm,或冻结预训练编码器中的 BN。
9.4 先分析错误,再调阈值
二分类阈值 0.5 只是默认值。应在验证集上画 Precision-Recall 或 Dice-threshold 曲线,再选择阈值。阈值只能改变 FP/FN 权衡,无法修复模糊标签、数据泄漏和结构性漏检。
10. 变体怎么选
| 方法 | 主要改动 | 适用场景 |
|---|---|---|
| ResUNet | 卷积块加入残差连接 | 更深网络、优化更稳定 |
| UNet++ | 嵌套、多级 skip 融合 | 多尺度差异明显,显存允许 |
| Attention U-Net | 对 skip 做门控 | 背景复杂、需抑制无关区域 |
| 3D U-Net | Conv2d 改为 Conv3d | CT、MRI、显微体数据 |
| nnU-Net | 自动配置预处理、patch、网络和训练方案 | 医学分割强基线 |
| Swin/SSM U-Net | 用注意力或状态空间模块建模长程关系 | 高分辨率、全局上下文重要 |
| MedSAM/SAM2 | 提示式掩码生成 | 交互标注、开放对象分割 |
一个实用的选型顺序是:
- 第一次做任务:先跑通普通 U-Net;
- 医学 2D/3D:优先建立 nnU-Net 强基线;
- 自然场景:使用成熟框架和预训练编码器;
- 小目标或边界任务:先检查分辨率、采样、损失和标注质量;
- 提示式分割:明确 SAM 生成的是对象 mask,不等于固定类别语义分割。
11. 2024---2026 年值得关注的方向
近年的工作不再只是替换编码器。研究重点逐渐转向采样混叠、skip 几何、线性长程建模、基础模型协同、模型合并和评测可信度。
| 工作 | 会议 | 关注点 | 阅读入口 |
|---|---|---|---|
| nnU-Net Revisited | MICCAI 2024 | 强基线与公平评测 | 论文与评审 |
| SegMamba | MICCAI 2024 | 3D 体数据的线性长程建模 | 论文 / 代码 |
| SegMAN | CVPR 2025 | SSM、局部注意力与全尺度上下文 | 论文 / 代码 |
| LHU-Net | MICCAI 2025 | 轻量 3D 混合建模 | 论文 / 代码 |
| U-RWKV | MICCAI 2025 | 方向自适应线性长程建模 | 论文与评审 / 代码 |
| U-Net Transplant | MICCAI 2025 | 多个 3D 分割模型的能力合并 | 论文 / 代码 |
| CROWn | CVPR 2026 | 抗混叠下采样与跨尺度相位校准 | 论文 / 代码 |
| AD-GBC | CVPR 2026 | 区域级 skip 特征精炼 | 论文 / 代码 |
| Spatial-SAM | CVPR 2026 | 3D U-Net 的 SDF 记忆与 SAM2 协同 | 论文 / 代码 |
阅读新结构时,建议先问五个问题:
- 基线是否使用了同样的数据、预训练、增强和训练预算;
- 是否在多个数据集与多个随机种子上稳定成立;
- 提升是否超过实验波动;
- 是否同时报告边界指标、参数、显存和速度;
- 新模块拿掉后,强基线是否仍然被公平训练。
11.1 常用开源项目
以下 Stars 为 2026-08-23 快照,会随时间变化,不能代替代码质量、维护状态和许可证检查。
| 项目 | Stars | 许可证 | 适合做什么 |
|---|---|---|---|
| segmentation_models.pytorch | 11,703 | MIT | 快速组合 U-Net、FPN、DeepLab 与预训练编码器 |
| Pytorch-UNet | 11,605 | GPL-3.0 | 学习 U-Net 与单任务复现 |
| MMSegmentation | 9,924 | Apache-2.0 | 自然场景 benchmark 与配置化训练 |
| nnU-Net | 8,816 | Apache-2.0 | 医学 2D/3D 自配置强基线 |
| MONAI | 8,625 | Apache-2.0 | 医疗影像 transforms、训练、推理与部署 |
| SAM2 | 19,741 | Apache-2.0 | 图像与视频提示式分割 |
| MedSAM | 4,376 | Apache-2.0 | 医疗提示式分割与域适配 |
使用代码前应确认 commit、预训练权重、数据预处理和许可证。Stars 高不代表适合生产,未声明许可证也不等于可以自由商用。
12. 视频学习路线
不必把所有视频一次看完。先看结构,再完成一次实现,最后按任务进入 nnU-Net、3D 或基础模型。
| 阶段 | 视频 | 时长 |
|---|---|---|
| 中文结构 | U-Net 网络结构讲解 · 霹雳吧啦Wz | 12:15 |
| 中文实战 | PyTorch 搭建 U-Net 并训练 DRIVE · 霹雳吧啦Wz | 45:01 |
| 原论文热身 | 5 Minute Teaser Presentation of U-Net | 5:04 |
| 英文代码 | PyTorch Image Segmentation with U-Net from Scratch · Aladdin Persson | 51:53 |
| 医学强基线 | nnU-Net: A Self-configuring Method · Fabian Isensee | 29:53 |
| 3D 入门 | 3D U-Net for Volumetric Image Segmentation | 7:55 |
| 医疗工程 | Training Segmentation Models with MONAI and MedSAM | 15:24 |
| 基础模型 | Segment Anything Paper Explained | 6:15 |
最短路线是:12 分钟结构课、任选一门完整代码课,然后用自己的数据跑通训练、预测和错误分析。视频中的库版本可能过时,安装与接口以当前官方文档为准。
13. 总结
U-Net 可以用一句话概括:编码器扩大上下文,解码器恢复分辨率,跳跃连接补回定位信息,1×1 卷积完成逐像素分类。
真正把它用好,需要同时处理四个层面:
- 数据:图像与掩码严格对齐,划分方式没有泄漏;
- 结构:尺寸、通道和 skip 融合清楚可查;
- 目标:损失、阈值和指标与任务一致;
- 实验:强基线、受控变量、多随机种子和失败案例。
U-Net 的长期价值不在于"形状像字母 U",而在于它把语义压缩与定位恢复拆成了清晰、可验证的工程问题。先把这条主线跑通,再讨论注意力、Transformer、Mamba 或基础模型,结论通常会更可靠。
参考资料
- Ronneberger O, Fischer P, Brox T. U-Net: Convolutional Networks for Biomedical Image Segmentation. MICCAI, 2015.
- Çiçek Ö, et al. 3D U-Net: Learning Dense Volumetric Segmentation from Sparse Annotation. MICCAI, 2016.
- Isensee F, et al. nnU-Net: a self-configuring method for deep learning-based biomedical image segmentation. Nature Methods, 2021.
- Parkhi O M, et al. Cats and Dogs. CVPR, 2012.
- Penn-Fudan Database for Pedestrian Detection and Segmentation.
- PyTorch Documentation.
