深度学习学习笔记(第30周)

news/2025/1/20 4:07:15/

一、摘要

本周报的目的在于汇报第30周的学习成果,本周主要聚焦于基于深度学习的图像分割领域的常用模型U-net。  

U-net是最常用、最简单的一种分割模型,在2015年被提出。UNet网络是一种用于图像分割的卷积神经网络,其特点是采用了U型网络结构,因此称为UNet。UNet算法的关键创新是在解码器中引入了跳跃连接(Skip Connections),即将编码器中的特征图与解码器中对应的特征图进行连接。这种跳跃连接可以帮助解码器更好地利用不同层次的特征信息,从而提高图像分割的准确性和细节保留能力。

Abstract

The purpose of this weekly report is to present the learning achievements in Week 30. This week, the focus was mainly on the U - net, a commonly used model in the field of deep - learning - based image segmentation.

U - net is one of the most commonly used and simplest segmentation models, which was proposed in 2015. The UNet network is a convolutional neural network for image segmentation. It is characterized by a U - shaped network structure, thus named UNet. The key innovation of the UNet algorithm is the introduction of skip connections in the decoder. That is, the feature maps in the encoder are connected to the corresponding feature maps in the decoder. These skip connections can help the decoder make better use of feature information at different levels, thereby improving the accuracy of image segmentation and the ability to preserve details.

二、网络结构

 UNet网络是一种用于图像分割的卷积神经网络,其特点是采用了U型网络结构,因此称为UNet。该网络具有编码器和解码器结构,两种结构的功能如下:

  • 编码器:逐步提取输入图像的特征并降低空间分辨率。
  • 解码器:通过上采样操作将特征图恢复到原始输入图像的尺寸,并逐步生成分割结果。

【CNN角度的编码器、解码器】以卷积神经网络为例,输入为一个猫,进行特征提取后输出图片类别。

  • 编码器:完成对输入图片中猫的特征提取。
  • 解码器:将特征提取的结果解码为分类结果。

在这里插入图片描述
【RNN角度的编码器、解码器】以循环神经网络LSTM为例,输入为一个文本,进行特征提取再输出

  • 编码器:将文本表示为向量并实现特征提取。
  • 解码器:将向量转化为输出。

在这里插入图片描述
  UNet算法的关键创新是在解码器中引入了跳跃连接(Skip Connections),即将编码器中的特征图与解码器中对应的特征图进行连接。这种跳跃连接可以帮助解码器更好地利用不同层次的特征信息,从而提高图像分割的准确性和细节保留能力。

  UNet的设计思想是通过编码器逐渐提取丰富的低级特征和高级特征,然后通过解码器逐渐恢复分辨率,并将低级特征和高级特征进行融合,以便获取准确且具有上下文信息的分割结果。这种U字形结构使得UNet能够同时利用全局(高分辨率时的特征图)和局部信息(低分辨率时的特征图),适用于图像分割任务。执行过程可粗略描述为:

输入层 -> 编码器(下采样模块 + 编码器模块) -> 解码器(上采样模块 + 解码器模块)-> 输出层。

即:

  • 编码器(Encoder)部分
    • 输入层:接受输入图像作为模型的输入
    • 下采样模块(Downsampling Block):由一系列卷积层(通常是卷积、批归一化和激活函数的组合)和池化层组成,用于逐渐减小特征图的尺寸和通道数。这样可以逐渐提取出更高级别的特征信息。
    • 编码器模块(Encoder Block):重复使用多个下采样模块,以便逐渐减小特征图的尺寸和通道数。每个编码器模块通常包含一个下采样模块和一个跳跃连接(Skip Connection),将上一级的特征图连接到下一级,以便在解码器中进行特征融合。
  • 解码器(Decoder)部分
    • 上采样模块(Upsampling Block):由一系列上采样操作(如反卷积或转置卷积)和卷积操作组成,用于逐渐增加特征图的尺寸和通道数。这样可以逐渐恢复分辨率并且保留更多的细节信息。
    • 解码器模块(Decoder Block):重复使用多个上采样模块,以便逐渐增加特征图的尺寸和通道数。每个解码器模块通常包含一个上采样模块、一个跳跃连接和一个融合操作(如拼接或加权求和),用于将来自编码器的特征图与当前解码器的特征图进行融合。
  • 输出层:最后一层是一个卷积层,用于生成最终的分割结果。通常,输出层的通道数等于任务中的类别数,并应用适当的激活函数(如sigmoid或softmax),以产生每个像素点属于各个类别的概率分布。

