<jupyter_start><jupyter_text>深度卷积模型：案例分析  1. 案例分析  1.1 为什么要进行案例分析过去几年来，计算机视觉领域的主要工作，就在研究如何拼装卷积层、池化层、全连接层等等卷积神经网络模型的基本组件，组合出较为有效的模型，有效的模型通常可以在不同的计算机视觉模型之间通用。因此，通过学习这些模型实例，可以对深度卷积模型有更好的理解。  1.2 经典网络LeNet-5，用来处理灰度图片的数字识别问题。    - 在LeNet-5的年代，补全的技巧还不常用，所有卷积层都不补全    - 池化层在当时更多采用的是平均池化，目前最大池化更为常见    - 模型大约有60000个参数，相对于现代神经网络，规模较小    - 随着模型的深入，高度和宽度不断减小，通道数不断增加，这个实践沿用至今    - 当时的激活函数采用的是sigmoid或者tanh，还没有使用ReLU    - 当时单个过滤器，并不会处理所有的通道，这点和现在也不一样    - 当时在池化层后面会接一个非线性的激活层，和现在不同![LeNet-5.png](img/LeNet-5.png)AlexNet，真正使得计算机视觉领域开始重视神经网络    - 已经开始使用ReLU作为激活函数    - 大约6000万的参数，中等规模    - 当时的GPU计算能力还不够强，论文里涉及两个GPU的通信    - 用到了Local Response Normalization(LRN)的技巧，这个技巧目前已经不常用了    - same padding，同一补全![AlexNet.png](img/AlexNet.png)VGG-16，模式更为明确    - 固定3×3的过滤器，步长s=1，同一补全；最大池化层2×2，步长s=2    - 每次卷积层（连续两层）后，通道数翻倍    - 每次池化层后，高度和宽度减半    - 大约有1.38亿的参数，现代规模的神经网络    - VGG-16中的16是值整个神经网路中共有16层带有参数的层，另外还有一个更大的VGG-19版本，不常用![VGG-16.png](img/VGG-16.png)  1.3 残差网络 ResNets由于梯度消失和梯度爆炸问题，训练非常大的神经网络通常非常困难。而残差网络解决了这个问题。残差网络的基本组成结构如下：![Residual block.png](img/Residual block.png)由于计算能力的问题，普通神经网络（Plain Network）在实践中随着层数增多，并不能获得更好的效果。而残差神经网络使训练层数非常多的模型成为可能。![Residual Network.png](img/Residual Network.png)  1.4 残差网络为什么有效假设现在有一个 $l$ 层的普通神经网络，输出结果 $a^{[l]}$。我们在其之后，又增加了两层残差Block，得到输出 $a^{[l+2]}$，根据上面的公式，$a^{[l+2]}=g(z^{[l+2]}+a^{[l]})=g(W^{[l+2]}a^{[l+1]}+b^{[l+2]}+a^{[l]})$。假设我们对整个网络进行了L2正则化，那么$W^{[l+2]}$ 和 $b^{[l+2]}$ 就会约等于0，使得 $a^{[l+2]}$ 约等于 $a^{[l]}$。也即是说，在最坏的情况下，增加了两层残差Block，也只会使得神经网络的输出相同。而较好的情况下，我们可以训练出更好的模型。值得注意的是，$z^{[l+2]}$ 和 $a^{[l]}$ 要可加，这两个矩阵的维度需要相同。因此，残差网络通常配合着同一补全的卷积层来使用。而对于池化层，则会需要增加一个 $W_s$ 的矩阵，和 $a^{[l]}$ 相乘后再进行加运算。  1.5 网络中的网络，1×1卷积1×1的卷积在单一通道下看似乎没什么用，但是当通道数多了之后，1×1的卷积实际上可以对跨通道之间的数据项进行非线性组合。![Why does a 1×1 convolution do.png](img/Why does a 1×1 convolution do.png)1×1的卷积还可以对通道数进行缩减（就像池化层对高度和宽度进行缩减），当然如果愿意的化，1×1卷积也可以增加通道数。![Using 1×1 convolutions.png](img/Using 1×1 convolutions.png)  1.6 Inception网络的设想避免考虑要用1×1的卷积，还是3×3的卷积，还是池化层。直接将所有这些，叠加到同一层网络中，让模型自己去学习参数。![Motivation for inception network.png](img/Motivation for inception network.png)5×5的卷积，可能会引入比较大的计算量。这时，使用1×1的卷积，在中间做一层瓶颈层，可以有效地降低计算量。![The problem of computational cost.png](img/The problem of computational cost.png)![Using 1×1 convolution.png](img/Using 1×1 convolution.png)  1.7 Inception网络Inception模块使用到了上面的组件，对于3×3和5×5这样的卷积层，会在之前加入一层1×1的卷积作为瓶颈层，减少计算；而对于池化层，除去同一补全之外，还会在之后增加一层1×1的卷积层，用来缩减通道数量。![Inception module.png](img/Inception module.png)Inception网络是由多个Inception模块组合而成的。![Inception network.png](img/Inception network.png)一个小彩蛋，Inception的概念，其实就来自盗梦空间。![WE NEED TO GO DEEPER.png](img/WE NEED TO GO DEEPER.png)  2. 在实际项目中使用卷积网络的一些建议  2.1 使用开源实现上面介绍的神经网络架构都比较复杂，在实现的过程中，有很多需要注意的小技巧。对于计算机视觉应用，想要使用上面或其它研究文献中介绍的神经网络架构，通常的建议是去（比如Github上）寻找开源实现，在此基础上进行开发。  2.2 迁移学习很多开源实现除去实现神经网络架构之外，还会包含该架构在知名数据集（比如ImageNet）上训练完成后的各项权重。直接使用这些权重作为预训练好的权重，在此之上进行迁移学习，而不是重新随机初始化权重从头进行训练，是一个比较好的实践。根据所要解决问题数据量的不同，迁移学习的方案也有几种不同的模式：    - 如果训练集数量很小，建议冻结下载好的权重，在此基础上直接替换增加一层自己的Softmax分类层，只训练这个分类层的权重。冻结权重这个功能，各个框架都有支持；    - 如果训练集数量有中等规模，可以相应地冻结更少层权重，不冻结的几层和自己的Softmax层用来训练；    - 如果训练集数量非常大，可以只用下载的权重替代权重的随机初始化，替换Softmax，整个网络进行训练。计算机视觉领域，迁移学习几乎是必然的选择。  2.3 数据扩增对于计算机视觉领域，数据量总是显得不够，数据扩增是对原始数据进行加工，生成新数据的手段。    - 镜像（左右转置），随机剪切（不完美，可能失去图像中的物体，但只要一张图随机剪切的子集够大，在实践中效果也会不错），旋转，修剪（Shearing，不常用），局部翘曲（Local Warping，不常用）。    - 颜色偏移，比如对图片的RGB值进行较小幅度的变更。AlexNet论文中也介绍了应用PCA进行颜色偏移的方法。    实践中在实现时，可能会有一个单独的CPU线程，将扩增后的数据，喂给正常训练的其它CPU线程或者GPU。  2.4 计算机视觉领域的现状相比问题的复杂度，打标数据少，手动设计的特征工程多，在神经网络的架构设计上比较讲究。![Data vs hand-engineering.png](img/Data vs hand-engineering.png)模型融合（Ensembling）和Multi-crop是两个在比赛中提升成绩的好办法，但生产环境中使用的很少。![Tips for doing well on benchmarks or winning competitions.png](img/Tips for doing well on benchmarks or winning competitions.png)  3. Show me the Code  3.1 Keras教程 - 快乐之家在本节，我们将会学习：1. Python编写的高级神经网络API（编程框架）Keras，它可以跑在多个底层框架上，比如Tensorflow，CNTK。2. 看看我们如何可以在几个小时内就构建一个深度学习算法。开发Keras的目的，就是为了让深度学习工程师可以更快地构建和实验不同的模型。正如Tensorflow相对于Python标准库或者Numpy来说是更高级的框架，Keras是在其之上更高层次的框架，提供了更多的抽象。能够很快地将想法变成结果，对于找到正确的模型来说十分关键。但与此同时，Keras相比底层框架，限制也更多，所以会有一些十分复杂的模型，可以用Tensorflow来表示，但不能（轻易地）用Keras来表示。尽管如此，Keras对于绝大多数常见的模型来说，是十分管用的。在这个练习中，我们会解决“快乐之家”难题，下面会有详细的解析。首先，让我们导入相应的模块。<jupyter_code>import numpy as np
from keras import layers
from keras.layers import Input, Dense, Activation, ZeroPadding2D, BatchNormalization, Flatten, Conv2D
from keras.layers import AveragePooling2D, MaxPooling2D, Dropout, GlobalMaxPooling2D, GlobalAveragePooling2D
from keras.models import 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 kt_utils import *

