SENet及对轻量级网络的一些理解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SENet及对轻量级网络的一些理解相关的知识,希望对你有一定的参考价值。

参考技术A 转自: https://zhuanlan.zhihu.com/p/71995304

受限于硬件的羸弱,深度学习在上世纪的发展一直是不温不火,直到2012年,Alexnet横空出世,深度学习才开始前所未有的大发展。随着技术的发展,卷积神经网络逐渐取代了之前全连接的神经网络,成为深度学习领域的主流算法。

卷积神经网络的优势在于能够更好的提取特征和权值共享,自Alexnet之后,VGG又提出了神经网络的深度越深,效果越好的思想。自此深度成为构建网络时所考虑的第一要素。此时,又有人提出了Inception模型,这是人们在关注深度之外第一次关注网络的时间复杂度和空间复杂度。

图1

如图1所示,就是Inception v1的模型,该模型主要利用了1x1卷积进行了降维处理,达到了减小网络参数量和计算量的效果。在接下来的改进中,Inception又提出了利用两层3x3网络取代一层5x5网络这样的奇思妙想,进一步达到了减轻网络复杂度的作用。

如图2所示,就是5x5卷积变成3x3卷积的示意图。

图2

从此之后,轻量级网络开始盛行。诸如Squeezenet,Mobilenet等都是轻量级网络中的杰出代表。

自Inception之后,人们已经不再通过暴力的增加网络层数的方法来企图获得更高的准确率了,然而面对复杂的问题,过浅的网络很难达到理想的效果,所以加深网络依然是解决图像分类问题的最佳途径。但是增加网络很容易造成overfiting甚至训练集上得到的效果也比浅层网络要差,所以如何有效的增加网络层数就成了深度学习领域研究的重中之重。

为了解决这个问题,很多学者提出了自己的见解,如resnet所讲述的残差网络就是一例。然而残差网络虽然能够增加层数但是层数增加到三位数之后再增加也就有些无能为力了。此时作者另辟蹊径,提出了一种新型的网络结构SENet,这个网络结构可以对所有网络进行改进然后做到真正有效的增加层数,无论原网络层数有多深,通过加入SENet,都能增加相当数量的深度,并有效的提高实验效果。值得一提的是SENet在2017年的ImageNet挑战赛获得冠军。

从Inception开始,学者们提出网络的时候就主要是提出一个block,然后用这个block像搭积木一样的搭出整个网络。同样,作者也是用这个方法设计网络的,作者提出了一个Squeeze-and-Excitation block,然后用这个SE block搭出了整个网络。

卷积神经网络虽然有诸多的好处,但是卷积神经网络捕捉到的只是局部的信息,假如卷积核是7x7,那么感受野的大小也只有7x7。但是一张图片的每一个像素点之间都是互相有联系的,之前使用局部感受野的网络都忽略掉了全局像素点之间的关联信息,使得实验效果不够理想。Inception通过多尺度的卷积核,找到了提取一张feature map上全局关联信息的方法,然而直到本文网络之前都没有谁考虑各通道之间的全局关联信息。所以作者将关注点放到了通道关联信息上,作者发现提取出通道之间互相关联的信息可以有效的增加神经网络的分类准确率。

图3

如图3所示,就是一个SE block。对于任意给定的变换

(1)

我们可以构造一个对应的SE块来进行特征重新校准。首先通过挤压操作,跨越空间维度WXH产生一个全局描述符,这个全局描述符聚合了所有通道的空间信息,之后再进行激励操作,其中通过基于通道依赖性的门控机制为每个通道学习特定采样的激活,控制每个通道的激励。然后特征映射U被重新加权以生成SE块的输出,再与之前的block结合在一起,就达到了提取通道关联信息的目的。

SE网络可以通过简单地堆叠SE block的集合来生成,也可以用作架构中任意深度的原始块的直接替换。

