CS Repository/기초 딥러닝

[밑바닥부터 시작하는 딥러닝] 신경망 학습

조금씩 차근차근 2025. 8. 7. 16:38

본 내용은 밑바닥부터 시작하는 딥러닝 1도서를 참고하여 작성되었습니다.

 

밑바닥부터 시작하는 딥러닝 1 - 예스24

딥러닝 분야 부동의 베스트셀러!머리로 이해하고 손으로 익히는 가장 쉬운 딥러닝 입문서이 책은 딥러닝의 핵심 개념을 ‘밑바닥부터’ 구현해보며 기초를 한 걸음씩 탄탄하게 다질 수 있도록

www.yes24.com

 

본 글은 선형 회귀로지스틱 회귀를 배경 지식으로 작성되었습니다.
만약 읽다가 이해가 되지 않는 부분이 있다면, 해당 글들을 읽어 주시기 바랍니다.

이전 장에서 우리는 신경망을 이용한 "추론"을 학습했다.
하지만 여전히, 2장에서 해결하지 못한 "학습"의 영역은 해결하지 못했고, 가중치를 우리가 직접 설정해주고 추론을 수행했다.
이번 장에서는 학습의 핵심 키인 "손실함수"와 최적의 손실 함수를 탐색하는 "경사 하강법"을 학습하여 신경망의 학습을 기계에게 맡겨보자!

해당 장을 진행하며 미분값에 대해 굉장히 중요하게 다룰 것이다. 화룡점정을 위해, 미분 이야기가 나오면 일단 납득을 부탁한다.

데이터를 통해 학습한다!

신경망의 특징은 데이터를 보고 "학습"할 수 있다는 점이다.
이는 곧 알고리즘이 가중치 매개변수의 값을 데이터를 보고 자동으로 결정한다는 뜻이다.
만약 수천, 수만개의 파라미터를 앞서 설정한 XOR 게이트처럼 일일히 가중치를 설정하는 모습을 상상해보아라. 얼마나 끔찍한가?
이제 MNIST 데이터셋의 손글씨 숫자를 통해 학습하는 코드를 구현해보며, 신경망을 제대로 활용해보자.

데이터 주도 학습

머신러닝은 데이터가 생명이다.
데이터를 통해 패턴을 학습하고 데이터를 통해 예측을 수행하기 때문에, 양질의 데이터가 굉장히 중요하다.

 

우리가 일반적으로 문제를 해결하고자 할 때를 상상해보자. 특히, 어떤 패턴을 찾아내야 할 때는 사람이 이것저것 생각하고 답을 찾는다.

  • 이 문제는 아무래도 이런 규칙성이 있는 것 같아.
  • 아니, 근본 원인은 다른 데 있을지도 몰라

위와 같이 시행착오를 겪어가며 일을 진행한다.
반면, 머신러닝에서는 이러한 사고 과정을 "기계에게 위임"하고, 수집한 데이터로부터 머신이 직접 패턴을 찾도록 한다.

 

가령, 손글씨 숫자 5를 구분하는 문제를 생각해보자.

사람이 보면, 학습된 패턴을 통해 "이 숫자 모두 5이다."라는 결론을 내릴 수 있다.


그런데 이를 컴퓨터가 스스로 판단하게 하려면 어떻게 해야 하는가?
위에 두개의 수직을 이루는 선분? 그 아래 역 C자 모양? 컴퓨터에게 명확한 지시를 내리기가 어렵다.
이쯤 되면, '5'를 인식하는 알고리즘을 밑바닥부터 '설계하는' 대신, 주어진 데이터를 잘 활용해서 해결하는 방법이 없을까 탐색을 하고 싶어질 것이다.

 

그런 방법중의 하나로, 이미지에서 특징(feature)를 추출하고 그 특징의 패턴을 머신러닝 기술로 학습하는 방법이 있다.
여기서 말하는 특징은 입력 데이터(입력 이미지)에서 본질적인 데이터(중요한 데이터)를 정확하게 추출할 수 있도록 설계된 변환기를 의미한다.

위에서 이야기한 "수직을 이루는 선분 두개의 형식과, 아래 역C자 모양을 구분하는 변환기"같은 기능을 의미한다.

 