import keras.backend as K
K.set_image_data_format('channels_last')
import matplotlib.pyplot as plt
from matplotlib.pyplot import imshow

%matplotlib inline<jupyter_output>Using TensorFlow backend.<jupyter_text>3.1.1 快乐之家下一个假期，你觉得和五位朋友一起过。快乐之家的地理位置十分便利，但最重要的是所有人都承诺，在家的时候会十分开心。所以，想进入房子的人必须证明他们现在十分开心。   **Figure 1**   : **the Happy House**作为深度学习专家，为了保证“快乐”的原则被严格执行，你会构建一个算法，使用门口摄像头拍摄的图片来判断访客是否快乐。只有在判断访问快乐的时候，门才会自动打开。你收集了用门口摄像头拍摄的一组朋友和自己的照片，数据集已经进行了打标。执行下面的代码，对数据集进行正规化，并了解数据集的维度。<jupyter_code>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.

# Reshape
Y_train = Y_train_orig.T
Y_test = Y_test_orig.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))<jupyter_output>number of training examples = 600
number of test examples = 150
X_train shape: (600, 64, 64, 3)
Y_train shape: (600, 1)
X_test shape: (150, 64, 64, 3)
Y_test shape: (150, 1)<jupyter_text>**"快乐之家"数据集详情**:- 图片维度 (64,64,3)- 训练集: 600 张图片- 测试集: 150 张图片  3.1.2 使用Keras构建模型Keras非常适合用来做快速原型，在非常短的时间内，我们就可以构建出效果非常好的模型。下面是Keras模型的一个例子：```pythondef model(input_shape):     Define the input placeholder as a tensor with shape input_shape. Think of this as your input image!    X_input = Input(input_shape)     Zero-Padding: pads the border of X_input with zeroes    X = ZeroPadding2D((3, 3))(X_input)     CONV -> BN -> RELU Block applied to X    X = Conv2D(32, (7, 7), strides = (1, 1), name = 'conv0')(X)    X = BatchNormalization(axis = 3, name = 'bn0')(X)    X = Activation('relu')(X)     MAXPOOL    X = MaxPooling2D((2, 2), name='max_pool')(X)     FLATTEN X (means convert it to a vector) + FULLYCONNECTED    X = Flatten()(X)    X = Dense(1, activation='sigmoid', name='fc')(X)     Create model. This creates your Keras model instance, you'll use this instance to train/test the model.    model = Model(inputs = X_input, outputs = X, name='HappyModel')        return model```注意到，Keras使用了和Tensorflow或者numpy不太相同的变量命名风格。最主要的一点，它并没有随着前向传播过程创建并赋值一系列诸如 `X`, `Z1`, `A1`, `Z2`, `A2` 的变量。对于不同的层，Keras代码中仅仅是通过 `X = ...` 来重新对 `X` 赋值。换句话说，前向传播中的每个步骤，我们不断地将计算结果重新写回同一个变量 `X`。唯一的例外是 `X_input`，考虑后最后我们需要用 `model = Model(inputs = X_input, ...)` 来创建Keras模型，`X_input` 不会被覆盖。**练习**：实现 `HappyModel()`。<jupyter_code># GRADED FUNCTION: HappyModel

