pytorh学习笔记——cifar10(五)ResNet网络结构

一、理论基础

resnet网络结构的理论基础看这里,这篇博文讲得简洁而清晰:深度学习卷积神经网络CNN之ResNet模型网络详解说明(超详细理论篇)-****博客

ResNet网络是参考了VGG19网络,并在此基础上进行修改,通过短路机制加入了残差单元。

残差块的基本结构:

图1
图1

上图中,网络分为左右两支,左网络是主干分支,右网络是残差网络

 ResNet的结构与VGG网络的比较:

二、实现过程

新建resnet.py:

1、定义一个基本的块,类似于图1的结构,包含主干网络和残差网络:

class BasicBlock(nn.Module):   # 定义一个基本块,由主干和残差分支组成
    def __init__(self, in_channels, out_channels, stride=1):
        super(BasicBlock, self).__init__()
        self.layer = nn.Sequential(  # 左网络(也就是主干分支)
            nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1),  # 这三行代码来自标准的VGGNet网络
            # 第一次卷积有可能进行下采样,所以stride=stride
            nn.BatchNorm2d(out_channels),
            nn.ReLU(),
            nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1),   # 第二次卷积不进行下采样,这样每个基本快就只进行一次下采样
            nn.BatchNorm2d(out_channels)
        )
        if in_channels != out_channels:  # 如果输入通道数和输出通道数不同,说明输入和输出的维度不同,需要添加一个下采样层
            self.shortcut = nn.Sequential(  # 右网络(也就是残差分支)
                nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1),
                nn.BatchNorm2d(out_channels),
            )
        else:
            self.shortcut = nn.Sequential()  # 如果输入和输出的维度相同,就不需要添加下采样层

    def forward(self, x):   # 定义前向传播
        out = self.layer(x)   # 执行左网络
        out += self.shortcut(x)   # 执行右网络
        out = F.relu(out)   # 激活函数
        return out

 2、定义ResNet网络结构

class ResNet(nn.Module):  # 定义ResNet网络
    def make_layer(self, block, out_channels, stride, num_blocks):  # 生成网络层
        # 参数:
        # block:用于构建网络层的基本模块
        # out_channels:输出通道数,表示该层的特征图数量。
        # stride:步长,用于下采样操作。
        # num_blocks:该层中要生成的block数量。
        layers = []  # 定义一个列表,用来保存网络层
        for i in range(num_blocks):  # 生成num_blocks个block
            if i == 0:  # 如果是第一个block,需要下采样
                in_stride = stride
            else:
                in_stride = 1  # 其他block不需要下采样

            layers.append(block(self.in_channels, out_channels, in_stride))  # 将block添加到列表中
            self.in_channels = out_channels  # 更新输入通道数
        return nn.Sequential(*layers)  # 将列表中的网络层串联组合成一个网络层

    def __init__(self, block, num_blocks, num_classes=10):
        super(ResNet, self).__init__()
        self.in_channels = 32  # 定义输入通道数
        self.conv1 = nn.Sequential(  # 定义第1个卷积层,这个层不用残差,就是标准的VGGNet网络
            nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(32),
            nn.ReLU()
        )
        self.layer_1 = self.make_layer(BasicBlock, 64, 2, 2)   # 生成第1个网络层
        self.layer_2 = self.make_layer(BasicBlock, 128, 2, 2)  # 生成第2个网络层
        self.layer_3 = self.make_layer(BasicBlock, 256, 2, 2)  # 生成第2个网络层
        self.layer_4 = self.make_layer(BasicBlock, 512, 2, 2)  # 生成第2个网络层

        self.fc = nn.Linear(512, 10)  # 全连接层,输入为512,输出为10

    def forward(self, x):
        out = self.conv1(x)  # 执行第1个卷积层
        out = self.layer_1(out)  # 执行第1个网络层
        out = self.layer_2(out)  # 执行第2个网络层
        out = self.layer_3(out)  # 执行第3个网络层
        out = self.layer_4(out)  # 执行第4个网络层
        out = F.avg_pool2d(out, 2)  # 最大池化,池化核大小为2
        out = out.view(out.size(0), -1)  # 将输出展平
        out = self.fc(out)  # 全连接层
        return out

