개요

그동안 계속 미뤄둔 질문 두 개를 여기서 정리한다.

  1. 선형회귀보다 복잡한 관계는 어떻게 표현하나? → 신경망
  2. 공식으로 안 풀리면 값을 어떻게 정하나? → 경사 하강법과 역전파

첫 글에서 “학습 = 가설 공간에서 가장 덜 틀리는 함수 찾기”라고 했는데, 이번 글은 그 두 부분을 각각 넓히고 채우는 이야기다. 신경망이 가설 공간을 넓히고, 경사 하강법이 그 안에서 찾아간다.


1. 다시 출발점 — 모델은 값이 채워진 함수다

신경망으로 넘어가기 전에 관점을 하나 정리하고 간다.

단순 선형 모델은 이렇게 생겼다.

y = φ₀ + φ₁x

φ(파이)라는 새 기호가 나왔지만 겁먹을 것 없다. 앞 글에서 쓴 β와 같은 역할, 즉 우리가 조정해야 하는 값이다. 이렇게 함수의 모양은 정해져 있고 그 안의 숫자만 바꿔 쓰는 함수모수적(parametric) 함수라고 부른다.

여기서 두 가지가 확실해진다.

  • 값이 정해지면 함수가 하나 정해진다 → 예측할 수 있다
  • 훈련 데이터가 있으면 손실 함수를 정의할 수 있다 → 손실이 작아지도록 값을 조정하면 된다

그러니 학습은 결국 “손실을 가장 작게 만드는 값들을 찾는 문제”로 바뀐다. 첫 글에서 내린 정의를 한 번 더 좁힌 셈이다.


2. 얕은 신경망 — 직선을 여러 개 꺾어 붙이기

직선 모델의 한계는 명확하다. 직선밖에 못 그린다. 그럼 곡선은 어떻게 만드나?

신경망의 답이 재밌다. 직선을 여러 개 만들어서, 각각 한 번씩 꺾고, 다시 합친다.

얕은 신경망

구조는 세 층이다.

하는 일
입력층 데이터가 들어온다
은닉층 (hidden layer) 여기서 꺾인다. 이게 핵심
출력층 결과가 나온다

은닉층이 한 겹뿐인 걸 얕은(shallow) 신경망이라고 부른다.

은닉 유닛이 하는 일

은닉층의 각 유닛(h₁, h₂, h₃)은 딱 두 단계를 거친다.

  1. 입력에 각자 다른 기울기와 높이를 적용한다 (= 직선 하나 만들기)
  2. 그 결과를 활성화 함수에 통과시킨다 (= 꺾기)

가장 많이 쓰는 활성화 함수가 ReLU다. 하는 일은 어이없을 만큼 단순하다.

ReLU(z) = z가 0보다 작으면 → 0
          z가 0 이상이면   → z 그대로

음수는 전부 0으로 눌러버리고, 양수는 손대지 않는다. 그림에서 보듯 딱 한 번 꺾인 선이 된다.

왜 꺾어야 하나

이게 핵심 질문이다. 답은 이렇다.

꺾지 않으면, 아무리 층을 쌓아도 결국 직선 하나다.

직선에 직선을 통과시키면 여전히 직선이다. 층을 100개 쌓아도 마찬가지다. 활성화 함수 없는 신경망은 그냥 복잡하게 쓴 선형회귀일 뿐이다.

꺾는 순간 비로소 곡선을 만들 수 있게 된다. 활성화 함수가 하는 일은 “비선형성을 집어넣는 것” 딱 하나인데, 그게 전부를 가른다.

여기서 지난 글과 이어지는 지점이 하나 있다. 로지스틱회귀에서 쓴 시그모이드도 활성화 함수의 일종이다. 그러니 로지스틱회귀는 은닉층 없이 활성화 함수만 한 번 씌운, 가장 작은 신경망이라고 볼 수 있다. 실제로 요즘은 은닉층에 시그모이드보다 ReLU를 쓰는데, 계산이 훨씬 싸고 층이 깊어져도 학습이 잘 되기 때문이다.

