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

[실습] Initialization 초기화

by 별준 2020. 9. 26.
해당 내용은 Coursera의 딥러닝 특화과정(Deep Learning Specialization)의 두 번째 강의 
Improving Deep Neural Networks: Hyperparameter tuning, Regularization and Optimization를 듣고 정리한 내용입니다. (Week 1)

1주차 첫 번째 실습은 초기화(Initialization)입니다.

잘 선택된 초기화를 학습의 성능을 높일 수 있습니다. 

- Gradient Descent의 수렴 속도 상승

- Gradient Descent가 더 낮은 training error에 수렴하는 확률을 증가시킴

 

우선 이번 실습에서 분류해야될 planar dataset을 읽어봅시다.

import numpy as np
import matplotlib.pyplot as plt
import sklearn
import sklearn.datasets
from init_utils import sigmoid, relu, compute_loss, forward_propagation, backward_propagation
from init_utils import update_parameters, predict, load_dataset, plot_decision_boundary, predict_dec

%matplotlib inline
plt.rcParams['figure.figsize'] = (7.0, 4.0) # set default size of plots
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'

# load image dataset: blue/red dots in circles
train_X, train_Y, test_X, test_Y = load_dataset()

우리의 목표는 blue와 red point들의 구역을 분리하는 분류기를 구현하는 것입니다.

 

1. Neural Network model

3-layer NN을 사용해서 3가지의 초기화 방법을 사용해서 결과를 비교해봅시다.

1. Zero initialization - 0으로 초기화

2. Random initialization - 랜덤 초기화

3. He initialization - He et al의 논문에서 사용된 초기화 방법

사용되는 모델은 아래처럼 구현됩니다. 각각의 초기화는 initialization 매개변수를 통해서 선택합니다.

def model(X, Y, learning_rate = 0.01, num_iterations = 15000, print_cost = True, initialization = "he"):
    """
    Implements a three-layer neural network: LINEAR->RELU->LINEAR->RELU->LINEAR->SIGMOID.
    
    Arguments:
    X -- input data, of shape (2, number of examples)
    Y -- true "label" vector (containing 0 for red dots; 1 for blue dots), of shape (1, number of examples)
    learning_rate -- learning rate for gradient descent 
    num_iterations -- number of iterations to run gradient descent
    print_cost -- if True, print the cost every 1000 iterations
    initialization -- flag to choose which initialization to use ("zeros","random" or "he")
    
    Returns:
    parameters -- parameters learnt by the model
    """
        
    grads = {}
    costs = [] # to keep track of the loss
    m = X.shape[1] # number of examples
    layers_dims = [X.shape[0], 10, 5, 1]
    
    # Initialize parameters dictionary.
    if initialization == "zeros":
        parameters = initialize_parameters_zeros(layers_dims)
    elif initialization == "random":
        parameters = initialize_parameters_random(layers_dims)
    elif initialization == "he":
        parameters = initialize_parameters_he(layers_dims)

    # Loop (gradient descent)

    for i in range(0, num_iterations):

        # Forward propagation: LINEAR -> RELU -> LINEAR -> RELU -> LINEAR -> SIGMOID.
        a3, cache = forward_propagation(X, parameters)
        
        # Loss
        cost = compute_loss(a3, Y)

        # Backward propagation.
        grads = backward_propagation(X, Y, cache)
        
        # Update parameters.
        parameters = update_parameters(parameters, grads, learning_rate)
        
        # Print the loss every 1000 iterations
        if print_cost and i % 1000 == 0:
            print("Cost after iteration {}: {}".format(i, cost))
            costs.append(cost)
            
    # plot the loss
    plt.plot(costs)
    plt.ylabel('cost')
    plt.xlabel('iterations (per hundreds)')
    plt.title("Learning rate =" + str(learning_rate))
    plt.show()
    
    return parameters

 

2. Zero initialization

간단하게 모든 W와 b를 0으로 초기화합니다.

이미 알겠지만, 이 방법으로 학습하면 break symmetry에 실패하기 때문에, 알고리즘이 제대로 동작하지 않습니다.

numpy의 zeros((shape))을 사용합니다.

# GRADED FUNCTION: initialize_parameters_zeros 

