Module 12. Information-theoretic generalization bounds: PAC-Bayes and mutual information
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 KBThe archive preserves the folder structure of nine Python solution files shared by RU/EN.
Exercise 1. Proving PAC-Bayes-kl
Reconstruct the proof from Section 12.4.
-
Start from
for fixed .
-
Integrate over and apply Markov's inequality.
-
Use the change-of-measure inequality
-
Apply joint convexity of binary KL.
-
Identify exactly where independence of from is required.
Show answer and solution
Let
For each fixed , Maurer's exponential-moment inequality gives
Because the prior is independent of , expectations can be exchanged:
Markov's inequality implies that, with probability at least ,
Fix a sample for which this event holds. For every posterior , the change-of-measure inequality gives
Therefore,
Binary KL is jointly convex, so
Combining the inequalities and dividing by gives
Where independence of the prior is used
The critical equality is
If is chosen from the same sample, then inside the first expectation the law of changes together with . The fixed-hypothesis exponential-moment inequality can no longer be applied without an additional charge for the data dependence of the prior.
Exercise 2. Zero empirical error
Suppose
-
Show that
-
Compare it with
-
Evaluate both bounds at .
-
Explain why exact inversion matters when training risk is nearly zero.
Show answer and solution
For , binary KL equals
Hence
implies
and finally
For small ,
Pinsker gives only
At , for example,
whereas
The difference is almost sevenfold. For models with zero or near-zero training error, the binary-KL bound should therefore not be replaced by its square-root corollary without a reason.
Exercise 3. A Gaussian posterior and
For
derive .
-
Isolate the term proportional to .
-
Explain the role of the variance terms.
-
Explain why this establishes a connection to regularization but not full equivalence with ordinary deterministic weight decay.
Show answer and solution
For
the Gaussian KL formula gives
That is,
The first term does have the form of an penalty. But the complete PAC-Bayes objective also contains:
- a variance term;
- the empirical Gibbs risk, rather than the loss of a network with mean weights;
- the scale ;
- a confidence term inside the certificate itself;
- a validity requirement on the prior.
Ordinary deterministic weight decay is therefore related to the Gaussian PAC-Bayes penalty, but not identical to the whole procedure.
Exercise 4. Adaptive selection and MI
There are hypotheses with identical Bernoulli population loss . On a sample of size , the algorithm selects the hypothesis with the smallest empirical loss.
-
Why is the winner's expected empirical loss at most ? Give an example of equality and a sufficient condition for a strict decrease.
-
If selection is deterministic, why is
-
For losses in , derive
-
What changes under tempered Gibbs selection?
Show answer and solution
Every hypothesis has the same population risk , while its empirical risk is random. Taking the minimum cannot increase the expected estimate:
Equality is possible even when . Suppose all hypotheses incur the same loss on each observation, with independent Bernoulli losses across observations. Then all coincide, and their minimum has expectation . With a fixed tie-breaking rule, such as choosing the smallest index, is constant and .
A sufficient condition for a strict decrease is , , , and independent Bernoulli losses across candidates and observations. The event that the first candidate has loss 1 on every observation while the second has loss 0 on every observation then has positive probability. On that event, ; on every other outcome, the minimum is at most . Its expectation is therefore strictly below .
If the selection is deterministic, then
Consequently,
Since takes at most values,
For a loss in the MI bound may use :
Temperature-controlled Gibbs selection defines the distribution
As it approaches argmin, and as it approaches an almost uniform choice. Raising the temperature increases
which lowers
The price is a less aggressive use of the empirical fit.
Exercise 5. The exact average-KL decomposition
Prove
Then answer the following.
-
Which minimizes the left side on average?
-
Why is it called an oracle prior?
-
Under what knowledge would it be theoretically admissible in the standard theorem?
-
Why can it not be estimated from the same sample without a correction?
Show answer and solution
Let
and let be the marginal law of . Then
The second term is nonnegative, so the minimum over is attained at
Then
This distribution is called an oracle prior, because building it generally requires the law of the algorithm output over all datasets drawn from the unknown data distribution .
Unavailability should not be confused with invalidity. If and the conditional law of the algorithm were known, then the marginal
would be defined before the realized certification sample is observed. In that idealized setting, is a fixed and theoretically admissible prior for the standard PAC-Bayes theorem.
In practice is unknown, which invites estimating from the current sample and substituting that estimate into the certificate. This is a different object: the prior then depends on the certification data. It requires a separate argument—sample splitting, a hierarchical construction, a differential-privacy correction, or a theorem designed for data-dependent priors.
Exercise 6. Python: selection bias and a PAC-Bayes certificate
Run
M12_generalization_experiments.pyThe script performs two experiments.
-
Adaptive selection among 32 equally good hypotheses:
- compare selection bias with the MI bound;
- track the effect of temperature;
- verify the exact average-KL decomposition.
-
A stochastic linear classifier:
- compare empirical Gibbs risk, diagnostic test risk, and the PAC-Bayes-kl certificate;
- find the posterior variance that minimizes the certificate;
- compare exact binary-KL inversion with the square-root corollary.
Explain why test risk is not part of the certificate.
Show answer and solution
The script reproduces two experiments.
Part 1. Adaptive selection
For argmin:
and the MI bound is about
At temperature :
At both the mutual information and the gap become almost zero, because the algorithm has nearly stopped choosing the winner from the data.
For the script also checks
to within Monte Carlo noise.
Part 2. A stochastic linear classifier
The best posterior standard deviation among those tested is
For it:
The square-root Pinsker corollary gives about
that is, a slightly weaker result.
Test risk does not enter the certificate. It is used only to check how conservative the certificate is in a synthetic world where a large independent test set can be generated.
Code solution · the same source file for RU/EN
from __future__ import annotations
from pathlib import Path
import json
import math
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
from scipy.special import expit, ndtr
ROOT = Path(__file__).resolve().parent
OUT = ROOT / "generated_experiment"
OUT.mkdir(exist_ok=True)
SEED = 202612
def binary_kl(q: float, p: float) -> float:
"""Bernoulli KL kl(q || p), in nats."""
eps = 1e-15
q = float(np.clip(q, eps, 1 - eps))
p = float(np.clip(p, eps, 1 - eps))
return q * math.log(q / p) + (1 - q) * math.log((1 - q) / (1 - p))
def kl_upper_inverse(q: float, c: float) -> float:
"""Largest p in [q,1] such that kl(q || p) <= c."""
if c <= 0:
return float(q)
lo = float(q)
hi = 1.0 - 1e-12
if binary_kl(q, hi) <= c:
return 1.0
for _ in range(100):
mid = 0.5 * (lo + hi)
if binary_kl(q, mid) <= c:
lo = mid
else:
hi = mid
return lo
def softmax_rows(logits: np.ndarray) -> np.ndarray:
shifted = logits - np.max(logits, axis=1, keepdims=True)
weights = np.exp(shifted)
return weights / np.sum(weights, axis=1, keepdims=True)
def experiment_adaptive_selection() -> dict:
"""Adaptive selection among equally good hypotheses.
Each data point is a vector of M Bernoulli losses. All hypotheses have the
same population risk p. The algorithm chooses a hypothesis using a Gibbs
distribution over empirical losses. This makes the selection bias and the
MI bound directly visible.
"""
rng = np.random.default_rng(SEED)
m = 32
n = 80
true_risk = 0.20
trials = 120_000
empirical = rng.binomial(n, true_risk, size=(trials, m)) / n
temperatures = [0.0, 0.01, 0.02, 0.05, 0.10, 0.20, 0.50]
rows: list[dict] = []
# A deliberately mismatched fixed prior for the KL decomposition demo.
skew_prior = np.full(m, 0.5 / (m - 1))
skew_prior[0] = 0.5
for tau in temperatures:
if tau == 0.0:
minima = np.min(empirical, axis=1, keepdims=True)
ties = empirical == minima
q_cond = ties / np.sum(ties, axis=1, keepdims=True)
else:
q_cond = softmax_rows(-empirical / tau)
marginal = np.mean(q_cond, axis=0)
eps = 1e-300
h_marginal = -float(np.sum(marginal * np.log(np.clip(marginal, eps, None))))
h_conditional = -float(
np.mean(np.sum(q_cond * np.log(np.clip(q_cond, eps, None)), axis=1))
)
mutual_information = h_marginal - h_conditional
selected_empirical = float(np.mean(np.sum(q_cond * empirical, axis=1)))
expected_gap = true_risk - selected_empirical
# A [0,1]-valued loss is 1/2-sub-Gaussian by Hoeffding's lemma,
# hence sigma^2 = 1/4 and the Xu-Raginsky bound is sqrt(I/(2n)).
mi_bound = math.sqrt(mutual_information / (2 * n))
avg_kl_uniform = float(
np.mean(np.sum(q_cond * (np.log(np.clip(q_cond, eps, None)) + math.log(m)), axis=1))
)
avg_kl_skew = float(
np.mean(
np.sum(
q_cond
* (
np.log(np.clip(q_cond, eps, None))
- np.log(skew_prior)[None, :]
),
axis=1,
)
)
)
marginal_kl_skew = float(
np.sum(marginal * (np.log(np.clip(marginal, eps, None)) - np.log(skew_prior)))
)
rows.append(
{
"temperature": tau,
"mutual_information_nats": mutual_information,
"expected_empirical_risk": selected_empirical,
"population_risk": true_risk,
"expected_generalization_gap": expected_gap,
"mi_bound": mi_bound,
"marginal_entropy_nats": h_marginal,
"conditional_entropy_nats": h_conditional,
"avg_kl_to_uniform_prior": avg_kl_uniform,
"avg_kl_to_skew_prior": avg_kl_skew,
"marginal_kl_to_skew_prior": marginal_kl_skew,
}
)
fig, ax = plt.subplots(figsize=(9.6, 5.6))
x = np.array([r["mutual_information_nats"] for r in rows])
gap = np.array([r["expected_generalization_gap"] for r in rows])
bound = np.array([r["mi_bound"] for r in rows])
ax.plot(x, gap, marker="o", label="estimated expected gap")
ax.plot(x, bound, marker="s", linestyle="--", label="MI upper bound")
for r in rows:
label = "argmin" if r["temperature"] == 0 else f"τ={r['temperature']:g}"
ax.annotate(label, (r["mutual_information_nats"], r["expected_generalization_gap"]),
xytext=(4, 5), textcoords="offset points", fontsize=8)
ax.set_xlabel("I(W; S), nats")
ax.set_ylabel("expected generalization gap")
ax.set_title("Adaptive selection: less information about the sample, less selection bias")
ax.legend()
ax.grid(alpha=0.25)
fig.tight_layout()
fig.savefig(OUT / "M12_experiment_adaptive_selection.png", dpi=180)
plt.close(fig)
# Decomposition for tau=0.05.
chosen = next(r for r in rows if abs(r["temperature"] - 0.05) < 1e-12)
terms = [
chosen["mutual_information_nats"],
chosen["marginal_kl_to_skew_prior"],
chosen["avg_kl_to_skew_prior"],
]
fig, ax = plt.subplots(figsize=(8.8, 5.2))
ax.bar(["I(W;S)", "KL(P_W || P)", "E KL(Q_S || P)"], terms)
ax.set_ylabel("nats")
ax.set_title("Exact decomposition of average posterior-to-prior KL")
ax.text(2, terms[2] + 0.02, "sum of the first two terms", ha="center", fontsize=9)
ax.grid(axis="y", alpha=0.25)
fig.tight_layout()
fig.savefig(OUT / "M12_experiment_kl_decomposition.png", dpi=180)
plt.close(fig)
return {
"M": m,
"n": n,
"true_risk": true_risk,
"trials": trials,
"rows": rows,
}
def fit_logistic_regression(x: np.ndarray, y: np.ndarray) -> np.ndarray:
"""Simple deterministic full-batch logistic regression."""
n, d = x.shape
w = np.zeros(d)
learning_rate = 0.10
l2 = 1e-3
for _ in range(1800):
margins = y * (x @ w)
grad = -(x.T @ (y * expit(-margins))) / n + l2 * w
w -= learning_rate * grad
return w
def gaussian_kl_isotropic(mean: np.ndarray, sigma_q: float, sigma_p: float) -> float:
k = mean.size
return 0.5 * float(
np.sum((sigma_q**2 + mean**2) / sigma_p**2 - 1 + math.log(sigma_p**2 / sigma_q**2))
)
def gibbs_linear_risk(x: np.ndarray, y: np.ndarray, mean: np.ndarray, sigma_q: float) -> float:
"""Exact Gibbs 0-1 risk for W~N(mean, sigma_q^2 I) and sign(W^T x)."""
signed_mean = y * (x @ mean)
std = sigma_q * np.linalg.norm(x, axis=1)
z = -signed_mean / np.maximum(std, 1e-15)
return float(np.mean(ndtr(z)))
def experiment_pac_bayes_classifier() -> dict:
rng = np.random.default_rng(SEED + 1)
n_train = 4000
n_test = 80_000
d = 5
delta = 0.05
sigma_prior = 1.0
true_w = rng.normal(size=d)
true_w /= np.linalg.norm(true_w)
def sample_data(n: int) -> tuple[np.ndarray, np.ndarray]:
x = rng.normal(size=(n, d))
prob = expit(2.0 * (x @ true_w))
y = np.where(rng.random(n) < prob, 1.0, -1.0)
x_aug = np.column_stack([x, np.ones(n)])
return x_aug, y
x_train, y_train = sample_data(n_train)
x_test, y_test = sample_data(n_test)
mean = fit_logistic_regression(x_train, y_train)
deterministic_train = float(np.mean(y_train * (x_train @ mean) <= 0))
deterministic_test = float(np.mean(y_test * (x_test @ mean) <= 0))
sigmas = [0.03, 0.05, 0.10, 0.20, 0.30, 0.50, 0.80, 1.00]
rows: list[dict] = []
confidence_term = math.log(2 * math.sqrt(n_train) / delta)
for sigma_q in sigmas:
empirical_gibbs = gibbs_linear_risk(x_train, y_train, mean, sigma_q)
test_gibbs = gibbs_linear_risk(x_test, y_test, mean, sigma_q)
kl = gaussian_kl_isotropic(mean, sigma_q, sigma_prior)
complexity = (kl + confidence_term) / n_train
kl_bound = kl_upper_inverse(empirical_gibbs, complexity)
pinsker_bound = min(1.0, empirical_gibbs + math.sqrt(complexity / 2))
rows.append(
{
"sigma_posterior": sigma_q,
"empirical_gibbs_risk": empirical_gibbs,
"test_gibbs_risk": test_gibbs,
"kl_q_p_nats": kl,
"pac_bayes_kl_bound": kl_bound,
"pinsker_square_root_bound": pinsker_bound,
}
)
best = min(rows, key=lambda r: r["pac_bayes_kl_bound"])
fig, ax = plt.subplots(figsize=(9.6, 5.6))
xs = np.array(sigmas)
ax.plot(xs, [r["empirical_gibbs_risk"] for r in rows], marker="o", label="empirical Gibbs risk")
ax.plot(xs, [r["test_gibbs_risk"] for r in rows], marker="o", label="test Gibbs risk")
ax.plot(xs, [r["pac_bayes_kl_bound"] for r in rows], marker="s", label="PAC-Bayes-kl certificate")
ax.plot(xs, [r["pinsker_square_root_bound"] for r in rows], linestyle="--", label="square-root corollary")
ax.axhline(deterministic_test, linestyle=":", label="deterministic mean test error")
ax.set_xscale("log")
ax.set_xlabel("posterior standard deviation σ_Q")
ax.set_ylabel("0-1 risk")
ax.set_title("Stochastic linear classifier: empirical fit, KL complexity, and certificate")
ax.legend()
ax.grid(alpha=0.25)
fig.tight_layout()
fig.savefig(OUT / "M12_experiment_pac_bayes_classifier.png", dpi=180)
plt.close(fig)
return {
"n_train": n_train,
"n_test": n_test,
"dimension_with_bias": d + 1,
"delta": delta,
"sigma_prior": sigma_prior,
"mean_norm": float(np.linalg.norm(mean)),
"deterministic_train_error": deterministic_train,
"deterministic_test_error": deterministic_test,
"rows": rows,
"best_by_pac_bayes_kl": best,
}
def main() -> None:
results = {
"adaptive_selection": experiment_adaptive_selection(),
"pac_bayes_classifier": experiment_pac_bayes_classifier(),
}
(OUT / "M12_experiment_results.json").write_text(
json.dumps(results, indent=2, ensure_ascii=False), encoding="utf-8"
)
best = results["pac_bayes_classifier"]["best_by_pac_bayes_kl"]
print("Adaptive selection (argmin):")
row0 = results["adaptive_selection"]["rows"][0]
print(f" I(W;S): {row0['mutual_information_nats']:.6f} nats")
print(f" expected gap: {row0['expected_generalization_gap']:.6f}")
print(f" MI bound: {row0['mi_bound']:.6f}")
print("Best stochastic classifier certificate:")
print(f" sigma_Q: {best['sigma_posterior']:.3f}")
print(f" empirical Gibbs risk: {best['empirical_gibbs_risk']:.6f}")
print(f" test Gibbs risk: {best['test_gibbs_risk']:.6f}")
print(f" KL(Q||P): {best['kl_q_p_nats']:.6f} nats")
print(f" PAC-Bayes-kl bound: {best['pac_bayes_kl_bound']:.6f}")
if __name__ == "__main__":
main()
Bonus. A valid prior learned from part of the data
Split the sample into and .
-
Build from .
-
Train .
-
Conditional on , state a PAC-Bayes bound on the independent subset .
-
Explain the trade-off between prior quality and certification-sample size.
Show answer and solution
Split the sample:
where and are disjoint i.i.d. parts and therefore independent conditional on .
From build the prior
Now condition on a particular value of . After that conditioning, is fixed and independent of the random certification sample , so the standard PAC-Bayes theorem applies:
The posterior may depend on both and , because once the prior is fixed the bound holds simultaneously for all .
The trade-off:
- a larger allows a more informative prior and a smaller KL;
- a smaller weakens the concentration term and shrinks the effective certification sample.