《动手学深度学习》— ResNet实现

参考沐神

7.6. 残差网络(ResNet) — 动手学深度学习 2.0.0-beta0 documentationhttps://zh-v2.d2l.ai/chapter_convolutional-modern/resnet.html

ResNet结构

        这次就浅记一下,不深入讲解了。

         创新点是引入残差块,残差块有很好的性质。先看下面内容再展开残差块。

        简单说一下上图,对于输入X和标签y,我们希望学习一个好的函数F来尽可能正确预测给定输入X对应的y。可能我们最开始的函数为F1,效果狠辣鸡。我们一顿操作改成F2,效果也不好,又改了好几次,变成了F6,可能效果反而更差了,在上图左半边我们可以很清楚的看到,F1模型的规模很小,但其实它比复杂的F6离最优点f’更接近。上面说的这种调整策略就是非嵌套函数类的调整,显然通过这种非嵌套函数类调整策略并不合适。

        所以有大聪明提出了右边的嵌套函数类,它可以使我们在原来简单模型F1基础上不偏移的一点点向外扩张变复杂。在上图右边可以明显看出这种策略稳定性更高,可以很好的稳定的逼近最优点。

        实现嵌套函数类的方式就是使用残差块!来浅看一下残差块和原来的普通块的区别吧~

         ResNet残差块有两种形式,具体用哪种形式根据use_1x1conv设置。use_1x1conv的作用就是调整通道数,因为1×1的卷积核不改变输入张量的形状,所以我们多搞几个1×1的卷积核就可以控制输出通道。

代码实现

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project :深度学习入门
@File :ResNet.py
@Author :little_spice
@Date :2022/5/8 21:19
"""
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l

# 定义残差块
"""
    残差块⾥⾸先有2个有相同输出通道数的3 × 3卷积层。每个卷积
    层后接⼀个批量规范化层和ReLU激活函数。然后我们通过跨层数据通路,跳过这2个卷积运算,将输⼊直接
    加在最后的ReLU激活函数前。这样的设计要求2个卷积层的输出与输⼊形状⼀样,从⽽使它们可以相加。如
    果想改变通道数,就需要引⼊⼀个额外的1 × 1卷积层来将输⼊变换成需要的形状后再做相加运算。
"""
class Residual(nn.Module):
    # 构造函数
    def __init__(self,input_channels,num_channels,use_1x1conv=False,strides=1):
        # 调用父类也就是nn.Moudle的构造函数
        super().__init__()
        self.conv1 = nn.Conv2d(input_channels,num_channels,kernel_size=3,padding=1,stride=strides)
        self.conv2 = nn.Conv2d(num_channels, num_channels, kernel_size=3, padding=1)

        if use_1x1conv:
            self.conv3 = nn.Conv2d(input_channels,num_channels,kernel_size=1,stride=strides)
        else:
            self.conv3 = None
        self.bn1 = nn.BatchNorm2d(num_channels)
        self.bn2 = nn.BatchNorm2d(num_channels)

    # 前向传播过程
    def forward(self,X):
        # F.relu()一般在forward中用,nn.ReLU()在定义网络结构的时候用。前者是函数调用,后者是模块调用。
        Y = F.relu(self.bn1(self.conv1(X)))
        Y = self.bn2(self.conv2(Y))
        if self.conv3:
            X = self.conv3(X)
        Y += X
        return F.relu(Y)

# 不使用1x1卷积层
blk = Residual(3,3)
X = torch.rand(4,3,6,6)
Y = blk(X)
# print(Y)

# 使用1x1卷积层,同时可以在增加输出通道数的同时,减半输出的⾼和宽。
blk = Residual(3,6,use_1x1conv=True,strides=2)
# print(blk(X).shape)

# 定义ResNet模型
b1 = nn.Sequential(nn.Conv2d(1,64,kernel_size=7,stride=2,padding=3),
                   nn.BatchNorm2d(64),nn.ReLU(),
                   nn.MaxPool2d(kernel_size=3,stride=2,padding=1))

"""
    定义ResNet的模块
    ResNet使⽤4个由残差块组成的模块,每个模块使⽤若⼲个同样输出通道数的残差块。第⼀个模块的通道数同输⼊通道数⼀致。
    由于之前已经使⽤了步幅为2的最⼤汇聚层,所以⽆须减⼩⾼和宽。之后的每个模块在第⼀个残差块⾥将上⼀个模块的通道数翻倍,
    并将⾼和宽减半。
    对一个模块做了特殊处理。
"""
def resnet_block(input_channels,num_channels,num_residuals,first_block=False):
    blk = []
    for i in range(num_residuals):
        if i==0 and not first_block:
            blk.append(Residual(input_channels,num_channels,use_1x1conv=True,strides=2))
        else:
            blk.append(Residual(num_channels,num_channels))
    return blk

# 接着在ResNet加⼊所有残差块,这⾥每个模块使⽤2个残差块。
b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))
b3 = nn.Sequential(*resnet_block(64, 128, 2))
b4 = nn.Sequential(*resnet_block(128, 256, 2))
b5 = nn.Sequential(*resnet_block(256, 512, 2))

# 最后,与GoogLeNet⼀样,在ResNet中加⼊全局平均汇聚层,以及全连接层输出。
net = nn.Sequential(b1, b2, b3, b4, b5,nn.AdaptiveAvgPool2d((1,1)),nn.Flatten(), nn.Linear(512, 10))

"""
在训练ResNet之前,让我们观察⼀下ResNet中不同模块的输⼊形状是如何变化的。在之前所有架构中,分辨
率降低,通道数量增加,直到全局平均汇聚层聚集所有特征。
"""
X = torch.rand(size=(1,1,224,224))
for layer in net:
    X = layer(X)
    print(layer.__class__.__name__,'output shape:\t', X.shape)

# 在Fashion-MNIST数据集上训练模型
lr, num_epochs, batch_size = 0.05, 10, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())

 结果我就不展示了,感觉一跑我电脑巨卡,gtx1050确实顶不住了QAQ!

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
乘风的头像乘风管理团队
上一篇 2022年5月11日
下一篇 2022年5月11日

相关推荐