신경망 학습(1)

Hyungseop Lee·2023년 3월 5일
0
post-thumbnail

신경망 학습

학습이란 훈련 데이터로부터 가중치 매개변수의 최적값을 자동으로 획득하는 것이다.
손실 함수 신경망이 학습할 수 있도록 해주는 지표이다.

손실 함수의 결과값을 가장 작게 만드는 가중치 매개변수를 찾는 것이 학습의 목표이다.

데이터 주도 학습(머신러닝 vs 딥러닝)

데이터에서 답을 찾고 데이터에서 패턴을 발견하고 데이터로 이야기를 만드는 것이 바로 기계학습이다.
데이터가 없으면 아무것도 시작하지 못한다.
따라서 기계학습의 중심에는 데이터가 존재한다.
또한 사람의 경험과 직관을 단서로 시행착오를 거듭하며 일을 진행한다.

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

특징과 기계학습 방식에서는 사람이 설계했지만,
신경망(딥러닝)은 이미지에 포함된 중요한 특징까지 '기계'가 스스로 학습한다.
따라서 딥러닝종단간 기계학습(end-to-end machine learning)이라고도 한다.

여기서 종단간은 '처음부터 끝까지'라는 의미로, 데이터(입력)에서 목표한 결과(출력)을 사람의 개입 없이 얻는다는 뜻을 담는다.

신경망(딥러닝)의 이점은 모든 문제를 같은 맥락에서 풀 수 있다는 점.
예를 들어 손글씨 '5'를 인식하는 문제든, '개'를 인식하는 문제든, '사람의 얼굴'을 인식하는 문제든, 세부사항과 관계없이 주어진 데이터를 온전히 학습하고, 주어진 문제의 패턴을 발견하려 시도한다.

Training Data, Test Data

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

  1. 우선 훈련 데이터만 사용하여 학습하면서 최적의 매개변수를 찾음.
  2. 시험 데이터를 사용하여 앞서 훈련한 모델의 실력을 평가.

Training Data, Test Data 분리 이유

우리가 원하는 것은 범용적으로 사용할 수 있는 모델이기 때문에, 범용 능력을 제대로 평가하기 위해 훈련데이터와 시험 데이터를 분리하는 것이다.

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

그래서 데이터셋 하나로만 매개변수의 학습과 평가를 수행하면 올바른 평가가 될 수 없다.
다른 데이터셋은 제대로 맞히더라도 또 다른 데이터셋에는 엉망인 일이 벌어질 수도 있다. 이렇게 한 데이터셋에만 지나치게 최적화된 상태를 오버피팅(Overfitting)이라고 한다.


손실 함수(Loss Function)

신경망 학습에서는 현재의 상태를 '하나의 지표'로 표현한다.
그리고 그 지표를 가장 좋게 만들어주는 매개변수의 값을 탐색하는 것이다.

신경망은 '하나의 지표'를 기준으로 최적의 매개변수 값을 탐색하는데, 그 지표를 손실 함수(Loss Function)이라고 한다.

Loss Function은 일반적으로 오차제곱합교차 엔트로피 오차를 사용한다

오차제곱합(Sum of Squares for Error, SSE)


yky_k : 신경망의 출력(신경망이 추정한 값)
tkt_k : 정답 레이블
kk : 데이터의 차원 수

오차제곱합(SSE) 구현

정답 2, 신경망 출력도 2에서 가장 높은 경우 ➡️ 매우 작은 SSE값
오차제곱합(Sum of Squares Error) 기준으로 오차값이 더 작으니 정답에 더 가까울 것으로 판단할 수 있다.

정답 2, 신경망 출력은 7에서 가장 높은 경우 ➡️ 보다 큰 SSE값

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

  • loglog는 밑이 ee인 자연로그(loge==lnlog_e == ln)
  • yky_k : 신경망의 출력
  • tkt_k : 정답 레이블 (one-hot encoding)
    따라서 실질적으로 정답일 때의 추정($t_k$가 1일 때의 $y_k$)의 자연로그를 계산하는 식이 된다.

예를 들어, 정답 레이블은(tkt_k) '2'가 정답이라 하고 이때의 신경망 출력(yky_k)이 0.6이라면
Cross Entropy Error는 ln(0.6)==0.51-ln(0.6) == 0.51이 된다.
또한, 같은 조건에서 신경망 출력(yky_k)이 0.1이라면
Cross Entropy Error는 ln(0.1)==2.30-ln(0.1) == 2.30이 된다.

교차 엔트로피(CEE) 구현

정답 2, 신경망 출력도 2에서 가장 높은 경우

정답 2, 신경망 출력은 7에서 가장 높은 경우

미니배치 학습

  1. 기계학습 문제는 훈련 데이터에 대한 Loss Function값을 구하고, 그 값을 최대한으로 줄여주는 Parameter를 구해야 한다.
  2. 즉, 훈련 데이터가 100개 있으면 그로부터 계산한 100개의 Loss Function값들의 합을 지표로 삼는다.

