728x90
반응형

 이전 포스트에서 확률적 경사 하강법(SGD)에 대해 알아보았다. 해당 포스트에서 경사 하강법 함수 자체는 단순하므로, 이것만 구현하는 것은 쉬우나, 그 성능을 시각적으로 보기 위해선 학습에 대한 모든 알고리즘을 넣어야 하기 때문에 코드가 꽤 어려워지므로, 시간 낭비라고는 하였다.

 그러나, 이에 대하여 관심 있는 사람이 있을 수 있고, 눈으로 직접 코드가 돌아가는 과정을 본다면, 내용을 이해하기 더 쉬울 수 있으므로, 이를 다룬 책을 찾아 코드를 약간 수정하여, 이해하기 쉽도록 풀어보도록 하겠다. 딥러닝에서 사용되는 다층 퍼셉트론을 사용한 예시는 아니지만, 시각적으로 결과를 볼 수 있으므로 좋은 예시라고 생각한다.

 이번 포스트는 세바스찬 라시카, 바히드 미자리의 "머신러닝 교과서 with 파이썬, 사이킷런, 텐서플로"를 참고하여 작성하였다. 해당 책은 딥러닝을 구성하는 알고리즘에 대해 하나하나 다루고 있는 아주 좋은 책이므로, 꼭 읽어보기 바란다.

 

 

 

아달린 확률적 경사 하강법(AdalineSGD)

1. 아달린(ADAptive LInear NEuron, ADALINE)이란?

  • 스탠퍼드의 Bernard Widrow가 개발한 초기 신경망 모델 중 하나인 아달린은 적응형 선형 뉴런이라고 불리며, 연속 함수(Continous Function)로 손실 함수를 정의하고 최소화한다.
  • 아달린과 퍼셉트론의 차이는 가중치 업데이트를 위한 활성화 함수가 다르다.
    • A. 퍼셉트론: 실제값과 예측값의 활성 함수 출력 값이 다르면, 가중치 업데이트
    • B. 아달린: 실제값과 예측값이 다르면 경사 하강법으로 가중치 업데이트
  • Adaline은 퍼셉트론과 달리 선형 활성화 함수라는 것을 통해, 가중치를 업데이트하는 과정이 들어 있다. 활성화 함수는 초기 퍼셉트론과 마찬가지로 계단 함수를 사용한다.

선형 활성화 함수: $ \phi(w^Tx)=w^Tx $

  • 그러나, Adaline 역시 선형 분리가 가능한 논리 함수(AND, NAND, OR)는 실현할 수 있으나, 비선형 논리 함수(XOR)는 실현 불가능하다.
  • 다층 퍼셉트론처럼 다량의 Adaline으로 네트워크를 구성하는 Madaline을 사용하여 이를 해결하긴 하였으나, 계단 함수를 사용하기 때문에 미분이 불가능해 학습이 불가능하다는 단점이 있어, 다층 퍼셉트론(Multilayer Perceptron)에 밀려 요즘은 쓰지 않는다.
    (선형 분리 문제를 해결한 다층 퍼셉트론이 나오기 전엔 Madaline이 최고의 신경망 모델이었다고 한다.)

  • 아달린은 손실 함수로 앞서 학습하였던 제곱 오차합(SSE)을 사용한다.
    2021/01/29 - [Machine Learning/Basic] - 머신러닝-5.0. 손실함수(1)-제곱오차(SE)와 오차제곱합(SSE)
 

머신러닝-5.0. 손실함수(1)-제곱오차(SE)와 오차제곱합(SSE)

 이전 포스트에서 신경망 학습이 어떠한 원리에 의해 이루어지는지 간략하게 살펴보았다. 이번 포스트에서는 제곱 오차(Square Error)와 제곱 오차를 기반으로 만든 손실 함수 오차제곱합(SSE)에 대

gooopy.tistory.com

 

 

 

 

 

2. 구현해보자!

import numpy as np


class AdalineSGD(object):
    """ADAptive LInear NEuron 분류기
    
    매개변수
    -----------------------
    eta : float
    >>> 학습률 (0.0과 1.0 사이)
    n_iter : int
    >>> 훈련 데이터셋 반복 횟수
    shuffle : bool (default: True)
    >>> True로 설정하면 같은 반복이 되지 않도록 에포크마다 훈련 데이터를 섞는다.
    random_state : int
    >>> 가중치 무작위 초기화를 위한 난수 생성기 시드
    
    속성
    -----------------------
    w_ : 1d-array
    >>> 학습된 가중치
    cost_ : list
    >>> 모든 훈련 샘플에 대해 에포크마다 누적된 평균 비용 함수의 제곱합
    """
    def __init__(self, eta=0.01, n_iter=10, shuffle=True, random_state=None):
        self.eta = eta
        self.n_iter = n_iter
        self.w_initialized = False
        self.shuffle = shuffle
        self.random_state = random_state
        
    def fit(self, X, y):
        """훈련 데이터 학습
        
        매개변수
        -----------------------
        X : {array-like}, shape = [n_samples, n_features]
        >>> n_samples개의 샘플과 n_features개의 특성으로 이루어진 훈련 데이터
        y : array-like, shape = [n_samples]
        >>> 타깃 벡터
        
        
        반환값
        -----------------------
        self : object
        """
        self._initialize_weights(X.shape[1])
        self.cost_ = []
        for i in range(self.n_iter):
            if self.shuffle:
                X, y = self._shuffle(X, y)
            cost = []
            for xi, target in zip(X, y):
                cost.append(self._update_weights(xi, target))
            avg_cost = sum(cost) / len(y)
            self.cost_.append(avg_cost)
        return self
    
    def partial_fit(self, X, y):
        """가중치를 다시 초기화하지 않고 훈련 데이터를 학습"""
        if not self.w_initialized:
            self._initialize_weights(X.shape[1])
        if y.ravel().shape[0] > 1:
            for xi, target in zip(X, y):
                self._update_weights(xi, target)
        else:
            self._update_weights(X, y)
        return self
    
    def _shuffle(self, X, y):
        """훈련 데이터를 섞는다."""
        r = self.rgen.permutation(len(y))
        return X[r], y[r]
    
    def _initialize_weights(self, m):
        """랜덤한 작은 수로 가중치를 초기화합니다."""
        self.rgen = np.random.RandomState(self.random_state)
        self.w_ = self.rgen.normal(loc=0.0, scale=0.01, size=1+m)
        self.w_initialized = True
        
    def _update_weights(self, xi, target):
        """아달린 학습 규칙을 적용해 가중치 업데이트"""
        output = self.activation(self.net_input(xi))
        error = (target - output)
        self.w_[1:] += self.eta * xi.dot(error)
        self.w_[0] += self.eta * error
        cost = 0.5 * error**2
        return cost
    
    def net_input(self, X):
        """최종 입력 계산"""
        return np.dot(X, self.w_[1:]) + self.w_[0]
    
    def activation(self, X):
        """선형 활성화 계산"""
        return X
    
    def predict(self, X):
        """단위 계단 함수를 사용하여 클래스 레이블을 반환"""
        return np.where(self.activation(self.net_input(X)) >= 0.0, 1, -1)
  • 위 코드는 아달린으로 SGD를 구현한 것이다.
  • 아달린은 역전파를 통해 가중치 업데이트가 이루어지는 다층 퍼셉트론과 달리 층 자체에서 가중치를 업데이트하므로, 다층 퍼셉트론에 비해 개념이 단순하므로, 아달린을 사용했다.
# iris Data Import
from sklearn.datasets import load_iris
import pandas as pd

# Data Handling
X = pd.DataFrame(load_iris()["data"]).iloc[0:100, [0,2]].values
y = load_iris()["target"][0:100]
y = np.where(y==0, -1, 1)

# 변수 2개만 분석의 대상으로 사용할 것이므로, 이 2개만 표준화시키자.
X_std = np.copy(X)
X_std[:,0] = (X[:,0] - X[:,0].mean()) / X[:,0].std()
X_std[:,1] = (X[:,1] - X[:,1].mean()) / X[:,1].std()
  • 학습에 사용될 데이터 셋은 붓꽃에 대한 정보가 담긴 iris로 데이터 분석을 해본 사람이라면 꽤 친숙한 데이터일 것이다.
  • 해당 데이터에 대한 자세한 내용을 보고 싶다면, load_iris().keys()를 입력하여, dictionary에 있는 key들을 확인하고, 데이터를 살펴보도록 하자.
# 시각화 함수
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap

def plot_decision_regions(X, y, classifier, resolution=0.02):
    
    # 마커와 컬러맵 설정
    markers = ('s', 'x', 'o', '^', 'v')
    colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
    cmap = ListedColormap(colors[:len(np.unique(y))])
    
    # 결정 경계를 그린다.
    x1_min, x1_max = X[:,0].min() - 1, X[:,0].max() + 1
    x2_min, x2_max = X[:,1].min() - 1, X[:,1].max() + 1
    xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution),
                           np.arange(x2_min, x2_max, resolution))
    Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
    Z = Z.reshape(xx1.shape)
    plt.contourf(xx1, xx2, Z, alpha=0.3, cmap=cmap)
    plt.xlim(xx1.min(), xx1.max())
    plt.xlim(xx2.min(), xx2.max())
    
    # 샘플의 산점도를 그린다.
    for idx, cl in enumerate(np.unique(y)):
        plt.scatter(x=X[y == cl, 0],
                    y=X[y == cl, 1],
                    alpha = 0.8,
                    c=colors[idx], 
                    marker=markers[idx], 
                    label=cl,
                    edgecolor='black')
  • 위 학습 코드만으로는 그 결과를 인지하기 어려우므로, 그 과정을 시각화해주는 코드를 생성하였다.
ada = AdalineSGD(n_iter=15, eta=0.01, random_state=1)
ada.fit(X_std, y)

