본문 바로가기
Coursera 강의/Deep Learning

[실습] Building Deep Neural Network : Step by Step

by 별준 2020. 9. 25.
해당 내용은 Coursera의 딥러닝 특화과정(Deep Learning Specialization)의 첫 번째 강의 Neural Networks and Deep Learning를 듣고 정리한 내용입니다. (Week 4)

4주차 첫 번째 실습은 딥러닝을 순서대로 구현해보는 것입니다.

여기서 ouput layer를 제외한 layer에서 activation function을 ReLU함수를 사용하고, output layer에서만 sigmoid 함수를 사용할 것이고, 이번 실습에서는 2-layer와 L-layer를 위한 함수를 각각 구현할 것입니다.

 

시작하기에 앞서 표기법을 정리하겠습니다.

- 위첨자 [l][l]lthl^{th} layer를 의미합니다. a[L]a^{[L]}은 L번째 layer의 activation을 의미합니다.

- 위첨자 (l)(l)ithi^{th} sample을 의미합니다.

- 아래첨자 i는 ithi^{th} 번째 vector를 의미합니다. ai[l]a_i^{[l]}라면 l번째 layer의 i번째 activation 입니다.

 

 

1. Packages

이번 실습에서 사용되는 패키지는 다음과 같습니다.

  • numpy is the main package for scientific computing with Python.
  • matplotlib is a library to plot graphs in Python.
  • dnn_utils provides some necessary functions for this notebook.
  • testCases provides some test cases to assess the correctness of your functions
  • np.random.seed(1) is used to keep all the random function calls consistent. It will help us grade your work. Please don't change the seed.

dnn_utils에서 FP와 BP에서 사용되는 activation 함수를 제공하고 있으며, 아래와 같이 정의되어 있습니다.

import numpy as np
def sigmoid(Z):
"""
Implements the sigmoid activation in numpy
Arguments:
Z -- numpy array of any shape
Returns:
A -- output of sigmoid(z), same shape as Z
cache -- returns Z as well, useful during backpropagation
"""
A = 1/(1+np.exp(-Z))
cache = Z
return A, cache
def relu(Z):
"""
Implement the RELU function.
Arguments:
Z -- Output of the linear layer, of any shape
Returns:
A -- Post-activation parameter, of the same shape as Z
cache -- a python dictionary containing "A" ; stored for computing the backward pass efficiently
"""
A = np.maximum(0,Z)
assert(A.shape == Z.shape)
cache = Z
return A, cache
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 dZ
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

 

그리고 적용되는 라이브러리 입니다.

import numpy as np
import h5py
import matplotlib.pyplot as plt
from testCases_v4a import *
from dnn_utils_v2 import sigmoid, sigmoid_backward, relu, relu_backward
%matplotlib inline
plt.rcParams['figure.figsize'] = (5.0, 4.0) # set default size of plots
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'
%load_ext autoreload
%autoreload 2
np.random.seed(1)

 

2. Outline of the Assignment

시작하기에 앞서 오늘 실습의 개요를 살펴보겠습니다.

NN을 구현하기 위해서, 우리는 이번에 Helper function들을 정의할 것이고, 이 정의된 함수들은 다음 과제에서 2-layer NN과 L-layer NN을 구현하는데 사용할 것입니다. 

구현되는 함수는 다음과 같습니다.

  • L-layer의 파라미터 초기화 함수
  • Forward Propagation 수행 함수
  • Cost 계산 함수
  • Backward Propagation 수행 함수
  • 파라미터 업데이트 함수

위와 같은 과정을 거치게 됩니다. L-1 layer(hidden layers)에서는 ReLU함수를 activation function으로 사용했고, 마지막 ouput layer에서는 Sigmoid 함수를 사용한 것을 볼 수 있습니다.

 

3. Initialization

3.1 2-layer Neural Network

2-layer NN의 파라미터 초기화를 위한 함수를 구현해봅시다.

model의 구조는 Linear -> ReLU -> Linear -> Sigmoid 로 구성되며, 우리는 numpy의 random.randn(shape)*0.01로 랜덤하게 초기화할 수 있습니다. 이전의 실습과 마찬가지로 파라미터 b는 0으로 초기화해도 무관합니다.

# GRADED FUNCTION: initialize_parameters
def initialize_parameters(n_x, n_h, n_y):
"""
Argument:
n_x -- size of the input layer
n_h -- size of the hidden layer
n_y -- size of the output layer
Returns:
parameters -- python dictionary containing your parameters:
W1 -- weight matrix of shape (n_h, n_x)
b1 -- bias vector of shape (n_h, 1)
W2 -- weight matrix of shape (n_y, n_h)
b2 -- bias vector of shape (n_y, 1)
"""
np.random.seed(1)
### START CODE HERE ### (≈ 4 lines of code)
W1 = np.random.randn(n_h, n_x) * 0.01
b1 = np.zeros((n_h, 1))
W2 = np.random.randn(n_y, n_h) * 0.01
b2 = np.zeros((n_y, 1))
### END CODE HERE ###
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 L-layer Neural Network

다음으로 L-layer를 가진 NN을 구현해봅시다.

L-layer의 파라미터를 초기화하는 것은 조금 더 복잡한데, 각 layer에서의 파라미터 W와 b의 차원을 맞추어 주어야하기 때문입니다. n[l]n^{[l]}이 l-layer의 unit 갯수라는 것을 기억하면서 이전 실습이었던 고양이 판별기를 예시로 살펴보면 다음과 같이 각 layer의 파라미터 차원을 구할 수 있습니다.(입력 X의 차원이 (12288, 209)(with m = 209 examples))

# 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)
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))
### 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

코드는 위와 같이 구현할 수 있습니다. 매개변수로 받는 layer_dim은 각 layer의 크기를 담고 있는 list입니다. 

만약, layer_dim이 [5,4,3]으로 주어진다면, 

- W1 = (4, 5)

- b1 = (4, 1)

- W2 = (3, 4)

- b2 = (3, 1)

로 파라미터의 차원이 결정됩니다. layer_dim의 맨 처음 요소는 input layer의 크기 n[0]n^{[0]} 입니다.

 

4. Forward propagation module

4.1 Linear Forward

파라미터를 초기화했고, 다음으로 FP를 진행해야 합니다. 

순서대로 다음의 3개의 값을 계산하는 함수를 만들어봅시다.

1. Linear

2. Linear -> activation (activation은 ReLU 또는 Sigmoid임)

3. [Linear -> ReLU] x (L-1) -> Linear -> Sigmoid (FP 전체과정)

 

Linear Forward는 다음과 같이 구할 수 있습니다.

Z[l]=W[l]A[l1]+b[l]Z^{[l]} = W^{[l]}A^{[l-1]} + b^{[l]}

여기서 A[0]=XA^{[0]} = X 입니다.

 

# GRADED FUNCTION: linear_forward
def linear_forward(A, W, b):
"""
Implement the linear part of a layer's forward propagation.
Arguments:
A -- activations from previous layer (or input data): (size of previous layer, number of examples)
W -- weights matrix: numpy array of shape (size of current layer, size of previous layer)
b -- bias vector, numpy array of shape (size of the current layer, 1)
Returns:
Z -- the input of the activation function, also called pre-activation parameter
cache -- a python tuple containing "A", "W" and "b" ; stored for computing the backward pass efficiently
"""
### 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

 

4.2 Linear-Activation Forward

activation 계산에 두 가지 종류의 함수 Sigmoid와 ReLU가 사용됩니다.

- Sigmoid: σ(Z)=σ(WA+b)=11+e(WA+b)\sigma(Z) = \sigma(WA + b) = \frac{1}{1 + e^{-(WA + b)}}로 계산되고, 이 함수는 Activation 값과 Z를 포함하는 cache를 반환합니다(cache는 BP에서 재사용됩니다).

