课程来源:人工智能实践:Tensorflow笔记2


前言

本讲目标:讲解卷积神经网络的基本步骤以及分析比较经典的网络架构,希望对你有所帮助
经典的5个论文的下载链接:
链接:https://pan.baidu.com/s/1rIH1nh28ON6DKM6T9HPXbQ
提取码:kbd8


1、卷积神经网络的基本步骤

1、卷积神经网络计算convolution

卷积概念:

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

卷积注意点:

输入特征图的深度,决定了卷积核的深度
当前层的卷积核个数,决定了当前层的输出特征图深度
如果觉得某一层的特征提取能力不足,可以在这一层多用几个卷积核
卷积:使用立体卷积核实现了参数的空间共享
执行卷积计算时,卷积核里的参数是固定的,反向传播时会更新参数

示例:
在这里插入图片描述

2、感受野以及卷积核的选取

这个之前有思考过,见下面链接:
为什么两层33卷积核效果比1层55卷积核效果要好?

3、全零填充Padding

Tensorflow 框架中,用参数 padding = ‘SAME’或 padding = ‘VALID’表示是否进行全
零填充,其对输出特征尺寸大小的影响如下:
在这里插入图片描述
对于 5×5×1 的图像来说,
当 padding = ‘SAME’时,输出图像边长为 5;当 padding = ‘VALID’时,输出图像边长为 3。

4、tf描述卷积层

tf.keras.layers.Conv2D(
filters=卷积核个数,
kernel_size=卷积核尺寸、#正方形写核长整数,或(核高h,核宽w)
strides=滑动步长,#横纵向相同写步长整数,或(纵向步长h,横向步长w),默认1
padding ="same" or "valid", #使用全零填充是"same",不使用是"valid"(默认)
activation ="relu" or "sigmoid" or"train" or"softmax",#如果有BN此处不填
input_shape =(高,宽,通道数) #输入特征维度,可省略) 

调用方法如下:
代码解释:

1、使用6个(5,5)的卷积核卷积,不全零填充,使用sigmoid作为激活函数
2、使用(2,2)的池化核,步长为2,选用最大池化
3、使用Flatten将输出拉直成一维数组
4、使用10个神经元构成的全连接,激活函数使用softmax

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,kenrnel_size=(5,5),padding='valid',activation='sigmoid'),
    #MaxPool2D(pool_size=(2,2),strides=2),
    Flatten(),
    Dense(10,activation='softmax')
])

在利用 Tensorflow 框架构建卷积网络时,一般会利用 BatchNormalization
函数来构建 BN 层,进行批归一化操作,所以在 Conv2D 函数中经常不写 BN

5、批标准化(BN操作)

神经网络对0附近的数据更敏感
但是随着网络层数的增加,特征数据会出现偏离0均值的情况
使用标准化,将偏移的数据重新拉回
批标准化:是对一个batch的数据做标准化处理,常用在卷积操作和激活操作之间

在这里插入图片描述
第K个,batch张输出特征图:
在这里插入图片描述

BN操作,将原本偏移的特征数据,重新拉回到0均值,使进入激活函数的数据分布在激活函数线性区,使得输入数据的微小变化,更明显地体现到输出上,提升了激活函数对输入数据的区分力。
在这里插入图片描述

但是这种简单的特征数据标准化,使特征数据完全满足标准正太分布,集中在激活函数的线性区域,使激活函数丧失了非线性性质,因此在BN操作中为每个卷积核引入两个可训练参数。
反向传播时,缩放因子和偏移因子会与其他待训练参数一同呗训练优化。使标准正太分布后的特征数据,通过缩放因子和偏移因子,优化了特征数据分布的宽窄和偏移量。保证了网络的非线性表达力。
在这里插入图片描述

BN层位于卷积层与激活层之间
tf提供了BN操作的函数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层
])

6、池化Pooling

池化用于减少特征数据量
最大值池化可以提取图片纹理,均值池化可以保留背景特征
如果用2 * 2的池化核对输入图片进行步长为2的池化操作,输出图片将变为输入图片的四分之一大小
tf描述池化操作:

tf.keras.layers.MaxPool2D(
pool_size=池化核尺寸,
strides=池化步长,
padding='valid'or'same'#same全零填充,valid不全零填充、
)
tf.keras.layers.AveragePooling2D(
pool_size=池化核尺寸,
strides=池化步长,
padding='valid'or'same'#same全零填充,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层
])

7、舍弃Dropout

