残差网络缓解了在深度神经网络中增加深度带来的梯度消失问题,使在实际效果中加深神经网络得到更好的训练结果理论成真。其主要由输入部分,多个残差块,全连接部分网络组成。
残差块即是指在计算时可以通过跳过某几层直接向后传到输出部分的几层网络组成,简单来说就是跳步进行权重的优化,具体形式如下图
残差块使得本来需要顺序执行的网络模式变得可以跳跃着执行,加速了训练的同时,使深层的神经网络在实际效果上更好,但使用残差块的时候仍有许多需要注意的地方,放在后面代码中说。
上干货,本次使用的数据集为Fashion_mnist,有10个种类的服装,像素大小为(28x28),有60000个数据。
首先需要通过自定义网络来创建残差块实现自己的layers.Layer
class Baisblock(layers.Layer):
def __init__(self,filter_num,stride=1):
super(Baisblock,self).__init__()
self.conv1 = layers.Conv2D(filter_num,(3,3),strides=stride,padding='same')
# 残差块的第一个卷积层
self.bn1 = layers.BatchNormalization()
# 将卷积层输出的数据批量归一化
self.relu = layers.ReLU()
# 归一化后进行线性计算
self.conv2 = layers.Conv2D(filter_num,(3,3),strides=stride,padding='same')
# 残差块的第二个卷积层
self.bn2 = layers.BatchNormalization()
# 将卷积层输出的数据批量归一化
if stride != 1:
self.downsample = Sequential()
self.downsample.add(layers.Conv2D(filter_num,(1,1),strides=stride*2))
else:
self.downsample = lambda x:x
#这里涉及到了不同残差块中步长不同的问题
def call(self, inputs, training= None):
out = self.conv1(inputs)
out = self.bn1(out,training=training)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out , training=training)
identity = self.downsample(inputs)
output = layers.add([out,identity])
output = tf.nn.relu(output)
return output
如代码中所说,这里遇到残差块通过接近跳步时,输入和输出的维度可能会不匹配的问题,所以当我们的步长不为1的时候我们需要使用self.downsample
来下采样,利用一个1*1的卷积核使得我们输入的数据维度匹配我们的输出维度self.downsample.add(layers.Conv2D(filter_num,(1,1),strides=stride*2))
。
配置好了残差块,接下来就来实现残差网络的整体网络结构,同样也是实现自己的keras.Model
class ResNet (keras.Model):
def __init__(self,layer_dims,num_classes=100):
super(ResNet,self).__init__()
self.getdata = keras.Sequential([
layers.Conv2D(input_shape=(28,28,1),filters=64,kernel_size=(3,3),strides=(1,1)),
layers.BatchNormalization(),
layers.Activation('relu'),
layers.MaxPool2D(pool_size=(2,2),strides=(1,1),padding='same')
])
#一个由卷积层、批量归一化层、激活层和池化层组成的数据导入部分
self.layer1 = self.build_resblok(64,layer_dims[0])
self.layer2 = self.build_resblok(128, layer_dims[1],stride=2)
self.layer3 = self.build_resblok(256, layer_dims[2],stride=2)
self.layer4 = self.build_resblok(512, layer_dims[3],stride=2)
#四个残差块组成的残差部分
self.avgpool = layers.GlobalAvgPool2D()
#全局平均化
self.fc = layers.Dense(num_classes)
# 全连接层
self.out = layers.Dense(10,activation='softmax')
#由于进行的是多分类的问题所以最后增加一层全连接层使用softmax函数激活
#定义了数据在层之间的传递过程
def call(self, inputs, training=None):
x = self.getdata(inputs,training=training)
x = self.layer1(x,training=training)
x = self.layer2(x,training=training)
x = self.layer3(x, training=training)
x = self.layer4(x, training=training)
x = self.avgpool(x)
x = self.fc(x)
x = self.out(x)
return x
def build_resblok(self, filter_num,blocks,stride=1):
res_block = keras.Sequential()
res_block.add(Baisblock(filter_num,stride))
for _ in range(1,blocks):
res_block.add(Baisblock(filter_num,stride=1))
return res_block
最后在主函数中实例化我们整体的残差网络定义损失率,并可视化我们的准确率
def main():
model= ResNet([2,2,2,2])
model.build(input_shape=(None, 28, 28, 1))
optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3)
model.compile(optimizer,loss=tf.keras.losses.sparse_categorical_crossentropy,metrics = ['accuracy'])
history = model.fit(train_image,train_layber,epochs=5,validation_data=(test_image,test_layber))
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.legend(['training', 'valivation'], loc='upper left')
plt.show()
model.summary()
main()