A, activation_cache = sigmoid(Z)

 

- ReLU : A=RELU(Z)=max(0,Z)A = RELU(Z) = max(0, Z) 로 계산됩니다. 마찬가지로 Activation값과 cache를 반환합니다.

A, activation_cache = relu(Z)

 

중간의 hidden layer는 ReLU로 계산해야되고, 마지막 ouput layer는 sigmoid로 계산해야되기 때문에, 어떤 함수가 사용되는지 알려주는 역할을 하는 activation을 매개변수로 입력받습니다.

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

 

# GRADED FUNCTION: linear_activation_forward
def linear_activation_forward(A_prev, W, b, activation):
"""
Implement the forward propagation for the LINEAR->ACTIVATION layer
Arguments:
A_prev -- activations from previous layer (or input data): (size of previous layer, number of examples)
W -- weights matrix: numpy array of shape (size of current layer, size of previous layer)
b -- bias vector, numpy array of shape (size of the current layer, 1)
activation -- the activation to be used in this layer, stored as a text string: "sigmoid" or "relu"
Returns:
A -- the output of the activation function, also called the post-activation value
cache -- a python tuple containing "linear_cache" and "activation_cache";
stored for computing the backward pass efficiently
"""
if activation == "sigmoid":
# 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 = 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

 

4.3 L-layer Model

모든 layer에서의 FP를 진행하기 위한 함수를 구현합니다. 예측값인 AL(Y^\hat{Y}), 그리고 각 layer의 cache를 저장한 caches를 반환합니다.

[Linear -> ReLU] x (L-1) -> Linear -> Sigmoid

AL 변수는 다음을 의미합니다.

A[L]=σ(Z[L])=σ(W[L]A[L1]+b[L])=Y^A^{[L]} = \sigma(Z^{[L]}) = \sigma(W^{[L]}A^{[L-1]} + b^{[L]}) = \hat{Y}

# GRADED FUNCTION: L_model_forward
def L_model_forward(X, parameters):
"""
Implement forward propagation for the [LINEAR->RELU]*(L-1)->LINEAR->SIGMOID computation
Arguments:
X -- data, numpy array of shape (input size, number of examples)
parameters -- output of initialize_parameters_deep()
Returns:
AL -- last post-activation value
caches -- list of caches containing:
every cache of linear_activation_forward() (there are L-1 of them, indexed from 0 to L-1)
"""
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)], activation = "relu")
caches.append(cache)
### END CODE HERE ###
# Implement LINEAR -> SIGMOID. Add "cache" to the "caches" list.
### START CODE HERE ### (≈ 2 lines of code)
AL, cache = linear_activation_forward(A, parameters['W'+str(L)], parameters['b'+str(L)], activation = "sigmoid")
caches.append(cache)
### END CODE HERE ###
assert(AL.shape == (1,X.shape[1]))
return AL, caches

 

5. Cost Function

Cost J를 계산하는 함수입니다. 아래의 식으로 계산됩니다.

J=1mi=1m(y(i)log(a[L](i))+(1y(i))log(1a[L](i)))J = -\frac{1}{m}\sum_{i = 1}^{m}(y^{(i)}log(a^{[L](i)}) + (1 - y^{(i)})log(1 - a^{[L](i)}))

# GRADED FUNCTION: compute_cost
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.
### START CODE HERE ### (≈ 1 lines of code)
cost = (-1 / m) * np.sum(Y * np.log(AL) + (1 - Y) * np.log(1 - AL), axis = 1)
### END CODE HERE ###
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

 

6. Backward propagation module

FP에서 구한 결과를 사용해서 BP를 진행해서 Cost Function의 gradient를 구합니다.

FP와 마찬가지로 3단계의 BP 함수를 구현해봅시다.

1. Linear backward

2. Linear->Activation backward(마찬가지로 activation은 sigmoid와 ReLU 두 가지입니다.)

