포스트

MLP와 역전파 — 신경망이 함수를 배우는 방법

MLP와 역전파 — 신경망이 함수를 배우는 방법

서론

3D Gaussian Splatting 글을 쓰고 나서, 그 글이 설명 없이 지나간 것들이 눈에 밟혔습니다. “장면을 MLP 하나로 표현한다”, “Adam으로 갱신한다”, “전 과정이 미분 가능하다”, “SH 계수로 색을 둔다” — 각각이 따로 한 편씩 필요한 이야기입니다.

그래서 3DGS를 읽기 위한 기초지식 시리즈를 시작합니다. 이 글이 1편입니다.

첫 주제는 MLP(다층 퍼셉트론)와 역전파입니다. NeRF는 장면 하나를 통째로 MLP의 가중치에 집어넣는 방법이었고, 3DGS는 MLP를 걷어냈지만 “손실을 파라미터까지 역전파해서 갱신한다”는 학습 구조는 그대로 물려받았습니다. 그 구조가 어떻게 돌아가는지가 이 글의 내용입니다.

한 문장으로 미리 요약하면 이렇습니다.

신경망은 단순한 함수를 겹쳐 복잡한 함수를 만드는 장치이고, 역전파는 그 겹침을 거꾸로 한 번 훑어 모든 파라미터의 기울기를 한꺼번에 얻는 계산 요령이다.


1. 뉴런 하나는 이미 알고 있는 것이다

뉴런 하나가 하는 일은 이렇습니다. 입력 $x \in \mathbb{R}^d$ 를 받아, 가중합을 내고, 거기에 함수 하나를 씌웁니다.

\[z = w^\top x + b, \qquad a = \phi(z)\]

$\phi$ 를 시그모이드로 두면 이건 로지스틱 회귀와 정확히 같은 모델입니다. 이 블로그에서 L1·L2 규제를 다룰 때 나온 그 선형 모델입니다.

그래서 뉴런 하나의 한계도 로지스틱 회귀의 한계와 같습니다. $\phi$ 가 단조 함수라면 $a$ 의 등고선은 $z$ 의 등고선과 같고, $z$ 는 $x$ 의 선형 함수이므로 결정 경계는 언제나 초평면 하나입니다. 아무리 오래 학습해도 직선은 직선입니다.


2. 층을 쌓는다는 것

그러면 층을 쌓아 봅니다. 은닉층 하나짜리 신경망은 이렇게 씁니다.

\[z_1 = W_1 x + b_1, \quad a_1 = \phi(z_1), \quad z_2 = W_2 a_1 + b_2, \quad \hat{y} = \sigma(z_2)\]

여기서 $\phi$ 가 비선형이라는 점이 전부입니다. 만약 $\phi$ 가 항등함수라면

\[z_2 = W_2(W_1 x + b_1) + b_2 = (W_2 W_1)x + (W_2 b_1 + b_2)\]

이 되어, 층을 백 개 쌓아도 결국 행렬 하나짜리 선형 모델로 접힙니다. 깊이가 표현력을 주는 게 아니라, 층 사이에 끼인 비선형이 표현력을 줍니다.

은닉층 유무에 따른 결정 경계 비교 같은 데이터, 같은 이진 교차엔트로피 손실. 왼쪽은 은닉층 없는 선형 모델이라 직선 하나로밖에 못 나눈다. 오른쪽은 뉴런 16개짜리 은닉층 하나를 넣은 것뿐인데 경계가 데이터 모양을 따라 휜다.

왜 이렇게 되는지는 직관적으로 볼 수 있습니다. 은닉 뉴런 하나하나가 자기만의 방향으로 공간을 자르는 “부드러운 계단”이고, 출력층은 그 계단들을 가중합합니다. 계단을 충분히 많이 준비해 적당한 높이로 더하면 거의 아무 모양이나 만들 수 있습니다. 이걸 엄밀하게 정리한 것이 보편 근사 정리(universal approximation theorem)입니다.12

은닉층 하나에 뉴런을 충분히 두면, 콤팩트 집합 위의 연속함수를 원하는 정밀도로 근사할 수 있다.

다만 이 정리는 “존재한다”만 말할 뿐, 그런 가중치를 어떻게 찾는지도, 뉴런이 몇 개 필요한지도 말해 주지 않습니다. 실무에서 층을 깊게 쌓는 이유는 정리 때문이 아니라, 같은 함수를 표현하는 데 깊은 쪽이 훨씬 적은 뉴런으로 끝나는 경우가 많기 때문입니다.


3. 활성함수 — 무엇을 끼울 것인가

