U-Net模型

1.定义DoubleConv模块

在网络结构中,卷积基本上都是成对使用的,所以就定义了一个DoubleConv

网络结构图:

在这里插入图片描述

 

U-Net原理图

 1.1DoubleConv 模块代码实现

class DoubleConv(nn.Sequential):
	def __init__(self,in_channels,out_channels,mid_channels=None):
		if mid_channels is None:
			mid_channels=out_channels
			super(DoubleConv,self).__init__(
	              nn.Conv2d(in_channels,mid_channels,kernel_size=3,padding=1,bias=False),
	              nn.BatchNorm2d(mid_channels),
	              nn.ReLU(inplace=True),
	              nn.Conv2d(mid_channels,out_channels,kernel_size=3,padding=1,bias=False),
	              nn.BatchNorm2d(out_channels),
	              nn.ReLU(inplace = True)
			)

in_channels : 指的是输入特征层的channels
out_channels: 指的是经过DoubleConv层后输出特征层的channels
mid_channels: 指第一个卷积层输出的channels
通过父类的构造函数搭建DoubleConv,其中Conv2d的Kernel=3,padding=1,设置padding=1经过卷积后不会改变特征层的大小,这也是现在主流的实现方式
由于我们会使用到BN,因此将bias设置为Flase
 

1.2定义Down 模块

Down模块包括下采样(MaxPool) + 2个Conv2d,因为在网络左侧encoder部分基本上都是通过下采样(MaxPool) + 2个Conv2d搭建的。

Down 模块代码实现:

class Down(nn.Sequential):
	def __init__(self,in_channels,out_channels):
		super(Down,self).__init__(
			nn.MaxPool2d(2,stride=2),
			DoubleConv(in_channels,out_channels)
	)

1.3 定义UP 模块

UP模块包含:上采样(w,h翻倍)+concat拼接+2个Conv2d, 在UNet网络的右半部分decoder也就是解码器部分,基本上都是有各个上采样+concat拼接+2个Conv2d模块组成,因此这里定义了一个UP模块

UP 模块代码实现:

class Up(nn.Module):
	def __init__(self,in_channels,out_channels,bilinear=True):
		super(Up,self).__init__()
		if bilinear:
			self.up=nn.Upsample(scale_factor=2,mode='bilinear',align_corners=True)
			self.conv=DoubleConv(in_channels,out_channels,in_channels // 2)
		else:
			self.up = nn.ConvTranspose2d(in_channels,in_channels //2,kernel_size=2,stride=2)
			self.conv=DoubleConv(in_channels,out_channels)
	
	def forward(self,x1,x2):
		x1=self.up(x1)
		#[N,C,H,W]
		diff_y=x2.size()[2] - x1.size()[2]
		diff_x=x2.size()[3] - x1.size()[3]
		# padding_left,padding_right,padding_top,padding_bottom
		x1=F.pad(x1,[diff_x //2,diff_x - diff_x //2,
		             diff_y //2,diff_y - diff_y//2])
		x=torch.cat([x2,x1],dim=1)
		x=self.conv(x)
		return x

输入参数bilinear=True,表示默认情况下是会使用双线性差值
in_channels :指的是concat拼接后的channels,或者是up这个模块第一个卷积输入的channels
如果bilinear=False, 则使用论文中提到的转置卷积进行上采样,长宽翻倍,channels会减半,此时DoubleConv(in_channels,out_channels) 中mid_channels和out_channels是一样的。因为在原论文中这两个卷积的channels是一样的。参考:https://blog.csdn.net/weixin_38346042/article/details/125246551?spm=1001.2014.3001.5502
 

如果采用双线性插值进行上采样的话(经过双线性插值自身不会改变channels),上采样后面跟着的两个卷积的channels是不一样的,比如通过第一个卷积后channels会减半,通过第二个卷积后,channels又会减半。这样做的目的是为了经过双线性插值后得到的channels和我们要concat拼接的特征层的channels保持一致。
 

在这里插入图片描述

forward正向传播过程,x1指的是需要上采样的特征层,x2指的是要concat拼接的特征层。

在这里插入图片描述

流程是:首先我们需要对x1进行上采样x1=self.up(x1)得到采样之后的特征层,按理说可以直接通过concat拼接,将两个特征层在我们的channels维度进行拼接就可以了。然后再通过两个卷积层得到我们的特征输出。

def forward(self,x1,x2):
		x1=self.up(x1)
		#[N,C,H,W]
		diff_y=x2.size()[2] - x1.size()[2] # H
		diff_x=x2.size()[3] - x1.size()[3] # W
		# padding_left,padding_right,padding_top,padding_bottom
		x1=F.pad(x1,[diff_x //2,diff_x - diff_x //2,
		             diff_y //2,diff_y - diff_y//2])
		x=torch.cat([x2,x1],dim=1)
		x=self.conv(x)
		return x

但是这里作者有多做了一步,对我们上采样之后的x1进行了padding,目的是为了防止我们输入的图片如果不是16的整数倍的话,通过下采样得到的x1与我们要拼接的x2的高度和宽度是不一致的。

在我们unet的搭建过程下采样了4次也就是16倍,如果高和宽不是16的整数倍,那么在下采样的过程中,可能面临着向下取整的情况。比如在某个位置特征层大小是7x7的,经过下采样它的高和宽就变为3x3了,在通过上采样是6x6,很明显与7x7的特征层就无法拼接了。为了防止出现该问题,对x1进行了padding,padding之后就能保住x1和x2的高和宽是一致的了,这样就可以进行concat拼接了。
 

定义OutConv 模块

 OutConv对应的是最后一个1x1的卷积层,通过这个1x1的卷积层之后就得到我们最终的输出了。这个1x1的卷积它是没有BN和ReLU激活函数的。

class OutConv(nn.Sequential):
	def __init__(self,in_channels,num_classes):
		super(OutConv,self).__init__(
			nn.Conv2d(in_channels,num_classes,kernel_size=1)
  )

UNet 网络整体搭建过程

class UNet(nn.Module):
    def __init__(self,
                 in_channels: int = 1,
                 num_classes: int = 2,
                 bilinear: bool = True,
                 base_c: int = 64):
        super(UNet, self).__init__()
        self.in_channels = in_channels
        self.num_classes = num_classes
        self.bilinear = bilinear

        self.in_conv = DoubleConv(in_channels, base_c)
        self.down1 = Down(base_c, base_c * 2)
        self.down2 = Down(base_c * 2, base_c * 4)
        self.down3 = Down(base_c * 4, base_c * 8)
        factor = 2 if bilinear else 1
        self.down4 = Down(base_c * 8, base_c * 16 // factor)
        self.up1 = Up(base_c * 16, base_c * 8 // factor, bilinear)
        self.up2 = Up(base_c * 8, base_c * 4 // factor, bilinear)
        self.up3 = Up(base_c * 4, base_c * 2 // factor, bilinear)
        self.up4 = Up(base_c * 2, base_c, bilinear)
        self.out_conv = OutConv(base_c, num_classes)

    def forward(self, x: torch.Tensor) -> Dict[str, torch.Tensor]:
        x1 = self.in_conv(x)
        x2 = self.down1(x1)
        x3 = self.down2(x2)
        x4 = self.down3(x3)
        x5 = self.down4(x4)
        x = self.up1(x5, x4)
        x = self.up2(x, x3)
        x = self.up3(x, x2)
        x = self.up4(x, x1)
        logits = self.out_conv(x)

        return {"out": logits}

in_channels,输入的图片如果是彩色的,in_channels=3,如果使用的是黑白的`in_channels=1
bilinear 默认是True,根据测试无论采用双线性插值还是采用转置卷积计算,他们的结果其实是差不多的。那么你采用双线性差值其实会更高效点。
base_c就是网络中第一个卷积层输出的channels,在unet网络中各层的channels都是翻倍的,比如64,128,256,512,所以就定义了一个base channel,默认为64,也可以根据自己的想法去调整channels的大小。我这边训练的时候将base_c设置为32,发现得到的结果也没啥区别,但设置为32网络的参数会降低、训练速度会得到提升。
然后经过down1,down2,dow3,down4,4个下采样过程,注意到down1~down3通过两层卷积后channels都翻倍了,但down4经过两层卷积后channels没有翻倍。因为我们要保证经过双线性差值上采样得到的channels要与拼接的特征的channels保持一致,所以这里就没有进行翻倍。因此对输出的channels除了一个factor,如果采用双线性差值factor=2,如果采用转置卷积的话(本身会对w,h翻倍,channels减半,而双线性插值不会影响channels),因此factor=1。
 

 factor = 2 if bilinear else 1
 self.down4 = Down(base_c * 8, base_c * 16 // factor)

完整的UNet搭建代码:

from typing import Dict
import torch
import torch.nn as nn
import torch.nn.functional as F


class DoubleConv(nn.Sequential):
    def __init__(self, in_channels, out_channels, mid_channels=None):
        if mid_channels is None:
            mid_channels = out_channels
        super(DoubleConv, self).__init__(
            nn.Conv2d(in_channels, mid_channels, kernel_size=3, padding=1, bias=False),
            nn.BatchNorm2d(mid_channels),
            nn.ReLU(inplace=True),
            nn.Conv2d(mid_channels, out_channels, kernel_size=3, padding=1, bias=False),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(inplace=True)
        )


class Down(nn.Sequential):
    def __init__(self, in_channels, out_channels):
        super(Down, self).__init__(
            nn.MaxPool2d(2, stride=2),
            DoubleConv(in_channels, out_channels)
        )


class Up(nn.Module):
    def __init__(self, in_channels, out_channels, bilinear=True):
        super(Up, self).__init__()
        if bilinear:
            self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)
            self.conv = DoubleConv(in_channels, out_channels, in_channels // 2)
        else:
            self.up = nn.ConvTranspose2d(in_channels, in_channels // 2, kernel_size=2, stride=2)
            self.conv = DoubleConv(in_channels, out_channels)

    def forward(self, x1: torch.Tensor, x2: torch.Tensor) -> torch.Tensor:
        x1 = self.up(x1)
        # [N, C, H, W]
        diff_y = x2.size()[2] - x1.size()[2]
        diff_x = x2.size()[3] - x1.size()[3]

        # padding_left, padding_right, padding_top, padding_bottom
        x1 = F.pad(x1, [diff_x // 2, diff_x - diff_x // 2,
                        diff_y // 2, diff_y - diff_y // 2])

        x = torch.cat([x2, x1], dim=1)
        x = self.conv(x)
        return x


class OutConv(nn.Sequential):
    def __init__(self, in_channels, num_classes):
        super(OutConv, self).__init__(
            nn.Conv2d(in_channels, num_classes, kernel_size=1)
        )


class UNet(nn.Module):
    def __init__(self,
                 in_channels: int = 1,
                 num_classes: int = 2,
                 bilinear: bool = True,
                 base_c: int = 64):
        super(UNet, self).__init__()
        self.in_channels = in_channels
        self.num_classes = num_classes
        self.bilinear = bilinear

        self.in_conv = DoubleConv(in_channels, base_c)
        self.down1 = Down(base_c, base_c * 2)
        self.down2 = Down(base_c * 2, base_c * 4)
        self.down3 = Down(base_c * 4, base_c * 8)
        factor = 2 if bilinear else 1
        self.down4 = Down(base_c * 8, base_c * 16 // factor)
        self.up1 = Up(base_c * 16, base_c * 8 // factor, bilinear)
        self.up2 = Up(base_c * 8, base_c * 4 // factor, bilinear)
        self.up3 = Up(base_c * 4, base_c * 2 // factor, bilinear)
        self.up4 = Up(base_c * 2, base_c, bilinear)
        self.out_conv = OutConv(base_c, num_classes)

    def forward(self, x: torch.Tensor) -> Dict[str, torch.Tensor]:
        x1 = self.in_conv(x)
        x2 = self.down1(x1)
        x3 = self.down2(x2)
        x4 = self.down3(x3)
        x5 = self.down4(x4)
        x = self.up1(x5, x4)
        x = self.up2(x, x3)
        x = self.up3(x, x2)
        x = self.up4(x, x1)
        logits = self.out_conv(x)

        return {"out": logits}

  • 8
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个使用 U-Net 模型进行测试的示例代码,使用 PyTorch 框架: ```python import torch import torch.nn as nn from PIL import Image class UNet(nn.Module): def __init__(self, in_channels, out_channels): super(UNet, self).__init__() # Encoder self.conv1 = nn.Conv2d(in_channels, 64, kernel_size=3, padding=1) self.conv2 = nn.Conv2d(64, 128, kernel_size=3, padding=1) self.conv3 = nn.Conv2d(128, 256, kernel_size=3, padding=1) self.conv4 = nn.Conv2d(256, 512, kernel_size=3, padding=1) # Decoder self.upconv1 = nn.ConvTranspose2d(512, 256, kernel_size=2, stride=2) self.conv5 = nn.Conv2d(512, 256, kernel_size=3, padding=1) self.upconv2 = nn.ConvTranspose2d(256, 128, kernel_size=2, stride=2) self.conv6 = nn.Conv2d(256, 128, kernel_size=3, padding=1) self.upconv3 = nn.ConvTranspose2d(128, 64, kernel_size=2, stride=2) self.conv7 = nn.Conv2d(128, 64, kernel_size=3, padding=1) self.conv8 = nn.Conv2d(64, out_channels, kernel_size=1) self.relu = nn.ReLU(inplace=True) def forward(self, x): # Encoder x1 = self.relu(self.conv1(x)) x2 = self.relu(self.conv2(x1)) x3 = self.relu(self.conv3(x2)) x4 = self.relu(self.conv4(x3)) # Decoder x = self.relu(self.upconv1(x4)) x = torch.cat([x, x3], dim=1) x = self.relu(self.conv5(x)) x = self.relu(self.upconv2(x)) x = torch.cat([x, x2], dim=1) x = self.relu(self.conv6(x)) x = self.relu(self.upconv3(x)) x = torch.cat([x, x1], dim=1) x = self.relu(self.conv7(x)) x = self.conv8(x) return x # 测试模型 model = UNet(3, 1) # 输入通道数为3,输出通道数为1 model.load_state_dict(torch.load('model.pth')) # 加载保存的模型参数 model.eval() # 切换到评估模式 image = Image.open('test.jpg') # 读取测试图像 image = image.convert('RGB') # 转换为RGB格式 image = image.resize((256, 256)) # 缩放为256x256大小 image = torch.tensor([transforms.ToTensor()(image)]) # 转换为张量 with torch.no_grad(): output = model(image) # 模型输出 output = output.sigmoid() # 将输出映射到0~1之间 output = (output > 0.5).float() # 将输出二值化 output = output.squeeze().cpu().numpy() # 转换为NumPy数组并去除批次维度 output = (output * 255).astype('uint8') # 将输出转换为0~255之间的整数 Image.fromarray(output).save('output.jpg') # 保存输出图像 ``` 在这个示例代码中,我们首先定义了一个 U-Net 模型,并加载了保存的模型参数。然后,我们读取一张测试图像,并将其转换为 PyTorch 张量。我们使用 `with torch.no_grad()` 上下文管理器来关闭自动求导,以减少内存占用和计算时间。 我们将测试图像输入到模型中,得到模型的输出。由于模型输出的值域为 $(-\infty, \infty)$,我们使用 `sigmoid()` 函数将其映射到 $(0, 1)$,然后使用阈值为 $0.5$ 的二值化操作将其转换为二值图像。最后,我们将输出转换为 NumPy 数组,并将其保存为图像文件。 在实际应用中,我们可以使用测试代码来评估模型的性能,并调整模型的超参数以获得更好的结果。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值