ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [NLP] 신경망 학습을 시작해보자 : 손실 함수를 중심으로↗
    ML/NLP 2020. 3. 18. 02:11

     

     

     

    이번 주제는 드디어 신경망 학습이다!

     

    학습이란, 훈련 데이터로부터 가중치 매개변수의 최적값을 자동으로 획득하는 것이다.

     

    신경망을 학습시키려면 잘 하고 있는 건지 확인이 필요하다~

     

    그때 사용하는 지표가 손실 함수이다.

     

    즉, 손실 함수의 값을 확인해가면서 더 들어 맞는 가중치 매개변수 쪽으로 학습이 진행되는 거다.

     

     

     

     

    학습은 데이터가 중심이다!


    기계학습은 데이터가 생명이다.

     

    대부분 학습을 할 때는 사람의 개입을 최소화하여 수집한 데이터로부터 패턴을 찾으려고 시도한다.

     

    게다가 신경망과 딥러닝은 기존 기계학습에서 사용하던 방법보다 사람의 개입을 더욱 배제할 수 있게 해주는 중요한 특성을 지녔다.

     

     

     

    하나의 예를 들어보겠다.

     

    '5'를 뜻하는 손글씨 이미지들이 여러 개 모여있다고 하자.

     

    이때, 기존에 가지고 있던 데이터를 기반으로 하여 새로운 사람이 쓴 '5'라는 글자를 '5'라고 인식할 수 있게 하고 싶다.

     

    이는 주어진 데이터를 잘 활용해서 해결할 수 있다.

     

     

     

    이미지에서 특징(feature)을 추출하고 그 특징의 패턴을 기계학습 기술로 학습하는 방법이 있다.

     

    여기서 말하는 특징은 입력 데이터(입력 이미지)에서 본질적인 데이터를 정확하게 추출할 수 있도록 설계된 변환기이다. 

     

    즉, 입력 이미지를 특징이라는 것을 통해 벡터 정보로 추출할 수 있다.

     

     

     

    이와 같은 기계학습에서는 방금 언급한 특징이라는 것을 여전히 사람이 설계하기는 한다.

     

    따라서 사람이 문제에 적합한 특징을 쓰지 않으면 좀처럼 좋은 결과를 얻을 수 없다.

     

     

     

    그러나! 이를 해결할 수 있는 방법이 있다.

     

    바로 신경망이다.

     

    신경망은 이미지를 있는 그대로 학습한다.

     

    특징을 사람이 설계하는 방법과 달리, 신경망은 이미지에 포함된 중요한 특징까지도 기계가 스스로 학습하게 된다.

     

     

     

     

     

    훈련 데이터와 시험 데이터


    본격적인 신경망 학습 설명에 앞서 설명할 개념이 있다.

     

     

     

    기계학습 문제는 데이터를 훈련 데이터(training data)와 시험 데이터(test data)로 나눠 학습과 실험을 수행하는 것이 일반적이다.

     

    훈련 데이터만 사용하여 학습하며 최적의 매개변수를 찾고, 그런 다음 시험 데이터를 사용하여 앞서 훈련한 모델의 실력을 평가한다.

     

    이렇게 데이터를 나누는 까닭은 '범용 능력'을 활용하기 위함이다.

     

     

     

    범용 능력은 학습시킨 모델이 아직 보지 못한 데이터(훈련 데이터에 포함되지 않는 데이터)로도 문제를 올바르게 풀어내는 능력이다.

     

    이 범용 능력을 획득하는 것이 기계학습의 최종 목표다.

     

    참고로 한 데이터셋에만 지나치게 최적화된 상태를 오버피팅(overfitting)이라고 한다. 오버피팅 피하기는 기계학습의 중요한 과제이다.

     

     

     

     

     

    손실 함수


    신경망 학습에서는 현재 학습되고 있는 상태를 '하나의 지표'로 표현한다.

     

    그리고 '하나의 지표'를 기준으로 최적의 가중치 매개변수 값을 탐색한다.

     

    신경망 학습에서 사용하는 지표는 손실 함수(loss function)라고 한다.

     

    손실 함수는 임의의 함수를 사용할 수도 있지만 일반적으로 오차제곱합과 교차 엔트로피 오차를 사용한다.

     

     

     

     

     

    오차제곱합


    가장 많이 쓰이는 손실 함수는 오차제곱합(sum of squares for errror, SSE)이다. 수식은 다음과 같다.

    여기서 yk는 신경망의 출력(신경망이 추정한 값), tk는 정답 레이블, k는 데이터의 차원 수를 나타낸다.

    python으로 구현해보자.

     

    import numpy as np
    def sum_squares_error(y, t):
        return 0.5 * np.sum((y-t)**2)
    y1 = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]   # '2'라고 추정
    y2 = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]   # '7'이라고 추정
    t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]   # 정답은 '2'임
    sum_squares_error(np.array(y1), np.array(t))
    Out:    0.09750000000000003
    sum_squares_error(np.array(y2), np.array(t))
    Out:    0.5975

     

     

     

     

     

    교차 엔트로피 오차


    또 다른 손실 함수로서 교차 엔트로피 오차(cross entropy error, CEE)도 자주 이용한다. 수식은 다음과 같다.

    여기서 log는 밑이 e인 자연로그이다. yk는 신경망의 출력, tk는 정답 레이블이다. 또한 tk는 정답에 해당하는 인덱스의 원소만 1이고 나머지는 0이다(원-핫 인코딩). 그래서 실질적으로 정답일 때의 추정(tk가 1일 때의 yk)의 자연로그를 계산하는 식이 된다.

     

    예를 들어 정답 레이블은 '2'가 정답이라 하고 이때의 신경망 출력이 0.6이라면 교차 엔트로피 오차는 -log0.6=0.51이 된다. 또한 같은 조건에서 신경망 출력이 0.1이라면 -log0.1=2.30이 된다.

     

    즉, 교차 엔트로피 오차는 정답일 때의 출력이 전체 값을 정하게 된다.

     

    log가 들어가기 때문에 정답에 해당하는 출력이 커질수록 0에 다가가다가, 그 출력이 1일 때 0이 된다. 반대로 정답일 때의 출력이 작아질수록 오차는 커진다.

     

    python으로 구현을 해보자.

    def cross_entropy_error(y, t):
        delta = 1e-7   #np.log() 함수에 0이 들어가는걸 방지하기 위해 설정
        return -np.sum(t * np.log(y+delta))
    cross_entropy_error(np.array(y1), np.array(t))
    Out:    0.510825457099338
    cross_entropy_error(np.array(y2), np.array(t))
    Out:    2.302584092994546

    결과(오차 값)가 더 작은 첫 번째 추정이 정답일 가능성이 높다고 판단했다. 앞서 오차제곱합의 판단과 일치한다.

     

     

     

     

     

     

    미니배치 학습


    기계학습 문제는 훈련 데이터에 대한 손실 함수의 값을 구하고, 그 값을 최대한 줄여주는 매개변수를 찾아낸다.

     

    이렇게 하려면 모든 훈련 데이터를 대상으로 손실 함수 값을 구해야 한다.

     

    지금까지 데이터 하나에 대한 손실 함수만 생각했으니, 이제 훈련 데이터 모두에 대한 손실 함수의 합을 구하는 방법을 생각해보겠다.

     

    예를 들어 교차 엔트로피 오차는 아래와 같이 된다.

     

    데이터가 N개라면 tnk는 n번째 데이터의 k번째 값을 의미한다(ynk는 신경망의 출력, tnk는 정답 레이블).

     

    이전 교차 엔트로피 수식과 거의 비슷하다.

     

    다만, 마지막에 N으로 나누어 정규화하고 있다. N으로 나눔으로써 '평균 손실 함수'를 구하는 것이다.

     

    이렇게 평균을 구해서 사용하면 훈련 데이터 개수와 관계없이 언제든 통일된 지표를 얻을 수 있다. 

     

     

    그런데 MNIST 데이터셋은 훈련 데이터가 60,000개 였다.

     

    이 많은 데이터를 대상으로 일일이 손실 함수를 계산하는 것은 현실적이지 않다.

     

    이런 경우 데이터 일부를 추려 전체의 근사치로 이용할 수 있다.

     

     

    신경망 학습에서도 훈련 데이터로부터 일부만 골라 학습을 수행한다. 이 일부를 미니배치라고 한다.

     

    그리고 이렇게 뽑아서 학습시키는 것을 미니배치 학습이라고 한다.

     

     

    python으로 구현해보자.

    import sys, os
    sys.path.append(os.pardir) # 부모 디텍터리의 파일을 가져올 수 있도록 설정
    from dataset.mnist import load_mnist
    (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
    print(x_train.shape)
    Out:    (60000, 784)
    print(t_train.shape)
    Out:    (60000, 10)
    train_size = x_train.shape[0]
    batch_size = 10
    batch_mask = np.random.choice(train_size, batch_size)   
    x_batch = x_train[batch_mask]
    x_batch = x_train[batch_mask]
    • x_train은 [ [ ], ... , [ ] ] 형식
    • np.random.choice(60000, 10) => 0이상 60000미만의 수 중에서 무작위로 10개를 골라냄
    • 몇 번째 인덱스 뽑을 건지 랜덤으로 설정 e.g.[0, 55, 66] => 0, 55, 66번째 인덱스 데이터 뽑아라

     

     

    미니배치의 손실 함수는 일부 표본 데이터로 전체를 비슷하게 계측한다. 즉, 전체 훈련 데이터의 대표로서 무작위로 선택한 작은 덩어리(미니배치)를 사용하는 것이다.

     

     

     

     

    (배치용) 교차 엔트로피 오차 구현하기


    1. 레이블이 원-핫 인코딩 일때

    # label이 one hot encoding 일때
    def cross_entropy_error(y, t):
        if y.ndim == 1:    # 데이터 []로 한 개면 [[]]로 바꿔줌 
            t = t.reshape(1, t.size)
            y = y.reshape(1, y.size)
            
        batch_size = y.shape[0]
        return -np.sum(t * np.log(y + 1e-7)) / batch_size

     

     

    2. 레이블이 그냥 정수일 때

    # label이 정수일때
    def cross_entropy_error(y, t):
        if y.ndim == 1:    # 데이터 []로 한 개면 [[]]로 바꿔줌 
            t = t.reshape(1, t.size)   
            y = y.reshape(1, y.size)
            
        batch_size = y.shape[0]
        return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size

     

     

    • y[np.arange(batch_size), t] 설명

     

    batch_size=5 일때
    np.arange(batch_size)=[0,1,2,3,4]
    t = [2,7,0,9,4]
    y[np.arange(batch_size), t] => [y[0,2],y[1,7],y[2,0],y[3,9],y[4,4]]
    즉, 정답 레이블 쪽에 있는 신경망의 예측 결과값만 뽑아냄 (어차피 t가 0인 원소는 교차 엔트로피 오차도 0이기 때문)

     

     

     

     

     

    왜 손실 함수를 설정하는가?


    예컨대 숫자 인식의 경우도 우리의 궁극적인 목적은 높은 '정확도'를 끌어내는 매개변수 값을 찾는 것이다.

     

    그렇다면 '정확도'라는 지표를 놔두고 '손실 함수의 값'이라는 우회적인 방법을 택하는 이유는 뭘까?

     

     

    이 의문은 신경망 학습에서의 '미분'의 역할에 주목한다면 해결된다. 

     

    신경망 학습에서는 최적의 매개변수(가중치와 편향)를 탐색할 때 손실 함수의 값을 가능한 한 작게 하는 매개변수 값을 찾는다.

     

    매개변수의 미분(정확히는 기울기)을 계산하고, 그 미분 값을 단서로 매개변수의 값을 서서히 갱신하는 과정을 반복한다.

     

     

    가령 여기에 가상의 신경망이 있고 그 신경망의 어느 한 가중치 매개변수에 주목한다고 하자.

     

    이때 그 가중치 매개변수의 손실 함수의 미분이란 '가중치 매개변수의 값을 아주 조금 변화시켰을 때, 손실 함수가 어떻게 변하나'라는 의미이다.

     

     

    미분 값이 음수면 그 가중치 매개변수를 양의 방향으로 변화시켜 손실 함수의 값을 줄일 수 있다.

     

    반대로 미분 값이 양수면 가중치 매개변수를 음의 방향으로 변화시켜 손실 함수의 값을 줄일 수 있다. 

     

    미분 값이 0이면 가중치 매개변수를 어느 쪽으로 움직여도 손실 함수의 값은 줄어들지 않는다.

     

    그래서 가중치 매개변수의 갱신은 거기서 멈춘다. 

     

     

    정확도를 지표로 삼아서는 안 되는 이유는 미분 값이 대부분의 장소에서 0이 되어 매개변수를 갱신할 수 없기 때문이다.

     

     

    정리하자면!

     

    신경망을 학습할 때 정확도를 지표로 삼아서는 안 된다. 정확도를 지표로 하면 매개변수의 미분이 대부분의 장소에서 0이 되기 때문이다.

     

    정확도는 매개변수의 미세한 변화에는 거의 반응을 보이지 않고, 반응이 있더라도 그 값이 불연속적으로 갑자기 변화한다.

     

     

    이는 '계단 함수'를 활성화 함수로 사용하지 않는 이유와도 들어맞는다.

     

    매개변수의 작은 변화가 주는 파장을 계단 함수가 말살하여 손실 함수의 값에는 아무런 변화가 나타나지 않기 때문이다.

     

     

    반면에 시그모이드 함수의 미분은 어느 장소라도 0이 되지는 않는다.

     

    이는 신경망 학습에서 중요한 성질로, 기울기가 0이 되지 않는 덕분에 신경망이 올바르게 학습할 수 있는 것이다.

     

     

     

     

    댓글

dokylee's Tech Blog