新CNN架构的开发是一项具有挑战性的工程任务,通常涉及许多新的超参数和网络各层配置的选择。相比之下,上面概述的SE块的设计是简单的,并且可以直接与现有的最新架构一起使用,其卷积层可以通过直接用对应的SE层来替换从而使效果得到加强。另外,SE块在计算上是轻量级的,并且在模型复杂性和计算负担方面仅稍微增加。为了支持这些声明,作者开发了一些SENets,即SE-ResNet,SE-Inception,SE-ResNeXt和SE-Inception-ResNet,并在ImageNet 2012数据集上对SENets进行了广泛的评估。此外,为了证明SE块的一般适用性,作者还呈现了ImageNet之外的结果,这表明作者所提出的方法不受限于特定的数据集或任务。

图3所示的Ftr,我们可以将其看做一个普通的卷积层,事实上,在SE-ResNet,SE-Inception中,对应的Ftr分别就是残差块和Inception块。

Squeeze操作的目的其实就是将空间信息提取出来,为了网络的轻量级,事实上我们还希望能够将信息进行压缩,不要增加过大的时间复杂度和空间复杂度。

因此作者选取了全局平均池化进行Squeeze操作。如图3所示,我们将通过全局平均池化得到的向量设为Z,那么z的第c个元素即可通过公式(2)进行计算:

(2)

这个操作实际上是在得到U之后再进行的全局平均池化。一般CNN的每个通道的滤波器都是对局部感受野进行特征提取,因此U中的每个feature map都无法利用与其他feature map之间的关联关系,而且在较低网络层次上对应到原图尺寸上的感受野相对于较高层数的来说,无疑是很小的,这样空间关联信息的流失就会更加严重。

那么作者提出的这个Squeeze,实际上就相当于将感受野扩展到了全局,自然能够提取的特征就更为丰富,通过训练得到的分类识别的准确率就更高了。

为了利用在上一个操作中得到的全局信息,作者就提出了Excitation这个操作来全面捕获通道相关性。如图4和图5所示,就分别是SE-Inception Module和SE-Inception Module。作者指出我们所需的这个Excitation必须是灵活的同时又要能学习到非互斥的信息。

那么通过观察图4和图5我们也能轻易的得知:

(3)

其中指的是Relu函数,指的是sigmoid函数,r为压缩比例。这实际上就是两个全连接层再加上两个激活层。第一层全连接层的作用就是将global pooling所得到的全局信息进行压缩,作者通过实验发现,压缩比例r=16的时候实验结果最为理想。那么第二层全连接层的作用就是将被压缩的向量进行还原,还原到和global pooling层之后的向量大小一致。然后再将该向量与Ftr得到的三维卷积进行乘运算,实际上该向量中的每个值就与三维卷积中每个通道对应的feature map 相乘,这样每一个feather map就得到了与其他通道的空间关联特征了。

图4

那么图4和图5中的C指的就是通道的数目,而指的就是这个模块输入的feather map的尺寸大小。而通过图示我们也能清楚的看到SE block可以通用的加在所有的网络结构中从而达到提升性能的作用。

SENet通过堆叠一组SE块来构建。实际上,它是通过用原始块的SE对应部分(即SE残差块)替换每个原始块(即残差块)而产生的,作者在表1中描述了SE-ResNet-50和SE-ResNeXt-50的架构。

图5

表1

作者通过实验发现,SE block改进resnet-50时,所增加参数量不超过10%,却提升了1.5个百分点的准确率,并且作者后来通过实验发现整体网络最末尾的SE block对效果提升作用很小,去除掉这些block网络的准确率也不过下降0.1个百分点,而增加的额外参数量就减小到4%了。如表2所示,是几个原有的网络结构的top-1和top-5的准确率,original表示的是网络提出者得到的结果,而re-implementation指的是作者复现的结果,而SENet指的是作者运用SENet加强之后得到的结果,很明显有显著的准确率提升。

表2(该表截取自论文)

同时,作者还提出了一个计算模型复杂度的方法来探究运用了SENet之后,模型的参数量到底增加了多少,很显然准确率增加百分比相同的情况下,参数量增加越少的话,算法性价比越高。

两个全连接层的参数量都是,那么两个全连接层的参数量就是。以resnet为例,假设resnet一共包含了S个stage,其中第s个stage里面包含了个重复的残差块,那么用SENet加强过的resnet所增加的参数量就可以用公式(4)进行计算:

(4)

SENet的结构非常简单,特别容易部署,不需要引入新的函数或者卷积层,并且增加的参数量微乎其微,是一个有效增加神经网络分类准确率的方法。同时,作者通过实验发现,在Faster-rcnn等用于实现目标检测的网络中增加SENet块进行加强,也能得到很好的效果,这进一步证明了SENet网络的通用性。

接下来,作者思考了最后一个问题,增加的SE block添加在原有block中位置的不同会不会影响到实验结果。为此,作者进行了对比试验。

如图6所示, 是原来的残差网络与作者在四个不同位置添加了SE block的四种不同的网络结构,总共是五个不同的网络结构。

图6

表3

而表3即是四种不同的SENet结构的错误率对比,我们能够看到SE-PRE结构是其中相对表现最好的,这说明了SE block块所在位置确实会影响到神经网络的整体效果,所以我们在运用SENet时,对于其所添加的位置应该好好斟酌。

这篇论文提出了一个名为名为SE block的架构单元,通过使网络能够执行动态的信道特征重新校准来提高网络的表示能力。实验证明了SENets的有效性,它在多个数据集和任务之间实现了最先进的性能。此外,SE块揭示了以前的体系结构无法充分地对通道相关关系建模。并且SE块生成的特征重要度值可以用于其他任务,例如用于模型压缩的网络剪枝。

图像分类用最简短的代码复现SeNet,小白一定要收藏(keras,Tensorflow2.x)

目录

摘要

一、SENet概述

二、SENet 结构组成详解

三、详细的计算过程

                                                  ​

SENet 在具体网络中应用(代码实现SE_ResNet)

第一个残差模块

第二个残差模块

ResNet18、ResNet34模型的完整代码

ResNet50、ResNet101、ResNet152完整代码


摘要

一、SENet概述

           Squeeze-and-Excitation Networks(简称 SENet)是 Momenta 胡杰团队(WMW)提出的新的网络结构,利用SENet,一举取得最后一届 ImageNet 2017 竞赛 Image Classification 任务的冠军,在ImageNet数据集上将top-5 error降低到2.251%,原先的最好成绩是2.991%。

     作者在文中将SENet block插入到现有的多种分类网络中,都取得了不错的效果。作者的动机是希望显式地建模特征通道之间的相互依赖关系。另外,作者并未引入新的空间维度来进行特征通道间的融合,而是采用了一种全新的「特征重标定」策略。具体来说,就是通过学习的方式来自动获取到每个特征通道的重要程度,然后依照这个重要程度去提升有用的特征并抑制对当前任务用处不大的特征。

     通俗的来说SENet的核心思想在于通过网络根据loss去学习特征权重,使得有效的feature map权重大,无效或效果小的feature map权重小的方式训练模型达到更好的结果。SE block嵌在原有的一些分类网络中不可避免地增加了一些参数和计算量,但是在效果面前还是可以接受的 。Sequeeze-and-Excitation(SE) block并不是一个完整的网络结构,而是一个子结构,可以嵌到其他分类或检测模型中。

二、SENet 结构组成详解

    上述结构中,Squeeze 和 Excitation 是两个非常关键的操作,下面进行详细说明。

  

    上图是SE 模块的示意图。给定一个输入 x,其特征通道数为 {C}',通过一系列卷积等一般变换后得到一个特征通道数为C 的特征。通过下面的三个操作还重标前面得到的特征:

   1、Squeeze 操作,顺着空间维度来进行特征压缩,将每个二维的特征通道变成一个实数,这个实数某种程度上具有全局的感受野,并且输出的维度和输入的特征通道数相匹配。它表征着在特征通道上响应的全局分布,而且使得靠近输入的层也可以获得全局的感受野,这一点在很多任务中都是非常有用的。

  2、 Excitation 操作,它是一个类似于循环神经网络中门的机制。通过参数 w 来为每个特征通道生成权重,其中参数 w 被学习用来显式地建模特征通道间的相关性。

  3、 Reweight 操作,将 Excitation 的输出的权重看做是进过特征选择后的每个特征通道的重要性,然后通过乘法逐通道加权到先前的特征上,完成在通道维度上的对原始特征的重标定。

