深度学习(2):神经网络

神经网络(Neural Network)的构筑理念是受到生物神经网络功能的运作启发而产生的。人工神经网络通常是通过一个基于数学统计学类型的学习方法得以优化,所以人工神经网络也是数学统计学方法的一种实际应用。

和其他机器学习方法一样,神经网络已经被用于解决各种各样的问题,例如机器视觉和语音识别。这些问题都是很难被传统基于规则的编程所解决的。

神经网络

机器学习领域所说的神经网络指的是一种模仿生物神经网络的结构和功能而建立的数学或计算模型,用于对函数进行估计或近似。

例如,给定一些关于市面上房子的面积及价格的数据,要你根据这些数据建立一个房价预测模型,即输入一个房子的面积,希望通过这个模型输出一个房价的预测值。显然,这是一个线性回归问题,因为一般情况下房价和房子的面积都成正相关。这时,我们可以将已知数据的关系表现在平面坐标系中:

房价线性模型

对数据进行线性拟合,且房价永远不会是负数,得到图中的ReLU函数(Rectified Linear Unit,修正线性单元)

房价神经网络模型

在这个简单的例子中,房子的面积作为输入,房价作为输出,而ReLU函数便充当一个神经元的作用,来产生输出。

然而房价除了受房子的面积影响之外,还会受卧室的数量、房子的位置以及地区的财富水平等因素的影响,这时就需要构建一个更为复杂的神经网络模型。

神经网络模型

这就构成了一个神经网络模型基本结构,神经网络会自动生成隐藏层(Hidden Units)来处理输入,生成输出。这个问题中,只要拥有足够的训练数据,就能生成一个较好的神经网络模型,得到较为精确的结果。

简单而言,深度学习便是更为复杂的神经网络。

在逻辑回归中,通过建立一个简单的神经网络模型,输入训练样本(x,y),希望得出一个预测值$\hat{y}$,使得$\hat{y}$尽可能等于y。训练的流程如下:

逻辑回归流程

在这个模型中,先建立了一个损失函数,进而不断采用梯度下降法找到参数w和b的最优解。采用这种算法编写的猫识别器最终的准确率只有70%,想要进一步提高识别的精准度,就需要建立起一个多层的神经网络来训练样本。

符号约定

如图所示的神经网络中,前面为输入层,中间为隐藏层 ,最后为输出层。中间层被称为隐藏层的原因是因为在训练过程中,将看到输入的样本有哪些,输出的结果是什么,中间层中的神经节点产生的真实值无法被观察到。所以中间层被称为隐藏层,只是因为你不会在训练集中看到它。

两层神经网络

前面的逻辑回归中,用$X$表示输入,这里用符号$a^{[0]}$代替,上标“[ ]”括号中的数字表示神经网络中的第几层,且符号$a$代表着激活(Activation),指的是不同层次的神经网络传递给后续层次的值。

将输入集传递给隐藏层后,隐藏层随之产生激活表示为$a^{[1]}$,而隐藏层的第一节点生成的激活表示为$a^{[1]}_1$,第二个节点产生的激活为$a^{[1]}_2$,以此类推,则:$${ a^{[1]} = \begin{bmatrix} a^{[1]}_1 \\ a^{[1]}_2 \\ a^{[1]}_3 \\ a^{[1]}_4 \end{bmatrix}\quad}$$
最后,输出层输出的值表示为$a^{[2]}$,则$\hat{y} = a^{[2]}$。

图中的这个神经网络也被称为两层神经网络,原因是计算神经网络的层数时,通常不考虑输入层。所以这个神经网络中,隐藏层是第一次层,输出层是第二层,而输入层为第零层。

图中的隐藏层中,将存在参数w和b,它们将分别表示为$w^{[1]}$和$b^{[1]}$,$w^{[1]}$将会是个4×3矩阵,$b^{[1]}$将会是个4×1矩阵。输出层中,也会存在参数$w^{[2]}$和$b^{[2]}$,$w^{[2]}$是个1×4矩阵,$b^{[2]}$是个1×1矩阵。

