VKVlad KulikovPublic / lectures
Information Theory for ML11 / 16

Module 11. Comparing distributions: f-divergences, variational critics, and Wasserstein distance

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

Download course files (.zip) 33.9 KB

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

Exercise 1. Generators and normalizations

  1. Show that adding a(t−1)a(t-1) to a generator does not change the f-divergence.

  2. Derive the explicit forms of forward KL, reverse KL, TV, and standard squared Hellinger from the table.

  3. Verify that the chosen JS generator yields

    12DKL(P∥M)+12DKL(Q∥M),M=P+Q2.\frac12D_{\mathrm{KL}}(P\|M) + \frac12D_{\mathrm{KL}}(Q\|M), \qquad M=\frac{P+Q}{2}.
  4. Explain why power divergence and Rényi divergence should not be treated as the same family without qualification.

Show answer and solution

Forward KL

For

f(t)=tlog⁡tf(t)=t\log t

we have f(1)=0f(1)=0 and f′′(t)=1/t>0f''(t)=1/t>0. Hence

Df(P∥Q)=∫q pqlog⁡pq dx=∫plog⁡pq dx=DKL(P∥Q).\begin{aligned} D_f(P\|Q) &=\int q\,\frac pq\log\frac pq\,dx\\ &=\int p\log\frac pq\,dx =D_{\mathrm{KL}}(P\|Q). \end{aligned}

Reverse KL

For f(t)=−log⁡tf(t)=-\log t,

Df(P∥Q)=∫qlog⁡qp dx=DKL(Q∥P).D_f(P\|Q) =\int q\log\frac qp\,dx =D_{\mathrm{KL}}(Q\|P).

Total variation

The function

f(t)=12∣t−1∣f(t)=\frac12|t-1|

is convex, and

Df(P∥Q)=12∫q∣pq−1∣dx=12∫∣p−q∣dx=TV⁡(P,Q).\begin{aligned} D_f(P\|Q) &=\frac12\int q\left|\frac pq-1\right|dx\\ &=\frac12\int|p-q|dx =\operatorname{TV}(P,Q). \end{aligned}

Squared Hellinger

For

f(t)=12(t−1)2,f(t)=\frac12(\sqrt t-1)^2, Df(P∥Q)=12∫q(pq−1)2dx=12∫(p−q)2dx=1−∫pq dx.\begin{aligned} D_f(P\|Q) &=\frac12\int q \left(\sqrt{\frac pq}-1\right)^2dx\\ &=\frac12\int(\sqrt p-\sqrt q)^2dx\\ &=1-\int\sqrt{pq}\,dx. \end{aligned}

Pearson χ2\chi^2

For f(t)=(t−1)2f(t)=(t-1)^2,

Df(P∥Q)=∫q(pq−1)2dx=∫(p−q)2q dx.D_f(P\|Q) =\int q\left(\frac pq-1\right)^2dx =\int\frac{(p-q)^2}{q}\,dx.

Jensen–Shannon

Take

f(t)=12[tlog⁡t−(t+1)log⁡t+12].f(t) =\frac12\left[ t\log t-(t+1)\log\frac{t+1}{2} \right].

Let M=(P+Q)/2M=(P+Q)/2, with density m=(p+q)/2m=(p+q)/2. Substitution and regrouping give

Df(P∥Q)=12∫plog⁡pm dx+12∫qlog⁡qm dx=DJS(P,Q).\begin{aligned} D_f(P\|Q) &=\frac12\int p\log\frac p{m}\,dx +\frac12\int q\log\frac q{m}\,dx\\ &=D_{\mathrm{JS}}(P,Q). \end{aligned}

Why a linear term changes nothing

Let

f~(t)=f(t)+a(t−1).\widetilde f(t)=f(t)+a(t-1).

Then

Df~(P∥Q)=Df(P∥Q)+a∫q(pq−1)dx=Df(P∥Q)+a(1−1)=Df(P∥Q).\begin{aligned} D_{\widetilde f}(P\|Q) &=D_f(P\|Q) +a\int q\left(\frac pq-1\right)dx\\ &=D_f(P\|Q)+a(1-1)\\ &=D_f(P\|Q). \end{aligned}

