5.1 Fundamentals of Hierarchical Bayesian Models
Hierarchical Bayesian models are a powerful modeling technique for representing parameter uncertainty across multiple levels. By estimating different parameters for each group while sharing information across the whole, they enable stable estimation even with small amounts of data.
š Structure of a Hierarchical Bayesian Model
A hierarchical model has the following three-level structure:
- Data level: the distribution of the observed data \( y_i \)
\[ y_i \sim P(y_i | \theta_i) \]
- Parameter level: the distribution of the individual parameters \( \theta_i \)
\[ \theta_i \sim P(\theta_i | \phi) \]
- Hyperparameter level: the prior distribution of the hyperparameters \( \phi \)
\[ \phi \sim P(\phi) \]
A hyperparameter is a higher-level parameter that governs the distribution of the parameters. This allows each group's characteristics to be captured while sharing information across groups.
5.1.1 Advantages of Hierarchical Models
- Partial pooling: an intermediate approach between complete pooling (treating all data as a single group) and no pooling (treating each group independently)
- Shrinkage estimation: pulls extreme estimates toward the overall mean, preventing overfitting
- Handling small data: even for groups with little data, estimation accuracy is improved by borrowing information from the whole
- Interpretability: within-group variation and between-group variation can be understood separately
š» Code Example 1: Building a Hierarchical Bayesian Model (estimating product quality across multiple factories)
import numpy as np
import matplotlib.pyplot as plt
import pymc3 as pm
import arviz as az
# Generate simulation data
np.random.seed(42)
# Data for 5 factories
n_factories = 5
n_samples_per_factory = [10, 15, 8, 12, 20] # sample size per factory
# True parameters (treated as unknown)
true_global_mean = 100 # overall mean
true_global_std = 10 # between-factory variability
true_factory_means = np.random.normal(true_global_mean, true_global_std, n_factories)
true_within_std = 5 # within-factory variability
# Generate data
factory_data = []
factory_labels = []
for i, n in enumerate(n_samples_per_factory):
data = np.random.normal(true_factory_means[i], true_within_std, n)
factory_data.extend(data)
factory_labels.extend([i] * n)
factory_data = np.array(factory_data)
factory_labels = np.array(factory_labels)
print("=== Data Summary ===")
for i in range(n_factories):
factory_i_data = factory_data[factory_labels == i]
print(f"Factory {i+1}: sample size={len(factory_i_data)}, mean={factory_i_data.mean():.2f}, std={factory_i_data.std():.2f}")
print(f"\nOverall mean: {factory_data.mean():.2f}")
# Build the hierarchical Bayesian model
with pm.Model() as hierarchical_model:
# Hyperparameters (mean and standard deviation across all factories)
mu_global = pm.Normal('mu_global', mu=100, sigma=20)
sigma_global = pm.HalfNormal('sigma_global', sigma=20)
# Mean of each factory (generated from the hyperparameters)
mu_factory = pm.Normal('mu_factory', mu=mu_global, sigma=sigma_global, shape=n_factories)
# Within-factory standard deviation
sigma_within = pm.HalfNormal('sigma_within', sigma=10)
# Likelihood (observed data)
y_obs = pm.Normal('y_obs', mu=mu_factory[factory_labels], sigma=sigma_within, observed=factory_data)
# Sampling
trace = pm.sample(2000, tune=1000, return_inferencedata=True, random_seed=42)
# Summary of results
print("\n=== Bayesian Estimation Results ===")
print(az.summary(trace, var_names=['mu_global', 'sigma_global', 'sigma_within', 'mu_factory']))
# Visualization
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 1. Posterior distributions of each factory's mean
ax = axes[0, 0]
for i in range(n_factories):
ax.hist(trace.posterior['mu_factory'].values[:, :, i].flatten(), bins=30, alpha=0.6, label=f'Factory {i+1}')
ax.axvline(true_global_mean, color='red', linestyle='--', linewidth=2, label='True overall mean')
ax.set_xlabel('Factory mean', fontsize=12)
ax.set_ylabel('Frequency', fontsize=12)
ax.set_title('Posterior distributions of each factory mean', fontsize=14, fontweight='bold')
ax.legend()
ax.grid(alpha=0.3)
# 2. Posterior distribution of the overall mean
ax = axes[0, 1]
ax.hist(trace.posterior['mu_global'].values.flatten(), bins=50, alpha=0.7, color='purple', edgecolor='black')
ax.axvline(true_global_mean, color='red', linestyle='--', linewidth=2, label='True overall mean')
ax.set_xlabel('Overall mean (mu_global)', fontsize=12)
ax.set_ylabel('Frequency', fontsize=12)
ax.set_title('Posterior distribution of the overall mean', fontsize=14, fontweight='bold')
ax.legend()
ax.grid(alpha=0.3)
# 3. Posterior distribution of the between-factory standard deviation
ax = axes[1, 0]
ax.hist(trace.posterior['sigma_global'].values.flatten(), bins=50, alpha=0.7, color='orange', edgecolor='black')
ax.axvline(true_global_std, color='red', linestyle='--', linewidth=2, label='True between-factory std')
ax.set_xlabel('Between-factory std (sigma_global)', fontsize=12)
ax.set_ylabel('Frequency', fontsize=12)
ax.set_title('Posterior distribution of between-factory variability', fontsize=14, fontweight='bold')
ax.legend()
ax.grid(alpha=0.3)
# 4. Effect of shrinkage estimation
ax = axes[1, 1]
sample_means = [factory_data[factory_labels == i].mean() for i in range(n_factories)]
posterior_means = [trace.posterior['mu_factory'].values[:, :, i].mean() for i in range(n_factories)]
x_pos = np.arange(n_factories)
width = 0.35
ax.bar(x_pos - width/2, sample_means, width, label='Sample mean (no pooling)', alpha=0.7, color='skyblue')
ax.bar(x_pos + width/2, posterior_means, width, label='Posterior mean (partial pooling)', alpha=0.7, color='lightcoral')
ax.axhline(true_global_mean, color='red', linestyle='--', linewidth=2, label='True overall mean')
ax.set_xlabel('Factory number', fontsize=12)
ax.set_ylabel('Estimated mean', fontsize=12)
ax.set_title('Effect of shrinkage estimation', fontsize=14, fontweight='bold')
ax.set_xticks(x_pos)
ax.set_xticklabels([f'Factory {i+1}' for i in range(n_factories)])
ax.legend()
ax.grid(alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig('hierarchical_bayesian_model.png', dpi=300, bbox_inches='tight')
plt.show()
print("\nā The hierarchical Bayesian model achieves estimation that captures each factory's characteristics while also leveraging overall information")
5.2 Bayesian Linear Regression
In the Bayesian approach to linear regression, the regression coefficients are estimated as probability distributions. This allows not only point estimates but also quantification of uncertainty, enabling proper evaluation of prediction intervals.
š Bayesian Linear Regression Model
Linear regression model:
Bayesian formulation:
- Likelihood: \( y_i \sim \mathcal{N}(\beta_0 + \beta_1 x_i, \sigma^2) \)
- Prior distributions:
\[ \beta_0 \sim \mathcal{N}(0, \sigma_{\beta_0}^2), \quad \beta_1 \sim \mathcal{N}(0, \sigma_{\beta_1}^2), \quad \sigma \sim \text{HalfNormal}(\sigma_0) \]
- Posterior distribution: \( P(\beta_0, \beta_1, \sigma | \mathbf{y}, \mathbf{x}) \propto P(\mathbf{y} | \mathbf{x}, \beta_0, \beta_1, \sigma) P(\beta_0) P(\beta_1) P(\sigma) \)
š» Code Example 2: Bayesian Linear Regression (visualizing the posterior and prediction intervals)
import numpy as np
import matplotlib.pyplot as plt
import pymc3 as pm
import arviz as az
# Generate data (tensile strength of a material vs. processing temperature)
np.random.seed(123)
n = 50
temperature = np.linspace(200, 400, n) # processing temperature [degC]
true_intercept = 50
true_slope = 0.15
true_sigma = 5
strength = true_intercept + true_slope * temperature + np.random.normal(0, true_sigma, n)
# Visualize the data
plt.figure(figsize=(10, 6))
plt.scatter(temperature, strength, alpha=0.6, s=50, label='Observed data')
plt.xlabel('Processing temperature [degC]', fontsize=12)
plt.ylabel('Tensile strength [MPa]', fontsize=12)
plt.title('Relationship between material strength and processing temperature', fontsize=14, fontweight='bold')
plt.legend()
plt.grid(alpha=0.3)
plt.savefig('bayesian_regression_data.png', dpi=300, bbox_inches='tight')
plt.show()
# Bayesian linear regression model
with pm.Model() as bayesian_regression:
# Prior distributions
intercept = pm.Normal('intercept', mu=0, sigma=100)
slope = pm.Normal('slope', mu=0, sigma=10)
sigma = pm.HalfNormal('sigma', sigma=20)
# Linear predictor
mu = intercept + slope * temperature
# Likelihood
y_obs = pm.Normal('y_obs', mu=mu, sigma=sigma, observed=strength)
# Sampling
trace = pm.sample(2000, tune=1000, return_inferencedata=True, random_seed=42)
# Summary of results
print("=== Bayesian Linear Regression Results ===")
print(az.summary(trace, var_names=['intercept', 'slope', 'sigma']))
# Comparison with true values
print(f"\nTrue intercept: {true_intercept:.2f}, estimate: {trace.posterior['intercept'].mean().values:.2f}")
print(f"True slope: {true_slope:.3f}, estimate: {trace.posterior['slope'].mean().values:.3f}")
print(f"True standard deviation: {true_sigma:.2f}, estimate: {trace.posterior['sigma'].mean().values:.2f}")
# Visualize the posterior and predictions
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 1. Posterior distributions of the regression coefficients
ax = axes[0, 0]
ax.hist(trace.posterior['intercept'].values.flatten(), bins=50, alpha=0.7, color='skyblue', edgecolor='black')
ax.axvline(true_intercept, color='red', linestyle='--', linewidth=2, label='True value')
ax.set_xlabel('Intercept (beta_0)', fontsize=12)
ax.set_ylabel('Frequency', fontsize=12)
ax.set_title('Posterior distribution of the intercept', fontsize=14, fontweight='bold')
ax.legend()
ax.grid(alpha=0.3)
ax = axes[0, 1]
ax.hist(trace.posterior['slope'].values.flatten(), bins=50, alpha=0.7, color='lightcoral', edgecolor='black')
ax.axvline(true_slope, color='red', linestyle='--', linewidth=2, label='True value')
ax.set_xlabel('Slope (beta_1)', fontsize=12)
ax.set_ylabel('Frequency', fontsize=12)
ax.set_title('Posterior distribution of the slope', fontsize=14, fontweight='bold')
ax.legend()
ax.grid(alpha=0.3)
# 2. Joint distribution of the regression coefficients
ax = axes[1, 0]
intercept_samples = trace.posterior['intercept'].values.flatten()
slope_samples = trace.posterior['slope'].values.flatten()
ax.scatter(intercept_samples, slope_samples, alpha=0.1, s=1, color='purple')
ax.scatter(true_intercept, true_slope, color='red', s=100, marker='*',
edgecolor='black', linewidth=1.5, label='True value', zorder=5)
ax.set_xlabel('Intercept (beta_0)', fontsize=12)
ax.set_ylabel('Slope (beta_1)', fontsize=12)
ax.set_title('Joint distribution of the regression coefficients', fontsize=14, fontweight='bold')
ax.legend()
ax.grid(alpha=0.3)
# 3. Predictions and uncertainty
ax = axes[1, 1]
# Draw regression lines from 100 posterior samples
n_samples = 100
indices = np.random.choice(len(intercept_samples), n_samples, replace=False)
for idx in indices:
y_pred = intercept_samples[idx] + slope_samples[idx] * temperature
ax.plot(temperature, y_pred, color='gray', alpha=0.05, linewidth=0.5)
# Mean prediction and 95% credible interval
mu_pred = trace.posterior['intercept'].mean().values + trace.posterior['slope'].mean().values * temperature
y_pred_samples = np.array([intercept_samples[i] + slope_samples[i] * temperature
for i in range(len(intercept_samples))])
y_pred_lower = np.percentile(y_pred_samples, 2.5, axis=0)
y_pred_upper = np.percentile(y_pred_samples, 97.5, axis=0)
ax.fill_between(temperature, y_pred_lower, y_pred_upper, alpha=0.3, color='lightblue', label='95% credible interval')
ax.plot(temperature, mu_pred, color='blue', linewidth=2, label='Posterior mean prediction')
ax.scatter(temperature, strength, alpha=0.6, s=30, color='black', label='Observed data')
ax.set_xlabel('Processing temperature [degC]', fontsize=12)
ax.set_ylabel('Tensile strength [MPa]', fontsize=12)
ax.set_title('Bayesian linear regression: predictions and uncertainty', fontsize=14, fontweight='bold')
ax.legend()
ax.grid(alpha=0.3)
plt.tight_layout()
plt.savefig('bayesian_regression_results.png', dpi=300, bbox_inches='tight')
plt.show()
# Predictions at new data points
new_temp = np.array([250, 300, 350])
with bayesian_regression:
# Posterior predictive distribution
pm.set_data({'temperature': new_temp})
posterior_predictive = pm.sample_posterior_predictive(trace, var_names=['y_obs'])
print("\n=== Predictions at New Temperatures ===")
for i, temp in enumerate(new_temp):
pred_mean = posterior_predictive.posterior_predictive['y_obs'].values[:, :, i].mean()
pred_std = posterior_predictive.posterior_predictive['y_obs'].values[:, :, i].std()
pred_lower = np.percentile(posterior_predictive.posterior_predictive['y_obs'].values[:, :, i], 2.5)
pred_upper = np.percentile(posterior_predictive.posterior_predictive['y_obs'].values[:, :, i], 97.5)
print(f"Temperature {temp} degC: mean={pred_mean:.2f} MPa, std={pred_std:.2f}, 95% prediction interval=[{pred_lower:.2f}, {pred_upper:.2f}]")
print("\nā Bayesian linear regression enables predictions that quantify parameter uncertainty")
5.3 Bayesian Logistic Regression
By making logistic regression Bayesian, probabilistic predictions and uncertainty evaluation become possible in binary classification problems. This is useful for pass/fail decisions on materials, predicting the presence or absence of defects, and similar tasks.
š Bayesian Logistic Regression Model
Logistic regression model:
Bayesian formulation:
- Likelihood: \( y_i \sim \text{Bernoulli}(p_i) \), where \( p_i = \sigma(\beta_0 + \beta_1 x_i) \)
- Prior distributions: \( \beta_0, \beta_1 \sim \mathcal{N}(0, \sigma^2) \)
- Posterior distribution: estimated by MCMC (no analytical solution)
š» Code Example 3: Bayesian Logistic Regression (material pass/fail decision)
import numpy as np
import matplotlib.pyplot as plt
import pymc3 as pm
import arviz as az
from scipy.special import expit # sigmoid function
# Generate data (processing temperature vs. product pass/fail)
np.random.seed(456)
n = 100
temperature = np.random.uniform(200, 400, n)
# True parameters
true_intercept = -15
true_slope = 0.05
# Logit probability
logit_p = true_intercept + true_slope * temperature
prob = expit(logit_p) # sigmoid transformation
# Generate binary data
quality = np.random.binomial(1, prob, n)
print(f"=== Data Summary ===")
print(f"Number passed: {quality.sum()}/{n} ({100*quality.sum()/n:.1f}%)")
print(f"Temperature range: {temperature.min():.1f} degC - {temperature.max():.1f} degC")
# Visualize the data
plt.figure(figsize=(10, 6))
plt.scatter(temperature[quality==0], quality[quality==0], alpha=0.6, s=50, color='red', label='Fail', marker='x')
plt.scatter(temperature[quality==1], quality[quality==1], alpha=0.6, s=50, color='green', label='Pass', marker='o')
plt.xlabel('Processing temperature [degC]', fontsize=12)
plt.ylabel('Quality decision (0=fail, 1=pass)', fontsize=12)
plt.title('Relationship between processing temperature and product quality', fontsize=14, fontweight='bold')
plt.legend()
plt.grid(alpha=0.3)
plt.savefig('logistic_regression_data.png', dpi=300, bbox_inches='tight')
plt.show()
# Bayesian logistic regression model
with pm.Model() as bayesian_logistic:
# Prior distributions
intercept = pm.Normal('intercept', mu=0, sigma=10)
slope = pm.Normal('slope', mu=0, sigma=1)
# Logit
logit_p = intercept + slope * temperature
# Likelihood
y_obs = pm.Bernoulli('y_obs', logit_p=logit_p, observed=quality)
# Sampling
trace = pm.sample(2000, tune=1000, return_inferencedata=True, random_seed=42)
# Summary of results
print("\n=== Bayesian Logistic Regression Results ===")
print(az.summary(trace, var_names=['intercept', 'slope']))
print(f"\nTrue intercept: {true_intercept:.2f}, estimate: {trace.posterior['intercept'].mean().values:.2f}")
print(f"True slope: {true_slope:.3f}, estimate: {trace.posterior['slope'].mean().values:.3f}")
# Visualization
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# 1. Posterior distribution of the regression coefficients
ax = axes[0]
intercept_samples = trace.posterior['intercept'].values.flatten()
slope_samples = trace.posterior['slope'].values.flatten()
ax.scatter(intercept_samples, slope_samples, alpha=0.1, s=1, color='purple')
ax.scatter(true_intercept, true_slope, color='red', s=200, marker='*',
edgecolor='black', linewidth=2, label='True value', zorder=5)
ax.set_xlabel('Intercept (beta_0)', fontsize=12)
ax.set_ylabel('Slope (beta_1)', fontsize=12)
ax.set_title('Posterior distribution of the regression coefficients', fontsize=14, fontweight='bold')
ax.legend(fontsize=12)
ax.grid(alpha=0.3)
# 2. Predicted probability curve
ax = axes[1]
temp_range = np.linspace(200, 400, 100)
# Draw 100 samples from the posterior
n_samples = 100
indices = np.random.choice(len(intercept_samples), n_samples, replace=False)
for idx in indices:
logit_pred = intercept_samples[idx] + slope_samples[idx] * temp_range
prob_pred = expit(logit_pred)
ax.plot(temp_range, prob_pred, color='gray', alpha=0.05, linewidth=0.5)
# Mean predicted probability
logit_mean = trace.posterior['intercept'].mean().values + trace.posterior['slope'].mean().values * temp_range
prob_mean = expit(logit_mean)
ax.plot(temp_range, prob_mean, color='blue', linewidth=3, label='Posterior mean prediction')
# True probability curve
true_prob = expit(true_intercept + true_slope * temp_range)
ax.plot(temp_range, true_prob, color='red', linestyle='--', linewidth=2, label='True probability')
# Data points
ax.scatter(temperature[quality==0], quality[quality==0], alpha=0.6, s=50, color='red', marker='x', label='Fail')
ax.scatter(temperature[quality==1], quality[quality==1], alpha=0.6, s=50, color='green', marker='o', label='Pass')
ax.set_xlabel('Processing temperature [degC]', fontsize=12)
ax.set_ylabel('Pass probability', fontsize=12)
ax.set_title('Pass probability prediction by Bayesian logistic regression', fontsize=14, fontweight='bold')
ax.legend(fontsize=10)
ax.grid(alpha=0.3)
plt.tight_layout()
plt.savefig('bayesian_logistic_regression_results.png', dpi=300, bbox_inches='tight')
plt.show()
# Pass probability prediction at specific temperatures
test_temps = [250, 300, 350]
print("\n=== Pass Probability Prediction at Specific Temperatures ===")
for temp in test_temps:
logit_pred = intercept_samples + slope_samples * temp
prob_pred = expit(logit_pred)
prob_mean = prob_pred.mean()
prob_lower = np.percentile(prob_pred, 2.5)
prob_upper = np.percentile(prob_pred, 97.5)
print(f"Temperature {temp} degC: pass probability={prob_mean:.3f}, 95% credible interval=[{prob_lower:.3f}, {prob_upper:.3f}]")
print("\nā Bayesian logistic regression enables evaluation of the uncertainty in classification probabilities")
5.4 Bayes Factors and Model Selection
The Bayes factor is a metric that quantifies the relative strength of evidence between two models. It allows statistical evaluation of which model better explains the data.
š Definition of the Bayes Factor
For two models \( M_1 \) and \( M_2 \), the Bayes factor is:
Each term:
- \( P(D | M_i) \): the marginal likelihood of the data under model \( M_i \)
- \( BF_{12} > 1 \): model \( M_1 \) explains the data better
- \( BF_{12} < 1 \): model \( M_2 \) is favored
Kass-Raftery scale (interpretation of the Bayes factor):
- \( 1 < BF_{12} < 3 \): barely worth mentioning
- \( 3 < BF_{12} < 20 \): positive evidence
- \( 20 < BF_{12} < 150 \): strong evidence
- \( BF_{12} > 150 \): very strong evidence
š» Code Example 4: Model Selection by Bayes Factor (linear vs. quadratic model)
import numpy as np
import matplotlib.pyplot as plt
import pymc3 as pm
import arviz as az
# Generate data (the true model is quadratic)
np.random.seed(789)
n = 50
x = np.linspace(0, 10, n)
true_intercept = 5
true_slope1 = 2
true_slope2 = -0.15
noise_std = 2
y = true_intercept + true_slope1 * x + true_slope2 * x**2 + np.random.normal(0, noise_std, n)
plt.figure(figsize=(10, 6))
plt.scatter(x, y, alpha=0.6, s=50, label='Observed data')
plt.xlabel('x', fontsize=12)
plt.ylabel('y', fontsize=12)
plt.title('Observed data (the true model is quadratic)', fontsize=14, fontweight='bold')
plt.legend()
plt.grid(alpha=0.3)
plt.savefig('model_selection_data.png', dpi=300, bbox_inches='tight')
plt.show()
# Model 1: linear model
with pm.Model() as model_linear:
intercept = pm.Normal('intercept', mu=0, sigma=10)
slope = pm.Normal('slope', mu=0, sigma=10)
sigma = pm.HalfNormal('sigma', sigma=5)
mu = intercept + slope * x
y_obs = pm.Normal('y_obs', mu=mu, sigma=sigma, observed=y)
trace_linear = pm.sample(2000, tune=1000, return_inferencedata=True, random_seed=42)
# Model 2: quadratic model
with pm.Model() as model_quadratic:
intercept = pm.Normal('intercept', mu=0, sigma=10)
slope1 = pm.Normal('slope1', mu=0, sigma=10)
slope2 = pm.Normal('slope2', mu=0, sigma=10)
sigma = pm.HalfNormal('sigma', sigma=5)
mu = intercept + slope1 * x + slope2 * x**2
y_obs = pm.Normal('y_obs', mu=mu, sigma=sigma, observed=y)
trace_quadratic = pm.sample(2000, tune=1000, return_inferencedata=True, random_seed=42)
# Model comparison (using WAIC and LOO)
print("=== Model Comparison ===")
compare_dict = {'Linear model': trace_linear, 'Quadratic model': trace_quadratic}
comparison = az.compare(compare_dict, ic='waic')
print(comparison)
# Approximate computation of the Bayes factor (ratio of marginal likelihoods)
# Approximation using WAIC
waic_linear = az.waic(trace_linear, scale='deviance')
waic_quadratic = az.waic(trace_quadratic, scale='deviance')
print(f"\n=== WAIC ===")
print(f"Linear model: {waic_linear.waic:.2f}")
print(f"Quadratic model: {waic_quadratic.waic:.2f}")
print(f"Difference: {waic_linear.waic - waic_quadratic.waic:.2f} (positive means the quadratic model is favored)")
# LOO (Leave-One-Out Cross-Validation)
loo_linear = az.loo(trace_linear, scale='deviance')
loo_quadratic = az.loo(trace_quadratic, scale='deviance')
print(f"\n=== LOO ===")
print(f"Linear model: {loo_linear.loo:.2f}")
print(f"Quadratic model: {loo_quadratic.loo:.2f}")
print(f"Difference: {loo_linear.loo - loo_quadratic.loo:.2f}")
# Prediction comparison
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# Prediction of the linear model
ax = axes[0]
intercept_lin = trace_linear.posterior['intercept'].values.flatten()
slope_lin = trace_linear.posterior['slope'].values.flatten()
n_samples = 100
indices = np.random.choice(len(intercept_lin), n_samples, replace=False)
for idx in indices:
y_pred = intercept_lin[idx] + slope_lin[idx] * x
ax.plot(x, y_pred, color='gray', alpha=0.05, linewidth=0.5)
y_pred_mean = intercept_lin.mean() + slope_lin.mean() * x
ax.plot(x, y_pred_mean, color='blue', linewidth=3, label='Linear model prediction')
ax.scatter(x, y, alpha=0.6, s=50, color='black', label='Observed data')
ax.set_xlabel('x', fontsize=12)
ax.set_ylabel('y', fontsize=12)
ax.set_title('Prediction of the linear model', fontsize=14, fontweight='bold')
ax.legend()
ax.grid(alpha=0.3)
# Prediction of the quadratic model
ax = axes[1]
intercept_quad = trace_quadratic.posterior['intercept'].values.flatten()
slope1_quad = trace_quadratic.posterior['slope1'].values.flatten()
slope2_quad = trace_quadratic.posterior['slope2'].values.flatten()
for idx in indices:
y_pred = intercept_quad[idx] + slope1_quad[idx] * x + slope2_quad[idx] * x**2
ax.plot(x, y_pred, color='gray', alpha=0.05, linewidth=0.5)
y_pred_mean = intercept_quad.mean() + slope1_quad.mean() * x + slope2_quad.mean() * x**2
ax.plot(x, y_pred_mean, color='red', linewidth=3, label='Quadratic model prediction')
# True curve
y_true = true_intercept + true_slope1 * x + true_slope2 * x**2
ax.plot(x, y_true, color='green', linestyle='--', linewidth=2, label='True model')
ax.scatter(x, y, alpha=0.6, s=50, color='black', label='Observed data')
ax.set_xlabel('x', fontsize=12)
ax.set_ylabel('y', fontsize=12)
ax.set_title('Prediction of the quadratic model', fontsize=14, fontweight='bold')
ax.legend()
ax.grid(alpha=0.3)
plt.tight_layout()
plt.savefig('model_comparison_predictions.png', dpi=300, bbox_inches='tight')
plt.show()
print("\nā The Bayes factor (WAIC/LOO) confirms that the quadratic model is superior to the linear model")
5.5 Bayesian Analysis of Variance (ANOVA)
Bayesian ANOVA evaluates differences in means across multiple groups using a hierarchical model. Unlike the traditional F-test, it directly estimates the parameter distribution of each group and quantifies uncertainty.
š The Hierarchical Model for Bayesian ANOVA
Bayesian model for one-way analysis of variance:
Each term:
- \( y_{ij} \): the \( j \)-th observation of group \( i \)
- \( \mu_i \): the mean of group \( i \)
- \( \mu_{\text{global}} \): the overall mean
- \( \sigma_{\text{group}} \): the between-group standard deviation
- \( \sigma \): the within-group standard deviation
š» Code Example 5: Bayesian ANOVA (comparing three manufacturing conditions)
import numpy as np
import matplotlib.pyplot as plt
import pymc3 as pm
import arviz as az
# Generate data (product strength under three manufacturing conditions A, B, C)
np.random.seed(101)
condition_A = np.random.normal(100, 5, 30)
condition_B = np.random.normal(105, 5, 30)
condition_C = np.random.normal(98, 5, 30)
# Combine data
data = np.concatenate([condition_A, condition_B, condition_C])
groups = np.array([0]*30 + [1]*30 + [2]*30)
group_names = ['Condition A', 'Condition B', 'Condition C']
print("=== Data Summary ===")
for i, name in enumerate(group_names):
group_data = data[groups == i]
print(f"{name}: mean={group_data.mean():.2f}, std={group_data.std():.2f}, sample size={len(group_data)}")
# Visualize the data
plt.figure(figsize=(10, 6))
positions = [1, 2, 3]
bp = plt.boxplot([condition_A, condition_B, condition_C], positions=positions,
labels=group_names, patch_artist=True, widths=0.6)
for patch, color in zip(bp['boxes'], ['skyblue', 'lightcoral', 'lightgreen']):
patch.set_facecolor(color)
patch.set_alpha(0.7)
plt.ylabel('Product strength [MPa]', fontsize=12)
plt.title('Product strength under three manufacturing conditions', fontsize=14, fontweight='bold')
plt.grid(alpha=0.3, axis='y')
plt.savefig('bayesian_anova_data.png', dpi=300, bbox_inches='tight')
plt.show()
# Bayesian ANOVA model
with pm.Model() as bayesian_anova:
# Hyperparameters
mu_global = pm.Normal('mu_global', mu=100, sigma=20)
sigma_group = pm.HalfNormal('sigma_group', sigma=10)
# Mean of each group
mu_groups = pm.Normal('mu_groups', mu=mu_global, sigma=sigma_group, shape=3)
# Within-group standard deviation
sigma_within = pm.HalfNormal('sigma_within', sigma=10)
# Likelihood
y_obs = pm.Normal('y_obs', mu=mu_groups[groups], sigma=sigma_within, observed=data)
# Sampling
trace = pm.sample(2000, tune=1000, return_inferencedata=True, random_seed=42)
# Summary of results
print("\n=== Bayesian ANOVA Results ===")
print(az.summary(trace, var_names=['mu_global', 'sigma_group', 'sigma_within', 'mu_groups']))
# Visualization
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 1. Posterior distributions of each group's mean
ax = axes[0, 0]
colors = ['skyblue', 'lightcoral', 'lightgreen']
for i, (name, color) in enumerate(zip(group_names, colors)):
samples = trace.posterior['mu_groups'].values[:, :, i].flatten()
ax.hist(samples, bins=50, alpha=0.6, color=color, label=name, edgecolor='black')
ax.set_xlabel('Group mean', fontsize=12)
ax.set_ylabel('Frequency', fontsize=12)
ax.set_title('Posterior distributions of each condition mean', fontsize=14, fontweight='bold')
ax.legend()
ax.grid(alpha=0.3)
# 2. Posterior distributions of between-group differences
ax = axes[0, 1]
mu_A = trace.posterior['mu_groups'].values[:, :, 0].flatten()
mu_B = trace.posterior['mu_groups'].values[:, :, 1].flatten()
mu_C = trace.posterior['mu_groups'].values[:, :, 2].flatten()
diff_AB = mu_B - mu_A
diff_AC = mu_C - mu_A
diff_BC = mu_C - mu_B
ax.hist(diff_AB, bins=50, alpha=0.6, color='purple', label='B - A', edgecolor='black')
ax.hist(diff_BC, bins=50, alpha=0.6, color='orange', label='C - B', edgecolor='black')
ax.axvline(0, color='red', linestyle='--', linewidth=2, label='Difference=0')
ax.set_xlabel('Difference in means', fontsize=12)
ax.set_ylabel('Frequency', fontsize=12)
ax.set_title('Posterior distributions of between-group differences', fontsize=14, fontweight='bold')
ax.legend()
ax.grid(alpha=0.3)
# 3. Overall mean and between-group standard deviation
ax = axes[1, 0]
ax.hist(trace.posterior['mu_global'].values.flatten(), bins=50, alpha=0.7,
color='navy', edgecolor='black', label='Overall mean')
ax.set_xlabel('Overall mean (mu_global)', fontsize=12)
ax.set_ylabel('Frequency', fontsize=12)
ax.set_title('Posterior distribution of the overall mean', fontsize=14, fontweight='bold')
ax.legend()
ax.grid(alpha=0.3)
# 4. Variance components
ax = axes[1, 1]
sigma_group_samples = trace.posterior['sigma_group'].values.flatten()
sigma_within_samples = trace.posterior['sigma_within'].values.flatten()
ax.hist(sigma_group_samples, bins=50, alpha=0.6, color='red', label='Between-group SD', edgecolor='black')
ax.hist(sigma_within_samples, bins=50, alpha=0.6, color='blue', label='Within-group SD', edgecolor='black')
ax.set_xlabel('Standard deviation', fontsize=12)
ax.set_ylabel('Frequency', fontsize=12)
ax.set_title('Posterior distributions of the variance components', fontsize=14, fontweight='bold')
ax.legend()
ax.grid(alpha=0.3)
plt.tight_layout()
plt.savefig('bayesian_anova_results.png', dpi=300, bbox_inches='tight')
plt.show()
# Statistical evaluation of the differences
print("\n=== Statistical Evaluation of Between-Group Differences ===")
print(f"B - A: mean={diff_AB.mean():.2f}, 95% HDI=[{np.percentile(diff_AB, 2.5):.2f}, {np.percentile(diff_AB, 97.5):.2f}]")
print(f" -> P(B > A) = {(diff_AB > 0).mean():.3f}")
print(f"C - A: mean={diff_AC.mean():.2f}, 95% HDI=[{np.percentile(diff_AC, 2.5):.2f}, {np.percentile(diff_AC, 97.5):.2f}]")
print(f" -> P(C > A) = {(diff_AC > 0).mean():.3f}")
print(f"C - B: mean={diff_BC.mean():.2f}, 95% HDI=[{np.percentile(diff_BC, 2.5):.2f}, {np.percentile(diff_BC, 97.5):.2f}]")
print(f" -> P(C > B) = {(diff_BC > 0).mean():.3f}")
print("\nā Bayesian ANOVA enables probabilistic evaluation of between-group differences")
5.6 Applications to Quality Control
In quality control, Bayesian statistics can be applied to estimating product pass rates, monitoring defect rates, evaluating process capability, and more. By using the posterior distribution, decision-making that quantifies risk becomes possible.
š» Code Example 6: Using the Posterior Distribution in Quality Control (estimating the product pass rate)
import numpy as np
import matplotlib.pyplot as plt
import pymc3 as pm
import arviz as az
from scipy import stats
# Scenario: in a product quality inspection, 92 out of 100 pass
# We want to estimate the pass rate and evaluate the probability that it is 90% or higher
n_inspected = 100
n_passed = 92
print(f"=== Quality Inspection Data ===")
print(f"Number inspected: {n_inspected}")
print(f"Number passed: {n_passed}")
print(f"Pass rate: {n_passed/n_inspected:.1%}")
# Bayesian estimation (Beta-Binomial model)
with pm.Model() as quality_model:
# Prior: Beta(2, 2) (weakly informative prior)
p_pass = pm.Beta('p_pass', alpha=2, beta=2)
# Likelihood: binomial distribution
n_obs = pm.Binomial('n_obs', n=n_inspected, p=p_pass, observed=n_passed)
# Sampling
trace = pm.sample(2000, tune=1000, return_inferencedata=True, random_seed=42)
# Summary of results
print("\n=== Bayesian Estimation Results ===")
print(az.summary(trace, var_names=['p_pass']))
p_pass_samples = trace.posterior['p_pass'].values.flatten()
# Probability that the pass rate is 90% or higher
prob_above_90 = (p_pass_samples > 0.90).mean()
print(f"\nProbability that the pass rate is 90% or higher: {prob_above_90:.3f}")
# 95% credible interval
hdi_95 = az.hdi(trace, var_names=['p_pass'], hdi_prob=0.95)
print(f"95% HDI of the pass rate: [{hdi_95['p_pass'].values[0]:.3f}, {hdi_95['p_pass'].values[1]:.3f}]")
# Visualization
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 1. Comparison of prior and posterior distributions
ax = axes[0, 0]
p_range = np.linspace(0, 1, 500)
# Prior distribution Beta(2, 2)
prior_pdf = stats.beta.pdf(p_range, 2, 2)
ax.plot(p_range, prior_pdf, linewidth=2, color='blue', label='Prior Beta(2,2)')
# Posterior distribution (analytical): Beta(2+92, 2+8) = Beta(94, 10)
posterior_pdf = stats.beta.pdf(p_range, 2+n_passed, 2+(n_inspected-n_passed))
ax.plot(p_range, posterior_pdf, linewidth=2, color='red', label='Posterior Beta(94,10)')
# Histogram of MCMC samples
ax.hist(p_pass_samples, bins=50, density=True, alpha=0.3, color='red', edgecolor='black', label='MCMC samples')
ax.axvline(0.90, color='green', linestyle='--', linewidth=2, label='Threshold 90%')
ax.set_xlabel('Pass rate p', fontsize=12)
ax.set_ylabel('Probability density', fontsize=12)
ax.set_title('Prior and posterior distributions of the pass rate', fontsize=14, fontweight='bold')
ax.legend()
ax.grid(alpha=0.3)
# 2. Cumulative distribution function
ax = axes[0, 1]
sorted_samples = np.sort(p_pass_samples)
cdf = np.arange(1, len(sorted_samples)+1) / len(sorted_samples)
ax.plot(sorted_samples, cdf, linewidth=2, color='purple')
ax.axvline(0.90, color='green', linestyle='--', linewidth=2, label='Threshold 90%')
ax.axhline(prob_above_90, color='orange', linestyle='--', linewidth=2,
label=f'P(p>=0.90)={prob_above_90:.3f}')
ax.set_xlabel('Pass rate p', fontsize=12)
ax.set_ylabel('Cumulative probability', fontsize=12)
ax.set_title('Cumulative distribution function of the pass rate', fontsize=14, fontweight='bold')
ax.legend()
ax.grid(alpha=0.3)
# 3. Risk assessment: predictive distribution of the number of failures in the next lot (n=50)
ax = axes[1, 0]
n_next = 50
predicted_failures = []
for p in p_pass_samples[:1000]: # use 1000 samples
n_fail = np.random.binomial(n_next, 1-p)
predicted_failures.append(n_fail)
ax.hist(predicted_failures, bins=range(0, 15), alpha=0.7, color='coral', edgecolor='black', density=True)
ax.set_xlabel('Number of failures in the next lot (n=50)', fontsize=12)
ax.set_ylabel('Probability', fontsize=12)
ax.set_title('Predictive distribution of failures in the next lot', fontsize=14, fontweight='bold')
ax.grid(alpha=0.3, axis='y')
# 4. Decision-making: risk by pass-rate threshold
ax = axes[1, 1]
thresholds = np.linspace(0.80, 0.99, 20)
probs = [(p_pass_samples > t).mean() for t in thresholds]
ax.plot(thresholds, probs, linewidth=2, marker='o', markersize=6, color='navy')
ax.axhline(0.95, color='red', linestyle='--', linewidth=2, label='95% confidence level')
ax.axvline(0.90, color='green', linestyle='--', linewidth=2, label='Threshold 90%')
ax.set_xlabel('Pass-rate threshold', fontsize=12)
ax.set_ylabel('P(pass rate >= threshold)', fontsize=12)
ax.set_title('Achievement probability by pass-rate threshold', fontsize=14, fontweight='bold')
ax.legend()
ax.grid(alpha=0.3)
plt.tight_layout()
plt.savefig('quality_control_bayesian.png', dpi=300, bbox_inches='tight')
plt.show()
# Decision-support information
print("\n=== Decision-Support Information ===")
expected_failures = np.mean(predicted_failures)
print(f"Predicted number of failures in the next lot (n=50): mean={expected_failures:.2f}")
print(f"Probability of 5 or more failures: {(np.array(predicted_failures) >= 5).mean():.3f}")
print(f"Probability of 10 or more failures: {(np.array(predicted_failures) >= 10).mean():.3f}")
print("\nā Bayesian estimation enables risk assessment that accounts for the uncertainty in the pass rate")
5.7 Application to Machine Learning: Bayesian Optimization
Bayesian optimization is a technique for efficiently optimizing objective functions whose evaluation is expensive. It is widely used for hyperparameter tuning, materials design, experimental design, and more.
š Principles of Bayesian Optimization
Bayesian optimization proceeds through the following steps:
- Building a surrogate model: approximate the objective function with a Gaussian process (GP)
\[ f(x) \sim \mathcal{GP}(\mu(x), k(x, x')) \]
- Maximizing the acquisition function: select the next point to evaluate
- Expected Improvement (EI): the expected amount of improvement
\[ EI(x) = \mathbb{E}[\max(f(x) - f(x^*), 0)] \]
- Upper Confidence Bound (UCB): the upper confidence bound
\[ UCB(x) = \mu(x) + \kappa \sigma(x) \]
- Expected Improvement (EI): the expected amount of improvement
- Evaluation and update: evaluate the objective function at the selected point and update the GP
š» Code Example 7: Hyperparameter Tuning by Bayesian Optimization
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
from skopt import gp_minimize
from skopt.space import Integer
from skopt.plots import plot_convergence, plot_objective
from skopt.acquisition import gaussian_ei
# Generate classification dataset
np.random.seed(42)
X, y = make_classification(n_samples=500, n_features=20, n_informative=15,
n_redundant=5, random_state=42)
print("=== Dataset Summary ===")
print(f"Number of samples: {X.shape[0]}")
print(f"Number of features: {X.shape[1]}")
print(f"Class distribution: {np.bincount(y)}")
# Objective function: hyperparameter optimization of a RandomForest
def objective(params):
"""
Objective function over the hyperparameters (minimization problem)
params: [n_estimators, max_depth, min_samples_split]
"""
n_estimators, max_depth, min_samples_split = params
# RandomForest model
model = RandomForestClassifier(
n_estimators=n_estimators,
max_depth=max_depth,
min_samples_split=min_samples_split,
random_state=42,
n_jobs=-1
)
# 5-fold cross-validation
scores = cross_val_score(model, X, y, cv=5, scoring='accuracy')
# Return the negative accuracy (to make it a minimization problem)
return -scores.mean()
# Define the search space
space = [
Integer(10, 200, name='n_estimators'), # number of decision trees
Integer(3, 30, name='max_depth'), # depth of the trees
Integer(2, 20, name='min_samples_split') # minimum samples required to split
]
print("\n=== Starting Bayesian Optimization ===")
print("Search space:")
print(f" n_estimators: [10, 200]")
print(f" max_depth: [3, 30]")
print(f" min_samples_split: [2, 20]")
# Run Bayesian optimization
result = gp_minimize(
objective,
space,
n_calls=30, # number of evaluations
n_initial_points=5, # number of random initialization points
acq_func='EI', # Expected Improvement
random_state=42,
verbose=False
)
print(f"\n=== Optimization Results ===")
print(f"Best parameters:")
print(f" n_estimators: {result.x[0]}")
print(f" max_depth: {result.x[1]}")
print(f" min_samples_split: {result.x[2]}")
print(f"Best score (accuracy): {-result.fun:.4f}")
# Convergence plot
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 1. Convergence curve
ax = axes[0, 0]
n_calls = len(result.func_vals)
best_so_far = np.minimum.accumulate(result.func_vals)
ax.plot(range(1, n_calls+1), -result.func_vals, 'bo-', alpha=0.6, label='Score at each evaluation')
ax.plot(range(1, n_calls+1), -best_so_far, 'r-', linewidth=2, label='Best score (cumulative)')
ax.set_xlabel('Number of evaluations', fontsize=12)
ax.set_ylabel('Accuracy', fontsize=12)
ax.set_title('Convergence of Bayesian optimization', fontsize=14, fontweight='bold')
ax.legend()
ax.grid(alpha=0.3)
# 2. Exploration of the parameter space (n_estimators vs max_depth)
ax = axes[0, 1]
params_history = np.array(result.x_iters)
scores_history = -np.array(result.func_vals)
scatter = ax.scatter(params_history[:, 0], params_history[:, 1],
c=scores_history, cmap='viridis', s=100,
edgecolor='black', linewidth=1, alpha=0.7)
ax.scatter(result.x[0], result.x[1], color='red', s=300, marker='*',
edgecolor='black', linewidth=2, label='Optimal parameters', zorder=5)
plt.colorbar(scatter, ax=ax, label='Accuracy')
ax.set_xlabel('n_estimators', fontsize=12)
ax.set_ylabel('max_depth', fontsize=12)
ax.set_title('Exploration of the parameter space (n_estimators vs max_depth)', fontsize=14, fontweight='bold')
ax.legend()
ax.grid(alpha=0.3)
# 3. Distribution of each parameter
ax = axes[1, 0]
param_names = ['n_estimators', 'max_depth', 'min_samples_split']
param_values = [params_history[:, i] for i in range(3)]
colors = ['skyblue', 'lightcoral', 'lightgreen']
bp = ax.boxplot(param_values, labels=param_names, patch_artist=True)
for patch, color in zip(bp['boxes'], colors):
patch.set_facecolor(color)
patch.set_alpha(0.7)
ax.set_ylabel('Parameter value', fontsize=12)
ax.set_title('Distribution of the explored parameters', fontsize=14, fontweight='bold')
ax.grid(alpha=0.3, axis='y')
# 4. Evolution of the acquisition function
ax = axes[1, 1]
# Recompute the EI value at each evaluation point (simplified version)
ei_values = []
for i in range(1, len(result.func_vals)):
best_y = np.min(result.func_vals[:i])
current_y = result.func_vals[i]
improvement = max(best_y - current_y, 0)
ei_values.append(improvement)
ax.plot(range(2, n_calls+1), ei_values, 'go-', linewidth=2, markersize=6)
ax.set_xlabel('Number of evaluations', fontsize=12)
ax.set_ylabel('Improvement (simplified EI)', fontsize=12)
ax.set_title('Evolution of the acquisition function (improvement)', fontsize=14, fontweight='bold')
ax.grid(alpha=0.3)
plt.tight_layout()
plt.savefig('bayesian_optimization_results.png', dpi=300, bbox_inches='tight')
plt.show()
# Comparison with random search
print("\n=== Comparison with Random Search ===")
np.random.seed(42)
random_scores = []
for _ in range(30):
random_params = [
np.random.randint(10, 200), # n_estimators
np.random.randint(3, 30), # max_depth
np.random.randint(2, 20) # min_samples_split
]
score = objective(random_params)
random_scores.append(score)
best_random_score = -min(random_scores)
print(f"Best score of Bayesian optimization: {-result.fun:.4f}")
print(f"Best score of random search: {best_random_score:.4f}")
print(f"Improvement: {(-result.fun - best_random_score):.4f} ({100*(-result.fun - best_random_score)/best_random_score:.2f}%)")
# Final evaluation with the optimal model
best_model = RandomForestClassifier(
n_estimators=result.x[0],
max_depth=result.x[1],
min_samples_split=result.x[2],
random_state=42,
n_jobs=-1
)
final_scores = cross_val_score(best_model, X, y, cv=5, scoring='accuracy')
print(f"\nDetailed evaluation of the optimal model:")
print(f" Mean accuracy: {final_scores.mean():.4f}")
print(f" Standard deviation: {final_scores.std():.4f}")
print(f" Each fold: {final_scores}")
print("\nā Bayesian optimization efficiently optimizes the hyperparameters")
š Exercises
Problem 1: Understanding Hierarchical Bayesian Models
The same experiment was conducted in three laboratories (A, B, C), yielding the following data. Build a hierarchical Bayesian model and estimate the effect of each laboratory and the overall effect.
- Laboratory A: [25.1, 26.3, 24.8, 25.9, 26.1]
- Laboratory B: [28.2, 29.1, 27.8, 28.5]
- Laboratory C: [23.5, 24.2, 23.8, 24.0, 23.6, 24.1]
(a) Find the posterior distribution of each laboratory's mean
(b) Find the 95% credible interval of the overall mean
(c) Estimate the between-laboratory standard deviation
Problem 2: Application of Bayesian Linear Regression
You have data on the hardness (HV) and carbon content (mass%) of a material. Apply Bayesian linear regression and find the 95% prediction interval of the hardness when the carbon content is 0.5%.
carbon = np.array([0.1, 0.2, 0.3, 0.4, 0.6, 0.7, 0.8])
hardness = np.array([120, 145, 170, 195, 245, 270, 295])
Problem 3: Model Selection by Bayes Factor
For the following data, evaluate whether a linear model or a cubic model is more appropriate using WAIC or LOO.
x = np.array([1, 2, 3, 4, 5, 6, 7, 8])
y = np.array([2.1, 3.9, 9.2, 15.8, 25.1, 36.3, 49.8, 64.2])
Problem 4: Bayesian Estimation in Quality Control
In a product inspection, 475 out of 500 passed. Find the probability that the pass rate is 95% or higher, and draw the predictive distribution of the number of failures in the next lot (n=100).
Problem 5: Implementing Bayesian Optimization
Use Bayesian optimization to search for the parameters (x, y) that minimize the following function (search range: x, y ā [-5, 5]).
def objective(params):
x, y = params
return (x - 2)**2 + (y + 1)**2 + np.sin(5*x) * np.cos(5*y)
5.8 Summary
In this chapter, we learned about hierarchical Bayesian models and their applications to real-world problems.
š Key Points of This Chapter
- Hierarchical Bayesian models: introduce hyperparameters and estimate individual parameters while sharing information across groups
- Partial pooling: an intermediate between complete pooling and no pooling that enables stable estimation even for groups with little data
- Bayesian linear regression: estimates regression coefficients as probability distributions and quantifies prediction uncertainty
- Bayesian logistic regression: provides probabilistic predictions and credible intervals for binary classification problems
- Bayes factors: use WAIC/LOO for model selection to choose the best model while preventing overfitting
- Bayesian ANOVA: probabilistically evaluates between-group differences when comparing multiple groups
- Applications to quality control: support decision-making that accounts for uncertainty through pass-rate estimation and risk assessment
- Bayesian optimization: efficiently optimizes expensive-to-evaluate objective functions using a Gaussian process and an acquisition function
Practical Applications
Hierarchical Bayesian models are widely used to analyze data with a group structure, such as in materials science, quality control, and clinical trials. Bayesian optimization is especially effective for problems where evaluation is expensive, such as experimental design, materials design, and hyperparameter tuning in machine learning.
Throughout this series, we have learned from the fundamentals to the applications of estimation theory, hypothesis testing, and Bayesian inference. Using these methods appropriately and drawing reliable insights from data is essential in data-driven research and development.