Contents
Deep Breakdown
What it tells me (Situation Awareness): The derivative of (logarithm base ) is . This is the generalization of : the natural log has , so the factor disappears. For base (bits): , about times larger than the natural log derivative at the same . For base : , smaller than the natural log derivative. In DA/DS: base- logarithms appear in information theory (entropy in bits), base- in decibels and order-of-magnitude analyses, and this derivative is needed when differentiating information-theoretic loss functions written in non-natural-log bases.
Flowchart
Real-world use
Where it is used
Differentiating Shannon entropy with respect to : , using . Differentiating mutual information expressions. Gradient of log-likelihood when the model uses for interpretability. Change-of-base in information-theoretic optimization problems.
Problem it solves
When a loss function or metric is expressed in base-2 or base-10 logarithms (e.g., bits of entropy, nats vs bits conversion, decibel-scale regression), the gradient must be computed for optimization. Using the wrong base in the derivative introduces a constant-factor error equal to .
How it solves the problem
Change-of-base formula: . Since is a constant (not depending on ), .
Example in a business
A DS computes the gradient of binary cross-entropy loss expressed in bits: . The gradient: . Compared to the natural-log version, it differs by exactly . This constant factor affects the effective learning rate but not the direction of optimization.
The formula, piece by piece
- : logarithm base ; defined for , ,
- : natural log of the base; constant w.r.t. ; always positive for (negative for )
- : the derivative; has the same structure as the natural log, scaled by
- Change of base:
- Special cases: ; ;
- Chain rule:
- Integral:
The formula in words
"The derivative of is over times the natural log of the base. The factor converts from natural log units to base- units. When the base is , this factor is and you get the natural log derivative. For other bases, it's a constant rescaling." Tiny examples: . . . .
Critical Concepts
- The base matters for magnitude, not direction: all log functions increase with (for ), so all derivatives are positive. But the magnitude scales inversely with : larger base smaller derivative at the same .
- Optimization is base-independent: gradient descent with a base- log loss will converge to the same minimum as with the natural log, because the derivative differs only by a constant , which just rescales the effective learning rate. For optimization, always use internally and apply the base- formula only when the output needs to be in specific units.
- Entropy gradient: for Shannon entropy in base . The term comes from (product rule).
- Conversion factor: for any base . The derivative of any log base is the natural log derivative scaled by . This factor is: (nats to bits), (nats to decibels).
- Negative for : when , and the derivative for .
This is correct: is a decreasing function when .
Step-by-step calculation (tiny example)
Differentiate (contribution to binary entropy) and evaluate at .
Step 1: Product rule. .
Step 2: Apply . .
Step 3: Evaluate at . . . . Interpretation: the entropy contribution from increases if increases slightly (slope is positive at ).
Code examples
Beginner example (Python)
import numpy as np
def log_base_a_deriv(a: float, x: float) -> float:
"""d/dx(log_a(x)) = 1 / (x * ln(a)). Requires x > 0, a > 0, a != 1."""
if x <= 0:
raise ValueError(f"x = {x} <= 0: log undefined.")
if a <= 0 or a == 1:
raise ValueError(f"a = {a} invalid: need a > 0, a != 1.")
return 1.0 / (x * np.log(a)) # Compare derivatives for different bases at
x = 1
print("d/dx(log_a(x)) at x=1 for different bases:")
print(f"{'Base a':>8} {'ln(a)':>10} {'Derivative at x=1':>20}")
for a in [2.0, np.e, 10.0, 100.0]:
deriv = log_base_a_deriv(a, 1.0)
print(f"{a:>8.4f} {np.log(a):>10.6f} {deriv:>20.6f}") # Shannon entropy gradient
def entropy_gradient(p: np.ndarray, base: float = 2.0) -> np.ndarray:
"""d/dp(-p * log_a(p)) = -(log_a(p) + 1/ln(a))."""
return -(np.log(p) / np.log(base) + 1.0 / np.log(base))
print("\nShannon entropy gradient d/dp(-p*log2(p)):")
for p in [0.1, 0.25, 0.5, 0.75, 0.9]:
grad = entropy_gradient(np.array([p]))[0]
print(f" p={p:.2f}: d/dp(-p*log2(p)) = {grad:.4f}")Beginner example (R)
# d/dx(log_a(x)) = 1 / (x * log(a)) [log() in R is natural log]
log_base_a_deriv <- function(a, x) 1 / (x * log(a))
cat("d/dx(log_a(x)) at x=1 for different bases:\n")
cat(sprintf("%-8s %-10s %-20s\n", "Base a", "ln(a)", "Derivative at x=1"))
for (a in c(2, exp(1), 10, 100)) { cat(sprintf("%-8.4f %-10.6f %-20.6f\n", a, log(a), log_base_a_deriv(a, 1)))
} # Binary entropy and its gradient
binary_entropy_grad <- function(p) -(log(p)/log(2) + 1/log(2))
cat("\nBinary entropy gradient d/dp(-p*log2(p)):\n")
for (p in c(0.1, 0.25, 0.5, 0.75, 0.9)) { cat(sprintf(" p=%.2f: %.4f\n", p, binary_entropy_grad(p)))
}Professional example (Python)
import numpy as np
from scipy.special import entr
np.random.seed(42) # -- Information-theoretic loss: cross-entropy in bits vs nats --
# Use case: a DA computes KL divergence in bits and needs to verify that
# optimizing in nats vs bits gives the same argmin but different gradient magnitudes.
def cross_entropy_bits(p_true: np.ndarray, p_pred: np.ndarray) -> float:
"""H(P, Q) = -sum_i p_i * log_2(q_i). Cross-entropy in bits."""
p_pred_safe = np.clip(p_pred, 1e-10, 1.0)
return float(-np.sum(p_true * np.log2(p_pred_safe)))
def cross_entropy_nats(p_true: np.ndarray, p_pred: np.ndarray) -> float:
"""H(P, Q) = -sum_i p_i * ln(q_i). Cross-entropy in nats."""
p_pred_safe = np.clip(p_pred, 1e-10, 1.0)
return float(-np.sum(p_true * np.log(p_pred_safe)))
def cross_entropy_bits_gradient(p_true: np.ndarray, p_pred: np.ndarray) -> np.ndarray:
"""d/d(q_i)(-p_i * log_2(q_i)) = -p_i / (q_i * ln(2)). Uses d/dx(log_2(x)) = 1/(x * ln(2)). """
p_pred_safe = np.clip(p_pred, 1e-10, 1.0)
return -p_true / (p_pred_safe * np.log(2))
def cross_entropy_nats_gradient(p_true: np.ndarray, p_pred: np.ndarray) -> np.ndarray:
"""d/d(q_i)(-p_i * ln(q_i)) = -p_i / q_i."""
p_pred_safe = np.clip(p_pred, 1e-10, 1.0)
return -p_true / p_pred_safe # Softmax probabilities from a small 3-class distribution
K = 5
p_true = np.array([0.4, 0.3, 0.15, 0.1, 0.05]) # target distribution
p_pred = np.array([0.3, 0.25, 0.2, 0.15, 0.1]) # predicted distribution (unnormalized after softmax)
grad_bits = cross_entropy_bits_gradient(p_true, p_pred)
grad_nats = cross_entropy_nats_gradient(p_true, p_pred)
ce_bits = cross_entropy_bits(p_true, p_pred)
ce_nats = cross_entropy_nats(p_true, p_pred)
print("Cross-Entropy in Bits vs Nats: d/dq(-p*log_a(q)) = -p/(q * ln(a))")
print(f"\nCross-entropy: {ce_bits:.6f} bits = {ce_nats:.6f} nats")
print(f"Conversion: {ce_bits:.6f} / {ce_nats:.6f} = {ce_bits/ce_nats:.6f} (should = log2(e) = {np.log2(np.e):.6f})")
print(f"\n{'i':>3} {'p_true':>8} {'p_pred':>8} {'grad (bits)':>14} {'grad (nats)':>14} {'ratio':>8}")
for i in range(K):
ratio = grad_bits[i] / grad_nats[i]
print(f"{i+1:>3} {p_true[i]:>8.3f} {p_pred[i]:>8.3f} {grad_bits[i]:>14.6f} {grad_nats[i]:>14.6f} {ratio:>8.5f}")
print(f"\nGradient ratio (bits/nats) = 1/ln(2) = {1/np.log(2):.6f} (constant, as expected)")
print(f"Key: optimizing bits cross-entropy = optimizing nats cross-entropy (same argmin, scaled gradient)")Source: domain knowledge