Thus one may affine-normalize a generator—for example, also impose f′(1)=0f'(1)=0—without changing the divergence.

Power divergence and Rényi divergence

A power divergence has the ordinary f-divergence form

Dfα(P∥Q)=∫qfα(p/q).D_{f_\alpha}(P\|Q) =\int q f_\alpha(p/q).

Rényi divergence applies the logarithm after integration:

DαR(P∥Q)=1α−1log⁡∫pαq1−α.D_\alpha^{\mathrm R}(P\|Q) = \frac1{\alpha-1} \log\int p^\alpha q^{1-\alpha}.

It is therefore a monotone transform of the corresponding power integral, but in general it is not itself of the form ∫qf(p/q)\int qf(p/q) for one fixed ff.

Exercise 2. DPI through conditional Jensen

For P≪QP\ll Q and a common channel K(dy∣x)K(dy\mid x), prove

dPYdQY(Y)=EQ[dPdQ(X)|Y]\frac{dP_Y}{dQ_Y}(Y) = \mathbb E_Q \left[ \frac{dP}{dQ}(X) \middle| Y \right]

and derive

Df(PY∥QY)≤Df(P∥Q).D_f(P_Y\|Q_Y) \le D_f(P\|Q).

State the meaning for a representation Z=T(X)Z=T(X) and a classifier that distinguishes two data groups.

Show answer and solution

Let

r(X)=dPdQ(X).r(X)=\frac{dP}{dQ}(X).

For every bounded measurable function φ(Y)\varphi(Y),

EPY[φ(Y)]=EP[φ(Y)]=EQ[r(X)φ(Y)]=EQ[EQ[r(X)∣Y] φ(Y)].\begin{aligned} \mathbb E_{P_Y}[\varphi(Y)] &=\mathbb E_P[\varphi(Y)]\\ &=\mathbb E_Q[r(X)\varphi(Y)]\\ &=\mathbb E_Q[ \mathbb E_Q[r(X)\mid Y]\,\varphi(Y) ]. \end{aligned}

The Radon–Nikodym characterization therefore yields

dPYdQY(Y)=EQ[r(X)∣Y].\boxed{ \frac{dP_Y}{dQ_Y}(Y) = \mathbb E_Q[r(X)\mid Y] }.

Conditional Jensen now gives

Df(PY∥QY)=EQYf ⁣(EQ[r(X)∣Y])≤EQ[f(r(X))]=Df(P∥Q).\begin{aligned} D_f(P_Y\|Q_Y) &=\mathbb E_{Q_Y} f\!\left(\mathbb E_Q[r(X)\mid Y]\right)\\ &\le \mathbb E_Q[f(r(X))]\\ &=D_f(P\|Q). \end{aligned}

For a representation Z=T(X)Z=T(X), fixed processing cannot make two population distributions more distinguishable than they were at the input. An encoder may make the distinction easier for a restricted classifier, but it cannot increase the population f-divergence.

Exercise 3. Optimal discriminator and JS

Assume real and model samples have equal class priors.

  1. Minimize binary cross-entropy pointwise to obtain

    D∗(x)=p(x)p(x)+q(x).D^*(x)=\frac{p(x)}{p(x)+q(x)}.
  2. Show

    logit⁡D∗(x)=log⁡p(x)q(x).\operatorname{logit}D^*(x)=\log\frac{p(x)}{q(x)}.
  3. Substitute D∗D^* into the GAN value function and obtain

    −log⁡4+2DJS(P,Q).-\log4+2D_{\mathrm{JS}}(P,Q).
  4. Explain why this does not imply that an alternating GAN follows the exact JS gradient at every step.

Show answer and solution

With equal class priors, for each xx we maximize

p(x)log⁡D(x)+q(x)log⁡(1−D(x)).p(x)\log D(x)+q(x)\log(1-D(x)).

The stationarity condition is

pD−q1−D=0,\frac pD-\frac q{1-D}=0,

so