더 쉬운 비유로. 직선 자만 가지고 동그라미를 그리라고 하면 못 그린다. 그런데 짧은 직선을 여러 개 꺾어서 이어붙이면 동그라미와 거의 비슷해진다. 신경망이 하는 게 정확히 그거다.


3. 유닛을 늘리면 어디까지 되나

은닉 유닛 하나가 꺾인 선 하나를 만든다면, 유닛을 늘릴수록 꺾을 수 있는 횟수가 늘어난다.

  • 유닛 3개 → 구간 몇 개짜리 거친 근사
  • 유닛 10개 → 꽤 매끄러워짐
  • 유닛 20개 → 원본과 구별이 잘 안 됨

그래서 이런 정리가 나온다.

보편적 근사 정리(Universal Approximation Theorem) 은닉 유닛을 충분히 많이 두면, 얕은 신경망은 임의의 연속 함수를 원하는 정밀도로 근사할 수 있다.

처음 봤을 때 “오 만능인걸?” 이라 생각했다. 은닉층 한 겹만으로 이론상 뭐든 표현할 수 있다는 말이니까.

그런데 여기엔 함정이 있다. “충분히 많이”가 얼마인지는 안 알려준다. 이론적으로 가능한 것과 현실적으로 만들 수 있는 건 다르다.


4. 깊은 신경망 — 넓히기보다 쌓기

얕은 신경망으로 뭐든 된다면, 왜 딥러닝은 층을 깊게 쌓을까?

효율 때문이다. 강의에서 본 비교가 인상적이었다.

구조 값의 개수 표현할 수 있는 구간 수
은닉층 2겹 (각 3유닛) 20개 최대 16개
은닉층 1겹 (6유닛) 19개 최대 7개

비슷한 개수의 값을 쓰는데 표현력은 2배 이상 차이가 난다. 옆으로 늘리는 것보다 위로 쌓는 게 훨씬 남는 장사인 것이다.

이유는 접기(folding)로 설명된다. 첫 층이 만든 꺾인 결과를 두 번째 층이 또 꺾는다. 종이를 한 번 접고 그 상태에서 또 접으면, 펼쳤을 때 접힌 자국이 2배가 아니라 훨씬 많이 생기는 것과 같다.

층을 쌓는 건 덧셈이 아니라 곱셈이다. 이 한 문장이 “왜 깊게(deep) 가는가”에 대한 답이었다.

표기가 갑자기 깔끔해지는 지점

층이 깊어지면 식이 걷잡을 수 없이 길어진다. 그래서 행렬과 벡터로 묶어서 쓴다.

h₁ = a(β₀ + Ω₀x)      ← 1층
h₂ = a(β₁ + Ω₁h₁)     ← 2층
y  = β₂ + Ω₂h₂        ← 출력
기호
Ω (오메가) 가중치 행렬 — 연결선마다 붙은 값들을 모아놓은 표
β (베타) 편향 벡터 — 각 유닛에 더해지는 기본값
a( ) 활성화 함수 (ReLU 등)

말로 옮기면 “입력에 가중치를 곱하고, 편향을 더하고, 꺾는다. 그걸 층마다 반복한다.” 층이 100개여도 이 문장은 그대로다.

여기가 개인적으로 반가웠던 부분이다. 결국 신경망의 정체는 행렬 곱셈의 반복이다.

얼마 전 NumPy를 정리하면서 “반복문 대신 배열 통째로 계산하는 게 왜 훨씬 빠른가”를 봤는데, 그 벡터화가 여기서 그대로 이어진다. 신경망 한 층의 계산은 파이썬 for문으로 유닛을 하나씩 도는 게 아니라, 행렬 곱 한 방이다. 그리고 행렬 곱은 각 칸을 서로 상관없이 동시에 계산할 수 있다.

