VKVlad KulikovPublic / lectures
Information Theory for ML3 / 16

Module 3. Cross-Entropy and KL Divergence

Try each exercise first. Expand worked answers or project guidance when needed; their open state is shared between RU/EN.

Main route

Download course files (.zip) 33.9 KB

The archive preserves the folder structure of nine Python solution files shared by RU/EN.

Exercise 1. Zero probability and smoothing

Let

P=(0.9,0.1),Q=(1,0).P=(0.9,0.1),\qquad Q=(1,0).

  1. Compute H(P,Q)H(P,Q) and DKL(P∥Q)D_{\mathrm{KL}}(P\|Q).

  2. Replace QQ with Qε=(1−ε,ε)Q_\varepsilon=(1-\varepsilon,\varepsilon), where 0<ε<10<\varepsilon<1, and write both quantities.

  3. Find the leading growth as ε→0+\varepsilon\to0^+.

  4. Explain why probability clipping removes inf numerically but also changes the distribution being evaluated.

Show answer and solution

Let

P=(0.9,0.1),Q=(1,0).P=(0.9,0.1),\qquad Q=(1,0).

1. Support mismatch

In bits,

H2(P,Q)=−0.9log⁡21−0.1log⁡20=+∞.H_2(P,Q) =-0.9\log_2 1-0.1\log_2 0 =+\infty.

Consequently,

DKL,2(P∥Q)=+∞,D_{\mathrm{KL},2}(P\|Q)=+\infty,

because PP allows the second outcome while QQ declares it impossible.

2. ε\varepsilon-smoothing

For

Qε=(1−ε,ε),Q_\varepsilon=(1-\varepsilon,\varepsilon),

we obtain

H2(P,Qε)=−0.9log⁡2(1−ε)−0.1log⁡2ε.H_2(P,Q_\varepsilon) =-0.9\log_2(1-\varepsilon) -0.1\log_2\varepsilon.

The entropy of PP is

H2(P)=−0.9log⁡20.9−0.1log⁡20.1.H_2(P) =-0.9\log_2 0.9-0.1\log_2 0.1.

Therefore,

DKL,2(P∥Qε)=0.9log⁡20.91−ε+0.1log⁡20.1ε=H2(P,Qε)−H2(P).\begin{aligned} D_{\mathrm{KL},2}(P\|Q_\varepsilon) &=0.9\log_2\frac{0.9}{1-\varepsilon} +0.1\log_2\frac{0.1}{\varepsilon}\\ &=H_2(P,Q_\varepsilon)-H_2(P). \end{aligned}

3. Asymptotics as ε→0+\varepsilon\to0^+

Since

−log⁡2(1−ε)=O(ε),-\log_2(1-\varepsilon)=O(\varepsilon),

the leading diverging term is

H2(P,Qε)=0.1log⁡21ε+O(1).H_2(P,Q_\varepsilon) =0.1\log_2\frac1\varepsilon+O(1).

KL has the same coefficient:

DKL,2(P∥Qε)=0.1log⁡21ε+O(1).D_{\mathrm{KL},2}(P\|Q_\varepsilon) =0.1\log_2\frac1\varepsilon+O(1).

Even an outcome with probability only 0.10.1 creates an unbounded penalty as the model drives its probability toward zero.

4. What clipping does

Replacing probabilities by

Python
q = np.clip(q, eps, 1.0)
q = q / q.sum()

creates a new smoothed distribution Q~ε\widetilde Q_\varepsilon (for the code shown, it is not literally (1−ε,ε)(1-\varepsilon,\varepsilon)). This may be a sensible implementation safeguard, but the resulting loss evaluates the smoothed model, not the original QQ. If clipping is applied only inside the logarithm and the values are not renormalized, the object may cease to be a probability distribution at all.

In practice, computing log-probabilities from logits through a stable log_softmax is preferable: finite logits already produce strictly positive softmax probabilities, with no manual clipping required.

Exercise 2. The softmax cross-entropy gradient

For

qj=ezj∑kezk,ℓ(z;r)=−∑jrjlog⁡qj,q_j=\frac{e^{z_j}}{\sum_k e^{z_k}}, \qquad \ell(z;r)=-\sum_j r_j\log q_j,

derive

∂ℓ∂zj=qj−rj\frac{\partial\ell}{\partial z_j}=q_j-r_j

and

∇z2ℓ=diag⁡(q)−qq⊤.\nabla_z^2\ell =\operatorname{diag}(q)-qq^\top.