D∗(x)=p(x)p(x)+q(x).\boxed{ D^*(x)=\frac{p(x)}{p(x)+q(x)} }.

Its logit is

logit⁡D∗(x)=log⁡p(x)q(x).\operatorname{logit}D^*(x) =\log\frac{p(x)}{q(x)}.

Let m=(p+q)/2m=(p+q)/2. Then

V(D∗,G)=∫plog⁡pp+q+qlog⁡qp+q dx=DKL(P∥M)+DKL(Q∥M)−2log⁡2=2DJS(P,Q)−log⁡4.\begin{aligned} V(D^*,G) &=\int p\log\frac p{p+q} +q\log\frac q{p+q}\,dx\\ &=D_{\mathrm{KL}}(P\|M) +D_{\mathrm{KL}}(Q\|M)-2\log2\\ &=2D_{\mathrm{JS}}(P,Q)-\log4. \end{aligned}

Thus

max⁡DV(D,Q)=−log⁡4+2DJS(P,Q).\boxed{ \max_DV(D,Q) =-\log4+2D_{\mathrm{JS}}(P,Q) }.

This exact statement assumes an optimal discriminator for the current QQ. Alternating training uses an imperfect discriminator, a restricted generator family, and often the non-saturating generator loss, so a practical update need not equal the exact JS gradient.

Exercise 4. From Fenchel KL to Donsker–Varadhan

  1. Compute the conjugate of f(t)=tlog⁡tf(t)=t\log t:

    f∗(s)=es−1.f^*(s)=e^{s-1}.
  2. Derive the NWJ form

    DKL(P∥Q)=sup⁡T{EPT−EQeT−1}.D_{\mathrm{KL}}(P\|Q) = \sup_T \{\mathbb E_PT-\mathbb E_Qe^{T-1}\}.
  3. Set T=G+cT=G+c and maximize analytically over cc.

  4. Recover the DV form and explain why merely shifting T↦T+1T\mapsto T+1 is insufficient.

Show answer and solution

For f(t)=tlog⁡tf(t)=t\log t,

f∗(s)=sup⁡t>0{st−tlog⁡t}.f^*(s)=\sup_{t>0}\{st-t\log t\}.

Stationarity gives

t∗=es−1,t^*=e^{s-1},

hence

f∗(s)=es−1.\boxed{f^*(s)=e^{s-1}}.

The Fenchel representation becomes

DKL(P∥Q)=sup⁡T{EPT−EQeT−1}.\boxed{ D_{\mathrm{KL}}(P\|Q) = \sup_T \left\{ \mathbb E_PT-\mathbb E_Qe^{T-1} \right\} }.

For a fixed function GG, put T=G+cT=G+c:

J(c)=EPG+c−ec−1EQ[eG].J(c) =\mathbb E_PG+c -e^{c-1}\mathbb E_Q[e^G].

The optimum is

c∗=1−log⁡EQ[eG],c^*=1-\log\mathbb E_Q[e^G],

and therefore

J(c∗)=EPG−log⁡EQ[eG].J(c^*) =\mathbb E_PG- \log\mathbb E_Q[e^G].

Thus

DKL(P∥Q)=sup⁡G{EPG−log⁡EQ[eG]}.\boxed{ D_{\mathrm{KL}}(P\|Q) = \sup_G \left\{ \mathbb E_PG- \log\mathbb E_Q[e^G] \right\} }.

A fixed shift T↦T+1T\mapsto T+1 does not remove the exponential expectation. DV appears after optimizing the free constant, which depends on GG.

The optimal NWJ critic is

T∗(x)=1+log⁡p(x)q(x),T^*(x)=1+\log\frac{p(x)}{q(x)},

whereas the optimal DV critic is

G∗(x)=log⁡p(x)q(x)+c.G^*(x)=\log\frac{p(x)}{q(x)}+c.

Exercise 5. InfoNCE: ceiling and interpretation

  1. From LNCE≥0\mathcal L_{\mathrm{NCE}}\ge0, show that

    log⁡K−LNCE\log K-\mathcal L_{\mathrm{NCE}}

    cannot exceed log⁡K\log K.

  2. For K=32K=32 and loss 1.21.2 nats, calculate the numerical lower bound.

  3. Explain why a numerical MINE estimate need not have the same ceiling.

  4. Give two mechanisms by which more negatives may hurt downstream representations despite raising the bound's ceiling.