GPU가 딥러닝에 쓰이는 이유가 여기서 설명된다. 순서를 지켜야 하는 일은 CPU가 빠르지만, 서로 독립인 계산 수천 개를 한꺼번에 하는 데는 GPU가 압도적이다. 신경망 계산이 정확히 그 모양이다.


5. 이제 진짜 문제 — 값은 어떻게 정하나

구조는 알았다. 그런데 그 안에 들어갈 수많은 값(가중치)을 어떻게 정하나?

선형회귀 편에서 봤듯 단순선형회귀는 공식으로 한 번에 풀렸다. 하지만 신경망은 공식이 없다. 로지스틱회귀부터 이미 없었다.

그래서 다른 전략을 쓴다. 한 번에 못 찾으면, 조금씩 나은 쪽으로 옮겨가면 된다.

준비운동 — 미분은 기울기다

경사 하강법을 이해하려면 미분이 필요한데, 알아야 할 건 딱 하나다.

미분값 = 그 지점에서의 기울기 = “지금 어느 쪽으로 얼마나 가파른가”

포물선 위에서 미분값을 보면 이렇게 읽힌다.

위치 미분값
최저점 오른쪽 양수 오른쪽으로 가면 올라간다
최저점 왼쪽 음수 오른쪽으로 가면 내려간다
최저점 0 어느 쪽으로도 평평하다

그리고 기울기의 절댓값이 클수록 더 가파르다.

여기서 결론이 자동으로 나온다. 미분값의 반대 방향으로 가면 내려간다.


6. 경사 하강법

경사 하강법(Gradient Descent) = 손실 함수의 기울기를 구하고, 그 반대 방향으로 값을 조금씩 옮기는

경사 하강법

절차는 두 단계의 반복이다.

  1. 현재 위치에서 손실의 기울기를 계산한다
  2. 기울기의 반대 방향으로 한 걸음 옮긴다

이걸 손실이 더는 안 줄어들 때까지 반복한다.

더 쉬운 비유로. 안개 낀 산에서 눈을 감고 내려오는 것과 같다. 앞이 안 보이니 발밑을 더듬어 어느 쪽이 내리막인지만 알아낸 다음, 그쪽으로 한 걸음 옮긴다. 이걸 계속 반복하면 언젠가 골짜기에 닿는다.

학습률 — 보폭을 정하는 값

한 걸음을 얼마나 크게 뗄지 정하는 값이 학습률(learning rate)이다. 이게 생각보다 훨씬 까다롭다.

학습률 결과
너무 크면 최저점을 지나쳐 반대편으로 튕긴다. 심하면 발산
너무 작으면 조금씩만 움직여서 끝없이 오래 걸린다

위 그림의 오른쪽이 학습률이 큰 경우다. 골짜기를 넘어 반대편 벽으로 튕기고, 다시 더 세게 튕겨 나간다. 학습이 되기는커녕 점점 나빠진다.

산을 내려오는데 한 걸음이 100m라면, 골짜기를 훌쩍 넘어 건너편 산중턱에 착지한다. 반대로 한 걸음이 1cm라면 해가 질 때까지 못 내려온다.

학습률은 사람이 정해줘야 하는 값이라, 딥러닝에서 가장 손이 많이 가는 설정 중 하나다.


7. 골짜기가 하나가 아닐 때

경사 하강법에는 치명적인 약점이 있다. 손실 함수의 모양에 따라 난이도가 완전히 달라진다.

Convex와 Non-convex

  Convex (볼록) Non-convex
모양 U자처럼 아래로 볼록 봉우리와 웅덩이가 섞임
최저점 하나뿐 여러 개
결과 어디서 출발해도 같은 답 출발점에 따라 다른 답

Convex 문제는 마음이 편하다. 내려가다 보면 무조건 정답에 도착한다.

문제는 신경망의 손실 함수는 대부분 non-convex라는 것이다. 웅덩이(지역 최솟값)에 빠지면, 주변이 다 오르막이라 더 갈 데가 없어 보인다. 실제로는 저 멀리 진짜 골짜기가 있는데도.