이미지의 특징은 보통 벡터로 기술하고, 컴퓨터 비전 분야에서는 SIFT, SURF, HOG 등의 특징을 많이 사용한다.
이런 특징을 사용하여 이미지를 벡터로 변환하고, 변환된 벡터를 가지고 지도 학습 방식의 대표 분류 기법인 SVN, KNN등으로 학습할 수 있다.

 

지금까지 이야기한 과정에서는 주요 "특징"을 찾아내는 과정을 사람이 담당했다.
이것이 기존까지 이어져오던 일반적인 머신러닝의 특징이었다.
아래 그림에서 보면 두번째 칸의 경우에 해당한다.


하지만 (신경망을 이용한) 딥러닝은 여기서 한걸음 더 나아간다.
신경망은 "특징"마저 기계가 스스로 학습한다.
사람은 데이터와 최종 평가만을 정의하며, 그 중간의 과정은 전부 기계에게 맡긴다.
즉, 신경망은 모든 문제를 주어진 데이터 그대로를 입력 데이터로 활용해 'end-to-end'로 학습한다.

훈련 데이터와 시험 데이터

머신러닝 문제에서는 "훈련 데이터"와 "시험 데이터"를 분리한다.

  1. 우선 훈련 데이터만 사용하여 학습하면서 최적의 매개변수를 찾는다.
  2. 이후, 시험 데이터를 통해 해당 학습 결과를 평가한다.

쉽게 말해, 학습자가 정답지를 "암기"하는 문제를 방지하기 위해 학습 데이터와 시험 데이터를 분리한다.
시험 문제가 기출에서만 나온다면, 시험공부가 개념을 학습하는 형태가 아닌, 시험을 학습하는 형태가 될 것은 불 보듯 뻔하다.

 

위 예시에서는 "한 사람의 필체"만 가지고 숫자 5를 학습하면, 다른 사람이 작성한 숫자 5는 분류하기 어려울 것이다.
우리는 "임의의 누군가가 쓴 임의의 글자"를 분류하는 프로그램이 필요하다.

 

머신러닝에서는 이와 같이 한 데이터셋에만 지나치게 최적화된 상태를 "과대적합"(overfitting, 오버피팅)이라고 한다.
과대적합 피하기는 머신러닝의 중요한 과제이기도 하다.

머신러닝은 "닫힌 방정식" 형태의 엄밀한 수학적 증명이 함께하지 않는 경우가 많습니다.
수많은 매개변수를 일일히 증명하는 것보다, 시험 데이터를 통해 빠르게 모델을 검증하는 데 집중하겠다는 목적에 기인합니다.
이는 머신러닝이 지나치게 이론에 집중되어 있는 연구 분야가 아니라, "실무적 관점에서 성과를 내기 위한 트레이드오프를 반영했다"라고 보는 것이 타당합니다.
만약 수학적 증명 관점을 좀 더 자세히 이해하고 싶다면, 다음 링크의 "훈련/테스트 분할" 항목을 참고해주시길 바랍니다.

손실 함수

신경망의 학습에는 학습 데이터의 평가를 위한 지표가 필요하다.
우리가 마치 '행복 지표'를 가진 사람이 그 지표를 근거로 '최적의 인생'을 탐색하듯, 신경망도 '하나의 지표'를 기준으로 최적의 매개변수 값을 탐색한다.
신경망 학습에서 사용하는 지표는 손실 함수(Loss Function, 비용 함수 - cost function 이라고도 불림) 라고 부른다.
이 손실 함수로는 일반적으로 다음과 같은 것들이 있다.

  • SSE
  • MSE
  • CEE

우리는 이 중에서 SSE와 CEE를 공부해보자.

오차제곱합 - SSE

가장 많이 쓰이는 손실 함수는 MSE인데, MSE는 SSE를 배치 크기로 나누면 끝인 손실 함수이다.
좀 더 정의에 집중하기 위해, SSE를 학습해보자.

    • y_k = 신경망의 출력(신경망이 추정한 값)
    • t_k = 정답 레이블
    • k = 데이터의 차원 수
    • t는 원-핫 인코딩(원소 중 하나만 1이고 나머지는 전부 0)이다.
