深度学习 -- 神经网络 2


1. 深度神经网络 vs 神经网络

深度学习 -- 神经网络 2

如上图所示:
当数据量比较少的时候,传统机器学习的性能提升很快,甚至是优于基于神经网络的机器学习的。但是随着数据量的越来越大,传统机器学习的性能逐渐趋于平坦,而基于神经网络的机器学习的性能却越来越好。而且随着神经网路的规模越来越大,性能也是越来越好。
所以说是规模推动深度学习快速发展,不仅仅是神经网络的规模,还有数据的规模。当下,要想达到高性能,要么训练一个大的网络,要么有大量的训练数据。然而这个在一定程度上也会达到瓶颈,比如用光了所有数据,或者网络太大需要太长的训练时间。不过目前条件下,光是规模就可以促进深度学习前进一大步。

当数据量在Small Training set这个区间时,不同的算法性能是不同的。如果没有很大的数据集,那么手工提取出来的feature在很大程度上决定了算法的性能。比如SVM的性能可能就会优于神经网络的性能,这个性能更多的是依赖提取feature的能力和算法的细节。
但是随着数据量的增大以及计算能力的加强,神经网络的性能表现的越来越好。当然除了数据和计算力,算法的发展也增强了神经网络的性能,比如**函数从sigmoid换成了ReLU,使得神经网络有了重大的突***决了sigmoid算法中存在的梯度消失的问题(梯度几乎为0时,学习的进度会非常得缓慢),而ReLU的梯度始终为1,梯度永远不会消失,这使得梯度下降算法的速度很快。

支持深度学习快速发展的三要素:

  • Data
  • Computation
  • Algorithm

在上一篇文章中,https://www.zybuluo.com/hummingbird2018/note/1265140,介绍了最简单的一层神经网络,接下来循序渐进地介绍深层神经网络。

2. 单层隐藏层的神经网络

深度学习 -- 神经网络 2

这是一个2层的神经网络,其中:输入层2个神经元(代表2个feature),1层隐藏层4个神经元,**函数为tanh,输出层1个神经元,**函数为sigmoid

2.1 **函数

在神经网络中,目前经常使用的**函数主要是以下几种:

  • sigmoid::S曲线

深度学习 -- 神经网络 2

a的范围在(0, 1)之间,一般当a>=0.5时认为输出为1,a<0.5时为0。
该**函数在神经网络中基本不再使用,因为下面的tanh完全能够替换掉它,而且效果比它还要好。唯一使用sigmoid的场景就是当需要输出一个二分类的结果时,因为这时要求的输出为 0y10 \le y \le 1,正好是sigmoid的范围。

  • tanh:双正切曲线

深度学习 -- 神经网络 2

它和sigmoid的差别就在于做了纵向平移和比例放大,使得它能够通过原点,范围在(-1, 1)之间。之所以比sigmoid的效果好在于:经过该**函数输出的数据平均值更加接近于0,而不是0.5,使得数据中心化,当作为下一层的输入数据进行计算时更加的简单。在之前的机器学习中也曾经讲过,作为输入数据时,一般要做的预处理动作就有scale normalization和mean normalization(zero mean),目的都是为了计算效果更好。

但这两种**函数有一个共同的缺点:当Z越大或者越小时,a的值越接近1或者-1,变化的幅度越来越小,这就会导致斜率会变得很小,接近于0,这会使得梯度下降变得缓慢,最终导致梯度消失的严重问题。为解决此问题,产生了一个新的**函数:ReLU

  • ReLU:Rectified Linear Unit

深度学习 -- 神经网络 2

当z<0时,a = 0;当z >= 0 时,a = 1,此时斜率永远为1,所以不会发生梯度消失的问题。ReLU现在已经被广泛地应用在神经网络上,所以如果你不确定该选用什么**函数时,那么就选择ReLU。
当然ReLU也有一个缺点:就是当z为负数时,其导数为0,虽然这在实际应用中并不是什么大的问题,因为通常隐藏单元输出的z都会大于0。不过为了解决这个问题,有个改进的版本:Leaky ReLU

  • Leaky ReLU

深度学习 -- 神经网络 2

它和ReLU的区别在于当z为负数的时候有一个很小的斜率,比如0.01,公式为:
g(z)=max(0.01z,z)g(z) = max(0.01z, z)

2.2 为什么需要非线性的**函数

**函数的作用就是去线性,如果没有**函数会怎么样?
Z[1]=W[1]X+b[1]Z^{[1]} = W^{[1]}X + b^{[1]}
A[1]=Z[1]A^{[1]} = Z^{[1]}
Z[2]=W[2]A[1]+b[2]Z^{[2]} = W^{[2]}A^{[1]} + b^{[2]}
A[2]=Z[2]A^{[2]} = Z^{[2]}
Y=A[2]=W[2](W[1]X+b[1])+b[2]Y = A^{[2]} = W^{[2]}(W^{[1]}X + b^{[1]}) + b^{[2]}
扩展开来仍然得到一个如下格式的结果:
Y=WX+bY = W^{\prime} X + b^{\prime}$
它仍然是线性的,所以不管神经网络有多少隐藏层,有多少隐藏单元,这些最终都没有任何意义。所以在神经网络中,至少在隐藏层是必须要去线性的。唯一不需要**函数的只有在输出层,当要求输出为一个连续的变量时,比如线性回归问题。

2.3 **函数的导数

