基于UNet结构的卷积神经网络模型

目录

 

基本框架

 代码实现

1.首先定义基本卷积块

2.下采样模块  Downsampling

3.上采样模块 Upsampling

4.UNet 主干网络

 4.1逐层解释,首先是下采样

 4.2上采样

 4.3最后定义连接 

5.测试


基本框架

  1. 收缩路径(左侧)

    • 使用重复的2次3x3卷积操作,每次后接ReLU激活函数和2x2的最大池化操作(步长为2),用于特征提取和下采样。
    • 每次下采样后,特征通道数量翻倍,以增加网络对特征的抽象能力。
  2. 扩展路径(右侧)

    • 包括特征图的上采样操作,紧接着是一个2x2的“上卷积”,用于减少特征通道的数量。
    • 将扩展路径的输出与收缩路径相对应的特征图进行连接,以恢复空间信息。
    • 每一步包括两次3x3的卷积操作,每次后跟ReLU激活函数,用于特征提取和重建。
  3. 最终层

    • 使用1x1的卷积层将64维特征向量映射到所需的类别数量,生成最终的分割地图。
  4. 总体层数

    • 整个网络由23层卷积层组成,结合了收缩和扩展路径的操作,用于实现高效的语义分割任务。(18个3*3+4个copy and crop+1个1*1)
  5. 输出分割地图的平铺

    • 为了实现输出分割地图的无缝平铺,需要选择适当的输入瓦片大小,确保所有的2x2最大池化操作应用于具有偶数大小的层。

这种结构设计旨在通过有效的特征提取、下采样、上采样和特征重建操作,实现准确和高效的图像语义分割

 代码实现

1.首先定义基本卷积块

class Conv(nn.Module):
    def __init__(self, C_in, C_out):  
        super(Conv, self).__init__() 
        self.layer = nn.Sequential(

 #nn.Sequential 是 PyTorch 中一个容器类,
 #用于顺序地排列和执行一系列的神经网络层, 多层堆叠

 #第一次卷积
            nn.Conv2d(C_in, C_out, 3, 1, 1),
          # 二维卷积层,输入通道 输出通道,卷积核大小为3*3,步长为1填充为1                         
            nn.BatchNorm2d(C_out), #批量归一化 Batch Normalization 
            # 防止过拟合
            nn.Dropout(0.3),
        #丢弃层,以0.3的概率随机将输入张量中的元素置为零,防止过拟合
            nn.LeakyReLU(),  #LeakyReLU 激活函数

   #第二次卷积
            nn.Conv2d(C_out, C_out, 3, 1, 1),
            nn.BatchNorm2d(C_out),
            # 防止过拟合
            nn.Dropout(0.4),
            nn.LeakyReLU(),
        )

    def forward(self, x):
        return self.layer(x)

批量归一化(Batch Normalization)是一种用于加速深度神经网络训练的技术。在深度神经网络中,随着层数的增加,数据在不同层之间的分布会发生变化,这可能导致梯度消失或爆炸,使得训练变得困难。批量归一化可以通过在每个小批量数据上进行归一化操作,即将输入数据进行均值为0,方差为1的归一化处理,来加速深度神经网络的训练。

过拟合(Overfitting)指的是在训练机器学习模型时,模型在训练数据集上表现很好,但在测试数据集上表现不佳的现象。这通常是因为模型过度学习了训练数据的细节和噪声,在训练集上表现较好,但却无法很好地泛化到新的数据

2.下采样模块  Downsampling

下采样 是指减少信号或数据的采样率,即减少采样点的数量,通常伴随着降低信号或图像的分辨率。主要目的是为了减少数据量或降低计算复杂度,常见的下采样方法包括:

  • 减少采样频率: 对于时间序列信号,降低采样频率即减少采样点的数量。
  • 平均池化(Average Pooling): 选择区域内数值的平均值来减少数据的维度和复杂度。
  • 最大池化(Max Pooling): ,选择区域内数值的最大值作为采样结果。
# 下采样模块 

class DownSampling(nn.Module):
    def __init__(self, C):
        super(DownSampling, self).__init__()
        self.Down = nn.Sequential(
            # 使用卷积进行2倍的下采样,通道数不变
            nn.Conv2d(C, C, 3, 2, 1),
            nn.LeakyReLU()  #LeakyReLU 激活函数

        )

    def forward(self, x):
        return self.Down(x)


# (下采样不改变通道数,通道数由卷积核决定)

