神经网络模型层数的加深会在训练过程中出现梯度弥散和梯度爆炸现象,网络层越深,该现象越容易出现,也越严重。
Skip Connection机制
由于浅层神经网络不容易出现这些梯度现象,那么通过在输入和输出之间添加一条直接连接的 Skip Connection 可以让神经网络具有回退的能力。
以VGG13深度神经网络为例,假设在10层没有观测到梯度弥散现象,那么可考虑在最后的两个卷积层中添加Skip Connection,使模型能自动选择是经过这两个卷积层完成特征变换,还是跳过这两个卷积层直接输出,或者也可以结合两个卷积层和 Skip Connection 的输出。
那么对于输入x来说,要和经过两个卷积层后的f(x)上相应元素的相加,才能够计算出最后的输出H(x)。为了能够满足上述关系,x要和f(x)的shape保持一致,因此x要经过identify这个步骤,即经过一定的卷积运算使得和f(x)的shape相一致,其具体抽象为下图:
残差模块
根据tensorflow里keras中的layer类,可以基于layer类重新定义个关于上述过程的类,以方便后期经常调用。其实现方式如下:
class BasicBlock(layers.Layer):
def __init__(self, filter_num, stride=1):
super(BasicBlock, self).__init__()
# f(x)包含了2个普通卷积层,创建卷积层1
self.conv1 = layers.Conv2D(filter_num, (3, 3), strides=stride, padding='same')
self.bn1 = layers.BatchNormalization()
self.relu = layers.Activation('relu')
# 创建卷积层2
self.conv2 = layers.Conv2D(filter_num, (3, 3), strides=1, padding='same')
self.bn2 = layers.BatchNormalization()
if stride != 1: # 插入identity层
self.downsample = Sequential()
self.downsample.add(layers.Conv2D(filter_num, (1, 1), strides=stride))
else: # 否则,直接连接
self.downsample = lambda x:x
def call(self, inputs, training=None):
# 前向传播函数
out = self.conv1(inputs) # 通过第一个卷积层
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out) # 通过第二个卷积层
out = self.bn2(out)
# 输入通过identity()转换
identity = self.downsample(inputs)
# f(x)+x运算
output = layers.add([out, identity])
# 再通过激活函数并返回
output = tf.nn.relu(output)
return output
ReNet
标准的 ResNet18 接受输入为224 × 224 大小的图片,为降低计算代价,使电脑能运行起来,故将 ResNet18 进行适量调整,使输入大小为32 × 32,输出维度为 10,调整后的 ResNet18 网络结构如下。
完整代码:
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras