ResNet网络结构的搭建(一)

目录

1.基本结构:BasicBlock和BottleNeck

2.构建ResNet网络

3.完整代码


代码部分参考b站视频

1.基本结构:BasicBlock和BottleNeck

        ResNet中最基本也是最重要的两个结构(图片来自:ResNet论文):BasicBlock(左)和BottleNeck(右),这两个结构分别用在轻量级的(ResNet18,ResNet34)及基础版(ResNet50,ResNet101,ResNet152)中。

        首先是BasicBlock,经过两个3×3卷积后,与输入端的特征图进行相加的操作,这就是残差网络的基本思想。由于要进行残差连接,所以可能会遇到特征图分辨率或者通道数不匹配的情况,所以要对残差边的特征图进行判断,如果需要调整特征图分辨率或者通道数,则进行downsample操作。

        然后是BottleNeck,经过1×1、3×3、1×1卷积后,与输入端的特征图进行相加的操作。

使用例子增加downsample说明:

        1°首先要了解downsample的功能:a.调整特征图的分辨率(观察ResNet的设计可知,只有layer2/3/4第一个block块才需要进行调整分辨率,进行下采样)b.调整特征图的通道数(观察ResNet的设计可知,在不同的layer之间需要进行通道数转换,分辨率)

        2°其次了解哪里需要进行下采样:a.首先是layer2/3/4层的第一个Block,通过stride=2调整输入特征图的宽高和通道数。b.其次是layer2/3/4层的第一个Block中的downsample结构,需要进行特征图分辨率和通道数的调整。

        3°最后layer1的第一个block中的downsample作用:是用于调整通道数的,为输入通道数的四倍。

import torch
from torch import nn as nn

class BasicBlock(nn.Module):
    expansion=1
    #in_channel表示输入通道数,out_channel表示第二层的通道数,不是指最后一层的通道数
    def __init__(self,in_channel,out_channel,stride=1,downsample=None):
        super(BasicBlock, self).__init__()
        #通过传入stride,方便对特征图的分辨率进行调整
        self.conv1=nn.Conv2d(in_channel,out_channel,kernel_size=3,stride=stride,padding=1,bias=False)
        self.bn1=nn.BatchNorm2d(out_channel)
        self.conv2=nn.Conv2d(out_channel,out_channel*self.expansion,kernel_size=3,stride=1,padding=1,bias=False)
        self.bn2=nn.BatchNorm2d(out_channel*self.expansion)
        self.relu=nn.ReLU(inplace=True)

        self.downsample=downsample

    def forward(self,x):
        residual=x
        x=self.conv1(x)
        x=self.bn1(x)
        x=self.conv2(x)
        x=self.bn2(x)

        #如果需要进行下采样,则将残差边加入下采样操作
        if self.downsample:
            residual=self.downsample(residual)

        x+=residual
        x=self.relu(x)
        return x

class BottleNeck(nn.Module):
    #第一个卷积的通道数和第三个卷积的通道数倍数是4
    expansion=4
    #in_channel表示输入通道数,out_channel表示第二个卷积的通道数
    def __init__(self,in_channel,out_channel,stride=1,downsample=None):
        super(BottleNeck, self).__init__()
        self.conv1=nn.Conv2d(in_channel,out_channel,kernel_size=1,stride=1,bias=False)
        self.bn1=nn.BatchNorm2d(out_channel)
        #通过传入stride,方便对特征图的分辨率进行调整
        self.conv2=nn.Conv2d(out_channel,out_channel,kernel_size=3,stride=stride,padding=1,bias=False)
        self.bn2=nn.BatchNorm2d(out_channel)
        self.conv3=nn.Conv2d(out_channel,out_channel*self.expansion,kernel_size=1,stride=1,bias=False)
        self.bn3=nn.BatchNorm2d(out_channel*self.expansion)

        self.relu=nn.ReLU(inplace=True)
        self.downsample=downsample

    def forward(self,x):
        residual=x
        x=self.conv1(x)
        x=self.bn1(x)
        x = self.conv2(x)
        x = self.bn2(x)
        x = self.conv3(x)
        x = self.bn3(x)

        if self.downsample:
            residual=self.downsample(residual)

        x+=residual
        x=self.relu(x)

        return x

