森说AI:用paddle2.x完成resnet50模型转写并实现前向对齐

Posted 神佑我调参侠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了森说AI:用paddle2.x完成resnet50模型转写并实现前向对齐相关的知识,希望对你有一定的参考价值。

前言

这次飞桨的论文复现的作业是要求去将pytorch的resnet50模型来用paddle来实现,因为我以前根本没有接触过pytorch,所以这里面实现起来还是有点困难的,然后我们这里就开始实现吧!

pytorch源码解析

这里面给了一个pytorch的链接:https://github.com/pytorch/vision/blob/master/torchvision/models/resnet.py
但是我有点看不懂,我先去了解一下resnet50的模型结构是什么样的?我看了半天只是懂了点皮毛,然后我们一会结合代码实际看看!

然后这里面说是要对比最后两者的权重啥的,那个我torch的我直接去使用了一下别人已经得到的权重吧,然后将它的权重和我用paddle去实现的权重做一个对比。

resnet50网络结构


这里我们来看一下resnet50的网络结构:
参考:https://zhuanlan.zhihu.com/p/353235794
从这个图上我们可以看到主要是分成了5个步骤,这里输入的图片格式为:(3,224,224),然后第一层的话是一个7x7的卷积核,个数为64个,这个就是可以代表我们可以去提取特征的数目,然后卷积的步长为2,然后得到的是(3,112,1112)大小的形式,这里可能有人会问了,这是怎么得到的,卷积的计算过程是什么呢?那这里我们先来回顾一下卷积神经网络的基础
参考:https://www.paddlepaddle.org.cn/tutorials/projectdetail/1516119

卷积神经网络基础

在应用卷积之前是怎么做的呢?是将数据变成一维的进行计算,这里可以借鉴我以前的手写数字识别的文章,数据的大小就是WXH,但是这种方法有很多的弊端:空间信息丢失和当图片过大的话,数据就会呈平方式的增长,那样的话会导致参数过大引起过拟合。而使用卷积的话就不会,因为输入的还是原来的形式不用变成一维的,然后就是卷积核也不会受到图片的大小的影响

卷积

这里我们先看一下卷积的运算过程,看下面这张图:

这里先要去看卷积核就是中间的那个,然后在左面去对应,之后对应相乘相加得到结果

从这个图中我们可以看到,最后得到的形式与卷积核的大小一致,但是加了填充和步幅就不一定了

然后我们来看看下一步:

就是这样,相当于一个滑动的窗口在滑动,每滑动一次做一次卷积。
这里我们会发现通过卷积的时候输出的形式会变小,为了防止这种事情的发生我们这里可以去使用填充来解决,先看一下特征图的计算过程:

然后我们看下什么叫做填充:

填充


这里的话我们再计算一下:比如用4X4的输入与3X3的卷积进行计算,特征图是4-3+1=2
但是我们用1的填充后,输入变成了6X6,然后在计算一下特征图,为6-3+1=4,这样就不改变形式了
最后我们给出一个通用的公式,这样不用去计算填充后的输入,只要用原输入就可以啦

我们重新计算一下:4+2-3+1=4,ph指的是填充
之后我们看一下步幅:

步幅

这个是最好理解的,就是滑动的间隔的像素点:

然后的话有了步幅,那么得到的特征图的大小就有不一样了,这里我们给出最后的公式:

我们来计算一下:(4+0-2)/2 +1 =2
上面的内容可能我们了解的比较多,但是接下来的感受野可能就不是了解的很清楚了

感受野


感受野理解起来并不是很难,因为我们在输入图像经历卷积的时候,每个卷积核大小的面积区域就会得到一个点,那个那个原图像的区域就是特征图的对应的点的感受野,根据网络结构的加深,感受野会越来越大。

批量操作


这里面输出多少个特征图是由N决定的,特征图的通道数是由卷积核的数目决定的!已上面的图为例:N=2,所以最后也会生成2个特征图,然后卷积核为2,所以特征图通道数也为2

API介绍

这里主要用到下面这个API:

如果是图像处理就要2D,如果是视频处理就要用3D,然后说说这里面的参数,第一个是输入图像的通道数,比如说RGB图像就是3,然后是输出的通道数,上面我们也说了,就是卷积核的数目,接下来是卷积核的大小,然后是步长,填充

池化

介绍完了卷积,我们来说说池化!下面看一下这个图:

当我们得到特征图后,可以用一个值来代替一个区域的值,常用的是平均值池化和最大值池化,这样就可以减少特征图的大小从而减小计算量,然后我们看一下计算过程:

(4+0-2)/2 +1 = 2 大体上和我们上面的卷积计算过程差不多

在卷积神经网络中通常会用到2X2的池化窗口,并且步幅也为2,这时的话,输出特征图大小是原来特征图大小的一半

Relu激活函数


在以前的时候用sigmoid会多一点,但是因为会出现梯度消失的情况,所以现在多用Relu函数

批归一化(Batch Normalization)

这个也叫做BN层, 在ResNet中也是应用到了,其目的是对神经网络中间层的输出进行标准化处理,使得中间层的输出更加稳定。我们一般会对数据去使用归一化但是随着网络结构的深入,数据对后面的层影响就不那么大了,所以我们也要对中间层的输出也做归一化处理,这样有很多好处:可以加快收敛,使用更大的学习率去学习,最重要的可以抑制过拟合

丢弃法Dropout

这也是一种抑制过拟合的方法:

这里面丢弃的是随机的!
学到这里差不多了,我们来继续上面的去看ResNet的网络结构!

再看ResNet网络结构

党部整除的时候:卷积向下取整,池化向上取整。

我们来说下过程,第一层的话是(224-7)/2 +1= 109呀,为什么这上面写的是112呀,为啥呀?气死我了,弄了半个点才弄明白是因为这里面有一个3的填充,然后我们计算一下:(224-7+6)/2 + 1=112.5 = 112,合理了,哈哈。然后接下来是一个最大池化,我们计算一下:(112+0-3)/2+1=55.5=56,合理,这回会算了,然后到这步我们的特征图的形状为(64,56,56)。然后下面我们结合代码去做

paddle代码实现

这里先说一下1X1卷积核的重要性,它不是为了改变特征图的大小而是为了改变特征图的维度,通过使用改变卷积核的数目来改变维度的大小。
然后像下面这种结构我们也称为瓶颈结构:

因为输入的维度是256,先通过1X1的卷积核,将维度变成64,然后进行3x3的卷积操作,之后在经历1X1的卷积来增加维度为256,这样大大减少了计算的参数。我们在来看一下示意图:

一共有49层卷积和一层的全连接层
这里面我们要去实现3部分,一个就是BN(这个不是卷积,就是起到一个归一化的作用),但是这里面为了方便我们直接将这个BN与卷积合到一个函数里面去,也就是BN层。一个是残差块就是1X1,3X3,1X1的3个卷积的那个额,最后一个就是一个集成的ResNet模型结构了,下面我们一个个去实现:

定义卷积批归一化块

import paddle
import numpy as np
class ConvBNlayer(paddle.nn.layers):
    def __init__(self,num_channels,num_filters,filter_size,stride=1,group=1,act=None):
        super(ConvBNlayer,self).__init__()
        #定义卷积层
        self.conv = paddle.nn.Conv2D(
            in_channels=num_channels,
            out_channels=num_filters,
            kernel_size=filter_size,
            stride=stride,
            padding=(filter_size - 1) // 2,
            groups=groups,
            bias_attr=False
            )
        #定义BN层
        self._batch_norm = paddle.nn.BatchNorm2D(num_filters)
        #定义激活函数
        self.act = act
    #前向传播
    def forward(self,inputs):
        y = self.conv(imputs)
        y = self._batch_norm(y)
        if self.act == 'relu':
            y = paddle.nn.functional.relu(y)
        return y

定义残差块

这里面是将输入经历了3次卷积之后和输入短接。

class BottleneckBlock(paddle.nn.layer):
    def __init__(self,           
                num_channels,
                num_filters,
                stride,
                #是否短接
                shortcut=True
            ):
        super(BottleneckBlock,self).__init__()
        #第一个卷积1X1来降维
        self.conv0 = ConvBNlayer(
                num_channels=num_channels,
                num_filters=num_filters,
                filter_size=1,
                act='relu'
            )
        #第二个卷积3X3
        self.conv1 = ConvBNlayer(
                num_channels=num_channels,
                num_filters=num_filters,
                filter_size=3,
                stride=stride,
                act='relu'
            )
        #第三个卷积1X1来升维
        self.conv1 = ConvBNlayer(
                num_channels=num_channels,
                num_filters=num_filters*4,
                filter_size=1,
                act=None
            )
        self.shortcut = shortcut
        self._num_channels_out = num_filters * 4
    def forward(self, inputs):
        y = self.conv0(inputs)
        conv1 = self.conv1(y)
        conv2 = self.conv2(conv1)
        if self.shortcut:
            short = inputs
        y = paddle.add(x=short, y=conv2)
        y = F.relu(y)
        return y

最后ResNet模型

# ResNet模型代码
import numpy as np
import paddle
import paddle.nn as nn
import paddle.nn.functional as F
# 定义ResNet模型
class ResNet(paddle.nn.Layer):
    def __init__(self, layers=50, class_dim=1):
        """
        
        layers, 网络层数,可以是50, 101或者152
        class_dim,分类标签的类别数
        """
        super(ResNet, self).__init__()
        self.layers = layers
        supported_layers = [50, 101, 152]
        assert layers in supported_layers, \\
            "supported layers are {} but input layer is {}".format(supported_layers, layers)

        if layers == 50:
            #ResNet50包含多个模块,其中第2到第5个模块分别包含3、4、6、3个残差块
            depth = [3, 4, 6, 3]
        elif layers == 101:
            #ResNet101包含多个模块,其中第2到第5个模块分别包含3、4、23、3个残差块
            depth = [3, 4, 23, 3]
        elif layers == 152:
            #ResNet152包含多个模块,其中第2到第5个模块分别包含3、8、36、3个残差块
            depth = [3, 8, 36, 3]
        
        # 残差块中使用到的卷积的输出通道数
        num_filters = [64, 128, 256, 512]

        # ResNet的第一个模块,包含1个7x7卷积,后面跟着1个最大池化层
        self.conv = ConvBNLayer(
            num_channels=3,
            num_filters=64,
            filter_size=7,
            stride=2,
            act='relu')
        self.pool2d_max = nn.MaxPool2D(
            kernel_size=3,
            stride=2,
            padding=1)

        # ResNet的第二到第五个模块c2、c3、c4、c5
        self.bottleneck_block_list = []
        num_channels = 64
        for block in range(len(depth)):
            shortcut = False
            for i in range(depth[block]):
                bottleneck_block = self.add_sublayer(
                    'bb_%d_%d' % (block, i),
                    BottleneckBlock(
                        num_channels=num_channels,
                        num_filters=num_filters[block],
                        stride=2 if i == 0 and block != 0 else 1, # c3、c4、c5将会在第一个残差块使用stride=2;其余所有残差块stride=1
                        shortcut=shortcut))
                num_channels = bottleneck_block._num_channels_out
                self.bottleneck_block_list.append(bottleneck_block)
                shortcut = True

        # 在c5的输出特征图上使用全局池化
        self.pool2d_avg = paddle.nn.AdaptiveAvgPool2D(output_size=1)

        # stdv用来作为全连接层随机初始化参数的方差
        import math
        stdv = 1.0 / math.sqrt(2048 * 1.0)
        
        # 创建全连接层,输出大小为类别数目,经过残差网络的卷积和全局池化后,
        # 卷积特征的维度是[B,2048,1,1],故最后一层全连接的输入维度是2048
        self.out = nn.Linear(in_features=2048, out_features=class_dim,
                      weight_attr=paddle.ParamAttr(
                          initializer=paddle.nn.initializer.Uniform(-stdv, stdv)))

    def forward(self, inputs):
        y = self.conv(inputs)
        y = self.pool2d_max(y)
        for bottleneck_block in self.bottleneck_block_list:
            y = bottleneck_block(y)
        y = self.pool2d_avg(y)
        y = paddle.reshape(y, [y.shape[0], -1])
        y = self.out(y)
        return y

总结

我这个思路是不对的,最终也是没有实现出来,不用pytorch肯定是不行的,下一篇文章的化我会去安装然后使用,虽然这一回没有实现出来,但是我已经了解论文复现的流程了,并且我们也复习了卷积的基础和学习resnet这个经典的卷积神经网络,我们下一篇文章见。

以上是关于森说AI:用paddle2.x完成resnet50模型转写并实现前向对齐的主要内容,如果未能解决你的问题,请参考以下文章

森说AI:用Paddle2.x完成resnet50模型转写并实现前向对齐

森说AI:用Paddle2.x完成resnet50模型转写并实现前向对齐

森说AI:AI创造营大作业--应用paddlex完成对自定义车道路的语义分割

森说AI:AI创造营大作业--应用paddlex完成对自定义车道路的语义分割

森说AI:从零开始应用paddlehub转换手写数字识别模型并完成部署:使用paddle2.xAPI简易实现手写数字识别模型

森说AI:从零开始应用paddlehub转换手写数字识别模型并完成部署:使用paddle2.xAPI简易实现手写数字识别模型