IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 人工智能 -> ResNet学习笔记(一) -> 正文阅读

[人工智能]ResNet学习笔记(一)

ResNet

由于随着卷积层数的增加,会导致梯度消失/爆炸的问题,虽然这两种方法可以通过归一化等方法解决,但是还存在退化问题,所以提出了ResNet。
(关于梯度消失和爆炸,退化可参照 链接: link.)

模型的搭建

对于整个模型的搭建主要是Block类,接下来以BasicBlock为例来对代码进行解析。(代码来源:太阳花的小绿豆的CSDN 链接: link.))
** Block**的含义:
例如:ResNet34由以下几部分组成,conv1,conv2_x,conv3_x,conv4_x,conv5_x,以及最后的全连接层, BasicBlock指的是 conv2_x,conv3_x,conv4_x,conv5_x里的这些具有重复结构的块,如conv3_x层中具有四个([33,128]+[33,128])块, BasicBlock就指的是这些块,在ResNet中,通过重复调用BasicBlock方法就能方便的构建出这些块。简单来说,Block是为了方便调用代码,就是为了省力。
我们可以了解到ResNet中对于50层以下的构建块采用的是BasicBlock,而大于50的深层则采用的是Bottleneck

class BasicBlock(nn.Module):
    expension = 1

    def __init__(self, in_channel, out_channel, stride = 1, downsample = None, **kwargs):
        super(BasicBlock, self).__init__()

        self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel,
                               kernel_size=3, stride=stride, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(out_channel)
        self.relu = nn.ReLU()

        self.conv2 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel,
                               kernel_size=3, stride=stride, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(out_channel)
        self.downsample = downsample

    def forward(self, x):
        identity = x
        if self.downsample is not None:
            identity = self.downsample(x)

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)

        out += identity
        out = self.relu(out)

        return out

Resnet网络结构
50层以下的残差模块

**步骤1:**令BasicBlock继承nn.Module(对于这一模块,可查看Pytorch文档中的nn.Module或 参考
LoveMIss-Y的CSDN链接link.,简单来说,就是利用库中定义的一个模型来改写)
**步骤2:**重写__init__和forward
(1)一般把网络中具有可学习参数的层(如全连接层、卷积层等)放在构造函数__init__()中,当然我也可以吧不具有参数的层也放在里面。
(2)一般把不具有可学习参数的层(如ReLU、dropout、BatchNormanation层)可放在构造函数中,也可不放在构造函数中,如果不放在构造函数__init__里面, 则在forward方法里面可以使用nn.functional来代替。
(3)forward方法是必须要重写的,它是实现模型的功能,实现各个层之间的连接关系的核心。

对__init__的理解,我们可以发现在18层和34层的网络中,卷积核大小均为3*3,且特征图数量没有增加,因此在BasicBlock中我们的conv的kernel_size=3,stride默认取1,这里需要注意BN层必须是在conv层和激活函数层中间,且conv中的bias(偏置参数)不需要设置,因为经过BN层后bias会被消掉。

关于downsample的理解

(参照马佳的男人的CSDN博文 链接link.)
首先我们要明确,在resnet中的downsample有两种:
1、真正意义上让output.shape长宽变成1/2的我暂且称之为real_downsample
2、shortcut(是指经过结构中捷径的过程)前的x的为了适应shortcut后变化的shape而做的自适应调节,暂且称之为identity_downsample
接下来看ResNet这个类:

class ResNet(nn.Module):
    def __init__(self,
                 block,
                 block_num,
                 num_classes=1000,
                 include_top=True,
                 groups=1,
                 width_per_group=64):
        super(ResNet, self).__init__()
        self.include_top = include_top
        self.in_channel = 64

        self.groups = groups
        self.width_per_group = width_per_group

        self.conv1 = nn.Conv2d(3, self.in_channel, kernel_size=7, stride=2,
                               padding=3, bias=False)
        self.bn1 = nn.BatchNorm2d(self.in_channel)
        self.relu = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        self.layer1 = self._make_layer(block, 64, block_num[0])
        self.layer2 = self._make_layer(block, 128, block_num[1], stride=2)
        self.layer3 = self._make_layer(block, 256, block_num[2], stride=2)
        self.layer4 = self._make_layer(block, 512, block_num[3], stride=2)
        if self.include_top:
            self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
            self.fc = nn.Linear(512 * block.expansion, num_classes)

        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')  #权重初始化(正态分布)

    def _make_layer(self, block, channel, block_num, stride=1):
        downsample = None
        if stride != 1 or self.in_channel != channel * block.expansion:
            downsample = nn.Sequential(
                nn.Conv2d(self.in_channel, channel * block.expansion, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(channel * block.expansion))

            layers = []
            layers.append(block(
                self.in_channel,
                channel,
                downsample=downsample,
                stride=stride,
                groups=self.groups,
                width_per_group=self.width_per_group))
            self.in_channel = channel * block.expansion

            for _ in range(1, block_num):
                layers.append(block(
                    self.in_channel,
                    channel,
                    groups=self.groups,
                    width_per_group=self.width_per_group))

            return nn.Sequential(*layers)

    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        if self.include_top:
            x = self.avgpool(x)
            x = torch.flatten(x, 1)
            x = self.fc(x)

        return x

** real_downsample:**
#这里的layer是指包含多个block的一个层,层>块
(1) 第一个大layer不做real_downsample,所以此时stride=1
(2)剩下的所有大layer都只在第一个block里的第一个3x3用stride=2做real_downsample 那么如何对所有的第一个block进行调整?
当然就是对于每个layer,先把第一个block拉出来用我们传进去的stride进行领导的特殊对待啦(stride默认为1)传进去stride = 1,那么第一个block就不用downsample;如果传进去stride = 2,那第一个block就要downsample。至于剩下的block, 就不用看领导脸色了,传进去stride = 1还是2都跟它们无关,因为它们反正都不downsample,所以可以看到在_make_layer的循环里没有指定接下来的stride。
** identity_downsample:**(identity:其实意思就是这个网络层的设计是用于占位的,即不干活,只是有这么一个层,放到残差网络里就是在跳过连接的地方用这个层,显得没有那么空虚!)
由于在残差结构里,参数通过主线和捷径后又一个相加操作,所以需要保证两者维度一致,所以需要做accommodation(适应)
什么时候需要做identity_downsample?
简单来说,做了real_downsample就要做identity_downsample

        if stride != 1 or self.in_channel != channel * block.expansion:
            downsample = nn.Sequential(
                nn.Conv2d(self.in_channel, channel * block.expansion, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(channel * block.expansion))

现在,来看代码,我们对stride是否等于1进行了判断,因为我们可以知道除了第一个大layer没做real_downsample外,其余所有大layer的第一个block都做了real_downsample,所以后续对传入参数进行变换。

downsample总结:
real downsample是Resnet主动为了downsample而downsample的,而identity_downsample只是我们为了能让out += identity 的被动的适应性调整

对于参数expansion的理解:

通过对比BasicBlock和Bottleneck可以发现在前者中expansion=1,后者等于4。
所以这个参数的用途究竟是什么?
我们经过两个block的对比可以发现,实际上这个参数是用于区分这两个block,已经知道了对于50层以下的网络使用BasicBlock,而以上使用Bottleneck。两者的区别可以通过表格看出,在block中50层以下网络,卷积后没有改变深度(特征图的数目),而对于50层以上的网 络,最后一次卷积将深度扩大了4倍,所以这个参数就是这么来的。

小结

这是我在学习过程中,对于ResNet代码阅读的一些心得和参考的博文。刚刚开始学习深度学习,这笔记算是对学习的证明吧!接下来将使用迁移学习的方法对宝可梦数据集进行训练。

  人工智能 最新文章
2022吴恩达机器学习课程——第二课(神经网
第十五章 规则学习
FixMatch: Simplifying Semi-Supervised Le
数据挖掘Java——Kmeans算法的实现
大脑皮层的分割方法
【翻译】GPT-3是如何工作的
论文笔记:TEACHTEXT: CrossModal Generaliz
python从零学(六)
详解Python 3.x 导入(import)
【答读者问27】backtrader不支持最新版本的
上一篇文章      下一篇文章      查看所有文章
加:2021-12-14 15:56:46  更:2021-12-14 15:58:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年6日历 -2024/6/26 22:49:44-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码