三、详细的计算过程

 首先F_{tr}这一步是转换操作(严格讲并不属于SENet,而是属于原网络,可以看后面SENet和Inception及ResNet网络的结合),在文中就是一个标准的卷积操作而已,输入输出的定义如下表示:

                                       

    那么这个F_{tr}的公式就是下面的公式1(卷积操作,V_{c}表示第c个卷积核,X^{s}表示第s个输入)。

                                                
    F_{tr}得到的U就是Figure1中的左边第二个三维矩阵,也叫tensor,或者叫C个大小为H*W的feature map。而uc表示U中第c个二维矩阵,下标c表示channel。
    接下来就是Squeeze操作,公式非常简单,就是一个global average pooling:
                              
    因此公式2就将H*W*C的输入转换成1*1*C的输出,对应Figure1中的Fsq操作。为什么会有这一步呢?这一步的结果相当于表明该层C个feature map的数值分布情况,或者叫全局信息。
    再接下来就是Excitation操作,如公式3。直接看最后一个等号,前面squeeze得到的结果是z,这里先用W1乘以z,就是一个全连接层操作,W1的维度是C/r * C,这个r是一个缩放参数,在文中取的是16,这个参数的目的是为了减少channel个数从而降低计算量。又因为z的维度是1*1*C,所以W1z的结果就是1*1*C/r;然后再经过一个ReLU层,输出的维度不变;然后再和W2相乘,和W2相乘也是一个全连接层的过程,W2的维度是C*C/r,因此输出的维度就是1*1*C;最后再经过sigmoid函数,得到s:
                                    
    也就是说最后得到的这个s的维度是1*1*C,C表示channel数目。这个s其实是本文的核心,它是用来刻画tensor U中C个feature map的权重。而且这个权重是通过前面这些全连接层和非线性层学习得到的,因此可以end-to-end训练。这两个全连接层的作用就是融合各通道的feature map信息,因为前面的squeeze都是在某个channel的feature map里面操作。
    在得到s之后,就可以对原来的tensor U操作了,就是下面的公式4。也很简单,就是channel-wise multiplication,什么意思呢?u_{c}是一个二维矩阵,s_{c}是一个数,也就是权重,因此相当于把u_{c}矩阵中的每个值都乘以s_{c}。对应Figure1中的Fscale。

                                                  

SENet 在具体网络中应用(代码实现SE_ResNet)

介绍完具体的公式实现,下面介绍下SE block怎么运用到具体的网络之中。


    上图是将 SE 模块嵌入到 Inception 结构的一个示例。方框旁边的维度信息代表该层的输出。

    这里我们使用 global average pooling 作为 Squeeze 操作。紧接着两个 Fully Connected 层组成一个 Bottleneck 结构去建模通道间的相关性,并输出和输入特征同样数目的权重。我们首先将特征维度降低到输入的 1/16,然后经过 ReLu 激活后再通过一个 Fully Connected 层升回到原来的维度。这样做比直接用一个 Fully Connected 层的好处在于:

    1)具有更多的非线性,可以更好地拟合通道间复杂的相关性;

    2)极大地减少了参数量和计算量。然后通过一个 Sigmoid 的门获得 0~1 之间归一化的权重,最后通过一个 Scale 的操作来将归一化后的权重加权到每个通道的特征上。

    除此之外,SE 模块还可以嵌入到含有 skip-connections 的模块中。上右图是将 SE 嵌入到 ResNet 模块中的一个例子,操作过程基本和 SE-Inception 一样,只不过是在 Addition 前对分支上 Residual 的特征进行了特征重标定。如果对 Addition 后主支上的特征进行重标定,由于在主干上存在 0~1 的 scale 操作,在网络较深 BP 优化时就会在靠近输入层容易出现梯度消散的情况,导致模型难以优化。

    目前大多数的主流网络都是基于这两种类似的单元通过 repeat 方式叠加来构造的。由此可见,SE 模块可以嵌入到现在几乎所有的网络结构中。通过在原始网络结构的 building block 单元中嵌入 SE 模块,我们可以获得不同种类的 SENet。如 SE-BN-Inception、SE-ResNet、SE-ReNeXt、SE-Inception-ResNet-v2 等等。

