MAP estimation and conjugate priors
Add a prior, compute the posterior, and use conjugacy to keep it in closed form.
Maximum likelihood trusts the data alone. When data are scarce or you hold prior belief about a parameter, Bayesian inference combines both: it treats the parameter as random, places a prior on it, and updates to a posterior[1]. Conjugate priors keep that update in closed form, and MAP estimation reads off its peak.
The posterior over parameters
Treat the parameter as a random variable with prior density . Bayes' rule gives the posterior after observing data :
The evidence is a constant in , so for finding the shape (and the peak) of the posterior it can be ignored: posterior likelihood prior.
MAP estimation
The maximum a posteriori (MAP) estimate is the posterior mode:
It is maximum likelihood plus an additive prior term. With a Gaussian prior on weights, , and MAP becomes likelihood maximization with an L2 (ridge) penalty. A Laplace prior gives an L1 penalty. Regularization is a prior in disguise.
As data accumulate, the likelihood term grows with while the prior term stays fixed, so the MAP estimate converges to the MLE: the prior washes out.
Conjugate priors
A prior is conjugate to a likelihood if the posterior belongs to the same family as the prior[2]. Then updating only changes the family's parameters, with no integration required.
Beta-Bernoulli. For Bernoulli() trials with successes, the likelihood is . The conjugate prior is the Beta distribution . Multiplying,
which is . The update just adds successes to and failures to . The prior parameters act as pseudo-counts: prior belief stated as "as if I had already seen successes and failures".
The posterior mean and mode are
The MAP mode reduces to the MLE when (a flat prior).
Check it on arrays
import numpy as np
rng = np.random.default_rng(12)
# True coin bias, small sample so the prior matters
true_q = 0.7
n = 20
data = (rng.random(n) < true_q).astype(int)
k = data.sum()
print(f"observed {k} heads in {n} flips; MLE = {k/n:.3f}")
# Beta(2,2) prior (mild belief near 0.5), conjugate update
alpha0, beta0 = 2.0, 2.0
alpha_post, beta_post = alpha0 + k, beta0 + (n - k)
post_mean = alpha_post / (alpha_post + beta_post)
post_mode = (alpha_post - 1) / (alpha_post + beta_post - 2)
print(f"posterior Beta({alpha_post}, {beta_post})")
print(f"posterior mean {post_mean:.3f}, MAP mode {post_mode:.3f}")
# Verify conjugacy numerically: posterior ∝ likelihood * prior on a grid
q = np.linspace(1e-3, 1 - 1e-3, 5000)
dq = q[1] - q[0]
prior = q**(alpha0 - 1) * (1 - q)**(beta0 - 1)
like = q**k * (1 - q)**(n - k)
post = prior * like
post /= post.sum() * dq # normalize (Riemann sum)
beta_pdf = q**(alpha_post - 1) * (1 - q)**(beta_post - 1)
beta_pdf /= beta_pdf.sum() * dq
print("max |grid posterior - Beta pdf|:", round(np.max(np.abs(post - beta_pdf)), 6))
print("grid MAP:", round(q[np.argmax(post)], 3))The conjugate update gives a Beta posterior whose density matches the likelihood-times-prior computed on a grid, and the MAP mode pulls the estimate toward the prior on this small sample.
Exercises
Exercise 1. Start from a prior on a coin's bias . You observe heads in flips. Give the posterior distribution and compute its mean and MAP mode.
Solution
Step 1. The Beta-Bernoulli update adds successes to and failures to . With , , , :
Step 2. Posterior mean .
Step 3. MAP mode . Both sit below the MLE , pulled toward the prior mean .
Exercise 2. Show that with a flat prior, the MAP mode equals the MLE , and explain why this is the prior that adds no information.
Solution
Step 1. The MAP mode formula is . Substitute :
which is the MLE.
Step 2. The prior parameters act as pseudo-counts: adds prior successes and prior failures. With zero pseudo-counts the prior contributes nothing to the mode, and is the uniform density on , flat over all biases.
Exercise 3 (code). Confirm the conjugate update numerically. Compute the normalized likelihood-times-prior on a grid for the Exercise 1 setting, and check that its mode matches the closed-form MAP and its mean matches .
Solution
import numpy as np
alpha0, beta0 = 2.0, 2.0
k, n = 8, 10
q = np.linspace(1e-4, 1 - 1e-4, 200_000)
dq = q[1] - q[0]
prior = q**(alpha0 - 1) * (1 - q)**(beta0 - 1)
like = q**k * (1 - q)**(n - k)
post = prior * like
post /= post.sum() * dq # normalize (Riemann sum)
grid_mode = q[np.argmax(post)]
grid_mean = (q * post).sum() * dq
assert np.isclose(grid_mode, 0.75, atol=1e-3)
assert np.isclose(grid_mean, 5 / 7, atol=1e-3)
print("grid MAP:", round(grid_mode, 4), " grid mean:", round(grid_mean, 4))The grid posterior peaks at and averages to , matching the closed-form Beta(, ) mode and mean.
The next lesson shifts from estimating distributions to quantifying their information content, starting with entropy.