首页 新能源汽车

告别繁琐:PyTorch 零基础 SegFormer 语义分割实战指南

字数: (0217)
阅读: (1450)
内容摘要:告别繁琐:PyTorch 零基础 SegFormer 语义分割实战指南,

在计算机视觉领域,语义分割是至关重要的任务,它能够像素级别地理解图像内容。SegFormer 凭借其高效性和卓越的性能,成为了语义分割领域的热门选择。本文将深入探讨如何使用 PyTorch 从零开始实现 SegFormer 语义分割,并分享实战过程中遇到的各种问题及解决方案。在开始之前,假设你已经对 PyTorch 有一定的基础了解,例如张量操作、模型构建和训练循环。

SegFormer 原理剖析

SegFormer 采用了 Transformer 架构,但针对语义分割任务进行了优化,主要包含以下几个关键模块:

告别繁琐:PyTorch 零基础 SegFormer 语义分割实战指南
  • Hierarchical Transformer Encoder: SegFormer 使用分层 Transformer 编码器来提取多尺度的特征表示,这对于处理不同大小的对象至关重要。它不像传统的卷积神经网络那样,使用固定的感受野,而是通过自注意力机制动态地调整感受野的大小。
  • Mix Transformer Decoder: SegFormer 的解码器设计简洁高效,它将来自不同层级的编码器特征进行混合,并使用 MLP (Multilayer Perceptron) 进行像素级别的分类。相比于复杂的解码器结构,Mix Transformer Decoder 在保持性能的同时,显著降低了计算成本。

Transformer 编码器细节

SegFormer 的编码器由多个 Transformer 块组成,每个块包含以下几个部分:

告别繁琐:PyTorch 零基础 SegFormer 语义分割实战指南
  1. Self-Attention: 自注意力机制是 Transformer 的核心,它允许模型关注图像中不同区域之间的关系。通过计算每个像素与其他像素之间的相似度,模型可以学习到全局的上下文信息。
  2. Feed-Forward Network: 前馈网络是一个两层 MLP,用于对自注意力机制的输出进行非线性变换。它可以增强模型的表达能力,从而更好地学习图像特征。
  3. Normalization: 为了稳定训练过程,SegFormer 使用了 Layer Normalization,它将每个样本的特征进行归一化,使其具有相同的均值和方差。

解码器设计要点

SegFormer 的解码器将来自不同层级的编码器特征进行聚合,并使用 MLP 进行像素级别的分类。具体来说,它将低分辨率的特征图进行上采样,使其与高分辨率的特征图具有相同的尺寸,然后将它们拼接在一起。最后,使用一个 MLP 将拼接后的特征映射到类别概率。

告别繁琐:PyTorch 零基础 SegFormer 语义分割实战指南

PyTorch 代码实现

接下来,我们将使用 PyTorch 实现 SegFormer 语义分割模型。首先,我们需要定义 Transformer 编码器:

告别繁琐:PyTorch 零基础 SegFormer 语义分割实战指南
import torch
import torch.nn as nn

class Attention(nn.Module):
    def __init__(self, dim, num_heads, dropout=0.):
        super().__init__()
        self.num_heads = num_heads
        head_dim = dim // num_heads
        self.scale = head_dim ** -0.5

        self.qkv = nn.Linear(dim, dim * 3, bias=False)
        self.proj = nn.Linear(dim, dim)
        self.proj_drop = nn.Dropout(dropout)

    def forward(self, x):
        B, N, C = x.shape
        qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)
        q, k, v = qkv[0], qkv[1], qkv[2]   # make torchscript happy (cannot use tensor as tuple)

        attn = (q @ k.transpose(-2, -1)) * self.scale
        attn = attn.softmax(dim=-1)

        x = (attn @ v).transpose(1, 2).reshape(B, N, C)
        x = self.proj(x)
        x = self.proj_drop(x)
        return x

class Mlp(nn.Module):
    def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.):
        super().__init__()
        out_features = out_features or in_features
        hidden_features = hidden_features or in_features
        self.fc1 = nn.Linear(in_features, hidden_features)
        self.act = act_layer()
        self.fc2 = nn.Linear(hidden_features, out_features)
        self.drop = nn.Dropout(drop)

    def forward(self, x):
        x = self.fc1(x)
        x = self.act(x)
        x = self.drop(x)
        x = self.fc2(x)
        x = self.drop(x)
        return x

class Block(nn.Module):
    def __init__(self, dim, num_heads, mlp_ratio=4., qkv_bias=False, qk_scale=None, drop=0., attn_drop=0.,
                 drop_path=0., act_layer=nn.GELU, norm_layer=nn.LayerNorm):
        super().__init__()
        self.norm1 = norm_layer(dim)
        self.attn = Attention(dim, num_heads=num_heads, dropout=attn_drop)
        self.drop_path = nn.Dropout(drop_path) if drop_path > 0. else nn.Identity()
        self.norm2 = norm_layer(dim)
        mlp_hidden_dim = int(dim * mlp_ratio)
        self.mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop)

    def forward(self, x):
        x = x + self.drop_path(self.attn(self.norm1(x)))
        x = x + self.drop_path(self.mlp(self.norm2(x)))
        return x

然后,我们需要定义 SegFormer 模型:

