FCN实现语义分割-Pytorch(一)

目录

0、说明
1、语义分割
2、FCN简介
3、FCN实现语义分割
3.1、网络模型(Model)
3.1.1、模型初始化
3.1.2、权重初始化
3.1.3、前向计算(Forward)
3.2、数据集(Dataset)
3.2.1、自定义Dataset
3.2.2、one-hot编码
3.2.3、数据预处理-数据变换(Transform)
3.2.4、数据加载器(DataLoader)
3.2.5、交叉验证(Cross Validation)
3.3、训练(Train)
3.3.1、学习准则(Criterion)
3.3.2、优化(Optimizer)
3.3.3、指标(Metrics)
3.3.3、混淆矩阵(Confusion Matrix)
3.4、验证(Validate)
3.4.1、模型与参数保存
3.4.2、模型验证
3.4.3、混合训练与验证
3.5、测试(Test)
3.5.1、ROC指标
3.5.2、PR指标
3.5.3、绘制测试结果
3.5.4、网格化标注
4、总结

0、说明

本文侧重于各种技术在语义分割代码中的实践,以及如何把各个部分联合起来,完成一个完整的人工智能任务。具体的技术的理论和原理,需要参考其它资料来了解。

1、语义分割

语义分割,是计算机视觉中的一项技术,用于识别图像中的对象,并为对象进行分类。比如下图中的图像,经过语义分割后被划分为不同的区域,以及每个区域的语义。
语义分割

语义分割工作主要包含以下内容:

  1. 语义识别:对图像中的每一个像素进行分类
  2. 目标定位:识别物体边界
  3. 语义分割:生成分割标签

语义分割目前被应用在地理信息系统,无人驾驶,医疗影像,机器人,人脸识别等诸多领域。

2、FCN 简介

全卷积神经网络(FCN),是一种特殊的卷积神经网络(CNN), 最早出现于2015年的一篇“Fully Convolutional Networks for Semantic Segmentation”论文, 和传统的CNN不同,FCN使用卷积层来代替CNN中的全连接(FC)层,使得整个网络结构中的分层全部为卷积层。FCN中使用的主要技术包含:卷积化(Convolutional),跨步卷积(Strided Convolution),跨层连接(Skip Layer),下采样(CNN中的Pooling)(Downsampling)和上采样(Upsampling)

一个典型的FCN网络

FCN 通过多次Downsampling操作,把数据大小缩放为原始图像大小的1/32,原始图像的特征信息在神经网络传输过程中丢失,导致预测结果的精度损失。针对此问题,FCN最终的打分策略,分为直接打分,联合使用上一次Downsampling结果打分,和联合使用上两次Dowsamping结果打分的策略,分别称为 FCN-32s, FCN-16s和FCN-8s。其中FCN-8s由于使用了前两次Downsampling的结果,所以最终预测的结果的精度通常高于FCN-16s和FCN-32s.

FCN

3、FCN实现语义分割

本文使用Pytorch框架和经典的FCN-8s模型来实现语义分割网络

3.1、网络模型(Model)

3.1.1、模型初始化

网络模型主要分为三部分:卷积层,取代全连接层的卷积层,上采样层

class FCN8s(nn.Module):
    def __init__(self, num_classes):
        super(FCN8s, self).__init__()
        self.num_classes = num_classes
        # 第一层卷积
        self.layer1 = nn.Sequential(
            nn.Conv2d(3, 48, (3, 3), padding=1),
            nn.ReLU(inplace=True),
nn.Conv2d(48, 48, (3, 3), padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, stride=2, ceil_mode=True)  # Downsampling 1/2
        )

        # 第二层卷积
        self.layer2 = nn.Sequential(
            nn.Conv2d(48, 128, (3, 3), padding=1),
            nn.ReLU(inplace=True),
nn.Conv2d(128, 128, (3, 3), padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, stride=2, ceil_mode=True)  # Downsampling 1/4
        )

        # 第三层卷积
        self.layer3 = nn.Sequential(
            nn.Conv2d(128, 192, (3, 3), padding=1),
            nn.ReLU(inplace=True),
nn.Conv2d(192, 192, (3, 3), padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, stride=2, ceil_mode=True)  # Downsampling 1/8
        )

        # 第四层卷积
        self.layer4 = nn.Sequential(
            nn.Conv2d(192, 256, (3, 3), padding=1),
            nn.ReLU(inplace=True),
nn.Conv2d(256, 256, (3, 3), padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, stride=2, ceil_mode=True)  # Downsampling 1/16
        )

        # 第五层卷积
        self.layer5 = nn.Sequential(
            nn.Conv2d(256, 512, (3, 3), padding=1),
            nn.ReLU(inplace=True),
nn.Conv2d(512, 512, (3, 3), padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, stride=2, ceil_mode=True)  # Downsampling 1/32
        )

        # 第六层使用卷积层取代FC层
        self.score_1 = nn.Conv2d(512, num_classes, (1, 1))
        self.score_2 = nn.Conv2d(256, num_classes, (1, 1))
        self.score_3 = nn.Conv2d(192, num_classes, (1, 1))

        # 第七层反卷积
        self.upsampling_2x = nn.ConvTranspose2d(num_classes, num_classes, (4, 4), (2, 2), (1, 1), bias=False)
        self.upsampling_4x = nn.ConvTranspose2d(num_classes, num_classes, (4, 4), (2, 2), (1, 1), bias=False)
        self.upsampling_8x = nn.ConvTranspose2d(num_classes, num_classes, (16, 16), (8, 8), (4, 4), bias=False)

        self._initialize_weights()
}
…
}