이 개념, 교차검증 편의 K-means에서 이미 만났다. 초기값에 따라 결과가 달라진다던 그 문제와 정확히 같다. 이름만 다르지 구조가 똑같다.


8. 확률적 경사 하강법(SGD)

지금까지 설명한 방식에는 두 가지 문제가 있다.

  1. 매 걸음마다 전체 데이터의 기울기를 계산한다 → 데이터가 100만 개면 한 걸음에 100만 번 계산
  2. 웅덩이에 빠지면 못 나온다

해결책은 어이없을 만큼 간단하다.

전체 데이터를 다 보지 말고, 무작위로 뽑은 일부(미니배치)만 보고 걸음을 정하자.

이게 확률적 경사 하강법(SGD)이다.

두 가지가 동시에 해결된다

① 계산이 싸진다

100만 개 대신 32개만 보고 한 걸음을 정한다. 한 걸음의 비용이 확 줄어서, 같은 시간에 훨씬 많이 걸을 수 있다.

② 웅덩이에서 빠져나온다

이게 훨씬 흥미로운 부분이다. 일부만 보고 정한 방향은 정확하지 않다. 뽑힌 데이터에 따라 방향이 조금씩 흔들린다.

그런데 이 흔들림이 오히려 도움이 된다. 웅덩이에 빠져도 흔들리다 보면 툭 튀어나올 수 있기 때문이다.

눈 감고 산을 내려오는데 가끔 발을 헛디딘다고 하자. 평소엔 손해지만, 작은 웅덩이에 빠졌을 때는 그 헛디딤 덕분에 밖으로 나오게 된다.

부정확함이 약점이 아니라 무기가 되는 구조라는 게 인상적이었다. 정확한 방향으로만 가면 갇히고, 대충 가면 빠져나온다.

정리하면

  경사 하강법 (전체) SGD (일부)
한 걸음에 쓰는 데이터 전부 무작위 일부
한 걸음 비용 비쌈
이동 경로 매끄러움 지그재그
웅덩이 탈출 어려움 상대적으로 쉬움

주의 — SGD가 항상 더 빠른 건 아니다. Convex 문제처럼 모양이 좋은 경우엔 전체를 보는 쪽이 더 빨리 수렴하기도 한다. “SGD가 더 빠르다”는 말은 조건부다.


9. 옵티마이저 — Adam은 뭘 더 해주나

여기까지 오면 걸음을 정하는 규칙이 하나뿐이었다. 기울기 반대 방향으로 학습률만큼 간다. 그런데 이 단순한 규칙에는 아직 불편한 점이 남아 있다.

  • 학습률을 사람이 정해야 한다. 크면 튕기고 작으면 하염없이 걸린다
  • 모든 값에 같은 보폭을 쓴다. 어떤 값은 크게 움직여야 하고 어떤 값은 미세 조정만 필요한데도
  • 골짜기가 길쭉하면 지그재그가 된다. 좁은 쪽 벽을 계속 왔다 갔다 하느라 정작 목표 쪽으로는 조금씩만 나아간다

옵티마이저 비교

왼쪽 그림이 마지막 문제다. 좁은 방향은 기울기가 가파르니 크게 튕기고, 넓은 방향은 완만하니 찔끔 움직인다. 가야 할 방향으로는 거의 못 가면서 옆으로만 요동친다.

이 걸음 규칙을 개선한 것들을 옵티마이저(optimizer)라고 부른다. 개선 아이디어는 크게 두 갈래다.

① 모멘텀 — 관성을 준다

이번 걸음을 이번 기울기만 보고 정하지 않는다. 직전까지 가던 방향을 일정 비율로 이어받는다.

이번 이동 = (직전에 가던 방향 × 관성) + (이번 기울기 × 학습률)