本例通过实现SE-ResNet,来显示如何将SE模块嵌入到ResNet网络中。SE-ResNet模型如下图:

第一个残差模块

第一个残差模块用于实现ResNet18、ResNet34模型,SENet嵌入到第二个卷积的后面。

# 第一个残差模块
class BasicBlock(layers.Layer):
    def __init__(self, filter_num, stride=1):
        super(BasicBlock, self).__init__()
        self.conv1 = layers.Conv2D(filter_num, (3, 3), strides=stride, padding='same')
        self.bn1 = layers.BatchNormalization()
        self.relu = layers.Activation('relu')
        self.conv2 = layers.Conv2D(filter_num, (3, 3), strides=1, padding='same')
        self.bn2 = layers.BatchNormalization()
        # se-block
        self.se_globalpool = keras.layers.GlobalAveragePooling2D()
        self.se_resize = keras.layers.Reshape((1, 1, filter_num))
        self.se_fc1 = keras.layers.Dense(units=filter_num // 16, activation='relu',
                                         use_bias=False)
        self.se_fc2 = keras.layers.Dense(units=filter_num, activation='sigmoid',
                                         use_bias=False)
        if stride != 1:
            self.downsample = Sequential()
            self.downsample.add(layers.Conv2D(filter_num, (1, 1), strides=stride))
        else:
            self.downsample = lambda x: x

    def call(self, input, training=None):
        out = self.conv1(input)
        out = self.bn1(out)
        out = self.relu(out)
        out = self.conv2(out)
        out = self.bn2(out)
        # se_block
        b = out
        out = self.se_globalpool(out)
        out = self.se_resize(out)
        out = self.se_fc1(out)
        out = self.se_fc2(out)
        out = keras.layers.Multiply()([b, out])

        identity = self.downsample(input)
        output = layers.add([out, identity])
        output = tf.nn.relu(output)
        return output

第二个残差模块

第二个残差模块用于实现ResNet50、ResNet101、ResNet152模型,SENet模块嵌入到第三个卷积后面。

# 第二个残差模块
class Block(layers.Layer):
    def __init__(self, filters, downsample=False, stride=1):
        super(Block, self).__init__()
        self.downsample = downsample
        self.conv1 = layers.Conv2D(filters, (1, 1), strides=stride, padding='same')
        self.bn1 = layers.BatchNormalization()
        self.relu = layers.Activation('relu')
        self.conv2 = layers.Conv2D(filters, (3, 3), strides=1, padding='same')
        self.bn2 = layers.BatchNormalization()
        self.conv3 = layers.Conv2D(4 * filters, (1, 1), strides=1, padding='same')
        self.bn3 = layers.BatchNormalization()
        # se-block
        self.se_globalpool = keras.layers.GlobalAveragePooling2D()
        self.se_resize = keras.layers.Reshape((1, 1, 4 * filters))
        self.se_fc1 = keras.layers.Dense(units=4 * filters // 16, activation='relu',
                                         use_bias=False)
        self.se_fc2 = keras.layers.Dense(units=4 * filters, activation='sigmoid',
                                         use_bias=False)

        if self.downsample:
            self.shortcut = Sequential()
            self.shortcut.add(layers.Conv2D(4 * filters, (1, 1), strides=stride))
            self.shortcut.add(layers.BatchNormalization(axis=3))

    def call(self, input, training=None):
        out = self.conv1(input)
        out = self.bn1(out)
        out = self.relu(out)
        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)
        out = self.conv3(out)
        out = self.bn3(out)
        b = out
        out = self.se_globalpool(out)
        out = self.se_resize(out)
        out = self.se_fc1(out)
        out = self.se_fc2(out)
        out = keras.layers.Multiply()([b, out])
        if self.downsample:
            shortcut = self.shortcut(input)
        else:
            shortcut = input
        output = layers.add([out, shortcut])
        output = tf.nn.relu(output)
        return output