神经网络的表示

神经网络的表示

如图所示,将样本输入隐藏层中的第一个节点后,可得;$$ z^{[1]}_1 = w^{[1]T}_1X + b^{[1]}_1, a^{[1]}_1 = σ(z^{[1]}_1)$$
以此类推:$$ z^{[1]}_2 = w^{[1]T}_2X + b^{[1]}_2, a^{[1]}_2 = σ(z^{[1]}_2)$$ $$ z^{[1]}_3 = w^{[1]T}_3X + b^{[1]}_3, a^{[1]}_3 = σ(z^{[1]}_3)$$ $$ z^{[1]}_4 = w^{[1]T}_4X + b^{[1]}_4, a^{[1]}_4 = σ(z^{[1]}_4)$$
将它们都表示成矩阵形式:$${ z^{[1]} = \begin{bmatrix} w^{[1]}_1 & w^{[1]}_1 & w^{[1]}_1\\ w^{[1]}_2 & w^{[1]}_2 & w^{[1]}_2 \\ w^{[1]}_3 & w^{[1]}_3 & w^{[1]}_3 \\ w^{[1]}_4 & w^{[1]}_4 & w^{[1]}_4 \end{bmatrix}\quad}\begin{bmatrix} x_1 \\ x_2 \\ x_3 \end{bmatrix}\quad + \begin{bmatrix} b^{[1]}_1 \\ b^{[1]}_2 \\ b^{[1]}_3 \\ b^{[1]}_4 \end{bmatrix} = \begin{bmatrix} z^{[1]}_1 \\ z^{[1]}_2 \\ z^{[1]}_3 \\ z^{[1]}_4 \end{bmatrix}\quad\quad$$
即:$$ z^{[1]} = w^{[1]}X + b^{[1]} $$ $$a^{[1]} = σ(z^{[1]})$$
神经网络的表示
进过隐藏层后进入输出层,又有:$$ z^{[2]} = w^{[2]}a^{[1]} + b^{[2]}$$ $$a^{[2]} = σ(z^{[2]})$$

可以发现,在一个的共有l层,且第l层有$n^{[l]}$个节点的神经网络中,参数矩阵 $w^{[l]}$的大小为$n^{[l]}$*$n^{[l-1]}$,$b^{[l]}$的大小为$n^{[l]}$*1。

逻辑回归中,直接将两个参数都初始化为零。而在神经网络中,通常将参数w进行随机初始化,参数b则初始化为0。

除w、b外的各种参数,如学习率$\alpha$、神经网络的层数$l$,第$l$层包含的节点数$n^{[l]}$及隐藏层中用的哪种激活函数,都称为超参数(Hyper Parameters),因为它们的值决定了参数w、b最后的值。

激活函数

建立一个神经网络时,需要关心的一个问题是,在每个不同的独立层中应当采用哪种激活函数。逻辑回归中,一直采用sigmoid函数作为激活函数,此外还有一些更好的选择。

tanh函数(Hyperbolic Tangent Function,双曲正切函数)的表达式为:$$tanh(z) = \frac{e^z - e^{-z}}{e^z + e^{-z}}$$
函数图像为:
tanh函数

tanh函数其实是sigmoid函数的移位版本。对于隐藏单元,选用tanh函数作为激活函数的话,效果总比sigmoid函数好,因为tanh函数的值在-1到1之间,最后输出的结果的平均值更趋近于0,而不是采用sigmoid函数时的0.5,这实际上可以使得下一层的学习变得更加轻松。对于二分类问题,为确保输出在0到1之间,将仍然采用sigmiod函数作为输出的激活函数。

然而sigmoid函数和tanh函数都具有的缺点之一是,在z接近无穷大或无穷小时,这两个函数的导数也就是梯度变得非常小,此时梯度下降的速度也会变得非常慢。