효과가 두 방향으로 나온다.

  • 같은 방향이 반복되면 점점 빨라진다 — 계속 내리막이면 가속이 붙는다
  • 왔다 갔다 하는 성분은 서로 상쇄된다 — 지그재그가 저절로 줄어든다

더 쉬운 비유로. 매 걸음 멈춰서 발밑을 확인하고 다시 딛는 게 기본 경사 하강법이라면, 모멘텀은 비탈에서 공을 굴리는 것에 가깝다. 공은 이미 가던 방향으로 계속 가려 하기 때문에, 작은 웅덩이 정도는 관성으로 그냥 넘어간다.

② 적응적 학습률 — 값마다 보폭을 다르게

모든 값에 같은 보폭을 쓰는 대신, 각 값이 그동안 얼마나 크게 움직였는지를 기억해두고 보폭을 조절한다.

  • 최근에 기울기가 크게 나온 값 → 이미 많이 흔들렸으니 보폭을 줄인다
  • 최근에 거의 안 움직인 값 → 아직 자리를 못 잡았으니 보폭을 키운다

이렇게 하면 사람이 학습률 하나만 대충 정해줘도, 세부 보폭은 알아서 맞춰진다.

Adam = 둘을 합친 것

Adam(Adaptive Moment Estimation) = 모멘텀 + 적응적 학습률

지금 딥러닝에서 기본값처럼 쓰이는 옵티마이저다. 흔히 쓰는 설정은 이렇다.

흔히 쓰는 기본값 역할
학습률 0.001 전체 보폭의 기준
β₁ 0.9 관성을 얼마나 유지할지
β₂ 0.999 보폭 조절에 과거를 얼마나 반영할지

β 값들은 특별한 이유가 없으면 거의 건드리지 않는다. 실제로 조정하는 건 대개 학습률 하나다.

  걸음 정하는 방식 특징
기본 경사 하강법 기울기 × 고정 학습률 단순. 학습률에 아주 민감
모멘텀 추가
  • 직전 방향의 관성
지그재그 감소, 가속
Adam
  • 값마다 보폭 자동 조절
손이 덜 간다. 초기 수렴이 빠름

그렇다고 만능은 아니다

Adam이 기본값처럼 쓰이니 “무조건 Adam”으로 생각하기 쉬운데, 그렇지는 않다고 한다.

  • 초반에 빠르게 내려가는 건 대체로 Adam이 낫다
  • 하지만 잘 조정한 모멘텀 방식이 최종 일반화 성능에서 더 좋게 나오는 경우도 많다

그래서 실무에서는 Adam으로 빠르게 시작하고, 성능을 더 짜내야 할 때 다른 방식으로 바꿔 비교하는 식으로 쓴다. 여기서도 결국 같은 결론이다. 기본값은 출발점이지 정답이 아니다.


10. 역전파 — 기울기를 실제로 구하는 방법

마지막 조각이 남았다. 경사 하강법은 “기울기를 구해서 반대로 간다”인데, 층이 여러 겹인 신경망에서 그 기울기를 어떻게 구하나?

문제는 이렇다. 첫 번째 층의 가중치 하나를 바꾸면, 그 영향이 2층 → 3층 → 출력 → 손실까지 줄줄이 퍼진다. “이 값 하나가 최종 손실에 얼마나 영향을 주는가”를 어떻게 계산하나?

연쇄법칙

답은 연쇄법칙(chain rule)이다. 핵심 아이디어는 한 줄로 요약된다.

전체 변화율 = 바깥쪽 변화율 × 안쪽 변화율

말로 풀면 이렇다.

톱니바퀴 A가 B를 돌리고, B가 C를 돌린다고 하자. A를 1만큼 돌리면 C는 얼마나 도나? → (A가 B를 돌리는 비율) × (B가 C를 돌리는 비율)

곱하기만 하면 된다. 층이 몇 겹이든 마찬가지다. 각 단계의 변화율을 구해서 쭉 곱하면 된다.

그래서 거꾸로 간다

순전파와 역전파