为了缓解神经网络过拟合,在神经网络训练中常常把隐藏层的部分神经元按照一定比例从神经网络中临时舍弃,
在使用神经网络时,再把所有神经元恢复到神经网络中。
在这里插入图片描述

Dropout函数:

tf.keras.layers.Dropout(舍弃的概率)

8、卷积神经网络搭建以及参数分析

卷积神经网络:借助卷积核提取特征后,送入全连接网络
卷积神经网络的主要模块:
卷积(conv)
批标准化(BN)
激活(Activation)
池化(Pooling)
卷积是什么:卷积就是特征提取器,就是CBAPD

在这里我们仍然套用网络八股的构架:

1、import引入tensorflow及keras、numpy等所需模块
2、读取数据集
3、搭建所需的网络结构,当网络结构比较简单时,可以利用keras模块中的tf.keras.Sequential来搭建顺序网络模型;但是当网络不再是简单的顺序结构,而是有其他特殊的结构出现(如ResNet中的跳连结构),急需要利用class来定义自己的网络结构
4、对搭建好的网络进行编译(compile),通常在这一步指定所采用的优化器(Adam、SGD、RMSdrop)以及损失函数(交叉熵函数、均方差函数等)
5、将数据输入编译好的网络来训练(model.fit),在这一步中指定训练轮数epochs以及batch_size等信息。由于参数量和计算量一般都比较大,训练所需的时间也会比较长,这一步中通常会加入断点续训以及模型参数保存
6、将神经网络模型具体信息打印出来(model.summary),包括网络结构、网络各层参数

这里还要讲一下flatten函数:

Flatten层用来将输入“压平”,即把多维的输入一维化,常用在从卷积层到全连接层的过渡。Flatten不影响batch的大小。

接下来我们使用类搭建5x5的卷积核,6个,池化核2x2,步长2的网络结构:

#使用类搭建网络结构,CBAPD
class Baseline(Model):
    def _init_(self):
        super(Baseline,self)._init_()
        self.c1 =Conv2D(filters=6,kernel_size=(5,5),padding='same')
        self.b1 =BatchNormalization()
        self.a1 =Activaction('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')
#call函数调用init函数中搭建好的每层网络结构
def call(self,x):
    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
#从输入到输出过一次前向传播,返回推理结果

我们来分析一下参数:

打开weights文件:
baseline/conv2d/kernel:0
第一层网络 5x5x3的卷积核 一共6个
(5, 5, 3, 6)=>450
baseline/conv2d/bias:0
每个卷积核的偏置项b
(6,)=>6
baseline/batch_normalization/gamma:0
BN操作中的缩放因子γ,每个卷积核一个γ
(6,)=>6
baseline/batch_normalization/beta:0
BN操作中的偏移因子γ,每个卷积核一个γ
(6,)=>6
baseline/dense/kernel:0
第一层全连接网络
(1536, 128)=>196608
baseline/dense/bias:0
第一层全连接网络128个偏置b
(128,)=>128
baseline/dense_1/kernel:0
第二层全连接网络
(128, 10)=>1280
baseline/dense_1/bias:0
第二层全连接网络10个偏置b
(10,)=>10
总共:450+6+6+6+196608+128+1280+10=198494
有了这些参数就可以复现出神经网络的前向传播实现应用
可以发现,神经网络的网络参数十分多,多大十几万甚至更多(当网络复杂层数增加),并且可以发现绝大部分参数都集中在全连接层上,卷积层的参数占比较小。然而卷积核的参数却是非常重要的(因为卷积是特征提取器,特征的参数才是图片识别的重点)。所以减少全连接网络的参数或许会是一个不错的网络优化方法。

至此,基本的神经网络的搭建方法已经讲解完毕。接下来会逐一讲解LeNet、AlexNet、VGGNet、InceptionNet 和 ResNet的特点,并且用基于tensorflow的代码复现出网络架构。

2、经典卷积网络讲解

1、LeNet

网络结构:
在这里插入图片描述
tf复现模型:
这里对原模型进行调整,输入图片大小修改为32 * 32 * 3,用来适应数据集cifar10,并且将大的卷积核用小卷积核来替代
在这里插入图片描述

