今天是参加昇思25天学习打卡营的第16天,今天打卡的课程是“ResNet50图像分类”,这里做一个简单的分享。
1.简介
自卷积神经网络模型取得成功以来,更深、更宽、更复杂的网络成为卷积神经网络搭建的主流。但是随着网络往更深、更宽的方向发展,人们发现模型的准确率并不能一直上升达到100%,甚至会出现下降,这种现象被称为“神经网络的退化”。神经网络退化的产生说明卷积神经网络不能够简单的使用堆叠的方法进行优化。
2015年,152层深的ResNet(残差网络)的出现提出了一种新的解决方案,将训练深度扩展到的数数千层的同时保证性能仍然优越,成为了AI界的一个经典。
今天要学习的内容就是认识ResNet,了解其原理,并基于mindspore在ResNet50模型的基础上进行微调和推理实战。
2.ResNet
残差网络结构图如下图所示,残差网络由两个分支构成:一个主分支,一个shortcuts(图中弧线表示)。主分支通过堆叠一系列的卷积操作得到,shotcuts从输入直接到输出,主分支输出的特征矩阵𝐹(𝑥)𝐹(𝑥)加上shortcuts输出的特征矩阵𝑥𝑥得到𝐹(𝑥)+𝑥𝐹(𝑥)+𝑥,通过Relu激活函数后即为残差网络最后的输出。
残差网络结构主要由两种,一种是Building Block,适用于较浅的ResNet网络,如ResNet18和ResNet34;另一种是Bottleneck,适用于层数较深的ResNet网络,如ResNet50、ResNet101和ResNet152。
Building Block
Building Block结构图如下图所示,主分支有两层卷积网络结构:
主分支第一层网络以输入channel为64为例,首先通过一个 3×3
的卷积层,然后通过Batch Normalization层,最后通过Relu激活函数层,输出channel为64;
主分支第二层网络的输入channel为64,首先通过一个 3×3
的卷积层,然后通过Batch Normalization层,输出channel为64。
最后将主分支输出的特征矩阵与shortcuts输出的特征矩阵相加,通过Relu激活函数即为Building Block最后的输出。
Bottleneck
Bottleneck结构图如下图所示,在输入相同的情况下Bottleneck结构相对Building Block结构的参数数量更少,更适合层数较深的网络,ResNet50使用的残差结构就是Bottleneck。该结构的主分支有三层卷积结构,分别为 1×1
的卷积层、 3×3
卷积层和 1×1
的卷积层,其中 1×1
的卷积层分别起降维和升维的作用。
主分支第一层网络以输入channel为256为例,首先通过数量为64,大小为 1×1
的卷积核进行降维,然后通过Batch Normalization层,最后通过Relu激活函数层,其输出channel为64;
主分支第二层网络通过数量为64,大小为 3×3
的卷积核提取特征,然后通过Batch Normalization层,最后通过Relu激活函数层,其输出channel为64;
主分支第三层通过数量为256,大小 1×1
的卷积核进行升维,然后通过Batch Normalization层,其输出channel为256。
最后将主分支输出的特征矩阵与shortcuts输出的特征矩阵相加,通过Relu激活函数即为Bottleneck最后的输出。
3.ResNet50
ResNet网络层结构如下图所示,以输入彩色图像 224×224
为例,首先通过数量64,卷积核大小为 7×7
,stride为2的卷积层conv1,该层输出图片大小为 112×112
,输出channel为64;然后通过一个 3×3
的最大下采样池化层,该层输出图片大小为 56×56
,输出channel为64;再堆叠4个残差网络块(conv2_x、conv3_x、conv4_x和conv5_x),此时输出图片大小为 7×7
,输出channel为2048;最后通过一个平均池化层、全连接层和softmax,得到分类概率。
对于每个残差网络块,以ResNet50网络中的conv2_x为例,其由3个Bottleneck结构堆叠而成,每个Bottleneck输入的channel为64,输出channel为256。
ResNet50网络共有5个卷积结构,一个平均池化层,一个全连接层,以CIFAR-10数据集为例:
conv1:输入图片大小为 32×32
,输入channel为3。首先经过一个卷积核数量为64,卷积核大小为 7×7
,stride为2的卷积层;然后通过一个Batch Normalization层;最后通过Reul激活函数。该层输出feature map大小为 16×16
,输出channel为64。
conv2_x:输入feature map大小为 16×16
,输入channel为64。首先经过一个卷积核大小为 3×3
,stride为2的最大下采样池化操作;然后堆叠3个 [1×1,64;3×3,64;1×1,256]
结构的Bottleneck。该层输出feature map大小为 8×8
,输出channel为256。
conv3_x:输入feature map大小为 8×8
,输入channel为256。该层堆叠4个[1×1,128;3×3,128;1×1,512]结构的Bottleneck。该层输出feature map大小为 4×4
,输出channel为512。
conv4_x:输入feature map大小为 4×4
,输入channel为512。该层堆叠6个[1×1,256;3×3,256;1×1,1024]结构的Bottleneck。该层输出feature map大小为 2×2
,输出channel为1024。
conv5_x:输入feature map大小为 2×2
,输入channel为1024。该层堆叠3个[1×1,512;3×3,512;1×1,2048]结构的Bottleneck。该层输出feature map大小为 1×1
,输出channel为2048。
average pool & fc:输入channel为2048,输出channel为分类的类别数。
模型定义示例代码:
from mindspore import load_checkpoint, load_param_into_net
class ResNet(nn.Cell):
def __init__(self, block: Type[Union[ResidualBlockBase, ResidualBlock]],
layer_nums: List[int], num_classes: int, input_channel: int) -> None:
super(ResNet, self).__init__()
self.relu = nn.ReLU()
# 第一个卷积层,输入channel为3(彩色图像),输出channel为64
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, weight_init=weight_init)
self.norm = nn.BatchNorm2d(64)
# 最大池化层,缩小图片的尺寸
self.max_pool = nn.MaxPool2d(kernel_size=3, stride=2, pad_mode='same')
# 各个残差网络结构块定义
self.layer1 = make_layer(64, block, 64, layer_nums[0])
self.layer2 = make_layer(64 * block.expansion, block, 128, layer_nums[1], stride=2)
self.layer3 = make_layer(128 * block.expansion, block, 256, layer_nums[2], stride=2)
self.layer4 = make_layer(256 * block.expansion, block, 512, layer_nums[3], stride=2)
# 平均池化层
self.avg_pool = nn.AvgPool2d()
# flattern层
self.flatten = nn.Flatten()
# 全连接层
self.fc = nn.Dense(in_channels=input_channel, out_channels=num_classes)
def construct(self, x):
x = self.conv1(x)
x = self.norm(x)
x = self.relu(x)
x = self.max_pool(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = self.avg_pool(x)
x = self.flatten(x)
x = self.fc(x)
return x
def _resnet(model_url: str, block: Type[Union[ResidualBlockBase, ResidualBlock]],
layers: List[int], num_classes: int, pretrained: bool, pretrained_ckpt: str,
input_channel: int):
model = ResNet(block, layers, num_classes, input_channel)
if pretrained:
# 加载预训练模型
download(url=model_url, path=pretrained_ckpt, replace=True)
param_dict = load_checkpoint(pretrained_ckpt)
load_param_into_net(model, param_dict)
return model
def resnet50(num_classes: int = 1000, pretrained: bool = False):
"""ResNet50模型"""
resnet50_url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/models/application/resnet50_224_new.ckpt"
resnet50_ckpt = "./LoadPretrainedModel/resnet50_224_new.ckpt"
return _resnet(resnet50_url, ResidualBlock, [3, 4, 6, 3], num_classes,
pretrained, resnet50_ckpt, 2048)
4.小结
今天学习了ResNet的产生的背景、基本概念、模型的基本结构,以及ResNet50的模型结构、创建方法内容,最后基于ResNet50的预训练模型进行微调实现了基本的图像分类推理实战。ResNet是通过直连和模块化的方法提出一种新的构建神经网络模型的方法,改变了以往依靠堆叠神经网络层数来获取更高性能的做法,一定程度上了解决了梯度消失和梯度爆炸的问题,是一个时代性的发明。今天的算是一个简单的了解,后续还需要学习的内容有很多,继续努力吧。
以上是第16天的学习内容,附上今日打卡记录: