카테고리 없음

MNIST Lab 2편 - Softmax forward/backward 직접 구현하기

cedis 2026. 5. 28. 02:06

MNIST Lab 기본 구현 2편

Softmax는 모델이 만든 10개 점수를 확률처럼 바꾸는 출력층 함수다. 핵심은 각 행의 합이 1이 되게 만들고, 큰 값에서 exp가 터지지 않도록 안정화하는 것이다.

1. Softmax가 필요한 이유

모델의 마지막 Affine 출력은 아직 점수(logit)일 뿐이다. 숫자 0~9 중 무엇일 가능성이 높은지 비교하려면 이 점수를 확률처럼 바꿔야 한다.

1
logit

예: [2.0, 1.0, 0.1]처럼 정규화되지 않은 점수다.

2
exp

점수를 양수로 바꾸고 큰 점수의 영향이 더 커지게 한다.

3
normalize

각 행을 자기 행의 exp 합으로 나눠 합이 1이 되게 한다.

2. 최종 구현 코드

class Softmax:
    def forward(self, x):
        shifted = x - np.max(x, axis=1, keepdims=True)
        exp_x = np.exp(shifted)
        out = exp_x / np.sum(exp_x, axis=1, keepdims=True)
        return out

    def backward(self, dout):
        return dout
왜 최댓값을 빼는가

np.exp()는 입력이 크면 매우 쉽게 overflow가 난다. 행별 최댓값을 빼도 softmax 결과의 비율은 변하지 않으므로, 계산만 안정적으로 만든다.

3. shape 기준으로 읽기

코드 shape 의미
x (batch, classes) 각 데이터의 클래스별 점수
np.max(x, axis=1, keepdims=True) (batch, 1) 각 행의 최댓값
exp_x (batch, classes) 안정화된 exp 값
out (batch, classes) 각 행 합이 1인 확률 분포

4. 테스트가 묻는 것

테스트 확인 조건
test_softmax_forward_sum_one 각 행의 출력 합이 1인가
test_softmax_forward_non_negative 출력이 0 이상 1 이하인가
test_softmax_backward_shape backward가 gradient shape를 유지하는가
왜 backward가 그대로 반환되는가

이 과제에서는 Softmax와 Cross Entropy를 합친 미분 시작값을 train()에서 직접 만든다. 그래서 Softmax.backward()는 받은 gradient를 그대로 넘기는 통로 역할만 한다.

이번 글에서 기억할 것

Softmax는 출력 점수를 행별 확률 분포로 바꾸며, 구현에서는 overflow를 막기 위한 최댓값 보정이 중요하다.

스스로 점검

  1. Softmax 출력의 각 행 합은 왜 1이어야 하는가?
  2. 행별 최댓값을 빼도 결과가 변하지 않는 이유는 무엇인가?
  3. 이 과제에서 Softmax.backward가 짧은 이유는 무엇인가?

다음 글 예고

다음 글에서는 Affine 계층을 구현한다. Affine은 xW+b로 층의 차원을 바꾸는 핵심 계층이다.

한 줄 정리

Softmax는 출력 점수를 행별 확률 분포로 바꾸며, 구현에서는 overflow를 막기 위한 최댓값 보정이 중요하다.