NN : 데이터 개수
yny_nk_k : 신경망의 출력
tnt_nk_k : 정답 레이블

복잡해 보이지만
앞서 CEE Loss Function에서 단순히 N개의 데이터로 확장하고,
N으로 나누어 정규화했을 뿐. ➡️ 평균 손실 함수

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

하지만 빅데이터 수준이 되면 모든 데이터를 대상으로 손실 함수의 합을 구하기에 시간이 많이 걸린다.
이런 경우, 데이터를 일부러 추려 전체의 근사치로 이용할 수 있다.

이 일부를 mini-batch(미니배치)라고 한다.
60,000장의 훈련 데이터 중에서 100장을 무작위로 뽑아 그 100장만을 사용하여 학습하는 것을 미니배치 학습이라고 한다.

mini-batch 구현

  • 훈련 데이터에서 무작위로 10장만 빼내기 위해 np.random.choice()함수를 사용

train_size = x_train.shape[0] # 60,000장의 사진(데이터)
batch_size = 10
batch_mask = np.random.choice(train_size, batch_size)
print(batch_mask) #  10개 data의 index번호 random하게 뽑기

x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]

(batch용 CEE)정답 레이블이 one-hot encoding된 경우

# y : 신경망 출력, t : 정답 레이블
# 데이터 하나당 Cross Entropy를 구하는 경우는 데이터의 형상을 바꿔준다.
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

(batch용 CEE)정답 레이블이 숫자 레이블로 된 경우

# y : 신경망 출력, t : 정답 레이블
# 데이터 하나당 Cross Entropy를 구하는 경우는 데이터의 형상을 바꿔준다.
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, t=[2, 7, 0, 9, 4]라면?
# np.arange(batch_size, t)
# == y[np.arange([0, 1, 2, 3, 4]), [2, 7, 0, 9, 4]]]
# == [y[0, 2], y[1, 7], y[2, 0], y[3, 9], y[4, 4]] 

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

우리의 궁극적인 목표는 높은 정확도를 끌어내는 매개변수 값을 찾는 것이다.

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

  • 신경망 학습에서는 최적의 매개변수(가중치와 편향)를 탐색할 때 손실 함수의 값을 가능한 작게 하는 매개변수 값을 찾는다.
  • 이때 매개변수의 미분(기울기)을 계산하고, 그 미분 값을 단서로 매개변수의 값을 서서히 갱신하는 과정을 반복한다.
  • 정확도를 지표로 삼아서는 안 되는 이유는 미분 값이 대부분의 장소에서 0이 되어 매개변수를 갱신할 수 없기 때문이다.

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

정확도를 지표로 삼으면, 매개변수의 미분이 대부분 0이 되는 이유?

한 신경망이 100장의 훈련 데이터 중 32장을 올바르게 인식한다고 할 때, 정확도는 32%이다.
만약 정확도가 지표였다면 가중치 매개변수의 값을 조금 바꾼다고 해도 정확도는 그대로 32%일 것이다.

즉, 매개변수를 약간만 조정해서는 정확도가 개선되지 않고 일정하게 유지된다.
정확도가 개선된다 하더라도 그 값은 32.0123%와 같은 연속적인 변화보다는 33%나 34%처럼 불연속적인 띄엄띄엄한 값으로 바뀌게 된다.

정확도는 매개변수의 미소한 변화에는 거의 반응을 보이지 않고, 반응이 있더라도 그 값이 불연속적으로 확 변화하게 된다.
➡️ 계단 함수를 Activation Function으로 사용하지 않는 이유와도 똑같다.

  • 계단 함수는 대부분의 장소에서 미분값이 0이기 때문에, Activiation Function으로 계단 함수를 사용하지 않는다.
    반면에 시그모이드 함수는 기울기가 0이 되지 않고 연속적으로 변하기 때문에 신경망이 올바르게 학습할 수 있는 것이다.

수치 미분(Numerical Differentiation)

경사법에서는 기울기(경사)값을 기준으로 나아갈 방향을 정한다.

미분

미분은 어느 한순간의 변화량을 표시하는 것이다.

따라서 10분에 2Km를 달린 사람이 있다고 가정했을 때,
이 때의 평균 속도는 0.2[km/h] 라고 할 수 있다.

미분특정 한순간의 변화량을 뜻하기 때문에
10분 이라는 시간을 가능한 줄여서
➡️ 직전 1분에 달린 거리
➡️ 직전 1초에 달린 거리
➡️ 직전 0.1초에 달린 거리
이런 식으로 간격을 줄여 한순간의 변화량(한순간의 속도)를 얻는 것이다.

수치 미분 구현