线性修正单元,也就是上面举例解释什么是神经网络时用到的ReLU函数也是机器学习中常用到的激活函数之一,它的表达式为:$$g(z) = max(0,z) =\begin{cases} 0, & \text{($z$ $\le$ 0)} \\ z, & \text{($z$ $\gt$ 0)} \end{cases} $$
函数图像为:
ReLU函数

当z大于0时是,ReLU函数的导数一直为1,所以采用ReLU函数作为激活函数时,随机梯度下降的收敛速度会比sigmoid及tanh快得多,但负数轴的数据都丢失了。

ReLU函数的修正版本,称为Leaky-ReLU,其表达式为:$$g(z) = max(0,z) =\begin{cases} \alpha z, & \text{($z$ $\le$ 0)} \\ z, & \text{($z$ $\gt$ 0)} \end{cases} $$
函数图像为:
Leaky-ReLU

其中$ \alpha $是一个很小的常数,用来保留一部非负数轴的值。

可以发现,以上所述的几种激活函数都是非线性的,原因在于使用线性的激活函数时,输出结果将是输入的线性组合,这样的话使用神经网络与直接使用线性模型的效果相当,此时神经网络就类似于一个简单的逻辑回归模型,失去了其本身的优势和价值。

前向传播和反向传播

如图,通过输入样本$x$及参数$w^{[1]}$、$b^{[1]}$到隐藏层,求得$z^{[1]}$,进而求得$a^{[1]}$;再将参数$w^{[2]}$、$b^{[2]}$和$a^{[1]}$一起输入输出层求得$z^{[2]}$,进而求得$a^{[2]}$,最后得到损失函数$\mathcal{L}(a^{[2]},y)$,这样一个从前往后递进传播的过程,就称为前向传播(Forward Propagation)
前向传播
前向传播过程中:$$ z^{[1]} = w^{[1]T}X + b^{[1]} $$ $$a^{[1]} = g(z^{[1]})$$ $$ z^{[2]} = w^{[2]T}a^{[1]} + b^{[2]}$$ $$a^{[2]} = σ(z^{[2]}) = sigmoid(z^{[2]})$$ $${\mathcal{L}(a^{[2]}, y)=-(ylog\ a^{[2]} + (1-y)log(1-a^{[2]}))}$$

在训练过程中,经过前向传播后得到的最终结果跟训练样本的真实值总是存在一定误差,这个误差便是损失函数。想要减小这个误差,当前应用最广的一个算法便是梯度下降,于是用损失函数,从后往前,依次求各个参数的偏导,这就是所谓的反向传播(Back Propagation),一般简称这种算法为BP算法
反向传播
sigmoid函数的导数为:$${a^{[2]’} = sigmoid(z^{[2]})’ = \frac{\partial a^{[2]}}{\partial z^{[2]}} = a^{[2]}(1 - a^{[2]})}$$

