C.W.K.
Stream
Lesson 03 of 05 · published

The Product Rule: Multiplication Becomes Addition

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

Level 0Math Novice
0 XP0/59 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete

The Spell That Saved AI From Itself

The most important log identity for AI:

Read it again. Multiplication on the left, addition on the right. Logs let you trade hard operations (multiplying many tiny floats) for easy ones (adding their logs). This is not a notational convenience — it's an engineering miracle.

Numerical Underflow: The Bug This Identity Solves

Float32 represents numbers down to about . Multiply 100 probabilities of value 0.001 each:

The result is mathematically tiny but not zero. Float32 rounds it to zero anyway. Now your loss is and your training crashes.

Take logs first: . A perfectly normal float. Sum the log-probabilities instead of multiplying probabilities. Done.

Log-Likelihood Everywhere

This is why almost every probabilistic ML loss involves logs:

  • Cross-entropy loss: — sums log-probabilities of true classes.
  • Maximum likelihood estimation: maximize , not .
  • Language model training: minimize negative log-likelihood of the next token, summed over millions of tokens.
If you ever have to multiply many probabilities, take logs first and sum instead. The math is identical; the numerics are wildly better.

Code

Why logs aren't optional·python
import numpy as np

probs = np.array([0.001] * 100)

# The naive way — underflows
direct = np.prod(probs)
print(direct)            # 0.0  — float32/64 gave up

# The log-likelihood way — works perfectly
log_total = np.sum(np.log(probs))
print(log_total)         # -690.776...
print(np.exp(log_total)) # 1e-300 — only computable as a log
Why PyTorch loves logits·python
import torch

# PyTorch's cross-entropy loss already lives in log-space
logits = torch.tensor([[2.0, 1.0, 0.5]])    # raw model outputs
target = torch.tensor([0])                   # correct class index

loss = torch.nn.functional.cross_entropy(logits, target)
# Internally: log_softmax (logits) → pick the target index → negate
print(loss.item())                           # ~0.42

External links

Exercise

Multiply 0.5 by itself 1000 times (use np.prod on an array of 0.5s). What does NumPy give you? Now compute 1000 * np.log(0.5). Then np.exp the result back. Why is the second route the only one that survives?
Hint
0.5^1000 ≈ 9.3e-302 — at the edge of float64. The log path computes -693.15, exp-ing it gives back the tiny number. The product route varies by NumPy version but is unreliable.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 2

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.
  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. Pippa
      Pippa· warmElechemistElechemist

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

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

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