4.2 图像分类基本概念和ResNet设计思想

简介: 这篇文章介绍了图像分类的基本概念,详细阐述了ResNet(残差网络)的设计思想和实现方法,包括残差单元的结构设计、整体网络结构以及如何使用飞桨框架的高层API快速构建和训练图像分类模型。

4.2 图像分类基本概念和ResNet设计思想

4.2.1 图像分类

在第三章我们介绍了计算机视觉常见任务,其中一个就是图像分类。本节简单回顾,图像分类利用计算机对图像进行定量分析,把图像或图像中的每个像元或区域划归为若干个类别中的某一种,下图展示了分类经典数据集ImageNet,包含2万多类图片。

图1:ImageNet数据集图片示意图

图像是计算机视觉的核心,是物体检测、图像分割、物体跟踪、行为分析、人脸识别等其他高层次视觉任务的基础。图像分类在许多领域都有着广泛的应用,如:安防领域的人脸识别和智能视频分析等,交通领域的交通场景识别,互联网领域基于内容的图像检索、相册自动归类、商品识别、医学领域的图像识别等。

4.2.2 ResNet

ResNet是2015年ImageNet比赛的冠军,将识别错误率降低到了3.6%,这个结果甚至超出了正常人眼识别的精度。

随着深度学习的不断发展,模型的层数越来越多,网络结构也越来越复杂。那么是否加深网络结构,就一定会得到更好的效果呢?从理论上来说,假设新增加的层都是恒等映射,只要原有的层学出跟原模型一样的参数,那么深模型结构就能达到原模型结构的效果。换句话说,原模型的解只是新模型的解的子空间,在新模型解的空间里应该能找到比原模型解对应的子空间更好的结果。但是实践表明,增加网络的层数之后,训练误差往往不降反升

Kaiming He等人提出了残差网络ResNet来解决上述问题,在残差网络中最基本的单位为残差单元,其基本思想如图2所示。

  • 图6(a):表示增加网络的时候,将x映射成 y = F ( x )输出。
  • 图6(b):对图6(a)作了改进,输出 y = F ( x ) + x 。这时不是直接学习输出特征 y 的表示,而是学习 y − x
  • 如果想学习出原模型的表示,只需将 F ( x ) 的参数全部设置为0,则 y = x 是恒等映射。
  • F(x) = y - x 也叫做残差项,如果 x → y的映射接近恒等映射,图6(b)中通过学习残差项也比图6(a)学习完整映射形式更加容易。

图2:残差块设计思想

一个残差网络通常有很多个残差单元堆叠而成,下面我们来构建残差网络。

4.2.2.1 残差单元

残差单元也叫做残差块(Residual block),输入x通过跨层连接,能更快的向前传播数据,或者向后传播梯度。通俗的比喻,在火热的电视节目《王牌对王牌》上有一个“传声筒”的游戏,排在队首的嘉宾把看到的影视片段表演给后面一个嘉宾看,经过四五个嘉宾后,最后一个嘉宾如果能表演出更多原剧的内容,就能取得高分。我们常常会发现刚开始的嘉宾往往表演出最多的信息(类似于Loss),而随着表演的传递,有效的表演信息越来越少(类似于梯度弥散)。如果每个嘉宾都能看到原始的影视片段,那么相信传声筒的效果会好很多。类似的,由于ResNet每层都存在直连的旁路,相当于每一层都和最终的损失有“直接对话”的机会,自然可以更好的解决梯度弥散的问题

残差块的具体设计方案如图3所示,这种设计方案也常称作瓶颈结构(BottleNeck)

1*1的卷积核可以非常方便的调整中间层的通道数,在进入3*3的卷积层之前减少通道数(256->64),经过该卷积层后再恢复通道数(64->256),可以显著减少网络的参数量。这个结构(256->64->256)像一个中间细,两头粗的瓶颈,所以被称为“BottleNeck”。

图3:残差块结构示意图

这里,我们实现一个算子ResBlock来构建残差单元。ResNet中使用了BatchNorm层,在卷积层的后面加上BatchNorm以提升数值稳定性,首先定义卷积批归一化块ConvBNLayer包含卷积层和BatchNorm层:

In [1]