class SegFormer(nn.Module):
    def __init__(self, num_classes=19, embed_dims=[64, 128, 256, 512], num_heads=[1, 2, 4, 8], depths=[3, 4, 6, 3]):
        super().__init__()
        self.encoder1 = nn.Sequential(
            nn.Conv2d(3, embed_dims[0], kernel_size=7, stride=4, padding=3, bias=False),
            nn.BatchNorm2d(embed_dims[0]),
            nn.ReLU(inplace=True)
        )
        self.encoder2 = nn.Sequential(
            nn.Conv2d(embed_dims[0], embed_dims[1], kernel_size=3, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(embed_dims[1]),
            nn.ReLU(inplace=True)
        )
        self.encoder3 = nn.Sequential(
            nn.Conv2d(embed_dims[1], embed_dims[2], kernel_size=3, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(embed_dims[2]),
            nn.ReLU(inplace=True)
        )
        self.encoder4 = nn.Sequential(
            nn.Conv2d(embed_dims[2], embed_dims[3], kernel_size=3, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(embed_dims[3]),
            nn.ReLU(inplace=True)
        )

        self.blocks1 = nn.ModuleList([Block(dim=embed_dims[0], num_heads=num_heads[0]) for _ in range(depths[0])])
        self.blocks2 = nn.ModuleList([Block(dim=embed_dims[1], num_heads=num_heads[1]) for _ in range(depths[1])])
        self.blocks3 = nn.ModuleList([Block(dim=embed_dims[2], num_heads=num_heads[2]) for _ in range(depths[2])])
        self.blocks4 = nn.ModuleList([Block(dim=embed_dims[3], num_heads=num_heads[3]) for _ in range(depths[3])])

        self.decoder = nn.Sequential(
            nn.Conv2d(embed_dims[0] + embed_dims[1] + embed_dims[2] + embed_dims[3], embed_dims[2], kernel_size=1, bias=False),
            nn.BatchNorm2d(embed_dims[2]),
            nn.ReLU(inplace=True),
            nn.ConvTranspose2d(embed_dims[2], embed_dims[1], kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(embed_dims[1]),
            nn.ReLU(inplace=True),
            nn.ConvTranspose2d(embed_dims[1], embed_dims[0], kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(embed_dims[0]),
            nn.ReLU(inplace=True),
            nn.ConvTranspose2d(embed_dims[0], num_classes, kernel_size=4, stride=4, padding=0, bias=False)
        )

    def forward(self, x):
        x1 = self.encoder1(x)
        for block in self.blocks1:
            x1 = block(x1.flatten(2).transpose(1, 2)).transpose(1, 2).reshape(x1.shape)
        x2 = self.encoder2(x1)
        for block in self.blocks2:
            x2 = block(x2.flatten(2).transpose(1, 2)).transpose(1, 2).reshape(x2.shape)
        x3 = self.encoder3(x2)
        for block in self.blocks3:
            x3 = block(x3.flatten(2).transpose(1, 2)).transpose(1, 2).reshape(x3.shape)
        x4 = self.encoder4(x3)
        for block in self.blocks4:
            x4 = block(x4.flatten(2).transpose(1, 2)).transpose(1, 2).reshape(x4.shape)

        x = torch.cat([x1, x2, x3, x4], dim=1)
        x = self.decoder(x)

        return x

实战避坑经验

  • 数据集准备: 选择合适的数据集至关重要。对于语义分割任务,常用的数据集包括 Cityscapes、Pascal VOC 和 ADE20K。确保数据集的标注质量,并进行适当的预处理,例如图像大小调整和归一化。
  • 损失函数选择: 常用的损失函数包括交叉熵损失和 Dice 损失。交叉熵损失适用于类别平衡的数据集,而 Dice 损失更适用于类别不平衡的数据集。也可以尝试结合两种损失函数,以获得更好的性能。
  • 优化器选择: 常用的优化器包括 Adam 和 SGD。Adam 优化器具有自适应学习率的优点,但可能容易陷入局部最优解。SGD 优化器具有更好的泛化性能,但需要手动调整学习率。根据实际情况选择合适的优化器。
  • 显存优化: SegFormer 模型参数量较大,容易占用大量显存。可以尝试使用混合精度训练 (FP16) 或梯度累积等技术来降低显存占用。此外,还可以减小 batch size 或图像大小,以进一步降低显存占用。
  • 模型部署: 在模型部署时,需要考虑模型的推理速度和精度。可以使用 TensorRT 或 ONNX 等工具对模型进行优化,以提高推理速度。此外,还可以使用量化技术来减小模型大小,并降低计算成本。

通过本文,你应该能够使用 PyTorch 从零开始实现 SegFormer 语义分割模型,并掌握实战过程中可能遇到的各种问题及解决方案。希望这些经验能够帮助你更好地应用 SegFormer 模型解决实际问题。在实际项目中,图像分割任务经常需要部署到服务器上,可以使用 Nginx 进行反向代理,同时开启负载均衡,以应对高并发的请求。如果服务器是 Linux 系统,可以使用宝塔面板来简化服务器管理。同时,需要监控服务器的 CPU 使用率、内存占用率和并发连接数,以确保服务器的稳定运行。

告别繁琐:PyTorch 零基础 SegFormer 语义分割实战指南

转载请注明出处: 脱发程序员

本文的链接地址: http://m.acea2.store/blog/875202.SHTML

本文最后 发布于2026-04-10 02:36:48,已经过了17天没有更新,若内容或图片 失效,请留言反馈

()
您可能对以下文章感兴趣
评论
  • 月亮不营业 4 天前
    请问一下,在训练过程中,如果出现显存溢出的情况,除了降低 batch size 之外,还有什么其他的解决方案吗?
  • 西红柿鸡蛋面 5 天前
    写的太棒了,刚好最近在研究语义分割,这篇文章简直是雪中送炭!
  • 真香警告 4 天前
    请问一下,在训练过程中,如果出现显存溢出的情况,除了降低 batch size 之外,还有什么其他的解决方案吗?