h = 0 ➡️ 미분값이 inf 되니까 h = 1e-50 (매우 작은 값으로 대체?)
h = 1e-50 ➡️ 반올림 오차(Rounding Error) 문제 발생.

  1. h = 1e-4 (0.0001) ➡️ 0.0001 정도의 값을 사용하면 좋은 결과를 얻는다고 알려져 있다. (근사로 구한 접선 == 수치 미분)
    (진정한 미분과의 값은 엄밀히 일치하지 않는다. 이 차이는 h를 무한히 0으로 좁히는 것이 불가능하여 생기는 한계이다.)
    ➡️ 여기에서 처럼 아주 작은 차분으로 미분하는 것을 수치 미분이라고 한다.
    한편, 수식을 전개해 미분하는 것은 해석적(Analytic) 미분이라고 한다.
    해석적 미분은 오차를 포함하지 않는 진정한 미분값을 구해준다.

  2. 수치 미분(근사로 구한 접선)의 오차를 줄이기 위해 중심 차분 사용
    중심 차분 : f(x+h)f(xh)f(x+h) - f(x-h) (오차를 줄이는 방법)
    전방 차분 : f(x+h)f(x)f(x+h) - f(x) (기존 미분 방식이지만, 오차가 존재)

수치 미분의 예

python으로 한 수치 미분값과 실제 미분값을 확인해보자.

f(x)=0.01x2+0.1xf(x) = 0.01x^2 + 0.1x

def fucntion_1(x) :
    return (0.01 * (x ** 2)) + (0.1*x)
import numpy as np
import matplotlib.pylab as plt

x = np.arange(0.0, 20.0, 0.1) # 0~20 (0.1 간격)
y = fucntion_1(x)

plt.xlabel('x')
plt.ylabel('f(x)')
plt.plot(x, y);
  • 시각화 하면 다음과 같은 그래프가 나온다.

    또한 다음과 같이 수치 미분을 사용하여 구현한 코드의 미분값실제 미분값(2.0, 3.0)이 실제로 거의같은 값이라고 해도 될 만큼 매우 작은 오차이다.

편미분

f(x0,x1)=x02+x12f(x_0, x_1) = x_0 ^ 2 + x_1^2

함수 ff는 변수가 2개라는 점에 주의해야 한다.
그래서 어느 변수에 대한 미분이냐를 구별해야 한다.
이와 같이 변수가 여럿인 함수에 대한 미분을 편미분이라고 한다.

기울기

x0x_0x1x_1의 편미분을 동시에 계산하고 싶다면?
➡️ 이처럼 모든 변수의 편미분을 Vector로 정리한 것을 Gradient(기울기)라고 한다.

기울기가 가리키는 쪽은 각 장소에서 함수의 출력 값을 가장 크게 줄이는 방향이다.

기울기 구현

  • Analytic Gradient (해석적 미분)
  • Numerical Gradient (수치 미분)
def numerical_gradient(f, x) :
    h = 1e-4 # 0.0001
    gradient = np.zeros_like(x) # x shape의 배열 생성(x값마다 기울기를 구하여 vector형으로 만드는 것이 '기울기'이기 때문.)
    
    for idx in range(x.shape) :
        tmp_val = x[idx] 
        
        # f(x + h) 
        x[idx] = tmp_val + h
        fxh1 = f(x)
        
        # f(x - h)
        x[idx] = tmp_val - h
        fxh1 = f(x)
        
    return gradient

def _numerical_gradient_no_batch(f, x):
    h = 1e-4 # 0.0001
    grad = np.zeros_like(x) # x와 형상이 같은 배열을 생성
    
    for idx in range(x.size):
        tmp_val = x[idx] 
        
        # f(x+h) 계산
        x[idx] = float(tmp_val) + h
        fxh1 = f(x)
        
        # f(x-h) 계산
        x[idx] = tmp_val - h 
        fxh2 = f(x) 
        
        grad[idx] = (fxh1 - fxh2) / (2*h)
        x[idx] = tmp_val # 값 복원
        
    return grad

def numerical_gradient(f, X):
    if X.ndim == 1: # X가 vector형태(1차원)라면 바로 gradient를 구해서 return.
        return _numerical_gradient_no_batch(f, X)
    else: # X가 Matrix형태(다차원)라면, gradient도 Matrix형태로 반환하기 위해 밑에 for문을 수행하고 return.
        grad = np.zeros_like(X)
        # X Matrix를 vector로 나누어 vector shape의 기울기를 하나씩 구하여 grad Matrix에 하나씩 추가.
        for idx, x in enumerate(X):
            grad[idx] = _numerical_gradient_no_batch(f, x)
        
        return grad

# x(0)^2 + x(1)^2 + x(2)^2 + .. + x(n)^2 형태의 함수 f
def function_2(x):
    if x.ndim == 1:
        return np.sum(x**2)
    else:
        return np.sum(x**2, axis=1)      
    
# 점(3, 4)에서의 기울기
print(numerical_gradient(function_2, np.array([3.0, 4.0])))
# 점(0, 2)에서의 기울기
print(numerical_gradient(function_2, np.array([0.0, 2.0])))
# 점(3, 0)에서의 기울기
print(numerical_gradient(function_2, np.array([3.0, 0.0])))

profile
Efficient Deep Learning Model

0개의 댓글

관련 채용 정보