3.上采样模块 Upsampling

 上采样是指增加信号或数据的采样率,通常用于增加数据的分辨率或维度。主要目的是为了恢复信号的细节或提高数据的表达能力,常见的上采样方法包括:

  • 插值方法: 如线性插值、双线性插值、三次样条插值等,通过已知点之间的插值来增加采样点,从而增加数据的分辨率。
  • 转置卷积(Transposed Convolution): 通过卷积操作来增加特征图的尺寸。
  • 反池化(Unpooling): 与池化操作相对应的一种操作,用于恢复被池化丢失的信息,从而实现上采样。
  • #上采样模块
    class UpSampling(nn.Module):
    
        def __init__(self, C):
            super(UpSampling, self).__init__()
            # 特征图大小扩大2倍,通道数减半
            self.Up = nn.Conv2d(C, C // 2, 1, 1)
            # 表示输入通道数为 C,输出通道数为 C // 2,卷积核大小为 1x1,步长为 1。
            # 这一层实际上是一个点卷积,用于将输入通道数减半
    
        def forward(self, x, r):
            # 使用邻近插值进行下采样
            up = F.interpolate(x, scale_factor=2, mode="nearest")
            x = self.Up(up)
            # 拼接,当前上采样的,和之前下采样过程中的
            return torch.cat((x, r), 1)
    
    # x 是输入的特征图。
    # r 是来自下采样过程中的特征图或其他数据。
    # F.interpolate(x, scale_factor=2, mode="nearest") 
    # 使用邻近插值方法对输入特征图 x 进行上采样,将特征图的大小扩大为原来的两倍。
    # x = self.Up(up) 
    # 将上采样后的特征图 up 输入到之前定义的卷积层 self.Up 中进行处理,减少通道数。
    # torch.cat((x, r), 1) 
    # 将上采样后的结果 x 和输入的 r 拼接在通道维度上(维度1),得到最终的上采样结果

    4.UNet 主干网络

# 主干网络
class UNet(nn.Module):

    def __init__(self):
        super(UNet, self).__init__()

        # 4次下采样
        self.C1 = Conv(3, 64)
        self.D1 = DownSampling(64)
# 每一次卷积操作 (Conv) 后紧跟一个下采样操作 (DownSampling) 
#是为了逐步减少特征图的空间大小,从而提取更高级别的特征表示
        self.C2 = Conv(64, 128)
        self.D2 = DownSampling(128
        self.C3 = Conv(128, 256)
        self.D3 = DownSampling(256)
        self.C4 = Conv(256, 512)
        self.D4 = DownSampling(512)

        self.C5 = Conv(512, 1024)

        # 4次上采样
        self.U1 = UpSampling(1024)
        self.C6 = Conv(1024, 512)
        self.U2 = UpSampling(512)
        self.C7 = Conv(512, 256)
        self.U3 = UpSampling(256)
        self.C8 = Conv(256, 128)
        self.U4 = UpSampling(128)
        self.C9 = Conv(128, 64)

        # 定义输出部分
        self.Th = torch.nn.Sigmoid() 
        # Sigmoid 函数将输出值压缩到 (0, 1) 范围内,产生最终的预测结果
        self.pred = torch.nn.Conv2d(64, 3, 3, 1, 1)  # 定义了一个卷积层 self.pred
       #  输入通道 输出通道 卷积核 步长 填充  负责将上采样之后的特征图映射为最终的预测输出

    def forward(self, x):
        # 下采样部分
        R1 = self.C1(x)
        R2 = self.C2(self.D1(R1))
        R3 = self.C3(self.D2(R2))
        R4 = self.C4(self.D3(R3))
        Y1 = self.C5(self.D4(R4))

        # 上采样部分
        # 上采样的时候需要拼接起来
        O1 = self.C6(self.U1(Y1, R4))
        O2 = self.C7(self.U2(O1, R3))
        O3 = self.C8(self.U3(O2, R2))
        O4 = self.C9(self.U4(O3, R1))

        # 输出预测,这里大小跟输入是一致的
        # 可以把下采样时的中间抠出来再进行拼接,这样修改后输出就会更小
        return self.Th(self.pred(O4))

                4.1逐层解释,首先是下采样

        # 4次下采样
        self.C1 = Conv(3, 64)
        self.D1 = DownSampling(64)

        self.C2 = Conv(64, 128)
        self.D2 = DownSampling(128)

        self.C3 = Conv(128, 256)
        self.D3 = DownSampling(256)

        self.C4 = Conv(256, 512)
        self.D4 = DownSampling(512)

        self.C5 = Conv(512, 1024)

 4.2上采样

  # 4次上采样
        self.U1 = UpSampling(1024)
        self.C6 = Conv(1024, 512)

        self.U2 = UpSampling(512)
        self.C7 = Conv(512, 256)

        self.U3 = UpSampling(256)
        self.C8 = Conv(256, 128)

        self.U4 = UpSampling(128)
        self.C9 = Conv(128, 64)

  # 定义输出部分
        self.Th = torch.nn.Sigmoid()  
    # Sigmoid 函数将输出值压缩到 (0, 1) 范围内,产生最终的预测结果
        self.pred = torch.nn.Conv2d(64, 3, 3, 1, 1)  # 定义了一个卷积层 self.pred
     #  输入通道 输出通道 卷积核 步长 填充  负责将上采样之后的特征图映射为最终的预测输出

 4.3最后定义连接 

可以看这个视频 讲怎么连接 【十分钟讲懂Unet | The U-Net (actually) explained in 10 minutes

   def forward(self, x):
        # 下采样部分
        R1 = self.C1(x)
        R2 = self.C2(self.D1(R1))
        R3 = self.C3(self.D2(R2))
        R4 = self.C4(self.D3(R3))
        Y1 = self.C5(self.D4(R4))

        # 上采样部分
        # 上采样的时候需要拼接起来
        O1 = self.C6(self.U1(Y1, R4))
        O2 = self.C7(self.U2(O1, R3))
        O3 = self.C8(self.U3(O2, R2))
        O4 = self.C9(self.U4(O3, R1))

        # 输出预测,这里大小跟输入是一致的
        # 可以把下采样时的中间抠出来再进行拼接,这样修改后输出就会更小
        return self.Th(self.pred(O4))

5.测试

if __name__ == '__main__':
    a = torch.randn(2, 3, 256, 256)
    net = UNet()
    print(net(a).shape)


#输出 torch.Size([2, 3, 256, 256]) 就代表正确了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值