Show that the Hessian is positive semidefinite, and explain why this does not make a deep network convex in its weights.

Show answer and solution

Let

qj=ezj∑kezk,ℓ(z;r)=−∑jrjlog⁡qj,∑jrj=1.q_j=\frac{e^{z_j}}{\sum_ke^{z_k}}, \qquad \ell(z;r)=-\sum_jr_j\log q_j, \qquad \sum_jr_j=1.

Using

log⁡qj=zj−log⁡∑kezk,\log q_j=z_j-\log\sum_ke^{z_k},

we get

ℓ(z;r)=−∑jrjzj+(∑jrj)log⁡∑kezk=−r⊤z+log⁡∑kezk.\begin{aligned} \ell(z;r) &=-\sum_jr_jz_j +\left(\sum_jr_j\right)\log\sum_ke^{z_k}\\ &=-r^\top z+\log\sum_ke^{z_k}. \end{aligned}

Differentiate with respect to zmz_m:

∂ℓ∂zm=−rm+ezm∑kezk=qm−rm.\frac{\partial\ell}{\partial z_m} =-r_m+\frac{e^{z_m}}{\sum_ke^{z_k}} =q_m-r_m.

Hence

∇zℓ=q−r.\boxed{\nabla_z\ell=q-r}.

For a one-hot target r=eyr=e_y,

∇zℓ=q−ey.\nabla_z\ell=q-e_y.

Hessian

The standard softmax derivative is

∂qi∂zj=qi(δij−qj),\frac{\partial q_i}{\partial z_j} =q_i(\delta_{ij}-q_j),

so

∇z2ℓ=diag⁡(q)−qq⊤.\nabla_z^2\ell =\operatorname{diag}(q)-qq^\top.

For any v∈RKv\in\mathbb R^K,

v⊤∇z2ℓ v=∑jqjvj2−(∑jqjvj)2=Var⁡J∼q(vJ)≥0.\begin{aligned} v^\top\nabla_z^2\ell\,v &=\sum_jq_jv_j^2-\left(\sum_jq_jv_j\right)^2\\ &=\operatorname{Var}_{J\sim q}(v_J)\\ &\ge0. \end{aligned}

Thus the Hessian is positive semidefinite and cross-entropy is convex in the logits.

The null direction corresponds to adding the same constant to every logit: softmax is invariant under z↦z+c1z\mapsto z+c\mathbf1. But logits depend nonlinearly on a deep network's weights, so composing this loss with the network is not generally convex in the parameters.

Exercise 3. Conditional KL for a language model

Derive

EP[−log⁡qθ(Y∣C)]=HP(Y∣C)+EC∼PDKL(P(⋅∣C)∥Qθ(⋅∣C)).\mathbb E_P[-\log q_\theta(Y\mid C)] =H_P(Y\mid C) +\mathbb E_{C\sim P} D_{\mathrm{KL}} \bigl(P(\cdot\mid C)\|Q_\theta(\cdot\mid C)\bigr).

Then prove the chain rule

DKL(P1:T∥Q1:T)=∑t=1TEP(X<t)DKL(P(⋅∣X<t)∥Q(⋅∣X<t)).D_{\mathrm{KL}}(P_{1:T}\|Q_{1:T}) = \sum_{t=1}^T \mathbb E_{P(X_{<t})} D_{\mathrm{KL}} \bigl(P(\cdot\mid X_{<t})\|Q(\cdot\mid X_{<t})\bigr).

In two or three sentences, explain why validation loss under teacher forcing does not fully characterize behavior on the model's own generated prefixes.

Optional mathematical deepening

Show answer and solution

1. Conditional cross-entropy decomposition

Start with

L(Q)=EC,Y∼P[−log⁡q(Y∣C)].\mathcal L(Q) =\mathbb E_{C,Y\sim P}[-\log q(Y\mid C)].

Add and subtract log⁡p(Y∣C)\log p(Y\mid C):

L(Q)=EP[−log⁡p(Y∣C)]+EP[log⁡p(Y∣C)q(Y∣C)].\begin{aligned} \mathcal L(Q) &=\mathbb E_P[-\log p(Y\mid C)]\\ &\quad+ \mathbb E_P\left[ \log\frac{p(Y\mid C)}{q(Y\mid C)} \right]. \end{aligned}

The first term is conditional entropy:

EP[−log⁡p(Y∣C)]=HP(Y∣C).\mathbb E_P[-\log p(Y\mid C)]=H_P(Y\mid C).

For a fixed C=cC=c, the inner expectation in the second term is

EY∼P(⋅∣c)[log⁡p(Y∣c)q(Y∣c)]=DKL(P(⋅∣c)∥Q(⋅∣c)).\mathbb E_{Y\sim P(\cdot\mid c)} \left[ \log\frac{p(Y\mid c)}{q(Y\mid c)} \right] =D_{\mathrm{KL}}\bigl(P(\cdot\mid c)\|Q(\cdot\mid c)\bigr).

Averaging over CC gives

L(Q)=HP(Y∣C)+EC∼PDKL(P(⋅∣C)∥Q(⋅∣C)).\boxed{ \mathcal L(Q) =H_P(Y\mid C) +\mathbb E_{C\sim P} D_{\mathrm{KL}} \bigl(P(\cdot\mid C)\|Q(\cdot\mid C)\bigr) }.

2. Autoregressive KL chain rule

Factor both distributions:

p(x1:T)=∏t=1Tp(xt∣x<t),q(x1:T)=∏t=1Tq(xt∣x<t).p(x_{1:T})=\prod_{t=1}^Tp(x_t\mid x_{<t}), \qquad q(x_{1:T})=\prod_{t=1}^Tq(x_t\mid x_{<t}).

Then

log⁡p(x1:T)q(x1:T)=∑t=1Tlog⁡p(xt∣x<t)q(xt∣x<t).\log\frac{p(x_{1:T})}{q(x_{1:T})} =\sum_{t=1}^T \log\frac{p(x_t\mid x_{<t})}{q(x_t\mid x_{<t})}.

Taking expectation under PP and interchanging a finite sum with expectation,

DKL(P1:T∥Q1:T)=∑t=1TEX1:T∼P[log⁡p(Xt∣X<t)q(Xt∣X<t)]=∑t=1TEX<t∼PDKL(P(⋅∣X<t)∥Q(⋅∣X<t)).\begin{aligned} D_{\mathrm{KL}}(P_{1:T}\|Q_{1:T}) &=\sum_{t=1}^T \mathbb E_{X_{1:T}\sim P} \left[ \log\frac{p(X_t\mid X_{<t})}{q(X_t\mid X_{<t})} \right]\\ &=\sum_{t=1}^T \mathbb E_{X_{<t}\sim P} D_{\mathrm{KL}} \bigl(P(\cdot\mid X_{<t})\|Q(\cdot\mid X_{<t})\bigr). \end{aligned}

The last step is the law of total expectation: for each fixed prefix, the inner expectation is taken under the true next-token distribution.

3. Why teacher-forced loss is not the whole deployment story

Teacher forcing evaluates prefixes distributed as P(X<t)P(X_{<t}), i.e. real prefixes from the dataset. During free generation, the prefix distribution is induced by QQ. An early deviation changes later contexts and may move the model into regions poorly covered by teacher-forced validation. Good held-out NLL is therefore necessary for a good probabilistic model on the data distribution, but it is not a complete closed-loop generation evaluation.

Exercise 4. Forward and reverse KL for a restricted model

For

p(x)=12N(x;−3,1)+12N(x;3,1)p(x)=\tfrac12\mathcal N(x;-3,1) +\tfrac12\mathcal N(x;3,1)

and qμ,σ(x)=N(x;μ,σ2)q_{\mu,\sigma}(x)=\mathcal N(x;\mu,\sigma^2):

  1. find the forward-KL optimum analytically;

  2. minimize reverse KL numerically using Gauss–Hermite quadrature;

  3. start the optimizer from several points and recover both minima near the individual modes and the broad central local minimum;

  4. plot pp and the three representative qq distributions.

Do not discretize densities as unnormalized arrays or replace zeros by arbitrary clipping inside the objective: that can create a different optimization problem.

Show answer and solution

Let

p(x)=12N(x;−3,1)+12N(x;3,1),p(x)=\tfrac12\mathcal N(x;-3,1)+\tfrac12\mathcal N(x;3,1),

and

qμ,σ(x)=N(x;μ,σ2).q_{\mu,\sigma}(x)=\mathcal N(x;\mu,\sigma^2).

1. Analytic forward-KL optimum

For fixed PP, minimizing DKL(P∥Q)D_{\mathrm{KL}}(P\|Q) is equivalent to minimizing the cross-entropy