因为在反向传播中需要使用**函数的导数,所以这里顺便做一个简单的介绍。如果需要了解详细的推导过程,请参考微积分的链式法则,这里只列出结果。

  • sigmoid的导数
    g(z)=11+ezg(z) = \frac{1}{1 + e^{-z}}
    g(z)=g(z)(1g(z))g^{\prime}(z) = g(z)(1 - g(z))
    dAdz=A(1A)\frac{dA}{dz} = A(1 - A)
  • tanh的导数
    g(z)=tanh(z)g(z) = tanh(z)
    g(z)=1(tanh(z))2g^{\prime}(z) = 1 - (tanh(z))^2
    dAdz=1A2\frac{dA}{dz} = 1 - A^2
  • ReLU的导数
    g(z)=max(0,z)g(z) = max(0, z)
    KaTeX parse error: Expected & or \\ or \cr or \end at position 54: …{cases} 0, & \̲m̲b̲o̲x̲{if }z < 0 \\ 1…
  • Leaky ReLU的导数
    g(z)=max(0.01z,z)g(z) = max(0.01z, z)
    KaTeX parse error: Expected & or \\ or \cr or \end at position 57: …ses} 0.01, & \̲m̲b̲o̲x̲{if }z < 0 \\ 1…

2.4 前向传播Forward propagration

前向传播的目的就在于求出每一层的A[l]A^{[l]}和最后的JJ。以上图的神经网络为例,结果如下:
Z[1]=W[1]X+b[1]Z^{[1]} = W^{[1]}X + b^{[1]}
A[1]=tanh(Z[1])A^{[1]} = tanh(Z^{[1]})
Z[2]=W[2]A[1]+b[2]Z^{[2]} = W^{[2]}A^{[1]} + b^{[2]}
A[2]=sigmoid(Z[2])A^{[2]} = sigmoid(Z^{[2]})
Y^=A[2]\hat Y = A^{[2]}
J=1m(Ylog(Y^)T+(1Y)log(1Y^)T)J = -\frac{1}{m} (Y log(\hat Y)^T + (1 - Y)log(1 - \hat Y)^T)

2.5 反向传播Backward propagation

它的目的就是通过最小化JJ,更新每一层的参数WWbb。而最小化的过程就是梯度下降的过程。首先以dWdW为例解释推导过程,因为是反向开始的,所以首先从dW[2]dW^{[2]}开始,根据微积分链式法则

dW[2]=LW[2]=LA[2]A[2]Z[2]Z[2]W[2] dW^{[2]} = \frac{\partial \mathcal{L} }{\partial W^{[2]}} = \frac{\partial \mathcal{L}} {\partial A^{[2]} } \frac{\partial A^{[2]} } {\partial Z^{[2]} } \frac{\partial Z^{[2]} } {\partial W^{[2]} }

dW[1]=LW[1]=LA[2]A[2]Z[2]Z[2]A[1]A[1]Z[1]Z[1]W[1] dW^{[1]} = \frac{\partial \mathcal{L} }{\partial W^{[1]}} = \frac{\partial \mathcal{L}} {\partial A^{[2]} } \frac{\partial A^{[2]} } {\partial Z^{[2]} } \frac{\partial Z^{[2]} } {\partial A^{[1]} } \frac{\partial A^{[1]} } {\partial Z^{[1]} } \frac{\partial Z^{[1]} } {\partial W^{[1]} }

db[2]db[1]db^{[2]} db^{[1]}同理。为了简化,令:
dZ[2]=LA[2]A[2]Z[2]dZ^{[2]} = \frac{\partial \mathcal{L}} {\partial A^{[2]} } \frac{\partial A^{[2]} } {\partial Z^{[2]} }

那么针对该例,求导的结果如下:
深度学习 -- 神经网络 2

最后更新参数:
W[2]=W[2]α dW[2] W^{[2]} = W^{[2]} - \alpha \text{ } dW^{[2]}
b[1]=b[2]α db[2] b^{[1]} = b^{[2]} - \alpha \text{ } db^{[2]}
W[1]=W[1]α dW[1] W^{[1]} = W^{[1]} - \alpha \text{ } dW^{[1]}
b[1]=b[1]α db[1] b^{[1]} = b^{[1]} - \alpha \text{ } db^{[1]}

Summary
通过Forward/Backward Propagation,就能够不断地优化参数W,bW, b

3. 单层隐藏层神经网络的构建实例

问题:对下面图中花瓣形状的两种颜色的数据进行分类
深度学习 -- 神经网络 2

如果使用简单的线性分类,比如:

clf = sklearn.linear_model.LogisticRegressionCV();
clf.fit(X.T, Y.T);

那么得到的结果为:
深度学习 -- 神经网络 2
准确率只有47%。下面我们只使用一层隐藏层的神经网络来分类。
步骤如下:
The general methodology to build a Neural Network is to:

  1. Define the neural network structure ( # of input units, # of hidden units, etc).
    深度学习 -- 神经网络 2

    input layer: n_x = 2
    hidden layer: n_h = 4, activation: tanh
    output layer: n_y = 2, activation: sigmoid
    
  2. Initialize the model’s parameters

    def initialize_parameters(n_x, n_h, n_y):
    
  3. Loop:

    • Implement forward propagation

        def forward_propagation(X, parameters):
      
    • Compute loss

        def compute_cost(A2, Y, parameters):
      
    • Implement backward propagation to get the gradients

        def backward_propagation(parameters, cache, X, Y):
      
    • Update parameters (gradient descent)

        def update_parameters(parameters, grads, learning_rate = 1.2):
      

通过上述的几个重要的步骤,以及对超参learning_rate和hidden layer层数的调试,最终发现下图是个最佳的结果。在下面的代码实例中有详细的实现过程和讲解

深度学习 -- 神经网络 2

代码实例

OneHiddenLayerNeuralNetwork