跳跃连接(skip connection):输入数据直接添加到网络某一层输出之上。这种设计使得信息可以更自由地流动,并且保留了原始输入数据中的细节和语义信息。 使信息更容易传播到后面的层次,避免了信息丢失。跳跃连接通常会通过求和操作或拼接操作来实现。
在这里插入图片描述
以图像分类任务为例,假设我们使用卷积神经网络进行特征提取,在每个卷积层后面都加入一个池化层来减小特征图尺寸。然而,池化操作可能导致信息损失。通过添加一个跳跃连接,将原始输入直接与最后一个池化层输出相加或拼接起来,可以保留原始图像中更多的细节和语义信息。

2.1编码器部分

在这里插入图片描述
  编码器部分由多个下采样模块(down sampling step)组成,每个下采样模块都由两个卷积层(卷积核大小为3x3,且与ReLU函数配合使用。由于图像尺寸变小,可见并未填充)和一个最大池化层(池化核大小2x2,步幅为2,将图像尺寸收缩一半)组成,并且每一次下采样操作后特征图的通道数均增加一倍。
  事实上,随着不断执行下采样模块(也成为收缩路径),特征图通道数随着卷积操作也不断增加,从而获取了图像的更多特征。并且在进入下一下采样模块前,进行 2x2 最大池化以获得最大像素值,虽然丢失一些特征,但保留最大像素值。通过这种方式,可将图像中目标的像素按类别进行分割。

2.2解码器部分

在这里插入图片描述

  • up-conv 2x2:上采样操作,通过反卷积操作实现。
  • copy and crop:复制和裁剪,将下采样模块输出的特征图进行复制和裁剪,方便和上采样生成的特征图进行拼接。

  下采样操作后,虽然使用最大池化运算丢失了部分细节信息,但模型已经得到了所有类的像素特征值。而在上采样中,模型将特征图通过具有相同级别的上采样过滤器来将特征图恢复到原始的分辨率大小,并通过跳跃连接的方式将下采样模块输出的每个特征图都添加到上采样模块的相应特征层中,从而保留特征。最后,通过完整的特征图即可定位每个类在图像中存在的位置,从而实现类别的定位。

  第一个上采样模块细节如下:
在这里插入图片描述
  最下面的下采样模块输出特征图大小为( 28 , 28 , 1024 ) (28,28,1024)(28,28,1024),经过反卷积操作(up-conv 2x2)得到大小为( 56 , 56 , 512 ) (56,56,512)(56,56,512)的特征图,即尺寸扩大一倍,通道数减半。之后,将左侧下采样模块输出的( 64 , 64 , 512 ) (64,64,512)(64,64,512)图像进行复制并中心裁剪(copy and crop)同样转化为( 56 , 56 , 512 ) (56,56,512)(56,56,512)大小,并与之拼接得到( 56 , 56 , 1024 ) (56,56,1024)(56,56,1024)大小的特征图(可见,此拼接仅是通道方向的拼接)。

三、完整代码