plot_decision_regions(X_std, y, classifier=ada)
plt.title('Adaline - Stochastic Gradient Descent')
plt.xlabel('sepal length [standardized]')
plt.ylabel('petal length [standardized]')
plt.legend(loc='upper left')
plt.show()
plt.plot(range(1, len(ada.cost_) + 1), ada.cost_, marker='o')
plt.xlabel('Epochs')
plt.ylabel('Average Cost')
plt.show()

  • 출력된 결과를 보면, 두 집단(-1, 1)을 선으로 잘 분리한 것을 볼 수 있다(아달린은 선형 분리에 특화되어 있다.)
  • Epoch별 평균 비용(미니 배치 손실 함수의 평균값)이 빠르게 최솟값에 수렴하는 것을 볼 수 있다.

 

 

 

 이번 포스트는 어떤 주제에 대해 설명하기보다는 소개를 목적으로 글을 적었다 보니, 내용상 부족함이 많다. 위 코드는 꽤나 복잡하고, 이해하기가 힘든데, 개인적으로는 굳이 이해하려고 노력하지 않기를 바란다.

 머신러닝에서 굉장히 많이 사용되는 프레임워크인 텐서플로우의 케라스를 사용하여 코드를 작성하면, 코드가 보다 직관적이고, 내가 원하는 형태로 수정하기도 쉽기 때문에 굳이 위 코드를 이해하려 시간을 낭비할 필요는 없다.

 다만, 인공지능 역사에서 아달린이 차지했던 비중이 꽤 되고, 확률적 경사 하강법을 가장 손쉽게 실제 학습에 적용하여, 그 효과를 볼 수 있는 예시로는 위 코드가 가장 좋다고 생각되어 소개해보았다. 

 다음 포스트에서는 이전에 말했던 모멘텀(Momentum)에 대해 다뤄보도록 하겠다.

 

 

[ 참고 자료 ]

www.aistudy.com/neural/model_kim.htm#_bookmark_1a77358

 

초기의 신경망 이론과 모델 : 김대수

< 퍼셉트론 학습 과정 > [단계 1] 연결강도들과 임계값을 초기화한다. wi(0)(0 ≤ i ≤ N - 1) 와 θ 를 임의수 (random number) 로 정한다.  여기에서 wi(t) 는 시각 t 일 때 입력 i 로부터의 연결강도를 나타

www.aistudy.com

blog.naver.com/samsjang/220959562205

 

[6편] 아달라인(Adaline)과 경사하강법(Gradient Descent)

​아달라인(Adaline) 단층 인공신경망인 퍼셉트론이 발표된지 몇 년 후인 1960년에 Bernard Widrow와 T...

blog.naver.com

 

728x90
반응형
728x90
반응형

 이전 포스트에서는 학습 단위에 대한 단어인 에포크(Epoch), 배치 크기(Batch size), 이터레이션(Iteration)에 대해 알아보았다. 이번 포스트에서 알아볼 확률적 경사 하강법(SGD)의 키는 배치 크기와 랜덤 추출이다.

 경사 하강법에 다른 식을 붙이지 않고 바로 사용하는 방법은 크게 두 가지인 배치 경사 하강법(BGD)과 확률적 경사 하강법(SGD)이 있는데, 이 둘은 손실 함수의 기울기 계산에 사용되는 데이터 셋의 규모만 제외하고 같다.

 중요한 것은 손실 함수의 경사를 구하는 대상이다!

 

 

1. 배치 경사 하강법(Batch Gradient Descent, BGD)

  • 배치 경사 하강법(BGD)은 경사 하강법의 손실 함수의 기울기 계산에 전체 학습 데이터셋의 크기와 동일하게 잡는 방법이다.
  • 즉, 경사 하강법 대상이 배치 크기와 동일하다는 것이다.
  • 데이터셋 모두를 대상으로 하다 보니 파라미터가 한번 이동할 때마다, 계산해야 할 값이 지나치게 많으므로, 계산 시간도 엄청 길어지고, 소모되는 메모리도 엄청나다.
  • mini batch 안 모든 데이터를 대상으로 경사 하강법을 실시하므로, 안정적으로 수렴한다.

  • 안정적으로 수렴하므로, 수렴까지 발생하는 총 파라미터 업데이트 수는 매우 적다.
  • 안정적으로 수렴하는 것은 좋으나, 안정적으로 움직이기 때문에 지역 최소해(Local Minimum)에 빠지더라도 안정적으로 움직이므로 빠져나오기 힘들다. 즉, Local Optima(minimum) 문제가 발생할 가능성이 높다.
  • 학습 데이터셋이 커지면 커질수록 시간과 리소스 소모가 지나치게 크다.

 

 

 

 

2. 확률적 경사 하강법(Stochastic Gradient Descent, SGD)

  • 전체 훈련 데이터셋을 대상으로 학습하는 것은 한정된 리소스를 가지고 있는 우리의 분석 환경에서 매우 비효율적이며, 파라미터 업데이트 수가 적다는 것은 랜덤 하게 뽑힌 시작 위치의 가중치 수도 적으므로, Local minimum 현상이 발생할 확률도 높다는 것이다.
  • 그래서 나온 방법이 학습 데이터셋에서 무작위로 한 개의 샘플 데이터 셋을 추출하고, 그 샘플에 대해서만 기울기를 계산하는 것이다.
  • 샘플 데이터 셋에 대해서만 경사(Gradient)를 계산하므로, 매 반복에서 다뤄야 할 데이터 수가 매우 적어, 학습 속도가 매우 빠르다.
  • 하나의 샘플만 대상으로 경사를 계산하므로, 메모리 소모량이 매우 낮으며, 매우 큰 훈련 데이터 셋이라 할지라도 학습 가능하다.
  • 그러나, 무작위로 추출된 샘플에 대해서 경사를 구하므로, 배치 경사 하강법보다 훨씬 불안정하게 움직인다.

  • 손실 함수가 최솟값에 다다를 때까지 위아래로 요동치며 움직이다 보니, 학습이 진행되다 보면, 최적해에 매우 근접하게 움직이긴 하겠으나, 최적해(Global minimum)에 정확히 도달하지 못할 가능성이 있다.
  • 그러나, 이렇게 요동치며 움직이므로, 지역 최솟값(Local minimum)에 빠진 다할지라도, 지역 최솟값에서 쉽게 빠져나올 수 있으며, 그로 인해 전역 최솟값(Global minimum)을 찾을 가능성이 BGD에 비해 더 높다.
  • 즉, 확률적 경사 하강법(SGD)은 속도가 매우 빠르고 메모리를 적게 먹는다는 장점이 있으나, 경사를 구할 때, 무작위성을 띄므로 지역 최솟값에서 탈출하기 쉬우나, 전역 최솟값에 다다르기 힘들다는 단점을 가지고 있다.
  • 이 문제를 해결하기 미니 배치 경사 하강법(mini-Batch gradient descent)이 등장하였다.

 

학습률 스케줄(Learning rate schedule)

  • 전역 최솟값에 도달하기 어렵다는 문제를 해결하기 위한 방법으로, 학습률을 천천히 줄여 전역 최솟값에 다다르게 하는 방법이 있다.
  • 학습률은 작아질수록 이동하는 양이 줄어들기 때문에 전역 최솟값에 안정적으로 수렴할 수 있다.
  • 만약 학습률이 너무 급격하게 감소하면, Local Optima 문제나 Plateau 현상이 발생할 가능성이 높아진다.
  • 그렇다고 학습률을 너무 천천히 줄이면 최적해 주변을 맴돌 수 있다.

 

 

 

 

3. 미니 배치 경사 하강법(mini-Batch gradient descent)

  • 앞서 이야기한 배치 경사 하강법(BGD)나 확률적 경사 하강법(SGD)은 모두 배치 크기가 학습 데이터 셋 크기와 동일하였으나, 미니 배치 경사 하강법은 배치 크기를 줄이고, 확률적 경사 하강법을 사용하는 기법이다.
  • 예를 들어, 학습 데이터가 1000개고, batch size를 100으로 잡았다고 할 때, 총 10개의 mini batch가 나오게 된다. 이 mini batch 하나당 한 번씩 SGD를 진행하므로, 1 epoch당 총 10번의 SGD를 진행한다고 할 수 있다.
  • 일반적으로 우리가 부르는 확률적 경사 하강법(SGD)은 실제론 미니 배치 경사 하강법(mini-BGD)이므로, 지금까지 학습했던 차이들은 기억하되, 앞으로 SGD를 말하면, 미니 배치 경사 하강법을 떠올리면 된다.

  • 미니 배치 경사 하강법은 앞서 이야기했던, 전체 데이터셋을 대상으로 한 SGD보다 파라미터 공간에서 Shooting이 줄어들게 되는데, 이는 한 미니 배치의 손실 값 평균에 대해 경사 하강을 진행하기 때문이다.
  • 그로 인해, 최적해에 더 가까이 도달할 수 있으나, Local optima 현상이 발생할 수 있다. 그러나, 앞서 말했듯 Local optima 문제는 무수히 많은 임의의 파라미터로부터 시작되면, 해결되는 문제이며, 학습 속도가 빠른 SGD의 장점을 사용하여, 학습량을 늘리면 해결되는 문제다.
  • 배치 크기는 총 학습 데이터셋의 크기를 배치 크기로 나눴을 때, 딱 떨어지는 크기로 하는 것이 좋다.
  • 만약, 1050개의 데이터에 대하여 100개로 배치 크기를 나누면, 마지막 50개 데이터셋에 대해 과도한 평가를 할 수 있기 때문이다.
  • 그러나, 만약 배치 크기로 나누기 애매한 경우라면, 예를 들어 총 학습 데이터 셋이 1,000,050개가 있고, 배치 크기를 1,000개로 나누고 싶은 경우라면, 나머지인 50개는 버리도록 하자(물론 완전 무작위 하게 50개를 선택해서 버려야 한다.).

 

 

 

  지금까지 확률적 경사 하강법(SGD)에 대해 알아보았다. 본래의 SGD는 "배치 크기 = 학습 데이터 셋 크기"이지만, 일반적으로 통용되는 SGD는 "배치 크기 < 학습 데이터 셋 크기"인 미니 배치를 만들어 학습시키는 미니 배치 경사 하강법이다. 

 경사 하강법의 파이썬 코드화는 경사 하강법 함수 자체는 단순하지만, 학습에서 발생하는 모든 알고리즘이 복합적으로 작동하므로, 코드화시키는 것은 시간 낭비로 판단된다. Optimizer 파트부턴 그 개념과 특징을 이해하고, 텐서플로우로 학습을 해보도록 하자.

 다음 포스트에서는 경사 하강법의 한계점을 보완하기 위한 시도 중 하나인 모멘텀(Momentum)에 대해 학습해보도록 하겠다.

728x90
반응형
728x90
반응형

 이전 포스트에서는 경사 하강법의 한계점에 대해 학습해보았다. 이번 포스트에서는 경사 하강법의 실행 과정을 살펴보고, 기본 사용 방법인 배치 경사 하강법(Batch Gradient Descent)이 어떤 단점을 가지고 있기에 최적화 기법의 기반이 되는 경사 하강법인 확률적 경사 하강법(Stochastic Gradient Descent, SGD)이 나오게 되었는지를 알아보고자 하였으나, 이 과정을 쉽게 이해하려면 먼저 학습이 일어나는 구조와 학습 단위에 대한 개념을 알아야 한다.

 

 