−EP[log⁡qμ,σ(X)].-\mathbb E_P[\log q_{\mu,\sigma}(X)].

For a Gaussian,

−log⁡qμ,σ(x)=12log⁡(2πσ2)+(x−μ)22σ2.-\log q_{\mu,\sigma}(x) =\frac12\log(2\pi\sigma^2) +\frac{(x-\mu)^2}{2\sigma^2}.

Up to an additive constant, we minimize

J(μ,σ2)=12log⁡σ2+EP[(X−μ)2]2σ2.J(\mu,\sigma^2) =\frac12\log\sigma^2 +\frac{\mathbb E_P[(X-\mu)^2]}{2\sigma^2}.

The optimum in μ\mu is

μ∗=EP[X]=0.\mu^*=\mathbb E_P[X]=0.

Then

(σ∗)2=Var⁡P(X).(\sigma^*)^2=\operatorname{Var}_P(X).

For the symmetric mixture,

Var⁡P(X)=E[Var⁡(X∣Z)]+Var⁡(E[X∣Z])=1+9=10.\operatorname{Var}_P(X) =\mathbb E[\operatorname{Var}(X\mid Z)] +\operatorname{Var}(\mathbb E[X\mid Z]) =1+9=10.

Therefore,

μfwd=0,σfwd=10≈3.1623.\boxed{ \mu_{\mathrm{fwd}}=0, \qquad \sigma_{\mathrm{fwd}}=\sqrt{10}\approx3.1623 }.

2. Reverse KL with Gauss–Hermite quadrature

For reverse KL, reparameterize

X=μ+σZ,Z∼N(0,1),X=\mu+\sigma Z,\qquad Z\sim\mathcal N(0,1),

and evaluate

DKL(Q∥P)=EZ[log⁡q(μ+σZ)−log⁡p(μ+σZ)].D_{\mathrm{KL}}(Q\|P) =\mathbb E_Z[\log q(\mu+\sigma Z)-\log p(\mu+\sigma Z)].

The code below uses roots_hermitenorm; its nodes and weights directly match integrals with the e−z2/2e^{-z^2/2} weight.

Python
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import minimize
from scipy.special import logsumexp, roots_hermitenorm
from scipy.stats import norm

LOG_2PI = np.log(2.0 * np.pi)

# E[f(Z)] for Z ~ N(0, 1)
z_nodes, z_weights = roots_hermitenorm(800)
z_weights = z_weights / np.sqrt(2.0 * np.pi)


def log_p(x: np.ndarray) -> np.ndarray:
    """Log-density of 0.5 N(-3,1) + 0.5 N(3,1)."""
    components = np.stack([
        np.log(0.5) - 0.5 * (x + 3.0) ** 2 - 0.5 * LOG_2PI,
        np.log(0.5) - 0.5 * (x - 3.0) ** 2 - 0.5 * LOG_2PI,
    ])
    return logsumexp(components, axis=0)


def reverse_kl(params: np.ndarray) -> float:
    mu, log_sigma = params
    sigma = np.exp(log_sigma)

    # Reparameterization X = mu + sigma Z, Z ~ N(0,1)
    x = mu + sigma * z_nodes
    log_q = -0.5 * z_nodes**2 - log_sigma - 0.5 * LOG_2PI

    return float(np.dot(z_weights, log_q - log_p(x)))


starts = [
    (-4.0, 0.0),
    (-2.0, 0.0),
    (0.0, 0.0),
    (2.0, 0.0),
    (4.0, 0.0),
    (0.0, 1.0),
]

solutions = []
for start in starts:
    result = minimize(
        reverse_kl,
        x0=np.array(start),
        method="L-BFGS-B",
        bounds=[(-8.0, 8.0), (-4.0, 3.0)],
        options={"ftol": 1e-13, "gtol": 1e-9, "maxiter": 2000},
    )
    if not result.success:
        raise RuntimeError(result.message)

    mu, log_sigma = result.x
    solutions.append((start, mu, np.exp(log_sigma), result.fun))

for start, mu, sigma, value in solutions:
    print(
        f"start={start!s:>12} -> "
        f"mu={mu:+.6f}, sigma={sigma:.6f}, reverse_KL={value:.6f}"
    )

Typical output is