ResNet18、ResNet34模型的完整代码

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, Sequential


# 第一个残差模块
class BasicBlock(layers.Layer):
    def __init__(self, filter_num, stride=1):
        super(BasicBlock, self).__init__()
        self.conv1 = layers.Conv2D(filter_num, (3, 3), strides=stride, padding='same')
        self.bn1 = layers.BatchNormalization()
        self.relu = layers.Activation('relu')
        self.conv2 = layers.Conv2D(filter_num, (3, 3), strides=1, padding='same')
        self.bn2 = layers.BatchNormalization()
        # se-block
        self.se_globalpool = keras.layers.GlobalAveragePooling2D()
        self.se_resize = keras.layers.Reshape((1, 1, filter_num))
        self.se_fc1 = keras.layers.Dense(units=filter_num // 16, activation='relu',
                                         use_bias=False)
        self.se_fc2 = keras.layers.Dense(units=filter_num, activation='sigmoid',
                                         use_bias=False)
        if stride != 1:
            self.downsample = Sequential()
            self.downsample.add(layers.Conv2D(filter_num, (1, 1), strides=stride))
        else:
            self.downsample = lambda x: x

    def call(self, input, training=None):
        out = self.conv1(input)
        out = self.bn1(out)
        out = self.relu(out)
        out = self.conv2(out)
        out = self.bn2(out)
        # se_block
        b = out
        out = self.se_globalpool(out)
        out = self.se_resize(out)
        out = self.se_fc1(out)
        out = self.se_fc2(out)
        out = keras.layers.Multiply()([b, out])

        identity = self.downsample(input)
        output = layers.add([out, identity])
        output = tf.nn.relu(output)
        return output


class ResNet(keras.Model):
    def __init__(self, layer_dims, num_classes=10):
        super(ResNet, self).__init__()
        # 预处理层
        self.padding = keras.layers.ZeroPadding2D((3, 3))
        self.stem = Sequential([
            layers.Conv2D(64, (7, 7), strides=(2, 2)),
            layers.BatchNormalization(),
            layers.Activation('relu'),
            layers.MaxPool2D(pool_size=(3, 3), strides=(2, 2), padding='same')
        ])
        # resblock
        self.layer1 = self.build_resblock(64, layer_dims[0])
        self.layer2 = self.build_resblock(128, layer_dims[1], stride=2)
        self.layer3 = self.build_resblock(256, layer_dims[2], stride=2)
        self.layer4 = self.build_resblock(512, layer_dims[3], stride=2)
        # 全局池化
        self.avgpool = layers.GlobalAveragePooling2D()
        # 全连接层
        self.fc = layers.Dense(num_classes, activation=tf.keras.activations.softmax)

    def call(self, input, training=None):
        x= self.padding(input)
        x = self.stem(x)
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        # [b,c]
        x = self.avgpool(x)
        x = self.fc(x)
        return x

    def build_resblock(self, filter_num, blocks, stride=1):
        res_blocks = Sequential()
        res_blocks.add(BasicBlock(filter_num, stride))
        for pre in range(1, blocks):
            res_blocks.add(BasicBlock(filter_num, stride=1))
        return res_blocks


def ResNet34(num_classes=10):
    return ResNet([2, 2, 2, 2], num_classes=num_classes)


def ResNet34(num_classes=10):
    return ResNet([3, 4, 6, 3], num_classes=num_classes)


model = ResNet34(num_classes=1000)
model.build(input_shape=(1, 224, 224, 3))
print(model.summary())  # 统计网络参数

ResNet50、ResNet101、ResNet152完整代码

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, Sequential


