본문 바로가기
  • 비둘기다
  • 비둘기다
  • 비둘기다
AI/Deep learning from Scratch

[머신러닝, 딥러닝] 신경망 학습 (1) - 손실함수

by parzival56 2023. 1. 24.

지난 장까지 신경망의 전체적인 개요에 대해 알아봤다면 이번에는 신경망의 학습에 대해 알아볼 차례입니다.

여기서 말하는 학습이란, 단순히 배우는 것이 아니라 훈련 데이터로부터 가중치 매개변수의 최적값을 자동으로 획득하는 것을 말합니다.

신경망의 학습은 특정한 지표로부터 학습을 이어가는데 이 지표를 손실함수라고 합니다. 이 손실함수의 결괏값을 가장 작게 만드는 가중치 매개변수를 찾는 것이 학습의  목표입니다. 손실되는 값이 가장 적어야 우리가 원하는 정확도가 높은 값을 얻어낼 수 있기 때문이죠.

 

신경망의 특징은 데이터를 보고 학습할 수 있다는 점입니다. 데이터를 보고 학습한다는 것은 가중치의 매개변수 값을 데이터를 보고 자동으로 결정하여 처리한다는 뜻입니다. 

 

만약에 MNIST 이미지 데이터셋과 관련된 알고리즘을 구현한다고 할 때 방법을 크게 3가지입니다.

첫 번째는 사람이 직접 다 구현하는 것입니다.

두 번째는 알고리즘을 밑바닥부터 설계하는 대신, 이미지의 특징을 추출하고 그 특징의 패턴을 기계학습 기술로 학습하는 방법이 있습니다. 여기서 특징은 입력 데이터에서 본질적인 데이터를 정확하게 추출할 수 있도록 설계된 변환기를 가리킵니다. 이미지의 특징은 보통 벡터로 기술하고, 컴퓨터 비전 분야에서는 SIFT, SURF, HOG 등의 특징을 많이 사용합니다. 이러한 특징으로 이미지 데이터를 벡터로 변환하고, 변환된 벡터를 가지고 지도 학습 방식의 대표 분류 기법인 SVM, KNN 등으로 학습할 수 있습니다.

마지막은 신경망의 딥러닝입니다. 앞의 두 가지 경우는 사람이 설계를 하는 부분이 존재한다면 딥러닝은 오로지 기계로만 모든 과정이 이루어집니다. 

 

앞선 페이지들에서 저희는 항상 훈련 데이터와 시험 데이터를 나눴습니다. 굳이 왜 이렇게 하는 것일까요?

이유는 범용성 있는 모델을 만들기 위해서입니다. 이 범용 능력을 제대로 평가하기 위해 훈련 데이터와 시험 데이터를 분리하는 것입니다. '범용성 있다'라는 말은 흔히 여러 분야에 두루 쓰일 수 있을 때 사용합니다.

여기서 말하는 범용 능력은 비슷하게도 아직 보지 못한 데이터 즉, 훈련 데이터에 포함하지 않은 데이터로도 문제를 올바르게 풀어내는 능력입니다. 그리고 이 범용 능력을 얻는 것이 기계 학습의 최종적인 목표이기도 합니다.

 

예를 들어 MNIST 손글씨 인식 알고리즘은 우편번호 인식과 같은 분야에도 쓰일 수 있습니다.

그러나 한 가지 데이터로만 학습이 이루어진다면 이를 다른 데이터로 사용할 시 원하고자 하는 결과가 나오지 않을 수 있습니다. 한 가지 데이터셋에만 지나치게 최적화된 상태를 오버피팅이라고 하는데 오버피팅을 피하는 것이 기계학습의 중요한 과제 중 하나입니다.

 

 

손실함수

오차제곱합

위에서 손실함수의 대략적인 정의에 대해서는 설명했으니 본론으로 넘어가도록 하겠습니다.

손실함수는 말씀드렸듯 하나의 지표입니다. 그리고 그 지표 중 가장 많이 쓰이는 것이 바로 오차제곱합입니다.

오차제곱합의 수식은 다음과 같습니다.

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

 

그럼 위 식을 바탕으로 간단하게 넘파이를 이용하여 오차제곱합 함수를 구현할 수 있습니다.

#오차제곱합
def sum_squares_error(y, t):
    return 0.5 * np.sum((y-t)**2)