Show answer and solution

Since cross-entropy loss is nonnegative,

LNCE≥0,\mathcal L_{\mathrm{NCE}}\ge0,

and hence

log⁡K−LNCE≤log⁡K.\boxed{ \log K-\mathcal L_{\mathrm{NCE}} \le\log K }.

For K=32K=32 and loss 1.21.2 nats,

log⁡32−1.2≈2.265736 nats.\log32-1.2 \approx2.265736\ \text{nats}.

MINE uses the DV functional

E[T]−log⁡E[eT],\mathbb E[T]-\log\mathbb E[e^T],

which has no algebraic log⁡K\log K ceiling. A numerical MINE value can therefore exceed log⁡K\log K, although this does not make it a certified distribution-free lower bound after adaptive finite-sample training.

Increasing the number of negatives can hurt the downstream representation because, for example:

  1. false negatives push semantically related objects apart;

  2. under fixed compute, optimization may worsen or the number of independent updates may fall;

  3. batch composition may fail to represent the intended marginal distribution;

  4. a larger numerical bound need not correspond to more task-relevant information.

Any two mechanisms were sufficient.

Exercise 6. Python: critic, InfoNCE, and support geometry

Run M11_divergence_experiments.py.

It:

  1. compares exact KL with optimized NWJ and DV objectives on a finite alphabet;

  2. verifies that the optimal classifier logit recovers log⁡(p/q)\log(p/q);

  3. checks the original GAN identity;

  4. computes oracle InfoNCE bounds for correlated Gaussian variables;

  5. compares JS, TV, and W1W_1 for shifted Dirac measures.

Answer:

  • why the NWJ critic differs from log⁡(p/q)\log(p/q) by exactly 1;
  • why the DV critic is defined only up to an additive constant;
  • why InfoNCE is far below true MI for ρ=0.99\rho=0.99 at small KK;
  • why W1W_1 changes with ∣θ∣|\theta| while JS and TV do not.
Show answer and solution

The script uses

P=(0.45,0.05,0.45,0.05),P=(0.45,0.05,0.45,0.05), Q=(0.05,0.45,0.05,0.45).Q=(0.05,0.45,0.05,0.45).

It obtains

DKL(P∥Q)≈1.757779662 nats.D_{\mathrm{KL}}(P\|Q) \approx1.757779662\ \text{nats}.

Unrestricted NWJ and DV critics reproduce this value to numerical precision. The best linear critic reaches only

0.314646043 nats,0.314646043\ \text{nats},

showing the variational gap caused by a restricted function class.

For f(t)=tlog⁡tf(t)=t\log t,

f′(t)=1+log⁡t,f'(t)=1+\log t,

so the NWJ optimum is

T∗=1+log⁡(p/q).T^*=1+\log(p/q).

The DV functional is invariant under G↦G+cG\mapsto G+c, hence

G∗=log⁡(p/q)+c.G^*=\log(p/q)+c.

The Bayes classifier recovered

logit⁡D∗=log⁡(p/q)\operatorname{logit}D^*=\log(p/q)

with zero maximum numerical error. The original-GAN identity also matched to about 10−1610^{-16}:

V(D∗,G)=−log⁡4+2DJS(P,Q).V(D^*,G) =-\log4+2D_{\mathrm{JS}}(P,Q).

For a jointly Gaussian pair with ρ=0.99\rho=0.99,

I(X;Y)=−12log⁡(1−ρ2)≈1.958518 nats.I(X;Y) =-\frac12\log(1-\rho^2) \approx1.958518\ \text{nats}.

The oracle InfoNCE estimates were approximately:

KKbound, nats
20.5705
41.0521
161.6600
641.8719
2561.9281

For small KK, the bound is far from MI mainly because it cannot exceed log⁡K\log K. It tightens as KK grows, while remaining a Monte Carlo estimate of a particular multi-sample bound.