def HappyModel(input_shape):
    """
    Implementation of the HappyModel.
    
    Arguments:
    input_shape -- shape of the images of the dataset

    Returns:
    model -- a Model() instance in Keras
    """
    
    ### START CODE HERE ###
    # Feel free to use the suggested outline in the text above to get started, and run through the whole
    # exercise (including the later portions of this notebook) once. The come back also try out other
    # network architectures as well. 
        # Define the input placeholder as a tensor with shape input_shape. Think of this as your input image!
    X_input = Input(input_shape)

    # Zero-Padding: pads the border of X_input with zeroes
    X = ZeroPadding2D((3, 3))(X_input)

    # CONV -> BN -> RELU Block applied to X
    X = Conv2D(32, (7, 7), strides = (1, 1), name = 'conv0')(X)
    X = BatchNormalization(axis = 3, name = 'bn0')(X)
    X = Activation('relu')(X)

    # MAXPOOL
    X = MaxPooling2D((2, 2), name='max_pool')(X)

    # FLATTEN X (means convert it to a vector) + FULLYCONNECTED
    X = Flatten()(X)
    X = Dense(1, activation='sigmoid', name='fc')(X)

    # Create model. This creates your Keras model instance, you'll use this instance to train/test the model.
    model = Model(inputs = X_input, outputs = X, name='HappyModel')
    
    ### END CODE HERE ###
    
    return model<jupyter_output><empty_output><jupyter_text>我们创建了一个函数，来描述我们的模型。要训练和测试这个模型，在Keras中分为四步：1. 调用上面的函数创建模型。2. 调用 `model.compile(optimizer = "...", loss = "...", metrics = ["accuracy"])` 编译模型。3. 调用 `model.fit(x = ..., y = ..., epochs = ..., batch_size = ...)` 训练模型。4. 调用 `model.evaluate(x = ..., y = ...)` 测试模型。如果你想了解关于 `model.compile()`, `model.fit()`, `model.evaluate()` 及其参数的更多信息，可以参考官方的[Keras文档](https://keras.io/models/model/).**练习**: 实现步骤1，创建模型。<jupyter_code>### START CODE HERE ### (1 line)
happyModel = HappyModel((X_train.shape[1], X_train.shape[2], X_train.shape[3]))
### END CODE HERE ###<jupyter_output><empty_output><jupyter_text>**练习**: 实现步骤2，设置学习过程的相关参数以编译模型。请小心地选择 `compile()` 的三个参数。提示：快乐之家是一个二分类问题。<jupyter_code>### START CODE HERE ### (1 line)
happyModel.compile(optimizer="Adam", loss="binary_crossentropy", metrics=["accuracy"])
### END CODE HERE ###<jupyter_output><empty_output><jupyter_text>**练习**: 实现步骤3，训练模型。选择epochs和批次大小。<jupyter_code>### START CODE HERE ### (1 line)
happyModel.fit(x=X_train, y=Y_train, epochs=40, batch_size=16)
### END CODE HERE ###<jupyter_output>Epoch 1/40
600/600 [==============================] - 1s - loss: 0.9612 - acc: 0.7117     
Epoch 2/40
600/600 [==============================] - 1s - loss: 0.2637 - acc: 0.8967     
Epoch 3/40
600/600 [==============================] - 1s - loss: 0.1671 - acc: 0.9300     
Epoch 4/40
600/600 [==============================] - 1s - loss: 0.1410 - acc: 0.9417     
Epoch 5/40
600/600 [==============================] - 1s - loss: 0.0871 - acc: 0.9733     
Epoch 6/40
600/600 [==============================] - 1s - loss: 0.0846 - acc: 0.9717     
Epoch 7/40
600/600 [==============================] - 1s - loss: 0.0964 - acc: 0.9683     
Epoch 8/40
600/600 [==============================] - 1s - loss: 0.0563 - acc: 0.9817     
Epoch 9/40
600/600 [==============================] - 1s - loss: 0.1381 - acc: 0.9467     
Epoch 10/40
600/600 [==============================] - 1s - loss: 0.0879 - acc: 0.9767     
Epoch 11/40
600/600 [==============================] - 1s - loss: 0.0567 - acc: 0.9817   [...]<jupyter_text>注意这时如果重新执行 `fit()`，`model` 会使用之前已经学到的参数继续训练，而不是重新对参数初始化。**练习**: 实现步骤4，即测试/评估模型。<jupyter_code>### START CODE HERE ### (1 line)
preds = happyModel.evaluate(x=X_test, y=Y_test)
### END CODE HERE ###
print()
print ("Loss = " + str(preds[0]))
print ("Test Accuracy = " + str(preds[1]))<jupyter_output>150/150 [==============================] - 0s     

Loss = 0.235924696922
Test Accuracy = 0.92666667064<jupyter_text>如果 `happyModel()` 有效的话，这里我们会获得一个远高于随机值（50%）的准确率。这里给一个参考值，**40 epochs，95%的测试准确率**（99%的训练准确率），微批的大小为16，使用adam优化器。如果这里没能取得很好的准确率（80%以上），下面有一些策略可以考虑尝试一下。- 尝试使用 CONV->BATCHNORM->RELU 这样的结构```pythonX = Conv2D(32, (3, 3), strides = (1, 1), name = 'conv0')(X)X = BatchNormalization(axis = 3, name = 'bn0')(X)X = Activation('relu')(X)```直到高度和宽度非常小，而通道数非常大（比如大约32）。这时候，立方体中很多有用的信息都编码在通道维度中。之后可以打平立方体，使用一个全连接层。- 在上面的结构后，使用最大池化层。这可以用来降低高度和宽度的维度。- 修改优化器，我们发现Adam在这个问题中比较好用。- 如果模型跑不起来，用内存问题，尝试降低微批大小（12是一个不错的折中值）- 跑更多轮次（epoch），知道训练集准确率进入平缓状态。即便你的模型准确率已经不错，也还是可以试着修改参数，以获得更好的效果。**注意**: 如果你对模型的超参进行调节，测试集实际上就变成了开发集，你的模型很可能会对测试集（开发集）过拟合。在本练习中，我们暂时不考虑这个问题。  3.1.3 Keras中其它一些有用的函数下面有两个Keras中的特性，可能会比较有用：- `model.summary()`: 以表格的形式打印出模型各层输入输出的大小。- `plot_model()`: 打印模型图片，甚至可以使用SVG()函数保存为".png"形式。执行下面的代码。<jupyter_code>happyModel.summary()<jupyter_output>_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 64, 64, 3)         0         
_________________________________________________________________
zero_padding2d_1 (ZeroPaddin (None, 70, 70, 3)         0         
_________________________________________________________________
conv0 (Conv2D)               (None, 64, 64, 32)        4736      
_________________________________________________________________
bn0 (BatchNormalization)     (None, 64, 64, 32)        128       
_________________________________________________________________
activation_1 (Activation)    (None, 64, 64, 32)        0         
_________________________________________________________________
max_pool (MaxPooling2D)      (None, 32, 32, 32)        0         
_________________________________________________________________
flatten_1 [...]<jupyter_text>3.2 残差网络本节我们将使用残差网络（ResNets）来构建非常深的卷积网络。理论上，深度网络可以表示非常复杂的函数，但在实践中，深层的神经网络非常难以训练。[He et al.](https://arxiv.org/pdf/1512.03385.pdf) 发现的残差网络，使得我们可以训练更加深层的神经网络。**在这个练习中，我们将会**- 实现残差网络的基本单元- 将这些基本单元组装起来，实现一个神经网络图片分类器这个练习将通过Keras来实现，首先引入相关的包。<jupyter_code>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
%matplotlib inline

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

K.clear_session()<jupyter_output><empty_output><jupyter_text>3.2.1 非常深的神经网络所面临的问题在卷积神经网络基础这一节中，我们构建了卷积神经网络。近些年来，神经网络变得越来越深层，从起初的几层（比如AlexNet）到现在的上百层。神经网络深度加深使我们可以表示更为复杂的函数。模型也可以在不同的抽象层次学习到更多特征，从边（浅层）到非常复杂的特征（深层）。然而，实际中训练更深的神经网络不一定总能产出更好的效果。一个巨大的阻碍在于梯度消失的问题：对于非常深的神经网络，梯度信号很快就会降为0，从而使得梯度下降的过程过于缓慢。具体来说，在梯度下降的过程中，随着反向传播从最终层传播回第一层，每一步都在做矩阵相乘的运算，使得梯度以幂指数级别下降到0。（在某些罕见的情况下，梯度以幂指数级别爆炸上升）因此在训练时，经常可以看到前几层网络梯度的量级很快地下降为0。    **Figure 1**   : **Vanishing gradient**  The speed of learning decreases very rapidly for the early layers as the network trains 我们将通过构建残差网络来解决这个问题。  3.2.2 构建残差网络在残差网络中，“捷径（shortcut）”或者“跳跃连接（skip connection）”使得梯度可以直接反向传播到较早的层级。   **Figure 2**   : A ResNet block showing a **skip-connection**  左侧的图展示的是网络传播的“主要路径（main path）”。右侧的图在主要路径增加了一条“捷径（shortcut）”。通过将多个残差网络单元叠加在一起，我们就可以构建非常深的网络。在上面的课程中也提到，残差网络中的捷径使其非常容易学习同一函数（identity function）。这意味着叠加残差单元，损害之前模型的可能性极小。残差网络单元主要存在两种形式，它们的区别是输入和输出维度相同还是不同。我们会实现这两种残差单元。  3.2.2.1 同一残差单元 The identity block同一残差单元是残差网络中的标准单元，对应着输入激活(比如 $a^{[l]}$) 和输出激活 (比如 $a^{[l+2]}$) 维度相同的情况。下面是同一残差单元的另一种展示形式：   **Figure 3**   : **Identity block.** Skip connection "skips over" 2 layers. 上面的路径是“捷径”，下面的路径是“主要路径”。图中我们也显式绘制了每一层中的卷积和激活步骤。为了加速计算，我们还增加了一步批量正则化。而在练习中，我们将会实现一个略强版本的同一残差单元，跳跃连接会跳过3个隐藏层，而不是2个。   **Figure 4**   : **Identity block.** Skip connection "skips over" 3 layers.每一步具体如下：主要路径的第一个组件：- 第一个卷积层 CONV2D 有 $F_1$ 个 (1,1) 的过滤器，步长为 (1,1)。使用"valid"补全，名称为`conv_name_base + '2a'`。使用0作为随机数初始化的种子。 - 第一个批量正则化 BatchNorm 针对通道坐标进行正则化，名称为 `bn_name_base + '2a'`。- 应用 ReLU 激活函数。这一层没有名称或超参。主要路径的第二个组件：- 第二个卷积层 CONV2D 有 $F_2$ 个 (f,f) 的过滤器，步长为 (1,1)。使用"same"补全，名称为`conv_name_base + '2b'`。使用0作为随机数初始化的种子。 - 第二个批量正则化 BatchNorm 针对通道坐标进行正则化，名称为 `bn_name_base + '2b'`。- 应用 ReLU 激活函数。这一层没有名称或超参。主要路径的第三个组件：- 第三个卷积层 CONV2D 有 $F_3$ 个 (1,1) 的过滤器，步长为 (1,1)。使用"valid"补全，名称为`conv_name_base + '2c'`。使用0作为随机数初始化的种子。 - 第三个批量正则化 BatchNorm 针对通道坐标进行正则化，名称为 `bn_name_base + '2c'`。注意这个组件中没有ReLU激活函数。最终步骤: - 捷径和输入累加在一起- 应用ReLU激活函数。没有名称或超参。**练习**: 实现ResNet同一单元。下面是一些参考文档：- 实现 Conv2D 步骤: [See reference](https://keras.io/layers/convolutional/conv2d)- 实现 BatchNorm 步骤: [See reference](https://faroit.github.io/keras-docs/1.2.2/layers/normalization/) (axis: Integer, 需要正则化的坐标 (通常是通道坐标))- 激活可以使用:  `Activation('relu')(X)`- 前向传播和捷径相加: [See reference](https://keras.io/layers/merge/add)<jupyter_code># GRADED FUNCTION: identity_block

def identity_block(X, f, filters, stage, block):
    """
    Implementation of the identity block as defined in Figure 3
    
    Arguments:
    X -- input tensor of shape (m, n_H_prev, n_W_prev, n_C_prev)
    f -- integer, specifying the shape of the middle CONV's window for the main path
    filters -- python list of integers, defining the number of filters in the CONV layers of the main path
    stage -- integer, used to name the layers, depending on their position in the network
    block -- string/character, used to name the layers, depending on their position in the network
    
    Returns:
    X -- output of the identity block, tensor of shape (n_H, n_W, n_C)
    """
    
    # 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)
    
    ### START CODE HERE ###
    
    # Second component of main path (≈3 lines)
    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 (≈2 lines)
    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 (≈2 lines)
    X = Add()([X, X_shortcut])
    X = Activation('relu')(X)
    
    ### END CODE HERE ###
    
    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]))<jupyter_output>out = [ 0.94822985  0.          1.16101444  2.747859    0.          1.36677003]<jupyter_text>**预期输出**:                        **out**                           [ 0.94822985  0.          1.16101444  2.747859    0.          1.36677003]              3.2.2.2 卷积残差单元 我们已经实现了同一残差单元，接下来，卷积残差单元处理的是输入和输出维度不匹配的情况。和同一残差单元不同的地方在于，捷径中现在有一层 CONV2D。   **Figure 4**   : **Convolutional block** 捷径中的 CONV2D 层就是用来调整输入 $x$ 的维度，以便最后将捷径的值加回主要路径时，两个矩阵的维度相同。捷径上的卷积层 CONV2D 不附加非线性的激活函数。它主要的用途就是为了之后的可加。卷积残差单元的细节如下：主要路径的第一个组件：- 第一个卷积层 CONV2D 有 $F_1$ 个 (1,1) 的过滤器，步长为 (s,s)。使用"valid"补全，名称为`conv_name_base + '2a'`。- 第一个批量正则化 BatchNorm 针对通道坐标进行正则化，名称为 `bn_name_base + '2a'`。- 应用 ReLU 激活函数。这一层没有名称或超参。主要路径的第二个组件：- 第二个卷积层 CONV2D 有 $F_2$ 个 (f,f) 的过滤器，步长为 (1,1)。使用"same"补全，名称为`conv_name_base + '2b'`。- 第二个批量正则化 BatchNorm 针对通道坐标进行正则化，名称为 `bn_name_base + '2b'`。- 应用 ReLU 激活函数。这一层没有名称或超参。主要路径的第三个组件：- 第三个卷积层 CONV2D 有 $F_3$ 个 (1,1) 的过滤器，步长为 (1,1)。使用"valid"补全，名称为`conv_name_base + '2c'`。- 第三个批量正则化 BatchNorm 针对通道坐标进行正则化，名称为 `bn_name_base + '2c'`。注意这个组件中没有ReLU激活函数。捷径：- CONV2D 有 $F_3$ 个 (1,1) 的过滤器，步长为 (s,s)。使用"valid"补全，名称为`conv_name_base + '1'`。- 批量正则化 BatchNorm 针对通道坐标进行正则化，名称为 `bn_name_base + '1'`。最终步骤:- 捷径和输入累加在一起- 应用ReLU激活函数。没有名称或超参。**练习**: 实现卷积残差单元。依然使用0作为随机数种子，以保证结果可以浮现。- [Conv Hint](https://keras.io/layers/convolutional/conv2d)- [BatchNorm Hint](https://keras.io/layers/normalization/batchnormalization) (axis: Integer, the axis that should be normalized (typically the features axis))- For the activation, use:  `Activation('relu')(X)`- [Addition Hint](https://keras.io/layers/merge/add)<jupyter_code># GRADED FUNCTION: convolutional_block

def convolutional_block(X, f, filters, stage, block, s = 2):
    """
    Implementation of the convolutional block as defined in Figure 4
    
    Arguments:
    X -- input tensor of shape (m, n_H_prev, n_W_prev, n_C_prev)
    f -- integer, specifying the shape of the middle CONV's window for the main path
    filters -- python list of integers, defining the number of filters in the CONV layers of the main path
    stage -- integer, used to name the layers, depending on their position in the network
    block -- string/character, used to name the layers, depending on their position in the network
    s -- Integer, specifying the stride to be used
    
    Returns:
    X -- output of the convolutional block, tensor of shape (n_H, n_W, n_C)
    """
    
    # 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


    ##### MAIN PATH #####
    # First component of main path 
    X = Conv2D(F1, (1, 1), strides = (s,s), 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)
    
    ### START CODE HERE ###

    # Second component of main path (≈3 lines)
    X = Conv2D(F2, (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 (≈2 lines)
    X = Conv2D(F3, (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)

    ##### SHORTCUT PATH #### (≈2 lines)
    X_shortcut = Conv2D(F3, (1, 1), strides = (s,s), padding = 'valid', 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)
    
    ### END CODE HERE ###
    
    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]))<jupyter_output>out = [ 0.09018463  1.23489773  0.46822017  0.0367176   0.          0.65516603]<jupyter_text>**预期输出**:                        **out**                           [ 0.09018463  1.23489773  0.46822017  0.0367176   0.          0.65516603]              3.2.3 构建残差网络（50层）这样我们就有了构建非常深的残差网络的基本单元。下面的图详细描述了这个神经网络的架构，"ID BLOCK"表示同一残差单元，"ID BLOCK x3"表示连续叠加三个同一残差单元。   **Figure 5**   : **ResNet-50 model** 这个50层的残差网络具体包括：- 使用(3, 3)的零补全对输入补全。- 第一阶段：    - 2D卷积，共64个 (7,7) 的过滤器，步长为 (2,2)。名称叫做"conv1"。    - 批量正则化，应用于输入的通道维度    - 最大池化层，(3,3)的窗口，(2,2)的步长- 第二阶段：    - 卷积残差单元，三组过滤器大小分别为[64, 64, 256]，f=3，s=1，单元名称为"a"    - 两个同一残差单元，三组过滤器大小分别为[64, 64, 256]，f=3，单元名称为"b"和"c"- 第三阶段：    - 卷积残差单元，三组过滤器大小分别为[128, 128, 512]，f=3，s=2，单元名称为"a"    - 三个同一残差单元，三组过滤器大小分别为[128, 128, 512]，f=3，单元名称为"b"，"c"，"d"- 第四阶段：    - 卷积残差单元，三组过滤器大小分别为[256, 256, 1024]，f=3，s=2，单元名称为"a"    - 五个同一残差单元，三组过滤器大小分别为[256, 256, 1024]，f=3，单元名称为"b"，"c"，"d"，"e"，"f"- 第五阶段：    - 卷积残差单元，三组过滤器大小分别为[512, 512, 2048]，f=3，s=2，单元名称为"a"    - 两个同一残差单元，三组过滤器大小分别为[512, 512, 2048]，f=3，单元名称为"b"，"c"- 2D平均池化层，(2,2)的窗口，名称为"avg_pool"- Flatten，打平，没有超参，也没有名称- 全连接层，使用softmax激活函数，将输入转为多元分类数目的输出。名称为'fc' + str(classes)**练习**: 实现上图所描述的50层残差网络。平均池化相关文档: - Average pooling [see reference](https://keras.io/layers/pooling/averagepooling2d)函数文档参考:- Conv2D: [See reference](https://keras.io/layers/convolutional/conv2d)- BatchNorm: [See reference](https://keras.io/layers/normalization/batchnormalization) (axis: Integer, the axis that should be normalized (typically the features axis))- Zero padding: [See reference](https://keras.io/layers/convolutional/zeropadding2d)- Max pooling: [See reference](https://keras.io/layers/pooling/maxpooling2d)- Fully conected layer: [See reference](https://keras.io/layers/core/dense)- Addition: [See reference](https://keras.io/layers/merge/add)<jupyter_code># GRADED FUNCTION: ResNet50

def ResNet50(input_shape = (64, 64, 3), classes = 6):
    """
    Implementation of the popular ResNet50 the following architecture:
    CONV2D -> BATCHNORM -> RELU -> MAXPOOL -> CONVBLOCK -> IDBLOCK*2 -> CONVBLOCK -> IDBLOCK*3
    -> CONVBLOCK -> IDBLOCK*5 -> CONVBLOCK -> IDBLOCK*2 -> AVGPOOL -> TOPLAYER

    Arguments:
    input_shape -- shape of the images of the dataset
    classes -- integer, number of classes

    Returns:
    model -- a Model() instance in Keras
    """
    
    # 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')

    ### START CODE HERE ###

    # Stage 3 (≈4 lines)
    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 (≈6 lines)
    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 (≈3 lines)
    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 (≈1 line). Use "X = AveragePooling2D(...)(X)"
    X = AveragePooling2D((2, 2))(X)
    
    ### END CODE HERE ###

    # 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<jupyter_output><empty_output><jupyter_text>执行下面的代码来构建模型图。<jupyter_code>model = ResNet50(input_shape = (64, 64, 3), classes = 6)
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])<jupyter_output><empty_output><jupyter_text>这样模型就可以准备开始训练了，让我们载入数据集   **Figure 6**   : **SIGNS dataset**<jupyter_code>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 = 100, batch_size = 32)<jupyter_output>Epoch 1/100
1080/1080 [==============================] - 33s - loss: 2.9785 - acc: 0.2472    
Epoch 2/100
1080/1080 [==============================] - 32s - loss: 1.6969 - acc: 0.5028    
Epoch 3/100
1080/1080 [==============================] - 32s - loss: 1.3083 - acc: 0.7130    
Epoch 4/100
1080/1080 [==============================] - 32s - loss: 2.2020 - acc: 0.4519    
Epoch 5/100
1080/1080 [==============================] - 32s - loss: 1.2940 - acc: 0.6565    
Epoch 6/100
1080/1080 [==============================] - 32s - loss: 1.1647 - acc: 0.7056    
Epoch 7/100
1080/1080 [==============================] - 32s - loss: 0.9057 - acc: 0.8111    
Epoch 8/100
1080/1080 [==============================] - 32s - loss: 0.7449 - acc: 0.7806    
Epoch 9/100
1080/1080 [==============================] - 32s - loss: 0.4622 - acc: 0.8583    
Epoch 10/100
1080/1080 [==============================] - 32s - loss: 0.7148 - acc: 0.7944    
Epoch 11/100
1080/1080 [==============================] - 3[...]<jupyter_text>我们来看一下训练后的模型，在测试集上表现如何。<jupyter_code>preds = model.evaluate(X_test, Y_test)
print ("Loss = " + str(preds[0]))
print ("Test Accuracy = " + str(preds[1]))
model.summary()<jupyter_output>____________________________________________________________________________________________________
Layer (type)                     Output Shape          Param #     Connected to                     
====================================================================================================
input_1 (InputLayer)             (None, 64, 64, 3)     0                                            
____________________________________________________________________________________________________
zero_padding2d_1 (ZeroPadding2D) (None, 70, 70, 3)     0           input_1[0][0]                    
____________________________________________________________________________________________________
conv1 (Conv2D)                   (None, 32, 32, 64)    9472        zero_padding2d_1[0][0]           
____________________________________________________________________________________________________
bn_conv1 (BatchNormalization)    (None, 32, 32, 64)    256         conv1[0][0]             [...]
{'fraction_non_alphanumeric': 0.16427190721649484, 'fraction_numerical': 0.05280283505154639, 'mean_word_length': 4.732409972299169, 'pattern_counts': {'":': 0, '<': 52, '<?xml version=': 0, '>': 71, 'https://': 15, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '643932', 'license': 'MIT', 'n_tokens_mistral': 16013, 'n_tokens_neox': 16321, 'n_words': 2398, 'path': 'Deep Convolutional Models Case Studies.ipynb', 'repo_name': 'reata/DeepLearning', 'size': '31040'}