森说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简易实现手写数字识别模型