본문 바로가기
C.W.K.
Stream
Lesson 03 of 05 · published

곱 규칙: 곱셈이 덧셈이 되다

~10 min · product-rule, log-likelihood, underflow

Level 0수학 초심자
0 XP0/59 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete

곱셈을 덧셈으로 옮기기

양수 에 대해 로그의 곱 규칙은

이야. 지수법칙 를 거꾸로 읽은 결과지. 확률을 많이 곱하는 계산에서는 이 규칙 덕분에 곱 전체 대신 로그확률의 합을 다룰 수 있어.

자료형에 따라 달라지는 밑돌림

예를 들어 이야. float32가 표현할 수 있는 가장 작은 양수보다 훨씬 작아서 0으로 밑돌지만, float64에서는 아직 0이 아닌 수로 표현돼. “컴퓨터가 모두 포기한다”가 아니라 자료형의 표현 범위를 넘으면 밑돌림이 생기는 거야.

로그 공간에서는 을 저장해. 이 값은 float32에서도 무리 없이 표현돼. 원래 확률이 꼭 필요할 때 다시 지수화할 수 있지만, 그 순간 결과가 자료형 범위 밖이면 역시 0이 될 수 있어. 그래서 비교·최적화·합산을 가능한 오래 로그 공간에 두는 거야.

로그우도가 등장하는 곳

  • 최대우도추정: 관측별 확률의 곱을 최대화하는 대신 로그확률의 합을 최대화해.
  • 교차엔트로피: 원-핫 분류에서는 정답 클래스의 음의 로그확률과 이어져.
  • 언어모델: 다음 토큰들의 음의 로그우도를 더해 학습 목표를 만들고, 평균 로그손실에서 perplexity도 계산해.
확률의 긴 곱은 로그확률의 합으로 바꿔. 같은 수학을 더 넓은 수치 범위에서 다룰 수 있어.

Code

확률의 곱을 로그의 합으로 바꾸기·python
import numpy as np

probs32 = np.full(100, 0.001, dtype=np.float32)
probs64 = np.full(100, 0.001, dtype=np.float64)

print(np.prod(probs32))  # 0.0: float32 범위에서 밑돌아
print(np.prod(probs64))  # 약 1e-300: float64에는 아직 남아

log_total = np.sum(np.log(probs32), dtype=np.float64)
print(log_total)         # 약 -690.776
print(np.exp(log_total)) # 약 1e-300: 여기까지는 float64로 복원 가능

# 더 긴 곱은 float64도 결국 밑돌아. 로그값은 그대로 남아.
deeper_log_total = 400 * np.log(0.001)
print(deeper_log_total)          # 약 -2763.102
print(np.exp(deeper_log_total))  # 0.0: float64에서도 밑돌아
# 그래서 계산 목적에 따라 로그값을 가능한 오래 유지해.
PyTorch가 logits를 직접 받는 이유·python
import torch

# PyTorch cross-entropy 가 이미 로그 공간에 살아
logits = torch.tensor([[2.0, 1.0, 0.5]])    # raw 모델 출력
target = torch.tensor([0])                   # 정답 클래스 인덱스

loss = torch.nn.functional.cross_entropy(logits, target)
# 내부적으로: log_softmax (logits) → target 인덱스 픽 → 음수
print(loss.item())                           # ~0.42

External links

Exercise

dtype=np.float32dtype=np.float64로 각각 0.5가 1,500개 든 배열을 만들고 곱을 계산해. 이어서 1500 * np.log(0.5)를 계산해 자료형별 결과를 비교해.
Hint
0.5^1500은 float64의 정상 범위보다도 작아. 직접 곱과 마지막 지수화는 0이 될 수 있지만 로그값 약 -1039.7은 그대로 비교하고 더할 수 있어.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 2

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.
  1. Elechemist
    Elechemist

    import numpy as np

    방법 1

    x = np.prod(np.full(1000, 0.5)) print(x)

    방법 2

    y = np.exp(1000 * np.log(0.5)) print(y)

    비교

    print(x == y)

    첫번째 방법에서는 확률을 계속 곱하기 때문에 언더플로우가 나버려서 0이되어버림

    1. 피파
      피파· warmElechemistElechemist

      맞아요, Elechemist님. 이 lesson의 핵심을 거의 정확히 잡으셨어요.

      다만 한 가지 nuance만 붙이면, 0.5 ** 1000은 float64에서는 아직 아주 작은 양수로 남을 수 있어요. 그래도 구조적으로는 말씀하신 방향이 맞아요. 확률을 계속 곱하는 방식은 금방 수치 바닥에 가까워지고, 더 긴 sequence나 더 작은 확률에서는 언더플로우로 0이 되어 버리거든요.

      그래서 log space로 바꾸면 “곱하기”가 “더하기”가 되고, 모델이 아주 작은 확률도 훨씬 안정적으로 다룰 수 있어요. 지금 실험이 바로 그 감각을 잡는 좋은 예예요.