함수미분성격
시그모이드$\sigma(z) = 1/(1+e^{-z})$$\sigma(1-\sigma)$출력이 $(0,1)$. 최대 기울기 0.25
$\tanh$$(e^z-e^{-z})/(e^z+e^{-z})$$1-\tanh^2$출력이 $(-1,1)$, 0 중심
ReLU$\max(0, z)$$0$ 또는 $1$양수 구간 기울기가 1로 유지

초기 신경망은 시그모이드를 썼습니다. 문제는 미분값이 최대 0.25라는 점입니다. 층을 $L$ 개 통과하면 기울기에 이 값이 $L$ 번 곱해지고, $0.25^{10} \approx 10^{-6}$ 이 됩니다. 앞쪽 층은 사실상 학습되지 않습니다. 이것이 기울기 소실(vanishing gradient) 입니다.

ReLU는 양수 구간에서 미분이 정확히 1이라 이 곱셈이 기울기를 줄이지 않습니다. 계산도 비교 한 번이면 끝납니다. 대신 음수 구간의 미분이 0이라 뉴런이 죽는 문제가 있고, 그래서 Leaky ReLU, GELU 같은 변형이 나왔습니다.

NeRF가 쓰는 것. NeRF의 MLP는 ReLU 8층입니다. 다만 입력이 3차원 좌표 하나뿐이라, 좌표를 그대로 넣으면 부드러운 함수밖에 못 배웁니다. 그래서 좌표를 고주파 사인·코사인으로 펼쳐 넣는 위치 인코딩을 붙입니다. 이 이야기는 9편 NeRF에서 합니다.


4. 손실 — 무엇을 줄일 것인가

모델이 함수라면, 학습은 “그 함수가 데이터를 얼마나 못 맞히는지”를 재는 스칼라 $L$ 을 정하고 그걸 줄이는 일입니다.

\[L_{\text{MSE}} = \frac{1}{N}\sum_i (\hat{y}_i - y_i)^2, \qquad L_{\text{BCE}} = -\frac{1}{N}\sum_i \big[y_i \log \hat{y}_i + (1-y_i)\log(1-\hat{y}_i)\big]\]

이진 분류에 BCE를 쓰는 데는 계산상의 이유도 있습니다. 시그모이드 출력에 BCE를 씌우면 두 미분이 서로 상쇄돼

\[\frac{\partial L}{\partial z_2} = \hat{y} - y\]

라는, 놀랄 만큼 깔끔한 식이 나옵니다. 예측이 정답에서 벗어난 만큼이 그대로 기울기가 됩니다.

3DGS와 NeRF가 쓰는 손실도 형태만 다를 뿐 하는 일은 같습니다. 렌더된 이미지와 실제 사진 사이의 $L_1$ 오차에 구조적 유사도(D-SSIM)를 섞은 스칼라 하나입니다.


5. 역전파 — 연쇄법칙을 한 방향으로 정리한 것

이제 핵심입니다. 파라미터가 수백만 개일 때 $\partial L / \partial W$ 를 전부 구해야 하는데, 어떻게 해야 현실적인 시간에 끝날까요.

가장 순진한 방법은 수치 미분입니다. 파라미터 하나를 $\epsilon$ 만큼 흔들고 손실을 다시 계산합니다. 파라미터가 $P$ 개면 순전파를 $P$ 번 돌려야 합니다. $P$ 가 백만이면 그냥 불가능합니다.

역전파는 순전파 한 번 + 역방향 한 번으로 $P$ 개 기울기를 전부 얻습니다. 비용이 파라미터 수에 비례하지 않습니다3

역전파 계산 그래프 순전파는 값을 앞으로 흘리며 중간값($z_1, a_1, z_2, \hat{y}$)을 저장해 둔다. 역전파는 손실에서 출발해 국소 미분을 곱해 가며 기울기를 뒤로 흘린다. 파라미터의 기울기는 그 자리를 지나가는 기울기와 국소 미분의 곱 하나로 얻어진다.

5.1 손으로 한 번 유도하기

연쇄법칙을 출력 쪽부터 적용합니다. 편의상 $\delta_2 \equiv \partial L / \partial z_2$, $\delta_1 \equiv \partial L / \partial z_1$ 로 둡니다.

\[\delta_2 = \hat{y} - y\]

$z_2 = W_2 a_1 + b_2$ 이므로, $W_2$ 와 $a_1$ 에 대한 기울기가 바로 나옵니다.

\[\frac{\partial L}{\partial W_2} = \delta_2\, a_1^\top, \qquad \frac{\partial L}{\partial b_2} = \delta_2, \qquad \frac{\partial L}{\partial a_1} = W_2^\top \delta_2\]

한 층 더 내려갑니다. $a_1 = \phi(z_1)$ 이므로 원소별 미분을 곱합니다.

\[\delta_1 = \left(W_2^\top \delta_2\right) \odot \phi'(z_1)\] \[\frac{\partial L}{\partial W_1} = \delta_1\, x^\top, \qquad \frac{\partial L}{\partial b_1} = \delta_1\]

패턴이 보입니다. 층마다 하는 일은 딱 두 가지입니다.

  1. 위에서 내려온 기울기를 받아 내 파라미터의 기울기를 만든다 ($\delta\, a^\top$).
  2. 그 기울기를 아래층에 넘겨준다 ($W^\top \delta \odot \phi’$).

층이 몇 개든, 무엇을 하는 층이든 이 두 규칙만 정의하면 됩니다. 딥러닝 프레임워크가 임의의 모델을 자동으로 미분하는 근거가 이것입니다.

5.2 왜 하필 “역”방향인가

연쇄법칙은 방향이 없습니다. 앞에서부터 곱해 나가도(전방 모드) 답은 같습니다. 그런데 왜 역방향일까요.

기울기를 야코비 행렬의 곱으로 보면 답이 보입니다. 입력 $P$ 차원, 출력 1차원(손실은 스칼라)인 함수라면,

  • 전방 모드: 입력 방향 하나씩 밀어 넣어야 하므로 $P$ 번 훑어야 한다.
  • 역방향 모드: 출력이 하나뿐이므로 한 번이면 끝난다.

딥러닝은 언제나 “파라미터는 엄청 많고 손실은 스칼라 하나”인 구조입니다. 역방향이 이길 수밖에 없는 판입니다. 대가는 메모리입니다. 역방향에서 $\phi’(z_1)$ 같은 값이 필요하므로 순전파의 중간값을 전부 들고 있어야 합니다. 딥러닝 학습이 추론보다 GPU 메모리를 훨씬 많이 먹는 이유가 여기 있습니다4


6. 40줄로 직접 만들어 보기

프레임워크 없이 넘파이만으로 씁니다. 위에서 유도한 식이 그대로 코드가 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import numpy as np

def sigmoid(z):
    return 1.0 / (1.0 + np.exp(-z))

class MLP:
    """은닉층 1개, tanh 활성, 시그모이드 출력, BCE 손실."""

    def __init__(self, d_in, d_hidden, seed=0):
        r = np.random.default_rng(seed)
        # 입력 차원으로 스케일을 나눠 초기 활성이 포화되지 않게 (Xavier 초기화)
        self.W1 = r.normal(0, 1 / np.sqrt(d_in), (d_in, d_hidden))
        self.b1 = np.zeros(d_hidden)
        self.W2 = r.normal(0, 1 / np.sqrt(d_hidden), (d_hidden, 1))
        self.b2 = np.zeros(1)

    def forward(self, X):
        self.X  = X
        self.z1 = X @ self.W1 + self.b1
        self.a1 = np.tanh(self.z1)            # 역전파에서 다시 쓴다
        self.z2 = self.a1 @ self.W2 + self.b2
        return sigmoid(self.z2)

    def backward(self, y_hat, y, lr):
        n = len(y)
        d2 = (y_hat - y) / n                       # dL/dz2  (시그모이드+BCE 상쇄)
        gW2, gb2 = self.a1.T @ d2, d2.sum(0)

        d1 = (d2 @ self.W2.T) * (1 - self.a1 ** 2)  # dL/dz1  ← 연쇄법칙 한 칸
        gW1, gb1 = self.X.T @ d1, d1.sum(0)

        for p, g in ((self.W1, gW1), (self.b1, gb1),
                     (self.W2, gW2), (self.b2, gb2)):
            p -= lr * g                             # 가장 단순한 경사하강

backward가 순전파의 역순으로 딱 한 번 흐르고, 그 한 번에 네 파라미터의 기울기가 전부 나옵니다. 이게 역전파의 전부입니다.

기울기가 맞는지 확인하는 표준적인 방법은 수치 미분과 대조하는 것입니다. 느려서 학습에는 못 쓰지만 검산에는 확실합니다.

1
2
3
4
5
6
7
def grad_check(f, p, idx, eps=1e-6):
    """f: 파라미터를 읽어 손실을 돌려주는 함수. p[idx] 하나만 흔들어 본다."""
    orig = p[idx]
    p[idx] = orig + eps; hi = f()
    p[idx] = orig - eps; lo = f()
    p[idx] = orig
    return (hi - lo) / (2 * eps)      # 역전파가 준 값과 상대오차 1e-7 수준이면 정상

7. 자주 나오는 질문들

Q1. 층을 깊게 하면 항상 좋아지나?

아닙니다. 깊어질수록 기울기가 소실되거나 발산하기 쉽고, 학습이 아예 진행되지 않는 구간이 생깁니다. 잔차 연결(ResNet), 정규화 층, ReLU 계열 활성은 전부 “깊어도 기울기가 살아서 내려오게 하는” 장치들입니다. 참고로 NeRF의 MLP는 8층으로, 요즘 기준으로는 얕은 편입니다.

Q2. 은닉 뉴런은 몇 개가 적당한가?

보편 근사 정리는 “충분히 많으면 된다”고만 합니다. 실무에서는 데이터 양과 과적합 사이에서 고릅니다. 이 판단은 L1·L2 규제 글에서 다룬 편향–분산 절충과 같은 이야기이고, 베이지안 관점에서는 사전분포와 귀납 편향의 문제이기도 합니다.

Q3. 왜 가중치를 0으로 초기화하면 안 되나?

모든 은닉 뉴런이 같은 값을 받고 같은 기울기를 받아 영원히 같은 값으로 움직이기 때문입니다. 뉴런 16개를 두어도 실질적으로 1개짜리 신경망이 됩니다. 대칭을 깨려면 무작위 초기화가 필요하고, 그 스케일을 입력 차원으로 조절하는 것이 Xavier·He 초기화입니다.

Q4. 3DGS에는 MLP가 없는데, 이 글이 왜 필요한가?

3DGS가 학습하는 대상은 MLP 가중치가 아니라 가우시안의 위치·스케일·회전·불투명도·SH 계수입니다. 하지만 “손실을 정의하고, 그 손실을 파라미터까지 역전파하고, 기울기로 갱신한다”는 골격은 완전히 동일합니다. 달라지는 건 중간에 낀 것이 신경망 층이 아니라 렌더러라는 점뿐이고, 그래서 렌더러가 미분 가능해야 합니다. 이 이야기가 8편 미분 가능 렌더링입니다.

Q5. 그럼 신경망 없이도 역전파를 쓸 수 있나?

그렇습니다. 역전파는 신경망 전용 알고리즘이 아니라 연쇄법칙의 역방향 모드 자동미분(reverse-mode AD) 이고, 미분 가능한 연산의 합성이라면 무엇에든 적용됩니다. 이 블로그에서 다룬 번들 조정의 야코비 계산도 같은 뿌리입니다. 3DGS는 이 사실을 아주 직접적으로 이용한 사례입니다.


마치며

  • 뉴런 하나는 선형 모델이고, 비선형 활성이 층 사이에 끼어야 층을 쌓은 보람이 있다.
  • 보편 근사 정리는 “표현할 수 있다”만 말한다. 찾을 수 있다는 별개 문제이고, 그걸 담당하는 게 기울기다.
  • 역전파는 순전파 한 번과 역방향 한 번으로 모든 파라미터의 기울기를 얻는다. 손실이 스칼라이기 때문에 역방향이 유리하다.
  • 대가는 메모리다. 중간값을 전부 저장해야 한다.

기울기를 얻었으니 다음은 그걸로 어떻게 움직일 것인가입니다. 단순 경사하강은 잘 안 됩니다. 3DGS가 위치·스케일·불투명도·SH를 한 옵티마이저로 굴릴 수 있는 이유가 바로 다음 글의 주제입니다.


이 시리즈 — 3DGS를 읽기 위한 기초지식

  1. MLP와 역전파 ← 지금 읽는 글
  2. SGD에서 Adam까지
  3. SfM 파이프라인
  4. MVS — 밀집 복원
  5. 래스터화
  6. 알파 블렌딩과 볼륨 렌더링
  7. 구면 조화 함수
  8. 미분 가능 렌더링
  9. NeRF
  10. 3D Gaussian Splatting ← 도착지

참고문헌

  1. Cybenko, G. (1989). Approximation by superpositions of a sigmoidal function. Mathematics of Control, Signals and Systems, 2(4), 303–314. ↩︎

  2. Hornik, K. (1991). Approximation capabilities of multilayer feedforward networks. Neural Networks, 4(2), 251–257. ↩︎

  3. Rumelhart, D. E., Hinton, G. E., & Williams, R. J. (1986). Learning representations by back-propagating errors. Nature, 323, 533–536. — 역전파를 신경망 학습에 대중화한 논문. ↩︎

  4. Goodfellow, I., Bengio, Y., & Courville, A. (2016). Deep Learning. MIT Press. — 6장 순전파 신경망, 6.5절 역전파와 자동미분. ↩︎

이 기사는 저작권자의 CC BY-NC-ND 4.0 라이센스를 따릅니다.

License

CC BY-NC-ND 4.0

이 포스트는 CC BY-NC-ND 4.0 라이선스로 제공됩니다. 출처 표시는 필요하며, 상업적 이용과 변경은 허용되지 않습니다.