여기서 y와 t는 넘파이의 sum을 이용하기 때문에 둘 다 넘파이 배열입니다.

그러면 위 함수를 실제 값들을 바탕으로 테스트해 보겠습니다.

t = [0,0,1,0,0,0,0,0,0,0]
# 정답을 2 (인덱스)

y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
# 2일 확률이 가장 높다고 추정함 (0.6)

print(sum_squares_error(np.array(y), np.array(t)))

y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]

print(sum_squares_error(np.array(y), np.array(t)))

일단 t인 정답 레이블은 정답이 2인 것으로 고정시켰습니다.

그리고 추정 값인 y는 두 가지 경우로 나눠서 진행했습니다. 하나는 실제 정답과 맞는 경우, 아니면 틀린 경우로 나눴습니다. 첫 번째 y 배열이 정답인데 이를 계산하면 0.0975가 나옵니다. 오차값이 0.0975로 매우 작다는 뜻입니다.

그러나 오답으로 추정 값을 바꾸고 다시 진행하면 0.5975가 나옵니다. 전에 비해 6배가량의 오차가 나는 것을 확인할 수 있습니다. 

정리하자면 첫 번째 경우가 가장 오차가 작으니 '정답에 가깝다'라고 할 수 있습니다. 왜 '정답이다'라고 하지 않냐면 y값은 

0.0에서 1.0 사이인 확률 변수 즉, 소프트맥스 함수로 부터 나온 값들이기 때문입니다. 그래서 원칙적으로는 가장 높은 확률을 하나씩 다 집어넣어 보고 아차가 가장 적은 값을 정답이라고 하는 것이 맞습니다.

 

 

○교차 엔트로피 오차

이는 오차제곱합과는 또 다른 손실함수입니다.  교차 엔트로피 오차의 수식은 다음과 같습니다.

yk와 tk는 오차제곱합의 수식과 의미하는 바는 똑같습니다. 위에서 오차제곱합 함수의 예시 코드에서 보셨겠지만 t는 원-핫 인코딩으로 되어 있어 실질적으로는 정답일 때 (tk가 1일 때의 yk)의 로그식을 계산하는 것입니다. 

예를 들어 아까처럼 정답의 yk가 0.6이라면 계산식은 -log0.6 = 0.51이 됩니다. 

이로써 알 수 있는 교차 엔트로피 오차의 특징은 정답일 때의 출력이 전체 값을 정하게 한다는 것입니다.

자연로그 y = logx의 그래프입니다. 이 그래프를 가져온 이유는 교차 엔트로피 오차에서는 결과가 0 아니면 정답일 때의 값 밖에 나오지 않는데 심지어 정답 값은 단순한 로그식 계산이기 때문입니다. 

그래프에서 보듯이 x가 1일 때 y는 0이 되고 x가 0에 가까워질수록 y의 값은 점점 작아집니다. 위 손실함수의 수식도 마찬가지로 정답에 해당하는 출력이 커질수록 0에 다가가다가, 그 출력이 1일 때 0이 됩니다. 

반대로 정답일 때의 출력이 작아질수록 오차는 커집니다.

 

그럼 교차 엔트로피 오차 함수를 구현할 차례입니다.

# 교차 엔트로피 오차
def cross_entropy_error(y, t):
    delta = 1e-7
    return -np.sum(t * np.log(y + delta))

여기서 delta의 값인 1e-7은 아주 작은 값을 뜻합니다. 반환식에 delta를 더해주는 이유는 np.log() 함수에 0을 입력하면 마이너스 무한대를 뜻하는 -inf가 출력되어 더 이상 계산을 진행할 수 없기 때문입니다. 

오차제곱합에서 사용한 동일한 y, t를 교차 엔트로피 오차에 활용해 보겠습니다.

t = [0,0,1,0,0,0,0,0,0,0]

y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]

print(cross_entropy_error(np.array(y), np.array(t)))

y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
print(cross_entropy_error(np.array(y), np.array(t)))

첫 번째 출력은 0.5108이 나오고, 두 번째 출력은 2.3025가 나옵니다. 마찬가지로 오차가 작은 첫 번째의 답이 정답과 가깝다고 할 수 있고 오차제곱합의 판단과 일치합니다.

 

 

지금까지 신경망의 학습에 대한 간략한 설명과 학습에 필요한 지표인 손실함수 두 가지를 알아봤습니다.

댓글