one-hot: 디지털 회로나 기계 학습에서, 여러 상태 중 하나만 활성화(1)되고 나머지는 모두 비활성화(0) 되는 상태를 의미한다.

 

오차 제곱합은, 신경망이 출력한 값과 실제 정답 사이의 차이를 제곱하여 더해 계산한다.


즉, 크게 틀릴수록 손실함수의 값이 커진다.

수식만 보면 머리가 아프니, 직접 계산해보며 어떠한 원리로 동작하는지 느껴보자.


위 식에서 y는 신경망의 출력 값이라고 가정하자.
t는 정답 레이블이다.

 

첫번째 식의 계산 결과, 손실 함수 값이 0.0975를 출력했다.
두번째 식의 계산 결과, 손실 함수 값이 0.5975를 출력했다.

 

현재 정답 레이블은 t[2] 인데, 두번째 인덱스의 값이 더 큰(0.6 > 0.1) 첫번째 y가 더 낮은 손실함수 값을 출력하는 것을 확인할 수 있었다.

"차이를 더하려면 절댓값을 씌우지, 왜 제곱을 하고 더하지?" 라는 의문이 들 수 있기 때문에, 다음 링크에 있는 내용을 아래에 첨부한다.

 

최적에 도달하려면 어떻게 해야 하는가?
위에서 설명했듯이, 잔차(residual)를 최소화 해보자. 그렇다면 총 잔차는 어떻게 정의할 것인가?
다음과 같은 세가지 방식을 고려할 수 있겠다.

  • 단순 더하기
    • 음수와 양수가 함께 더해져, 올바른 최솟값을 찾기 어렵다.
  • 절댓값 씌우기
    • 경사 하강법에서 미분을 사용하는데, 절댓값을 사용하면 미분 불가능 지점이 발생하게 된다.
    • 경사 하강법과 잘 맞지 않는다.
  • 제곱 오차 합 사용하기
    • 잔차를 제곱하고, 그 제곱의 합을 더하자.
    • 미분과도 잘 맞고, 최솟값을 찾을 수도 있다.

MSE는 SSE의 "미니 배치"용 손실함수로, 이 또한 SSE를 미니 배치에 그대로 활용할 때 학습이 배치 크기에 의존하는 문제를 해결하기 위해 고안되었으며, SSE를 배치 크기로 나눠 구현한다. 아래 "미니 배치 학습"챕터에서 더욱 자세하게 다루겠다.

Cross Entropy 교차

또 다른 자주 쓰이는 손실 함수로는 크로스 엔트로피 오차(Cross Entropy Error, CEE, 교차 엔트로피 오차)가 있다.

  • 여기서 log는 밑이 e인 자연로그(ln)이다.
  • y_k = 신경망의 출력(신경망이 추정한 값)
  • t_k = 정답 레이블
  • k = 데이터의 차원 수
  • t는 원-핫 인코딩(원소 중 하나만 1이고 나머지는 전부 0)이다.

자연로그의 그래프를 보며 원리를 자세히 생각해보자.


이 그림에서 보듯이 x가 1에 가까워질수록 출력값이 0이 되고, 1에서 멀어질수록 오차는 커진다.

이 그림에 기반하여 위 수식을 서술적으로 해석하면 다음과 같다.

  • 정답 레이블에 한하여
  • 모델이 예측한 해당 해당 레이블이 정답일 확률을 구한다.
  • 정답이 아니라고 강하게 예측할수록 손실 오차는 지수적으로 증가한다.

그럼 코드로 교차 엔트로피 오차를 구현해보자.


delta = 1e-7 값을 기존 수식에 추가로 더해줬는데, 이는 np.log(0)이 입력되면 -inf가 출력되기에 이를 방지하고자 아주 작은 값을 더해주었다.

더 작은 값을 더해주게 되면 컴퓨터가 내부적으로 아예 0으로 바꿔버린다.
그래서 일반적으로 32비트 실수형이 표현 가능한 가장 작은 수인 소수점 7째자리 수를 더해준다.

 

계산 결과를 확인해보자.


이번에도 정답은 똑같이 2를 출력했다.

미니 배치 학습

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

