深入理解吴恩达深度学习(01神经网络和深度学习 第四周深层神经网络

深入理解吴恩达深度学习(01神经网络和深度学习 第四周深层神经网络)

      • 4.1 深层神经网络(Deep L-layer neural network)
      • 4.2 前向传播和反向传播(Forward and backward propagation)
      • 4.3 深层网络中的前向传播(Forward propagation in a Deep Network)
      • 4.4 核对矩阵的维数(Getting your matrix dimensions right)
      • 4.5 为什么使用深层表示?(Why deep representations?)
      • 4.6 搭建神经网络块(Building blocks of deep neural networks)
      • 4.7 参数VS超参数(Parameters vs Hyperparameters)
      • 4.8 深度学习和大脑的关联性(What does this have to do with the brain?)

4.1 深层神经网络(Deep L-layer neural network)

复习下前三周的课的内容:

1.逻辑回归,结构如下图左边。一个隐藏层的神经网络,结构下图右边:

深入理解吴恩达深度学习(01神经网络和深度学习 第四周深层神经网络_第1张图片

注意,神经网络的层数是这么定义的:从左到右,由0开始定义,比如上边右图, x 1 {x}_{1} x1 x 2 {x}_{2} x2 x 3 {x}_{3} x3,这层是第0层,这层左边的隐藏层是第1层,由此类推。如下图左边是两个隐藏层的神经网络,右边是5个隐藏层的神经网络。

深入理解吴恩达深度学习(01神经网络和深度学习 第四周深层神经网络_第2张图片

严格上来说逻辑回归也是一个一层的神经网络,而上边右图一个深得多的模型,浅与深仅仅是指一种程度。记住以下要点:

有一个隐藏层的神经网络,就是一个两层神经网络。记住当我们算神经网络的层数时,我们不算输入层,我们只算隐藏层和输出层。
定义总结:

  1. 有神经元的层为隐藏层。
  2. 隐藏层从左到右数依次是第一层,第二层……
  3. 每一层的神经元个数分别为 n [ 1 ] , n [ 2 ] … … n [ L ] n^{[1]},n^{[2]}……n^{[L]} n[1],n[2]n[L]
  4. 每一层的输出结果分别为 a [ 1 ] , a [ 2 ] … … a [ L ] a^{[1]},a^{[2]}……a^{[L]} a[1],a[2]a[L]

代码:
根据输入的每层神经元个数,设置每层神经网络的参数:

# GRADED FUNCTION: initialize_parameters_deep

# 根据输入的隐藏神经元个数数组初始化每层的参数
def initialize_parameters_deep(layer_dims):
    """
    Arguments:
    layer_dims -- python array (list) containing the dimensions of each layer in our network
    
    Returns:
    parameters -- python dictionary containing your parameters "W1", "b1", ..., "WL", "bL":
                    Wl -- weight matrix of shape (layer_dims[l], layer_dims[l-1])
                    bl -- bias vector of shape (layer_dims[l], 1)
    """
    
    np.random.seed(3)
    parameters = {}
    L = len(layer_dims)            # number of layers in the network

    for l in range(1, L):
        ### START CODE HERE ### (≈ 2 lines of code)
        ### 随机初始化W
        parameters['W' + str(l)] = np.random.rand(layer_dims[l],layer_dims[l-1])*0.01
        parameters['b' + str(l)] = np.zeros((layer_dims[l],1))
        ### END CODE HERE ###
        
        assert(parameters['W' + str(l)].shape == (layer_dims[l], layer_dims[l-1]))
        assert(parameters['b' + str(l)].shape == (layer_dims[l], 1))

        
    return parameters

为什么我们要使用深层神经网络?

在过去的几年中,DLI(深度学习学院 deep learning institute)已经意识到有一些函数,只有非常深的神经网络能学会,而更浅的模型则办不到。(说白了就是目标模型需要拟合的函数太复杂,现有简单模型不能够拟合,因此需要深层神经网络提升模型的学习能力) 尽管对于任何给定的问题很难去提前预测到底需要多深的神经网络,所以先去尝试逻辑回归,尝试一层然后两层隐含层,然后把隐含层的数量看做是另一个可以自由选择大小的超参数,然后再保留交叉验证数据上评估,或者用你的开发集来评估。

4.2 前向传播和反向传播(Forward and backward propagation)

之前我们学习了构成深度神经网络的基本模块,比如每一层都有前向传播步骤以及一个相反的反向传播步骤,这次视频我们讲讲如何实现这些步骤。
深入理解吴恩达深度学习(01神经网络和深度学习 第四周深层神经网络_第3张图片

先讲前向传播,前向传播对应图中的紫色部分,输入 a [ l − 1 ] {a}^{[l-1]} a[l1],输出是 a [ l ] {a}^{[l]} a[l],缓存为 z [ l ] {z}^{[l]} z[l];从实现的角度来说我们可以缓存下 w [ l ] {w}^{[l]} w[l] b [ l ] {b}^{[l]} b[l],这样更容易在不同的环节中调用函数。

所以前向传播的步骤可以写成: z [ l ] = W [ l ] ⋅ a [ l − 1 ] + b [ l ] {z}^{[l]}={W}^{[l]}\cdot{a}^{[l-1]}+{b}^{[l]} z[l]=W[l]a[l1]+b[l]

a [ l ] = g [ l ] ( z [ l ] ) {{a}^{[l]}}={{g}^{[l]}}\left( {{z}^{[l]}}\right) a[l]=g[l](z[l])

向量化实现过程可以写成: z [ l ] = W [ l ] ⋅ A [ l − 1 ] + b [ l ] {z}^{[l]}={W}^{[l]}\cdot {A}^{[l-1]}+{b}^{[l]} z[l]=W[l]A[l1]+b[l]

A [ l ] = g [ l ] ( Z [ l ] ) {A}^{[l]}={g}^{[l]}({Z}^{[l]}) A[l]=g[l](Z[l])

前向传播需要喂入 A [ 0 ] {A}^{[0]} A[0]也就是 X X X,来初始化;初始化的是第一层的输入值。 a [ 0 ] {a}^{[0]} a[0]对应于一个训练样本的输入特征,而 A [ 0 ] {{A}^{[0]}} A[0]对应于一整个训练样本的输入特征,所以这就是这条链的第一个前向函数的输入,重复这个步骤就可以从左到右计算前向传播。

下面讲反向传播的步骤,,前向传播对应图中的红部分

输入为 d a [ l ] {{da}^{[l]}} da[l],输出为 d a [ l − 1 ] {{da}^{[l-1]}} da[l1] d w [ l ] {{dw}^{[l]}} dw[l], d b [ l ] {{db}^{[l]}} db[l]

所以反向传播的步骤可以写成:

(1) d z [ l ] = d a [ l ] ∗ g [ l ] ′ ( z [ l ] ) d{{z}^{[l]}}=d{{a}^{[l]}}*{{g}^{[l]}}'( {{z}^{[l]}}) dz[l]=da[l]g[l](z[l])

(2) d w [ l ] = d z [ l ] ⋅ a [ l − 1 ]   d{{w}^{[l]}}=d{{z}^{[l]}}\cdot{{a}^{[l-1]}}~ dw[l]=dz[l]a[l1] 

(3) d b [ l ] = d z [ l ]    d{{b}^{[l]}}=d{{z}^{[l]}}~~ db[l]=dz[l]  

(4) d a [ l − 1 ] = w [ l ] T ⋅ d z [ l ] d{{a}^{[l-1]}}={{w}^{\left[ l \right]T}}\cdot {{dz}^{[l]}} da[l1]=w[l]Tdz[l]

(5) d z [ l ] = w [ l + 1 ] T d z [ l + 1 ] ⋅   g [ l ] ′ ( z [ l ] )   d{{z}^{[l]}}={{w}^{[l+1]T}}d{{z}^{[l+1]}}\cdot \text{ }{{g}^{[l]}}'( {{z}^{[l]}})~ dz[l]=w[l+1]Tdz[l+1] g[l](z[l]) 

式子(5)由式子(4)带入式子(1)得到,前四个式子就可实现反向函数。

向量化实现过程可以写成:

(6) d Z [ l ] = d A [ l ] ∗ g [ l ] ′ ( Z [ l ] )    d{{Z}^{[l]}}=d{{A}^{[l]}}*{{g}^{\left[ l \right]}}'\left({{Z}^{[l]}} \right)~~ dZ[l]=dA[l]g[l](Z[l])  

(7) d W [ l ] = 1 m d Z [ l ] ⋅ A [ l − 1 ] T d{{W}^{[l]}}=\frac{1}{m}\text{}d{{Z}^{[l]}}\cdot {{A}^{\left[ l-1 \right]T}} dW[l]=m1dZ[l]A[l1]T

(8) d b [ l ] = 1 m   n p . s u m ( d z [ l ] , a x i s = 1 , k e e p d i m s = T r u e ) d{{b}^{[l]}}=\frac{1}{m}\text{ }np.sum(d{{z}^{[l]}},axis=1,keepdims=True) db[l]=m1 np.sum(dz[l],axis=1,keepdims=True)

(9) d A [ l − 1 ] = W [ l ] T . d Z [ l ] d{{A}^{[l-1]}}={{W}^{\left[ l \right]T}}.d{{Z}^{[l]}} dA[l1]=W[l]T.dZ[l]

总结一下:

深入理解吴恩达深度学习(01神经网络和深度学习 第四周深层神经网络_第4张图片
第一层你可能有一个ReLU激活函数,第二层为另一个ReLU激活函数,第三层可能是sigmoid函数(如果你做二分类的话),输出值为,用来计算损失;这样你就可以向后迭代进行反向传播求导来求 d w [ 3 ] {{dw}^{[3]}} dw[3] d b [ 3 ] {{db}^{[3]}} db[3] d w [ 2 ] {{dw}^{[2]}} dw[2] d b [ 2 ] {{db}^{[2]}} db[2] d w [ 1 ] {{dw}^{[1]}} dw[1] d b [ 1 ] {{db}^{[1]}} db[1]。在计算的时候,缓存会把 z [ 1 ] {{z}^{[1]}} z[1] z [ 2 ] {{z}^{[2]}} z[2] z [ 3 ] {{z}^{[3]}} z[3]传递过来,然后回传 d a [ 2 ] {{da}^{[2]}} da[2] d a [ 1 ] {{da}^{[1]}} da[1] ,可以用来计算 d a [ 0 ] {{da}^{[0]}} da[0],但我们不会使用它,这里讲述了一个三层网络的前向和反向传播,还有一个细节没讲就是前向递归——用输入数据来初始化,那么反向递归(使用Logistic回归做二分类)——对 A [ l ] {{A}^{[l]}} A[l] 求导。

忠告:补补微积分和线性代数,多推导,多实践。

4.3 深层网络中的前向传播(Forward propagation in a Deep Network)

跟往常一样,我们先来看对其中一个训练样本 x x x如何应用前向传播,之后讨论向量化的版本。

第一层需要计算 z [ 1 ] = w [ 1 ] x + b [ 1 ] {{z}^{[1]}}={{w}^{[1]}}x+{{b}^{[1]}} z[1]=w[1]x+b[1] a [ 1 ] = g [ 1 ] ( z [ 1 ] ) {{a}^{[1]}}={{g}^{[1]}} {({z}^{[1]})} a[1]=g[1](z[1]) x x x可以看做 a [ 0 ] {{a}^{[0]}} a[0]

第二层需要计算 z [ 2 ] = w [ 2 ] a [ 1 ] + b [ 2 ] {{z}^{[2]}}={{w}^{[2]}}{{a}^{[1]}}+{{b}^{[2]}} z[2]=w[2]a[1]+b[2] a [ 2 ] = g [ 2 ] ( z [ 2 ] ) {{a}^{[2]}}={{g}^{[2]}} {({z}^{[2]})} a[2]=g[2](z[2])

以此类推,

第四层为 z [ 4 ] = w [ 4 ] a [ 3 ] + b [ 4 ] {{z}^{[4]}}={{w}^{[4]}}{{a}^{[3]}}+{{b}^{[4]}} z[4]=w[4]a[3]+b[4] a [ 4 ] = g [ 4 ] ( z [ 4 ] ) {{a}^{[4]}}={{g}^{[4]}} {({z}^{[4]})} a[4]=g[4](z[4])

前向传播可以归纳为多次迭代 z [ l ] = w [ l ] a [ l − 1 ] + b [ l ] {{z}^{[l]}}={{w}^{[l]}}{{a}^{[l-1]}}+{{b}^{[l]}} z[l]=w[l]a[l1]+b[l] a [ l ] = g [ l ] ( z [ l ] ) {{a}^{[l]}}={{g}^{[l]}} {({z}^{[l]})} a[l]=g[l](z[l])

向量化实现过程可以写成:

Z [ l ] = W [ l ] a [ l − 1 ] + b [ l ] {{Z}^{[l]}}={{W}^{[l]}}{{a}^{[l-1]}}+{{b}^{[l]}} Z[l]=W[l]a[l1]+b[l] A [ l ] = g [ l ] ( Z [ l ] ) {{A}^{[l]}}={{g}^{[l]}}{({Z}^{[l]})} A[l]=g[l](Z[l]) ( A [ 0 ] = X ) {{A}^{[0]}} = X) A[0]=X)

这里只能用一个显式for循环, l l l从1到 L L L,然后一层接着一层去计算。下一节讲的是避免代码产生BUG,我所做的其中一件非常重要的工作。
实现代码:
前向传播实现过程
1.线性前向传播

# GRADED FUNCTION: 线性前向传播
# 根据当前的WAB计算出Z值
def linear_forward(A, W, b):
    ### START CODE HERE ### (≈ 1 line of code)
    Z = np.dot(W,A)+b
    ### END CODE HERE ###
    
    assert(Z.shape == (W.shape[0], A.shape[1]))
    cache = (A, W, b)
    
    return Z, cache
  1. 带激活函数的前向传播
# GRADED FUNCTION: linear_activation_forward
# 根据当前的激活函数,前向计算出A值,并存储到 activation_cache中
def linear_activation_forward(A_prev, W, b, activation):
    """
    Implement the forward propagation for the LINEAR->ACTIVATION layer
    Arguments:
    A_prev -- 前一层的激活函数输出值
    W --      本层的W值,维度  (n(L-1),n(L))
    b --      本层的b偏置数值,(n(L),1)维
    activation -- 计算本层输出值的激活函数,可选择使用relu或者sigmoid函数
    Returns:
    A --     本层的输出值
    cache -- 包含线性临时存储"linear_cache"和输出临时存储"activation_cache"的字典,用处是保存当前计算状态,为后来计算反向传播提供条件
    """
    
    if activation == "sigmoid":
        # Inputs: "A_prev, W, b". Outputs: "A, activation_cache".
        ### START CODE HERE ### (≈ 2 lines of code)
        Z, linear_cache = np.dot(A_prev, W)+ b
        A, activation_cache = sigmoid(Z)
        ### END CODE HERE ###
    
    elif activation == "relu":
        # Inputs: "A_prev, W, b". Outputs: "A, activation_cache".
        ### START CODE HERE ### (≈ 2 lines of code)
        Z, linear_cache = linear_forward(A_prev, W, b)
        A, activation_cache = relu(Z)
        ### END CODE HERE ###
    
    assert (A.shape == (W.shape[0], A_prev.shape[1]))
    cache = (linear_cache, activation_cache)

    return A, cache

3.计算每层前向传播的输出值

# GRADED FUNCTION: L_model_forward
# 逐层计算前向传播,其中最后一层使用sigmoid函数计算,其余层使用relu函数计算
def L_model_forward(X, parameters):
    """
    逐层计算前向传播,其中最后一层使用sigmoid函数计算,其余层使用relu函数计算
    
    Arguments:
    X -- data, numpy array of shape (input size, number of examples)
    parameters --保存每一层的W和B参数
    
    Returns:
    AL -- last post-activation value
    caches -- list of caches containing:
                every cache of linear_relu_forward() (there are L-1 of them, indexed from 0 to L-2)
                the cache of linear_sigmoid_forward() (there is one, indexed L-1)
    """

    ## print(str(parameters))
    caches = []
    A = X
    L = len(parameters) // 2                  # number of layers in the neural network
    
    # Implement [LINEAR -> RELU]*(L-1). Add "cache" to the "caches" list.
    for l in range(1, L):
        A_prev = A 
        ### START CODE HERE ### (≈ 2 lines of code)
        A, cache = linear_activation_forward(A_prev, parameters["W"+str(l)], parameters["b"+str(l)], "relu")
        caches.append(cache) 
        ### END CODE HERE ###
    
    # Implement LINEAR -> SIGMOID. Add "cache" to the "caches" list.
    ### START CODE HERE ### (≈ 2 lines of code)
    ### 这里不能够使用A_prev了,只能使用上一层的输入
    AL, cache = linear_activation_forward(A,  parameters["W"+str(L)], parameters["b"+str(L)], "sigmoid")
    caches.append(cache) 
    ### END CODE HERE ###
    
    assert(AL.shape == (1,X.shape[1]))
            
    return AL, caches

4.4 核对矩阵的维数(Getting your matrix dimensions right)

我们之前一直强调要深入理解矩阵的维度,以下是维度的总结:

  1. w w w的维度是(本层的维数,前一层的维数),即 w [ l ] {{w}^{[l]}} w[l]: ( n [ l ] {{n}^{[l]}} n[l], n [ l − 1 ] {{n}^{[l-1]}} n[l1]);
  2. b b b的维度是(本层层的维数,1) b [ l ] {{b}^{[l]}} b[l] : ( n [ l ] , 1 ) {{n}^{[l]}},1) n[l],1)
  3. z z z的维度是 (本层的维度,m) z [ l ] {{z}^{[l]}} z[l]= ( n [ l ] , m ) {{n}^{[l]}},m) n[l],m)
    解 释 : 考 虑 第 一 层 情 况 : 输 入 X 也 就 是 A [ 0 ] 维 度 ( n [ 0 ] , m ) , Z [ 0 ] = W X , 因 此 是 ( n [ 1 ] , n [ 0 ] ) 点 乘 ( n [ 0 ] , m ) = ( n [ 1 ] , m ) 的 矩 阵 , 因 此 z [ 1 ] 解释:考虑第一层情况:输入X也就是A^{[0]}维度(n^{[0]},m),Z^{[0]}=WX,因此是(n^{[1]},n^{[0]})点乘(n^{[0]},m)=(n^{[1]},m)的矩阵,因此{{z}^{[1]}} XA[0](n[0],m)Z[0]=WX,(n[1],n[0])(n[0],m)=(n[1],m)z[1]= ( n [ 1 ] , m ) {{n}^{[1]}},m) n[1],m)
  4. A A A的维度是 (本层的维度,m) A [ l ] {{A}^{[l]}} A[l]= ( n [ l ] , m ) {{n}^{[l]}},m) n[l],m)
    解释:不管激活函数是什么,因为激活函数不会进行点乘,或者reshape操作,所以他们不会改变矩阵的维度,因此 A [ l ] = z [ 1 ] = ( n [ l ] , m ) {{A}^{[l]}}={z}^{[1]}= ({{n}^{[l]}},m) A[l]=z[1]=(n[l],m)

