曹健老师 TensorFlow2.1 —— 第五章 卷积神经网络

第一章

第二章

第三章

第四章

本章目的:用图卷积神经网络实现离散数据的分类 ( 以图像分类为例 ) .

5.1 卷积计算过程

在实际项目中,输入神经网络的是具有更高分辨率的彩色图片,使得送入全连接网络的输入特征数过多,随着隐藏层层数的增加,网络规模过大,待优化参数过多,很容易使模型过拟合. 为了减少待训练参数,在实际应用时,会先对原始图片进行特征提取,把提取来的特征送入全连接网络,让其输出识别结果. 

卷积计算是一种有效的特征提取方法,一般会用一个正方形的卷积核,按指定步长在输入特征图上滑动,遍历这种输入特征图中的每个像素点,每滑动一个步长,卷积核会与输入特征图部分像素点重合,重合区域对应元素相乘、求和再加上偏置项,得到输出特征的一个像素点. 

如果输入特征是单通道灰度图,使用深度为 1 的单通道卷积核;如果输入特征是三通道彩色图,使用 3*3*3 的卷积核或 5*5*3 的卷积核. 总之,要使卷积核的通道数与输入特征图的通道数一致. 这是因为,如果想要让卷积核与输入特征图对应点匹配上,必须让卷积核的深度与输入特征图的深度一致. 所以,输入特征图的深度决定了当前层卷积核的深度.

由于每个卷积核在卷积计算后会得到一个输出特征图,所以当前层使用了几个卷积核,就会有几张输出特征图. 所以,当前层卷积核的个数,决定了当前层输出特征图的深度. 

卷积核立体图如上图所示,每个小颗粒都存储了一个待训练参数,在执行卷积计算时,卷积核中的参数是固定的,在每次反向传播时,这些待训练参数会被梯度下降法更新. 卷积就是利用立体卷积核实现了参数的空间共享.

  •  具体计算过程:

对于输入特征图是单通道的,选择单通道卷积核,上图为 5 行 5 列单通道,选用 3*3 单通道卷积核,滑动步长为 1 . 在输入特征图上滑动,每滑动一步输入特征图与卷积核里的 9 个元素重合,对应元素相乘求和再加上偏置项 b ,如卷积核滑动到图上位置时,计算为:(-1)*1+0*0+1*2+(-1)*5+0*4+1*2+(-1)*3+0*4+1*5+1 = 1.

对于输入特征图是三通道的,选择三通道卷积核,计算过程如上图. 

5.2 感受野 ( Receptive Field )

感受野指输出特征图 ( feature map ) 中的一个像素点映射到原始输入图片的区域大小. 

        如上图所示,原始输入图片大小为 5 * 5 ,用黄色的 3 * 3 卷积核作用,则会输出一个 3 * 3 的输出特征图,输出特征图上每个像素点映射到原始输入图片是 3 * 3 的区域,因此,它的感受野是 3 ;如果再对这个 3 * 3 的特征图用绿色的 3 * 3 卷积核作用,会输出一个 1 * 1 的输出特征图,那么,这个输出特征图上的像素点映射到原始图片是 5 * 5 的区域,因此,它的感受野是 5 . 

如上图所示,对原始图片直接用蓝色的 5 * 5 卷积核作用,则会输出一个 1 * 1 的输出特征图,它的感受野为 5 .

对于同样一个 5 * 5 的原始图片,经过两层 3 * 3 卷积核作用和经过一层 5 * 5 卷积核作用都可以得到一个感受野是 5 的 1 * 1 输出特征图,因此,两层 3 * 3 卷积核和一层 5 * 5 卷积核的特征提取能力是一样的,但如何做选择,就需要考虑他们所承载的待训练参数和计算量. 假设输入特征宽和高都为 x ,卷积计算步长均为 1 ,具体计算如下:

  • 对于两层 3 * 3 卷积核:

参数量:9 + 9 = 18

计算量:每个 3 * 3 卷积核计算得到一个输出像素点需要做 9 次乘加计算,两层则需要:(x-2)^2\times 9+(x-4)^2\times 9=18x^2-108x+180\ (x\geq 3)  

  • 对于一层 5 * 5 卷积核:

参数量:25

计算量:每个 5 * 5 卷积核计算得到一个输出像素点需要做 25 次乘加计算,一层则需要:(x-4)^2\times 25=25x^2-200x+400\ (x\geq 5)

因此,可以比较出,当输入特征图边长大于 10 个像素点时,两层 3 * 3 卷积核要比一层 5 * 5 卷积核性能好.