크로스 엔트로피 오차의 미니 배치 버전은 다음과 같다.


수식이 복잡해 보이지만, 기존 크로스 엔트로피 오차를 모두 더하고, 이를 배치 크기인 N으로 나눈 것에 불과하다.
N으로 나누어 배치 크기에 의존하지 않는 평균 교차 엔트로피 오차를 구해주었다.

 

미니 배치

그런데 MNIST 데이터셋은 훈련 데이터가 6만개였다. 그래서 모든 데이터를 대상으로 손실 함수의 합을 구하려면 시간이 좀 걸린다. 더 나아가 수천만개 이상의 빅데이터 수준이 되면 일일히 손실함수를 계산하는 것은 부하가 매우 커진다.

 

이 문제를 해결하기 위해, 머신러닝 학습 시 임의의 n장을 무작위로 추출하고, 추출한 n장의 데이터로만 1회 학습을 수행하는 것을 미니 배치(mini-batch)라고 한다.

 

 

그럼 MNIST 데이터셋을 미니 배치로 학습해보자.

이전 장에 이어 이번에도 colab 기준으로 작성되었으며, 정확한 연결 과정은 이전 장의 mnist.py 부분을 참고 부탁드립니다.

 


정상적으로 데이터를 로드했다.

  • load_mnist 호출 시 one_hot_label=True로 지정하여 원-핫 인코딩으로 출력했다.

이중에서 10장의 훈련 데이터를 무작위로 추출하려면 어떻게 해야 할까?
np.random.choice 메소드를 사용하면 손쉽게 뽑아낼 수 있다.


이후 매직 메소드를 이용해 x_batch와 t_batch를 찾아낸 것을 확인할 수 있다.

 

참고) np.random.choice()


첫번째 인자로 최댓값, 두번째 인자로 뽑아낼 수의 개수를 입력하면 무작위로 10개를 추출해 배열에 담아 리턴한다.

(배치용) cross entropy 오차 구현하기

이제 위에서 구한 데이터를 바탕으로 교차 엔트로피 오차를 계산해보자.

만약 정답 레이블이 원-핫 인코딩이 아니라 '2'나 '7'등의 숫자 레이블로 주어졌을 때의 교차 엔트로피 오차는 다음과 같이 구현할 수 있을 것이다.

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

이쯤이면 손실 함수의 정의는 이해했을 것이다.
근데 왜 굳이 손실함수를 사용해야 하는 걸까?
우리가 구하고자 하는건 결국 "높은 정확도"인데, 왜 애꿏은 "손실 함수"라는 개념을 새로 정의해서 사용하는 걸까?

 

그 이유는 다음과 같다.

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

예를 들어 생각해보자.
100장의 훈련 데이터 중 32장을 올바로 인식한다고 하면, 정확도는 32%가 될 것이다.
만약 가중치를 바꿔 한장을 더 맞췄다면? 정확도는 33%가 된다.
즉, 정확도는 이산적으로 변화한다.

 

결국 0부터 100까지의 계단 함수 형태를 띌 것이고, 미분을 통해 경사 하강법을 수행하기 어려워진다.

수치 미분

경사 하강법에서는 기울기(경사)값을 기준으로 나아갈 방향을 정한다.
그렇기 때문에, 미분부터 간단히 복습하겠다.

미분

미분은 "한 순간의 변화량"으로, 수식으로 나타내면 다음과 같다.


이를 파이썬 코드로 대강 나타내면 다음과 같을 것이다.


근데 여기서 두가지 문제가 있다.
컴퓨터의 소수점 표현 방식에는 한계가 있으며, 문자열/별도의 자료구조로 표현하지 않는 이상 이를 반올림하여 사용한다.

1e-50가 0으로 치환된 모습


따라서 위 코드는 0으로 나누는 문제가 발생하고, Error를 던지게 된다.

 

따라서 이 미세한 값 h로 10^-4를 사용해보자.

보통 10^-4정도의 작은 값이면 좋은 미분값을 얻는다고 알려져 있다.

문자열/별도의 자료구조로 표현하게 되면 연산 속도가 지나치게 오래 걸린다.

 

