吴恩达深度学习_4_Week2深度卷积网络:Residual networks

残差网络


1、非常深的神经网络的问题
2、构建残差函数
3、在自己的图像上进行测试


第四门课:卷积神经网络
第二周:深度卷积网络:实例探究

你将学习如何构建非常深的卷积网络,使用残差网络(ResNets)。在理论上,非常深的网络可以表示非常复杂的函数;但在实践中,它们很难训练。残差网络由何凯明等人引入,使你能够训练比以前实际可行的更深的网络。
在这个任务中,你将:
1、实现残差网络的基本构建块。
2、将这些构建块组合起来,实现和训练一个用于图像分类的最先进的神经网络。
3、本次任务将使用Keras完成。

import numpy as np
from keras import layers
from keras.layers import Input, Add, Dense, Activation, ZeroPadding2D, BatchNormalization, Flatten, Conv2D, AveragePooling2D, MaxPooling2D, GlobalMaxPooling2D
from keras.models import Model, load_model
from keras.preprocessing import image
from keras.utils import layer_utils
from keras.utils.data_utils import get_file
from keras.applications.imagenet_utils import preprocess_input
import pydot
from IPython.display import SVG
from keras.utils.vis_utils import model_to_dot
from keras.utils import plot_model
from resnets_utils import *
from keras.initializers import glorot_uniform
import scipy.misc
from matplotlib.pyplot import imshow

import keras.backend as K
K.set_image_data_format('channels_last')
K.set_learning_phase(1)

一、非常深的神经网络的问题

在上周,你构建了你的第一个卷积神经网络。近年来,神经网络变得越来越深,最先进的网络层数从几层(例如AlexNet)增加到超过一百层。
非常深的网络的主要优点是它可以表示非常复杂的函数。它还可以在许多不同的抽象层次上学习特征,从边缘(在较低层次上)到非常复杂的特征(在更深的层次上)。然而,使用更深的网络并不总是有帮助的。训练它们的一个巨大障碍是梯度消失:非常深的网络通常具有迅速趋近于零的梯度信号,从而使梯度下降变得难以忍受地慢。更具体地说,在梯度下降期间,当你从最后一层向后传播到第一层时,你在每个步骤中都要乘以权重矩阵,因此梯度可以呈指数级地迅速减小到零(或者,在极少数情况下,指数级地增长并“爆炸”到非常大的值)。
在训练过程中,你可能会发现早期层的梯度的幅度(或范数)随着训练的进行而迅速减小到零:
在这里插入图片描述

二、构建残差网络

在ResNets中,“shortcut"或"skip connection"允许梯度直接反向传播到较早的层次:
在这里插入图片描述
左侧的图像显示了网络中的"主路径”。右侧的图像在主路径上添加了一个shortcut。通过将这些ResNet块堆叠在一起,你可以构建一个非常深的网络。
我们在课程中还看到,使用shortcut的ResNet块也使得其中一个块很容易学习成为一个恒等函数。这意味着你可以在额外的ResNet块上堆叠,而几乎不会对训练集性能造成影响。(还有一些证据表明,学习恒等函数的便利性——甚至比跳过连接对抗梯度消失有更大的帮助——解释了ResNets的显著性能。)
在ResNet中使用两种主要类型的块,主要取决于输入/输出维度是否相同。你将实现这两种类型的块。

1、恒等块

恒等块是ResNets中使用的标准块,对应于输入激活(假设为𝑎[𝑙])与输出激活(假设为𝑎[𝑙+2])具有相同维度的情况。为了详细说明ResNet的恒等块中发生的不同步骤,这里是一个显示各个步骤的替代图表:
在这里插入图片描述
上方路径是"shortcut路径"。下方路径是"main路径"。在这个图表中,我们还明确了每个层中的CONV2D和ReLU步骤。为了加快训练速度,我们还添加了一个BatchNorm步骤。不要担心这个实现会很复杂——你会发现在Keras中,BatchNorm只需一行代码!
在这个练习中,你将实际上实现一个稍微更强大的恒等块版本,其中跳过连接"跳过了"3个隐藏层,而不是2个层。它的结构如下所示:
在这里插入图片描述
主路径的第一个组件:
1、第一个CONV2D层具有形状为(1,1)和步幅为(1,1)的𝐹1个滤波器。填充方式为"valid",名称应为conv_name_base + ‘2a’。使用0作为随机初始化的种子。
2、第一个BatchNorm层对通道轴进行归一化。名称应为bn_name_base + ‘2a’。
3、然后应用ReLU激活函数。这没有名称和超参数。