由复合函数求导中的链式法则,反向传播过程中:$$ da^{[2]} = \frac{\partial \mathcal{L}(a^{[2]}, y)}{\partial a^{[2]}} = -\frac{y}{a^{[2]}} + \frac{1 - y}{1 - a^{[2]}}$$ $$ dz^{[2]} = \frac{\partial \mathcal{L}(a^{[2]}, y)}{\partial a^{[2]}} \cdot \frac{\partial a^{[2]}}{\partial z^{[2]}} = a^{[2]} - y$$ $$ dw^{[2]} = \frac{\partial \mathcal{L}(a^{[2]}, y)}{\partial a^{[2]}} \cdot \frac{\partial a^{[2]}}{\partial z^{[2]}}\cdot \frac{\partial z^{[2]}}{\partial w^{[2]}} = dz^{[2]}\cdot a^{[1]T}$$ $$ db^{[2]} = \frac{\partial \mathcal{L}(a^{[2]}, y)}{\partial a^{[2]}} \cdot \frac{\partial a^{[2]}}{\partial z^{[2]}}\cdot \frac{\partial z^{[2]}}{\partial b^{[2]}} = dz^{[2]}$$ $$ da^{[1]} = \frac{\partial \mathcal{L}(a^{[2]}, y)}{\partial a^{[2]}} \cdot \frac{\partial a^{[2]}}{\partial z^{[2]}} \cdot \frac{\partial z^{[2]}}{\partial a^{[1]}} = dz^{[2]} \cdot w^{[2]} $$ $$ dz^{[1]} = \frac{\partial \mathcal{L}(a^{[2]}, y)}{\partial a^{[2]}} \cdot \frac{\partial a^{[2]}}{\partial z^{[2]}} \cdot \frac{\partial z^{[2]}}{\partial a^{[1]}} \cdot \frac{\partial a^{[1]}}{\partial z^{[1]}}= dz^{[2]} \cdot w^{[2]} × g^{[1]’}(z^{[1]}) $$ $$ dw^{[1]} = \frac{\partial \mathcal{L}(a^{[2]}, y)}{\partial a^{[2]}} \cdot \frac{\partial a^{[2]}}{\partial z^{[2]}} \cdot \frac{\partial z^{[2]}}{\partial a^{[1]}} \cdot \frac{\partial a^{[1]}}{\partial z^{[1]}} \cdot \frac{\partial z^{[1]}}{\partial w^{[1]}}= dz^{[1]} \cdot X^T $$ $$ db^{[1]} = \frac{\partial \mathcal{L}(a^{[2]}, y)}{\partial a^{[2]}} \cdot \frac{\partial a^{[2]}}{\partial z^{[2]}} \cdot \frac{\partial z^{[2]}}{\partial a^{[1]}} \cdot \frac{\partial a^{[1]}}{\partial z^{[1]}} \cdot \frac{\partial z^{[1]}}{\partial b^{[1]}}= dz^{[1]}$$
这便是反向传播的整个推导过程。

在具体的算法实现过程中,还是需要采用逻辑回归中用到梯度下降的方法,将各个参数进行向量化、取平均值,不断进行更新。

深层神经网络

深层神经网络含有多个隐藏层,构建方法如前面所述,训练时根据实际情况选择激活函数,进行前向传播获得成本函数进而采用BP算法,进行反向传播,梯度下降缩小损失值。

拥有多个隐藏层的深层神经网络能更好得解决一些问题。如图,例如利用神经网络建立一个人脸识别系统,输入一张人脸照片,深度神经网络的第一层可以是一个特征探测器,它负责寻找照片里的边缘方向,卷积神经网络(Convolutional Neural Networks,CNN)专门用来做这种识别。

深层神经网络

深层神经网络的第二层可以去探测照片中组成面部的各个特征部分,之后一层可以根据前面获得的特征识别不同的脸型的等等。这样就可以将这个深层神经网络的前几层当做几个简单的探测函数,之后将这几层结合在一起,组成更为复杂的学习函数。从小的细节入手,一步步建立更大更复杂的模型,就需要建立深层神经网络来实现。

Python实现

之前使用逻辑回归实现过一个猫分类器,最终用测试集测试最高能达到的准确率有70%,现在用一个4层神经网络来实现。

1.L层神经网络参数初始化

1
2
3
4
5
6
7
8
9
10
11
#初始化参数
def initialize_parameters_deep(layer_dims):
np.random.seed(1)
parameters = {}
L = len(layer_dims)

for l in range(1, L):
parameters['W' + str(l)] = np.random.randn(layer_dims[l], layer_dims[l-1]) / np.sqrt(layer_dims[l-1]) #初始化为随机值
parameters['b' + str(l)] = np.zeros((layer_dims[l], 1)) #初始化为0

return parameters

