深度学习
依依旧是跟着曹健老师的课程学习,这一节到了大名鼎鼎的卷积神经网络,给出了各种经典卷积神经网络模型代码,非常值得学习,不过对于几个经典卷积神经网络只是模型结构的介绍,对其理论原理没有进行深入
5. 卷积神经网络
5.1 卷积计算
实际项目的图片多是高分辨率的彩色图片,待优化参数过多,容易导致模型过拟合,为了减少待训练参数,我们在实际应用中会对原始图片进行特征提取,再把提取到的特征送给全连接网络
卷积计算是一种有效的特征提取方法
如果输入特征是单通道灰度图可以使用深度为1的单通道卷积核;如果输入特征是三通道彩色图,可以使用333的卷积核或者553的卷积核,总之卷积核的深度一定要和输入特征的深度(通道数)一致
卷积就是利用立体卷积核实现了参数的空间共享
卷积计算过程
- 单通道图
如下面一个551的单通道图像,选用了3*3的卷积核,滑动步长是1,每滑动一步输入特征图与卷卷积核中的9个元素重合,它们对应元素相乘再加上偏置项b
红绿蓝三层分别与卷积核的三层数据重合,它们对应元素相乘求和再加上偏置项b(红绿蓝三层的都计算上)到的输出中的一个特征值
当输入遍历完得到一个输出特征图,完成一个卷积计算过程,当有n个卷积核就会有n个输出特征图
5.2 感受野
第二个图的一个小方块是由原图33的大方块映射到的,所以其感受野为3,第三张图的一张小方块是由原图55的映射到的,所以它的感受野为5
这个感受野也是5
第一个图经过两个33的卷积核作用
第二个图直接经过一个55的卷积核的作用,但是最终效果时一样的
但是计算量上不一样,3*3卷积核的计算量更小
5.3 全零填充padding
有时候我们希望卷积计算保持输入特征图的尺寸不变,可以使用全零填充,在输入特征图周围填充0
如下面这个551的输入特征图经过全零填充后再通过331的卷积核进行步长为1的卷积计算,输出特征图仍是551
卷积输出特征图维度的计算公式,即计算输出特征图的变长
在TF中使用全零填充使用参数padding='SAME'
(全零填充)或padding='VALID'
(不全零填充)表示
5.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 'tanh' or 'softmax'等#如有卷积后还有批标准化操作,不在这里进行激活,不写激活函数
input_shape=(高,宽,通道数)#输入特征维度,可以忽略
黄框括起来的是几种写法,推荐第三种传递参数哦的方式,代码可读性更强
5.5 批标准化(Batch Normalization,BN)
神经网络对0附近的数据更敏感,但是随着网络层数的增加,特征数据会出现便宜0均值的情况,标准化可以使数据符合以0为均值
- 标准化:使数据符合0均值,1为标准差的分布,把偏移的数据重新拉回0附近
- 批标准化:对一小批数据(batch),做标准化处理
批标准化(BN)常用在卷积操作和激活操作之间,可以通过下面的式子计算批标准化后的输出特征
其中
通过批标准化操作,数据重新分布到了激活函数的线性区,使得输入数据的微小变化,更明显的体现到激活函数的输出,提升了激活函数对数据数据的区分力
但是这种简单的数据标准化,使得数据完全满足标准正态分布,集中在激活函数的线性区域,使得激活函数丧失了非线性特性,因此在BN操作中为每个卷积核引入了两个可训练参数
反向传播时候引入的两个参数和其他参数一样会被训练优化,优化特征数据分布的宽窄和偏移量
BN层位于卷积层之后,激活层之前
- TF 描述批标准化
tf.keras.layers.BatchNormalization()
例如
5.6 池化Pooling
池化操作用于减少神经网络中特征数据量,池化的主要方法有
- 最大池化:可用于提取图片纹理
- 均值池化:可保留背景特征
TF描述池化 - 最大池化tf.keras.layers.MaxPool2D
- 均值池化tf.keras.layers.AveragePooling2d
5.7 舍弃Dropout
为了缓解神经网络过拟合,在神经网络训练时候,将隐藏层一部分神经元按照一定的概率从神经网络中暂时舍弃,神经网络使用时,被舍弃的神经元恢复链接
- TF描述池化
tf.keras.layer.Dropout(舍弃的概率)
5.8 卷积神经网络
卷积神经网络:借助卷积核提取特征后,送入全连接网络
卷积神经网络的主要模块
卷积是什么?即编写卷积神经网络的八股套路
卷积就是特征提取器,就是CBAPD
5.9 CIFAR10数据集
共有5万张训练集和1万张测试集的10类数据,对应标签0-9
- 导入cifar10数据集
import tensorflow as tf
from matplotlib import pyplot as plt
import numpy as np
np.set_printoptions(threshold=np.inf)
cifar10 = tf.keras.datasets.cifar10
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
# 可视化训练集输入特征的第一个元素
plt.imshow(x_train[0]) # 绘制图片
plt.show()
# 打印出训练集输入特征的第一个元素
print("x_train[0]:\n", x_train[0])
# 打印出训练集标签的第一个元素
print("y_train[0]:\n", y_train[0])
# 打印出整个训练集输入特征形状
print("x_train.shape:\n", x_train.shape)
# 打印出整个训练集标签的形状
print("y_train.shape:\n", y_train.shape)
# 打印出整个测试集输入特征的形状
print("x_test.shape:\n", x_test.shape)
# 打印出整个测试集标签的形状
print("y_test.shape:\n", y_test.shape)
得到的x_train[0]是32行32列的RGB三通道数据,y_train[0]是数值6代表青蛙
5.10 卷积神经网络搭建示例
通过卷积神经网络来训练cifar10数据集,搭建一个一层卷积,两层全连接的网络
其中卷积层包括
- 5*5的卷积核,步长为6
- 2*2的池化核,步长为2
搭建神经网络诀窍CBAPD
带入一下得到
由于网络相对复杂,此处采用class类搭建网络结构
使用六步法可以写出相应代码,最重要的就是第三部分
##1 导入相关包
import tensorflow as tf
import os
import numpy as np
from matplotlib import pyplot as plt
from tensorflow.keras.layers import Conv2D, BatchNormalization, Activation, MaxPool2D, Dropout, Flatten, Dense
from tensorflow.keras import Model
np.set_printoptions(threshold=np.inf)
## 2.划分训练集和测试集
cifar10=tf.keras.datasets.cifar10
(x_train,y_train),(x_test,y_test)=cifar10.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
## 3.定义神经网络模型
class Baseline(Model):
def __init__(self):
super(Baseline, self).__init__()
self.c1 = Conv2D(filters=6, kernel_size=(5, 5), padding='same') # 卷积层
self.b1 = BatchNormalization() # BN层
self.a1 = Activation('relu') # 激活层
self.p1 = MaxPool2D(pool_size=(2, 2), strides=2, padding='same') # 池化层
self.d1 = Dropout(0.2) # dropout层
self.flatten = Flatten()
self.f1 = Dense(128, activation='relu')
self.d2 = Dropout(0.2)
self.f2 = 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.d1(x)
x = self.flatten(x)
x = self.f1(x)
x = self.d2(x)
y = self.f2(x)
return y
model=Baseline()
## 4.设置神经网络参数
model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
metrics=['sparse_categorical_accuracy'])
##设置断点保存
checkpoint_save_path='./checkpoint/Baseline.ckpt'
if os.path.exists(checkpoint_save_path+'.index'):
print('----------------load the model------------------')
model.load_weights(checkpoint_save_path)
#使用回调函数进行断点续存
cp_callback=tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_save_path,
save_best_only=True,
save_weights_only=True)
##5.训练模型
history = model.fit(x_train, y_train, batch_size=32, epochs=5, validation_data=(x_test, y_test), validation_freq=1,
callbacks=[cp_callback])
##6.打印结果
model.summary()
#保存参数
# print(model.trainable_variables)
file = open('./weights.txt', 'w')
for v in model.trainable_variables:
file.write(str(v.name) + '\n')
file.write(str(v.shape) + '\n')
file.write(str(v.numpy()) + '\n')
file.close()
#结果可视化
# 显示训练集和验证集的acc和loss曲线
acc = history.history['sparse_categorical_accuracy']
val_acc = history.history['val_sparse_categorical_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
plt.subplot(1, 2, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.title('Training and Validation Accuracy')
plt.legend()
plt.subplot(1, 2, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.title('Training and Validation Loss')
plt.legend()
plt.show()
5.11 经典卷积网络
5.11.1 LeNet
LeNet由Yann LeCun提出是卷积网络的开篇之作,通过共享卷积核减少了网络的参数
在统计卷积网络层数时候,一般只统计卷积计算层和全连接层
LeNet一共有五层网络:卷积(CBAPD)-卷积(CBAPD)-全连接-全连接-全连接
LeNet时期主流激活函数为sigmoid,没有dropout,没有全零填充
对应代码为:
class LeNet5(Model):
def __init__(self):
super(LeNet5, self).__init__()
self.c1 = Conv2D(filters=6, kernel_size=(5, 5),
activation='sigmoid')
self.p1 = MaxPool2D(pool_size=(2, 2), strides=2)
self.c2 = Conv2D(filters=16, kernel_size=(5, 5),
activation='sigmoid')
self.p2 = MaxPool2D(pool_size=(2, 2), strides=2)
self.flatten = Flatten()
self.f1 = Dense(120, activation='sigmoid')
self.f2 = Dense(84, activation='sigmoid')
self.f3 = 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
5.11.2 AlexNet
AlexNet诞生于2012年,是Hinton的代表作之一,也是当年ImageNet竞赛冠军
- 使用relu激活函数提高了计算效率
- 使用dropout缓解了过拟合
AlexNet共有八层
卷积层(最初始的用的LRN操作,但是现在已经被主流BN操作所替代)-卷积层-卷积层-卷积层-卷积层-全连接-全连接-全连接
其中第三层和第四层完全一致
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.f2 = Dense(2048, activation='relu')
self.d2 = Dropout(0.5)
self.f3 = 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()
5.11.3VGGNet
VGGNet诞生于2014年,是当年ImageNet竞赛的亚军,Top5错误率减少到7.3%,
VGGNet使用小尺寸卷积核,在减少参数的同时,提高了识别准确率
VGGNet的网络结构规整,非常适合硬件加速
具体结构为两次CBA-CBAPD三次CBA-CBA-CBAPD三个全连接层,共计十六层
- CAB
- CBAPD
- CBA
- CBAPD
- CBA
- CBA
- CBAPD
- CBA
- CBA
- CBAPD
- CBAPD
- CBA
- CBA
- CBAPD
- 全连接层
- 全连接层
- 全连接层
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()
5.11.4 Inception Net
Inception Net也诞生于2014年,是当年ImageNet竞赛冠军,TOP5错误率为6.67%
Inception Net引入了Inception结构块
- 在同一层网络内使用不同尺寸的卷积核,可以提取不同尺寸的特征,提升了模型感知力
- 使用批标准化,缓解了梯度消失
Inception Net的核心是它的基本单元Inception 结构块无论是GoogLeNet也就是Inception v1及其后续版本v2,v3,v4都是基于Inception结构块搭建的
通过11卷积核作用到图中每个像素点,通过设定少于输入特征图深度的11卷积核个数减少了输出特诊图深度,起到了降维
作用,减少了参数量和计算量
下面便是一个Inception结构块,Inception结构块包含四个分支
- 经过1*1卷积核输出到卷积连接器
- 经过11卷积核配合33卷积核输出到卷积连接器
- 经过11卷积核配合55卷积核输出到卷积连接器
- 经过33最大池化核配合11卷积核输出到卷积连接器
送到卷积连接器的特征数据尺寸相同,卷积连接器会把收到的这四路特征按深度方向拼接形成Inception结构块的输出
Inception结构块的CBAPD描述为
由于Inception结构块都采用了CBA结构即先卷积在BN再relu激活函数所以可以将其定义为一个新的类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, training=False) #在training=False时,BN通过整个训练集计算均值、方差去做批归一化,training=True时,通过当前batch的均值、方差去做批归一化。推理时 training=False效果好
return x
整体过程如下
其中c1为第一分支,c2_1,c2_2为第二分支。。。。。
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(x3_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
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)
# enlarger out_channels 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
#num_classes用于指定是几分类的
model = Inception10(num_blocks=4, num_classes=10)
5.11 ResNet2015
ResNet诞生于2015年是当年ImageNet竞赛冠军,ResNet提出了层间残差跳连,引入了前方信息,缓解了梯度消失,使得神经网络层数增加成为可能
- 可以发现网络层数加深提高识别准确率,但是单纯的堆叠网络层数会使得神经网络模型退化以至于后边的特征丢失了前边特征的原本模样
Resnet通过一根跳连线,使得前面层的特征直接跟后面层相连
下图中的X跳过了两层,直接把信息给了我第三层
resnet的两种结构,一种结构堆叠卷积前后维度相同,一种堆叠卷积前后维度不同,将这两种结构封装到下面橙色块中形成ResentBloack类
每调用一次ResNet类会生成一个黄色块 - 如果堆叠卷积前后维度不同,residual_path=1,调用红色块中的代码使用1*1卷积操作,调整输入特征图inputs的尺寸或深度后将堆叠卷积输出特征y和if预计计算出的residual相加或激活输出
- 如果堆叠卷积层前后维度相同,不执行红色块内的代码,直接将堆叠卷积层输出特征y和输入特征图inputs相加过激活输出
ResNet18用CBAPD表示的结构
结构:卷积层-八个ResNet块-全连接,每一个ResNet块有两层卷积,一共是是18层网络
循环此处是由参数次数决定的,这里列表赋值为2,2,2,2,所以最外层for循环执行4次,每次进来根据当前是第几个元素选择residual_path=True用虚线连接和residual_path=False用实线连接,调用ResnetBlock生产左边ResNet18结构中的一个橙色块,经过平均全局池化核全连接得到输出结果
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时,对输入进行下采样,即用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
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
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网络结构
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
model = ResNet18([2, 2, 2, 2])