主路径的第二个组件:
1、第二个CONV2D层具有形状为(𝑓,𝑓)和步幅为(1,1)的𝐹2个滤波器。填充方式为"same",名称应为conv_name_base + ‘2b’。使用0作为随机初始化的种子。
2、第二个BatchNorm层对通道轴进行归一化。名称应为bn_name_base + ‘2b’。
3、然后应用ReLU激活函数。这没有名称和超参数。

主路径的第三个组件:
1、第三个CONV2D层具有形状为(1,1)和步幅为(1,1)的𝐹3个滤波器。填充方式为"valid",名称应为conv_name_base + ‘2c’。使用0作为随机初始化的种子。
2、第三个BatchNorm层对通道轴进行归一化。名称应为bn_name_base + ‘2c’。请注意,此组件中没有ReLU激活函数。

最后一步:
1、将shortcut与输入相加。
2、然后应用ReLU激活函数。这没有名称和超参数。

练习:实现ResNet的恒等块。我们已经实现了主路径的第一个组件。请仔细阅读并确保你理解它的功能。你应该实现其余的组件。
1、实现Conv2D步骤:参考文档
2、实现BatchNorm:参考文档(轴:整数,应进行归一化的轴(通常是通道轴))
3、对于激活函数,使用:Activation(‘relu’)(X)
4、将通过shortcut传递的值相加:参考文档

按照图3中定义的恒等块实现如下:
参数:
X -- 输入张量,形状为 (m, n_H_prev, n_W_prev, n_C_prev)
f -- 整数,指定主路径中间 CONV 层窗口的形状
filters -- Python 列表,定义主路径中 CONV 层的滤波器数量
stage -- 整数,用于根据其在网络中的位置给层命名
block -- 字符串/字符,用于根据其在网络中的位置给层命名
返回:X -- 恒等块的输出,形状为 (n_H, n_W, n_C)
# GRADED FUNCTION: identity_block
def identity_block(X, f, filters, stage, block):
    # defining name basis
    conv_name_base = 'res' + str(stage) + block + '_branch'
    bn_name_base = 'bn' + str(stage) + block + '_branch'
    
    # Retrieve Filters
    F1, F2, F3 = filters
    
    # Save the input value. You'll need this later to add back to the main path. 
    X_shortcut = X
    
    # First component of main path
    X = Conv2D(filters = F1, kernel_size = (1, 1), strides = (1,1), padding = 'valid', name = conv_name_base + '2a', kernel_initializer = glorot_uniform(seed=0))(X)
    X = BatchNormalization(axis = 3, name = bn_name_base + '2a')(X)
    X = Activation('relu')(X)
   
    # Second component of main path 
    X = Conv2D(filters = F2, kernel_size = (f, f), strides = (1,1), padding = 'same', name = conv_name_base + '2b', kernel_initializer = glorot_uniform(seed=0))(X)
    X = BatchNormalization(axis = 3, name = bn_name_base + '2b')(X)
    X = Activation('relu')(X)
       
    # Third component of main path 
    X = Conv2D(filters = F3, kernel_size = (1, 1), strides = (1,1), padding = 'valid', name = conv_name_base + '2c', kernel_initializer = glorot_uniform(seed=0))(X)
    X = BatchNormalization(axis = 3, name = bn_name_base + '2c')(X)
 
    # Final step: Add shortcut value to main path, and pass it through a RELU activation 
    X = Add()([X, X_shortcut])
    X = Activation('relu')(X)
    
    return X
    