def initialize_parameters_zeros(layers_dims):
    """
    Arguments:
    layer_dims -- python array (list) containing the size of each layer.
    
    Returns:
    parameters -- python dictionary containing your parameters "W1", "b1", ..., "WL", "bL":
                    W1 -- weight matrix of shape (layers_dims[1], layers_dims[0])
                    b1 -- bias vector of shape (layers_dims[1], 1)
                    ...
                    WL -- weight matrix of shape (layers_dims[L], layers_dims[L-1])
                    bL -- bias vector of shape (layers_dims[L], 1)
    """
    
    parameters = {}
    L = len(layers_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.zeros((layers_dims[l], layers_dims[l - 1]))
        parameters['b' + str(l)] = np.zeros((layers_dims[l], 1))
        ### END CODE HERE ###
    return parameters

추가적으로 각 파라미터 \(W^{[l]}\)은 \((n^{[l]}, n^{[l-1]})\)의 차원을 갖습니다.

 

이렇게 초기화한 파라미터로 3-layer NN을 학습하면 아래처럼 결과가 나옵니다.

parameters = model(train_X, train_Y, initialization = "zeros")
print ("On the train set:")
predictions_train = predict(train_X, train_Y, parameters)
print ("On the test set:")
predictions_test = predict(test_X, test_Y, parameters)

결과는 매우 좋지 않습니다. Cost는 감소하지 않고, 알고리즘의 성능도 좋아지지 않습니다.

이렇게 학습한 모델의 결과와 decision boundary를 살펴봅시다.

모든 예측값이 0입니다.

모든 파라미터 W를 0으로 초기화하게 되면, break symmetry에 실패한 네트워크가 됩니다. 이것은 각 layer의 모든 neuron이 같은 값으로 학습되며, 이것은 모든 layer의 크기 \(n^{[l]} = 1\)인 네트워크를 학습하는 것과 동일합니다. 이

 

따라서,

1. 파라미터 \(W^{[l]}\)은 break symmetry를 위해 랜덤하게 초기화해야 하며,

2. 그러나, bias인 \(b^{[l]}\)은 0으로 초기화해도 무관합니다.

 

3. Random initialization

위에서 설명했듯이, break symmetry(대칭이 되는 것을 피함)을 위해서 모든 파라미터 W를 랜덤하게 초기화해야합니다. 랜덤 초기화를 하면, 각 neuron은 각기 다른 함수로 학습하게 됩니다.

 

아래 예제에서 랜덤으로 초기화를 하지만, 매우 큰 값으로 초기화를 하면 어떻게 되는지 알아봅시다.

np.random.randn(shape)를 사용하는데, *10을 통해서 큰 값으로 만들어서 초기화하는 함수입니다.

# GRADED FUNCTION: initialize_parameters_random

def initialize_parameters_random(layers_dims):
    """
    Arguments:
    layer_dims -- python array (list) containing the size of each layer.
    
    Returns:
    parameters -- python dictionary containing your parameters "W1", "b1", ..., "WL", "bL":
                    W1 -- weight matrix of shape (layers_dims[1], layers_dims[0])
                    b1 -- bias vector of shape (layers_dims[1], 1)
                    ...
                    WL -- weight matrix of shape (layers_dims[L], layers_dims[L-1])
                    bL -- bias vector of shape (layers_dims[L], 1)
    """
    
    np.random.seed(3)               # This seed makes sure your "random" numbers will be the as ours
    parameters = {}
    L = len(layers_dims)            # integer representing the number of layers
    
    for l in range(1, L):
        ### START CODE HERE ### (≈ 2 lines of code)
        parameters['W' + str(l)] = np.random.randn(layers_dims[l], layers_dims[l - 1]) * 10
        parameters['b' + str(l)] = np.zeros((layers_dims[l], 1))
        ### END CODE HERE ###

    return parameters

b는 0으로 초기화를 했고, W는 다음과 같이 큰 값을 갖게 됩니다.

이렇게 초기화한 파라미터로 3-layer NN을 학습해봅시다.

parameters = model(train_X, train_Y, initialization = "random")
print ("On the train set:")
predictions_train = predict(train_X, train_Y, parameters)
print ("On the test set:")
predictions_test = predict(test_X, test_Y, parameters)

결과는 위와 같습니다. 

어쨋든 큰 값이기는 하지만 랜덤 초기화를 통해서 broken symmetry는 성공한 것 같습니다. 이렇게 학습된 모델은 더이상 0으로만 예측하지 않습니다.

Decision Boundary는 아래와 같이 결정됩니다.

몇 가지 특징을 정리해보겠습니다.

1. Cost는 처음에 매우 큰 값으로 시작합니다. 이것은 랜덤 초기화가 초기에 매우 큰 값이기 때문이고, 그 결과 sigmoid한 예측값이 대부분 0이나 1이기 때문입니다. 그래서 실제 label과 다르게 예측한 경우에 매우 큰 loss가 발생하게 됩니다. 실제로 loss가 \(log(a^{[3]}) = log(0)\)이 되면, 무한대가 됩니다.

2. 좋지 않은 초기화(Pool initialization)은 vanishing/exploding gradients를 야기합니다. 이것은 최적화 알고리즘을 느리게 만듭니다.

3. 오랫동안 학습하면 좋은 결과를 얻을 수는 있지만, 큰 수로 초기화를 하게되면 최적화 속도는 느려집니다.

 

-> 매우 큰 값으로 랜덤하게 초기화를 하면 잘 동작하지 않습니다. 

 

4. He initialization

'He Initialization'은 논문의 제1저서인 He et al의 이름을 따왔습니다(2015년).

'Xavier initialization'과 유사한데, xavier initialization은 파라미터 W에 sqrt(1./layers_dims[l-1]) scaling factor를 사용하지만, He Initialization은 sqrt(2./layers_dims[l-1])를 scaling factor로 사용한다.

즉, 파라미터를 랜덤하게 초기화하고, \(\sqrt{\frac{2}{\text{dimension of the previous layer}}}\)을 곱해주면 된다.

그리고 He initialization은 ReLU activation을 사용하는 layer에서 사용되는 것을 추천한다.

 

# GRADED FUNCTION: initialize_parameters_he

def initialize_parameters_he(layers_dims):
    """
    Arguments:
    layer_dims -- python array (list) containing the size of each layer.
    
    Returns:
    parameters -- python dictionary containing your parameters "W1", "b1", ..., "WL", "bL":
                    W1 -- weight matrix of shape (layers_dims[1], layers_dims[0])
                    b1 -- bias vector of shape (layers_dims[1], 1)
                    ...
                    WL -- weight matrix of shape (layers_dims[L], layers_dims[L-1])
                    bL -- bias vector of shape (layers_dims[L], 1)
    """
    
    np.random.seed(3)
    parameters = {}
    L = len(layers_dims) - 1 # integer representing the number of layers
     
    for l in range(1, L + 1):
        ### START CODE HERE ### (≈ 2 lines of code)
        parameters['W' + str(l)] = np.random.randn(layers_dims[l], layers_dims[l-1]) * np.sqrt(2 / layers_dims[l - 1])
        parameters['b' + str(l)] = np.zeros((layers_dims[l], 1))
        ### END CODE HERE ###
        
    return parameters

꽤 작은 값으로 초기화된 것을 볼 수 있다.

parameters = model(train_X, train_Y, initialization = "he")
print ("On the train set:")
predictions_train = predict(train_X, train_Y, parameters)
print ("On the test set:")
predictions_test = predict(test_X, test_Y, parameters)

이렇게 초기화된 파라미터로 학습하면, 아래와 같은 결과를 얻을 수 있다.

그리고 Decision Boundary는 아래와 같이 구해진다.

정확도도 매우 높고, blue와 red points를 잘 분리한 것을 볼 수 있다.

 

5. Conclusion

3가지의 초기화를 비교하면 다음과 같습니다.

 

 

What you should remember from this notebook:

  • 다른 초기화는 다른 결과값으로 예측합니다.
  • 랜덤한 값으로 초기화하면 break symmetry하고, 각각의 hidden unit들이 다른 값들로 학습하도록 합니다.
  • 다만, 너무 큰 값으로 초기화하면 안됩니다.
  • He Initialization은 ReLU activation을 가지는 layer에서 잘 동작합니다.

 

 

댓글