
U-Net 技术详解为什么一个 2015 年的分割网络还在被反复使用U-Net 很容易被讲成一句话左边下采样右边上采样中间用 skip connection 连起来。这个说法没错但太轻了。真正让 U-Net 变成医学图像分割默认基线的并不是这个 U 字形画得漂亮而是它同时解决了三个在像素级预测里很烦的问题小数据、定位精度、全图推理效率。Ronneberger、Fischer 和 Brox 在 2015 年的 MICCAI 论文里提出 U-Net 时目标场景不是 ImageNet 那种有大量标注的分类任务而是生物医学图像标注贵、样本少、边界重要、输出要到像素级。论文摘要里有两个细节现在看仍然很有代表性网络可以用很少的标注图像端到端训练在当时的 GPU 上一张512×512512\times512512×512图像的分割不到 1 秒。这篇文章不把 U-Net 当成“经典网络结构”背诵一遍而是拆它为什么有效、原始论文里哪些设计后来被简化或遗忘、实际写 PyTorch 版本时哪些地方最容易踩坑。目录U-Net 要解决的不是分类而是密集预测编码器真正做的是上下文压缩解码器为什么不能只靠反卷积skip connection 的关键是“定位信息回流”原始 U-Net 里的 valid convolution、crop 和 overlap-tile加权损失分开粘连细胞的一个老但有用的技巧PyTorch 实现时最常见的几个差异U-Net 的局限以及它为什么还没过时U-Net 要解决的不是分类而是密集预测图像分类只需要给整张图一个标签。语义分割要给每个像素一个标签医学图像里这个差别尤其大一个细胞边界偏几像素可能就会影响计数、形态分析和后续 tracking。U-Net 论文的出发点很直接。当时的 CNN 在分类任务上已经很强但生物医学分割通常拿不到成千上万张精标图像。滑动窗口方法可以把一张图切成大量 patch看起来缓解了数据量问题可它有两个硬伤每个像素都要跑一次局部窗口计算高度重复窗口小则缺上下文窗口大又会丢定位精度。U-Net 把问题改成了全卷积密集预测。网络一次吃进一个较大的图像 tile直接输出对应区域的分割图。这样做有两个好处计算共享不用对每个像素重复跑 CNN网络在深层能看到较大感受野同时通过浅层特征补回空间细节。可以把 U-Net 的任务理解成这样fθ:RH×W×C→[0,1]H′×W′×K f_\theta: \mathbb{R}^{H\times W\times C}\rightarrow [0,1]^{H\times W\times K}fθ:RH×W×C→[0,1]H′×W′×K输入是一张图像输出是每个像素属于KKK个类别的概率。原始 U-Net 使用 unpadded 的3×33\times33×3卷积所以输出尺寸H′×W′H\times WH′×W′比输入小现在很多实现会用 padding 保持尺寸不变这一点后面会讲。编码器真正做的是上下文压缩U-Net 左半边通常叫 contracting path也就是编码器。它重复执行两个3×33\times33×3卷积、ReLU再接一个2×22\times22×2max pooling。每次下采样后空间分辨率减半通道数翻倍。这个设计很朴素但作用清楚浅层特征保留纹理、边缘和局部形状深层特征牺牲空间分辨率换取更大的上下文。医学图像里这很重要。单看一个小 patch细胞膜、噪声、染色纹理可能很像看到更大的邻域后网络才更容易判断某个结构是不是边界、内部还是背景。一个简化的编码器可以写成Input ↓ conv 3x3 ReLU, conv 3x3 ReLU Feature 64 ↓ maxpool 2x2 Feature 128 ↓ maxpool 2x2 Feature 256 ↓ maxpool 2x2 Feature 512 ↓ maxpool 2x2 Bottleneck 1024这跟普通分类 CNN 很像。差别在于分类网络会继续把空间信息压到全局向量里最后接全连接层U-Net 不这么做。它保留每一级特征图等右半边恢复分辨率时再拿回来用。原始论文里的网络总共有 23 个卷积层。这个数字今天不算深但在 2015 年的小样本医学分割设定下已经足够表达复杂边界。更重要的是网络没有全连接层因此可以自然地处理大图 tile也能用 overlap-tile 策略在任意大小图像上滑动推理。解码器为什么不能只靠反卷积如果只有编码器输出会变成低分辨率特征图。要做像素级预测网络必须把它放大回接近原图的尺寸。U-Net 右半边叫 expansive path也就是解码器。每一级先上采样再用2×22\times22×2convolution 减少通道数然后和左边同尺度的特征拼接最后接两层3×33\times33×3卷积。很多初学者会把解码器理解成“把图放大”。这不够准确。上采样只能恢复网格大小不能凭空恢复已经丢掉的边界位置。比如 max pooling 之后一个边界到底在左上还是右下深层特征未必还记得清楚。解码器如果只靠深层语义去猜会得到平滑但边界粗糙的 mask。U-Net 的解法是让深层语义和浅层定位在同一尺度重新汇合。深层特征告诉网络“这里大概是什么结构”浅层特征告诉网络“边缘在哪里”。拼接之后的卷积层再学习如何融合二者。这个结构可以粗略画成Encoder feature ─────────────┐ ↓ concat Upsampled decoder feature → conv → conv → refined mask feature注意这里是 concat不是 element-wise add。concat 会把通道维度直接拼起来让后续卷积自己学哪些浅层细节该保留、哪些只是噪声。ResNet 式相加更省参数但语义假设更强U-Net 原始设计选择 concat符合它对精细定位的需求。skip connection 的关键是“定位信息回流”U-Net 最常被记住的就是 skip connection。可这个词后来在 ResNet、DenseNet、Transformer 里都被用过含义容易混在一起。U-Net 的 skip connection 不是为了解决深层网络梯度消失也不是简单做残差学习。它主要是为了把高分辨率定位信息送回解码器。这点可以用一个边界分割例子理解。编码器深层可能已经知道“这一片区域大概率是细胞内部”但它无法精确知道边界曲线贴在哪个像素上。浅层特征对边缘、纹理变化更敏感却缺少语义判断。U-Net 把它们拼起来等于让网络在恢复分辨率时重新获得“局部证据”。这也是为什么 U-Net 在医学分割里比单纯 encoder-decoder 更稳。医学图像的目标常常形状不规则、边界细、样本少。如果模型只学一个低分辨率语义表示再通过上采样恢复 mask边界会很容易糊。skip connection 给了模型一条绕过瓶颈的细节通道。不过skip 也不是免费午餐。浅层特征里包含大量低级纹理可能把噪声带回解码器。实际项目里如果训练集成像条件变化很大U-Net 有时会过度依赖局部纹理跨域表现就会掉。这也是后来 Attention U-Net、UNet、nnU-Net、TransUNet 等变体不断改 skip 融合方式的原因之一。原始 U-Net 里的 valid convolution、crop 和 overlap-tile今天很多 PyTorch U-Net 教程会用 padding1让每次3×33\times33×3卷积前后尺寸不变。这样实现最省事输入输出可以天然对齐。但原始 U-Net 用的是 valid convolution也就是不 padding。每做一次3×33\times33×3卷积特征图边缘会少一圈像素。这会带来一个看起来麻烦、但在论文里很关键的设计左边传到右边的 feature map 需要 crop 后才能 concat。因为编码器同尺度特征图比解码器当前特征图更大直接拼不上。为什么作者当时要这么做核心是边界预测的可靠性。valid convolution 只输出拥有完整上下文的像素预测。对于大图推理U-Net 使用 overlap-tile 策略每次输入一个较大的 tile只取中间可靠区域的输出图像边界缺失的上下文用 mirror extrapolation 补齐。这样可以在 GPU 显存有限时处理任意大小图像同时减少 tile 接缝处的伪影。简化后可以理解为输入 tile 比输出 mask 更大 [ 需要上下文的输入区域 ] [ 输出可靠区域 ]现在很多任务不再严格沿用这个策略。padding 版本更方便也更适合和现代框架的数据管线对齐。但如果你做的是病理切片、遥感大图、显微镜全幅图像tile 推理的边界伪影仍然是现实问题。此时原始 U-Net 的 overlap-tile 思路比“直接 padding 然后拼回去”更值得参考。加权损失分开粘连细胞的一个老但有用的技巧U-Net 论文里另一个常被略过的部分是加权 loss。医学图像分割的难点不只是前景和背景还包括相邻目标之间的分界。两个细胞贴在一起时如果模型把它们预测成一个连通块像素准确率可能还不错但实例层面的结果会很差。原始 U-Net 给每个像素分配一个权重尤其提高相邻细胞之间背景边界的权重。论文里的权重图形式是w(x)wc(x)w0⋅exp(−(d1(x)d2(x))22σ2) w(x)w_c(x)w_0\cdot\exp\left(-\frac{(d_1(x)d_2(x))^2}{2\sigma^2}\right)w(x)wc(x)w0⋅exp(−2σ2(d1(x)d2(x))2)其中wc(x)w_c(x)wc(x)用来平衡类别频率d1(x)d_1(x)d1(x)是像素xxx到最近细胞边界的距离d2(x)d_2(x)d2(x)是到第二近细胞边界的距离。直觉上如果一个背景像素夹在两个细胞之间d1(x)d2(x)d_1(x)d_2(x)d1(x)d2(x)会比较小它的权重就会变大。模型因此会更认真地区分粘连目标之间的窄缝。训练目标是像素级 softmax 加交叉熵E∑x∈Ωw(x)logpℓ(x)(x) E\sum_{x\in\Omega} w(x)\log p_{\ell(x)}(x)Ex∈Ω∑w(x)logpℓ(x)(x)实际实现时通常会写成带负号的 cross entropy并按框架约定做 mean 或 sum。公式符号不用死背重点是这个思想如果评估目标关心边界和实例分离就不要只用普通像素平均损失。Dice loss、Focal loss、Boundary loss、Tversky loss 等现代选择本质上也都在处理“普通交叉熵不够关心我真正关心的错误”这个问题。数据增强不是装饰项U-Net 能在少量标注上工作结构只是一半原因。另一半是强数据增强。原始论文特别强调 elastic deformation因为生物组织形变是显微图像中很常见的变化。模型如果只看原始训练图很容易记住局部纹理加上形变、旋转、平移、灰度变化后它更可能学到稳定的形态线索。这点在今天仍然适用。很多 U-Net 复现失败不是因为网络写错而是增强策略太弱。医学图像尤其容易出现这种情况训练集来自一个设备或一个染色流程测试集换了设备、实验批次或病人来源像素分布就变了。没有足够增强U-Net 会表现得像一个纹理记忆器。一个更实用的经验是增强要模拟你测试时真的会遇到的变化。显微图像可以考虑弹性形变、亮度/对比度、噪声、轻微模糊遥感图像可能更需要尺度、旋转、季节和光照变化工业缺陷检测则要小心过强增强把缺陷本身破坏掉。PyTorch 实现时最常见的几个差异现在大家写 U-Net通常不是照着 2015 年 Caffe 版本逐行复刻。下面这些差异很常见也会影响你怎么看论文结果和教程代码。第一padding 版本会让尺寸对齐更简单。典型 PyTorch 实现会在3×33\times33×3卷积里使用padding1这样 encoder 的 skip feature 和 decoder 的上采样 feature 不需要 crop。好处是代码清楚输出和输入同尺寸代价是边界位置引入了 padding 假设。第二上采样方式有多种。原始 U-Net 使用 up-convolution现代实现里可能用 transposed convolution也可能用 bilinear upsampling 后接卷积。转置卷积有可学习参数但如果 kernel/stride 设计不当容易出现 checkerboard artifact。bilinear conv 更稳一些但表达能力略有不同。第三归一化层不是原始 U-Net 的必需部件。很多现代版本会加入 BatchNorm、InstanceNorm 或 GroupNorm。医学图像 batch size 常常很小BatchNorm 的统计量未必稳定这时 InstanceNorm 或 GroupNorm 有时更合适。第四损失函数要跟任务匹配。二分类前景/背景可以用 BCEWithLogitsLoss、Dice loss 或二者组合多类语义分割常用 CrossEntropyLoss实例边界敏感任务可以引入 boundary-aware loss 或额外预测距离图。不要因为教程用了 Dice loss 就机械照搬先看评估指标到底惩罚什么。一个最小的 U-Net block 大概长这样importtorchimporttorch.nnasnnclassDoubleConv(nn.Module):def__init__(self,in_ch,out_ch):super().__init__()self.netnn.Sequential(nn.Conv2d(in_ch,out_ch,3,padding1),nn.ReLU(inplaceTrue),nn.Conv2d(out_ch,out_ch,3,padding1),nn.ReLU(inplaceTrue),)defforward(self,x):returnself.net(x)这段代码看起来和原论文不完全一样因为它用了 padding。它适合教学和很多普通项目但如果你要严谨复现原始 U-Net就要处理 valid convolution、crop、tile 推理和边界镜像。一个常见误区U-Net 不是“随便套就行”的万能分割器U-Net 的强项是中小数据、目标结构相对局部、需要精细定位的分割任务。它的弱项也很明显。如果任务需要特别长距离的全局关系普通 U-Net 的卷积感受野可能不够。比如一张大图里两个区域的关系决定了某个像素类别仅靠局部卷积不一定稳定。可以通过更深 encoder、空洞卷积、注意力模块或 Transformer encoder 缓解但这已经是变体路线。如果训练和测试分布差异很大U-Net 容易吃亏。skip connection 会把浅层纹理直接送到解码器这对同分布精细边界很好对跨设备、跨中心、跨染色流程的泛化不一定好。医学 AI 项目里外部验证比单数据集 Dice 更能说明问题。如果标注边界本身噪声很大模型也会被迫学习不稳定的边界。此时盲目堆 U-Net 深度通常不如先检查标注一致性、类别定义和后处理规则。分割问题经常不是网络结构单独决定的。U-Net 为什么还没过时U-Net 到现在还常被使用原因并不神秘。它提供了一个很好的默认归纳偏置局部卷积适合图像纹理编码器提供语义上下文解码器恢复分辨率skip connection 保留细节。这个组合简单、可训练、可改造也容易作为 baseline。很多后续模型其实是在回答“U-Net 哪一部分还不够好”UNet 改 skip 的层级融合Attention U-Net 给 skip 加门控nnU-Net 把预处理、网络配置、训练策略自动化TransUNet 和 Swin-UNet 引入更强的全局建模。它们不是把 U-Net 扔掉而是在 U-Net 的骨架上替换部件。这也是学习 U-Net 的价值。你理解了它就更容易理解后来的医学分割模型为什么要改 encoder、为什么要改 skip、为什么要加边界 loss、为什么很多论文仍然把 U-Net 当作比较对象。我的判断是如果你刚进入图像分割U-Net 仍然值得先认真写一遍。不要只把模块拼起来跑通。最好做三件事用 padding 版本写一个现代简洁实现再读原论文理解 valid convolution 和 overlap-tile最后在自己的数据上比较 BCE、Dice、边界权重和增强策略。这个过程比直接上一个更大的 Transformer 分割模型更能让你知道分割任务到底难在哪里。参考资料Olaf Ronneberger, Philipp Fischer, Thomas Brox. U-Net: Convolutional Networks for Biomedical Image Segmentation. MICCAI 2015 / arXiv:1505.04597. Retrieved 2026-06-30. https://arxiv.org/abs/1505.04597University of Freiburg LMB. U-Net project page and original Caffe release. Retrieved 2026-06-30. https://lmb.informatik.uni-freiburg.de/people/ronneber/u-net/milesial/Pytorch-UNet. PyTorch implementation and training examples. Retrieved 2026-06-30. https://github.com/milesial/Pytorch-UNet