다음 문제는 함수 f의 "차분"과 관련된 개선이다.
현재 구한 미분 값은 근사치로, 오차를 가지고 있다. 이 오차를 좀 더 줄일 방법이 없을까?
우리가 현재 사용한 방식은 "전방 차분"으로, 아래와 같이 "중앙 차분"으로 바꾸면 좀 더 나은 미분값을 얻을 수 있다.

진정한 접선에 비해 전방 차분으로 인한 기울기의 오차가 생긴 모습.



중앙 차분은 쉽게 말해, 전방 차분은 (x+h/2) 지점의 미분값이라고 보고, 현재 지점을 차분의 정중앙으로 만들어 좀 더 근사한 미분값을 획득하도록 돕는다.

좀 더 자세한 증명은 중앙 차분을 참고 바랍니다.

 

그리하여 아래와 같이 구현을 개선하였다.

수치 미분의 예

앞 절의 수치 미분을 사용하여 간단한 함수를 미분해보자.
우선 다음과 같은 2차 함수를 가정한다.

위 함수를 파이썬으로 구현하면 다음과 같다.

이어서 이 함수를 그래프로 그려보자.

그럼 x=5일때와 x=10일 때 이 함수의 미분 근사치를 계산해보자.

대략 0.2와 0.3 이 나왔다.
해석적 해랑 비교해봤을 때, 큰 차이가 없는 것을 확인할 수 있었다.

편미분

이어서 아래 식을 편미분해보자.


코드로 나타내면 다음과 같다.

편미분이 자세히 뭔지 모르신다면, 쉽게 말해 미분을 하되, “∂f/∂x0에서 ∂ 뒤 분모(?)에 적힌 x0​만 변수로 보고, 나머지 x1​은 상수 취급한다고 보면 됩니다.
그 결과, 해당 축을 기준으로 단면으로 자른 곡선의 기울기가 계산됩니다.

위 함수는 그래프로 나타내면 아래와 같은 곡면으로 그려질 것이다.

먼저, 해당 그래프의 x_0를 증분으로 하는 편도함수는 다음과 같다.

그럼, x0=3, x1 = 4일 때, x0에 대한 편미분을 구해보자.

 

현재 구현한 numerical diff가 x값을 따로 넣어줄 수 없기 때문에, 별도의 함수를 구현했다.


계산 결과, 6으로 해석적 결과와 오차가 거이 없는것을 확인할 수 있었다.

 

이번엔 x1에 대한 편도함수를 구해보자.

이번에도 x0를 따로 넣어준 함수를 직접 구현했다.


계산 결과 8로 해석적 결과와 오차가 거이 없는 것을 확인할 수 있었다.

기울기(그라디언트, Gradient)

앞서 x0과 x1의 편미분을 변수별로 따로 저장했다. 그렇다면 x0과 x1의 편미분을 동시에 계산한걸 뭐라고 할까?
이를 그라디언트(Gradient) 라고 하며, 아래와 같이 벡터로 표현한다.


코드로 구현하면 다음과 같다.

결과가 잘 출력되는지 확인해보자.


잘 출력되는 것을 확인할 수 있었다.

 

근데 여기서 그라디언트가 뭘 가리킬 수 있을까?
기울기의 값에 -1을 곱한 벡터장을 그려보면 아래와 같다.


모든 벡터가 어느 한 지점을 가리키고 있다.
해당 지점은 (0,0)으로, 현재 주어진 함수의 최솟값을 가리키는 지점이다.

 

정말 중요한 부분이니, 반드시 이해하고 넘어가자.

그라디언트 벡터가 가리키는 쪽은 각 장소에서 함수의 출력 값을 가장 크게 줄이는 방향이다.
또한, '가장 낮은 곳'에서 멀어질수록 화살표의 크기는 커진다.

경사 하강법

위에서 얻은 아이디어를 근거로, 학습 단계에서 최적의 매개변수를 찾아낼 때 편미분 값을 사용할 수 있다.
이런 상황에서 미분값을 통해 가능한 작은 값을 찾는 기법을 경사 하강법(Gradient Descent) 이라고 한다.

이를 수식으로 표현하면 다음과 같다.

  • 여기서 η(에타, eta)는 학습률(learning rate)을 의미한다. 이 값의 중요도에 대해서는 차근차근 알아보자.