import paddle
import paddle.nn as nn
class ConvBNLayer(paddle.nn.Layer):
    def __init__(self,
                 num_channels,
                 num_filters,
                 filter_size,
                 stride=1,
                 groups=1,
                 act=None):
       
        """
        num_channels, 卷积层的输入通道数
        num_filters, 卷积层的输出通道数
        stride, 卷积层的步幅
        groups, 分组卷积的组数,默认groups=1不使用分组卷积
        """
        super(ConvBNLayer, self).__init__()
        # 创建卷积层
        self._conv = 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)
        # 创建BatchNorm层
        self._batch_norm = paddle.nn.BatchNorm2D(num_filters)
        
        self.act = act
    def forward(self, inputs):
        y = self._conv(inputs)
        y = self._batch_norm(y)
        if self.act == 'leaky':
            y = F.leaky_relu(x=y, negative_slope=0.1)
        elif self.act == 'relu':
            y = F.relu(x=y)
        return y

然后定义残差块BottleneckBlock,每个残差块会对输入图片做三次卷积ConvBNLayer,然后跟输入图片进行短接,如果残差块中第三次卷积输出特征图的形状与输入不一致,则对输入图片使用1 x 1卷积,将其输出形状调整成一致。


1 × 1卷积:与标准卷积完全一样,唯一的特殊点在于卷积核的尺寸是1 × 1,也就是不去考虑输入数据局部信息之间的关系,而把关注点放在不同通道间。通过使用1 × 1卷积,可以起到如下作用:

  • 实现信息的跨通道交互与整合。考虑到卷积运算的输入输出都是3个维度(宽、高、多通道),所以1 × 1卷积实际上就是对每个像素点,在不同的通道上进行线性组合,从而整合不同通道的信息;
  • 对卷积核通道数进行降维和升维,减少参数量。经过1 × 1卷积后的输出保留了输入数据的原有平面结构,通过调控通道数,从而完成升维或降维的作用;
  • 利用1 × 1卷积后的非线性激活函数,在保持特征图尺寸不变的前提下,大幅增加非线性

4.2.2.2 整体结构

残差网络就是将很多个残差单元串联起来构成的一个非常深的网络,下图表示出了ResNet-50的结构,一共包含49层卷积和1层全连接,所以被称为ResNet-50。

图4:ResNet-50模型网络结构示意图

ResNet的具体实现如下代码所示:

In [3]

# 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]):
                # c3、c4、c5将会在第一个残差块使用stride=2;其余所有残差块stride=1
                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, 
                        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

4.2.2.3 飞桨分类网络高层API

飞桨开源框架除了基础API外,还支持了高层API。通过高低融合实现灵活组网,让飞桨API更简洁、更易用、更强大。高层API支持paddle.vision.models接口,实现了对常用模型的封装,包括ResNet、VGG、MobileNet、LeNet等。使用高层API调用这些网络,可以快速完成神经网络的训练和Fine-tune。

代码示例如下:

In [1]

import paddle
from paddle.vision.models import resnet50
# 调用高层API的resnet50模型
model = resnet50()
# 设置pretrained参数为True,可以加载resnet50在imagenet数据集上的预训练模型
# model = resnet50(pretrained=True)
# 随机生成一个输入
x = paddle.rand([1, 3, 224, 224])
# 得到残差50的计算结果
out = model(x)
# 打印输出的形状,由于resnet50默认的是1000分类
# 所以输出shape是[1x1000]
print(out.shape)

/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/nn/layer/norm.py:654: UserWarning: When training, we now always track global mean and variance.

"When training, we now always track global mean and variance.")

[1, 1000]

使用paddle.vision中的模型可以简单快速的构建一个深度学习任务,如下示例,仅14行代码即可实现resnet在Cifar10数据集上的训练。

# 从paddle.vision.models 模块中import 残差网络,VGG网络,LeNet网络
from paddle.vision.models import resnet50, vgg16, LeNet
from paddle.vision.datasets import Cifar10
from paddle.optimizer import Momentum
from paddle.regularizer import L2Decay
from paddle.nn import CrossEntropyLoss
from paddle.metric import Accuracy
from paddle.vision.transforms import Transpose
# 确保从paddle.vision.datasets.Cifar10中加载的图像数据是np.ndarray类型
paddle.vision.set_image_backend('cv2')
# 调用resnet50模型
model = paddle.Model(resnet50(pretrained=False, num_classes=10))
# 使用Cifar10数据集
train_dataset = Cifar10(mode='train', transform=Transpose())
val_dataset = Cifar10(mode='test', transform=Transpose())
# 定义优化器
optimizer = Momentum(learning_rate=0.01,
                     momentum=0.9,
                     weight_decay=L2Decay(1e-4),
                     parameters=model.parameters())
# 进行训练前准备
model.prepare(optimizer, CrossEntropyLoss(), Accuracy(topk=(1, 5)))
# 启动训练
model.fit(train_dataset,
          val_dataset,
          epochs=50,
          batch_size=64,
          save_dir="./output",
          num_workers=8)

小结

