티스토리 뷰

1. 경사법(경사 하강법)


기계학습 문제 대부분은 학습 단계에서 최적의 매개변수를 찾아냅니다. 신경망 역시 최적의 매개변수(가중치와 편향)을 학습 시에 찾아야 합니다.

여기에서 최적이란 손실 함수가 최솟값이 될 때의 매개변수 값입니다.

일반적인 문제의 손실 함수는 매우 복잡하죠 매개변수 공간이 광대하여 어디가 최솟값이 되는 곳인지를 짐작할 수 없습니다.

이런 상황에서 기울기를 잘 이용해 함수의 최솟값을 찾으려는 것이 경사법입니다.


여기서 주의할 점은 각 지점에서 함수의 값을 낮추는 방안을 제시하는 지표가 기울기라는 것입니다.


경사하강법은 현 위치에서 기울어진 방향으로 일정 거리만큼 이동합니다. 그런 다음 이동한 곳에서도 마찬가지로 기울기를 구하고, 또 기울어진 방향으로 나아가기를 반복합니다. 이렇게 해서 함수의 값을 점차 줄이는 것이 경사 하강법입니다.


인수 f는 최적화하려는 함수, init_x는 초기값, lr은 learning rate를 의미하는 학습률, step_num은 경사법에 따른 반복 횟수.



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




2. 신경망에서의 기울기


신경망 학습에서도 기울기를 구해야 하는데 여기서 말하는 기울기는 가중치 매개변수에 대한 손실 함수의 기울기입니다.


예를 들어 형상이 2 * 3, 가중치가 W, 손실 함수가 L인 신경망을 생각해보면 수식으로는 다음과 같습니다.

1행 1번째 원소인 W11을 조금 변경했을 때 손실 함수 L이 얼마나 변화하느냐를 나타내고 여기서 중요한 점은 편미분 한것이 W와 같다는 것입니다.


소스코드 구현


import sys, os

sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정

import numpy as np

#from common.functions import softmax, cross_entropy_error

#from common.gradient import numerical_gradient



def softmax(x):

    if x.ndim == 2:

        x = x.T

        x = x - np.max(x, axis=0)

        y = np.exp(x) / np.sum(np.exp(x), axis=0)

        return y.T 


    x = x - np.max(x) # 오버플로 대책

    return np.exp(x) / np.sum(np.exp(x))



def cross_entropy_error(y, t):

    if y.ndim == 1:

        t = t.reshape(1, t.size)

        y = y.reshape(1, y.size)

        

    # 훈련 데이터가 원-핫 벡터라면 정답 레이블의 인덱스로 반환

    if t.size == y.size:

        t = t.argmax(axis=1)

             

    batch_size = y.shape[0]

    return -np.sum(np.log(y[np.arange(batch_size), t])) / batch_size


def numerical_gradient(f, x):

    h = 1e-4 # 0.0001

    grad = np.zeros_like(x)

    

    it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])

    while not it.finished:

        idx = it.multi_index

        tmp_val = x[idx]

        x[idx] = float(tmp_val) + h

        fxh1 = f(x) # f(x+h)

        

        x[idx] = tmp_val - h 

        fxh2 = f(x) # f(x-h)

        grad[idx] = (fxh1 - fxh2) / (2*h)

        

        x[idx] = tmp_val # 값 복원

        

    return grad


def f(W):

    return net.loss(x, t)

class simpleNet:

    def __init__(self):

        self.W = np.random.randn(2,3) # 정규분포로 초기화


    def predict(self, x):

        return np.dot(x, self.W)


    def loss(self, x, t):

        z = self.predict(x)

        y = softmax(z)

        loss = cross_entropy_error(y, t)


        return loss




net = simpleNet()


print(net.W)

#[[ 2.13924928 -1.53187348  1.3352016 ]

 [-1.52693031 -0.43417503 -0.28699196]]



x = np.array([0.6, 0.9])

p = net.predict(x)


print(p)

#[-0.09068772 -1.30988162  0.54282819]


t = np.array([0, 0, 1])

print(net.loss(x, t))

#0.5232684141105788


dW = numerical_gradient(f, net.W)

print(dW)

#[[ 0.21924763 0.14356247 -0.36281009]

  [0.32887144 0.2153437 -0.54421514]]



편미분 한 w11은 대략 0.2입니다. 이는 w11을 h만큼 늘리면 손실 함수의 값은 0.2h 만큼 증가한다는 의미이고 편미분한 w23은은 대략 -0.5이니 

h만큼 늘리려면 손실 함수의 값은 0.5h만큼 감소하는 것입니다. 

그래서 손실 함수를 줄인다는 관점에서는 w23은 양의 방향으로 갱신하고 w11은 음의 방향으로 갱신해야 합니다. 

댓글
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31