Files
optimiz-rs/examples/notebooks/02_mcmc_tutorial.ipynb
T
Melvin Alvarez cafb3476a4 docs: fix 404 broken links
- Replace non-existent Python examples with actual files
- Fix all placeholder yourusername URLs to ThotDjehuty
- Remove references to non-existent optimal_control.md theory doc
- Update examples to reference: hmm_regime_detection.py, parallel_de_benchmark.py, polaroid_optimizr_integration.py, timeseries_integration.py
2026-01-06 14:36:08 +01:00

487 lines
16 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "dc5d5825",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"OptimizR MCMC Module Loaded!\n"
]
}
],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"from scipy import stats\n",
"from optimizr import mcmc_sample\n",
"\n",
"np.random.seed(42)\n",
"print(\"OptimizR MCMC Module Loaded!\")"
]
},
{
"cell_type": "markdown",
"id": "ddfbd617",
"metadata": {},
"source": [
"## Example 1: Inferring Parameters of a Normal Distribution\n",
"\n",
"Given observed data $\\{x_1, \\ldots, x_n\\}$, infer $\\mu$ and $\\sigma$.\n",
"\n",
"### Likelihood\n",
"$$L(\\mu, \\sigma | \\mathbf{x}) = \\prod_{i=1}^n \\frac{1}{\\sqrt{2\\pi\\sigma^2}} \\exp\\left(-\\frac{(x_i - \\mu)^2}{2\\sigma^2}\\right)$$\n",
"\n",
"### Log-Likelihood\n",
"$$\\log L(\\mu, \\sigma | \\mathbf{x}) = -\\frac{n}{2}\\log(2\\pi) - n\\log(\\sigma) - \\frac{1}{2\\sigma^2}\\sum_{i=1}^n (x_i - \\mu)^2$$"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f929c18e",
"metadata": {},
"outputs": [],
"source": [
"# Generate synthetic data\n",
"true_mu = 5.0\n",
"true_sigma = 2.0\n",
"n_obs = 100\n",
"\n",
"observed_data = np.random.normal(true_mu, true_sigma, n_obs)\n",
"\n",
"print(f\"True parameters: μ={true_mu}, σ={true_sigma}\")\n",
"print(f\"Sample mean: {observed_data.mean():.3f}\")\n",
"print(f\"Sample std: {observed_data.std():.3f}\")\n",
"\n",
"# Plot data\n",
"plt.figure(figsize=(10, 5))\n",
"plt.hist(observed_data, bins=20, density=True, alpha=0.6, color='skyblue', edgecolor='black')\n",
"x_range = np.linspace(observed_data.min(), observed_data.max(), 100)\n",
"plt.plot(x_range, stats.norm.pdf(x_range, true_mu, true_sigma), \n",
" 'r-', linewidth=2, label=f'True: N({true_mu}, {true_sigma}²)')\n",
"plt.xlabel('Value', fontsize=12)\n",
"plt.ylabel('Density', fontsize=12)\n",
"plt.title('Observed Data Distribution', fontsize=14, fontweight='bold')\n",
"plt.legend()\n",
"plt.grid(alpha=0.3)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "5626ba97",
"metadata": {},
"source": [
"### Define Log-Likelihood Function"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "37ac93a1",
"metadata": {},
"outputs": [],
"source": [
"def log_likelihood_normal(params, data):\n",
" \"\"\"\n",
" Log-likelihood for Normal(μ, σ²) given data.\n",
" \n",
" Args:\n",
" params: [μ, σ]\n",
" data: observed data points\n",
" \"\"\"\n",
" mu, sigma = params\n",
" \n",
" # Ensure sigma is positive\n",
" if sigma <= 0:\n",
" return -np.inf\n",
" \n",
" n = len(data)\n",
" residuals = (data - mu) / sigma\n",
" \n",
" log_lik = -0.5 * n * np.log(2 * np.pi)\n",
" log_lik -= n * np.log(sigma)\n",
" log_lik -= 0.5 * np.sum(residuals**2)\n",
" \n",
" return log_lik\n",
"\n",
"# Test the function\n",
"test_params = [5.0, 2.0]\n",
"print(f\"Log-likelihood at true params: {log_likelihood_normal(test_params, observed_data):.2f}\")"
]
},
{
"cell_type": "markdown",
"id": "a36ddef7",
"metadata": {},
"source": [
"### Run MCMC Sampling"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "60e64783",
"metadata": {},
"outputs": [],
"source": [
"# MCMC parameters\n",
"initial_params = [0.0, 1.0] # Start far from true values\n",
"param_bounds = [(-10, 10), (0.1, 10)] # μ ∈ [-10, 10], σ ∈ [0.1, 10]\n",
"proposal_std = [0.5, 0.2] # Proposal step sizes\n",
"n_samples = 20000\n",
"burn_in = 2000\n",
"\n",
"print(\"Running MCMC sampling...\")\n",
"samples, acceptance_rate = mcmc_sample(\n",
" log_likelihood_fn=log_likelihood_normal,\n",
" data=observed_data,\n",
" initial_params=initial_params,\n",
" param_bounds=param_bounds,\n",
" proposal_std=proposal_std,\n",
" n_samples=n_samples,\n",
" burn_in=burn_in\n",
")\n",
"\n",
"print(f\"\\nAcceptance rate: {acceptance_rate:.2%}\")\n",
"print(f\"Generated {len(samples)} samples after burn-in\")\n",
"print(f\"\\nPosterior estimates:\")\n",
"print(f\"μ: {samples[:, 0].mean():.3f} ± {samples[:, 0].std():.3f}\")\n",
"print(f\"σ: {samples[:, 1].mean():.3f} ± {samples[:, 1].std():.3f}\")\n",
"print(f\"\\nTrue values: μ={true_mu}, σ={true_sigma}\")"
]
},
{
"cell_type": "markdown",
"id": "2306bc9b",
"metadata": {},
"source": [
"## Visualize MCMC Results\n",
"\n",
"### Trace Plots - Check Convergence"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "856b7ca8",
"metadata": {},
"outputs": [],
"source": [
"fig, axes = plt.subplots(2, 2, figsize=(14, 8))\n",
"\n",
"# Trace plots\n",
"axes[0, 0].plot(samples[:, 0], linewidth=0.5, alpha=0.7)\n",
"axes[0, 0].axhline(true_mu, color='red', linestyle='--', linewidth=2, label='True μ')\n",
"axes[0, 0].set_xlabel('Sample', fontsize=11)\n",
"axes[0, 0].set_ylabel('μ', fontsize=11)\n",
"axes[0, 0].set_title('Trace Plot: μ', fontsize=13, fontweight='bold')\n",
"axes[0, 0].legend()\n",
"axes[0, 0].grid(alpha=0.3)\n",
"\n",
"axes[0, 1].plot(samples[:, 1], linewidth=0.5, alpha=0.7, color='orange')\n",
"axes[0, 1].axhline(true_sigma, color='red', linestyle='--', linewidth=2, label='True σ')\n",
"axes[0, 1].set_xlabel('Sample', fontsize=11)\n",
"axes[0, 1].set_ylabel('σ', fontsize=11)\n",
"axes[0, 1].set_title('Trace Plot: σ', fontsize=13, fontweight='bold')\n",
"axes[0, 1].legend()\n",
"axes[0, 1].grid(alpha=0.3)\n",
"\n",
"# Posterior distributions\n",
"axes[1, 0].hist(samples[:, 0], bins=50, density=True, alpha=0.6, color='skyblue', edgecolor='black')\n",
"axes[1, 0].axvline(true_mu, color='red', linestyle='--', linewidth=2, label='True μ')\n",
"axes[1, 0].axvline(samples[:, 0].mean(), color='green', linestyle='-', linewidth=2, label='Posterior mean')\n",
"axes[1, 0].set_xlabel('μ', fontsize=11)\n",
"axes[1, 0].set_ylabel('Density', fontsize=11)\n",
"axes[1, 0].set_title('Posterior Distribution: μ', fontsize=13, fontweight='bold')\n",
"axes[1, 0].legend()\n",
"axes[1, 0].grid(alpha=0.3)\n",
"\n",
"axes[1, 1].hist(samples[:, 1], bins=50, density=True, alpha=0.6, color='orange', edgecolor='black')\n",
"axes[1, 1].axvline(true_sigma, color='red', linestyle='--', linewidth=2, label='True σ')\n",
"axes[1, 1].axvline(samples[:, 1].mean(), color='green', linestyle='-', linewidth=2, label='Posterior mean')\n",
"axes[1, 1].set_xlabel('σ', fontsize=11)\n",
"axes[1, 1].set_ylabel('Density', fontsize=11)\n",
"axes[1, 1].set_title('Posterior Distribution: σ', fontsize=13, fontweight='bold')\n",
"axes[1, 1].legend()\n",
"axes[1, 1].grid(alpha=0.3)\n",
"\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "9b20c718",
"metadata": {},
"source": [
"### Joint Posterior Distribution"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ad2eebc7",
"metadata": {},
"outputs": [],
"source": [
"plt.figure(figsize=(10, 8))\n",
"\n",
"# 2D histogram\n",
"plt.hist2d(samples[:, 0], samples[:, 1], bins=50, cmap='Blues')\n",
"plt.colorbar(label='Sample Density')\n",
"\n",
"# Mark true values\n",
"plt.scatter([true_mu], [true_sigma], c='red', s=200, marker='*', \n",
" edgecolors='black', linewidths=2, label='True values', zorder=5)\n",
"\n",
"# Mark posterior mean\n",
"plt.scatter([samples[:, 0].mean()], [samples[:, 1].mean()], \n",
" c='green', s=200, marker='o', edgecolors='black', \n",
" linewidths=2, label='Posterior mean', zorder=5)\n",
"\n",
"plt.xlabel('μ', fontsize=12)\n",
"plt.ylabel('σ', fontsize=12)\n",
"plt.title('Joint Posterior Distribution', fontsize=14, fontweight='bold')\n",
"plt.legend(fontsize=11)\n",
"plt.grid(alpha=0.3)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "049df073",
"metadata": {},
"source": [
"## Example 2: Logistic Regression with MCMC\n",
"\n",
"Bayesian inference for binary classification.\n",
"\n",
"### Model\n",
"$$P(y=1 | \\mathbf{x}, \\boldsymbol{\\beta}) = \\frac{1}{1 + \\exp(-\\boldsymbol{\\beta}^T \\mathbf{x})}$$\n",
"\n",
"### Log-Likelihood\n",
"$$\\log L(\\boldsymbol{\\beta}) = \\sum_{i=1}^n \\left[y_i \\log p_i + (1-y_i) \\log(1-p_i)\\right]$$"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "072639a3",
"metadata": {},
"outputs": [],
"source": [
"# Generate synthetic classification data\n",
"from sklearn.datasets import make_classification\n",
"\n",
"X, y = make_classification(n_samples=200, n_features=2, n_redundant=0,\n",
" n_informative=2, random_state=42, n_clusters_per_class=1)\n",
"\n",
"# Add intercept\n",
"X_with_intercept = np.column_stack([np.ones(len(X)), X])\n",
"\n",
"print(f\"Features shape: {X_with_intercept.shape}\")\n",
"print(f\"Class distribution: {np.bincount(y)}\")\n",
"\n",
"# Visualize data\n",
"plt.figure(figsize=(8, 6))\n",
"plt.scatter(X[y == 0, 0], X[y == 0, 1], c='blue', label='Class 0', alpha=0.6, s=50)\n",
"plt.scatter(X[y == 1, 0], X[y == 1, 1], c='red', label='Class 1', alpha=0.6, s=50)\n",
"plt.xlabel('Feature 1', fontsize=12)\n",
"plt.ylabel('Feature 2', fontsize=12)\n",
"plt.title('Binary Classification Data', fontsize=14, fontweight='bold')\n",
"plt.legend()\n",
"plt.grid(alpha=0.3)\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6163cb52",
"metadata": {},
"outputs": [],
"source": [
"def log_likelihood_logistic(beta, X, y):\n",
" \"\"\"\n",
" Log-likelihood for logistic regression.\n",
" \"\"\"\n",
" z = X @ beta\n",
" # Numerically stable sigmoid\n",
" p = 1 / (1 + np.exp(-np.clip(z, -500, 500)))\n",
" p = np.clip(p, 1e-10, 1 - 1e-10) # Avoid log(0)\n",
" \n",
" log_lik = np.sum(y * np.log(p) + (1 - y) * np.log(1 - p))\n",
" \n",
" # Add weak prior: beta ~ N(0, 10²)\n",
" log_prior = -0.5 * np.sum(beta**2) / 100\n",
" \n",
" return log_lik + log_prior\n",
"\n",
"# Prepare data tuple\n",
"logistic_data = (X_with_intercept, y)\n",
"\n",
"# MCMC for logistic regression\n",
"initial_beta = np.zeros(3) # [intercept, coef1, coef2]\n",
"beta_bounds = [(-10, 10)] * 3\n",
"beta_proposal_std = [0.1] * 3\n",
"\n",
"print(\"Running MCMC for logistic regression...\")\n",
"beta_samples, beta_acceptance = mcmc_sample(\n",
" log_likelihood_fn=log_likelihood_logistic,\n",
" data=logistic_data,\n",
" initial_params=initial_beta,\n",
" param_bounds=beta_bounds,\n",
" proposal_std=beta_proposal_std,\n",
" n_samples=15000,\n",
" burn_in=1500\n",
")\n",
"\n",
"print(f\"\\nAcceptance rate: {beta_acceptance:.2%}\")\n",
"print(f\"\\nPosterior estimates:\")\n",
"print(f\"β₀ (intercept): {beta_samples[:, 0].mean():.3f} ± {beta_samples[:, 0].std():.3f}\")\n",
"print(f\"β₁: {beta_samples[:, 1].mean():.3f} ± {beta_samples[:, 1].std():.3f}\")\n",
"print(f\"β₂: {beta_samples[:, 2].mean():.3f} ± {beta_samples[:, 2].std():.3f}\")"
]
},
{
"cell_type": "markdown",
"id": "3cc9ff7c",
"metadata": {},
"source": [
"### Visualize Decision Boundary with Uncertainty"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2eb5d0d5",
"metadata": {},
"outputs": [],
"source": [
"# Plot decision boundaries from posterior samples\n",
"plt.figure(figsize=(10, 8))\n",
"\n",
"# Plot data\n",
"plt.scatter(X[y == 0, 0], X[y == 0, 1], c='blue', label='Class 0', alpha=0.6, s=50, zorder=3)\n",
"plt.scatter(X[y == 1, 0], X[y == 1, 1], c='red', label='Class 1', alpha=0.6, s=50, zorder=3)\n",
"\n",
"# Create grid\n",
"x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1\n",
"x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1\n",
"\n",
"# Plot decision boundaries from random posterior samples\n",
"n_boundary_samples = 100\n",
"indices = np.random.choice(len(beta_samples), n_boundary_samples, replace=False)\n",
"\n",
"for idx in indices:\n",
" beta = beta_samples[idx]\n",
" # Decision boundary: β₀ + β₁x₁ + β₂x₂ = 0\n",
" # => x₂ = -(β₀ + β₁x₁) / β₂\n",
" if abs(beta[2]) > 0.01: # Avoid division by zero\n",
" x1_line = np.array([x1_min, x1_max])\n",
" x2_line = -(beta[0] + beta[1] * x1_line) / beta[2]\n",
" plt.plot(x1_line, x2_line, 'gray', alpha=0.02, linewidth=0.5, zorder=1)\n",
"\n",
"# Plot mean decision boundary\n",
"beta_mean = beta_samples.mean(axis=0)\n",
"if abs(beta_mean[2]) > 0.01:\n",
" x1_line = np.array([x1_min, x1_max])\n",
" x2_line = -(beta_mean[0] + beta_mean[1] * x1_line) / beta_mean[2]\n",
" plt.plot(x1_line, x2_line, 'black', linewidth=3, label='Mean boundary', zorder=2)\n",
"\n",
"plt.xlim(x1_min, x1_max)\n",
"plt.ylim(x2_min, x2_max)\n",
"plt.xlabel('Feature 1', fontsize=12)\n",
"plt.ylabel('Feature 2', fontsize=12)\n",
"plt.title('Logistic Regression: Decision Boundary with Uncertainty', fontsize=14, fontweight='bold')\n",
"plt.legend()\n",
"plt.grid(alpha=0.3)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "8c4facb3",
"metadata": {},
"source": [
"## MCMC Diagnostics\n",
"\n",
"### Autocorrelation - Check Mixing"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0ab8cd82",
"metadata": {},
"outputs": [],
"source": [
"from statsmodels.graphics.tsaplots import plot_acf\n",
"\n",
"fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n",
"\n",
"plot_acf(samples[:, 0], lags=100, ax=axes[0], alpha=0.05)\n",
"axes[0].set_title('Autocorrelation: μ', fontsize=13, fontweight='bold')\n",
"axes[0].set_xlabel('Lag', fontsize=11)\n",
"\n",
"plot_acf(samples[:, 1], lags=100, ax=axes[1], alpha=0.05)\n",
"axes[1].set_title('Autocorrelation: σ', fontsize=13, fontweight='bold')\n",
"axes[1].set_xlabel('Lag', fontsize=11)\n",
"\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"print(\"Low autocorrelation at large lags indicates good mixing!\")"
]
},
{
"cell_type": "markdown",
"id": "e2ed2ccd",
"metadata": {},
"source": [
"## Key Takeaways\n",
"\n",
"1. **MCMC samples from complex distributions** using only likelihood evaluations\n",
"2. **Metropolis-Hastings** uses proposal distribution and accept/reject steps\n",
"3. **Burn-in period** allows chain to converge to target distribution\n",
"4. **Diagnostics** (trace plots, autocorrelation) verify convergence and mixing\n",
"5. **OptimizR provides 50-100x speedup** for likelihood evaluations\n",
"6. **Bayesian inference** naturally quantifies uncertainty in parameters\n",
"\n",
"## Further Reading\n",
"\n",
"- Gelman, A., et al. (2013). \"Bayesian Data Analysis\" - Chapter 11\n",
"- Brooks, S., et al. (2011). \"Handbook of Markov Chain Monte Carlo\"\n",
"- Betancourt, M. (2017). \"A Conceptual Introduction to Hamiltonian Monte Carlo\""
]
}
],
"metadata": {
"kernelspec": {
"display_name": "rhftlab",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}