Cheat sheets

Information Theory and Numerical Stability

Entropy, cross-entropy and KL, stable softmax and log-sum-exp, float formats, PCA and distance choice.

  • Concepts and terms
  • Tasks and commands
  • Annotated code
  • Diagrams
  • Defaults and numbers
  • Troubleshooting
  • Questions and tips

Source

Information formulas#

Surprise
I(x) = -\log p(x). Base 2 gives bits. Base e gives nats.
Entropy
H(P) = -\sum_x p(x) \log p(x). Fair coin 1 bit. A 99% coin 0.08 bits.
Cross-entropy
H(P,Q) = -\sum_x p(x) \log q(x). A one-hot label gives -\log q(\text{true class}).
KL divergence
D_{KL}(P \Vert Q) = H(P,Q) - H(P). It is not symmetric.
Mutual information
I(X;Y) = H(X) + H(Y) - H(X,Y). Zero means independent.
Conditional entropy
H(Y \mid X) = H(X,Y) - H(X). Decision tree splits minimize it.
Perplexity
e^{H(P,Q)} in nats. Perplexity 50 is a uniform pick from 50 tokens.
Label smoothing
(1-\epsilon)\,\text{hard} + \epsilon / K. With 0.1 and 4 classes the true class gets 0.925.
Units
1 nat is 1.4427 bits. PyTorch and TensorFlow use nats.

From Lesson 01.09

Stable loss path#

shiftedlog probsLogitsraw scores zSubtract maxc = max(x)Log-sum-explargest term isexp(0) = 1log_softmax_stablez minus lseCross-entropyminus log prob oftrue classKL divergenceCE minus entropyPerplexityexp of mean CEshiftedlog probsLogitsraw scores zSubtract maxc = max(x)Log-sum-explargest term isexp(0) = 1log_softmax_stablez minus lseCross-entropyminus log prob oftrue classKL divergenceCE minus entropyPerplexityexp of mean CE

From Lesson 01.13, 01.13 code/numerical.py, Lesson 01.09

Stable softmax, LSE and cross-entropy#

def logsumexp_stable(values):
c = max(values)largest exponent becomes 0
return c + math.log(sum(math.exp(v - c) for v in values))
def log_softmax_stable(logits):
c = max(logits)
lse = c + math.log(sum(math.exp(z - c) for z in logits))
return [z - lse for z in logits]log probs, no overflow
def cross_entropy_stable(true_class, logits):
log_probs = log_softmax_stable(logits)
return -log_probs[true_class]never calls log(0)

From 01.13 code/numerical.py

Tasks in code#

TaskCode
Entropy in bitsentropy(probs, base=2)
KL divergencekl_divergence(p, q, base=2)
Perplexity from mean CEperplexity(avg_cross_entropy, base="e")
Safe softmaxsoftmax_stable(logits)
Numerical gradientnumerical_gradient(f, x, h=1e-5)
Compare to backpropcheck_gradient(analytical, numerical)
Clip the gradient normclip_by_norm(gradients, max_norm)
Variance with a large meanwelford_variance(values)

From 01.09 code/information_theory.py, 01.13 code/numerical.py

Float formats and limits#

SettingDefaultNote
float64 max1.8e30811 exponent bits, ~15-16 digits
float32 max3.4028235e+388 exponent bits, ~7-8 digits
float32 min normal1.175e-38Denormals reach 1.401e-45
float32 exp overflowexp(89.0)Gives inf. exp(88.7) still fits
float32 exp underflowexp(-104)Gives 0.0
float16 max65,504Gradients below ~6e-8 become 0
bfloat16 max3.4e388 exponent bits, 7 mantissa bits
float32 machine epsilon1.19e-7Smallest e with 1.0 + e != 1.0

From Lesson 01.13

Knobs and thresholds#

SettingDefaultNote
Gradient check h1e-5Typical range 1e-5 to 1e-7
Gradient check pass1e-7Above 1e-3 something is wrong
max_norm1.0Transformers. 0.5 for RL, 5.0 for simple nets
LayerNorm epsilon1e-5Prevents division by zero
Epsilon in log and divide1e-8log(x + 1e-8)
t-SNE perplexity5-50The lesson uses 30
UMAP n_neighbors15With min_dist 0.1
PCA cumulative variance0.95Or a 90-95% threshold