Finally, for

P=δ0,Qθ=δθ,P=\delta_0, \qquad Q_\theta=\delta_\theta,

and θ≠0\theta\ne0,

DJS=log⁡2,TV⁡=1,D_{\mathrm{JS}}=\log2, \qquad \operatorname{TV}=1,

independently of the distance between the points, whereas

W1(P,Qθ)=∣θ∣.W_1(P,Q_\theta)=|\theta|.

JS and TV depend only on the probability measures; Wasserstein additionally uses the ground metric.

Code solution · the same source file for RU/EN

PythonM11_divergence_experiments.pyDownload .py
from __future__ import annotations

import json
import math
from pathlib import Path

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
from scipy.optimize import minimize
from scipy.special import logsumexp

ROOT = Path(__file__).resolve().parent
OUT = ROOT / "generated_experiment"
OUT.mkdir(exist_ok=True)


def discrete_variational_experiment() -> dict[str, object]:
    """Check NWJ, DV, classifier-ratio, and original-GAN identities."""
    p = np.array([0.45, 0.05, 0.45, 0.05], dtype=np.float64)
    q = np.array([0.05, 0.45, 0.05, 0.45], dtype=np.float64)
    x = np.arange(len(p), dtype=np.float64)

    log_ratio = np.log(p / q)
    exact_kl = float(np.sum(p * log_ratio))

    # Exact population optima.
    t_nwj = 1.0 + log_ratio
    nwj_full = float(np.sum(p * t_nwj) - np.sum(q * np.exp(t_nwj - 1.0)))

    g_dv = log_ratio
    dv_full = float(np.sum(p * g_dv) - math.log(float(np.sum(q * np.exp(g_dv)))))

    # Restricted linear critics. NWJ uses a*x+b; DV fixes the irrelevant intercept.
    def nwj_negative(params: np.ndarray) -> float:
        a, b = params
        t = a * x + b
        value = np.sum(p * t) - np.sum(q * np.exp(t - 1.0))
        return -float(value)

    nwj_result = minimize(nwj_negative, x0=np.zeros(2), method="BFGS")
    if not nwj_result.success:
        raise RuntimeError(f"NWJ critic optimization failed: {nwj_result.message}")
    a_nwj, b_nwj = nwj_result.x
    nwj_linear = -float(nwj_result.fun)

    def dv_negative(a_arr: np.ndarray) -> float:
        a = float(a_arr[0])
        g = a * x
        value = np.sum(p * g) - math.log(float(np.sum(q * np.exp(g))))
        return -float(value)

    dv_result = minimize(dv_negative, x0=np.zeros(1), method="BFGS")
    if not dv_result.success:
        raise RuntimeError(f"DV critic optimization failed: {dv_result.message}")
    a_dv = float(dv_result.x[0])
    dv_linear = -float(dv_result.fun)

    # Bayes classifier recovers the log density ratio for equal class priors.
    d_star = p / (p + q)
    classifier_logit = np.log(d_star / (1.0 - d_star))
    classifier_max_abs_error = float(np.max(np.abs(classifier_logit - log_ratio)))

    # Original GAN identity.
    m = 0.5 * (p + q)
    js = 0.5 * float(np.sum(p * np.log(p / m))) + 0.5 * float(np.sum(q * np.log(q / m)))
    gan_value = float(np.sum(p * np.log(d_star)) + np.sum(q * np.log(1.0 - d_star)))
    gan_identity_rhs = -math.log(4.0) + 2.0 * js

    labels = ["exact KL", "NWJ full", "DV full", "NWJ linear", "DV linear"]
    values = [exact_kl, nwj_full, dv_full, nwj_linear, dv_linear]
    fig, ax = plt.subplots(figsize=(9.0, 5.1))
    ax.bar(np.arange(len(labels)), values)
    ax.set_xticks(np.arange(len(labels)), labels, rotation=15, ha="right")
    ax.set_ylabel("nats")
    ax.set_title("Restricted critics leave a variational gap")
    ax.axhline(exact_kl, linestyle="--", linewidth=1.0, label="exact KL")
    ax.legend()
    fig.tight_layout()
    fig.savefig(OUT / "M11_experiment_variational_bounds.png", dpi=190)
    plt.close(fig)

    return {
        "p": p.tolist(),
        "q": q.tolist(),
        "log_density_ratio": log_ratio.tolist(),
        "exact_kl_nats": exact_kl,
        "nwj_full_nats": nwj_full,
        "dv_full_nats": dv_full,
        "nwj_linear_nats": nwj_linear,
        "dv_linear_nats": dv_linear,
        "nwj_linear_a": float(a_nwj),
        "nwj_linear_b": float(b_nwj),
        "dv_linear_a": a_dv,
        "classifier_logit_max_abs_error": classifier_max_abs_error,
        "js_nats": js,
        "gan_value": gan_value,
        "gan_identity_rhs": gan_identity_rhs,
        "gan_identity_abs_error": abs(gan_value - gan_identity_rhs),
    }


