프로젝트/딥러닝

[CH.4]신경망 학습하기-2 (경사하강법)

냥냥친구 2020. 2. 9. 13:36

지난 챕터에서는 데이터를 예측하고 예측 값에 대한 손실함수 구하기를 살펴보았다. 

지난 챕터 바로가기 : https://myphiloprogramming.tistory.com/22

 

다음 순서를 계속 진행해보자.

 

4. 경사하강법으로 가중치 값 개선하기

우리는 손실 함수 값을 줄여나감으로써 최적의 매개변수를 찾는다. 손실 함수 값을 줄이는 방법으로는 경사하강법을 사용하는데,

적용하기 전에 그 방법을 이해해보자.

먼저, 손실 함수값을 좌표 위에 찍어서 현재 위치를 확인한다. 적기로는 좌표 위에 점을 찍는다고 했지만, 실제로 차원은 가중치 매개변수 개수 만큼 있기 때문에 그릴수도, 그래프의 모양을 확인할 수도 없다.  적당히 이해하기로는 x축은 가중치 매개변수 개수 만큼있고 y축은 손실함수 값이 된다. 그림으로 나타내보면 다음과 같다.(이 그림은 이해를 돕기 위할 뿐이며 실제로는 그림으로 나타낼 수도 없다.)

 

전체 그래프를 모르기 때문에 어디가 손실함수 값의 최솟값인지 짐작할 수 없다. 이런 상황에서 기울기를 이용해 최솟값을 찾으려는 것이 경사법이다. 기울어진 방향에 꼭 최솟값이 있는 것은 아니지만 그 방향으로 갔을 때 손실함수 값을 줄일 수 있다. 그래서 기울기를 단서로 나아갈 방향을 정하게 된다. 기울기는 아래 그림처럼 방향을 가진 벡터로 그려진다.

기울기

화살표를 보면 한 곳을 향하고 있는데, 이 때 가리키는 위치가 가장 최솟값이 된다. 정리하면 현재 위치에서 기울기를 구한 후, 손실함수 값이 낮아지는 방향으로 이동한다.

경사하강법은 현 위치에서 기울어진 방향으로 일정 거리만큼이동한다. 그런 다음 이동한 곳에서 기울기를 한번 더 구하고 또 기울어진 방향으로 나아가기를 반복함으로써 최솟값을 찾아나간다. 

경사하강법을 구현하면 다음과 같다.

def numerical_gradient(f, x):
    h = 1e-4
    grad = np.zeros_like(x)
    
    for idx in range(x.size):
        tmp_val = x[idx]
        x[idx] = tmp_val + h
        fxh1 = f(x)
        
        x[idx] = tmp_val - h
        fxh2 = f(x)
        
        grad[idx] = (fxh1 - fxh2) / (2*h)
        x[idx] = tmp_val
        
    return grad


def gradient_descent(f, init_x, lr=0.01, step_num=100):
    x = init_x
    
    for i in range(step_num):
        grad = numerical_gradient(f,x)
        x -= lr * grad
    return x

 

gradient_descent함수의 파라미터를 살펴보면, f는 최적화하려는 함수, init_x는 초깃값, lr은 학습률, step_num은 반복 횟수를 의미한다.

그리고 numerical_gradient 함수는 기울기를 구한다.

init_x = np.array([-3.0, 4.0])
gradient_descent(function_2, init_x=init_x, lr=0.1, step_num=100)

gradient_descent 함수 실행 결과

gradient_descent함수를 사용하면 step_num만큼 반복하면서 찾은 최솟값의 위치를 리턴한다. 다시 말해, 손실함수가 최솟값이 되는 매개변수 값을 리턴한다. 위 예시에서는 초기 매개변수로 [-3.0, 4.0]을 넣었더니, [-6.11e-10, 8.14e-10] 결과가 반환됐다.


 5. 2,3,4 반복하며 최적값 찾기

gradient_descent 함수가 리턴한 값은 개선된 가중치 값이다. 이제 이 값을 가지고 다시 숫자 이미지를 맞춘다. 다시 배치 데이터를 뽑고 새로 갱신된 가중치 값으로 예측한 후, 새로운 손실함수 값을 또 최소화도록 경사하강법을 적용한다. 이렇게 이 과정을 반복하면서 가중치 최적값을 찾아나간다.

1번부터 5번까지에 대한 전체 코드는 다음과 같다.

class TwoLayerNet:
    def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
        #가중치 초기화
        self.params = {}
        self.params['W1'] = weight_init_std * \
                            np.random.randn(input_size, hidden_size)
        self.params['b1'] = np.zeros(hidden_size)
        self.params['W2'] = weight_init_std * \
                            np.random.randn(hidden_size, output_size)
        self.params['b2'] = np.zeros(output_size)
        
        
        
        
    def predict(self, x):
        W1, W2 = self.params['W1'], self.params['W2']
        b1, b2 = self.params['b1'], self.params['b2']
        
        a1 = np.dot(x, W1) + b1
        z1 = sigmoid(a1)
        a2 = np.dot(z1, W2) + b2
        y = softmax(a2)
        
        return y
    
    def loss(self, x, t):
        y = self.predict(x)
        
        return cross_entropy_error(y, t)
    
    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)
        t = np.argmax(t, axis=1)
        
        accuracy = np.sum(y==t) / float(x.shape[0])
        return accuracy


    def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x,t)
        
        grads = {}
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])    
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
        
        return grads
#미니 배치
(x_train, t_train), (x_test, t_test) = \
    load_mnist(normalize=True, one_hot_label=True)

train_loss_list = []

iters_num = 10000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1

network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)

for i in range(iters_num):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]
    
    grad = network.numerical_gradient(x_batch, t_batch)
    
    for key in ('W1','b1','W2','b2'):
        network.params[key] -= learning_rate * grad[key]
        
    loss = network.loss(x_batch, t_batch)
    train_loss_list.append(loss)

 

6. 테스트 데이터로 성능 테스트 해보기

이로써 신경망 구현이 끝났다. 이제 테스트 데이터를 적용해서 정확도를 확인해보자.

테스트 코드는 다음과 같다.

(x_train, t_train), (x_test, t_test) = \
    load_mnist(normalize=True, one_hot_label=True)

network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)

iters_num = 10000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1

train_loss_list = []
train_acc_list = []
test_acc_list = []

iters_per_epoch = max(train_size / batch_size, 1)

for i in range(iters_num):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = t_train[bathc_mask]
    
    grad = network.numerical_gradient(x_bathc, t_batch)
    
    for key in ('W1','b1','W2','b2'):
        network.params[key] -= learning_rate * grad[key]
        
    loss = network.loss(x_batch, t_batch)
    train_loss_list.append(loss)
    
    if i % iter_per_epoch == 0:
        train_acc = network.accuracy(x_train, t_train)
        test_acc = network.accuracy(x_test, t_test)
        train_acc_list.append(train_acc)
        test_acc_list.append(test_acc)
        print("train acc, test acc |" + str(train_acc) + ", " + str(test_acc))