From Lesson 01.13, Lesson 01.10

Mixed precision with loss scaling#

1Master weightskeep a float32 copy of the weights.2Forward passrun it in float16 for speed.3Losscompute it in float32 to prevent overflow.4Scalemultiply the loss by a factor, for example 1024.5Backward passrun it in float16. Gradients are 1024x larger.6Unscaledivide the gradients by 1024 before the update.7Updatechange the float32 master weights.8Dynamic scalestart at 65536. Halve on inf. Double after Nclean steps.

From Lesson 01.13

Numerical bugs#

SymptomCauseFix
Loss is NaN after a few epochsLogits too large, softmax overflowedStable softmax, lower LR, clip gradients
Loss stuck at log(num_classes)Near-uniform outputs, no learningCheck labels, loss and dead ReLUs
Val accuracy 1-3% lowMixed precision without loss scalingDynamic loss scaling or bfloat16
Gradient norm 0.0 in some layersDead ReLUs or float16 underflowLeakyReLU or GELU, gradient scaling
Results differ between GPUsFloat addition is not associativetorch.use_deterministic_algorithms(True)
exp() returns inf in the lossRaw logits without max subtractiontorch.nn.functional.log_softmax()
Diverges after float16 switchGrads below 6e-8, values above 65,504AMP with loss scaling, or bfloat16
Wrong variance for large valuesCatastrophic cancellationwelford_variance or center the data

From Lesson 01.13, 01.13 code/numerical.py

Pick a distance#

CommandWhat it does
cosine_similarity(a, b)Text and embeddings. Ignores magnitude
dot_product(a, b)Recommendation. Magnitude encodes popularity
l2_distance(a, b)Pixels and spatial data at comparable scale
l1_distance(a, b)Sparse high-dim features. Robust to outliers
linf_distance(a, b)Worst-case deviation in one dimension
mahalanobis_distance(x, y, cov_matrix)Outliers with correlated features
jaccard_similarity(set_a, set_b)Sets and tags. IoU for segmentation
edit_distance(s1, s2)Strings. Insert, delete or substitute

From Lesson 01.14, 01.14 code/distances.py

Reduce dimensions#

Curse of dimensionality
at 1000 dimensions the max/min distance ratio is ~1.02.
PCA
center, covariance, np.linalg.eigh, sort by eigenvalue, keep the top k.
Explained ratio
one eigenvalue divided by the sum of all eigenvalues.
Pick k
keep 90-95% of variance, find the elbow, or sweep k on the task.
Reconstruction error
the sum of the dropped eigenvalues. High error marks an outlier.
t-SNE
keeps local neighbors. Stochastic and O(n^2). Cluster gaps mean nothing.
UMAP
faster than t-SNE. It keeps more global structure.
Kernel PCA
PCA in a kernel feature space. RBF separates concentric circles.

From Lesson 01.10, 01.10 code/dim_reduction.py

Questions#

Why subtract the max?
The largest term becomes exp(0) = 1. The sum is at least 1.
Why bfloat16 for training?
It has the float32 range. Loss scaling is usually unnecessary.
Why minimize cross-entropy?
It equals negative log-likelihood. H(P) is constant.
Why is KL not a distance?
It is not symmetric and fails the triangle inequality.
Why Wasserstein for GANs?
It gives gradients when distributions do not overlap.
Why does L1 give sparse weights?
Loss contours touch the L1 diamond at a corner.
Why centered differences?
The error is O(h^2). Forward differences give O(h).

From Lesson 01.13, Lesson 01.14, Lesson 01.09

Tips#

  • Do not compare floats with ==. Use math.isclose().
  • Check for nan and inf after each forward pass while you debug: math.isfinite(x).
  • Clamp inputs to exp: exp(clamp(x, -80, 80)).
  • Clip by norm with torch.nn.utils.clip_grad_norm_(). It keeps the gradient direction.
  • Check each new layer or loss with torch.autograd.gradcheck().
  • With L2-normalized embeddings, cosine and dot product give the same result.

From Lesson 01.13, Lesson 01.14