2.构建ResNet网络

        首先输入一个224x224x3的图像到网络中,经过一个7x7的卷积后得到112x112x64的特征图,然后经过最大池化得到56x56x64的特征图。

        然后经过layer1/2/3/4层。

        然后经过平均池化层,刚好转换为1x1大小的图,最后经过全连接层得到(batch_size,num_class)大小的特征图。

class ResNet(nn.Module):
    """观察ResNet网络结构:
    1.需要定义的以上两个基本结构block,
    2.需要layer1/2/3/4层的数量(num_layers[2,2,2,2]),
    3.需要输入通道数init_channels=3,
    4.需要类别数num_class=10(手写)
    """
    def __init__(self,block_layer,num_layers,init_channels=3,num_class=10):
        super(ResNet, self).__init__()
        self.conv1=nn.Conv2d(init_channels,64,kernel_size=7,stride=2,padding=3,bias=False)
        self.bn1=nn.BatchNorm2d(64)
        self.max_pool=nn.MaxPool2d(kernel_size=3,stride=2)

        #输入四个layer层的通道数均为64
        self.in_channel=64

        """
        定义4个layer
        1.第一个layer不需要进行下采样,只需要给第一个block_layer的downsample进行调整通道数
        2.第二三四个layer的第一个block_layer的downsample需要下采样和调整通道数
        """
        self.layer1=self._make_layer(block_layer,64,stride=1,num_layer=num_layers[0])
        self.layer2 = self._make_layer(block_layer, 128, stride=2, num_layer=num_layers[1])
        self.layer3 = self._make_layer(block_layer, 256, stride=2, num_layer=num_layers[2])
        self.layer4 = self._make_layer(block_layer, 512, stride=2, num_layer=num_layers[3])

        #平均池化层,刚好转换为1x1大小的图
        self.avg_pool=nn.AvgPool2d(kernel_size=7,stride=1)
        #全连接层,输入通道数为512*expansion
        self.fc=nn.Linear(512*block_layer.expansion,num_class)

    def forward(self,x):
        x=self.conv1(x)
        x=self.bn1(x)
        x=self.max_pool(x)
        x=self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        x=self.avg_pool(x)

        #进行全连接层之前进行转换,[batch_size,num_class]
        x=x.view(x.size(0),-1)
        x=self.fc(x)

        return x

    """
    1.需要传入block构建网络,
    2.每个block的第二个卷积的通道数,
    3.一般不需要进行下采样,所以步长stride=1,
    4.每个layer的层数num_layer
    """
    def _make_layer(self,block_layer,out_channel,num_layer,stride=1):
        downsample = None
        """
        1.每一个layer的第一个block比较特殊,先构建第一个block
        2.如果stride!=1,即当stride=2需要进行下采样时候(位于两个layer交界处时),需要对残差边进行通道数与分辨率调整
        3.如果in_channel!=out_channel*block_layer.expansion,表示如果处于每个layer的第一层时候,需要进行通道数调整
        """
        if stride!=1 or self.in_channel!=out_channel*block_layer.expansion:
            downsample=nn.Sequential(
                #注意,此处传入的stride可以调节,即可以根据是否需要分辨率调整进行传值,卷积大小为1x1
                nn.Conv2d(self.in_channel,out_channel*block_layer.expansion,kernel_size=1,stride=stride,bias=False),
                nn.BatchNorm2d(out_channel*block_layer.expansion)
            )
        layer=[]
        #构建每一个layer的第一层,其是否需要进行进行下采样,可以由stride来调节,
        layer.append(block_layer(self.in_channel,out_channel,stride=stride,downsample=downsample))
        #layer的第二层输入通道数即为第一层的输出通道数,输出通道数也为第一层的输出通道数,注意此处的outchannel为第二个卷积的通道数
        self.in_channel=out_channel*block_layer.expansion
        for i in range(1,num_layer):
            layer.append(block_layer(self.in_channel,out_channel,stride=1,downsample=None))

        return nn.Sequential(*layer)