이를 코드로 구현하면 다음과 같다.

우리는 지금 이렇게 경사하강법을 구현했다.
이를 그림으로 표현하면, 아래와 같이 차근차근 (0, 0)을 향해 다가가는 모습을 상상할 수 있을 것이다.

 

하이퍼파라미터(Hyper Parameter)

우리는 위에서 학습률을 0.1로 지정해주었는데, 이 값을 변경하면 어떻게 될까?


학습률을 10으로 지정하자, 낮은 gradient로 조심스럽게 접근하지 못하고, 지나치게 크게 움직여 최종적으로 발산하는 것을 확인할 수 있었다.


그림으로 표현하면 다음과 같을 것이다.

그럼 이번엔 학습률을 매우 낮게 설정해보자.


학습률을 0.0000000001로 지정하자, 초기 값인 (-3, 4)에서 크게 이동하지 않았다.


그림으로 표현하면 다음과 같을 것이다.

 

분명 위에서 "경사 하강법은 만능이다!" 라는 느낌으로 이야기를 들었는데, 왜 올바른 값이 안나올까?

 

사실 경사 하강법에는 세가지 반례가 있다.
첫째는 안장점(saddle point), 두번째는 고원(플래토, plateau), 세번째로는 지역 최솟값(Local Minimum)이다.

안장점(saddle point)


위 그림의 빨간 점은 분명 최솟값이 아니지만, Gradient가 (0, 0)이 되는 것을 확인할 수 있다.

말 안장처럼 생겨서 안장점이라고 한다.

 

고원(플래토, plateau)와 지역 최솟값(Local Minimum)

  • plateau
    • 기울기가 낮은 완만한 곡선
    • 기울기가 작기 때문에, 학습이 정체되는 현상이 발생한다.
  • local minimum
    • 함수의 전체 최솟값이 아닌 일시적인 최솟값.
    • 최적 값이 아니기 때문에, global minimum에 비해 성능이 잘 나오지 않는다.

그렇기 때문에, 아래와 같은 하이퍼파라미터를 잘 설정해주어야 한다.

  • 학습률(Learning Rate, η)
  • epoch 수(위의 step_num)
  • etc.
    이들은 신경망의 가중치와 같은 매개변수와는 성질이 다른 매개변수로, 사람이 직접 설정해야 하는 매개변수이다.
    일반적으로 이 하이퍼파라미터들은 여러 후보 값 중에서 시험을 통해 가장 잘 학습하는 값을 찾는 과정을 거쳐야 한다.

사실 신경망의 튜닝에는 하이퍼파라미터 튜닝 뿐만이 아닌 경사 하강법과 손실함수 튜닝 등 다양한 방법이 존재한다.
지금부터 하나씩 배울 것이니 하나씩 알아가보자.

신경망에서의 기울기

이젠 신경망을 하나씩 직접 구현해보자.
해당 경사 하강법도 아래와 같은 행렬의 특징 덕분에 numpy를 이용한 연산 최적화가 가능하다.

 

여기서 중요한 점은 행렬 W와 손실함수를 W로 편미분한 행렬의 형식이 같다는 것이다.
이 덕분에 우리는 행렬 연산으로 가중치를 변경할 수 있음을 알 수 있다.

따라서 아래와 같이 식 정리가 가능하다.

이를 코드로 구현해보자.
먼저 저자가 제공한 functions.py 파일과 gradient.py 파일을 다운로드했다.

그 이후, 간단한 신경망 클래스를 구현했다.

한번 잘 돌아가는지 확인해보자.


각 함수들이 잘 돌아가는 것을 확인했다.

 

이어서 기울기를 구해보자.

파이썬의 람다를 이용해 구현했다.


잘 계산이 되는것을 확인할 수 있었다.

 

이제 매개변수를 갱신하기만 하면 된다.
매개변수를 갱신하는 과정을 구현해보자.

학습 알고리즘 구현하기 - 경사 하강법 구현

먼저 복습할 겸, 신경망 학습의 요구사항을 정리해보자.
전제

  • 신경망에는 적응 가능한 가중치와 편향이 있고, 이 가중치와 편향을 훈련 데이터에 적응하도록 조정하는 과정을 '학습'이라고 한다. 우리는 지금부터 이 학습을 구현한다.