def gaussian_log_ratio(x: np.ndarray, y: np.ndarray, rho: float) -> np.ndarray:
    """Return log p(y|x)/p(y) for a standard jointly Gaussian pair."""
    var = 1.0 - rho * rho
    return -0.5 * math.log(var) - 0.5 * (y - rho * x) ** 2 / var + 0.5 * y**2


def estimate_infonce_bound(
    rho: float,
    k: int,
    n_trials: int,
    rng: np.random.Generator,
    chunk_size: int = 500,
) -> tuple[float, float, float]:
    """Monte Carlo InfoNCE bound using the Bayes-optimal score."""
    losses: list[np.ndarray] = []
    remaining = n_trials
    sigma = math.sqrt(1.0 - rho * rho)
    while remaining > 0:
        n = min(chunk_size, remaining)
        x = rng.standard_normal((n, 1))
        y_pos = rho * x + sigma * rng.standard_normal((n, 1))
        y_neg = rng.standard_normal((n, k - 1)) if k > 1 else np.empty((n, 0))
        y_all = np.concatenate([y_pos, y_neg], axis=1)
        scores = gaussian_log_ratio(x, y_all, rho)
        loss = logsumexp(scores, axis=1) - scores[:, 0]
        losses.append(loss)
        remaining -= n
    all_losses = np.concatenate(losses)
    mean_loss = float(np.mean(all_losses))
    stderr = float(np.std(all_losses, ddof=1) / math.sqrt(len(all_losses)))
    return math.log(k) - mean_loss, mean_loss, stderr


def infonce_experiment() -> dict[str, object]:
    rng = np.random.default_rng(20260713)
    rhos = [0.5, 0.9, 0.99, 0.9999]
    ks = [2, 4, 16, 64, 256]
    n_trials = 20_000

    rows: list[dict[str, float | int]] = []
    fig, ax = plt.subplots(figsize=(9.2, 5.6))
    for rho in rhos:
        exact = -0.5 * math.log(1.0 - rho * rho)
        bounds = []
        for k in ks:
            bound, loss, stderr = estimate_infonce_bound(rho, k, n_trials, rng)
            bounds.append(bound)
            rows.append({
                "rho": rho,
                "K": k,
                "exact_mi_nats": exact,
                "infonce_loss_nats": loss,
                "infonce_bound_nats": bound,
                "loss_stderr": stderr,
                "log_K": math.log(k),
            })
        ax.plot(ks, bounds, marker="o", label=fr"$\rho={rho}$ bound")
        ax.axhline(exact, linestyle="--", linewidth=0.9, alpha=0.55)
    ax.set_xscale("log", base=2)
    ax.set_xlabel("number of candidates K")
    ax.set_ylabel("nats")
    ax.set_title("InfoNCE approaches MI but is capped by log K")
    ax.legend(fontsize=8, ncol=2)
    fig.tight_layout()
    fig.savefig(OUT / "M11_experiment_infonce_saturation.png", dpi=190)
    plt.close(fig)

    return {"n_trials": n_trials, "rows": rows}