class LeNet5(Model):
	def __init__(self):
		super(LeNet5,self).__init__()
		self.c1=Conv2D(filters=6,kernel_size=(5,5),padding='valid',input_shape=(32,32,3),activation='sigmoid')
		self.p1=MaxPool2D(pool_size=(2,2),strides=2)
		
		self.c2=Conv2D(filters=16,kernel_size=(5,5),padding='valid',activation='sigmoid')
		self.p2=MaxPool2D(pool_size=(2,2),strides=2)
		
		self.flatten =Flatten()
		self.f1 =Dense(120,activation='sigmoid')
		self.f1 =Dense(84,activation='sigmoid')
		self.f1 =Dense(10,activation='softmax')
    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()

优点:共享卷积核,减少网络参数.
如何理解卷积神经网络中的权值共享?

2、AlexNet

网络结构:
在这里插入图片描述
当时由于显存不足,训练分成两部分完成。这里对原模型进行调整,输入图片大小修改为32 * 32 * 3,用来适应数据集cifar10,并且将大的卷积核用小卷积核来替代。
在这里插入图片描述

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=BatchNormalization()
		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.p3=MaxPool2D(pool_size=(3,3),strides=2)
		
		self.flatten =Flatten()
		self.f1 =Dense(2048,activation='relu')
		self.d1 =Dropout(0.5)
		self.f1 =Dense(2048,activation='relu')
		self.d1 =Dropout(0.5)
		self.f1 =Dense(10,activation='softmax')
def call(self, x):
        x = self.c1(x)
        x = self.b1(x)
        x = self.a1(x)
        x = self.p1(x)

        x = self.c2(x)
        x = self.b2(x)
        x = self.a2(x)
        x = self.p2(x)

        x = self.c3(x)

        x = self.c4(x)

        x = self.c5(x)
        x = self.p3(x)

        x = self.flatten(x)
        x = self.f1(x)
        x = self.d1(x)
        x = self.f2(x)
        x = self.d2(x)
        y = self.f3(x)
        return y


model = AlexNet8()

优点:激活函数使用 Relu,提升训练速度;Dropout 防止过拟合

3、VGGNet

网络结构:
在这里插入图片描述
为适应 cifar10 数据集,将输入图像尺寸由 224 * 244 * 3 调整为 32 * 32 * 3
在这里插入图片描述