text
start= (-4.0, 0.0) -> mu=-2.984306, sigma=1.023369, reverse_KL=0.688769
start= (-2.0, 0.0) -> mu=-2.984306, sigma=1.023369, reverse_KL=0.688769
start=  (0.0, 0.0) -> mu=+0.000000, sigma=2.7438,   reverse_KL=0.8406
start=  (2.0, 0.0) -> mu=+2.984306, sigma=1.023369, reverse_KL=0.688769
start=  (4.0, 0.0) -> mu=+2.984306, sigma=1.023369, reverse_KL=0.688769
start=  (0.0, 1.0) -> mu=+0.000000, sigma=2.7438,   reverse_KL=0.8406

The last digits of the central solution vary slightly with quadrature order because this is numerical integration. The symmetric minima near ±2.9843\pm2.9843 are stable and have the lower objective.

3. Visualization

Python
x = np.linspace(-7.0, 7.0, 2000)
p = 0.5 * norm.pdf(x, -3.0, 1.0) + 0.5 * norm.pdf(x, 3.0, 1.0)

q_forward = norm.pdf(x, 0.0, np.sqrt(10.0))
q_reverse_right = norm.pdf(x, 2.984306, 1.023369)
q_reverse_center = norm.pdf(x, 0.0, 2.7438)

plt.figure(figsize=(10, 5))
plt.plot(x, p, linewidth=2.5, label="target mixture p")
plt.plot(x, q_forward, linewidth=2, label="forward KL optimum")
plt.plot(x, q_reverse_right, linewidth=2, label="reverse KL global optimum")
plt.plot(
    x,
    q_reverse_center,
    linewidth=2,
    linestyle="--",
    label="reverse KL central local optimum",
)
plt.xlabel("x")
plt.ylabel("density")
plt.legend()
plt.grid(alpha=0.25)
plt.show()

4. What the example establishes

Within the chosen Gaussian family, forward KL exactly matches mean and variance and therefore covers both modes. Reverse KL has two better solutions near the individual modes and one worse, broad central solution. This is a powerful illustration of asymmetry, not a universal law about every model family and optimizer.

Engineering bonus

Audit the PyTorch loss. On a small batch, verify that:

  1. F.cross_entropy(logits, target) matches F.nll_loss(F.log_softmax(logits, dim=-1), target);

  2. autograd returns the q−eyq-e_y gradient with the chosen reduction;

  3. probability targets implement soft-label cross-entropy;

  4. label_smoothing, weight, ignore_index, and reduction change the meaning of the reported average.

State exactly which loss may be exponentiated when reporting token perplexity.

Show answer and solution
Python
import torch
import torch.nn.functional as F


torch.manual_seed(7)
logits = torch.randn(4, 5, dtype=torch.float64, requires_grad=True)
target = torch.tensor([0, 3, 1, 4])

# 1. Fused CE == log_softmax + NLL for class-index targets
loss_fused = F.cross_entropy(logits, target, reduction="mean")
loss_manual = F.nll_loss(
    F.log_softmax(logits, dim=-1),
    target,
    reduction="mean",
)
torch.testing.assert_close(loss_fused, loss_manual)

# 2. Per-example gradient is (q - one_hot) / batch_size for mean reduction
loss_fused.backward()
q = logits.detach().softmax(dim=-1)
one_hot = F.one_hot(target, num_classes=5).to(torch.float64)
expected_grad = (q - one_hot) / logits.shape[0]
torch.testing.assert_close(logits.grad, expected_grad)

# 3. Soft-label cross-entropy
soft_target = 0.9 * one_hot + 0.1 / logits.shape[-1]
loss_soft_fused = F.cross_entropy(
    logits.detach(),
    soft_target,
    reduction="mean",
)
loss_soft_manual = -(
    soft_target * F.log_softmax(logits.detach(), dim=-1)
).sum(dim=-1).mean()
torch.testing.assert_close(loss_soft_fused, loss_soft_manual)

# 4. Label smoothing changes the target distribution and the loss
loss_smooth = F.cross_entropy(
    logits.detach(),
    target,
    label_smoothing=0.1,
)
print("ordinary CE:", float(loss_fused.detach()))
print("smoothed CE:", float(loss_smooth))

For token perplexity, exponentiate the mean ordinary NLL of the observed target tokens in nats:

PPL⁡=emean token NLL.\operatorname{PPL}=e^{\text{mean token NLL}}.

The mean must include exactly the intended unmasked targets, each with its ordinary weight. A loss with label smoothing, class weights, arbitrary sample reweighting, or a different normalization may be useful for training, but its exponential is not standard perplexity.