尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ResUNet图像分割实战:从残差原理到PyTorch实现与调优

ResUNet图像分割实战:从残差原理到PyTorch实现与调优 1. 项目概述从U-Net到ResUNet的演进之路在图像分割这个计算机视觉的核心战场上U-Net以其独特的编码器-解码器结构和跳跃连接一度成为医学图像分割等领域的标杆。但做过实际项目的人都知道当网络深度增加以追求更高精度时一个老问题又回来了梯度消失或爆炸导致模型难以训练性能甚至不升反降。ResUNet的出现正是为了解决这个痛点。它不是什么凭空创造的新架构而是将深度学习领域里程碑式的思想——ResNet残差网络中的残差连接巧妙地嫁接进了U-Net的框架里。简单来说ResUNet就是一个“带残差块的U-Net”。这个看似简单的组合却让深度分割网络的训练变得稳定让模型能够学习更复杂、更深层的特征表示从而在众多分割任务上尤其是需要精细边界的场景中表现出了更强的竞争力。如果你正在为分割任务的精度瓶颈发愁或者想知道如何让U-Net变得更强大那么深入理解并动手实现一个ResUNet会是极具价值的一步。2. 核心原理深度拆解残差思想如何赋能U-Net要搞懂ResUNet不能只停留在“U-NetResNet”这个表面概念必须深入理解残差学习Residual Learning的核心思想以及它如何与U-Net的架构产生化学反应。2.1 重温U-Net的骨架与瓶颈经典的U-Net结构对称形似字母“U”。左侧是编码器下采样路径通过卷积和池化层层提取特征同时压缩空间尺寸右侧是解码器上采样路径通过转置卷积或上采样操作恢复空间尺寸并融合来自编码器同层的特征图跳跃连接最终输出分割图。它的优势在于跳跃连接实现了多尺度特征融合让解码器在恢复细节时能“回忆”起编码器早期捕捉到的轮廓、纹理信息。然而其瓶颈也在于此编码器和解码器内部的每个阶段通常由两个连续的3x3卷积构成。当我们需要一个更深的U-Net例如增加每个阶段的卷积层数或增加下采样次数来提升模型容量时网络会变得难以优化。梯度在反向传播过程中需要经过许多层和非线性激活函数很容易变得极其微小消失或巨大爆炸导致深层的权重几乎得不到有效更新。2.2 残差学习的革命性思想ResNet提出的残差块Residual Block是解决深度网络训练难题的一把钥匙。它的核心公式可以简化为输出 恒等映射输入 残差函数输入。在一个标准的残差块中输入x会走两条路。一条路通过几个卷积层、批归一化层和激活函数学习所谓的“残差”F(x)。另一条路是“快捷连接”Shortcut Connection直接将输入x传递过来。最后将这两条路的输出相加H(x) F(x) x。这里的精妙之处在于解决梯度消失在反向传播时梯度可以通过快捷连接几乎无损地回传相当于为梯度开辟了一条“高速公路”确保了深层网络也能接收到有效的梯度信号。学习目标转变网络不再需要直接学习一个复杂的底层映射H(x)而是学习残差F(x) H(x) - x。通常认为学习残差比学习原始映射更容易。例如如果最优的H(x)就约等于x即恒等映射是最优解那么将残差F(x)推向0比让一堆非线性层去拟合一个恒等映射要简单得多。2.3 ResUNet的架构融合策略ResUNet将U-Net中的普通卷积块替换为残差块。具体融合方式主要有两种编码器/解码器阶段内部替换这是最直接、最常见的做法。U-Net每个下采样或上采样阶段中原本的“Conv - BN - ReLU - Conv - BN - ReLU”序列被替换为一个或多个串联的残差块。每个残差块内部包含两个3x3卷积以及跨越它们的快捷连接。跳跃连接的增强原始的U-Net跳跃连接只是将编码器的特征图与解码器上采样后的特征图在通道维度上拼接Concatenate。在ResUNet中这个拼接操作前后也可以引入残差思想。例如可以在跳跃连接的两侧分别添加一个卷积层调整通道数然后使用相加Add而非拼接这构成了一种更紧密的残差式特征融合但较少见因为拼接能保留更多原始特征信息。以第一种方式为例一个ResUNet的下采样阶段可能看起来像这样输入先经过一个步长为2的卷积或池化层进行下采样然后进入一个由2个残差块组成的序列。每个残差块让特征在其中进行更深的非线性变换同时通过快捷连接保持信息流的通畅。注意在替换时需要特别注意特征图尺寸和通道数的匹配。当快捷连接两端的特征图尺寸或通道数不一致时例如经过下采样后需要在快捷路径上添加一个1x1卷积层有时带步长来进行线性投影以确保可以正确相加。3. 从零实现ResUNetPyTorch实战指南理论清晰之后动手实现是巩固理解的最佳方式。下面我们用PyTorch框架一步步构建一个标准的ResUNet。我们将采用最经典的组合方式使用基础的ResNet残差块并保持U-Net的跳跃连接为拼接操作。3.1 基础构建块残差块Residual Block的实现首先我们实现一个基础的残差块。这里我们实现一个包含两个3x3卷积的“基本块”BasicBlock这也是ResNet-18/34中使用的结构。import torch import torch.nn as nn import torch.nn.functional as F class ResidualBlock(nn.Module): 基本的残差块包含两个3x3卷积 def __init__(self, in_channels, out_channels, stride1, downsampleNone): Args: in_channels: 输入特征图的通道数 out_channels: 输出特征图的通道数也是两个卷积层的输出通道数 stride: 第一个卷积层的步长默认为1。当需要下采样时可设为2。 downsample: 一个可调用模块通常是1x1卷积用于在快捷连接中匹配尺寸和通道数 super(ResidualBlock, self).__init__() self.conv1 nn.Conv2d(in_channels, out_channels, kernel_size3, stridestride, padding1, biasFalse) self.bn1 nn.BatchNorm2d(out_channels) self.relu nn.ReLU(inplaceTrue) self.conv2 nn.Conv2d(out_channels, out_channels, kernel_size3, stride1, padding1, biasFalse) self.bn2 nn.BatchNorm2d(out_channels) self.downsample downsample # 存储传入的下采样模块 def forward(self, x): identity x # 保存输入作为快捷连接的起点 out self.conv1(x) out self.bn1(out) out self.relu(out) out self.conv2(out) out self.bn2(out) # 如果需要进行下采样或通道数调整则对identity应用downsample if self.downsample is not None: identity self.downsample(x) # 核心操作残差相加 out identity out self.relu(out) # 相加后再经过一次激活 return out关键点解析nn.Conv2d中的biasFalse是因为后面紧跟了批归一化BatchNormBN层本身包含可学习的偏置参数因此卷积中可以省略bias以减少参数并稳定训练。nn.ReLU(inplaceTrue)中的inplaceTrue可以节省少量内存它直接修改输入张量而不是创建新的输出张量。在大多数情况下这是安全的。downsample模块的设计当stride ! 1进行空间下采样或in_channels ! out_channels进行通道数扩展时快捷连接两端的张量无法直接相加。此时downsample通常是一个包含1x1卷积和批归一化的序列负责将identity的尺寸和通道数变换到与out一致。3.2 编码器下采样路径实现编码器由多个阶段Stage组成每个阶段开始时进行下采样通常通过步长为2的卷积或最大池化然后跟随若干个残差块。class EncoderBlock(nn.Module): 编码器的一个阶段包含一个下采样层和多个残差块 def __init__(self, in_channels, out_channels, num_blocks, stride2): super(EncoderBlock, self).__init__() # 下采样层可以使用带步长的卷积也可以使用池化。这里使用步长为2的卷积。 self.downsample_conv nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size3, stridestride, padding1, biasFalse), nn.BatchNorm2d(out_channels), nn.ReLU(inplaceTrue) ) # 构建多个残差块 blocks [] # 第一个残差块可能需要处理下采样和通道数变化 downsample None if stride ! 1 or in_channels ! out_channels: downsample nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size1, stridestride, biasFalse), nn.BatchNorm2d(out_channels), ) blocks.append(ResidualBlock(in_channels, out_channels, stride, downsample)) # 后续的残差块输入输出通道相同步长为1 for _ in range(1, num_blocks): blocks.append(ResidualBlock(out_channels, out_channels, stride1)) self.blocks nn.Sequential(*blocks) def forward(self, x): x self.downsample_conv(x) # 初始下采样 x self.blocks(x) # 通过残差块序列 return x实操心得第一个EncoderBlock处理原始图像输入的stride通常设为1因为不需要立即下采样。也可以将下采样完全交给残差块的第一个卷积通过设置stride2这样结构更统一。上述实现是一种更清晰、将下采样与特征提取分离的方式。3.3 解码器上采样路径与跳跃连接解码器的目标是逐步恢复空间分辨率。我们使用转置卷积Transposed Convolution进行上采样然后与编码器对应层的特征图拼接。class DecoderBlock(nn.Module): 解码器的一个阶段包含上采样、特征融合和卷积细化 def __init__(self, in_channels, skip_channels, out_channels): Args: in_channels: 来自上一解码器层的输入通道数 skip_channels: 来自编码器跳跃连接的特征图通道数 out_channels: 本阶段输出通道数 super(DecoderBlock, self).__init__() # 上采样层使用转置卷积将空间尺寸扩大2倍 self.up_conv nn.ConvTranspose2d(in_channels, in_channels // 2, kernel_size2, stride2) # 上采样后与跳跃特征拼接所以拼接后的通道数是 (in_channels//2 skip_channels) concat_channels in_channels // 2 skip_channels # 拼接后的特征融合层可以用普通卷积块这里为了保持深度继续使用残差块 # 注意这里使用一个残差块其快捷连接可能需要处理通道数变化因为concat_channels可能不等于out_channels self.conv_block ResidualBlock(concat_channels, out_channels, stride1) def forward(self, x, skip): 前向传播 Args: x: 来自上一解码器层的输入 skip: 来自编码器的跳跃连接特征 x self.up_conv(x) # 确保跳跃连接的特征图尺寸与上采样后的x匹配由于池化等操作尺寸可能差1 if x.shape[2:] ! skip.shape[2:]: # 使用中心裁剪或插值对齐尺寸这里使用插值 x F.interpolate(x, sizeskip.shape[2:], modebilinear, align_cornersTrue) # 在通道维度上拼接 x torch.cat([x, skip], dim1) x self.conv_block(x) return x注意事项跳跃连接中特征图尺寸对齐是一个常见的坑。由于下采样时除法取整等问题上采样后的尺寸可能比编码器对应层的特征图尺寸小1个像素。常用的处理方法是1对编码器特征进行中心裁剪2对解码器特征进行插值放大。上述代码采用了更灵活的插值方法。在实际的U-Net原始论文中他们通过裁剪来保证尺寸一致。3.4 整合完整的ResUNet模型现在我们将编码器、解码器和最后的输出层组合起来。class ResUNet(nn.Module): def __init__(self, in_channels3, num_classes1, base_channels64): Args: in_channels: 输入图像通道数RGB图为3灰度图为1 num_classes: 分割类别数二分类通常为1输出一个通道用Sigmoid激活 base_channels: 第一个卷积层的输出通道数后续每下采样一次通道数翻倍 super(ResUNet, self).__init__() # 初始卷积层不属于任何编码器块提取浅层特征 self.initial_conv nn.Sequential( nn.Conv2d(in_channels, base_channels, kernel_size7, stride1, padding3, biasFalse), nn.BatchNorm2d(base_channels), nn.ReLU(inplaceTrue), # 可选的初始池化层原文U-Net没有ResNet有。这里保留灵活性。 # nn.MaxPool2d(kernel_size3, stride2, padding1) ) # 定义编码器各阶段的通道数和残差块数量这里参考一个4层下采样的结构 encoder_channels [base_channels, base_channels*2, base_channels*4, base_channels*8, base_channels*16] # 每个阶段的残差块数量可以自定义这里设为[2,2,2,2,2]模仿ResNet-18风格 num_blocks [2, 2, 2, 2, 2] # 构建编码器 self.encoders nn.ModuleList() in_ch base_channels for i, (out_ch, n_blocks) in enumerate(zip(encoder_channels[1:], num_blocks[1:])): # 第一个编码器阶段enc1的stride为1后续为2 stride 1 if i 0 else 2 self.encoders.append(EncoderBlock(in_ch, out_ch, n_blocks, stridestride)) in_ch out_ch # 桥接层Bottleneck位于编码器和解码器之间可以加深网络 self.bottleneck nn.Sequential( *[ResidualBlock(encoder_channels[-1], encoder_channels[-1]) for _ in range(2)] ) # 构建解码器 self.decoders nn.ModuleList() # 解码器通道数设计通常逐级减半 decoder_channels [base_channels*8, base_channels*4, base_channels*2, base_channels] for i, (out_ch, skip_ch) in enumerate(zip(decoder_channels, encoder_channels[-2::-1])): # in_ch_for_decoder: 对于第一个解码器输入是bottleneck的输出通道 in_ch_for_decoder encoder_channels[-1] if i 0 else decoder_channels[i-1] self.decoders.append(DecoderBlock(in_ch_for_decoder, skip_ch, out_ch)) # 最终输出层 self.final_conv nn.Conv2d(base_channels, num_classes, kernel_size1) # 如果是二分类最后接Sigmoid多分类则接Softmax通常在损失函数中处理 # self.final_activation nn.Sigmoid() if num_classes 1 else nn.Softmax(dim1) def forward(self, x): # 初始卷积 x0 self.initial_conv(x) # 编码过程并保存跳跃连接特征 skips [x0] x x0 for encoder in self.encoders: x encoder(x) skips.append(x) # 桥接层 x self.bottleneck(x) # 解码过程注意skips的顺序是反的 for i, decoder in enumerate(self.decoders): skip skips[-(i2)] # 从后往前取编码器特征 x decoder(x, skip) # 最终输出 out self.final_conv(x) # 激活函数可以放在这里也可以放在损失函数计算时 # out self.final_activation(out) return out模型结构要点encoder_channels定义了从浅到深的特征通道数遵循每下采样一次通道数翻倍的惯例。skips列表存储了所有编码器阶段的输出包括初始卷积后的x0用于后续与解码器对应层拼接。桥接层bottleneck在最低分辨率下进一步提取特征通常由几个残差块组成是增加网络深度的关键位置之一。解码器通道数逐级减少最终恢复到与base_channels一致然后通过一个1x1卷积映射到类别数。4. 模型训练的关键配置与技巧搭建好模型只是第一步如何有效地训练它同样至关重要。ResUNet的训练既有深度学习模型的通用性也有其特殊性。4.1 损失函数的选择图像分割任务的损失函数需要仔细考量二分类任务如病灶分割BCEWithLogitsLoss最常用。它将Sigmoid激活和二元交叉熵BCE损失合并数值上更稳定。我们的模型最后一层不接Sigmoid直接使用此损失。Dice Loss直接优化Dice系数F1分数对类别不平衡问题如小目标更鲁棒。常与BCE Loss结合使用Loss BCE_Loss Dice_Loss。多分类任务如语义分割CrossEntropyLoss标准选择。需要确保模型输出为[N, C, H, W]且标签为[N, H, W]的类别索引。import torch.nn as nn # 二分类 criterion_bce nn.BCEWithLogitsLoss() # 多分类 criterion_ce nn.CrossEntropyLoss(ignore_index255) # 忽略标签为255的像素 # Dice Loss 示例实现二分类 class DiceLoss(nn.Module): def __init__(self, smooth1e-6): super(DiceLoss, self).__init__() self.smooth smooth def forward(self, logits, targets): # logits: [N, 1, H, W], targets: [N, 1, H, W] probs torch.sigmoid(logits) intersection (probs * targets).sum(dim(2,3)) union probs.sum(dim(2,3)) targets.sum(dim(2,3)) dice (2. * intersection self.smooth) / (union self.smooth) return 1 - dice.mean()4.2 优化器与学习率策略优化器AdamW是目前很多视觉任务的首选它修正了Adam的权重衰减方式通常能带来更好的泛化性能。SGD配合动量如0.9和恰当的权重衰减在充分调优后可能达到更高的最终精度但需要更仔细的学习率调整。学习率调度对于ResUNet这类深度模型学习率热身Warmup和余弦退火Cosine Annealing是非常有效的策略。Warmup训练初期学习率从一个小值线性增加到初始学习率有助于稳定训练初期。CosineAnnealingLR学习率按照余弦函数从初始值衰减到接近0使训练后期进行更精细的优化。from torch.optim import AdamW, SGD from torch.optim.lr_scheduler import CosineAnnealingLR, LinearLR optimizer AdamW(model.parameters(), lr1e-4, weight_decay1e-4) # 或使用SGD # optimizer SGD(model.parameters(), lr0.01, momentum0.9, weight_decay1e-4) # 定义总epoch数和warmup epoch数 total_epochs 200 warmup_epochs 10 # Warmup调度器 scheduler_warmup LinearLR(optimizer, start_factor0.01, end_factor1.0, total_iterswarmup_epochs) # 主调度器余弦退火 scheduler_cosine CosineAnnealingLR(optimizer, T_maxtotal_epochs - warmup_epochs, eta_min1e-6) # 在训练循环中 for epoch in range(total_epochs): # ... 训练一个epoch ... if epoch warmup_epochs: scheduler_warmup.step() else: scheduler_cosine.step()4.3 数据增强策略数据增强是提升模型泛化能力、防止过拟合的利器对于医学图像等数据稀缺的领域尤其重要。空间变换随机水平/垂直翻转、随机旋转如±15度、随机缩放如0.8-1.2倍、弹性形变对医学图像有效、随机裁剪。像素变换随机亮度、对比度、饱和度调整对RGB图像随机高斯噪声随机模糊。重要提示必须保证对图像和标签Mask进行完全相同的空间变换否则会导致图像和标签不对齐训练完全失败。使用Albumentations或torchvision.transforms.functional等支持对图像-标签对进行同步变换的库。import albumentations as A from albumentations.pytorch import ToTensorV2 # 定义训练集增强管道 train_transform A.Compose([ A.RandomRotate90(p0.5), A.Flip(p0.5), A.ShiftScaleRotate(shift_limit0.0625, scale_limit0.1, rotate_limit15, p0.5, border_mode0), # border_mode0 表示填充0 A.RandomBrightnessContrast(brightness_limit0.1, contrast_limit0.1, p0.3), A.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), # ImageNet均值标准差可针对数据集调整 ToTensorV2(), ]) # 注意标签Mask的变换管道通常只包含空间变换不包含像素变换和归一化。5. 实战调试与性能优化经验模型跑起来后真正的挑战才开始。以下是一些从实际项目中总结的调试和优化经验。5.1 训练过程监控与诊断损失曲线观察训练损失不下降检查学习率是否太小、数据预处理是否正确如图像归一化范围、模型初始化是否有问题、梯度是否被裁剪torch.nn.utils.clip_grad_norm_得过小。验证损失远高于训练损失这是典型的过拟合。需要加强数据增强、增加正则化如Dropout、权重衰减、或使用更深的模型配合早停Early Stopping。损失出现NaN检查数据中是否有无效值如NaN或inf学习率是否过高损失函数计算是否可能溢出如Dice Loss中分母为0。中间特征可视化这是理解模型是否“学对了”的强力工具。可以随机选取几张验证集图片将编码器某层的输出特征图取平均或选几个通道可视化出来。你应该能看到浅层网络学习到的是边缘、纹理深层网络学习到的是更抽象、与目标相关的模式。如果深层特征图看起来是噪声可能意味着梯度没有有效回传残差连接可能配置有误。梯度流检查在PyTorch中你可以注册一个钩子hook来检查某一层输入的梯度。在ResUNet中特别要检查瓶颈层bottleneck或最深编码器块的梯度范数。如果这些地方的梯度非常小说明残差连接可能没有正常工作或者存在梯度消失。5.2 针对ResUNet的特定调优点残差块变体选择我们实现的是BasicBlock适用于像ResNet-18/34这样的中等深度网络。对于更深的ResUNet例如超过50层可以考虑使用BottleneckBlock1x1 - 3x3 - 1x1结构它在加深网络的同时控制了计算量和参数量。BottleneckBlock先将通道数压缩如减至1/4再进行3x3卷积最后恢复通道数。这大大减少了中间3x3卷积的计算负担。跳跃连接的处理拼接 vs 相加原始U-Net用拼接保留了更多原始特征信息。你也可以尝试将跳跃连接改为相加但这要求编码器和解码器对应层的通道数严格一致且通常需要在相加前用1x1卷积调整编码器特征的通道数。相加的计算量更小但可能损失一些信息。根据我的经验在大多数任务中拼接的表现更稳定、更好。注意力门控这是一个高级技巧。在跳跃连接处引入注意力机制如Attention Gate让解码器动态地、有选择性地关注编码器特征中最重要的空间位置可以进一步提升精度尤其是在目标大小不一、背景复杂的场景中。深度监督在解码器的中间层也添加辅助输出层并计算损失。这些辅助损失在训练时共同参与梯度回传相当于为网络提供了多层次的监督信号有助于缓解梯度消失并可能加速收敛。在推理时这些辅助层被丢弃。5.3 常见问题排查速查表问题现象可能原因排查与解决方法训练初期损失极高且不降1. 最后一层激活函数用错如二分类用了Softmax。2. 学习率极大。3. 数据标签格式错误如二分类标签应是0/1却成了0/255。1. 检查模型输出层和损失函数是否匹配。2. 将学习率降至1e-5或更低试跑几个batch。3. 可视化几个batch的标签确认其值域。验证集精度远低于训练集1. 过拟合。2. 训练和验证的数据预处理不一致如归一化参数不同。3. 数据泄露验证集数据以某种形式混入了训练过程。1. 增强数据增强、添加Dropout、增大权重衰减。2. 确保train_transform和val_transform唯一区别是增强部分。3. 严格检查数据划分代码。GPU内存溢出OOM1. 输入图像尺寸过大。2. 批次大小Batch Size过大。3. 模型过深、参数过多。1. 减小输入尺寸或使用更小的base_channels。2. 减小Batch Size并累积梯度Gradient Accumulation来模拟大Batch。3. 考虑使用BottleneckBlock或减少残差块数量。预测结果全是黑色或白色1. 类别极度不平衡模型倾向于预测主导类别。2. 损失函数权重设置不当。3. 模型输出层初始化导致输出饱和如Sigmoid输出恒为0或1。1. 使用Dice Loss、Focal Loss等对不平衡数据友好的损失。2. 在CrossEntropyLoss中为不同类别设置weight参数。3. 检查模型最后一层卷积的权重初始化避免过大。训练速度很慢1. 数据加载是瓶颈未使用多进程。2. 模型某些操作在CPU上如不必要的.item()或.numpy()调用。3. 使用了过大的、计算复杂的增强如高强度的弹性形变。1. 设置DataLoader的num_workers 0pin_memoryTrue。2. 使用PyTorch Profiler定位慢速操作。3. 简化或移除耗时增强。6. 超越基础ResUNet的进阶变体与应用掌握了标准ResUNet后了解其变体可以帮你应对更复杂的场景。6.1 注意力ResUNetAttention ResUNet在跳跃连接处集成注意力门控Attention Gate是提升ResUNet性能的有效手段。注意力门控通过学习一个权重图让解码器在融合编码器特征时能够聚焦于与当前解码任务更相关的区域抑制不相关的背景噪声。其核心是在DecoderBlock的拼接操作前对跳跃特征skip进行加权将解码器的上采样特征g和编码器特征x拼接或相加后通过一个小的网络通常包含卷积、ReLU、Sigmoid生成一个介于0到1之间的注意力系数图alpha。将alpha与编码器特征x逐元素相乘得到加权的跳跃特征。将加权后的跳跃特征与解码器特征拼接。这种方法在医学图像分割中效果显著能更好地勾勒出病灶的边界。6.2 深度可分离卷积ResUNet为了部署在移动端或边缘设备需要考虑模型的计算量和参数量。将残差块中的标准3x3卷积替换为深度可分离卷积Depthwise Separable Convolution可以大幅减少计算量。一个深度可分离残差块先进行逐通道的3x3卷积Depthwise Conv再进行1x1的逐点卷积Pointwise Conv来融合通道信息。这样做的理论计算量约为标准卷积的1/输出通道数 1/卷积核面积通常能减少几倍到十几倍的计算量而精度损失很小。6.3 在具体领域的应用微调医学图像分割这是ResUNet的“主战场”。针对CT、MRI等3D数据可以将其扩展为3D ResUNet将2D卷积换成3D卷积。预处理如窗宽窗位调整、骨骼剥离和后处理如连通域分析去除小噪声至关重要。遥感图像分割地物分类、建筑物提取等任务。由于图像尺寸巨大通常需要采用“裁剪-预测-拼接”的策略。可以尝试在编码器中使用带空洞卷积Dilated Convolution的残差块在不损失分辨率的情况下扩大感受野。自然图像抠图对于人像抠图等需要极度精细边界的任务可以尝试更浅但更宽的ResUNet增加base_channels并在跳跃连接中融合更多低层特征。损失函数上结合L1损失和感知损失Perceptual Loss可能效果更好。实现一个能工作的ResUNet是第一步但让它在你特定的数据集和任务上达到最佳性能才是一个合格的深度学习实践者应该追求的目标。这需要不断地实验、观察、分析和调整。从损失曲线的细微变化中发现问题从失败实验里总结规律这个过程本身就是深度学习中比模型结构更宝贵的“经验”。
返回列表