2.前向传播和后向传播

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#部分代码此处省略,详细代码见参考资料-Github
#L层神经网络模型的前向传播
def L_model_forward(X, parameters):
caches = []
A = X
L = len(parameters) // 2

for l in range(1, L):
A_prev = A
A, cache = linear_activation_forward(A_prev, parameters['W' + str(l)], parameters['b' + str(l)], activation = "relu")
caches.append(cache)

AL, cache = linear_activation_forward(A, parameters['W' + str(L)], parameters['b' + str(L)], activation = "sigmoid")
caches.append(cache)

assert(AL.shape == (1,X.shape[1]))

return AL, caches

#L层神经网络模型的反向传播
def L_model_backward(AL, Y, caches):
grads = {}
L = len(caches)
m = AL.shape[1]
Y = Y.reshape(AL.shape)

dAL = -(np.divide(Y, AL) - np.divide(1 - Y, 1 - AL))
current_cache = caches[L-1]
grads["dA" + str(L)], grads["dW" + str(L)], grads["db" + str(L)] = linear_activation_backward(dAL, current_cache, activation = "sigmoid")

for l in reversed(range(L-1)):
current_cache = caches[l]
dA_prev_temp, dW_temp, db_temp = linear_activation_backward(grads["dA" + str(l + 2)], current_cache, activation = "relu")
grads["dA" + str(l + 1)] = dA_prev_temp
grads["dW" + str(l + 1)] = dW_temp
grads["db" + str(l + 1)] = db_temp

return grads

3.L层神经网络模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#L层神经网络模型
def L_layer_model(X, Y, layers_dims, learning_rate = 0.0075, num_iterations = 3000, print_cost=False):

np.random.seed(1)
costs = []

parameters = initialize_parameters_deep(layers_dims)
for i in range(0, num_iterations):
AL, caches = L_model_forward(X, parameters)
cost = compute_cost(AL, Y)
grads = L_model_backward(AL, Y, caches)
parameters = update_parameters(parameters, grads, learning_rate)

if print_cost and i % 100 == 0:
print ("循环%i次后的成本值: %f" %(i, cost))
if print_cost and i % 100 == 0:
costs.append(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.输入数据,得出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
train_x_orig, train_y, test_x_orig, test_y, classes = load_dataset()

m_train = train_x_orig.shape[0] #训练集中样本个数
m_test = test_x_orig.shape[0] #测试集总样本个数
num_px = test_x_orig.shape[1] #图片的像素大小

train_x_flatten = train_x_orig.reshape(train_x_orig.shape[0],-1).T #原始训练集的设为(12288*209)
test_x_flatten = test_x_orig.reshape(test_x_orig.shape[0],-1).T #原始测试集设为(12288*50)

train_x = train_x_flatten/255. #将训练集矩阵标准化
test_x = test_x_flatten/255. #将测试集矩阵标准化

layers_dims = [12288, 20, 7, 5, 1]
parameters = L_layer_model(train_x, train_y, layers_dims, num_iterations = 2500, print_cost = True)

pred_train = predict(train_x, train_y, parameters)
pred_test = predict(test_x, test_y, parameters)

得到的结果为:
成本值
成本变化曲线
样本集预测准确度: 0.985645933014
测试集预测准确度: 0.8

参考资料

  1. 吴恩达-神经网络与深度学习-网易云课堂
  2. Andrew Ng-Neural Networks and Deep Learning-Coursera
  3. deeplearning.ai
  4. 课程代码与资料-GitHub

注:本文涉及的图片及资料均整理翻译自Andrew Ng的Deep Learning系列课程,版权归其所有。翻译整理水平有限,如有不妥的地方欢迎指出。


更新历史:

  • 2017.09.28 完成初稿
  • 2018.02.13 调整部分内容
文章作者: Hugsy
文章链接: http://binweber.top/2017/09/25/deep_learning_2/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Weber
支付宝打赏~
微信打赏~