tf.reset_default_graph()
with tf.Session() as test:
    np.random.seed(1)
    A_prev = tf.placeholder("float", [3, 4, 4, 6])
    X = np.random.randn(3, 4, 4, 6)
    A = identity_block(A_prev, f = 2, filters = [2, 4, 6], stage = 1, block = 'a')
    test.run(tf.global_variables_initializer())
    out = test.run([A], feed_dict={A_prev: X, K.learning_phase(): 0})
    print("out = " + str(out[0][1][1][0]))    

out [ 0.94822985 0. 1.16101444 2.747859 0. 1.36677003]

2、卷积块

已经实现了ResNet中的恒等块。接下来,ResNet的"卷积块"是另一种类型的块。当输入和输出的维度不匹配时,可以使用这种类型的块。与恒等块的区别在于,快捷路径中有一个CONV2D层:
在这里插入图片描述
快捷路径中的CONV2D层用于调整输入 𝑥 的维度,以便在最后的加法中,将快捷值添加回主路径时维度匹配。(这类似于课程中讨论的矩阵 𝑊𝑠 的作用。) 例如,要将激活的高度和宽度缩小2倍,可以使用步幅为2的1x1卷积。快捷路径上的CONV2D层不使用任何非线性激活函数。它的主要作用只是应用一个(学习到的)线性函数来减小输入的维度,以便在后续的加法步骤中维度匹配。
卷积块的详细步骤如下:
主路径的第一个组件:
1、第一个CONV2D层具有形状为(1,1)和步幅为(s,s)的𝐹1个滤波器。填充方式为"valid",名称应为conv_name_base + ‘2a’。
2、第一个BatchNorm层对通道轴进行归一化。名称应为bn_name_base + ‘2a’。
3、然后应用ReLU激活函数。这没有名称和超参数。

主路径的第二个组件:
1、第二个CONV2D层具有形状为(f,f)和步幅为(1,1)的𝐹2个滤波器。填充方式为"same",名称应为conv_name_base + ‘2b’。
2、第二个BatchNorm层对通道轴进行归一化。名称应为bn_name_base + ‘2b’。
3、然后应用ReLU激活函数。这没有名称和超参数。

主路径的第三个组件:
1、第三个CONV2D层具有形状为(1,1)和步幅为(1,1)的𝐹3个滤波器。填充方式为"valid",名称应为conv_name_base + ‘2c’。
2、第三个BatchNorm层对通道轴进行归一化。名称应为bn_name_base + ‘2c’。请注意,此组件中没有ReLU激活函数。

快捷路径:
1、CONV2D层具有形状为(1,1)和步幅为(s,s)的𝐹3个滤波器。填充方式为"valid",名称应为conv_name_base + ‘1’。
2、BatchNorm层对通道轴进行归一化。名称应为bn_name_base + ‘1’。

最后一步:
1、将快捷路径和主路径的值相加。
2、然后应用ReLU激活函数。这没有名称和超参数。

练习:实现卷积块。我们已经实现了主路径的第一个组件,你应该实现其余的组件。与之前一样,始终使用0作为随机初始化的种子,以确保与我们的评分器一致。

Conv提示
BatchNorm 提示(axis:整数,应进行归一化的轴(通常是特征轴))
对于激活函数,使用:Activation(‘relu’)(X)
加法提示