3、resnet.py脚本的完整内容:

import torch
import torch.nn as nn
import torch.nn.functional as F


class BasicBlock(nn.Module):  # 定义一个基本模块,由主干和残差分支组成
    def __init__(self, in_channels, out_channels, stride=1):
        super(BasicBlock, self).__init__()
        self.layer = nn.Sequential(  # 左网络(也就是主干分支)
            nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1),  # 这三行代码来自标准的VGGNet网络
            # 第一次卷积有可能进行下采样,所以stride=stride
            nn.BatchNorm2d(out_channels),
            nn.ReLU(),
            nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1),  # 第二次卷积不进行下采样,这样每个基本快就只进行一次下采样
            nn.BatchNorm2d(out_channels)
        )
        if in_channels != out_channels:  # 如果输入通道数和输出通道数不同,说明输入和输出的维度不同,需要添加一个下采样层
            self.shortcut = nn.Sequential(  # 右网络(也就是残差分支)
                nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1),
                # 为了保证左右网络的维度相同,使用了同一个stride
                nn.BatchNorm2d(out_channels),
            )
        else:
            self.shortcut = nn.Sequential()  # 如果输入和输出的维度相同,就不需要添加下采样层

    def forward(self, x):  # 定义前向传播
        out = self.layer(x)  # 执行左网络
        out += self.shortcut(x)  # 加上右网络
        out = F.relu(out)  # 激活函数
        return out


class ResNet(nn.Module):  # 定义ResNet网络
    def make_layer(self, block, out_channels, stride, num_blocks):  # 生成网络层
        # 参数:
        # block:用于构建网络层的基本模块
        # out_channels:输出通道数,表示该层的特征图数量。
        # stride:步长,用于下采样操作。
        # num_blocks:该层中要生成的block数量。
        layers = []  # 定义一个列表,用来保存网络层
        for i in range(num_blocks):  # 生成num_blocks个block
            if i == 0:  # 如果是第一个block,需要下采样
                in_stride = stride
            else:
                in_stride = 1  # 其他block不需要下采样

            layers.append(block(self.in_channels, out_channels, in_stride))  # 将block添加到列表中
            self.in_channels = out_channels  # 更新输入通道数
        return nn.Sequential(*layers)  # 将列表中的网络层串联组合成一个网络层

    def __init__(self, block=BasicBlock):
        super(ResNet, self).__init__()
        self.in_channels = 32  # 定义输入通道数
        self.conv1 = nn.Sequential(  # 定义第1个卷积层,这个层不用残差,就是标准的VGGNet网络
            nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(32),
            nn.ReLU()
        )
        self.layer_1 = self.make_layer(block, 64, 2, 2)   # 生成第1个网络层
        self.layer_2 = self.make_layer(block, 128, 2, 2)  # 生成第2个网络层
        self.layer_3 = self.make_layer(block, 256, 2, 2)  # 生成第2个网络层
        self.layer_4 = self.make_layer(block, 512, 2, 2)  # 生成第2个网络层

        self.fc = nn.Linear(512, 10)  # 全连接层,输入为512,输出为10

    def forward(self, x):
        out = self.conv1(x)  # 执行第1个卷积层
        out = self.layer_1(out)  # 执行第1个网络层
        out = self.layer_2(out)  # 执行第2个网络层
        out = self.layer_3(out)  # 执行第3个网络层
        out = self.layer_4(out)  # 执行第4个网络层
        out = F.avg_pool2d(out, 2)  # 最大池化,池化核大小为2
        out = out.view(out.size(0), -1)  # 将输出展平
        out = self.fc(out)  # 全连接层

        return out


def resnet():
    return ResNet(BasicBlock)  # 生成ResNet网络

4、训练 

将之前的train.py脚本中的
net = VGGNet().to(device),改为:
​​​​​​​net = resnet().to(device),即可运行开始训练:

上一篇:项目实战-图书管理系统之个人中心


下一篇:manim边学边做--极坐标平面