Skip to content
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

Move Products into Sums

For positive and ,

This is the exponential rule read backward. When a probability calculation contains a long product, the identity lets us work with a sum of log-probabilities instead.

Underflow Depends on the Data Type

For example, . That is far below float32's range and underflows to zero, while float64 can still represent a nonzero result. The computer did not universally “give up”; a particular representation ran out of range.

Log space stores , which is easy to represent. Exponentiating later can still underflow if the requested probability is outside the destination type's range, so comparisons, optimization, and accumulation should remain in log space as long as practical.

Where Log-Likelihood Appears

  • Maximum likelihood estimation maximizes a sum of observation log-probabilities instead of their product.
  • Cross-entropy for one-hot classification reduces to the negative log-probability of the target class.
  • Language models train on next-token negative log-likelihood and derive perplexity from mean log loss.
Turn long products of probabilities into sums of log-probabilities. The mathematics is equivalent, but the useful numerical range is much wider.

Code

Why logs aren't optional·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: underflow in float32
print(np.prod(probs64))  # about 1e-300: still representable in float64

log_total = np.sum(np.log(probs32), dtype=np.float64)
print(log_total)         # about -690.776
print(np.exp(log_total)) # about 1e-300: recoverable in float64 here

# A longer product eventually underflows in float64 too; its log still survives.
deeper_log_total = 400 * np.log(0.001)
print(deeper_log_total)          # about -2763.102
print(np.exp(deeper_log_total))  # 0.0: underflow even in float64
# Keep the log value as long as the computation permits.
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

Create arrays of 1,500 values equal to 0.5 with dtype=np.float32 and dtype=np.float64. Compare their direct products with 1500 * np.log(0.5).
Hint
0.5^1500 is below float64's normal range. Direct multiplication and final exponentiation may become zero, while the log value of about -1039.7 remains representable and comparable.

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