3. [Linear -> ReLU] x (L-1) -> Linear -> Sigmoid backward(전체 BP 모델)

 

6-1 Linear backward

하나의 layer ll에서, linear part는 Z[l]=W[l]Al1+b[l]Z^{[l]} = W^{[l]}A^{l -1} + b^{[l]} 입니다.

이 함수에서는 이미 계산된 미분항 dZ[l]=LZ[l]dZ^{[l]} = \frac{\partial \mathscr{L}}{\partial Z^{[l]}}를 가지고, (dW[l],db[l],dA[l1])(dW^{[l]}, db^{[l]}, dA^{[l-1]})를 구합니다.

output은 다음과 같이 계산됩니다.

dW[l]=JW[l]=1mdZ[l]A[l1]Tdb[l]=Jb[l]=1mi=1mdZ[l](i)dA[l1]=JA[l1]=W[l]TdZ[l]\begin{matrix} dW^{[l]} = \frac{\partial \mathscr{J}}{\partial W^{[l]}} = \frac{1}{m}dZ^{[l]}A^{[l-1]T} \\ db^{[l]} = \frac{\partial \mathscr{J}}{\partial b^{[l]}} = \frac{1}{m}\sum_{i = 1}^{m}dZ^{[l](i)} \\ dA^{[l-1]} = \frac{\partial \mathscr{J}}{\partial A^{[l-1]}} = W^{[l]T}dZ^{[l]} \end{matrix}

 

# GRADED FUNCTION: linear_backward
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]
### START CODE HERE ### (≈ 3 lines of code)
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)
### END CODE HERE ###
assert (dA_prev.shape == A_prev.shape)
assert (dW.shape == W.shape)
assert (db.shape == b.shape)
return dA_prev, dW, db

 

6.2 Linear-Activation backward

6.1 함수를 수행하기 전 dZ를 구하고, 6.1 함수를 수행하는 linear-activation backward 함수를 구현합니다.

여기서 제공되는 sigmoid_backward와 relu_backward 함수를 사용해서 dZ를 구하게 됩니다.

g(z)g(z)는 activation 함수, g(z){g(z)}'는 activation의 도함수입니다.

dZ[l]=dA[l]g(Z[l])dZ^{[l]} = dA^{[l]} \ast {g}'(Z^{[l]})

# GRADED FUNCTION: linear_activation_backward
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":
### START CODE HERE ### (≈ 2 lines of code)
dZ = relu_backward(dA, activation_cache)
dA_prev, dW, db = linear_backward(dZ, linear_cache)
### END CODE HERE ###
elif activation == "sigmoid":
### START CODE HERE ### (≈ 2 lines of code)
dZ = sigmoid_backward(dA, activation_cache)
dA_prev, dW, db = linear_backward(dZ, linear_cache)
### END CODE HERE ###
return dA_prev, dW, db

 

6.3 L-model Backward

이제, 전체 network에서의 backward 함수를 구현합니다. L_model_forward 함수를 통해서, 매 iteration마다 (X, W, b, z)를 저장한 cache를 기억하면서, BP module에서 cache를 사용해 gradient를 계산합니다. 

Initializing backpropagation: 신경망에서 BP를 진행하기 위해서는 우선 BP 입력값인 dAL을 계산해야 합니다. 

dAL = LA[L]\frac{\partial \mathscr{L}}{\partial A^{[L]}}은 다음과 같이 계산됩니다.

dAL = - (np.divide(Y, AL) - np.divide(1 - Y, 1 - AL))

즉, 다음을 계산한 것입니다.

(YAL(1Y)(1AL))-(\frac{Y}{AL} - \frac{(1 - Y)}{(1 - AL)})

계산된 미분값은 grads에 저장되며, l=3l = 3인 경우에 dW[l],db[l]dW^{[l]}, db^{[l]}는 grads["dW3"], grads["db3"]에 저장됩니다.