import torch
import torch.nn as nnclass Unet(nn.Module):def __init__(self):super(Unet, self).__init__()self.conv1_1 = nn.Conv2d(in_channels=1, out_channels=64, kernel_size=3, stride=1, padding=0)  # 由572*572*1变成了570*570*64self.relu1_1 = nn.ReLU(inplace=True)self.conv1_2 = nn.Conv2d(64, 64, kernel_size=3, stride=1, padding=0)  # 由570*570*64变成了568*568*64self.relu1_2 = nn.ReLU(inplace=True)self.maxpool_1 = nn.MaxPool2d(kernel_size=2, stride=2)  # 采用最大池化进行下采样,图片大小减半,通道数不变,由568*568*64变成284*284*64self.conv2_1 = nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=0)  # 284*284*64->282*282*128self.relu2_1 = nn.ReLU(inplace=True)self.conv2_2 = nn.Conv2d(128, 128, kernel_size=3, stride=1, padding=0)  # 282*282*128->280*280*128self.relu2_2 = nn.ReLU(inplace=True)self.maxpool_2 = nn.MaxPool2d(kernel_size=2, stride=2)  # 采用最大池化进行下采样  280*280*128->140*140*128self.conv3_1 = nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, stride=1, padding=0)  # 140*140*128->138*138*256self.relu3_1 = nn.ReLU(inplace=True)self.conv3_2 = nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=0)  # 138*138*256->136*136*256self.relu3_2 = nn.ReLU(inplace=True)self.maxpool_3 = nn.MaxPool2d(kernel_size=2, stride=2)  # 采用最大池化进行下采样  136*136*256->68*68*256self.conv4_1 = nn.Conv2d(in_channels=256, out_channels=512, kernel_size=3, stride=1, padding=0)  # 68*68*256->66*66*512self.relu4_1 = nn.ReLU(inplace=True)self.conv4_2 = nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=0)  # 66*66*512->64*64*512self.relu4_2 = nn.ReLU(inplace=True)self.maxpool_4 = nn.MaxPool2d(kernel_size=2, stride=2)  # 采用最大池化进行下采样  64*64*512->32*32*512self.conv5_1 = nn.Conv2d(in_channels=512, out_channels=1024, kernel_size=3, stride=1, padding=0)  # 32*32*512->30*30*1024self.relu5_1 = nn.ReLU(inplace=True)self.conv5_2 = nn.Conv2d(1024, 1024, kernel_size=3, stride=1, padding=0)  # 30*30*1024->28*28*1024self.relu5_2 = nn.ReLU(inplace=True)# 接下来实现上采样中的up-conv2*2self.up_conv_1 = nn.ConvTranspose2d(in_channels=1024, out_channels=512, kernel_size=2, stride=2, padding=0) # 28*28*1024->56*56*512self.conv6_1 = nn.Conv2d(in_channels=1024, out_channels=512, kernel_size=3, stride=1, padding=0)  # 56*56*1024->54*54*512self.relu6_1 = nn.ReLU(inplace=True)self.conv6_2 = nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=0)  # 54*54*512->52*52*512self.relu6_2 = nn.ReLU(inplace=True)self.up_conv_2 = nn.ConvTranspose2d(in_channels=512, out_channels=256, kernel_size=2, stride=2, padding=0) # 52*52*512->104*104*256self.conv7_1 = nn.Conv2d(in_channels=512, out_channels=256, kernel_size=3, stride=1, padding=0)  # 104*104*512->102*102*256self.relu7_1 = nn.ReLU(inplace=True)self.conv7_2 = nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=0)  # 102*102*256->100*100*256self.relu7_2 = nn.ReLU(inplace=True)self.up_conv_3 = nn.ConvTranspose2d(in_channels=256, out_channels=128, kernel_size=2, stride=2, padding=0) # 100*100*256->200*200*128self.conv8_1 = nn.Conv2d(in_channels=256, out_channels=128, kernel_size=3, stride=1, padding=0)  # 200*200*256->198*198*128self.relu8_1 = nn.ReLU(inplace=True)self.conv8_2 = nn.Conv2d(128, 128, kernel_size=3, stride=1, padding=0)  # 198*198*128->196*196*128self.relu8_2 = nn.ReLU(inplace=True)self.up_conv_4 = nn.ConvTranspose2d(in_channels=128, out_channels=64, kernel_size=2, stride=2, padding=0) # 196*196*128->392*392*64self.conv9_1 = nn.Conv2d(in_channels=128, out_channels=64, kernel_size=3, stride=1, padding=0)  # 392*392*128->390*390*64self.relu9_1 = nn.ReLU(inplace=True)self.conv9_2 = nn.Conv2d(64, 64, kernel_size=3, stride=1, padding=0)  # 390*390*64->388*388*64self.relu9_2 = nn.ReLU(inplace=True)# 最后的conv1*1self.conv_10 = nn.Conv2d(in_channels=64, out_channels=2, kernel_size=1, stride=1, padding=0)# 中心裁剪,def crop_tensor(self, tensor, target_tensor):target_size = target_tensor.size()[2]tensor_size = tensor.size()[2]delta = tensor_size - target_sizedelta = delta // 2# 如果原始张量的尺寸为10,而delta为2,那么"delta:tensor_size - delta"将截取从索引2到索引8的部分,长度为6,以使得截取后的张量尺寸变为6。return tensor[:, :, delta:tensor_size - delta, delta:tensor_size - delta]def forward(self, x):x1 = self.conv1_1(x)x1 = self.relu1_1(x1)x2 = self.conv1_2(x1)x2 = self.relu1_2(x2)  # 这个后续需要使用down1 = self.maxpool_1(x2)x3 = self.conv2_1(down1)x3 = self.relu2_1(x3)x4 = self.conv2_2(x3)x4 = self.relu2_2(x4)  # 这个后续需要使用down2 = self.maxpool_2(x4)x5 = self.conv3_1(down2)x5 = self.relu3_1(x5)x6 = self.conv3_2(x5)x6 = self.relu3_2(x6)  # 这个后续需要使用down3 = self.maxpool_3(x6)x7 = self.conv4_1(down3)x7 = self.relu4_1(x7)x8 = self.conv4_2(x7)x8 = self.relu4_2(x8)  # 这个后续需要使用down4 = self.maxpool_4(x8)x9 = self.conv5_1(down4)x9 = self.relu5_1(x9)x10 = self.conv5_2(x9)x10 = self.relu5_2(x10)# 第一次上采样,需要"Copy and crop"(复制并裁剪)up1 = self.up_conv_1(x10)  # 得到56*56*512# 需要对x8进行裁剪,从中心往外裁剪crop1 = self.crop_tensor(x8, up1)up_1 = torch.cat([crop1, up1], dim=1)y1 = self.conv6_1(up_1)y1 = self.relu6_1(y1)y2 = self.conv6_2(y1)y2 = self.relu6_2(y2)# 第二次上采样,需要"Copy and crop"(复制并裁剪)up2 = self.up_conv_2(y2)# 需要对x6进行裁剪,从中心往外裁剪crop2 = self.crop_tensor(x6, up2)up_2 = torch.cat([crop2, up2], dim=1)y3 = self.conv7_1(up_2)y3 = self.relu7_1(y3)y4 = self.conv7_2(y3)y4 = self.relu7_2(y4)# 第三次上采样,需要"Copy and crop"(复制并裁剪)up3 = self.up_conv_3(y4)# 需要对x4进行裁剪,从中心往外裁剪crop3 = self.crop_tensor(x4, up3)up_3 = torch.cat([crop3, up3], dim=1)y5 = self.conv8_1(up_3)y5 = self.relu8_1(y5)y6 = self.conv8_2(y5)y6 = self.relu8_2(y6)# 第四次上采样,需要"Copy and crop"(复制并裁剪)up4 = self.up_conv_4(y6)# 需要对x2进行裁剪,从中心往外裁剪crop4 = self.crop_tensor(x2, up4)up_4 = torch.cat([crop4, up4], dim=1)y7 = self.conv9_1(up_4)y7 = self.relu9_1(y7)y8 = self.conv9_2(y7)y8 = self.relu9_2(y8)# 最后的conv1*1out = self.conv_10(y8)return out
if __name__ == '__main__':input_data = torch.randn([1, 1, 572, 572])unet = Unet()output = unet(input_data)print(output.shape)# torch.Size([1, 2, 388, 388])