1. 학습의 구조

  • 학습은 기본적으로 다음과 같은 구조로 움직인다.
  1. 임의의 매개변수(가중치)를 정한다.
  2. 선택된 매개변수로 손실 값을 구하고, 손실 함수의 기울기(Gradient)를 계산한다.
  3. 계산된 기울기와 학습률(Learning Rate)을 이용해 다음 가중치의 위치로 이동하여, 파라미터를 업데이트한다.
    이때, 이동 거리는 경사 하강법 공식을 통해 구해진다.
    $$ \theta_{n+1} = \theta_n - \eta \bigtriangledown f(\theta_n) $$
  4. 이동된 지점에서 손실 함수의 기울기(Gradient)를 계산하고, 3.과정을 다시 실시한다.
  5. 손실함수의 기울기가 최솟값에 도달하면, 파라미터 업데이트를 멈춘다.

 

 

 

 

2. 학습 단위

  • 그런데, 위 과정을 보다 보면 한 가지 의문이 든다.
  • 바로, 기울기 계산이 엄청 많이 일어난다는 것인데, 우리가 기계를 학습시킬 때 사용하는 빅 데이터는 일반적으로 최소 1,000만 건 이상을 가리키며, 1억, 10억 건 이상 데이터도 심심치 않게 등장한다는 것이다.
  • 이렇게 많은 데이터를 한 번에 모델에 태우게 된다면, 아무리 좋은 컴퓨터라도 버티지 못할 것이다.
  • 한 번의 학습에 모든 학습 데이터셋을 사용한다면, 여러 문제를 일으킨다.
  1. 데이터의 크기가 너무 큰 경우, 메모리가 너무 많이 필요해진다.
  2. 학습 한 번에 계산돼야 할 파라미터(가중치) 수가 지나치게 많아지므로 계산 시간이 너무 오래 걸린다.
  • 여기서 Epoch, Batch size, iteration라는 개념이 등장하게 된다.

 

 

 

 

3. Epoch(에포크)

  • Epoch의 네이버 영어 사전 뜻은, "(중요한 사건·변화들이 일어난) 시대"라는 뜻이다.
  • 훈련 데이터셋에 포함된 모든 데이터들이 한 번씩 모델을 통과한 횟수로, 모든 학습 데이터셋을 학습하는 횟수를 의미한다.
  • 1 epoch는 전체 학습 데이터셋이 한 신경망에 적용되어 순전파와 역전파를 통해 신경망을 한 번 통과했다는 의미가 된다.
  • 즉 epoch가 10회라면, 학습 데이터 셋 A를 10회 모델에 학습시켰다는 것이다.
  • epoch를 높일수록, 다양한 무작위 가중치로 학습을 해보므로, 적합한 파라미터를 찾을 확률이 올라간다.
    (즉, 손실 값이 내려가게 된다.)
  • 그러나, 지나치게 epoch를 높이게 되면, 그 학습 데이터셋에 과적합(Overfitting)되어 다른 데이터에 대해선 제대로 된 예측을 하지 못할 수 있다.

 

 

 

 

4. Batch size(배치 사이즈)

  • Batch의 네이버 영어 사전 뜻은 "(일괄적으로 처리되는) 집단", "한 회분(한 번에 만들어 내는 음식 기계 등의 양)", "(일괄 처리를 위해) 함께 묶다"라는 의미가 있다.
  • 즉, 연산 한 번에 들어가는 데이터의 크기를 가리킨다.
  • 1 Batch size에 해당하는 데이터 셋을 mini Batch라고 한다.
  • 1회 epoch 안에 m 개($m \geq 1$)의 mini Batch가 들어가게 되며, 만약, m = 1인 경우, 배치 학습법이라고 한다.
  • 배치 사이즈가 너무 큰 경우 한 번에 처리해야 할 데이터의 양이 많아지므로, 학습 속도가 느려지고, 메모리 부족 문제가 발생할 위험이 있다.
  • 반대로, 배치 사이즈가 너무 작은 경우 적은 데이터를 대상으로 가중치를 업데이트하고, 이 업데이트가 자주 발생하므로, 훈련이 불안정해진다.

 

 

 

 

5. Iteration(이터레이션)

  • Iteration은 네이버 영어사전에서 "(계산·컴퓨터 처리 절차의) 반복"이라는 뜻이다.
  • 전체 데이터를 모델에 한번 학습시키는데 필요한 배치의 수를 말한다.
  • 즉, 1 epoch를 마치는데 필요한 파라미터 업데이트 횟수라 할 수 있다.
  • 각 배치마다 파라미터 업데이트가 한 번씩 이루어지므로, Iteration은 "파라미터 업데이트 횟수 = 배치의 수"가 된다.

 

 

 

 

※ 참고

 만약, 데이터셋이 너무 거대해서 전체를 메모리에 올리는 것만으로도 부하가 걸릴 정도라면, 배치 학습 방법을 하되, 한 번에 학습할 학습 데이터 셋의 크기를 줄이고, for문으로 실제 batch를 만들고, pickle로 파일로 만들어 놓은 데이터 셋을 일부씩 불러와 batch에 학습시키고, 모든 데이터 셋을 불러와 한번 학습하는 것을 epoch로 잡는 방식도 있다.

 위 글만으로는 이해가 가지 않을 수 있으므로, 나중에 기회가 된다면 이를 자세히 다뤄보도록 하겠다.

 

 

 

 

 이번 포스트에서는 학습 단위로 사용되는 단어인 Epoch, Batch size, mini batch, Iteration에 대해 알아보았다. 다음 포스트에서는 배치 경사 하강법(BGD)과 확률적 경사 하강법(SGD)에 대해 학습해보도록 하겠다.

 
728x90
반응형
728x90
반응형

 지난 포스트에서는 기계학습에서 사용되는 최적화 알고리즘인 경사 하강법에 대하여 살펴보았다. 이번 포스트에서는 경사 하강법의 한계점에 대해 학습해보도록 하겠다.

 

 

경사 하강법의 한계점

  • 앞서 손실함수를 기반으로 경사 하강법의 개형을 그려보았으나, 실제로는 저렇게 깔끔한 이차 함수 형태를 그리지 않는다.
  • 퍼셉트론의 공식이 활성화 함수를 타게 되면, 손실 함수의 모습은 거시적인 관점에서 봤을 때는 최적해를 1개 가진 이차 함수의 형태를 그리긴 하지만, 그 모습이 울퉁불퉁해져 최적해에 수렴하기 어려워진다.
  • 이번 포스트에서는 경사하강법의 한계점에 대해 하나하나 짚고 넘어가 보도록 하겠다.

 

 

 

 

1. 데이터가 많아질수록 계산량 증가

  • 앞서, 경사하강법(Gradient Descent)은 신경망에서 출력되는 예측값(Predict)과 실제값(Label)의 차이인 손실 함수(Loss Function)의 값을 최소화하는 것이 목적이다.
  • 그러나, 학습용 데이터 셋이 많아진다면, 당연히 계산량도 무지막지하게 많아지게 되는데, 그로 인해 학습 속도가 매우 느려지게 된다.
  • 기계학습에는 아주 거대한 빅데이터가 사용되게 되는데, 이러한 퍼포먼스 문제는 결코 무시할 수 없는 문제다.

 

 

 

 

2. Local minimum(Optima) 문제

  • 앞서 그린 대략적인 손실함수의 개형은 굉장히 매끈하였으나, 활성화 함수로 인해 그 모양이 울퉁불퉁해지게 되고, 그로 인해 최적해에 수렴하지 못할 수 있다.
  • 아래 그래프를 보도록 하자.

  • 실제 손실함수의 모양은 위 그래프보다 울퉁불퉁한 정도가 심하나 이해를 돕기 위해 일부분만 가져와봤다.
  • 위 그래프에서 $\alpha$를 전역 최소해(Global minimum), $\beta$를 지역 최소해(Local minimum)라 한다.

 

  • 경사 하강법의 목적은 손실 함수에서 랜덤 하게 선택한 가중치를 미분하여 나온 결과를 힌트로 해서, 최적해를 찾아가는 것인데, 위 그래프처럼 만약 랜덤 하게 선택된 가중치가 Local minimum 가까이에 있고, Local minimum에 수렴해버리면, 실제 목표인 Global  minimum을 찾지 못하는 문제가 발생할 수 있다.

 

  • 만약, 학습률(Learning Rate)을 너무 크게 설정한다면, Global minimum에 가까운 곳에서 시작한다 할지라도, 구간을 뛰어넘어 Local minimum에서 수렴할 수도 있다.
  • 그러나, 실제로는 모델의 학습이 지역 최소값(Local minimum)에 빠져, 최적의 가중치를 못 찾는 일이 발생할 위험은 그리 크지 않다.

  • 학습 시 가중치를 초기화하여 반복하여 최적해를 찾아가므로, $\beta$에서 수렴하여 Loss값이 0 가까이 떨어지지 못한다할지라도, 시작 위치가 다른 가중치에서 전역 최소값(Global minimum)에 수렴하여 Loss값이 0에 수렴할 수 있다.
  • 즉, 모든 초기화된 가중치가 지역 최솟값에 수렴할 수 있는 위치에 존재하지 않는다면, 지역 최솟값 문제는 발생하지 않는다. 그러므로, Local minimum 현상의 발생 위험은 그리 크지 않다고 할 수 있다.

 

 

 

 

3. Plateau 문제

  • 1. Local minimum 문제의 예시에서는 손실함수의 모양이 전반적으로 곡선을 그렸으나, 손실 함수의 안에는 평탄한 영역이 존재하기도 한다.

  • 위 그래프에서 Plateau(플래튜)라고 불리는 평탄한 영역에서는 학습 속도가 매우 느려지며, 느려지다 못해 정지해버릴 위험이 존재한다.
  • 경사 하강법의 공식을 보면, "현 지점의 기울기 X 학습률"을 통해 다음 가중치를 결정하는데, 평탄한 영역의 기울기는 매우 낮기 때문에 이동거리가 갈수록 줄어들게 되고, 그로 인해 더 이상 학습이 일어나지 않는 가중치 소실(Gradient Vanishing) 현상이 발생할 수 있다.
  • 이러한 Plateau 현상이 발생하면, 극솟값에 수렴하지 못해, 학습 시간이 매우 길어지고, 경사하강법의 랜덤 한 가중치에서 현재의 기울기를 힌트로 기울기가 0인 극솟값에 수렴시켜 최적해를 찾는다는 알고리즘이 제대로 작동하지 못하게 된다.

 

 

 

 