# GRADED FUNCTION: L_model_backward
def L_model_backward(AL, Y, caches):
"""
Implement the backward propagation for the [LINEAR->RELU] * (L-1) -> LINEAR -> SIGMOID group
Arguments:
AL -- probability vector, output of the forward propagation (L_model_forward())
Y -- true "label" vector (containing 0 if non-cat, 1 if cat)
caches -- list of caches containing:
every cache of linear_activation_forward() with "relu" (it's caches[l], for l in range(L-1) i.e l = 0...L-2)
the cache of linear_activation_forward() with "sigmoid" (it's caches[L-1])
Returns:
grads -- A dictionary with the gradients
grads["dA" + str(l)] = ...
grads["dW" + str(l)] = ...
grads["db" + str(l)] = ...
"""
grads = {}
L = len(caches) # the number of layers
m = AL.shape[1]
Y = Y.reshape(AL.shape) # after this line, Y is the same shape as AL
# Initializing the backpropagation
### START CODE HERE ### (1 line of code)
dAL = -(np.divide(Y, AL) - np.divide(1 - Y, 1 - AL))
### END CODE HERE ###
# Lth layer (SIGMOID -> LINEAR) gradients. Inputs: "dAL, current_cache". Outputs: "grads["dAL-1"], grads["dWL"], grads["dbL"]
### START CODE HERE ### (approx. 2 lines)
current_cache = caches[L-1]
grads["dA" + str(L-1)], grads["dW" + str(L)], grads["db" + str(L)] = linear_activation_backward(dAL, current_cache, activation = "sigmoid")
### END CODE HERE ###
# Loop from l=L-2 to l=0
for l in reversed(range(L-1)):
# lth layer: (RELU -> LINEAR) gradients.
# Inputs: "grads["dA" + str(l + 1)], current_cache". Outputs: "grads["dA" + str(l)] , grads["dW" + str(l + 1)] , grads["db" + str(l + 1)]
### START CODE HERE ### (approx. 5 lines)
current_cache = caches[l]
dA_prev_temp, dW_temp, db_temp = linear_activation_backward(grads["dA"+str(l+1)], current_cache, activation = "relu")
grads["dA" + str(l)] = dA_prev_temp
grads["dW" + str(l + 1)] = dW_temp
grads["db" + str(l + 1)] = db_temp
### END CODE HERE ###
return grads

 

6.4 Update Parameters

BP를 진행해 gradient를 구하고 gradient descent를 사용해 파라미터를 업데이트합니다.

W[l]=W[l]αdW[l]b[l]=b[l]αdb[l]\begin{matrix} W^{[l]} = W^{[l]} - \alpha dW^{[l]} \\ b^{[l]} = b^{[l]} - \alpha db^{[l]} \end{matrix}

위와 같이 갱신되며, α\alpha는 learning rate입니다. 업데이트된 파라미터값은 parameters에 저정됩니다.

# GRADED FUNCTION: update_parameters
def update_parameters(parameters, grads, learning_rate):
"""
Update parameters using gradient descent
Arguments:
parameters -- python dictionary containing your parameters
grads -- python dictionary containing your gradients, output of L_model_backward
Returns:
parameters -- python dictionary containing your updated parameters
parameters["W" + str(l)] = ...
parameters["b" + str(l)] = ...
"""
L = len(parameters) // 2 # number of layers in the neural network
# Update rule for each parameter. Use a for loop.
### START CODE HERE ### (≈ 3 lines of code)
for l in range(L):
parameters["W" + str(l+1)] = parameters["W" + str(l+1)] - learning_rate * grads["dW" + str(l + 1)]
parameters["b" + str(l+1)] = parameters["b" + str(l+1)] - learning_rate * grads["db" + str(l + 1)]
### END CODE HERE ###
return parameters

 

 

NN의 구성을 단계별로 구현해보았습니다. 다음 실습에서 이 함수들을 사용해서 2주차 실습에 Logistic Regression으로 구현한 cat vs non-cat 분류기를 NN으로 구현해보도록 하겠습니다.

댓글