如果仔细观察上面的一些数值,会发现这个模型和常见的FCN模型存在一些差异。我们看下这些调整,对模型训练和预测结果的影响。

  • 通道数量的变化
    通常的FCN模型,每一个卷积层的通道数量,从96到 4096,示例模型的通道数量为48到256,整体通道数量上比常见的FCN模型要小很多。通道数量会影响到权重参数的数量,进而影响模型的训练速度。我们降低了通道数量,获得了更快的训练速度,同时在预测上,由于特征数量的下降,也降低了预测的精度。

  • 卷积层数量
    后续改进版的FCN,无论是ResNet还是VGG,在每一个Downsampling之前,会有多个卷积层,示例模型每一个下采样只配了二个卷积层。带来的变化可参考通道数量变化,同样是简化了模型,提升了训练速度,降低了预测的精度。

  • 首次卷积的填充
    在FCN网络模型中,第一个卷积层通常给定100单位的填充。
    卷积计算公式:

Wout = (Win – kernal + 2*padding)/stride + 1, 

其中Wout是卷积后输出的大小,Win是输入大小, kernal是kernal size,padding是填充大小,stride是步长

卷积操作中中为了不改变W和H的大小,通常会采用和卷积核大小匹配的Padding和Stride
在第五层卷积后,输出的大小Wout = Win / 32, 接下来的卷积层通常采用大卷积核(比如7)来进行打分,那么根据卷积输出大小的计算公式,输出结果为Wout = Win / 32 – 7 + 1 = (Win - 192)/32, 此时如果原始图像大小小于192,那么将导致打分函数无法计算。而首次Pading从1增加到100,打分之前的输出结果会变成(Win + 2*100 – 2) / 32, 打分的输出也变成了Wout = (Win + 6)/32, 此时原始图像大小不受限制,但引入了过多的噪声,导致预测的精度会有所下降。

为了简化,我们在打分卷积层使用了大小为1的卷积核,那么打分的输出就变成了Wout = Win /32 -1 + 1 = Win / 32, 此时没有原始图像大小受限制的问题,同时由于卷积核变小,也提升了训练速度。

  • 反卷积层
    反卷积计算公式是卷积计算公式的反函数,额外多出了一个output_padding 参数:
Wout = (Win - 1)* stride + kernal - 2 * padding + output_padding

反卷积函数,通过Strided Convolution,配合合理的参数,能够实现特定倍数的Upsampling操作。

3.1.2、权重初始化

权重初始化涉及到为卷积层以及反卷积层的神经元初始化默认值。如果网络模型是已知的模型,比如VGG或ResNET,那么通常使用预训练好的参数来初始化默认权重值,我们使用了自己修改的网络模型,所以这里对权重做下手工初始化

class FCN8s(nn.Module):
    …
    @staticmethod
    def bilinear_kernel(in_channels, out_channels, kernel_size):
        factor = (kernel_size + 1) // 2
        if kernel_size % 2 == 1:
            center = factor - 1
        else:
            center = factor - 0.5
        og = np.ogrid[:kernel_size, :kernel_size]
        filt = (1 - abs(og[0] - center) / factor) * \
               (1 - abs(og[1] - center) / factor)
        weight = np.zeros((in_channels, out_channels, kernel_size, kernel_size),
                          dtype=np.float64)
        weight[range(in_channels), range(out_channels), :, :] = filt
        return torch.from_numpy(weight).float()

    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                torch.nn.init.xavier_uniform_(m.weight)
                if m.bias is not None:
                    m.bias.data.zero_()
            if isinstance(m, nn.ConvTranspose2d):
                assert m.kernel_size[0] == m.kernel_size[1]
                initial_weight = self.bilinear_kernel(
                    m.in_channels, m.out_channels, m.kernel_size[0])
                m.weight.data.copy_(initial_weight)
      …
}

对于卷积层的权重初始化,使用了xavier初始化方法,反卷积层的权重初始化采用了双线性插值(Bilinear interpolation)算法。

权重初始化这里非常重要,如果处理不当,在反向传播(backward)时可能产生梯度消失或梯度爆炸问题,甚至无法计算梯度。

3.1.3、前向计算(Forward)

前向计算(forward)和反向传播(backward),是模型训练中非常重要的两个组成部分。对于给定的输入,前向计算通过神经网络模型,为输入进行打分。

class FCN8s(nn.Module):
    …