按照图4中定义的卷积块实现如下:
参数:
X -- 输入张量,形状为 (m, n_H_prev, n_W_prev, n_C_prev)
f -- 整数,指定主路径中间 CONV 层窗口的形状
filters -- Python 列表,定义主路径中 CONV 层的滤波器数量
stage -- 整数,用于根据其在网络中的位置给层命名
block -- 字符串/字符,用于根据其在网络中的位置给层命名
s -- 整数,指定要使用的步幅
返回:X -- 卷积块的输出,形状为 (n_H, n_W, n_C)
# GRADED FUNCTION: convolutional_block
def convolutional_block(X, f, filters, stage, block, s = 2):

    # defining name basis
    conv_name_base = 'res' + str(stage) + block + '_branch'
    bn_name_base = 'bn' + str(stage) + block + '_branch'
    
    # Retrieve Filters
    F1, F2, F3 = filters
    
    # Save the input value
    X_shortcut = X

    # First component of main path 
    X = Conv2D(filters = F1, kernel_size = (1, 1), strides = (s,s), padding = 'valid', name = conv_name_base + '2a', kernel_initializer = glorot_uniform(seed=0))(X)
    X = BatchNormalization(axis = 3, name = bn_name_base + '2a')(X)
    X = Activation('relu')(X)

    # Second component of main path (≈3 lines)
    X = Conv2D(F2, (f, f), strides = (1,1), name = conv_name_base + '2b', padding = 'same', kernel_initializer = glorot_uniform(seed=0))(X)
    X = BatchNormalization(axis = 3, name = bn_name_base + '2b')(X)
    X = Activation('relu')(X)

    # Third component of main path (≈2 lines)
    X = Conv2D(F3, (1, 1), strides = (1,1), name = conv_name_base + '2c', kernel_initializer = glorot_uniform(seed=0))(X)
    X = BatchNormalization(axis = 3, name = bn_name_base + '2c')(X)

    ##### SHORTCUT PATH #### 
    X_shortcut = Conv2D(F3, (1, 1), strides = (s,s), name = conv_name_base + '1', kernel_initializer = glorot_uniform(seed=0))(X_shortcut)
    X_shortcut = BatchNormalization(axis = 3, name = bn_name_base + '1')(X_shortcut)

    # Final step: Add shortcut value to main path, and pass it through a RELU activation (≈2 lines)
    X = Add()([X, X_shortcut])
    X = Activation('relu')(X)

    return X
    
tf.reset_default_graph()
with tf.Session() as test:
    np.random.seed(1)
    A_prev = tf.placeholder("float", [3, 4, 4, 6])
    X = np.random.randn(3, 4, 4, 6)
    A = convolutional_block(A_prev, f = 2, filters = [2, 4, 6], stage = 1, block = 'a')
    test.run(tf.global_variables_initializer())
    out = test.run([A], feed_dict={A_prev: X, K.learning_phase(): 0})
    print("out = " + str(out[0][1][1][0]))    

out [ 0.09018463 1.23489773 0.46822017 0.0367176 0. 0.65516603]

3、构建你的第一个ResNet模型(50层)

你现在已经拥有了构建非常深的ResNet所需的块。下面的图详细描述了这个神经网络的架构。图中的"ID BLOCK"代表"恒等块",“ID BLOCK x3"表示你应该将3个恒等块堆叠在一起。
在这里插入图片描述
这个ResNet-50模型的细节如下:
1、使用(3,3)的填充将输入进行填充。
2、第1阶段:
2D卷积层具有64个形状为(7,7)的滤波器,并使用步幅为(2,2)。它的名称为"conv1”。
BatchNorm应用于输入的通道轴。
MaxPooling使用(3,3)的窗口和(2,2)的步幅。
2、第2阶段:
卷积块使用大小为[64,64,256]的三组滤波器,"f"为3,“s"为1,块的名称为"a”。
两个恒等块使用大小为[64,64,256]的三组滤波器,“f"为3,块的名称分别为"b"和"c”。
3、第3阶段:
卷积块使用大小为[128,128,512]的三组滤波器,"f"为3,“s"为2,块的名称为"a”。
三个恒等块使用大小为[128,128,512]的三组滤波器,“f"为3,块的名称分别为"b”、“c"和"d”。
4、第4阶段:
卷积块使用大小为[256,256,1024]的三组滤波器,"f"为3,“s"为2,块的名称为"a”。
五个恒等块使用大小为[256,256,1024]的三组滤波器,“f"为3,块的名称分别为"b”、“c”、“d”、“e"和"f”。
5、第5阶段:
卷积块使用大小为[512,512,2048]的三组滤波器,“f"为3,“s"为2,块的名称为"a”。
两个恒等块使用大小为[512,512,2048]的三组滤波器,“f"为3,块的名称分别为"b"和"c”。
6、2D平均池化层使用形状为(2,2)的窗口,名称为"avg_pool”。
7、Flatten层没有任何超参数或名称。
8、全连接(Dense)层使用softmax激活函数将输入减少到类别数。它的名称应为’fc’ + str(classes)。