http://www.ppmy.cn/news/1564577.html

相关文章

解决Spring Boot中Druid连接池“discard long time none received connection“警告

在使用Spring Boot结合Druid连接池时,开发者可能会遇到"discard long time none received connection"的警告信息。虽然这通常不会影响应用程序的正常运行,但这些警告信息可能会让人感到困扰。本文将探讨这个问题的原因,并提供几种…

vue 前端优化性能优化方法

1.列表使用唯一 key v-for"item in activeList" :key"item.id"原因是不使用 key 或者列表的 index 作为 key 的时候,每个元素对应的位置关系都是 index,直接导致我们插入的元素到后面的全部元素,对应的位置关系都发生了变…

如何写好ChatGPT的提示词:从入门到专家

在人工智能的飞速发展中,ChatGPT 等语言模型已经成为人们解决问题、生成内容和提升效率的强大工具。然而,要充分发挥这些模型的潜力,关键在于如何设计有效的提示词(Prompt)。本文将从基础概念到高级技巧,全…

Kotlin函数类型探索:T.()->Unit的扩展函数、无参函数()->Unit与类型参数函数(T)->Unit

在Kotlin编程语言的丰富特性中,函数类型扮演着至关重要的角色。它们不仅定义了代码的行为,还通过灵活的类型系统促进了代码的重用和模块化。本文将深入探讨Kotlin中的三种核心函数类型:T.()->Unit的扩展函数、无参函数()->Unit以及类型…

php-2025面试题准备

一、TCP、UDP、Unix Socket、HTTP、 WebSoct 1、概念 TCP:面向连接可靠的传输协议,会先建立连接,有确认应答、重传机制主要目的是保证数据的顺序性和完整性,常用于文件传输 FTP、电子邮件、网络浏览等大部分应用。 UDP&#xf…

【鸿蒙】0x02-LiteOS-M基于Qemu RISC-V运行

OpenHarmony LiteOS-M基于Qemu RISC-V运行 系列文章目录更新日志OpenHarmony技术架构OH技术架构OH支持系统类型轻量系统(mini system)小型系统(small system)标准系统(standard system) 简介环境准备安装QE…

深入浅出JSON:数据交换的轻量级解决方案

在现代Web开发中,数据交换是前后端通信的核心。JSON(JavaScript Object Notation)作为一种轻量级的数据交换格式,因其简洁、易读、易解析的特点,成为了前后端数据交互的首选格式。本文将详细介绍JSON的定义、数据格式、…

七十五:握手的优化:Session缓存、Ticket票据及TLS 1.3的0-RTT

引言 在现代互联网环境中,安全性和性能是设计网络协议时至关重要的两个方面。传输层安全性(TLS)协议是实现安全传输的关键机制。然而,传统的TLS握手过程虽然安全,但是存在潜在的延迟问题。为了优化握手的效率&#xf…