연쇄법칙을 쓰려면 뒤쪽부터 계산하는 게 훨씬 효율적이다. 그래서 학습 한 걸음은 왕복 구조가 된다.

방향 이름 하는 일
앞으로 → 순전파(forward) 입력을 층마다 통과시켜 예측을 만들고, 정답과 비교해 손실을 구한다
뒤로 ← 역전파(backward) 손실에서 출발해 거꾸로 돌아오며, 각 값이 손실에 준 영향(기울기)을 계산한다

그리고 구한 기울기로 값들을 조금 옮긴다. 이 왕복 한 번이 학습의 한 걸음이다.

더 쉬운 비유로. 여러 사람이 릴레이로 그림을 이어 그렸는데 결과가 이상하다고 하자. 순전파는 앞사람부터 순서대로 그려나가는 것, 역전파마지막 사람부터 거꾸로 물어보며 “여기서 얼마나 어긋났지?”를 따져 올라가는 것이다. 그래야 누구 탓인지 정확히 알 수 있다.

“역전파”라는 이름이 거창해서 뭔가 대단한 게 있나 했는데, 결국 미분의 연쇄법칙을 뒤에서부터 순서대로 적용하는 것이었다. 개념 자체는 오히려 단순하다.


전체 그림 — 학습 한 걸음

여기까지 배운 걸 하나로 이으면 이렇게 된다.

  1. 데이터에서 미니배치를 뽑는다 (SGD)
  2. 순전파 — 예측을 만들고 손실을 계산한다
  3. 역전파 — 각 값이 손실에 준 영향(기울기)을 구한다
  4. 옵티마이저가 그 기울기로 값을 옮긴다 (Adam이면 관성과 보폭 조절까지 함께)
  5. 1번으로 돌아간다

그리고 이 루프를 데이터 전체에 대해 한 바퀴 돈 것을 1 에폭(epoch)이라고 부른다.

첫 글에서 그렸던 데이터 → 모델 → 학습 → 평가 루프의 “학습” 칸 안을 확대하면 정확히 이게 들어 있다. 결국 처음 그림으로 돌아온 셈이다.


정리

  • 모델 = 모양이 정해진 함수 + 조정 가능한 값들. 학습은 그 값을 찾는 문제
  • 활성화 함수(ReLU)가 직선을 꺾어 비선형성을 만든다. 없으면 층을 쌓아도 직선 하나
  • 보편적 근사 정리 — 유닛이 충분하면 얕은 신경망도 뭐든 근사할 수 있다. 단 “충분히”가 문제
  • 깊게 쌓는 게 넓히는 것보다 효율적이다. 층을 쌓는 건 덧셈이 아니라 곱셈
  • 신경망의 정체는 행렬 곱셈의 반복. GPU를 쓰는 이유가 여기 있다
  • 경사 하강법 = 기울기의 반대 방향으로 조금씩 이동. 학습률이 보폭
  • 신경망의 손실은 대부분 non-convex — 웅덩이(지역 최솟값)에 빠질 수 있다
  • SGD는 일부만 보고 걷는다 → 싸고, 흔들림 덕분에 웅덩이에서 잘 빠져나온다
  • 모멘텀은 관성으로 지그재그를 줄이고, 적응적 학습률은 값마다 보폭을 조절한다
  • Adam = 모멘텀 + 적응적 학습률. 기본값처럼 쓰이지만 항상 최선은 아니다
  • 역전파 = 연쇄법칙을 뒤에서부터 적용해 기울기를 구하는 절차
  • 학습 한 걸음 = 순전파 → 역전파 → 값 갱신

다음 글에서

여기까지가 지금까지 정리한 범위다. 학습이 더 쌓이는 대로 이 시리즈에 이어서 추가할 예정이다.

한줄 평

  • 몰?루 대신 렐?루 학습 방식에 대한 기초적인 큰 흐름을 알게되어 유익한 강의였다! (아직 끝이 아니다)