练习:实现上面图中描述的具有50层的ResNet。我们已经实现了第1阶段和第2阶段。请实现剩下的部分。(实现第3-5阶段的语法应该与第2阶段的相似。)请确保按照上述文本中的命名规范进行命名。

你将需要使用以下函数:
1、Average pooling 参考
以下是我们在下面的代码中使用的其他函数:
2、Conv2D:参考
3、BatchNorm:参考(axis:整数,应进行归一化的轴(通常是特征轴))
4、Zero padding:参考
5、Max pooling:参考
6、全连接层:参考
7、加法:参考

实现了ResNet50的流行架构如下:
CONV2D -> BATCHNORM -> RELU -> MAXPOOL -> CONVBLOCK -> IDBLOCK2 -> CONVBLOCK -> IDBLOCK3
-> CONVBLOCK -> IDBLOCK5 -> CONVBLOCK -> IDBLOCK2 -> AVGPOOL -> TOPLAYER
参数:
input_shape -- 数据集图像的形状
classes -- 整数,类别数量
返回:model -- Keras中的一个Model()实例
# GRADED FUNCTION: ResNet50
def ResNet50(input_shape = (64, 64, 3), classes = 6):
    # Define the input as a tensor with shape input_shape
    X_input = Input(input_shape)

    # Zero-Padding
    X = ZeroPadding2D((3, 3))(X_input)
    
    # Stage 1
    X = Conv2D(64, (7, 7), strides = (2, 2), name = 'conv1', kernel_initializer = glorot_uniform(seed=0))(X)
    X = BatchNormalization(axis = 3, name = 'bn_conv1')(X)
    X = Activation('relu')(X)
    X = MaxPooling2D((3, 3), strides=(2, 2))(X)

    # Stage 2
    X = convolutional_block(X, f = 3, filters = [64, 64, 256], stage = 2, block='a', s = 1)
    X = identity_block(X, 3, [64, 64, 256], stage=2, block='b')
    X = identity_block(X, 3, [64, 64, 256], stage=2, block='c')

    # Stage 3   
    X = convolutional_block(X, f = 3, filters = [128, 128, 512], stage = 3, block='a', s = 2)
    X = identity_block(X, 3, [128, 128, 512], stage=3, block='b')
    X = identity_block(X, 3, [128, 128, 512], stage=3, block='c')
    X = identity_block(X, 3, [128, 128, 512], stage=3, block='d')
              
    # Stage 4 
    X = convolutional_block(X, f = 3, filters = [256, 256, 1024], stage = 4, block='a', s = 2)
    X = identity_block(X, 3, [256, 256, 1024], stage=4, block='b')
    X = identity_block(X, 3, [256, 256, 1024], stage=4, block='c')
    X = identity_block(X, 3, [256, 256, 1024], stage=4, block='d')
    X = identity_block(X, 3, [256, 256, 1024], stage=4, block='e')
    X = identity_block(X, 3, [256, 256, 1024], stage=4, block='f')

    # Stage 5 
    X = convolutional_block(X, f = 3, filters = [512, 512, 2048], stage = 5, block='a', s = 2)
    X = identity_block(X, 3, [512, 512, 2048], stage=5, block='b')
    X = identity_block(X, 3, [512, 512, 2048], stage=5, block='c')

    # AVGPOOL. Use "X = AveragePooling2D(...)(X)"    
    X = AveragePooling2D((2, 2), name='avg_pool')(X)

    # output layer
    X = Flatten()(X)
    X = Dense(classes, activation='softmax', name='fc' + str(classes), kernel_initializer = glorot_uniform(seed=0))(X)
      
    # Create model
    model = Model(inputs = X_input, outputs = X, name='ResNet50')

    return model

运行以下代码以构建模型的图。如果你的实现不正确,通过运行下面的model.fit(…)来检查你的准确性,你将会知道。

model = ResNet50(input_shape = (64, 64, 3), classes = 6)

如在Keras教程笔记本中所见,在训练模型之前,你需要通过编译模型来配置学习过程。

model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])

在这里插入图片描述