def resnet18():
    model=ResNet(BasicBlock,[2,2,2,2],num_class=10)
    return model

def resnet34():
    model=ResNet(BasicBlock,[3,4,6,3],num_class=10)
    return model

def resnet50():
    model=ResNet(BottleNeck,[3,4,6,3],num_class=10)
    return model

def resnet101():
    model=ResNet(BasicBlock,[3,4,23,3],num_class=10)
    return model

def resnet152():
    model=ResNet(BasicBlock,[3,8,36,3],num_class=10)
    return model

3.完整代码

        以下是搭建ResNet网络的完整代码。后面第二Part用作网络训练和验证。

#-*- coding = utf-8 -*- 
#@time:2022/6/7 16:32
#@Author:ice

import torch
from torch import nn as nn

class BasicBlock(nn.Module):
    expansion=1
    #in_channel表示输入通道数,out_channel表示第二层的通道数,不是指最后一层的通道数
    def __init__(self,in_channel,out_channel,stride=1,downsample=None):
        super(BasicBlock, self).__init__()
        #通过传入stride,方便对特征图的分辨率进行调整
        self.conv1=nn.Conv2d(in_channel,out_channel,kernel_size=3,stride=stride,padding=1,bias=False)
        self.bn1=nn.BatchNorm2d(out_channel)
        self.conv2=nn.Conv2d(out_channel,out_channel*self.expansion,kernel_size=3,stride=1,padding=1,bias=False)
        self.bn2=nn.BatchNorm2d(out_channel*self.expansion)
        self.relu=nn.ReLU(inplace=True)

        self.downsample=downsample

    def forward(self,x):
        residual=x
        x=self.conv1(x)
        x=self.bn1(x)
        x=self.conv2(x)
        x=self.bn2(x)

        #如果需要进行下采样,则将残差边加入下采样操作
        if self.downsample:
            residual=self.downsample(residual)

        x+=residual
        x=self.relu(x)
        return x

class BottleNeck(nn.Module):
    #第一个卷积的通道数和第三个卷积的通道数倍数是4
    expansion=4
    #in_channel表示输入通道数,out_channel表示第二个卷积的通道数
    def __init__(self,in_channel,out_channel,stride=1,downsample=None):
        super(BottleNeck, self).__init__()
        self.conv1=nn.Conv2d(in_channel,out_channel,kernel_size=1,stride=1,bias=False)
        self.bn1=nn.BatchNorm2d(out_channel)
        #通过传入stride,方便对特征图的分辨率进行调整
        self.conv2=nn.Conv2d(out_channel,out_channel,kernel_size=3,stride=stride,padding=1,bias=False)
        self.bn2=nn.BatchNorm2d(out_channel)
        self.conv3=nn.Conv2d(out_channel,out_channel*self.expansion,kernel_size=1,stride=1,bias=False)
        self.bn3=nn.BatchNorm2d(out_channel*self.expansion)

        self.relu=nn.ReLU(inplace=True)
        self.downsample=downsample

    def forward(self,x):
        residual=x
        x=self.conv1(x)
        x=self.bn1(x)
        x = self.conv2(x)
        x = self.bn2(x)
        x = self.conv3(x)
        x = self.bn3(x)

        if self.downsample:
            residual=self.downsample(residual)

        x+=residual
        x=self.relu(x)

        return x

class ResNet(nn.Module):
    """观察ResNet网络结构:
    1.需要定义的以上两个基本结构block,
    2.需要layer1/2/3/4层的数量(num_layers[2,2,2,2]),
    3.需要输入通道数init_channels=3,
    4.需要类别数num_class=10(手写)
    """
    def __init__(self,block_layer,num_layers,init_channels=3,num_class=10):
        super(ResNet, self).__init__()
        self.conv1=nn.Conv2d(init_channels,64,kernel_size=7,stride=2,padding=3,bias=False)
        self.bn1=nn.BatchNorm2d(64)
        self.max_pool=nn.MaxPool2d(kernel_size=3,stride=2)

        #输入四个layer层的通道数均为64
        self.in_channel=64

        """
        定义4个layer
        1.第一个layer不需要进行下采样,只需要给第一个block_layer的downsample进行调整通道数
        2.第二三四个layer的第一个block_layer的downsample需要下采样和调整通道数
        """
        self.layer1=self._make_layer(block_layer,64,stride=1,num_layer=num_layers[0])
        self.layer2 = self._make_layer(block_layer, 128, stride=2, num_layer=num_layers[1])
        self.layer3 = self._make_layer(block_layer, 256, stride=2, num_layer=num_layers[2])
        self.layer4 = self._make_layer(block_layer, 512, stride=2, num_layer=num_layers[3])

        #平均池化层,刚好转换为1x1大小的图
        self.avg_pool=nn.AvgPool2d(kernel_size=7,stride=1)
        #全连接层,输入通道数为512*expansion
        self.fc=nn.Linear(512*block_layer.expansion,num_class)

    def forward(self,x):
        x=self.conv1(x)
        x=self.bn1(x)
        x=self.max_pool(x)
        x=self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        x=self.avg_pool(x)

        #进行全连接层之前进行转换,[batch_size,num_class]
        x=x.view(x.size(0),-1)
        x=self.fc(x)

        return x

    """
    1.需要传入block构建网络,
    2.每个block的第二个卷积的通道数,
    3.一般不需要进行下采样,所以步长stride=1,
    4.每个layer的层数num_layer
    """
    def _make_layer(self,block_layer,out_channel,num_layer,stride=1):
        downsample = None
        """
        1.每一个layer的第一个block比较特殊,先构建第一个block
        2.如果stride!=1,即当stride=2需要进行下采样时候(位于两个layer交界处时),需要对残差边进行通道数与分辨率调整
        3.如果in_channel!=out_channel*block_layer.expansion,表示如果处于每个layer的第一层时候,需要进行通道数调整
        """
        if stride!=1 or self.in_channel!=out_channel*block_layer.expansion:
            downsample=nn.Sequential(
                #注意,此处传入的stride可以调节,即可以根据是否需要分辨率调整进行传值,卷积大小为1x1
                nn.Conv2d(self.in_channel,out_channel*block_layer.expansion,kernel_size=1,stride=stride,bias=False),
                nn.BatchNorm2d(out_channel*block_layer.expansion)
            )
        layer=[]
        #构建每一个layer的第一层,其是否需要进行进行下采样,可以由stride来调节,
        layer.append(block_layer(self.in_channel,out_channel,stride=stride,downsample=downsample))
        #layer的第二层输入通道数即为第一层的输出通道数,输出通道数也为第一层的输出通道数,注意此处的outchannel为第二个卷积的通道数
        self.in_channel=out_channel*block_layer.expansion
        for i in range(1,num_layer):
            layer.append(block_layer(self.in_channel,out_channel,stride=1,downsample=None))

        return nn.Sequential(*layer)

def resnet18():
    model=ResNet(BasicBlock,[2,2,2,2],num_class=10)
    return model

def resnet34():
    model=ResNet(BasicBlock,[3,4,6,3],num_class=10)
    return model

def resnet50():
    model=ResNet(BottleNeck,[3,4,6,3],num_class=10)
    return model

def resnet101():
    model=ResNet(BasicBlock,[3,4,23,3],num_class=10)
    return model

def resnet152():
    model=ResNet(BasicBlock,[3,8,36,3],num_class=10)
    return model

if __name__ == '__main__':
    a=torch.randn(5,3,224,224)
    m=resnet18()
    o=m(a)
    print(o.size())

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值