4. Zigzag 문제

  • 지금까지 경사하강법을 설명할 때, 이해하기 용이하도록 가중치($w$)가 1개만 있는 2차원 그래프를 사용했으나, 실제론 가중치의 수가 매우 많다. 이번엔 가중치가 2개인($w_1, w_2$) 3차원 그래프를 등고선으로 그려보자.

  • 위 그래프는 2개의 매개변수($w_1, w_2$)에 대한 손실 함수를 등고선으로 그린 것이다.
  • 가중치의 스케일(크기)이 동일하다면, 최적해로 바로 찾아갈 수 있으나, 가중치는 모르는 임의의 값이므로, 스케일이 동일하리란 보장이 없다.
  • 만약, 가중치 스케일이 다르다면, 다음과 같은 현상이 발생하게 된다.

  • 두 매개변수 $w_1$의 스케일이 $w_2$보다 크다보니, 손실 함수는 $x$축 방향 가중치인 $w_1$의 변화에 매우 둔감하고, $y$축인 $w_2$의 변화에 매우 민감하다.
  • 즉, $w_2$의 크기가 $w_2$에 비해 매우 작다보니, $w_2$가 조금만 변해도 손실 함수는 크게 변하게 되어, 두 매개변수의 변화에 따른 손실 함수 변화가 일정하지 않다.
  • 위 경우는 매개변수가 2개밖에 존재하지 않았으나, 실제에서는 그 수가 수백만개에 달할 수 있을 정도로 많기 때문에 이러한 Zigzag 현상은 더욱 복잡해지며, 그로 인해 최적해를 찾아가기가 어려워지고, 학습 시간 역시 길어지게 된다.

 

 

 

 

 지금까지 경사하강법의 문제점에 대해 알아보았다. 머신러닝에서는 위 문제들을 해결하기 위해 경사 하강법을 효율적으로 사용하기 위한 최적화 기법(Optimizer)들이 매우 많다.

 예를 들어 다음 포스트에서 학습할 SGD나 가장 많이 사용되는 Adam, Momentum, Adagrad 등이 있는데, 각 최적화 알고리즘들은 데이터의 형태에 따라 그에 맞는 방법을 사용하길 바란다.

 다음 포스트에서는 최적화 기법의 가장 기초가 되는 확률적 경사 하강법(Stochastic Gradient Descent, SGD)에 대해 학습해보도록 하겠다.

 

 

[참조]

towardsdatascience.com/demystifying-optimizations-for-machine-learning-c6c6405d3eea

 

Demystifying Optimizations for machine learning

Optimization is the most essential ingredient in the recipe of machine learning algorithms. It starts with defining some kind of loss…

towardsdatascience.com

www.programmersought.com/article/59882346228/

 

[2017CS231n] SEVEN: train the neural network (under) - Programmer Sought

First look at the sixth lecture: Data preprocessing. When we have the data normalized red, classifier weight matrix perturbation is not particularly sensitive, more robust. The left classifier little changes that will undermine the classification results.

www.programmersought.com

nittaku.tistory.com/271?category=742607

 

11. Optimization - local optima / plateau / zigzag현상의 등장

지난시간까지는 weight 초기화하는 방법에 대해 배웠다. activation func에 따라 다른 weight초기화 방법을 썼었다. 그렇게 하면 Layer를 더 쌓더라도 activation value(output)의 평균과 표준편차가 일정하게 유

nittaku.tistory.com

 

728x90
반응형
728x90
반응형

 지금까지 가중치를 평가하는 방법인 손실함수(Loss Function)에 대해 학습해보았다. 그렇다면, 어떻게 손실함수를 기반으로 최적의 가중치를 찾아낼까?

 이번 포스트에서는 손실함수로부터 어떻게 경사 하강법이 나오게 되었는지를 이야기해보고, 경사하강법을 위주로 설명해보도록 하겠다.

 

 

손실함수와 경사하강법

1. 최적화(Optimizer)

  • 최적화는 손실함수(Loss Function)의 결과값을 최소화하는 가중치를 찾아내는 것이 목적이다.
  • 그렇다면, 손실함수의 개형은 어떻게 생겼을까?
  • 예상한 값과 실제값의 차이인 제곱 오차(SE)를 가지고, 손실함수의 개형을 보도록 하자.

$$ SE = (y - \hat{y})^2 $$

  • 이해하기 쉽도록 예측값을 변수(가중치)가 1개만 있는 퍼셉트론을 가져와보자
  • 예측값은 선형이다(변수는 가중치인 $w$이며, $x$와 상관 없이, 가산성과 동차성이 성립한다.)

$$ \hat{y} = wx + b $$

  • 제곱 오차의 $\hat{y}$에 예측값을 대입하고 식을 풀어보자.

$$ (y - \hat{y})^2 = (y - (wx + b))^2 = y^2 -2y(wx + b) + (wx + b)^2 = w^2x^2 + 2wxb + b^2 - 2wxy - 2yb + y^2 $$

  • 위 식에서 변수는 $w$이므로, 위 함수는 1개의 최적해를 갖는 이차 함수 형태인 것을 알 수 있다.
  • 그러므로, 손실함수의 개형은 다음과 같다.

 

 

 

 

2. 경사하강법(Gradient Descent)

  • 경사 하강법은 1차 미분계수를 이용해 함수의 최소값을 찾아가는 방법으로, 함수 값이 낮아지는 방향으로 독립 변수 값을 변형시켜가면서 최종적으로 최소 함수 값을 갖도록 하는 독립 변수 값을 찾는 방법이다.
  • 위에서 보듯, 손실함수의 개형은 1개의 최적해를 갖는 2차 함수의 형태이므로, 경사하강법을 사용하여, 최소 함수 값을 갖도록 하는 최적해(가중치)를 탐색해야한다.
  • 경사 하강법은 임의의 가중치를 설정하여, 그 점에서의 기울기를 계산하고, 그 기울기를 힌트로 기울기가 0인 지점을 찾아간다.
  • 손실 함수의 부호를 반전시켜, 최댓값을 찾는다면 경사 상승법(Gradient Ascent)이 되나, 동일한 것이므로, 굳이 사용하지 않는다.

  • 위 그럼처럼 기울기는 손실함수에서 임의의 가중치에서 시작하며, 기울기가 음수인 경우에는 양의 방향으로 이동하고, 기울기가 양수인 경우에는 음의 방향으로 이동하여, 극솟값을 찾아간다.
  • 여기서 움직이는 기울기(경사)는 가중치에 대하여 편미분 한 벡터이고, 이 가중치를 조금씩 움직인다.

 

 

 

 

3. 경사하강법 공식

  • 경사 하강법을 공식으로 써보면 다음과 같다.

$$ x_{i+1} = x_i - \eta\bigtriangledown f(x_i) $$

  • 여기서 $\eta$(eta, 에타)는 학습률(Learning Rate)이라 하며, 한 번의 학습에서 얼마나 이동할지를 정한다.
  • $\bigtriangledown$는 벡터 미분 연산자로 델(del) 연산자라 하며 이 기호를 나블라(nabla) 기호라고 한다.
  • 스칼라 함수 $f(x)$의 기울기는 $\bigtriangledown f$로 표현한다.
  • 기울기는 $f$의 각 성분의 편미분으로 구성된 열 벡터로 정의하고, 아래와 같이 표시한다.

$$ \bigtriangledown f = (\frac{\partial f}{\partial x_1}, ..., \frac{\partial f}{\partial x_n}) $$

$$ \bigtriangledown f = (\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}) $$

  • 예를 들어 함수 $f(x, y, z) = 2x + 3y^2 - sin(z)$의 기울기는 다음과 같다.

$$ \bigtriangledown f = (\frac{\partial f}{\partial x} + \frac{\partial f}{\partial y} + \frac{\partial f}{\partial z}) =(2, 6y, -coas(z)) $$

 

기울기 (벡터) - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 둘러보기로 가기 검색하러 가기 물매는 여기로 연결됩니다. 무기에 대해서는 무릿매 문서를 참조하십시오. 위의 두 그림에서는 회색의 밝기가 스칼라계의 크

ko.wikipedia.org

  • 즉, 경사하강법 공식은 현재의 위치 $x_i$에 학습률 $\eta$에 그 위치에서의 기울기 $\bigtriangledown f(x_i)$만큼을 곱한 값을 뺀만큼 위치를 이동시켜 다음 위치 $x_{i+1}$로 이동한다는 소리다.
  • 여기서 학습률과 기울기 곱($\eta\bigtriangledown f(x_i)$)을 빼는 이유는 현재의 기울기의 반대방향으로 이동하여, 극소값에 도달하기 위해서이다.

 

 

 

 

4. 학습률(Learning Rate, LR)

4.1. 경사 하강법 공식에서의 학습률의 영향

  • 위 경사 하강법의 공식에서 중요한 것은 학습률(Laerning Rate)인데, 이 학습률에 따라 경사 하강법 시, 이동하는 수준이 달라지게 된다.
  • 예를 들어, 기울기가 2.5이고 학습률이 0.01이라면, 경사하강법 알고리즘은 이전 지점으로부터 0.025 떨어진 지점을 다음 지점으로 결정한다.
  • 즉, "이동 거리 = 학습률 X 기울기"로 움직인다. 이는 기울기가 낮다면 학습률이 높다할지라도 움직이는 거리가 줄어든다는 소리이고, 큰 고랑에 빠진다면, 거기서 나오지 못하고 수렴할 수 있다는 소리다.

 

4.2. 학습률이 낮은 경우

  • 학습률이 낮다면, 이동하는 거리가 짧으며, 경사하강법 공식에 의해 이동할수록 기울기가 더욱 감소하므로, 짧은 이동 거리가 더 짧아진다.
  • 그로 인해, 경사 하강법 알고리즘이 수렴하기 위해 반복해야하는 데이터 양이 많아지므로, 학습 시간이 늘어나게 된다.

 

4.3. 학습률이 높은 경우

  • 학습률이 지나치게 큰 경우, 크게 이동하므로, 수렴이 빨리 발생해 학습 시간이 적게 걸린다.
  • 그러나, 너무 크게 이동하므로, 전역 최솟값(Global minimum)이 있는 영역을 건너 뛰어 지역 최솟값에서 수렴할 수도 있다.
    (이 부분은 다음 포스트에서 세세하게 다루도록 하겠다.)

 

 

 

 

 지금까지 손실함수를 최소화하는 방법으로 어째서 경사하강법을 사용하는지와 경사하강법이 어떠한 구조로 움직이는지에 대하여 학습해보았다.

 다음 포스트에서는 경사하강법의 한계점에 대해 학습해보도록 하겠다.

728x90
반응형
728x90
반응형

 이전 포스트에서는 이진 분류에서 주로 사용되는 이진 교차 엔트로피 오차(Binary Cross Entropy Error, BCEE)에 대해 학습해보았다.

 이번 포스트에서는 다중 분류에서 사용되는 범주형 교차 엔트로피 오차(Categorical Cross Entropy error)에 대해 학습해보겠다.

 

 

 