1단계 - 미니 배치

  • 훈련 데이터 중 일부를 무작위로 가져온다.

2단계 - 기울기 산출

  • 미니배치의 손실 함수 값을 줄이기 위해 각 가중치 매개변수의 기울기를 구한다.
  • 기울기는 손실 함수의 값을 가장 작게 하는 방향을 제시한다.

3단계 - 매개변수 갱신

  • 가중치 매개변수를 기울기 방향으로 아주 조금 갱신한다.

4단계 - 반복

  • 1~3단계를 반복한다.

이와 같이 미니 배치로 경사 하강법을 구현하는 것을 Mini-batch Gradient Descent 라고 한다.
이는 확률적 경사 하강법(Stochastic Gradient Descent)의 한 종류로, 줄여서 SGD라고 불리는데, 요새는 SGD라고 하면 거의 미니-배치 그라디언트 디센트를 의미한다.

 

그럼 이제 2층 신경망을 대상으로 MNIST 데이터셋을 사용하여 학습을 진행해보자.

2층 신경망 클래스 구현하기

먼저 아래와 같이 TwoLayerNet 클래스를 구현해보자.
2층 신경망을 하나의 클래스로 구현하였다.

해당 클래스의 속성와 메소드는 다음과 같다.

한번 임의의 값을 넣어, 추론이 잘 되는지 확인해보자.


정상 출력 되는것을 확인할 수 있었다.

 

이어서 가중치 매개변수의 기울기를 구해보자.


구하는데 성공했다.

근데 기울기 계산 한번에 5분씩 걸리는데, 너무 오래걸린다고 생각하지 않는가?
이 문제를 해결하기 위해 편미분의 "chain rule"을 이용한 역전파(backpropagation) 기법을 사용하여 빠른 연산을 수행하며, 이는 다음 장에서 핵심적으로 배울 예정이다.

미니배치 학습 구현하기

그럼 이제 MNIST를 이용한 학습 로직을 구현해보자.


여기에서는 미니 배치 크기를 100으로 했다.
즉, 매번 6만개의 데이터에서 임의로 100개의 데이터를 추려내서, 해당 100개의 데이터로 SGD를 수행해 매개변수를 갱신했다.
그리고 이 과정을 1만번 반복했다.

 

이 손실 함수 값의 변화 추이는 아래와 같을 것이다.


좌측은 1만회 반복까지의 추이, 우측은 1천회 반복까지의 추이이다.

시험 데이터로 평가하기

이제 시험 데이터로 평가하는 소스코드를 작성해보자.


1 에포크마다 1번씩 정확도를 계산하도록 했다.

 

그럼 이 훈련 결과를 차근차근 살펴보자.


그래프에 표시된 결과를 해석해봤을 때, 다음 정보를 확인할 수 있었다.

  • 시험 데이터와 훈련 데이터의 성능 차이가 거의 없다.
  • 점차 정확도가 좋아지고 있다.
    따라서 이번 학습에서는 과대적합이 일어나지 않았다.

이번 장에서 배운 것

  • 머신러닝에서 사용하는 데이터셋은 훈련 데이터와 시험 데이터로 나눠 사용한다.
  • 훈련 데이터로 학습한 모델의 범용 능력을 시험 데이터로 평가한다.
  • 신경망 학습은 손실 함수를 지표로, 손실 함수의 값이 작아지는 방향으로 가중치 매개변수를 갱신한다.
  • 가중치 매개변수를 갱신할 때는 가중치 매개변수의 기울기를 이용하고, 기울어진 방향으로 가중치의 값을 갱신하는 작업을 반복한다.
  • 아주 작은 값을 주었을 때의 차분으로 미분하는 것을 수치 미분이라고 한다.
  • 수치 미분을 이용해 가중치 매개변수의 기울기를 구할 수 있다.
  • 수치 미분을 이용한 계산에는 시간이 걸리지만 구현은 간단하다. 한편, 다음 장에서 구현하는 chain rule을 이용한 역전파법은 기울기를 고속으로 구할 수 있다.