X_train_orig, Y_train_orig, X_test_orig, Y_test_orig, classes = load_dataset()

# Normalize image vectors
X_train = X_train_orig/255.
X_test = X_test_orig/255.

# Convert training and test labels to one hot matrices
Y_train = convert_to_one_hot(Y_train_orig, 6).T
Y_test = convert_to_one_hot(Y_test_orig, 6).T

print ("number of training examples = " + str(X_train.shape[0]))
print ("number of test examples = " + str(X_test.shape[0]))
print ("X_train shape: " + str(X_train.shape))
print ("Y_train shape: " + str(Y_train.shape))
print ("X_test shape: " + str(X_test.shape))
print ("Y_test shape: " + str(Y_test.shape))

model.fit(X_train, Y_train, epochs = 2, batch_size = 32)

输出:
** Epoch 1/2** loss: between 1 and 5, acc: between 0.2 and 0.5, although your results can be different from ours.
** Epoch 2/2** loss: between 1 and 5, acc: between 0.2 and 0.5, you should see your loss decreasing and the accuracy increasing.

让我们看看这个模型(仅经过两个epoch训练)在测试集上的表现。

preds = model.evaluate(X_test, Y_test)
print ("Loss = " + str(preds[0]))
print ("Test Accuracy = " + str(preds[1]))

Test Accuracy between 0.16 and 0.25

为了这个作业的目的,我们要求你只训练模型两个epoch。你可以看到它的性能很差。请继续提交你的作业;为了检查正确性,在线评估器也只会运行你的代码一小部分epoch。
在完成这个官方(评分)部分的作业后,如果你愿意,你还可以选择为ResNet进行更多的迭代训练。当我们在CPU上训练时,训练大约需要一个多小时才能获得更好的性能。
使用GPU,我们已经在SIGNS数据集上训练了自己的ResNet50模型的权重。你可以在下面的单元格中加载和运行我们训练好的模型在测试集上。加载模型可能需要大约1分钟的时间。

model = load_model('ResNet50.h5') 

preds = model.evaluate(X_test, Y_test)
print ("Loss = " + str(preds[0]))
print ("Test Accuracy = " + str(preds[1]))

当适当数量的迭代训练时,ResNet50是一个强大的图像分类模型。我们希望你能运用所学,并将其应用于你自己的分类问题,以获得最先进的准确性。
你现在已经实现了一个最先进的图像分类系统!

三、在自己的图像上进行测试

你也可以拍一张你自己的手的照片,并查看模型的输出。操作步骤如下:

  1. 点击此笔记本上方的"文件",然后点击"打开"以进入你的Coursera Hub。
  2. 将你的图像添加到此Jupyter笔记本的目录中,放在"images"文件夹中。
  3. 在下面的代码中写入你的图像名称。
  4. 运行代码,检查算法是否正确!
img_path = 'images/my_image.jpg'
img = image.load_img(img_path, target_size=(64, 64))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)
print('Input image shape:', x.shape)
my_image = scipy.misc.imread(img_path)
imshow(my_image)
print("class prediction vector [p(0), p(1), p(2), p(3), p(4), p(5)] = ")
print(model.predict(x))

# print a summary of your model by running the following code
model.summary()

# run the code below to visualize your ResNet50. You can also download a .png picture of your model by going to "File -> Open...-> model.png".
plot_model(model, to_file='model.png')
SVG(model_to_dot(model).create(prog='dot', format='svg'))

记住:

  • 跳跃连接有助于解决梯度消失问题。它们还使得ResNet块很容易学习身份函数。
  • 有两种主要类型的块:身份块和卷积块。
  • 构建非常深的残差网络是通过将这些块堆叠在一起实现的。
  • 参考资料
    该笔记本介绍了He等人(2015年)提出的ResNet算法。这里的实现也在很大程度上受到Francois Chollet的github存储库中给出的结构的启发和影响:

Kaiming He,Xiangyu Zhang,Shaoqing Ren,Jian Sun - 深度残差学习用于图像识别(2015年)
Francois Chollet的github存储库:https://github.com/fchollet/deep-learning-models/blob/master/resnet50.py

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值