在这一节里,给读者介绍了图像分类,以及ResNet的网络结构和代码实现。除此之外,还介绍了高层API直接调用常用深度神经网络的方法,方便开发者们快速完成深度学习网络迭代。

相关文章
|
6月前
|
机器学习/深度学习 算法 PyTorch
python手把手搭建图像多分类神经网络-代码教程(手动搭建残差网络、mobileNET)
python手把手搭建图像多分类神经网络-代码教程(手动搭建残差网络、mobileNET)
|
前端开发 iOS开发
分类预测 | MATLAB实现DRN深度残差网络多输入分类预测
分类预测 | MATLAB实现DRN深度残差网络多输入分类预测
|
6月前
|
机器学习/深度学习 PyTorch 语音技术
Pytorch迁移学习使用Resnet50进行模型训练预测猫狗二分类
深度学习在图像分类、目标检测、语音识别等领域取得了重大突破,但是随着网络层数的增加,梯度消失和梯度爆炸问题逐渐凸显。随着层数的增加,梯度信息在反向传播过程中逐渐变小,导致网络难以收敛。同时,梯度爆炸问题也会导致网络的参数更新过大,无法正常收敛。 为了解决这些问题,ResNet提出了一个创新的思路:引入残差块(Residual Block)。残差块的设计允许网络学习残差映射,从而减轻了梯度消失问题,使得网络更容易训练。
559 0
|
6月前
|
机器学习/深度学习 并行计算 算法
【计算机视觉+CNN】keras+ResNet残差网络实现图像识别分类实战(附源码和数据集 超详细)
【计算机视觉+CNN】keras+ResNet残差网络实现图像识别分类实战(附源码和数据集 超详细)
185 0
|
机器学习/深度学习 数据挖掘 PyTorch
图像分类经典神经网络大总结(AlexNet、VGG 、GoogLeNet 、ResNet、 DenseNet、SENet、ResNeXt )
图像分类经典神经网络大总结(AlexNet、VGG 、GoogLeNet 、ResNet、 DenseNet、SENet、ResNeXt )
6404 1
图像分类经典神经网络大总结(AlexNet、VGG 、GoogLeNet 、ResNet、 DenseNet、SENet、ResNeXt )
|
机器学习/深度学习 算法 TensorFlow
基于python+ResNet50算法实现一个图像识别分类系统
在本文中将介绍使用Python语言,基于TensorFlow搭建ResNet50卷积神经网络对四种动物图像数据集进行训练,观察其模型训练效果。
665 0
基于python+ResNet50算法实现一个图像识别分类系统
|
机器学习/深度学习 人工智能 PyTorch
【图像分类】基于OpenVINO实现PyTorch ResNet50图像分类
【图像分类】基于OpenVINO实现PyTorch ResNet50图像分类
313 0
|
机器学习/深度学习 数据挖掘 Go
深度学习论文阅读图像分类篇(五):ResNet《Deep Residual Learning for Image Recognition》
更深的神经网络更难训练。我们提出了一种残差学习框架来减轻 网络训练,这些网络比以前使用的网络更深。我们明确地将层变为学 习关于层输入的残差函数,而不是学习未参考的函数。我们提供了全 面的经验证据说明这些残差网络很容易优化,并可以显著增加深度来 提高准确性。在 ImageNet 数据集上我们评估了深度高达 152 层的残 差网络——比 VGG[40]深 8 倍但仍具有较低的复杂度。这些残差网络 的集合在 ImageNet 测试集上取得了 3.57%的错误率。这个结果在 ILSVRC 2015 分类任务上赢得了第一名。我们也在 CIFAR-10 上分析 了 100 层和 1000 层的残差网络。
265 0
|
机器学习/深度学习 算法
Resnet图像识别入门——Softmax分类是如何工作的
softmax作为一个分类器,它只是把重要的信息变得更重要了而已。
Resnet图像识别入门——Softmax分类是如何工作的
|
机器学习/深度学习 人工智能 计算机视觉
深度学习经典网络解析图像分类篇(七):ResNet
 如果说你对深度学习略有了解,那你一定听过大名鼎鼎的ResNet,正所谓ResNet 一出,谁与争锋?现如今2022年,依旧作为各大CV任务的backbone,比如ResNet-50、ResNet-101等。ResNet是2015年的ImageNet大规模视觉识别竞赛(ImageNet Large Scale Visual Recognition Challenge, ILSVRC)中获得了图像分类和物体识别的冠军,是中国人何恺明、张祥雨、任少卿、孙剑在微软亚洲研究院(AI黄埔军校)的研究成果。
476 0

热门文章

最新文章