범주형 교차 엔트로피 오차(Categorical Cross Entropy Error, CCEE)

  • 범주형 교차 엔트로피 오차는 클래스가 3개 이상인 데이터를 대상으로 사용하는 손실함수다.
  • CCEE는 주로, 소프트맥스(Softmax) 함수를 활성화 함수로 하여 사용된다.
  • 출력층의 노드 수는 클래스의 수와 동일하다.
  • 실제 데이터인 라벨은 원-핫 벡터로 구성되어 있다.
  • 출력된 벡터는 각 클래스에 속할 확률이 나오며, 총합은 1이다.
  • 처음 학습하였던 교차 엔트로피 오차를 N개의 데이터 셋에 대해 1개의 스칼라를 추출하는 방법이 CCEE다.

 

 

 

 

1.  범주형 교차 엔트로피 오차의 공식

  • 범주형 교차 엔트로피 오차 공식은 다음과 같다.

$$ Loss = -\frac{1}{N}\sum_{j=1}^{N}\sum_{i=1}^{C}t_{ij}log(y_{ij}) $$

  • 위 공식은 지금까지 잘 따라왔다면, 따로 풀이가 필요 없을 정도로 단순한 공식이다.
  • 앞서 학습하였던 교차 엔트로피 오차 공식을 데이터셋의 수 $N$개만큼 합하여 평균을 낸 것이다.
  • 이진형 교차 엔트로피 오차와의 차이는 출력층의 노드 수가 1개인지 $m$개$(m\geq3)$인지로, 출력층에서 데이터 하나당 클래스 수만큼의 원소를 가진 벡터가 나오므로, 각 벡터의 교차 엔트로피 오차들의 평균을 구하는 것이다.
  • 바로 구현으로 넘어가 보자.

 

 

 

 

2. 구현해보자.

>>> import numpy as np

>>> def CCEE(predict, label):
    
>>>     delta = 1e-7
>>>     log_pred = np.log(predict + delta)
    
>>>     return -(np.sum(np.sum(label * log_pred, axis = 1)))/label.shape[0]
  • np.sum() 함수를 보면 axis = 1이라는 것이 있다. 이는 0으로 설정하면, 열을 기준으로 해당 함수를 실행하고, 1으로 설정하면, 행을 기준으로 함수를 실행한다.
  •  이 부분은 헷갈리기 좋으므로, 익숙해지기 전이라면, 작게 데이터를 만들어서 한번 보고 실행해보는 것을 추천한다.
>>> predict = np.array([[0.1, 0.7, 0.05, 0.05, 0.1],
>>>                     [0.05, 0.0, 0.85, 0.1, 0.0],
>>>                     [0.05, 0.8, 0.05, 0.1, 0.1],
>>>                     [0.75, 0.15, 0.05, 0.05, 0.0],
>>>                     [0.0, 0.1, 0.1, 0.0, 0.8]])
                    
>>> label = np.array([[0, 1, 0, 0, 0],
>>>                   [0, 0, 1, 0, 0],
>>>                   [0, 1, 0, 0, 0],
>>>                   [1, 0, 0, 0, 0],
>>>                   [0, 0, 0, 0, 1]])
>>> CCEE(predict, label)
0.25063248093584295
  • 범주형 교차 엔트로피 오차의 구현은 아주 단순하다. 
  • 위에서 보듯, 교차 엔트로피는 각 벡터에 대해 일어나고, 교차 엔트로피 오차의 평균을 만들면 된다.
  • 실제 데이터와 예측 데이터를 아주 가깝게 해 보자.
>>> predict = np.array([[0.1, 0.85, 0.0, 0.05, 0.0],
>>>                     [0.05, 0.0, 0.9, 0.05, 0.0],
>>>                     [0.0, 0.95, 0.0, 0.1, 0.04],
>>>                     [0.9, 0.0, 0.05, 0.05, 0.0],
>>>                     [0.0, 0.1, 0.0, 0.0, 0.9]])

>>> label = np.array([[0, 1, 0, 0, 0],
>>>                   [0, 0, 1, 0, 0],
>>>                   [0, 1, 0, 0, 0],
>>>                   [1, 0, 0, 0, 0],
>>>                   [0, 0, 0, 0, 1]])

>>> CCEE(predict, label)
0.10597864292305711
  • 범주형 교차 엔트로피 오차 역시 편차가 줄어들수록 출력 값이 0에 가까워지는 것을 볼 수 있다.
  • 반대로 실제 데이터와 예측 데이터의 차이를 크게 만들어보자.
>>> predict = np.array([[0.1, 0.6, 0.2, 0.05, 0.05],
>>>                     [0.1, 0.2, 0.5, 0.2, 0.0],
>>>                     [0.1, 0.6, 0.0, 0.1, 0.2],
>>>                     [0.4, 0.0, 0.1, 0.3, 0.2],
>>>                     [0.05, 0.1, 0.05, 0.2, 0.6]])

>>> label = np.array([[0, 1, 0, 0, 0],
>>>                   [0, 0, 1, 0, 0],
>>>                   [0, 1, 0, 0, 0],
>>>                   [1, 0, 0, 0, 0],
>>>                   [0, 0, 0, 0, 1]])

>>> CCEE(predict, label)
0.6283827667464331
  • 앞서 교차 엔트로피 오차에서도 이야기하였지만, 원-핫 벡터에서 1에 해당하는 위치의 데이터만 가지고 연산을 한다.
  • 각 행의 총합은 1이다.

 

 

 

 

 지금까지 가장 기본이 되는 손실함수인 제곱오차(SE)에서 파생된 손실함수인 오차제곱합(SSE), 평균제곱오차(MSE), 평균제곱근오차(RMSE), 교차 엔트로피 오차에서 파생된 이진 교차 엔트로피 오차(BCEE), 범주형 교차 엔트로피 오차(CCEE)에 대하여 학습해보았다.

 이 밖에도 Huber나 Sparse Categorical Crossentropy 등이 여러 손실함수가 있으나, 이들까지 하나하나 다루다간 끝이 나지 않을지도 모른다. 이밖에 다른 손실함수에 대해 학습해보고자 한다면, TensorFlow의 keras에서 손실함수 API를 정리해놓은 아래 홈페이지를 참고하기를 바란다.

www.tensorflow.org/api_docs/python/tf/keras/losses

 

Module: tf.keras.losses  |  TensorFlow Core v2.4.1

Built-in loss functions.

www.tensorflow.org

 다음 포스트에서는 신경망의 핵심 알고리즘인 경사법에 대해 학습해보도록 하자.

728x90
반응형
728x90
반응형

 이전 포스트에서는 범주형 데이터를 분류하는데 주로 사용되는 손실함수인 교차 엔트로피 오차와 그 근간이 되는 정보 이론에서의 엔트로피가 무엇인지를 알아보았다.

 이번 포스트에서는 교차 엔트로피 오차 중에서도 이진 분류를 할 때, 주로 사용되는 이진 교차 엔트로피 오차에 대해 학습해보도록 하겠다.

 

 

이진 교차 엔트로피 오차(Binary Cross Entropy Error)

  • 교차 엔트로피 오차는 나누고자 하는 분류가 몇 개인지에 따라 사용하는 손실함수가 바뀌게 된다.
  • 이는 사용되는 활성화 함수가 다르기 때문으로, 범주가 2개인 데이터는 시그모이드(Sigmoid) 함수를 사용하여 0~1 사이의 값을 반환하거나, 하이퍼볼릭 탄젠트(Hyperbolic Tangent) 함수를 사용하여 -1~1 사이의 값을 반환한다. 이 두 활성화 함수 모두 출력값이 단 하나의 스칼라 값이다.
  • 반면에 범주가 3개 이상이라면, 총 합 1에 각 클래스에 속할 확률을 클래스의 수만큼 반환하는 소프트맥스(Softmax) 함수를 사용하여 클래스 수만큼의 원소가 들어있는 배열을 반환하므로, 이에 대한 평가 방법이 달라져야 한다.
  • 이진 교차 엔트로피 오차는 로그 손실(Log loss) 또는 로지스틱 손실(Logistic loss)라 불리며, 주로 로지스틱 회귀의 비용 함수로 사용된다.

 

 

 

 

1. 이진 교차 엔트로피 오차의 공식

  • 이진 교차 엔트로피 오차의 공식은 다음과 같다.

$$ Loss = -\frac{1}{N}\sum_{i=1}^{N}(y_i*ln\hat{y_i} + (1-y_i)*ln(1-\hat{y_i})) $$

  • $\hat{y_i}$는 예측값이며, $y_i$는 실제값이다.
  • 얼핏 보면, 꽤 어려워보이는데 앞서 우리가 학습했던 내용을 기반으로 보면 상당히 단순한 공식이다.
  • 먼저 앞서 학습헀던 공식들을 조금 더 이해해보자.
  • 엔트로피 공식은 다음과 같다.

$$H(X) = - \sum_{x}P(x)lnP(x) $$

  • 교차 엔트로피 공식은 다음과 같다. 

$$H(P, Q) = - \sum_{x}P(x)lnQ(x) $$

  • 위 두 공식에서 엔트로피 공식과 교차 엔트로피 공식의 차이는 실제값($P(x)$)과 타깃이 되는 예측값($Q(x)$)의 정보량 비율 합으로 구해지는 것을 알 수 있다.
  • 여기서, 교차 엔트로피 오차는 분류할 클래스의 수가 $N>2$인 정수이므로, 클래스별 확률이 다 달랐으나, 이진 교차 엔트로피 오차는 클래스가 "y=0"와 "y=1" 단 두 가지만 존재하는 것을 알 수 있다.

$$ p = [y, 1-y] $$

$$ q = [\hat{y}, 1-\hat{y}] $$

  • 그렇다면, $y=0$의 교차 엔트로피 공식을 만들어보자.

$$ H(y)= -\sum_{i=1}^{N}(y_i*ln\hat{y_i}) $$

  • $y=1$의 교차 엔트로피 공식을 만들어보자.

$$ H(y-1)= -\sum_{i=1}^{N}((1-y_i)*ln(1-\hat{y_i})) $$

  • 밑과 위가 같은 시그마끼리는 서로 합칠 수 있다.

$$ H(y) + H(y-1)= -\sum_{i=1}^{N}(y_i*ln\hat{y_i} + (1-y_i)*ln(1-\hat{y_i})) $$

  • 여기서 $N$개의 학습 데이터 전체에 대한 교차 엔트로피를 구해주는 것이므로, 평균으로 만들어 값을 줄여주자!

$$ Loss = -\frac{1}{N}\sum_{i=1}^{N}(y_i*ln\hat{y_i} + (1-y_i)*ln(1-\hat{y_i})) $$

  • 앞서, 오차제곱합(SSE)와 평균제곱오차(MSE)에 대해 보았을 텐데, 합은 데이터의 수가 많아질수록 증가하므로, 데이터의 수로 나눠 평균으로 만들어야 이를 보정해줄 수 있다.
  • 여기서 데이터의 수는 입력 값의 벡터 크기가 아니라, Input되는 데이터의 수를 말한다.
  • 이진 교차 엔트로피 오차는 출력층의 노드 수를 하나로 하여 출력값을 하나로 받으므로, 실제값(Label)과 예측값(predict) 모두 하나의 스칼라 값이다.
  • 왜 교차 엔트로피 오차(CEE)에서는 왜 N으로 나눠주지 않았는지 의문이 들 수 있는데, 그 이유는 교차 엔트로피 오차는 하나의 데이터에 대해서만 실시한 것이기 때문이다.
  • 교차 엔트로피 오차(CEE)를 N개의 데이터에 대해 실시하면 범주형 교차 엔트로피 오차(Categorical Cross Entropy Error)가 된다.

 

 

 

 

2. 구현해보자!

  • 이진형 교차 엔트로피 에러(BCEE)는 앞서 학습 했던, 교차 엔트로피 에러와 꽤 유사하다.
>>> import numpy as np

>>> def BCEE(predict, label):
    
>>>     delta = 1e-7
>>>     pred_diff = 1 - predict
>>>     label_diff = 1 - label
>>>     result = -(np.sum(label*np.log(predict+delta)+label_diff*np.log(pred_diff+delta)))/len(label)
    
>>>     return result
>>> predict = np.array([0.8, 0.1, 0.05, 0.9, 0.05])
>>> label = np.array([1, 0, 0, 1, 0])
>>> BCEE(predict, label)
0.10729012273129139
  • 위 데이터를 보면 총 5개의 데이터 셋에 대한 이진 분류 결과를 보았다.
  • 이번에는 예측값과 실제 데이터를 더 유사하게 하여 결과를 내보자.
>>> predict = np.array([0.95, 0.05, 0.01, 0.95, 0.01])
>>> label = np.array([1, 0, 0, 1, 0])
>>> BCEE(predict, label)
0.03479600741200121
  • 보다 0에 가까워진 것을 알 수 있다.
  • 이번에는 좀 멀게 만들어보자.
>>> predict = np.array([0.30, 0.40, 0.20, 0.65, 0.2])
>>> label = np.array([1, 0, 0, 1, 0])
>>> BCEE(predict, label)

 

 

 

 

 지금까지 이진 교차 엔트로피 오차(Binary Cross Entropy Error, BCEE)에 대해 학습해보았다. BCEE는 앞서 봤던 CEE를 단순하게 "y=0"일 사건과 "y=1"일 사건에 대한 교차 엔트로피 오차 합의 평균을 낸 것으로, 큰 차이가 없다는 것을 알 수 있다.

 다음 포스트에서는 이진 교차 엔트로피 오차에 대응하는 다중 분류에 사용되는 범주형 교차 엔트로피 오차(Categorical Cross Entropy Error)에 대해 학습해보도록 하겠다.

728x90
반응형
728x90
반응형

 지난 포스트까지 제곱오차(SE)에서 파생된 "오차제곱합(SEE), 평균제곱오차(MSE), 평균제곱근오차(RMSE)"에 대하여 알아보았다. 해당 개념들이 연속형 데이터를 대상으로 하는 회귀분석의 모델 적합도를 평가할 때, 사용돼 듯, 머신러닝에서도 해당 손실함수는 연속형 데이터를 대상으로 사용된다.

 이번 포스트에서는 범주형 데이터를 대상으로 하는 손실함수의 기반이 되는 교차 엔트로피 오차에 대해 알아보도록 하겠다.

 

 

교차 엔트로피 오차(Cross Entropy Error, CEE)

  • 연속형 데이터의 대표적인 손실함수인 제곱오차 시리즈와 달리 교차 엔트로피 오차(CEE)는 범주형 데이터를 분류할 때 주로 사용한다.
  • 교차 엔트로피 오차라는 단어를 풀이해보면, 서로 다른 엔트로피를 교차하여 그 오차를 본다는 말일 텐데, 그렇다면 엔트로피란 무엇일까?

 

 

 

 

1. 정보 이론에서 엔트로피란?

 엔트로피는 열역학과 정보 이론에서 사용되는 용어로, 현재 학습하는 분야는 열역학이 아닌 IT분야이므로, 정보 이론에서 엔트로피가 어떻게 사용되는지를 위키피디아를 참고해 알아보도록 하겠다.
ko.wikipedia.org/wiki/%EC%A0%95%EB%B3%B4_%EC%97%94%ED%8A%B8%EB%A1%9C%ED%94%BC

 

정보 엔트로피 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 2 섀넌의 엔트로피: 2 개의 공정한 동전을 던질 때 정보 엔트로피는 발생 가능한 모든 결과의 개수에 밑이 2 인 로그를 취한 것과 같다. 2 개의 동전을 던지면 4

ko.wikipedia.org

 

A. 정보 이론

  • 어떤 사람이 정보를 많이 알수록, 새롭게 알 수 있는 정보의 양(Information content)은 감소한다.

  • 위 그래프를 참고해보자, A라는 사건이 발생할 확률이 매우 크다면, 우리는 그 사건 A가 발생한다 할지라도 대수롭지 않게 느낀다. 우리가 대수롭지 않게 느낀다는 말은 사건 A에서 발생한 정보량이 작다는 소리다.
  • 반대로, B라는 사건은 발생할 확률이 매우 낮다면, 그 사건이 발생했을 때, 발생하는 정보량은 매우 크다.
  • 이를 이해하기 쉽게 예를 들자면, 정보를 "놀람의 정도"라고 생각해보자, 만약 당신이 동전을 던져서 앞면이 나온다면, 당신은 대수롭지 않게 느낄 것이다. 왜냐하면, 동전을 던졌을 때, 앞면이나 뒷면이 나올 확률은 같고, 평범하게 일어날 수 있는 일이기 때문이다. 때문에 당신에게 발생할 확률이 매우 높은 동전 던지기는 당신에게 매우 작은 정보를 준다.
  • 그러나, 만약 당신이 로또 1등에 당첨되었다고 해보자. 당신은 아마 기절할 정도로 놀랄지도 모른다. 왜냐면, 로또 1등에 당첨될 확률은 상상할 수 없을 정도로 낮기 때문이며, 이 상황에서 당신이 받은 정보량은 매우 크다고 할 수 있다.
  • 즉, 흔히 볼 수 있는 사건(확률이 낮은 사건)일수록, 정보량이 낮고, 매우 희귀하게 발생하는 사건은 정보량이 매우 크다.

 

B. 엔트로피

  • 엔트로피는 사건 A를 반복 실행하였을 때, 얻을 수 있는 "평균 정보량"으로, 어떤 사건에 대한 "정보량의 기댓값"이다.
  • 사건 A를 반복 실행하여 얻는 이유는 경험적 확률과 수학적 확률의 차이 때문으로, 정육면체 주사위를 던져서 1이 나올 확률은, 실행 횟수가 낮다면, 그 확률이 불규칙적으로 나올 수 있기 때문이다.
  • 간단히 말해서 주사위를 10번 던졌을 때, 우연히 1이 6번이나 나올 수 있고, 그로 인해 확률을 $\frac{6}{10}$으로 생각할 수 있다. 이를 많이 실행한다면, 그 확률은 $\frac{1}{6}$에 수렴하게 될 것이다.
  • 엔트로피의 크기는 정보량의 크기로, 예를 들어, 동전을 던져 앞면이 나올 확률과 주사위를 던져 1이 나올 확률을 비교해보면, 주사위 던지기의 확률 $\frac{1}{6}$이고, 동전 던지기의 확률은 $\frac{1}{2}$이므로, 주사위 던지기의 확률이 더 낮아 엔트로피가 더 높다.
  • 그러나, 주사위가 1, 2, 3, 4가 나올 확률이라면, 주사위 던지기 확률이 $\frac{4}{6}$이므로, 동전을 던져 앞면이 나올 확률의 엔트로피가 더 크다.
  • 즉, 엔트로피가 크다는 것은 사건 A의 확률이 낮다는 것으로, 엔트로피는 "어떤 상태에서의 불확실성"이라 할 수도 있다. 예측하기가 어려운 사건일수록 정보량이 많아지고, 엔트로피도 커지게 된다.

 

 

 

 

2. 엔트로피 공식

  • 위 내용을 간추려 이야기해보면, 엔트로피는 사건 A가 발생할 확률이 낮으면 낮을수록 커지는 존재로, 단순하게 확률이라고 생각해도 큰 문제가 없다.
  • 그렇다면, 엔트로피 공식이 어떻게 나오게 되었는지 보도록 하자.

정보량: $I(x) = ln(\frac{1}{p(x)}) = - ln(p(x)) $

엔트로피: $H(X) = -E[I(x)] = E[ln(\frac{1}{p(X)})] = -  \int_{E}p(x)ln(p(x))dx $

  • 만약 표본 공간 E가 이산 공간 $E = {x_1,...,x_n}$이라면, 르베그적분은 합이 되며, 정보 엔트로피는 다음과 같다.

엔트로피: $ H(X) = -\sum_{i}p(x_i)ln(p(x_i))$

  • 엔트로피는 정보량에 영향을 받으며, 정보량은 확률에 영향을 받는다.
  • 정보량은 역수를 취한 확률$\frac{1}{p(x)}$에 자연로그($log_e=ln$)를 사용하는데, 2진수 데이터가 대상인 경우, 밑이 $e$가 아니라 2인 로그를 사용하기도 한다.
    (밑이 2이면, 단위는 비트(bit)가 되고, 자연로그이면 단위는 내트(nat)가 된다.)
  • 정보량에 로그가 사용된 이유는 다음과 같다.

 

정보량에 로그가 사용되는 이유

 정보량은 다음과 같은 성질을 가져야 한다.

  1. 정보량은 항상 0보다 크다.
  2. 항상 발생하는 사건은 정보량이 0이다.
  3. 자주 일어나는 사건일수록 정보량은 0에 가깝다.
  4. 독립적인 사건들의 정보량 합은 각 사건의 합이어야 한다. 
  • 위 4가지 조건을 모두 만족하는 것이 로그인데, 이산확률분포에서의 확률은 $0< P(X) \leq 1$이므로, 정보량 $f(x) = -log(p(x)) \geq 0$을 만족하며, $p(x) = 1$이면, $f(x) = 0$이 된다.
  • 또, 독립 사건에 대하여, 사건 A가 일어날 확률 $p_A$와 사건 B가 일어날 확률 $p_B$가 동시에 일어날 확률인 두 사건의 교집합은 $p_{A}p_{B}$인데, 이를 로그 함수에 넣어보면, 로그의 성질에 의해 쉽게 분리가 된다.

$$ I(X_1, X_2) = -log(p_{1}p_{2}) = -log(p_1) - log(p_2) = I(X_1) + I(X_2)$$

  • 로그의 성질로 인해, 독립 사건인 A와 B이 동시에 발생한 정보량은 각 사건이 발생한 정보량의 합과 같다.

 

 

 

 

3. 교차 엔트로피 오차(Cross Entropy Error, CEE)의 공식

  • 교차 엔트로피 오차는 위 엔트로피 공식을 기반으로 각 사건이 발생할 확률이 몇 가지인지에 따라 조금씩 공식이 바뀐다.
  • 먼저 교차 엔트로피 오차의 공식을 보도록 하자.

$$H(P, Q) = -\sum_{x}P(x)lnQ(x)$$

  • 여기서 $Q(x)$는 신경망의 출력값, $P(x)$는 정답 레이블인데, 정답 레이블은 정답만 1이고 나머지는 0인 원-핫 벡터를 사용한다.
  • 원-핫 벡터는 이전 포스트인 "머신러닝-5.0. 손실함수(1)-제곱오차와 오차제곱합"에서 다뤘으므로, 넘어가도록 하겠다. gooopy.tistory.com/60?category=824281
 

머신러닝-5.0. 손실함수(1)-제곱오차와 오차제곱합

 이전 포스트에서 신경망 학습이 어떠한 원리에 의해 이루어지는지 간략하게 살펴보았다. 이번 포스트에서는 제곱 오차(Square Error)와 제곱 오차를 기반으로 만든 손실 함수 오차제곱합(SSE)에 대

gooopy.tistory.com

  • $P(X)$는 원-핫 벡터이므로, 정답 1이 있는 위치 $m$만 $1*lnQ(m)$이 나오게 되고 나머지는 $0*lnQ(n) = 0$으로 나와, 정답 위치에 해당하는 $lnQ(m)$의 값이 교차 엔트로피 오차로 출력되게 된다.
  • 이 부분이 앞서 학습한, 오차 제곱(SE) 시리즈와의 차이점인데, 오차 제곱 시리즈는 회귀식처럼 값의 흩어진 정도를 사용한다면, 교차 엔트로피 오차는 정답 레이블에서 정답에 해당하는 위치의 확률의 로그 값이 출력되게 된다.
  • 예를 들어 분류가 4개인 데이터를 사용한다고 해보자.

$$ label = [0, 0, 1, 0] $$

$$ pred = [0.1, 0.2, 0.6, 0.1] $$

  • 위 데이터를 교차 엔트로피 오차에 넣으면 다음과 같다.

$$ E = -(0*ln(0.1) + 0*ln(0.2) + 1*ln(0.6) + 0*ln(0.1)) = - ln(0.6) = 0.51$$

  • 이를 통해 교차 엔트로피 오차는 특정 클래스에 속할 정보량을 이용한다는 것을 알 수 있다.
  • 교차 엔트로피 오차 역시 정보량이 0에 가까워져 발생 확률이 1에 가깝게 만드는 것을 목적으로 한다.

 

 

 

 

4. 구현해보자!

>>> import numpy as np

>>> def CEE(predict, label):
>>>     delta = 1e-7
>>>     return -np.sum(label * np.log(predict + delta))
  • 로그 함수는 $x=0$에서 무한대로 발산하는 함수이기 때문에 $x=0$이 들어가서는 안된다.
  • 그러나, 원-핫 벡터는 정답 위치를 제외한 나머지 원소들이 모두 0이므로, 매우 작은 값을 넣어줘서 - 무한대가 나오는 것을 막아줘야 한다.

# 실제 데이터와 예측 데이터가 비슷하게 나온 경우
>>> label = np.array([0, 0, 1, 0, 0])
>>> predict = np.array([0.1, 0.1, 0.6, 0.1, 0.1])

>>> CEE(predict, label)
0.510825457099338


# 실제 데이터와 예측 데이터가 다르게 나온 경우
>>> label = np.array([0, 0, 1, 0, 0])
>>> predict = np.array([0.05, 0.4, 0.3, 0.2, 0.05])

>>> CEE(predict, label)
1.2039724709926583
  • 위 결과를 보면, 오차제곱(SE) 시리즈의 오차제곱합(SSE), 평균제곱오차(MSE), 평균제곱근오차(RMSE)와 마찬가지로 가중치로 인해 나온 예측값이 실제값과 얼마나 가까운지를 하나의 스칼라 값으로 출력한 것을 알 수 있다.

 

 

 

 

 지금까지 정보 이론에서 엔트로피가 무엇이고, 어떻게 교차 엔트로피 오차(CEE)라는 개념이 나오게 되었는지 알아보았다. 엔트로피에 대해 아주 단순하게 말하면, 확률을 조금 다르게 표현한 것이라 생각해도 무방하다. 

 앞서 학습하였던 오차제곱합(SEE), 평균제곱오차(MSE), 평균제곱근오차(RMSE)는 실제값과 예측값의 편차를 이용해서 가중치를 평가하므로, 연속형 데이터에 걸맞았으나, 교차 엔트로피 오차는 이산형 데이터임을 가정하고, 자신이 원하는 클래스에 해당하는 예측값이 나오는 확률을 이용해(엄밀히 따지면 확률과 약간 다르지만! 정보량이던 엔트로피던 확률의 영향을 받는다!) 가중치를 평가하였다.

 다음 포스트에서는 이진 교차 엔트로피 오차(Binary Cross Entropy Error)에 대해 알아보도록 하겠다.

728x90
반응형
728x90
반응형

 지난 포스트에서 "제곱오차(SE) > 오차제곱합(SSE) > 평균제곱오차(MSE)" 순으로 알아보았다. 이번 포스트에서는 SSE의 또 다른 파생 형제인 평균제곱근오차(RMSE)에 대해 알아보도록 하겠다.

 

 

평균제곱근오차(Root Mean Square Error, RMSE)

  • 평균제곱오차(MSE)는 "각 원소의 평균까지의 편차 제곱의 평균"인 분산과 굉장히 유사한 개념이다. 
  • 평균제곱오차 역시 분산과 마찬가지로 편차 제곱 합을 하였기 때문에 이것이 실제 편차라 보기 힘들며, 그로 인해 분산과 표준편차처럼 평균제곱오차에도 제곱근(Root)을 씌운 것이 평균제곱근오차다.
  • 그 공식은 다음과 같다.

$$RMSE = \sqrt{\frac{1}{n}\sum_{k}^{n}(y_k - \hat{y_k})^2}$$

 

 

 

 

1. 어째서 평균제곱근오차(RMSE)를 사용하는 것일까?

  • 분산 대신 표준편차를 사용하는 이유와 비슷한데, 평균제곱오차는 실제 오차의 편차 평균이 아니라, 오차의 편차 제곱의 평균이기 때문에, 실제 편차를 반영한다고 볼 수 없다.
  • 이는 평균제곱오차의 장점이자 단점으로 "큰 오류를 작은 오류에 비해 확대시킨다"는 것을 제곱근을 사용함으로써 어느 정도 보정할 수 있다.
  • 예를 들어, (1-0.01)과 (1.0.95)의 차이와 (1-0.01)^2과 (1-0.95)^2의 차이를 비교해보자.
>>> print((1-0.01) - (1-0.95))
>>> print(np.round((1-0.01)**2 - (1-0.95)**2, 3)) 
0.94
0.978
  • 위를 보면, 편차의 제곱을 하는 것이 그렇지 않은 것보다 차이가 크게 확대되는 것을 알 수 있다.
  • 때문에, 이를 보정해주기 위해  제곱근(Root)을 사용하는 것이다.

 

  • 물론, 제곱근을 사용한다고 하여, 평균절대값오차(MAE)에 비해 실제 편차라고 할 수는 없으나, MSE가 편차를 제곱시켜, 큰 오류를 작은 오류보다 확대시킨다는 장점은 제곱근을 사용하여도 유지되기 때문에 오차의 존재를 인지하는 데엔 더욱 도움이 된다.
  • 평균절대값오차(MAE)0에서 미분이 불가능하기 때문에 경사하강법을 이용해 최적의 값에 가까워지더라도 이동거리가 일정해 최적 값에 수렴하지 않으므로, 개인적으로는 추천하지 않는다.
  • 즉, "평균제곱근오차(RMSE)는 제곱근을 사용함으로써 평균제곱오차(MSE)의 왜곡을 줄여주기 때문에 오차를 보다 실제 편차와 유사하게 볼 수 있게 되어 사용한다"라고 할 수 있다.
  • 평균제곱근오차(RMSE) 역시 연속형 데이터를 대상으로 할 때 사용한다.

 

 

 

 

2.  구현해보자.

  • 지금까지 만들었던 오차제곱(SE)에서 파생된 손실함수들의 결과를 비교해보자.
  • Sample Dataset은 이전 포스트에서 만들었던 함수를 그대로 사용하겠다.
>>> import numpy as np

>>> def SSE(real, pred):
>>>     return 0.5 * np.sum((real - pred)**2)

>>> def MSE(real, pred):
>>>     return (1/len(real)) * np.sum((real - pred)**2)

>>> def RMSE(real, pred):
>>>     return np.sqrt((1/len(real)) * np.sum((real - pred)**2))


# sample Data를 만들어보자.
>>> def make_sample_dataset(data_len, one_index):

>>>     label = np.zeros((data_len,))
>>>     pred = np.full((data_len,), 0.05)

>>>     # 특정 index에 실제 데이터엔 1을 예측 데이터엔 0.8을 넣어보자.
>>>     label[one_index] = 1
>>>     pred[one_index] = 0.8
    
>>>     return label, pred
  • np.sqrt(x) 함수는 제곱근을 해준다.
>>> label, pred = make_sample_dataset(100, 30)

>>> print("SSE: ", np.round(SSE(label, pred), 5))
>>> print("MSE: ", np.round(MSE(label, pred), 5))
>>> print("RMSE: ", np.round(RMSE(label, pred), 5))

SSE:  0.14375
MSE:  0.00288
RMSE:  0.05362
  • 위 출력 결과를 보면 다음과 같이 해석할 수 있다.
  • SSE는 데이터의 수에 지나치게 영향을 받아, 오차가 가장 크게 나온다.
  • MSE는 편차를 지나치게 확대하므로, 오차가 가장 작게 나왔다.
  • RMSE는 MSE에 비해 편차가 확대된 정도를 보정하므로, 실제 편차와 어느 정도 유사한 결과가 나왔다고 할 수 있다.
  • 혹시, 데이터의 편차가 너무 일정해서 이런 결과가 나온 것이 아닐까? 하는 의구심이 들 수도 있으니, 이번엔 어느정도 랜덤한 데이터 셋을 사용해보자.
# sample Data를 만들어보자.
>>> def make_sample_dataset2(data_len, one_index):

>>>     label = np.zeros((data_len,))
    
>>>     # 0.01을 간격으로 0에서 0.3 사이인 값이 일부 섞인 배열을 만들어보자
>>>     pred_sample = np.arange(0, 0.3, 0.01)
    
>>>     # 전체 데이터의 절반은 값을 넣도록 하겠다.
>>>     random_data_len = int(data_len/2)
>>>     pred_1 = np.random.choice(pred_sample, random_data_len, replace = True)
>>>     pred_2 = np.zeros((data_len - random_data_len))
>>>     pred = np.concatenate((pred_1, pred_2), axis = 0)
>>>     np.random.shuffle(pred)

>>>     # 특정 index에 실제 데이터엔 1을 예측 데이터엔 0.95을 넣어보자.
>>>     label[one_index] = 1
>>>     pred[one_index] = 0.95
    
>>>     return label, pred
  • np.arange(시작, 끝, 간격) 함수를 이용해 샘플을 추출할 데이터 셋을 만들었다.
  • np.random.choice(데이터셋, 샘플 수, 복원 추출 여부) 함수를 이용해 랜덤한 배열을 만들었다.
  • np.concatenate((배열1, 배열2), axis=0) 함수를 이용해 배열을 합쳤다.
  • np.random.shuffle(배열) 함수를 이용해 배열을 섞었다.
>>> label, pred = make_sample_dataset2(10000, 30)
>>> pred[:100]
array([0.  , 0.33, 0.37, 0.  , 0.11, 0.  , 0.  , 0.  , 0.26, 0.  , 0.  ,
       0.  , 0.14, 0.1 , 0.26, 0.21, 0.1 , 0.07, 0.34, 0.  , 0.  , 0.  ,
       0.19, 0.14, 0.  , 0.  , 0.13, 0.17, 0.  , 0.  , 0.95, 0.  , 0.07,
       0.  , 0.03, 0.39, 0.  , 0.25, 0.32, 0.  , 0.  , 0.27, 0.  , 0.  ,
       0.  , 0.1 , 0.  , 0.3 , 0.  , 0.  , 0.  , 0.  , 0.19, 0.04, 0.2 ,
       0.28, 0.  , 0.  , 0.32, 0.  , 0.  , 0.  , 0.  , 0.03, 0.  , 0.26,
       0.08, 0.39, 0.  , 0.24, 0.  , 0.15, 0.  , 0.  , 0.  , 0.  , 0.  ,
       0.  , 0.14, 0.  , 0.  , 0.  , 0.  , 0.22, 0.  , 0.24, 0.  , 0.05,
       0.12, 0.12, 0.  , 0.09, 0.  , 0.19, 0.  , 0.  , 0.01, 0.23, 0.08,
       0.15])
  • 다음과 같은 형태의 데이터셋이 만들어졌다.
>>> print("SSE: ", np.round(SSE(label, pred), 5))
>>> print("MSE: ", np.round(MSE(label, pred), 5))
>>> print("RMSE: ", np.round(RMSE(label, pred), 5))

SSE:  127.74995
MSE:  0.02555
RMSE:  0.15984
  • 랜덤한 데이터셋을 사용한다 할지라도 손실함수가 비슷하게 반환되는 것을 알 수 있다.
  • 위 결과를 보면, 연속형 데이터를 대상으로 손실함수를 사용한다고 하면, SSE는 가능한 사용하지 않는 것을 추천하며, MSE는 실제 오차가 있는 수준보다 과소평가된 결과가 나올 수 있다. 반면에 RMSE는 오차를 보다 보정하여 나타내기 때문에 실제 오차와 꽤 가까운 것을 알 수 있다.

 

 

 

 

 지금까지 오차 제곱(SE)에서 파생된 손실함수들인 SSE, MSE, RMSE에 대해 알아보았다. 해당 손실함수는 연속형 데이터를 대상으로 사용하며, 평균절대오차(MAE)에 비해 미분이 잘되어, 학습률에 따른 이동 거리가 달라 학습에 유리하다. 가능하면 RMSE를 사용하길 추천한다.

 다음 포스트에서는 데이터를 분류하는 경우 사용되는 손실함수인 교차 엔트로피 오차(Cross Entropy Error, CEE)를 학습해보도록 하겠다.

728x90
반응형
728x90
반응형

 이전 포스트에서는 제곱오차(Square Error, SE)와 제곱오차를 기반으로 만들어진 손실함수인 오차제곱합(Sum of Squares for Error, SSE)에 대해 알아보았다. 이번 포스트에서는 이 SSE를 기반으로 만들어진 평균제곱오차(MSE)에 대해 알아보도록 하겠다.

 

 

평균제곱오차(Mean Square Error, MSE)

  • 단순히 실제 데이터와 예측 데이터 편차의 제곱 합이었던 오차제곱합(SSE)을 데이터의 크기로 나눠 평균으로 만든 것이 평균제곱오차다.
  • 그 공식은 다음과 같다.

$$ \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y_i})^2 $$

  • 이전에 봤던 오차제곱합(SSE)는 델타 규칙에 의해 $\frac{1}{2}$을 곱해주었으나, 평균제곱오차(MSE)는 $\frac{1}{n}$이 곱해지므로, 굳이 $\frac{1}{2}$를 곱하지 않아도 된다.

 

 

 

1. 오차제곱합(SSE) 대신 평균제곱오차(MSE)를 주로 사용하는 이유

  • 평균제곱합은 단순히 오차제곱합을 평균으로 만든 것에 지나지 않으므로, 이 둘은 사실상 같다고 볼 수 있다. 그럼에도 평균제곱오차(MSE)를 주로 사용하게 되는 이유는 다음과 같다.
  • 오차의 제곱 값은 항상 양수이며, 데이터가 많으면 많을수록 오차 제곱의 합은 기하급수적으로 커지게 된다.
  • 이로 인해, 오차제곱합으로는 실제 오차가 커서 값이 커지는 것인지 데이터의 양이 많아서 값이 커지는 것인지를 구분할 수 없게 된다.
  • 그러므로, 빅데이터를 대상으로 손실함수를 구한다면, 오차제곱합(SSE)보다 평균제곱오차(MSE)를 사용하는 것을 추천한다.

 

 

 

2. 평균제곱오차(MSE)는 언제 사용하는가?

  • 평균제곱오차(MSE)는 통계학을 한 사람이라면 굉장히 익숙한 단어일 것이다. 
  • 바로, 통계학의 꽃이라고 불리는 회귀분석에서 모델의 적합도를 판단할 때 사용하는 값인 결정 계수 $R^2$를 계산할 때, 분자로 사용되기 때문이다.
  • 딥러닝에서도 평균제곱오차(MSE)는 회귀분석과 유사한 용도로 사용된다.
  • 회귀분석이 연속형 데이터를 사용해 그 관계를 추정하는 방식이듯, 평균제곱오차(MSE) 역시 주식 가격 예측과 같은 연속형 데이터를 사용할 때 사용된다.

 

 

 

3. 구현해보자

  • MSE를 구현하고, SSE와의 차이를 비교해보자.
>>> import numpy as np

>>> def MSE(real, pred):
>>>     return (1/len(real)) * np.sum((real - pred)**2)

>>> def SSE(real, pred):
>>>     return 0.5 * np.sum((real - pred)**2)
# sample Data를 만들어보자.
>>> def make_sample_dataset(data_len, one_index):

>>>     label = np.zeros((data_len,))
>>>     pred = np.full((data_len,), 0.05)

>>>     # 특정 index에 실제 데이터엔 1을 예측 데이터엔 0.8을 넣어보자.
>>>     label[one_index] = 1
>>>     pred[one_index] = 0.8
    
>>>     return label, pred
    
>>> label1, pred1 = make_sample_dataset(100, 30)
>>> label2, pred2 = make_sample_dataset(10000, 30)
>>> label1
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
       0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
       
       
>>> pred1
array([0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
       0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
       0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.8 , 0.05, 0.05,
       0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
       0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
       0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
       0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
       0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
       0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05,
       0.05])
  • 자, 위에서 만든 샘플 데이터셋 함수는 개수만 다르지, 나머지 원소는 전부 동일한 데이터셋을 반환하는 함수다.
  • 즉, 데이터의 수만 다르지, 편차는 동일한 형태의 데이터셋을 반환한다.
>>> print("Data가 100개일 때, SSE의 결과:", np.round(SSE(label1, pred1), 5))
>>> print("Data가 1000개일 때, SSE의 결과:", np.round(SSE(label2, pred2), 5))
>>> print("----"*20)
>>> print("Data가 100개일 때, MSE의 결과:", np.round(MSE(label1, pred1), 5))
>>> print("Data가 1000개일 때, MSE의 결과:", np.round(MSE(label2, pred2), 5))


Data가 100개일 때, SSE의 결과: 0.14375
Data가 1000개일 때, SSE의 결과: 12.51875
--------------------------------------------------------------------------------
Data가 100개일 때, MSE의 결과: 0.00288
Data가 1000개일 때, MSE의 결과: 0.0025
  • 위 결과를 보면, 어째서 SSE를 사용하면 위험한지를 알 수 있다.
  • 두 데이터셋은 데이터의 양만 다를 뿐 편차는 같은데, SSE는 90배에 가까운 차이를 반환하였다.
  • 물론, 최적의 가중치를 찾아가면서 손실함수 SSE 역시 감소하긴 하겠으나, Data의 양이 지나치게 많다면, 실제로 오차가 거의 없다 할지라도 오차가 굉장히 크게 나올 위험이 있다.
  • 그러므로, 가능한 SSE보다는 MSE를 사용하길 바란다.

 

 

 

 지금까지 연속형 데이터를 다룰 때, 가장 많이 사용되는 손실함수 중 하나인 평균제곱오차(MSE)에 대하여 알아보았다. 다음 포스트에서는 MSE에서 유도되어 나온 또 다른 손실함수인 평균제곱근편차(RMSE)에 대하여 알아보도록 하겠다.

728x90
반응형

+ Recent posts