5.3 全零填充 ( Padding )

当希望卷积计算保持输入特征图的尺寸不变,可以使用全零填充,在输入特征图周围填充 0 .

卷积输出特征图维度的计算公式为:

其中,入长为输入特征图边长.

# 在 tf 中, 描述全零填充
padding = 'SAME' 或 padding = 'VALID'

5.4 TF 描述卷积计算层

  •  Tensorflow 给出了计算卷积的函数:
tf.keras.layers.Conv2D(
filters = 卷积核个数,
kenerl_size = 卷积核尺寸,    # 正方形写核长整数, 或 ( 核高 h ,核宽 w ) 
strides = 滑动步长,          # 横纵向相同写步长整数或 ( 纵向步长 h, 横向步长 w ), 默认 1
padding = 'same' or 'valid'    # 默认是 valid 
activation = 'relu' or 'sigmoid' or 'tanh' of 'softmax' ...     # 如有 BN ( batch normalization ) 此处不写
input_shape = ( 高, 宽, 通道数 )    # 输入特征图维度, 可省略 
)
  • 实例:描述三层卷积计算,每层用了一种表示形式
model = tf.keras.models.Sequential([
    Conv2D(6, 5, padding='valid', activation='sigmoid'),
    MaxPool2D(2, 2)
    Conv2D(6, (5, 5), padding='valid', activation='sigmoid'),
    MaxPool2D(2, (2, 2))
    Conv2D(filters=6, kernel_size=(5, 5), padding='valid', activation='sigmoid'),
    MaxPool2D(pool_size=(2, 2), strides=2),
    Flatten(),
    Dense(10, activation='softmax')
])

5.5 批标准化 ( Batch Normaliztion, BN )

  • 神经网络对 0 附近的数字更敏感,但随着网络层数的增加,特征数据会出现偏离 0 均值的情况,标准化可以使数据符号以 0 为均值,1 为标准差的标准正态分布,把偏移的特征数据,重新拉回到 0 附近;批标准化是对一个 batch 的数据做标准化处理,使数据回归标准正态分布,常用在卷积操作和激活操作之间. 批标准化操作会让每个像素点进行减均值除以标准差的自更新计算,可利用公式 ( 1 ) 计算批标准化后的输出特征图.

