吴恩达《深度学习课程》课后作业
课程一第四周课后作业1
前言
在之前的学习中,已经通过python
中的numpy
库构建了一个单隐藏层的神经网络,根据对于深度神经网络的学习,现在,可以一步步构建一个拥有多个隐藏层的神经网络了。
1.导入相关包
首先,需要导入构建神经网络的相关python包。除了需要导入相关包之外,为了保持每次生成的结果的不变性,还需要固定随机种子,具体实现如下代码所示
import numpy as np
import h5py
import matplotlib.pyplot as plt
np.random.seed(1)
2. 作业概述
实现一个多隐藏层的神经网络,需要根据神经网络的前向计算过程和反向传播过程一步步实现,具体包括一些前向激活函数,反向传播计算,参数初始化和参数更新等,相关实现步骤如下所示:
初始化层的权重参数;
-
实现前向传播,具体步骤如下所示:
- 根据前向传播,利用权重参数和输入特征计算每一层的值;
- 实现激活函数(包括线性修正单元和
sigmoid
函数); - 将以上两个步骤合并起来以实现前向传播的计算;
- 在神经网络中,利用线性修正单元(ReLU)前层的激活函数值,并通过最后一层层的
sigmoid
函数计算输出值。
计算损失;
-
实现反向传播,具体步骤如下所示:
- 完成反向传播过程中每一层的线性计算;
- 实现反向传播过程中的梯度计算;
- 将以上两个步骤合并起来实现一个反向传播的计算函数;
完成参数更新的计算。
以上所有步骤可以由以下所示的图形表示
注意: 每一步前向传播的计算都会对应着一个反向传播的计算函数,并且每一次前向传播的计算都会将一部分值进行缓存,以便于反向传播计算过程中的进行梯度的计算。
3. 初始化
3.1 两层的神经网络
一个两层的神经网络的参数初始化过程,主要包括以下几点:
- 两层神经网络的模型的结构是
LINER->RELU->LINER->SIGMOID
- 用随机高斯分布函数初始化参数矩阵,给参数乘以0.01,并且将偏置参数初始化为0.
具体实现代码如下所示:
# GRADED FUNCTION: initialize_parameters
def initialize_parameters(n_x, n_h, n_y):
"""
n_x -- 输入层的特征个数
n_h -- 隐藏层的神经单元数目
n_y -- 输出层的单元数目
"""
np.random.seed(1)
W1 = np.random.randn(n_h,n_x)*0.01
b1 = np.zeros((n_h,1))
W2 = np.random.randn(n_y,n_x)*0.01
b2 = np.zeros((n_y,1))
# 使用断言语句,确保权重参数的矩阵形状正确。
assert(W1.shape == (n_h, n_x))
assert(b1.shape == (n_h, 1))
assert(W2.shape == (n_y, n_h))
assert(b2.shape == (n_y, 1))
parameters = {"W1": W1,
"b1": b1,
"W2": W2,
"b2": b2}
return parameters
3.2 层的神经网络
以上,实现了一个两层神经网络的参数初始化,相比于实现一个两层的神经网络,一个更深层的神经网络的参数初始化更加复杂,并且在整个实现过程中,应该确保矩阵的维数得以匹配。在以下深层神经网络的实现中,输入样本是的矩阵维数应该满足,表示有209个输入样本。
多层神经网络的实现过程中,需要注意一下几点:
- 整个模型的架构是
[LINER->RELU]*(L-1)->LINER->SIGMOID
,表示前层是线性修正单元的激活函数,而层是一个sigmoid
激活函数的输出。 - 使用高斯随机初始化初始参数,并给每一个参数矩阵乘以0.01.
- 将偏置参数初始化为0.
- 根据神经网络参数矩阵维数的检查方法,层的参数的矩阵维数应满足,而参数的维数应该满足
综上,一个多层神经网络的参数初始化的实现代码如下所示:
def initialize_parameters_deep(layer_dims):
'''
参数layer_dims 表示神经网络的层数
'''
np.random.seed(3)
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])*0.01
parameters['b' + str(l)] = np.zeros((layer_dims[l],1))
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
4 前向传播的实现
4.1 前向传播的线性函数
参数初始化完成之后,就可以完成前向传播模块的搭建了,整个前向传播部分可以分为下几个模块:
LINER
-
LINER->ACTIVATION
,激活单元有线性修正单元和sigmoid
函数构成。 - 整个模型的架构
[LINER->RELU]*(L-1)->LINER->SIGMOID
其中,LINER
的计算公式为:
综上,线性部分计算的代码实现如下所示:
def liner_forward(A,W,b):
Z = np.dot(W,A)+b
cache = (A,W,b)
assert(Z.shape == (W.shape[0], A.shape[1]))
return Z,cache
4.2 前向传播中的线性激活函数
本次作业中的神经网络实现中,前向传播的过程中,涉及到了两个激活函数,分别是隐藏层的线性修正单元(ReLu)和输出层的sigmoid
函数,其实现分别如下所示:
- 线性修正单元的激活函数实现
线性修正单元的激活函数的数学表达式如下所示,,输入变量即是上一步根据权重参数和输入特征所求得的值。
def relu(z):
A = np.maxmiun(0,z)
assert(A.shape = z.shape)
cache = z #对z进行缓存,以便于反向传播过程中的应用
return A,cache
-
sigmoid
激活函数的实现
sigmoid
激活函数已经有很多的了解和应用了,所以,其实现代码直接如下所示:
def sigmoid(z):
A = 1/(1+np.exp(-z))
assert(A.shape == z.shape)
cache = z
return A
综上所述,前向传播中的激活函数的实现可以有以下代码实现
def linear_activation_forward(A_prev, W, b, activation):
if activation == "sigmoid":
Z, linear_cache = linear_forward(A_prev,W,b)
A, activation_cache = sigmoid(Z)
elif activation == "relu":
Z, linear_cache = linear_forward(A_prev,W,b)
A, activation_cache = relu(Z)
assert (A.shape == (W.shape[0], A_prev.shape[1]))
cache = (linear_cache, activation_cache)
return A, cache
4.3 L层模型的实现
由之前的介绍,可知,对于一个层模型而言,前层具有相同的激活函数,整体的结构如下图所示:
结合以上代码,多层神经网络的前向传播实现如下代码所示,通过迭代实现时,每一次实现时需要保存每一层的值,在代码中通过一个列表来保存这些数据,具体实现,如下所示:
def L_model_forward(X, parameters):
caches = []
A = X
L = len(parameters) // 2 #神经网络的层数,参数的长度除以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)],'relu')
caches.append(cache)
AL, cache = linear_activation_forward(A,parameters['W'+str(L)],parameters['b'+str(L)],'sigmoid')
caches.append(cache)
assert(AL.shape == (1,X.shape[1]))
return AL, caches
5 损失函数的实现
通过以上步骤,已经全部实现了神经网络的前向传播,得到了输出值之后,可以实现损失函数了,具体的损失函数计算公式如下所示:
根据以上公式,损失函数的实现代码如下所示
def compute_cost(AL, Y):
m = Y.shape[1]
cost = -1/m*np.sum(Y*np.log(AL)+(1-Y)*np.log(1-AL))
cost = np.squeeze(cost)
assert(cost.shape == ())
return cost
6. 反向传播模块
与前向传播模块类似,反向传播的过程是通过输出->输入,可以根据前向传播的实现方法一样,一步步实现反向传播,具体如下图所示;
整个反向传播的过程可以分为以下三个部分实现:
- 线性
Linear
反向传播; -
Linear->activation
传播,需要计算线性修正单元RELU
和激活函数simoid
的导数; - 整个模型
[Linear->activation]×(L-1)
和L层[Linear->sigmoid]
的实现。
6.1 线性反向传播
前向传播过程中的线性激活函数是根据参数和输入计算的值,而在反向传播中,则是根据前向传播的计算其导数值,整个过程,可以用下图表示:
反向传播中的计算公式如下所示;
根据以上公式,其实现代码如下所示:
def linear_backward(dZ, cache):
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
6.2 激活函数的反向传播
线性激活函数部分的反向传播,需要对激活函数进行求导,神经网络的实现用到了两个激活函数,其求导函数的实现如下所示:
- 线性修正单元(ReLu)导数的计算
线性修正单元的激活函数的导数如下公式所示:
其实现代码如下所示;
def relu_backward(dA, cache):
Z = cache
dZ = np.array(dA,copy = True)
dZ[Z <= 0] = 0
assert (dZ.shape == Z.shape)
return dZ
-
sigmoid
函数的导数计算
sigmoid
函数的导数计算公式如下所示;
根据以上公式,则有实现代码如下所示:
def sigmoid_backward(dA, cache):
Z = cache
s = 1/(1+np.exp(-Z))
dZ = dA * s * (1-s)
assert (dZ.shape == Z.shape)
return dZ
综上,将以上代码结合起来,实现一个整体激活函数反向传播的计算过程的实现代码如下所示:
def linear_activation_backward(dA, cache, activation):
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
注意:以上代码实现过程中,linear_cache
存放的是线性反向传播中的dW,db,dA_prev
等参数,而activation_cache
存放的其实就是前向传播过程中的Z
值(前向传播过程中提到过,前向的传播计算到的Z
值在反向传播中还会用到)。
6.3 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, "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, "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
L层模型的反向传播过程,首先需要对损失函数进行求导,也就是计算的值,根据损失函数求导公式,其实现代码如下所示:
dAL = - (np.divide(Y, AL) - np.divide(1 - Y, 1 - AL))
注意:反向传播函数的计算过程中,需要从后往前计算,for循环中,可以使用python
中自带的函数reversed
函数来实现。
6.4 参数更新
有了以上求得的梯度,现在可以对参数和进行更新了,参数更新公式如下所示;
其实现代码如下所示:
def update_parameters(parameters, grads, learning_rate):
L = parameters//2
for l in range(L):
parameters["W" + str(l+1)] -=learning_rate*grads["dW"+str(l+1)]
parameters["b" + str(l+1)] -=learning_rate*grads["db"+str(l+1)]
return parameters
7 总结
以上,一步步实现了一个神经网络的前向传播和反向传播,在这个过程中了解了前向传播和反向传播的具体实现过程和原理,接下来的作业中将利用神经网络实现一个图片分类器。