def support_geometry_experiment() -> dict[str, object]:
    theta = np.linspace(-3.0, 3.0, 601)
    nonzero = np.abs(theta) > 1e-12
    js = np.where(nonzero, math.log(2.0), 0.0)
    tv = np.where(nonzero, 1.0, 0.0)
    w1 = np.abs(theta)

    fig, ax = plt.subplots(figsize=(8.8, 5.1))
    ax.plot(theta, w1, label=r"$W_1(\delta_0,\delta_\theta)=|\theta|$")
    ax.plot(theta, js, label=r"$D_{JS}$")
    ax.plot(theta, tv, label="TV")
    ax.axvline(0.0, linewidth=0.8)
    ax.set_xlabel(r"shift $\theta$")
    ax.set_ylabel("discrepancy")
    ax.set_title("Ground geometry distinguishes disjoint supports")
    ax.legend()
    fig.tight_layout()
    fig.savefig(OUT / "M11_experiment_support_geometry.png", dpi=190)
    plt.close(fig)

    return {
        "theta_examples": [-2.0, -0.1, 0.0, 0.1, 2.0],
        "js_for_nonzero_theta": math.log(2.0),
        "tv_for_nonzero_theta": 1.0,
        "kl_for_nonzero_theta": "infinity",
        "w1_formula": "abs(theta)",
    }


def main() -> None:
    result = {
        "discrete_variational": discrete_variational_experiment(),
        "infonce": infonce_experiment(),
        "support_geometry": support_geometry_experiment(),
    }
    path = OUT / "M11_experiment_results.json"
    path.write_text(json.dumps(result, indent=2, ensure_ascii=False), encoding="utf-8")

    d = result["discrete_variational"]
    print("Exact KL:", f"{d['exact_kl_nats']:.9f}", "nats")
    print("NWJ full:", f"{d['nwj_full_nats']:.9f}", "nats")
    print("DV full:", f"{d['dv_full_nats']:.9f}", "nats")
    print("NWJ linear:", f"{d['nwj_linear_nats']:.9f}", "nats")
    print("DV linear:", f"{d['dv_linear_nats']:.9f}", "nats")
    print("Classifier logit max error:", f"{d['classifier_logit_max_abs_error']:.3e}")
    print("GAN identity error:", f"{d['gan_identity_abs_error']:.3e}")
    print("Saved results to", path)


if __name__ == "__main__":
    main()

Bonus. Total variation as both an f-divergence and an IPM

Show both identities:

TV⁡(P,Q)=12∫∣p−q∣,\operatorname{TV}(P,Q) = \frac12\int|p-q|,

and

TV⁡(P,Q)=sup⁡0≤h≤1∣EPh−EQh∣.\operatorname{TV}(P,Q) = \sup_{0\le h\le1} \left| \mathbb E_Ph- \mathbb E_Qh \right|.

Find an optimal hh using

A={x:p(x)≥q(x)}.A=\{x:p(x)\ge q(x)\}.
Show answer and solution

As an f-divergence, TV uses

f(t)=12∣t−1∣:f(t)=\frac12|t-1|: Df(P∥Q)=12∫∣p−q∣=TV⁡(P,Q).D_f(P\|Q) =\frac12\int|p-q| =\operatorname{TV}(P,Q).

Let

A={x:p(x)≥q(x)}.A=\{x:p(x)\ge q(x)\}.

For every 0≤h≤10\le h\le1,

EPh−EQh=∫h(p−q)≤∫A(p−q).\mathbb E_Ph-\mathbb E_Qh =\int h(p-q) \le\int_A(p-q).

Equality is attained by h∗=1Ah^*=\mathbf1_A, and

∫A(p−q)=12∫∣p−q∣=TV⁡(P,Q).\int_A(p-q) =\frac12\int|p-q| =\operatorname{TV}(P,Q).

Taking the complement handles the opposite sign, so

TV⁡(P,Q)=sup⁡0≤h≤1∣EPh−EQh∣.\boxed{ \operatorname{TV}(P,Q) = \sup_{0\le h\le1} \left| \mathbb E_Ph-\mathbb E_Qh \right| }.