class VGG16(Model):
    def __init__(self):
        super(VGG16, self).__init__()
        self.c1 = Conv2D(filters=64, kernel_size=(3, 3), padding='same')  # 卷积层1
        self.b1 = BatchNormalization()  # BN层1
        self.a1 = Activation('relu')  # 激活层1
        self.c2 = Conv2D(filters=64, kernel_size=(3, 3), padding='same', )
        self.b2 = BatchNormalization()  # BN层1
        self.a2 = Activation('relu')  # 激活层1
        self.p1 = MaxPool2D(pool_size=(2, 2), strides=2, padding='same')
        self.d1 = Dropout(0.2)  # dropout层

        self.c3 = Conv2D(filters=128, kernel_size=(3, 3), padding='same')
        self.b3 = BatchNormalization()  # BN层1
        self.a3 = Activation('relu')  # 激活层1
        self.c4 = Conv2D(filters=128, kernel_size=(3, 3), padding='same')
        self.b4 = BatchNormalization()  # BN层1
        self.a4 = Activation('relu')  # 激活层1
        self.p2 = MaxPool2D(pool_size=(2, 2), strides=2, padding='same')
        self.d2 = Dropout(0.2)  # dropout层

        self.c5 = Conv2D(filters=256, kernel_size=(3, 3), padding='same')
        self.b5 = BatchNormalization()  # BN层1
        self.a5 = Activation('relu')  # 激活层1
        self.c6 = Conv2D(filters=256, kernel_size=(3, 3), padding='same')
        self.b6 = BatchNormalization()  # BN层1
        self.a6 = Activation('relu')  # 激活层1
        self.c7 = Conv2D(filters=256, kernel_size=(3, 3), padding='same')
        self.b7 = BatchNormalization()
        self.a7 = Activation('relu')
        self.p3 = MaxPool2D(pool_size=(2, 2), strides=2, padding='same')
        self.d3 = Dropout(0.2)

        self.c8 = Conv2D(filters=512, kernel_size=(3, 3), padding='same')
        self.b8 = BatchNormalization()  # BN层1
        self.a8 = Activation('relu')  # 激活层1
        self.c9 = Conv2D(filters=512, kernel_size=(3, 3), padding='same')
        self.b9 = BatchNormalization()  # BN层1
        self.a9 = Activation('relu')  # 激活层1
        self.c10 = Conv2D(filters=512, kernel_size=(3, 3), padding='same')
        self.b10 = BatchNormalization()
        self.a10 = Activation('relu')
        self.p4 = MaxPool2D(pool_size=(2, 2), strides=2, padding='same')
        self.d4 = Dropout(0.2)

        self.c11 = Conv2D(filters=512, kernel_size=(3, 3), padding='same')
        self.b11 = BatchNormalization()  # BN层1
        self.a11 = Activation('relu')  # 激活层1
        self.c12 = Conv2D(filters=512, kernel_size=(3, 3), padding='same')
        self.b12 = BatchNormalization()  # BN层1
        self.a12 = Activation('relu')  # 激活层1
        self.c13 = Conv2D(filters=512, kernel_size=(3, 3), padding='same')
        self.b13 = BatchNormalization()
        self.a13 = Activation('relu')
        self.p5 = MaxPool2D(pool_size=(2, 2), strides=2, padding='same')
        self.d5 = Dropout(0.2)

        self.flatten = Flatten()
        self.f1 = Dense(512, activation='relu')
        self.d6 = Dropout(0.2)
        self.f2 = Dense(512, activation='relu')
        self.d7 = Dropout(0.2)
        self.f3 = Dense(10, activation='softmax')

    def call(self, x):
        x = self.c1(x)
        x = self.b1(x)
        x = self.a1(x)
        x = self.c2(x)
        x = self.b2(x)
        x = self.a2(x)
        x = self.p1(x)
        x = self.d1(x)

        x = self.c3(x)
        x = self.b3(x)
        x = self.a3(x)
        x = self.c4(x)
        x = self.b4(x)
        x = self.a4(x)
        x = self.p2(x)
        x = self.d2(x)

        x = self.c5(x)
        x = self.b5(x)
        x = self.a5(x)
        x = self.c6(x)
        x = self.b6(x)
        x = self.a6(x)
        x = self.c7(x)
        x = self.b7(x)
        x = self.a7(x)
        x = self.p3(x)
        x = self.d3(x)

        x = self.c8(x)
        x = self.b8(x)
        x = self.a8(x)
        x = self.c9(x)
        x = self.b9(x)
        x = self.a9(x)
        x = self.c10(x)
        x = self.b10(x)
        x = self.a10(x)
        x = self.p4(x)
        x = self.d4(x)

        x = self.c11(x)
        x = self.b11(x)
        x = self.a11(x)
        x = self.c12(x)
        x = self.b12(x)
        x = self.a12(x)
        x = self.c13(x)
        x = self.b13(x)
        x = self.a13(x)
        x = self.p5(x)
        x = self.d5(x)

        x = self.flatten(x)
        x = self.f1(x)
        x = self.d6(x)
        x = self.f2(x)
        x = self.d7(x)
        y = self.f3(x)
        return y


model = VGG16()

总体来看,VGGNet的结构是相当规整的,它继承了 AlexNet中的Relu激活函数、Dropout操作等有效的方法,同时采用了单一尺寸的 3 * 3 小卷积核,形成了规整的 C(Convolution,卷积)、B(Batch normalization)、A(Activation,激活)、P(Pooling,池化)、D(Dropout)结构,这一典型结构在卷积神经网络中的应用是非常广的
优点:小卷积核减少参数的同时,提高识别准确率;网络结构规整,适合并行加速。

4、InceptionNet

优点:一层内使用不同尺寸的卷积核,提升感知力(通过 padding 实现输出特征面积一致);使用 1 * 1 卷积核,改变输出特征 channel 数(减少网络参数)。
基本单元:
在这里插入图片描述

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,training=None):
		x=self.model(x,training=training)
		return x
#ch 特征图的通道数,即卷积核个数
class InceptionBlk(Model)def __init__(self,ch,strides=1):
		super(InceptionBlk,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=1)
		self.c3_1=ConvBNRelu(ch,kernelsz=1,strides=strides)
		self.c3_2=ConvBNRelu(ch,kernelsz=5,strides=1)
		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(c3_1)
		x4_1=self.p4_1(x)
		x4_2=self.c4_2(x4_1)
		#concat along axis=channel
		x=tf.concat([x1,x2_2,x3_2,x4_2],axis=3)
		return x