H_{i}^{'k}=\frac{H^{k}_{i}-\mu _{batch}^{k}}{\sigma _{batch}^{k}}                              ( 1 )

其中:

H_{i}^{'k} 表示批标准化后,第 k 个卷积核,输出特征图中的第 i 个像素点;

H_{i}^{k} 表示批标准化前,第 k 个卷积核,输出特征图中第 i 个像素点;

\mu _{batch}^{k} 表示批标准化前,第 k 个卷积核,batch 张输出特征图中所有像素点平均值;

\sigma _{batch}^{k} 表示批标准化前,第 k 个卷积核,batch 张输出特征图中所有像素点标准差.

如上面两张图所示,激活函数为 sigmoid ,左图为偏移的特征数据分布,经过公式 ( 1 ) 操作,将原本偏移的特征数据,重新拉回到 0 均值,如右图所示,使进入激活函数的数据分布在激活函数线性区,使得输入数据的微小变化,更明显地体现到激活函数的输出,提升了激活函数对输入数据的区分力.

  • 但这种简单的特征数据标准化使特征数据完全满足标准正态分布,集中在激活函数中心的线性区域,使激活函数丧失了非线性特性,因此,在 BN 操作中,为每个卷积核引入了两个可训练参数缩放因子 \gamma 和偏移因子 \beta . 反向传播时,缩放因子和偏移因子会与其他待训练参数一同被训练优化,使标准正态分布后的特征数据,通过缩放因子和偏移因子优化了特征数据分布的宽窄和偏移量,保证了网络的非线性表达力. 特征数据变为  x_{i}^{k}=\gamma_{k}H_{i}^{'k}+\beta_{k}.
  • BN 层位于卷积层之后,激活层之前,Tensorflow 提供了 BN 操作的函数 BatchNormalization
tf.keras.layers.BatchNormalization()

model = tf.keras.models.Sequential([
    Conv2D(filters=6, kernel_size=(5, 5), padding='same'),    # 卷积层
    BatchNormalization(),    # BN 层
    Activation('relu'),      # 激活层
    MaxPool2D(pool_size=(2, 2), strides=2, padding='same'),    #  池化层
    Dropout(0.2),    # dropout 层
])

5.6 池化 ( Pooling )

  • 池化操作用于减少卷积神经网络中特征数据量,池化的主要方法有:

( 1 ) 最大池化:可以提取图片纹理,选择最大像素点输出.

( 2 ) 均值池化:可以保留背景特征,计算平均像素点输出.

举例说明,如下图所示,如果用 2 * 2 的池化核对输入图片以 2 为步长进行池化,输出图片将变为输入图片的四分之一大小.

                                                                               

  • Tensorflow 描述池化:
# 最大池化
tf.keras.layers.MaxPool2D(
    pool_size = 池化核尺寸,    # 正方形写核长整数, 或 ( 核高 h, 核宽 w )
    strides = 池化步长,        # 步长整数, 或 ( 纵向步长 h, 横向步长 w ), 默认是 pool_size
    padding = 'valid' or 'same'    # 使用或不使用全 0 填充, 默认是 valid
)

# 平均池化
tf.keras.layers.AveragePooling2D(
    pool_size = 池化核尺寸,    # 正方形写核长整数, 或 ( 核高 h, 核宽 w )
    strides = 池化步长,        # 步长整数, 或 ( 纵向步长 h, 横向步长 w ), 默认是 pool_size
    padding = 'valid' or 'same'    # 使用或不使用全 0 填充, 默认是 valid
)
model = tf.keras.models.Sequential([
    Conv2D(filters=6, kernel_size=(5, 5), padding='same'),    # 卷积层
    BatchNormalization(),    # BN 层
    Activation('relu'),      # 激活层
    MaxPool2D(pool_size=(2, 2), strides=2, padding='same'),   # 池化层
    Dropout(0.2)    # dropout 层
])

5.7 舍弃 ( Dropout )

  • 为了缓解神经网络过拟合,在神经网络训练过程中,常把隐藏层的部分神经元按照一定比例从神经网络中临时舍弃,在使用神经网络时,再把所有神经元恢复到神经网络中.
  • TF 描述 Dropout :
tf.keras.layers.Dropout( 舍弃的概率 )

model = tf.keras.models.Sequential([
    Conv2D(filters=6, kernel_size=(5, 5), padding='same'),    # 卷积层
    BatchNormalization(),    # BN 层
    Activation('relu'),      # 激活层
    MaxPool2D(pool_size=(2, 2), strides=2, padding='same'),   # 池化层
    Dropout(0.2)        # Dropout 层, 随机舍弃 20% 的神经元
])

5.8 卷积神经网络

  • 卷积神经网络是借助卷积核对输入特征进行特征提取,再把提取特征送入全连接网络进行识别预测.
  • 提取特征包括卷积、批标准化、激活、池化四步.
  • 卷积即特征提取器. 就是 CBAPD ,C-Conv2D,B-BatchNormalization,A-Activation,M-Max/Meanpool2D,D-Dropout .

5.9 CIFAR10 数据集

  • CIFAR datasets:

( 1 ) 提供 5 万张 32 * 32 像素点的红蓝绿三通道的十分类彩色图片和标签,用于训练;

( 2 ) 提供 1 万张 32 * 32 像素点的红绿蓝三通道的十分类彩色图片和标签,用于测试.

  • 导入 CIFAR datasets:
cifar10 = tf.keras.datasets.cifar10
(x_train, y_train), (x_test, y_test) = cifar10.load_data()

5.10 卷积神经网络搭建示例

  • 用 CIFAR10 datasets 搭建一层卷积,两层全连接网络,如下图所示.

      

  • 搭建经典卷积神经网络结构
# 用 class 类搭建网络结构
class Baseline(Model):
    def __init__(self):
        # 在 __init__ 函数中,准备出搭建神经网络要用到的每一层结构
        super(Baseline, self).__init__()
        self.c1 = Conv2D(filters=6, kernel_size=(5, 5), padding='same')
        self.b1 = BatchNormalization()
        self.a1 = Activation('relu')
        self.p1 = MaxPool2D(pool_size=(2, 2), strides=2, padding='same')
        self.d1 = Dropout(0.2)
        
        self.flatten = Flatten()
        self.f1 = Dense(128, activation='relu')
        self.d2 = Dropout(0.2)
        self.f2 = Dense(10, activation='softmax')
        
    def call(self, x):
        # 在 call 函数中,调用 __init__ 函数搭建好的每层网络结构,从输入到输出经过一次前向传播,返回推理结果 y 
        x = self.c1(x)
        x = self.b1(x)
        x = self.a1(x)
        x = self.p1(x)
        x = self.d1(x)
        
        x = self.flatten(x)
        x = self.f1(x)
        x = self.d2(x)
        y = self.f2(x)
        return y
    
model = Baseline()
  • 搭建 LeNet 卷积神经网络

LeNet 卷积神经网络是 LeCun 于 1998 年提出,是卷积神经网络的开篇之作. 通过共享卷积核减少了网络的参数. LeNet 一共有五层网络,两层卷积层,三层全连接层.   

class 类定义 LeNet 卷积网络

class LeNet5(Model):
    def __init__(self):
        super(LeNet5, self).__init__()
        self.c1 = Conv2D(filters=6, kernel_size=(5, 5), activation='sigmoid')    # 卷积层 C1 
        self.p1 = MaxPool2D(pool_size=(2, 2), strides=2)
        
        self.c2 = Conv2D(filters=16, kernel_size=(5, 5), activation='sigmoid')   # 卷积层 C3
        self.p2 = MaxPool2D(pool_size=(2, 2), strides=2)
        
        self.flatten = Flatten()
        self.f1 = Dense(120, activation='sigmoid')    # 全连接层 F5
        self.f2 = Dense(84, activation='sigmoid')     # 全连接层 F6
        self.f3 = Dense(10, activation='softmax')     # 全连接层 Output
        
    def call(self, x):
        x = self.c1(x)
        x = self.p1(x)
        
        x = self.c2(x)
        x = self.p2(x)
        
        x = self.flatten(x)
        x = self.f1(x)
        x = self.f2(x)
        y = self.f3(x)
        return y
model = LeNet5()
  • 搭建 AlexNet 卷积神经网络

AlexNet 卷积神经网络是 Hinton 于 2012 年提出,使用 relu 激活函数,提升了训练速度,使用 Dropout 缓解过拟合. AlexNet 共有八层网络过,五个卷积层和三个全连接层.

           

class AlexNet8(Model):
    def __init__(self):
        super(AlexNet8, self).__init__()
        self.c1 = Conv2D(filters=96, kernel_size=(3, 3))
        self.b1 = BatchNormalization()
        self.a1 = Activation('relu')
        self.p1 = MaxPool2D(pool_size=(3, 3), strides=2)
        
        self.c2 = Conv2D(filters=256, kernel_size=(3, 3))
        self.b2 = BatchNormlization()
        self.a2 = Activation('relu')
        self.p2 = MaxPool2D(pool_size=(3, 3), strides=2)

        self.c3 = Conv2D(filters=384, kernel_size=(3, 3), padding='same', activation='relu')
        
        self.c4 = Conv2D(filters=384, kernel_size=(3, 3), padding='same', activation='relu')

        self.c5 = Conv2D(filters=256, kernel_size=(3, 3), padding='same', activation='relu')

        self.flatten = Flatten()
        self.f1 = Dense(2048, activation='relu')
        self.d1 = Dropout(0.5)
        self.f2 = Dense(2048, activation='relu')
        self.d2 = Dropout(0.5)
        self.f3 = Dense(10, activation='softmax')
  • 搭建 VGGNet 卷积神经网络

VGGNet 使用小尺寸卷积核,在减少参数的同时,提高了准确率. VGGNet 的网络结构规整,适合硬件加速 . 

网络结构框图如下:卷积核个数从 64 到 128 到 256 再到 512,逐渐增加,因为越靠后,特征图尺寸越小,通过增加卷积核的个数,增加了特征图的深度,保持了信息的承载能力.

  • 搭建 InceptionNet 卷积神经网络

InceptionNet 引入了 Inception 结构块,在同一层网络内,使用不同尺寸的卷积核,可以提取不同尺寸的特征,提升了模型感知力;使用了批标准化,缓解了梯度消失. GoogleNet 即 Inception v1,以及 InceptionNet 的后续版本 v2、v3、v4 等,都是基于 Inception 结构块搭建的网络.

通过 1*1 卷积核,作用到输入特征图的每个像素点,通过设定少于输入特征图深度的 1*1 卷积核个数,减少了输出特征图深度,起到了降维的作用,减少了参数量和计算量. 

下图为 Inception 结构块,Inception 结构块包含四个分支.  送入到卷积连接器 ( Filter Concatenation ) 的特征数据尺寸相同,卷积连接器会把收到的四路特征数据按深度方向拼接,形成 Inception 结构块的输出.

由于 Inception 结构块中的卷积均采用了 CBA 结构,所以将其定义为一个新的类 ConvBNRelu,可以减少代码长度,增加可读性. 

class ConvBNRelu(Model):
    def __init__(self, ch, kernelsz=3, strides=1, padding='same'):
        super(ConvBNRelu, self).__init__()
        self.model = tf.keras.models.Sequential([
            Conv2D(ch, kernelsz, strides=strides, padding=padding),
            BatchNormalization(),
            Activation('relu')
        ])
    def call(self, x):
        x = self.model(x)
        return x

Inception 结构块的实现:

class InceptionB1k(Model):
    def __init__(self, ch, strides=1):
        super(InceptionB1k, self).__init__()
        self.ch = ch
        self.strides = strides
        self.c1 = ConvBNRelu(ch, kernelsz=1, strides=strides)
        self.c2_1 = ConvBNRelu(ch, kernelsz=1, strides=strides)
        self.c2_2 = ConvBNRelu(ch, kernelsz=3, strides=strides)
        self.c3_1 = ConvBNRelu(ch, kernelsz=1, strides=strides)
        self.c3_2 = ConvBNRelu(ch, kernelsz=5, strides=strides)
        self.p4_1 = MaxPool2D(3, strides=1, padding='same')
        self.c4_2 = ConvBNRelu(ch, kernelsz=1, strides=strides)
    def call(self, x):
        x1 = self.c1(x)
        x2_1 = self.c2_1(x)
        x2_2 = self.c2_2(x2_1)
        x3_1 = self.c3_1(x)
        x3_2 = self.c3_2(x3_1)
        x4_1 = self.p4_1(x)
        x4_2 = self.c4_2(x4_1)
        # concat along axis = channel
        # axis = 3 指定堆叠的维度是沿深度方向
        x = tf.concat([x1, x2_2, x3_2, x4_2], axis=3)
        return x

有了 Inception 结构块之后,就可以搭建出精简版本的 InceptionNet 了,网络共有十层,如下图所示:

                                                

代码实现为:

class Inception10(Model):
    def __init__(self, num_blocks, num_classes, init_ch=16, **kwargs):
        super(Inception10, self).__init__()
        self.in_channels = init_ch
        self.out_channels = init_ch
        self.num_blocks = num_blocks
        self.init_ch = init_ch
        
        self.c1 = ConvBNRelu(init_ch)
        self.blocks = tf.keras.models.Sequential()
        for block_id in range(num_blocks):
        # 两个 Inception 结构块为一个 block
            for layer_id in range(2):
                if layer_id == 0:
                    block = InceptionB1k(self.out_channels, strides=2)
                    # block 中第一个 Inception 结构块卷积步长为 2
                    # 使得输出特征图尺寸减半
                else:
                    block = InceptionB1k(self.out_channels, strides=1)
                    # block 中第二个 Inception 结构块卷积步长为 1 
                self.blocks.add(block)
            # enlarger out_channel per block
            self.out_channels *= 2
            # 由于输出特征图尺寸减半,因此把深度加深,尽可能保证特征抽取中信息的承载量一致
        self.p1 = GlobalAveragePooling2D()
        self.f1 = Dense(num_classes, activation='softmax')
        
    def call(self, x):
        x = self.c1(x)
        x = self.blocks(x)
        x = self.p1(x)
        y = self.f1(x)
        return y
model = Inception10(num_blocks=2, num_classes=10)
# num_classes 指定网络是几分类
  • 搭建 ResNet 卷积神经网络

ResNet 提出了层间残差跳连,引入了前方信息,缓解梯度消失,使神经网络层数增加成为可能. 在探索卷积实现特征提取时,通过加深网络层数取得了越来越好的效果,但 ResNet 的作者何恺明在 CIFAR10 datasets 上通过做实验发现,一味地堆叠神经网络层数会使神经网络模型退化,以至于后边的特征丢失了前边特征的原本模样. 于是,何恺明用了一根跳连线,将前边的特征直接接到了后边,使输出结果 H ( x ) 包含了堆叠卷积的非线性输出 F ( x ) 和跳过这两层堆叠卷积直接连接过来的恒等映射 X ,让它们对应元素相加,如下图所示.  

# 注:Inception 块中的 “+” 是沿深度方向叠加 ( 类似千层蛋糕层数叠加 ),ResNet 块中的 “+” 是特征图对应元素值相加 ( 类似两个矩阵对应元素值相加 ).

这一操作有效缓解了神经网络模型堆叠导致的退化,使得神经网络可以向更深层级发展. 

如上图所示,ResNet 块中有两种情况,一种情况用图中的实线表示,这种情况,两层堆叠卷积没有改变特征图的维度,即特征图的个数、高、宽以及深度都相同,可以直接将 F(x) 与 X 相加;另一种情况用图中的虚线表示,两层堆叠卷积改变了特征图的维度,需要借助 1*1 的卷积来调整 x 的维度,使 W(x) 与 F(x) 的维度一致.

ResNet 块有两种形式,一种在堆叠卷积前后维度相同 ( 实线 ) ,另一种在堆叠卷积前后不同 ( 虚线 ),将两种结构封装为一个块,定义 ResnetBlock 类,每调用一次 ResnetBlock 类,生成ResNet 块中的一种形式.

class ResnetBlock(Model):
    def __init__(self, filters, strides=1, residual_path=False):
        super(ResnetBlock, self).__init__()
        self.filters = filters
        self.strides = strides
        self.residual_path = residual_path

        self.c1 = Conv2D(filters, (3, 3), strides=strides, padding='same', use_bias=False)
        self.b1 = BatchNormalization()
        self.a1 = Activation('relu')

        self.c2 = Conv2D(filters, (3, 3), strides=1, padding='same', use_bias=False)
        self.b2 = BatchNormalization()

        # residual_path 为 True 时,对输入进行下采样,利用 1*1 的卷积核做卷积操作,保证 X 和 F(x) 维度相同,顺利相加
        if residual_path:
            self.down_c1 = Conv2D(filters, (1, 1), strides=strides, padding='same, use_bias=False')
            self.down_b1 = BatchNormalization()

        self.a2 = Activation('relu')

    def call(self, inputs):
        residual = inputs   # residual 等于输入值本身,即 residual = x
        # 将输入通过卷积、BN 层、激活层,计算 F(x)
        x = self.c1(inputs)
        x = self.b1(x)
        x = self.a1(x)

        x = self.c2(x)
        y = self.b2(x)

        if self.residual_path:
        # 当生成前后维度不同的 ResNet 块时,residual_path = True
            residual = self.down_c1(input)
            residual = self.down_b1(residual)
        out = self.a2(y + residual)    # 最后输出的是两部分的和,即 F(x)+x 或 F(x)+W(x),再过激活函数
        return out

使用上述给出的 ResNet 块搭建 ResNet18 网络结构. ResNet18 由一层卷积、8 个 ResNet 块 ( 每块有两层卷积 )、一层全连接,共 18 层网络组成.

class ResNet18(Model):

    def __init__(self, block_list, initial_filters=64):  # block_list表示每个block有几个卷积层
        super(ResNet18, self).__init__()
        self.num_blocks = len(block_list)  # 共有几个block
        self.block_list = block_list
        self.out_filters = initial_filters

        # ResNet18 第一层是一个卷积层
        self.c1 = Conv2D(self.out_filters, (3, 3), strides=1, padding='same', use_bias=False)
        self.b1 = BatchNormalization()
        self.a1 = Activation('relu')

        self.blocks = tf.keras.models.Sequential()
        # 构建ResNet网络结构
        # 然后是 4 组 ResNet 块,每组生成两块 ResNet ,每一个 ResNet 块有两层卷积
        # 用 for 循环构建 8 个 ResNet 块,循环次数由参数列表元素个数决定
        for block_id in range(len(block_list)):  # 第几个resnet block
            for layer_id in range(block_list[block_id]):  # 第几个卷积层

                if block_id != 0 and layer_id == 0:  # 对除第一个block以外的每个block的输入进行下采样
                    block = ResnetBlock(self.out_filters, strides=2, residual_path=True)
                else:
                    block = ResnetBlock(self.out_filters, residual_path=False)
                self.blocks.add(block)  # 将构建好的block加入resnet
            self.out_filters *= 2  # 下一个block的卷积核数是上一个block的2倍

        # 平均全局池化
        self.p1 = tf.keras.layers.GlobalAveragePooling2D()
        # 最后是一层全连接
        self.f1 = tf.keras.layers.Dense(10, activation='softmax', kernel_regularizer=tf.keras.regularizers.l2())

    def call(self, inputs):
        x = self.c1(inputs)
        x = self.b1(x)
        x = self.a1(x)
        x = self.blocks(x)
        x = self.p1(x)
        y = self.f1(x)
        return y

# 这里列表赋值是 2,2,2,2 四个元素,所以最外层 for 循环执行 4 次
model = ResNet18([2, 2, 2, 2])

5.11 经典卷积网络小结

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值