在你做深度神经网络的反向传播时,一定要确认所有的矩阵维数是前后一致的,可以大大提高代码通过率。下一节我们讲为什么深层的网络在很多问题上比浅层的好。

4.5 为什么使用深层表示?(Why deep representations?)

我们都知道深度神经网络能解决好多问题,其实并不需要很大的神经网络,但是得有深度,得有比较多的隐藏层,这是为什么呢?我们一起来看几个例子来帮助理解,为什么深度神经网络会很好用。

首先,深度网络在计算什么?

深入理解吴恩达深度学习(01神经网络和深度学习 第四周深层神经网络_第5张图片

首先,深度网络究竟在计算什么?如果你在建一个人脸识别或是人脸检测系统,深度神经网络所做的事就是,当你输入一张脸部的照片,然后你可以把深度神经网络的第一层,当成一个特征探测器或者边缘探测器。在这个例子里,我会建一个大概有20个隐藏单元的深度神经网络,是怎么针对这张图计算的。隐藏单元就是这些图里这些小方块(第一张大图),举个例子,这个小方块(第一行第一列)就是一个隐藏单元,它会去找这张照片里“|”边缘的方向。那么这个隐藏单元(第四行第四列),可能是在找(“—”)水平向的边缘在哪里。之后的课程里,我们会讲专门做这种识别的卷积神经网络,到时候会细讲,为什么小单元是这么表示的。你可以先把神经网络的第一层当作看图,然后去找这张照片的各个边缘。我们可以把照片里组成边缘的像素们放在一起看,然后它可以把被探测到的边缘组合成面部的不同部分(第二张大图)。比如说,可能有一个神经元会去找眼睛的部分,另外还有别的在找鼻子的部分,然后把这许多的边缘结合在一起,就可以开始检测人脸的不同部分。最后再把这些部分放在一起,比如鼻子眼睛下巴,就可以识别或是探测不同的人脸(第三张大图)。

你可以直觉上把这种神经网络的前几层当作探测简单的函数,比如边缘,之后把它们跟后几层结合在一起,那么总体上就能学习更多复杂的函数。这些图的意义,我们在学习卷积神经网络的时候再深入了解。还有一个技术性的细节需要理解的是,边缘探测器其实相对来说都是针对照片中非常小块的面积。就像这块(第一行第一列),都是很小的区域。面部探测器就会针对于大一些的区域,但是主要的概念是,一般你会从比较小的细节入手,比如边缘,然后再一步步到更大更复杂的区域,比如一只眼睛或是一个鼻子,再把眼睛鼻子装一块组成更复杂的部分。

在这里插入图片描述

这种从简单到复杂的金字塔状表示方法或者组成方法,也可以应用在图像或者人脸识别以外的其他数据上。比如当你想要建一个语音识别系统的时候,需要解决的就是如何可视化语音,比如你输入一个音频片段,那么神经网络的第一层可能就会去先开始试着探测比较低层次的音频波形的一些特征,比如音调是变高了还是低了,分辨白噪音,咝咝咝的声音,或者音调,可以选择这些相对程度比较低的波形特征,然后把这些波形组合在一起就能去探测声音的基本单元。在语言学中有个概念叫做音位,比如说单词ca,c的发音,“嗑”就是一个音位,a的发音“啊”是个音位,t的发音“特”也是个音位,有了基本的声音单元以后,组合起来,你就能识别音频当中的单词,单词再组合起来就能识别词组,再到完整的句子。
深入理解吴恩达深度学习(01神经网络和深度学习 第四周深层神经网络_第6张图片
**所以深度神经网络的这许多隐藏层中,较早的前几层能学习一些低层次的简单特征,等到后几层,就能把简单的特征结合起来,去探测更加复杂的东西。**比如你录在音频里的单词、词组或是句子,然后就能运行语音识别了。同时我们所计算的之前的几层,也就是相对简单的输入函数,比如图像单元的边缘什么的。到网络中的深层时,你实际上就能做很多复杂的事,比如探测面部或是探测单词、短语或是句子。

有些人喜欢把深度神经网络和人类大脑做类比,这些神经科学家觉得人的大脑也是先探测简单的东西,比如你眼睛看得到的边缘,然后组合起来才能探测复杂的物体,比如脸。这种深度学习和人类大脑的比较,有时候比较危险。但是不可否认的是,我们对大脑运作机制的认识很有价值,有可能大脑就是先从简单的东西,比如边缘着手,再组合成一个完整的复杂物体,这类简单到复杂的过程,同样也是其他一些深度学习的灵感来源,之后的视频我们也会继续聊聊人类或是生物学理解的大脑。

Small:隐藏单元的数量相对较少

Deep:隐藏层数目比较多

深层的网络隐藏单元数量相对较少,隐藏层数目较多,如果浅层的网络想要达到同样的计算结果则需要指数级增长的单元数量才能达到。

tips:个人觉得关于电路理论的直观感觉比较靠谱点
另外一个,关于神经网络为何有效的理论,来源于电路理论,它和你能够用电路元件计算哪些函数有着分不开的联系。根据不同的基本逻辑门,譬如与门、或门、非门。在非正式的情况下,这些函数都可以用相对较小,但很深的神经网络来计算,小在这里的意思是隐藏单元的数量相对比较小,但是如果你用浅一些的神经网络计算同样的函数,也就是说在我们不能用很多隐藏层时,你会需要成指数增长的单元数量才能达到同样的计算结果。
深入理解吴恩达深度学习(01神经网络和深度学习 第四周深层神经网络_第7张图片
我再来举个例子,用没那么正式的语言介绍这个概念。假设你想要对输入特征计算异或或是奇偶性,你可以算 x 1 X O R x 2 X O R x 3 X O R … … x n x_{1}XOR x_{2} XOR x_{3} XOR ……x_{n} x1XORx2XORx3XORxn,假设你有 n n n或者 n x n_{x} nx个特征,如果你画一个异或的树图,先要计算 x 1 x_{1} x1 x 2 x_{2} x2的异或,然后是 x 3 x_{3} x3 x 4 x_{4} x4。技术上来说如果你只用或门,还有非门的话,你可能会需要几层才能计算异或函数,但是用相对小的电路,你应该就可以计算异或了。然后你可以继续建这样的一个异或树图(上图左),那么你最后会得到这样的电路来输出结果 y y y y ^ = y \hat{y}=y y^=y,也就是输入特征的异或,或是奇偶性,要计算异或关系。这种树图对应网络的深度应该是 O ( l o g ( n ) ) O(log(n)) O(log(n)),那么节点的数量和电路部件,或是门的数量并不会很大,你也不需要太多门去计算异或。

但是如果你不能使用多隐层的神经网络的话,在这个例子中隐层数为 O ( l o g ( n ) ) O(log(n)) O(log(n)),比如你被迫只能用单隐藏层来计算的话,这里全部都指向从这些隐藏单元到后面这里,再输出 y y y,那么要计算奇偶性,或者异或关系函数就需要这一隐层(上图右方框部分)的单元数呈指数增长才行,因为本质上来说你需要列举耗尽 2 n 2^{n} 2n种可能的配置,或是 2 n 2^{n} 2n种输入比特的配置。异或运算的最终结果是1或0,那么你最终就会需要一个隐藏层,其中单元数目随输入比特指数上升。精确的说应该是 2 n − 1 2^{n-1} 2n1个隐藏单元数,也就是 O ( 2 n ) O(2^{n}) O(2n)

我希望这能让你有点概念,意识到有很多数学函数用深度网络计算比浅网络要容易得多,我个人倒是认为这种电路理论,对训练直觉思维没那么有用,但这个结果人们还是经常提到的,用来解释为什么需要更深层的网络。

除了这些原因,说实话,我认为“深度学习”这个名字挺唬人的,这些概念以前都统称为有很多隐藏层的神经网络,但是深度学习听起来多高大上,太深奥了,对么?这个词流传出去以后,这是神经网络的重新包装或是多隐藏层神经网络的重新包装,激发了大众的想象力。抛开这些公关概念重新包装不谈,深度网络确实效果不错,有时候人们还是会按照字面意思钻牛角尖,非要用很多隐层。但是当我开始解决一个新问题时,我通常会从logistic回归开始,再试试一到两个隐层,把隐藏层数量当作参数、超参数一样去调试,这样去找比较合适的深度。但是近几年以来,有一些人会趋向于使用非常非常深邃的神经网络,比如好几打的层数,某些问题中只有这种网络才是最佳模型。

这就是我想讲的,为什么深度学习效果拔群的直觉解释,现在我们来看看除了正向传播以外,反向传播该怎么具体实现。

4.6 搭建神经网络块(Building blocks of deep neural networks)

前面4.3节已经使用python代码实现了如何搭建前向传播,下面我们分析如何实现反向传播过程,整个程序的实现逻辑是这样的:

逐层计算A直到最后一层
从最后一层倒序逐层计算dw和db
计算下一步的 w=w - 学习率*dw
循环n次
输入X
根据最后一层输出AL计算代价函数J
前向传播结束
得出每一层的dw和db
计算下一步的 b=b - 学习率*db
更新每一层的w和b
反向传播结束

从流程图我们知道,反向传播我们需要计算出每一层的dw和db,也就是损失函数对w和b的偏导数。(我们的目标是减小损失函数的值,因此对损失函数求偏导呀,啊喂!)
好吧,现在我们来讨论下,损失函数的偏导怎么求:(幼小,可怜,又无助)

  1. 首先,代价函数长这样,这货是m个样本分别计算出来的代价函数的和:
    J ( W [ 1 ] , b [ 1 ] , W [ 2 ] , b [ 2 ] ) = 1 m ∑ i = 1 m L ( y ^ , y ) J(W^{[1]},b^{[1]},W^{[2]},b^{[2]}) = {\frac{1}{m}}\sum_{i=1}^mL(\hat{y}, y) J(W[1],b[1],W[2],b[2])=m1i=1mL(y^,y)

  2. 然后代价函数长这样,这货代表输出是y的条件下,输出是 y ^ \hat{y} y^的概率求个log之后再取负值,这货越小说明模型输出越 y ^ \hat{y} y^接近y,因此模型输出的结果越好:
    L ( y ^ , y ) = − y log ⁡ ( y ^ ) − ( 1 − y ) log ⁡ ( 1 − y ^ ) L\left( \hat{y},y \right)=-y\log(\hat{y})-(1-y)\log (1-\hat{y}) L(y^,y)=ylog(y^)(1y)log(1y^)

代码:代价函数J的计算实现

def compute_cost(AL, Y):
    """
    Implement the cost function defined by equation (7).

    Arguments:
    AL -- probability vector corresponding to your label predictions, shape (1, number of examples)
    Y -- true "label" vector (for example: containing 0 if non-cat, 1 if cat), shape (1, number of examples)

    Returns:
    cost -- cross-entropy cost
    """
    m = Y.shape[1]
    # Compute loss from aL and y.
    cost = (1./m) * (-np.dot(Y,np.log(AL).T) - np.dot(1-Y, np.log(1-AL).T))
    cost = np.squeeze(cost)      # To make sure your cost's shape is what we expect (e.g. this turns [[17]] into 17).
    assert(cost.shape == ())
    return cost
  1. 再来,sigmoid函数的导数长这样:
    σ ( z ) = 1 1 + e − z ∣ σ ( z ) ′ = σ ( z ) ( 1 − σ ( z ) ) \sigma(z) = \frac{1}{{1 + e}^{- z}}|\sigma(z)^{'}= \sigma(z)(1-\sigma(z)) σ(z)=1+ez1σ(z)=σ(z)(1σ(z))
def sigmoid_backward(dA, cache):
    """
    Implement the backward propagation for a single SIGMOID unit.

    Arguments:
    dA -- post-activation gradient, of any shape
    cache -- 'Z' where we store for computing backward propagation efficiently

    Returns:
    dZ -- Gradient of the cost with respect to Z
    """
    Z = cache
    s = 1/(1+np.exp(-Z))
    dZ = dA * s * (1-s)
    assert (dZ.shape == Z.shape)
    return dZ
  1. 继续,不要停,relu函数的导数长这样:
    R e l u ( z ) ′ = { 1 , z > 0 0 , z < 0 u n d e f i n e d , z = 0 Relu(z)^{'}=\begin{cases}{1,z>0}\\{0,z<0}\\ {undefined,z=0}\end{cases} Relu(z)=1,z>00,z<0undefined,z=0
def relu_backward(dA, cache):
    """
    Implement the backward propagation for a single RELU unit.

    Arguments:
    dA -- post-activation gradient, of any shape
    cache -- 'Z' where we store for computing backward propagation efficiently

    Returns:
    dZ -- Gradient of the cost with respect to Z
    """
    Z = cache
    dZ = np.array(dA, copy=True) # just converting dz to a correct object.
    # When z <= 0, you should set dz to 0 as well. 
    dZ[Z <= 0] = 0
    assert (dZ.shape == Z.shape)
    return d
  1. 啊,刚到兴奋点。我们先求出最后一层(激活函数是sigmoid)的导数
    z [ L ] = W [ L ] a [ L − 1 ] + b [ L ] z^{[L]}=W^{[L]}a^{[L-1]}+b^{[L]} z[L]=W[L]a[L1]+b[L]
    a [ L ] = σ ( z [ L ] ) = σ ( W [ L ] a [ L − 1 ] + b [ L ] ) a^{[L]}=\sigma{(z^{[L]})}=\sigma{(W^{[L]}a^{[L-1]}+b^{[L]})} a[L]=σ(z[L])=σ(W[L]a[L1]+b[L])
    ∂ L ∂ b [ L ] = ∂ L ( z , y ) ∂ z ∗ ∂ z ∂ b [ L ] = ( σ ( z [ L ] ) − y ) ∂ ( W [ L ] a [ L − 1 ] + b [ L ] ) ∂ b [ L ] = a [ L ] − y \frac{\partial L}{\partial b^{[L]}}=\frac{\partial L\left( z,y \right)}{\partial z}*\frac{\partial z}{\partial b^{[L]}}=({\sigma(z^{[L]})-y})\frac{\partial (W^{[L]}a^{[L-1]}+b^{[L]})}{ \partial b^{[L]}}=a^{[L]}-y b[L]L=zL(z,y)b[L]z=(σ(z[L])y)b[L](W[L]a[L1]+b[L])=a[L]y
    ∂ L ∂ W [ L ] = ∂ L ( z , y ) ∂ z ∗ ∂ z ∂ W [ L ] = ( σ ( z [ L ] ) − y ) ∂ ( W [ L ] a [ L − 1 ] + b [ L ] ) ∂ W [ L ] = ( a [ L ] − y ) ∗ a [ L − 1 ] \frac{\partial L}{\partial W^{[L]}}=\frac{\partial L\left( z,y \right)}{\partial z}*\frac{\partial z}{\partial W^{[L]}}=({\sigma(z^{[L]})-y})\frac{\partial (W^{[L]}a^{[L-1]}+b^{[L]})}{ \partial W^{[L]}}=(a^{[L]}-y)*a^{[L-1]} W[L]L=zL(z,y)W[L]z=(σ(z[L])y)W[L](W[L]a[L1]+b[L])=(a[L]y)a[L1]
    ∂ L ∂ a [ L − 1 ] = ∂ L ( z , y ) ∂ z ∗ ∂ z ∂ a [ L − 1 ] = ( σ ( z [ L ] ) − y ) ∂ ( W [ L ] a [ L − 1 ] + b [ L ] ) ∂ a [ L − 1 ] = ( a [ L ] − y ) ∗ W [ L ] \frac{\partial L}{\partial a^{[L-1]}}=\frac{\partial L\left( z,y \right)}{\partial z}*\frac{\partial z}{\partial a^{[L-1]}}=({\sigma(z^{[L]})-y})\frac{\partial (W^{[L]}a^{[L-1]}+b^{[L]})}{ \partial a^{[L-1]}}=(a^{[L]}-y)*W^{[L]} a[L1]L=zL(z,y)a[L1]z=(σ(z[L])y)a[L1](W[L]a[L1]+b[L])=(a[L]y)W[L]

6.快点,才有感觉。我们计算L-1到第L-2层的偏导,此时激活函数是relu
∂ L ∂ a [ L − 2 ] = ∂ L ( z , y ) ∂ z ∗ ∂ z ∂ a [ L − 1 ] ∗ ∂ l a [ L − 1 ] ∂ a [ L − 2 ] = ( a [ L ] − y ) ∗ W [ L ] R e l u ′ ( a [ L − 1 ] ) ∗ W [ L − 1 ] \frac{\partial L}{\partial a^{[L-2]}}=\frac{\partial L\left( z,y \right)}{\partial z}*\frac{\partial z}{\partial a^{[L-1]}}*\frac{\partial l a^{[L-1]}}{\partial a^{[L-2]}}=(a^{[L]}-y)*W^{[L]}Relu'(a^{[L-1]})*W^{[L-1]} a[L2]L=zL(z,y)a[L1]za[L2]la[L1]=(a[L]y)W[L]Relu(a[L1])W[L1]

7.最后总结一下,从L-1层开始,根据链式求导法则,每一层都是上一层的偏导数,因此求导可以总结为下列过程:

(1) d Z [ l ] = d A [ l ] ∗ g [ l ] ′ ( Z [ l ] )    d{{Z}^{[l]}}=d{{A}^{[l]}}*{{g}^{\left[ l \right]}}'\left({{Z}^{[l]}} \right)~~ dZ[l]=dA[l]g[l](Z[l])  

(2) d W [ l ] = 1 m d Z [ l ] ⋅ A [ l − 1 ] T d{{W}^{[l]}}=\frac{1}{m}\text{}d{{Z}^{[l]}}\cdot {{A}^{\left[ l-1 \right]T}} dW[l]=m1dZ[l]A[l1]T

(3) d b [ l ] = 1 m   n p . s u m ( d z [ l ] , a x i s = 1 , k e e p d i m s = T r u e ) d{{b}^{[l]}}=\frac{1}{m}\text{ }np.sum(d{{z}^{[l]}},axis=1,keepdims=True) db[l]=m1 np.sum(dz[l],axis=1,keepdims=True)

(4) d A [ l − 1 ] = W [ l ] T . d Z [ l ] d{{A}^{[l-1]}}={{W}^{\left[ l \right]T}}.d{{Z}^{[l]}} dA[l1]=W[l]T.dZ[l]

def linear_backward(dZ, cache):
    """
    Implement the linear portion of backward propagation for a single layer (layer l)

    Arguments:
    dZ -- Gradient of the cost with respect to the linear output (of current layer l)
    cache -- tuple of values (A_prev, W, b) coming from the forward propagation in the current layer

    Returns:
    dA_prev -- Gradient of the cost with respect to the activation (of the previous layer l-1), same shape as A_prev
    dW -- Gradient of the cost with respect to W (current layer l), same shape as W
    db -- Gradient of the cost with respect to b (current layer l), same shape as b
    """
    A_prev, W, b = cache
    m = A_prev.shape[1]

    dW = 1./m * np.dot(dZ,A_prev.T)
    db = 1./m * np.sum(dZ, axis = 1, keepdims = True)
    dA_prev = np.dot(W.T,dZ)
    
    assert (dA_prev.shape == A_prev.shape)
    assert (dW.shape == W.shape)
    assert (db.shape == b.shape)
    
    return dA_prev, dW, db

def linear_activation_backward(dA, cache, activation):
    """
    Implement the backward propagation for the LINEAR->ACTIVATION layer.
    
    Arguments:
    dA -- post-activation gradient for current layer l 
    cache -- tuple of values (linear_cache, activation_cache) we store for computing backward propagation efficiently
    activation -- the activation to be used in this layer, stored as a text string: "sigmoid" or "relu"
    
    Returns:
    dA_prev -- Gradient of the cost with respect to the activation (of the previous layer l-1), same shape as A_prev
    dW -- Gradient of the cost with respect to W (current layer l), same shape as W
    db -- Gradient of the cost with respect to b (current layer l), same shape as b
    """
    linear_cache, activation_cache = cache
    """
    乘以各自的导数
	"""
    if activation == "relu":
        dZ = relu_backward(dA, activation_cache)
        dA_prev, dW, db = linear_backward(dZ, linear_cache)
        
    elif activation == "sigmoid":
        dZ = sigmoid_backward(dA, activation_cache)
        dA_prev, dW, db = linear_backward(dZ, linear_cache)
    
    return dA_prev, dW, db

好了,前向传播和反向传播的过程和代码我们已经解释清楚了,那么接下来,就是见证奇迹的关键时刻,把它们按照流程图组装起来:

逐层计算A直到最后一层
从最后一层倒序逐层计算dw和db
计算下一步的 w=w - 学习率*dw
循环n次
输入X
根据最后一层输出AL计算代价函数J
前向传播结束
得出每一层的dw和db
计算下一步的 b=b - 学习率*db
更新每一层的w和b
反向传播结束
# GRADED FUNCTION: L_layer_model

def L_layer_model(X, Y, layers_dims, learning_rate = 0.0075, num_iterations = 3000, print_cost=False):#lr was 0.009
    """
    Implements a L-layer neural network: [LINEAR->RELU]*(L-1)->LINEAR->SIGMOID.
    
    Arguments:
    X -- data, numpy array of shape (number of examples, num_px * num_px * 3)
    Y -- true "label" vector (containing 0 if cat, 1 if non-cat), of shape (1, number of examples)
    layers_dims -- list containing the input size and each layer size, of length (number of layers + 1).
    learning_rate -- learning rate of the gradient descent update rule
    num_iterations -- number of iterations of the optimization loop
    print_cost -- if True, it prints the cost every 100 steps
    
    Returns:
    parameters -- parameters learnt by the model. They can then be used to predict.
    """
# def initialize_parameters_deep(layer_dims):
#     ...
#     return parameters 
# def L_model_forward(X, parameters):
#     ...
#     return AL, caches
# def compute_cost(AL, Y):
#     ...
#     return cost
# def L_model_backward(AL, Y, caches):
#     ...
#     return grads
# def update_parameters(parameters, grads, learning_rate):
#     ...
#     return parameters

    np.random.seed(1)
    costs = []                         # keep track of cost
    
    # Parameters initialization.
    ### START CODE HERE ###
    """
    随机初始化每一层W和B参数
    """
    parameters = initialize_parameters_deep(layers_dims)
    ### END CODE HERE ###
    
    # Loop (gradient descent)
    """
    循环迭代次数
    """
    for i in range(0, num_iterations):

        # Forward propagation: [LINEAR -> RELU]*(L-1) -> LINEAR -> SIGMOID.
        ### START CODE HERE ### (≈ 1 line of code)
        """
		    前向传播
		"""
        AL, caches =  L_model_forward(X, parameters)
        ### END CODE HERE ###
        
        # Compute cost.
        ### START CODE HERE ### (≈ 1 line of code)
        """
		    计算代价函数
		"""
        cost = compute_cost(AL, Y)
        ### END CODE HERE ###
    
        # Backward propagation.
        ### START CODE HERE ### (≈ 1 line of code)
        """
		    反向传播,计算各层dw,db
		"""
        grads = L_model_backward(AL, Y, caches)
        ### END CODE HERE ###
 
        # Update parameters.
        ### START CODE HERE ### (≈ 1 line of code)
        """
		    更新参数 W = W-学习率*dW
					b = b-学习率*db
		"""
        parameters = update_parameters(parameters, grads, learning_rate)
        ### END CODE HERE ###
                
        # Print the cost every 100 training example
        if print_cost and i % 100 == 0:
            print ("Cost after iteration %i: %f" %(i, cost))
        if print_cost and i % 100 == 0:
            costs.append(cost)
            
    # plot the cost
    plt.plot(np.squeeze(costs))
    plt.ylabel('cost')
    plt.xlabel('iterations (per tens)')
    plt.title("Learning rate =" + str(learning_rate))
    plt.show()
    
    return parameters

4.7 参数VS超参数(Parameters vs Hyperparameters)

想要你的深度神经网络起很好的效果,你还需要规划好你的参数以及超参数。

什么是超参数?

比如算法中的learning rate a a a(学习率)、iterations(梯度下降法循环的数量)、 L L L(隐藏层数目)、 n [ l ] {{n}^{[l]}} n[l](隐藏层单元数目)、choice of activation function(激活函数的选择)都需要你来设置,这些数字实际上控制了最后的参数 W W W b b b的值,所以它们被称作超参数。

实际上深度学习有很多不同的超参数,之后我们也会介绍一些其他的超参数,如momentummini batch sizeregularization parameters等等。

如何寻找超参数的最优值?

Idea—Code—Experiment—Idea这个循环,尝试各种不同的参数,实现模型并观察是否成功,然后再迭代。

今天的深度学习应用领域,还是很经验性的过程,通常你有个想法,比如你可能大致知道一个最好的学习率值,可能说 a = 0.01 a=0.01 a=0.01最好,我会想先试试看,然后你可以实际试一下,训练一下看看效果如何。然后基于尝试的结果你会发现,你觉得学习率设定再提高到0.05会比较好。如果你不确定什么值是最好的,你大可以先试试一个学习率 a a a,再看看损失函数J的值有没有下降。然后你可以试一试大一些的值,然后发现损失函数的值增加并发散了。然后可能试试其他数,看结果是否下降的很快或者收敛到在更高的位置。你可能尝试不同的 a a a并观察损失函数 J J J这么变了,试试一组值,然后可能损失函数变成这样,这个 a a a值会加快学习过程,并且收敛在更低的损失函数值上(箭头标识),我就用这个 a a a值了。

在前面几页中,还有很多不同的超参数。然而,当你开始开发新应用时,预先很难确切知道,究竟超参数的最优值应该是什么。所以通常,你必须尝试很多不同的值,并走这个循环,试试各种参数。试试看5个隐藏层,这个数目的隐藏单元,实现模型并观察是否成功,然后再迭代。这页的标题是,应用深度学习领域,一个很大程度基于经验的过程,凭经验的过程通俗来说,就是试直到你找到合适的数值。

另一个近来深度学习的影响是它用于解决很多问题,从计算机视觉到语音识别,到自然语言处理,到很多结构化的数据应用,比如网络广告或是网页搜索或产品推荐等等。我所看到过的就有很多其中一个领域的研究员,这些领域中的一个,尝试了不同的设置,有时候这种设置超参数的直觉可以推广,但有时又不会。所以我经常建议人们,特别是刚开始应用于新问题的人们,去试一定范围的值看看结果如何。然后下一门课程,我们会用更系统的方法,用系统性的尝试各种超参数取值。然后其次,甚至是你已经用了很久的模型,可能你在做网络广告应用,在你开发途中,很有可能学习率的最优数值或是其他超参数的最优值是会变的,所以即使你每天都在用当前最优的参数调试你的系统,你还是会发现,最优值过一年就会变化,因为电脑的基础设施,CPU或是GPU可能会变化很大。所以有一条经验规律可能每几个月就会变。如果你所解决的问题需要很多年时间,只要经常试试不同的超参数,勤于检验结果,看看有没有更好的超参数数值,相信你慢慢会得到设定超参数的直觉,知道你的问题最好用什么数值。

这可能的确是深度学习比较让人不满的一部分,也就是你必须尝试很多次不同可能性。但参数设定这个领域,深度学习研究还在进步中,所以可能过段时间就会有更好的方法决定超参数的值,也很有可能由于CPUGPU、网络和数据都在变化,这样的指南可能只会在一段时间内起作用,只要你不断尝试,并且尝试保留交叉检验或类似的检验方法,然后挑一个对你的问题效果比较好的数值。

近来受深度学习影响,很多领域发生了变化,从计算机视觉到语音识别到自然语言处理到很多结构化的数据应用,比如网络广告、网页搜索、产品推荐等等;有些同一领域设置超参数的直觉可以推广,但有时又不可以,特别是那些刚开始研究新问题的人们应该去尝试一定范围内的结果如何,甚至那些用了很久的模型得学习率或是其他超参数的最优值也有可能会改变。

在下个课程我们会用系统性的方法去尝试各种超参数的取值。有一条经验规律:经常试试不同的超参数,勤于检查结果,看看有没有更好的超参数取值,你将会得到设定超参数的直觉。

4.8 深度学习和大脑的关联性(What does this have to do with the brain?)

深度学习和大脑有什么关联性吗?

关联不大。

那么人们为什么会说深度学习和大脑相关呢?

当你在实现一个神经网络的时候,那些公式是你在做的东西,你会做前向传播、反向传播、梯度下降法,其实很难表述这些公式具体做了什么,深度学习像大脑这样的类比其实是过度简化了我们的大脑具体在做什么,但因为这种形式很简洁,也能让普通人更愿意公开讨论,也方便新闻报道并且吸引大众眼球,但这个类比是非常不准确的。

一个神经网络的逻辑单元可以看成是对一个生物神经元的过度简化,但迄今为止连神经科学家都很难解释究竟一个神经元能做什么,它可能是极其复杂的;它的一些功能可能真的类似logistic回归的运算,但单个神经元到底在做什么目前还没有人能够真正可以解释。

深度学习的确是个很好的工具来学习各种很灵活很复杂的函数,学习到从 x x x y y y的映射,在监督学习中学到输入到输出的映射。

但这个类比还是很粗略的,这是一个logistic回归单元的sigmoid激活函数,这里是一个大脑中的神经元,图中这个生物神经元,也是你大脑中的一个细胞,它能接受来自其他神经元的电信号,比如 x 1 , x 2 , x 3 x_1,x_2,x_3 x1,x2,x3,或可能来自于其他神经元 a 1 , a 2 , a 3 a_1,a_2,a_3 a1,a2,a3 。其中有一个简单的临界计算值,如果这个神经元突然激发了,它会让电脉冲沿着这条长长的轴突,或者说一条导线传到另一个神经元。

所以这是一个过度简化的对比,把一个神经网络的逻辑单元和右边的生物神经元对比。至今为止其实连神经科学家们都很难解释,究竟一个神经元能做什么。一个小小的神经元其实却是极其复杂的,以至于我们无法在神经科学的角度描述清楚,它的一些功能,可能真的是类似logistic回归的运算,但单个神经元到底在做什么,目前还没有人能够真正解释,大脑中的神经元是怎么学习的,至今这仍是一个谜之过程。到底大脑是用类似于后向传播或是梯度下降的算法,或者人类大脑的学习过程用的是完全不同的原理。

所以虽然深度学习的确是个很好的工具,能学习到各种很灵活很复杂的函数来学到从x到y的映射。在监督学习中,学到输入到输出的映射,但这种和人类大脑的类比,在这个领域的早期也许值得一提。但现在这种类比已经逐渐过时了,我自己也在尽量少用这样的说法。

这就是神经网络和大脑的关系,我相信在计算机视觉,或其他的学科都曾受人类大脑启发,还有其他深度学习的领域也曾受人类大脑启发。但是个人来讲我用这个人类大脑类比的次数逐渐减少了。

你可能感兴趣的:(深入理解吴恩达深度学习(01神经网络和深度学习 第四周深层神经网络)