其实基本单元一开始是长这样的,通过不同尺寸的卷积层和池化层的横向组合(卷积和池化后的尺寸相同,便于叠加)来拓宽网络深度,增强网络对尺寸的适应性。但由于卷积核都是在上一层的输出上直接计算的,导致参数变多以及运算变得复杂,所以加入1 * 1卷积核,减少特征厚度。
以5 * 5的卷积运算为例说明这个
问题。假设网络上一层的输出为 100 * 100 * 128(H * W * C),通过 32 * 5 * 5(32 个大小
为 5 * 5 的卷积核)的卷积层(步长为 1、全零填充)后,输出为 100 * 100 * 32,卷积层的
参数量为 32 * 5 * 5 * 128 = 102400;如果先通过 32 * 1 * 1 的卷积层(输出为 100 * 100 * 32),
再通过 32 * 5 * 5 的卷积层,输出仍为 100 * 100 * 32,但卷积层的参数量变为 32 * 1 * 1 * 128

  • 32 * 5 * 5 * 32 = 29696,仅为原参数量的 30 %左右,这就是小卷积核的降维作用。
    在这里插入图片描述
    由基本模块组成网络构架如下:
    在这里插入图片描述
    代码描述:
class Inception10(Model):
			def __init__(self,num_blocks,num_classes,init_ch=16,**kwargs):
				super(Inception10,self).__init__(**kwargs)
				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):
					for layer_id in range (2):
						if layer_id==0:
							block=InceptionBlk(self.out_channels,strides=2)
						else:
							block=InceptionBlk(self.out_channels,strides=1)
						self.blocks.add(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_blocks代表InceptionNet的Block数,每个Block由两个基本单元构成,每经过一个
Block,特征图尺寸变为1/2,通道数变为原来的两倍; num_classes代表分类数
init_ch代表初始通道数,代表InceptionNet基本单元的初始卷积核个数

InceptionNet采用"全局平均池化+全连接层",VGGNet(有三层全连接层)
平均池化:在特征图上以窗口的形式滑动,取窗口内的平均值为采样值
全局平均池化:直接针对特征图取平均值,每一个特征图输出一个值,通过这种方式,每个特征图都与分类概率直接联系起来替代了全连接层的功能,并且不会产生额外的训练参数,减少了过拟合的可能,但会导致网络收敛速度变慢。
InceptionNet采用多尺寸卷积再聚合的方式拓宽了网络结构,并通过 1 * 1卷积运算来减少参数量。

5、ResNet

优点:层间残差跳连,引入前方信息,减少梯度消失,使神经网络层数变深成为可能。
已知:对于一个深度比较合适的网络来说,继续增加层数反而会导致训练错误率的提升:
在这里插入图片描述
ResNet核心思路为:对一个准确率达到饱和的浅层网络,在它后面加几个恒等映射层(即 y = x,输出等于输入),增加网络深度的同时不增加误差。这使得神经网络的层数可以超越之前的约束,提高准确率。
这种残差结构的示意图如下:
在这里插入图片描述
注意,这里的相加与 InceptionNet 中的相加是有本质区别的,Inception 中的相加是沿深度方向叠加,像“千层蛋糕”一样,对层数进行叠加;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为Ture时,对输入进行下采样,用1x1的卷积核卷积,保证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:
			residual=self.down_c1(inputs)
			residual=self.down_b1(residual)
		out = self.a2(y+residual)  #最后输出两部分的和,F(x)+x或者F(x)+Wx,再过激活函数
		return out

ResNet18网络结构以及其利用tf构建模型示意:

图1
图2
class ResNet(Model):
	def __init__(self,block_list,initial_filters=64):		#block_list表示每个block有几个卷积层
		super(ResNet,self).__init__()
		self.num_blocks =len(block_list)
		self.block_list =block_list
		self.out_filters = initial_filters
		self.c1 = Conv2D(self.out_filters,(3,3),strides=1,padding='same',use_bias=False,kernel_initialize='he_normal')
		self.b1 = tf.keras.layers.BatchNormalization()
		self.a1 = Activation('relu')
		self.blocks = tf.keras.models.Sequential()
		#构建ResNet网络结构
		for block_id in range(len(block_list)):			#第几个resnet_block
			for layer_id in range(block_list(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)
	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)		#由于使用了GAP,所以不需要dropout操作(不再是黑箱操作了)
		return y

三层残差单元用于构建更深的网络:
在这里插入图片描述

6、经典卷积网络总结

在这里插入图片描述

总结

课程链接:MOOC人工智能实践:TensorFlow笔记2
参考以及知识点补充链接:
卷积神经网络—AlexNet、VGG、GoogleNet、ResNet论文解读
CNN浅析和历年ImageNet冠军模型解析