def forward(self, x: torch.Tensor) -> torch.Tensor:
        h = self.layer1(x)
        h = self.layer2(h)
        s1 = self.layer3(h) # 1/8
        s2 = self.layer4(s1) # 1/16
        s3 = self.layer5(s2) # 1/32

        s3 = self.score_1(s3)
        s3 = self.upsampling_2x(s3)
        s2 = self.score_2(s2)
        s2 += s3
        s2 = self.upsampling_4x(s2)
        s1 = self.score_3(s1)
        score = s1 + s2
        score = self.upsampling_8x(score)

        return score
    …
}

假设我们有一个大小为5的输入,Downsampling后的大小会变成3,Upsampling后,大小则变成了6。经过一轮Downsampling和Upsampling,输出的大小发生了变化。我们需要在Upsampling后,对Tensor进行必要的裁减,使得大小不能被32整除的图像,在一轮Downsampling和Upsampling后,能够恢复原来的大小。添加了大小裁减后的代码如下:

class FCN8s(nn.Module):
    …
def forward(self, x: torch.Tensor) -> torch.Tensor:
        h = self.layer1(x)
        h = self.layer2(h)
        s1 = self.layer3(h) # 1/8
        s2 = self.layer4(s1) # 1/16
        s3 = self.layer5(s2) # 1/32

        s3 = self.score_1(s3)
        s3 = self.upsampling_2x(s3)
        s2 = self.score_2(s2)
s2 = s2[:, :, :s3.size()[2], :s3.size()[3]]
        s2 += s3
        s2 = self.upsampling_4x(s2)
        s1 = self.score_3(s1)
s1 = s1[:, :, :s2.size()[2], :s2.size()[3]]
        score = s1 + s2
        score = self.upsampling_8x(score)
score = score [:, :, :x.size()[2], :x.size()[3]]

        return score
    …
}
  • 6
    点赞
  • 70
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现FCN语义分割的基本步骤如下: 1. 准备数据集:需要一个带有标注的语义分割数据集,可以使用PASCAL VOC2012、COCO等公开数据集,或者自己收集、标注数据。 2. 定义模型:FCN模型是基于CNN的,需要定义卷积层、反卷积层和池化层等。可以使用PyTorch提供的nn.Module类来定义模型。 3. 训练模型:使用数据集进行模型训练,可以使用PyTorch提供的DataLoader类来加载数据集,并使用PyTorch提供的优化器和损失函数进行训练。 4. 模型评估:训练完成后,需要对模型进行评估,可以使用IoU(Intersection over Union)等指标来评价模型的性能。 下面是一个简单的FCN模型实现示例: ```python import torch import torch.nn as nn import torch.nn.functional as F class FCN(nn.Module): def __init__(self, num_classes): super(FCN, self).__init__() # 卷积层1 self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=100) # 卷积层2 self.conv2 = nn.Conv2d(64, 64, kernel_size=3, stride=1, padding=1) # 卷积层3 self.conv3 = nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1) # 卷积层4 self.conv4 = nn.Conv2d(128, 256, kernel_size=3, stride=1, padding=1) # 卷积层5 self.conv5 = nn.Conv2d(256, 512, kernel_size=3, stride=1, padding=1) # 卷积层6 self.conv6 = nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1) # 卷积层7 self.conv7 = nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1) # 反卷积层1 self.deconv1 = nn.ConvTranspose2d(512, 512, kernel_size=3, stride=1, padding=1) # 反卷积层2 self.deconv2 = nn.ConvTranspose2d(512, 256, kernel_size=3, stride=1, padding=1) # 反卷积层3 self.deconv3 = nn.ConvTranspose2d(256, 128, kernel_size=3, stride=1, padding=1) # 反卷积层4 self.deconv4 = nn.ConvTranspose2d(128, 64, kernel_size=3, stride=1, padding=1) # 反卷积层5 self.deconv5 = nn.ConvTranspose2d(64, 32, kernel_size=3, stride=1, padding=1) # 反卷积层6 self.deconv6 = nn.ConvTranspose2d(32, num_classes, kernel_size=3, stride=1, padding=1) def forward(self, x): # 卷积层1 x = F.relu(self.conv1(x)) # 卷积层2 x = F.relu(self.conv2(x)) # 池化层1 x = F.max_pool2d(x, kernel_size=2, stride=2) # 卷积层3 x = F.relu(self.conv3(x)) # 卷积层4 x = F.relu(self.conv4(x)) # 池化层2 x = F.max_pool2d(x, kernel_size=2, stride=2) # 卷积层5 x = F.relu(self.conv5(x)) # 卷积层6 x = F.relu(self.conv6(x)) # 卷积层7 x = F.relu(self.conv7(x)) # 反卷积层1 x = F.relu(self.deconv1(x)) # 反卷积层2 x = F.relu(self.deconv2(x)) # 反卷积层3 x = F.relu(self.deconv3(x)) # 反卷积层4 x = F.relu(self.deconv4(x)) # 反卷积层5 x = F.relu(self.deconv5(x)) # 反卷积层6 x = self.deconv6(x) # 裁剪边缘 x = x[:, :, 19:19+x.size()[2], 19:19+x.size()[3]] return x ``` 训练和评估模型的代码可以参考PyTorch官方文档和示例。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值