Bayesian vs Frequentist Thinking
Two fundamentally different interpretations of probability:
| Frequentist | Bayesian | |
|---|---|---|
| Probability means | Long-run frequency | Degree of belief |
| Parameters | Fixed unknowns | Random variables with distributions |
| Data | Repeated samples | Fixed, observed |
| Inference output | Point estimate + confidence interval | Full posterior distribution |
| Prior knowledge | Not used | Explicitly encoded as prior |
| Uncertainty | Frequentist CI (misinterpreted as credible interval) | Credible interval with correct probability interpretation |
The Bayesian Update
- Prior : what we believe about parameters before seeing data
- Likelihood : how probable the observed data is given parameters
- Posterior : updated belief after observing data
- Evidence : normalizing constant (often intractable)
Conjugate Priors and Closed-Form Posteriors
When the prior and likelihood are conjugate, the posterior has the same distributional family as the prior — yielding an analytic solution.
| Likelihood | Conjugate Prior | Posterior |
|---|---|---|
| Bernoulli (coin flip) | Beta() | Beta() |
| Poisson (counts) | Gamma() | Gamma() |
| Gaussian (known ) | Gaussian() | Gaussian (weighted combination) |
| Multinomial | Dirichlet | Dirichlet (add counts) |
Example: Coin Flip (Beta-Binomial)
Observe 7 heads in 10 flips. Prior: (weakly biased toward 0.5):
Posterior mean: (pulled toward prior — regularization effect).
Bayesian regularization: a Gaussian prior on weights L2 regularization. A Laplace prior L1 (LASSO).
Gaussian Processes (GPs)
A Gaussian Process is a distribution over functions:
- : mean function (often set to 0)
- : kernel / covariance function — encodes assumptions about smoothness, periodicity, etc.
Key insight: any finite collection of function values is jointly Gaussian:
where .
GP Regression (Prediction)
Given observations with , :
The posterior gives a predictive mean and uncertainty bands at every test point.
Common Kernels
| Kernel | Assumption | Use Case |
|---|---|---|
| RBF / Squared Exponential | Infinitely smooth | Default; smooth functions |
| Matérn 5/2 | Twice-differentiable | More realistic; robust |
| Periodic | Repeating patterns | Seasonal data |
| Linear | Linear functions | Bayesian linear regression |
| Rational Quadratic | Multi-scale smoothness | Mixture of RBF scales |
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import RBF, Matern, WhiteKernel, ConstantKernel
# ---- True latent function (unknown to the model) ----
def f_true(x):
return np.sin(3 * x) + 0.5 * x
np.random.seed(42)
X_train = np.sort(np.random.uniform(0, 5, 10)).reshape(-1, 1)
y_train = f_true(X_train.ravel()) + np.random.normal(0, 0.3, 10)
X_test = np.linspace(-0.5, 5.5, 200).reshape(-1, 1)
# ---- GP with RBF kernel ----
kernel = ConstantKernel(1.0) * RBF(length_scale=1.0) + WhiteKernel(noise_level=0.1)
gp = GaussianProcessRegressor(kernel=kernel, n_restarts_optimizer=10, normalize_y=True)
gp.fit(X_train, y_train)
mu, sigma = gp.predict(X_test, return_std=True)
print(f"Optimized kernel: {gp.kernel_}")
print(f"Log-marginal-likelihood: {gp.log_marginal_likelihood(gp.kernel_.theta):.3f}")
# ---- GP with Matérn kernel (more robust) ----
kernel_m = ConstantKernel(1.0) * Matern(length_scale=1.0, nu=2.5) + WhiteKernel(0.1)
gp_m = GaussianProcessRegressor(kernel=kernel_m, n_restarts_optimizer=10, normalize_y=True)
gp_m.fit(X_train, y_train)
mu_m, sigma_m = gp_m.predict(X_test, return_std=True)
# ---- Draw posterior samples ----
samples = gp.sample_y(X_test, n_samples=5, random_state=1)
print(f"
At x=2.5:")
idx = np.argmin(np.abs(X_test.ravel() - 2.5))
print(f" Predictive mean: {mu[idx]:.3f}")
print(f" Predictive std: {sigma[idx]:.3f}")
print(f" 95% CI: [{mu[idx]-1.96*sigma[idx]:.3f}, {mu[idx]+1.96*sigma[idx]:.3f}]")
print(f" True value: {f_true(2.5):.3f}")
# Uncertainty increases in regions far from training data (x < 0, x > 5)
far_idx = np.argmin(np.abs(X_test.ravel() - 5.3))
print(f"
At x=5.3 (extrapolation):")
print(f" Predictive std: {sigma[far_idx]:.3f} (should be higher)")Bayesian Optimization (BO)
Bayesian Optimization efficiently optimizes expensive black-box functions — those with no gradient, slow to evaluate, and potentially noisy.
Classic use: hyperparameter tuning where = validation accuracy after full training run.
The BO Loop
1. Initialize: evaluate f at a few random points
2. Fit a surrogate model (usually a GP) to observed {x_i, f(x_i)}
3. Maximize acquisition function α(x) to select next x_candidate
4. Evaluate f(x_candidate) (the expensive step)
5. Update surrogate with new observation
6. Repeat until budget exhausted
Acquisition Functions
| Function | Idea | Exploration vs Exploitation |
|---|---|---|
| Expected Improvement (EI) | Balanced (most popular) | |
| Upper Confidence Bound (UCB) | Tunable via | |
| Probability of Improvement (PI) | More exploitative | |
| Thompson Sampling | Sample from posterior, pick max | Randomized exploration |
EI is preferred in practice — it naturally balances exploration (high ) and exploitation (high ) without a tuning parameter.
import numpy as np
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import Matern
from scipy.stats import norm
from scipy.optimize import minimize
# ---- Black-box function (simulate expensive ML training) ----
def black_box(x):
"""Noisy function with global max near x=2.2"""
return -(x - 2.2)**2 + np.sin(5*x) + np.random.normal(0, 0.05)
# ---- Expected Improvement acquisition ----
def expected_improvement(x, gp, y_best, xi=0.01):
x = np.atleast_2d(x)
mu, sigma = gp.predict(x, return_std=True)
z = (mu - y_best - xi) / (sigma + 1e-9)
ei = (mu - y_best - xi) * norm.cdf(z) + sigma * norm.pdf(z)
return -ei.ravel() # minimize negative EI
# ---- Bayesian Optimization loop ----
np.random.seed(7)
X_obs = np.random.uniform(0, 5, 3).reshape(-1, 1)
y_obs = np.array([black_box(x[0]) for x in X_obs])
kernel = Matern(nu=2.5)
gp = GaussianProcessRegressor(kernel=kernel, alpha=1e-6, n_restarts_optimizer=5)
n_iter = 15
for i in range(n_iter):
gp.fit(X_obs, y_obs)
y_best = y_obs.max()
# Maximize EI by multi-start minimization
best_x, best_ei = None, np.inf
for x0 in np.random.uniform(0, 5, 20):
result = minimize(expected_improvement, x0, args=(gp, y_best),
bounds=[(0, 5)], method='L-BFGS-B')
if result.fun < best_ei:
best_ei = result.fun
best_x = result.x[0]
y_new = black_box(best_x)
X_obs = np.vstack([X_obs, [[best_x]]])
y_obs = np.append(y_obs, y_new)
print(f"Iter {i+1:2d}: x={best_x:.3f}, f(x)={y_new:.3f}, best so far={y_obs.max():.3f}")
print(f"
BO found maximum at x={X_obs[y_obs.argmax(), 0]:.3f} (true: ~2.2)")
# ---- Same thing with Optuna (production BO) ----
import optuna
optuna.logging.set_verbosity(optuna.logging.WARNING)
def objective(trial):
x = trial.suggest_float('x', 0, 5)
return -(x - 2.2)**2 + np.sin(5*x) # no noise for Optuna demo
study = optuna.create_study(direction='maximize',
sampler=optuna.samplers.TPESampler(seed=42))
study.optimize(objective, n_trials=20)
print(f"
Optuna best: x={study.best_params['x']:.3f}, f={study.best_value:.3f}")Approximate Bayesian Inference
For complex models, the posterior is intractable. Two major approximation families:
Markov Chain Monte Carlo (MCMC)
Samples from the posterior by constructing a Markov chain whose stationary distribution is .
- Metropolis-Hastings: propose → accept/reject based on likelihood ratio
- Hamiltonian Monte Carlo (HMC): uses gradient information for efficient exploration
- NUTS (No-U-Turn Sampler): adaptive HMC — used in PyMC and Stan
- Pros: asymptotically exact, full posterior
- Cons: slow for large datasets/models, convergence diagnostics needed
Variational Inference (VI)
Approximate the posterior with a simpler distribution from a tractable family by minimizing KL divergence:
Equivalently, maximize the ELBO (Evidence Lower BOund):
- Mean-field VI: — factored, fast
- Stochastic VI: mini-batch gradients for large data
- Pros: fast, scalable, GPU-friendly (used in VAEs, Bayesian neural nets)
- Cons: underestimates uncertainty, mode-seeking behavior
Practical Summary
| Method | Accuracy | Speed | Scalability |
|---|---|---|---|
| Conjugate Bayes | Exact | Instant | Small models only |
| MCMC (NUTS) | Near-exact | Slow | Medium data |
| Variational Inference | Approximate | Fast | Large data/models |
| Laplace Approximation | Approximate | Fast | Point estimate + curvature |
Knowledge check
A team wants to tune 5 hyperparameters of an XGBoost model where each training run takes 3 hours. They can run at most 50 evaluations. Which optimization strategy is best?
Summary
- Bayesian learning: treat parameters as distributions; update from prior → posterior using Bayes' theorem
- Conjugate priors give analytic posteriors; Gaussian prior on weights = L2 regularization
- Gaussian Processes: nonparametric Bayesian models — distributions over functions with principled uncertainty quantification
- Bayesian Optimization: use a GP surrogate + acquisition function to efficiently optimize expensive black-box functions (hyperparameter tuning, NAS, AutoML)
- MCMC: asymptotically exact posterior sampling — use when precision matters and data is moderate
- Variational Inference: fast approximate inference — use for large-scale models and deep learning
Bayesian methods underpin many modern ML systems: uncertainty-aware predictions, active learning, AutoML, and safe reinforcement learning.