# 第二个残差模块
class Block(layers.Layer):
    def __init__(self, filters, downsample=False, stride=1):
        super(Block, self).__init__()
        self.downsample = downsample
        self.conv1 = layers.Conv2D(filters, (1, 1), strides=stride, padding='same')
        self.bn1 = layers.BatchNormalization()
        self.relu = layers.Activation('relu')
        self.conv2 = layers.Conv2D(filters, (3, 3), strides=1, padding='same')
        self.bn2 = layers.BatchNormalization()
        self.conv3 = layers.Conv2D(4 * filters, (1, 1), strides=1, padding='same')
        self.bn3 = layers.BatchNormalization()
        # se-block
        self.se_globalpool = keras.layers.GlobalAveragePooling2D()
        self.se_resize = keras.layers.Reshape((1, 1, 4 * filters))
        self.se_fc1 = keras.layers.Dense(units=4 * filters // 16, activation='relu',
                                         use_bias=False)
        self.se_fc2 = keras.layers.Dense(units=4 * filters, activation='sigmoid',
                                         use_bias=False)

        if self.downsample:
            self.shortcut = Sequential()
            self.shortcut.add(layers.Conv2D(4 * filters, (1, 1), strides=stride))
            self.shortcut.add(layers.BatchNormalization(axis=3))

    def call(self, input, training=None):
        out = self.conv1(input)
        out = self.bn1(out)
        out = self.relu(out)
        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)
        out = self.conv3(out)
        out = self.bn3(out)
        b = out
        out = self.se_globalpool(out)
        out = self.se_resize(out)
        out = self.se_fc1(out)
        out = self.se_fc2(out)
        out = keras.layers.Multiply()([b, out])
        if self.downsample:
            shortcut = self.shortcut(input)
        else:
            shortcut = input
        output = layers.add([out, shortcut])
        output = tf.nn.relu(output)
        return output


class ResNet(keras.Model):
    def __init__(self, layer_dims, num_classes=10):
        super(ResNet, self).__init__()
        # 预处理层
        self.padding = keras.layers.ZeroPadding2D((3, 3))
        self.stem = Sequential([
            layers.Conv2D(64, (7, 7), strides=(2, 2)),
            layers.BatchNormalization(),
            layers.Activation('relu'),
            layers.MaxPool2D(pool_size=(3, 3), strides=(2, 2), padding='same')
        ])
        # resblock
        self.layer1 = self.build_resblock(64, layer_dims[0],stride=1)
        self.layer2 = self.build_resblock(128, layer_dims[1], stride=2)
        self.layer3 = self.build_resblock(256, layer_dims[2], stride=2)
        self.layer4 = self.build_resblock(512, layer_dims[3], stride=2)
        # 全局池化
        self.avgpool = layers.GlobalAveragePooling2D()
        # 全连接层
        self.fc = layers.Dense(num_classes, activation=tf.keras.activations.softmax)

    def call(self, input, training=None):
        x = self.padding(input)
        x = self.stem(x)
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        # [b,c]
        x = self.avgpool(x)
        x = self.fc(x)
        return x

    def build_resblock(self, filter_num, blocks, stride=1):
        res_blocks = Sequential()
        if stride != 1 or filter_num * 4 != 64:
            res_blocks.add(Block(filter_num, downsample=True,stride=stride))
        for pre in range(1, blocks):
            res_blocks.add(Block(filter_num, stride=1))
        return res_blocks


def ResNet50(num_classes=10):
    return ResNet([3, 4, 6, 3], num_classes=num_classes)


def ResNet101(num_classes=10):
    return ResNet([3, 4, 23, 3], num_classes=num_classes)

def ResNet152(num_classes=10):
    return ResNet([3, 8, 36, 3], num_classes=num_classes)

model = ResNet50(num_classes=1000)
model.build(input_shape=(1, 224, 224, 3))
print(model.summary())  # 统计网络参数


 

以上是关于SENet及对轻量级网络的一些理解的主要内容,如果未能解决你的问题,请参考以下文章

网络概念小结

WebSocket协议理解

Contiki——为微传感器网络而生的轻量级的灵活的操作系统

C 语言网络编程 — 轻量级 HTTP 服务器设计与实现

C 语言网络编程 — 轻量级 HTTP 服务器设计与实现

深度学习方法(十四):轻量级CNN网络设计——MobileNet,ShuffleNet,文末有思考