一步步构建一个神经网络

吴恩达《深度学习课程》课后作业
课程一第四周课后作业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 总结

以上,一步步实现了一个神经网络的前向传播和反向传播,在这个过程中了解了前向传播和反向传播的具体实现过程和原理,接下来的作业中将利用神经网络实现一个图片分类器。

你可能感兴趣的:(一步步构建一个神经网络)