CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pymc

Probabilistic Programming in Python: Bayesian Modeling and Probabilistic Machine Learning with PyTensor

Pending
Overview
Eval results
Files

sampling.mddocs/

PyMC Sampling and Inference

PyMC provides a comprehensive suite of sampling and inference methods for Bayesian analysis. The library supports Markov Chain Monte Carlo (MCMC), variational inference, sequential Monte Carlo, and predictive sampling with automatic tuning and diagnostics.

Main Sampling Interface

Primary Sampling Function

import pymc as pm

def sample(draws=1000, tune=1000, chains=None, cores=None, 
           random_seed=None, progressbar=True, step=None, 
           nuts_sampler='nutpie', initvals=None, init='auto',
           jitter_max_retries=10, n_init=200000, trace=None,
           discard_tuned_samples=True, compute_convergence_checks=True,
           keep_warning_stat=False, return_inferencedata=True,
           idata_kwargs=None, callback=None, mp_ctx=None, **kwargs):
    """
    Draw samples from the posterior distribution using MCMC.
    
    Parameters:
    - draws (int): Number of samples to draw (default: 1000)
    - tune (int): Number of tuning samples (default: 1000)  
    - chains (int): Number of chains (default: auto)
    - cores (int): Number of parallel processes (default: auto)
    - random_seed (int): Random seed for reproducibility
    - step (step method): Custom step method (default: auto-select)
    - init (str): Initialization method ('auto', 'adapt_diag', 'jitter+adapt_diag')
    - nuts_sampler (str): NUTS implementation ('nutpie', 'pymc')
    - return_inferencedata (bool): Return ArviZ InferenceData object
    
    Returns:
    - trace: MCMC samples as InferenceData or MultiTrace
    """
    
# Basic sampling
with pm.Model() as model:
    # Define model...
    trace = pm.sample()

# Custom sampling configuration
trace = pm.sample(
    draws=2000,           # More samples
    tune=1500,            # More tuning
    chains=4,             # Parallel chains
    cores=4,              # Use 4 cores
    target_accept=0.9,    # Higher acceptance rate
    max_treedepth=12      # Deeper trees for complex models
)

Initialization Methods

def init_nuts(init='auto', chains=None, n_init=200000, model=None,
              random_seed=None, progressbar=True, jitter_max_retries=10,
              tune=None, initvals=None, **kwargs):
    """
    Initialize NUTS sampler with optimal step size and mass matrix.
    
    Parameters:
    - init (str): Initialization strategy
        - 'auto': Automatic initialization
        - 'adapt_diag': Adapt diagonal mass matrix  
        - 'jitter+adapt_diag': Add jitter + adapt diagonal
        - 'adapt_full': Adapt full mass matrix
    - chains (int): Number of chains to initialize
    - n_init (int): Number of initialization samples
    - initvals (dict): Initial parameter values
    
    Returns:
    - step: Initialized NUTS step method
    - potential: Model potential function
    """

# Initialize NUTS with custom settings
step, potential = pm.init_nuts(
    init='adapt_full',     # Full mass matrix adaptation
    n_init=500000,         # More initialization samples
    chains=4
)

# Use initialized sampler
trace = pm.sample(step=step)

Step Methods

NUTS (No-U-Turn Sampler)

The default and most efficient sampler for continuous variables:

from pymc.step_methods import NUTS

class NUTS:
    """
    No-U-Turn Sampler for continuous variables.
    
    Parameters:
    - vars (list): Variables to sample (default: all continuous)
    - target_accept (float): Target acceptance probability (default: 0.8)
    - max_treedepth (int): Maximum tree depth (default: 10)
    - step_scale (float): Initial step size scaling
    - is_cov (array): Covariance matrix for mass matrix
    """
    
    def __init__(self, vars=None, target_accept=0.8, max_treedepth=10, **kwargs):
        pass

# Custom NUTS configuration
nuts_step = pm.NUTS(
    vars=[param1, param2],    # Specific variables
    target_accept=0.95,       # High acceptance rate
    max_treedepth=15          # Deep trees for complex geometry
)

trace = pm.sample(step=nuts_step)

Hamiltonian Monte Carlo

from pymc.step_methods import HamiltonianMC

class HamiltonianMC:
    """
    Hamiltonian Monte Carlo sampler.
    
    Parameters:
    - vars (list): Variables to sample
    - path_length (float): Length of Hamiltonian trajectory
    - step_rand (function): Step size randomization function
    - step_scale (float): Step size scaling factor
    """

# HMC with fixed trajectory length
hmc_step = pm.HamiltonianMC(vars=continuous_vars, path_length=2.0)

Metropolis Samplers

from pymc.step_methods import (Metropolis, BinaryMetropolis, 
                               CategoricalGibbsMetropolis, DEMetropolis)

# Standard Metropolis-Hastings
metropolis = pm.Metropolis(vars=param, proposal_dist=pm.NormalProposal)

# For binary variables
binary_metropolis = pm.BinaryMetropolis(vars=binary_var)

# For categorical variables  
categorical_gibbs = pm.CategoricalGibbsMetropolis(vars=categorical_var)

# Differential Evolution Metropolis
de_metropolis = pm.DEMetropolis(vars=param, scaling=1e-4)

Slice Sampler

from pymc.step_methods import Slice

class Slice:
    """
    Slice sampler for univariate distributions.
    
    Parameters:
    - vars (list): Variables to sample
    - w (float): Initial width of slice
    - tune (bool): Tune width during sampling
    """

# Slice sampling for specific parameter
slice_step = pm.Slice(vars=[difficult_param], w=2.0)

Specialized Step Methods

from pymc.step_methods import (
    BinaryGibbsMetropolis, BinaryMetropolis, CategoricalGibbsMetropolis,
    DEMetropolis, DEMetropolisZ, CompoundStep, BlockedStep
)

# Binary Gibbs Metropolis for binary variables
binary_gibbs = pm.BinaryGibbsMetropolis('binary_var')

# Binary Metropolis with custom proposal
binary_metro = pm.BinaryMetropolis('binary_var')

# Categorical Gibbs for categorical variables
cat_gibbs = pm.CategoricalGibbsMetropolis('categorical_var')

# Differential Evolution Metropolis
de_metro = pm.DEMetropolis(vars=[var1, var2], 
                          scaling=0.001,    # Scaling factor
                          tune_scaling=True, # Auto-tune scaling
                          tune_interval=100) # Tuning frequency

# DE Metropolis with Z proposals  
de_metro_z = pm.DEMetropolisZ(vars=[var1, var2])

Proposal Distributions

from pymc.step_methods.metropolis import (
    CauchyProposal, LaplaceProposal, MultivariateNormalProposal,
    NormalProposal, PoissonProposal, UniformProposal
)

# Custom Metropolis with different proposals
metro_cauchy = pm.Metropolis('var', proposal_dist=CauchyProposal)
metro_laplace = pm.Metropolis('var', proposal_dist=LaplaceProposal)
metro_uniform = pm.Metropolis('var', proposal_dist=UniformProposal)

# Multivariate normal proposal with custom covariance
mv_proposal = MultivariateNormalProposal(np.eye(3) * 0.1)
metro_mv = pm.Metropolis(vars=[var1, var2, var3], proposal_dist=mv_proposal)

# Poisson proposal for count data
metro_poisson = pm.Metropolis('count_var', proposal_dist=PoissonProposal)

Compound and Blocked Steps

# Compound step combining different samplers
step1 = pm.NUTS([continuous_var1, continuous_var2])
step2 = pm.BinaryGibbsMetropolis([binary_var])
step3 = pm.CategoricalGibbsMetropolis([categorical_var])

compound_step = pm.CompoundStep([step1, step2, step3])

# Use compound step in sampling
trace = pm.sample(step=compound_step)

# Blocked step for grouping variables
blocked_step = pm.BlockedStep(methods=[step1, step2], blocking=[0, 1])

Compound Step Methods

from pymc.step_methods import CompoundStep

# Combine different step methods
compound = pm.CompoundStep([
    pm.NUTS(vars=continuous_vars),
    pm.BinaryMetropolis(vars=binary_vars),
    pm.CategoricalGibbsMetropolis(vars=categorical_vars)
])

trace = pm.sample(step=compound)

Proposal Distributions

Standard Proposals

from pymc.step_methods.metropolis import (
    NormalProposal, UniformProposal, LaplaceProposal,
    CauchyProposal, PoissonProposal, MultivariateNormalProposal
)

# Normal proposal for Metropolis
normal_prop = pm.NormalProposal(s=0.5)  # Standard deviation

# Uniform proposal
uniform_prop = pm.UniformProposal(s=1.0)  # Half-width

# Multivariate normal proposal  
mv_prop = pm.MultivariateNormalProposal(cov=covariance_matrix)

Sequential Monte Carlo

def sample_smc(draws=1000, kernel='metropolis', n_steps=25, parallel=False,
               start=None, cores=None, compute_convergence_checks=True,
               model=None, random_seed=None, progressbar=True, **kernel_kwargs):
    """
    Sequential Monte Carlo sampling.
    
    Parameters:
    - draws (int): Number of samples to draw
    - kernel (str): SMC kernel ('metropolis', 'nuts')
    - n_steps (int): Number of SMC steps
    - parallel (bool): Parallel chain execution
    - start (dict): Starting values
    
    Returns:
    - trace: SMC samples as InferenceData
    """

# Basic SMC sampling
with pm.Model() as model:
    # Define model...
    smc_trace = pm.sample_smc(draws=2000, kernel='metropolis')

# Advanced SMC configuration
smc_trace = pm.sample_smc(
    draws=5000,
    kernel='nuts', 
    n_steps=50,
    parallel=True,
    cores=4
)

Predictive Sampling

Prior Predictive Sampling

def sample_prior_predictive(samples=500, var_names=None, model=None,
                           size=None, keep_size=False, random_seed=None,
                           return_inferencedata=True, extend_inferencedata=True,
                           predictions=False, **kwargs):
    """
    Sample from prior distributions.
    
    Parameters:
    - samples (int): Number of prior samples
    - var_names (list): Variables to sample (default: all)
    - size (int): Size for each sample draw
    - predictions (bool): Include deterministic variables
    - return_inferencedata (bool): Return ArviZ InferenceData
    
    Returns:
    - prior_samples: Prior predictive samples
    """

# Sample from priors
with pm.Model() as model:
    # Define priors and likelihood...
    prior_pred = pm.sample_prior_predictive(samples=1000)

# Sample specific variables only
prior_pred = pm.sample_prior_predictive(
    samples=2000,
    var_names=['alpha', 'beta', 'sigma']
)

Posterior Predictive Sampling

def sample_posterior_predictive(trace, samples=None, var_names=None,
                               size=None, keep_size=False, model=None,
                               progressbar=True, random_seed=None,
                               extend_inferencedata=True, predictions=False,
                               return_inferencedata=True, **kwargs):
    """
    Sample from posterior predictive distribution.
    
    Parameters:
    - trace: MCMC trace or InferenceData object
    - samples (int): Number of predictive samples per posterior sample
    - var_names (list): Variables to predict
    - size (int): Size for each prediction
    - predictions (bool): Include deterministic predictions
    
    Returns:
    - posterior_pred: Posterior predictive samples
    """

# Basic posterior predictive sampling
with pm.Model() as model:
    # Define model and sample...
    trace = pm.sample()
    post_pred = pm.sample_posterior_predictive(trace)

# Out-of-sample predictions
pm.set_data({'X_new': X_test})  # Update data
post_pred = pm.sample_posterior_predictive(
    trace, 
    var_names=['y_pred'],
    predictions=True
)

Direct Sampling

def draw(vars, draws=1, random_seed=None, **kwargs):
    """
    Draw samples directly from distributions.
    
    Parameters:
    - vars: Distribution(s) to sample from
    - draws (int): Number of samples
    - random_seed (int): Random seed
    
    Returns:
    - samples: Array of samples
    """

# Draw from distribution objects
normal_samples = pm.draw(pm.Normal.dist(0, 1), draws=1000)

# Draw from multiple distributions
samples = pm.draw([
    pm.Normal.dist(0, 1),
    pm.Beta.dist(2, 3)
], draws=500)

# Draw within model context
with pm.Model() as model:
    mu = pm.Normal('mu', 0, 1)
    sigma = pm.HalfNormal('sigma', 1)
    
    # Draw from prior
    mu_samples = pm.draw(mu, draws=100)

Sampling Utilities

Compilation Functions

def compile_forward_sampling_function(outputs, inputs, **kwargs):
    """
    Compile fast forward sampling function.
    
    Parameters:
    - outputs (list): Output variables to sample
    - inputs (list): Input parameter variables
    - **kwargs: Additional compilation options
    
    Returns:
    - compiled_fn: Compiled sampling function
    """

# Compile sampling function for predictions
with pm.Model() as model:
    # Define model...
    sampling_fn = pm.compile_forward_sampling_function(
        outputs=[y_pred],
        inputs=[X, theta]
    )
    
# Use compiled function
predictions = sampling_fn(X_new, theta_samples)

Vectorized Operations

def vectorize_over_posterior(fn, trace, var_names=None, **kwargs):
    """
    Vectorize function over posterior samples.
    
    Parameters:
    - fn (callable): Function to vectorize
    - trace: Posterior samples
    - var_names (list): Variables to pass to function
    
    Returns:
    - results: Vectorized function results
    """

# Define custom function
def custom_prediction(alpha, beta, X):
    return alpha + beta * X

# Vectorize over posterior
results = pm.vectorize_over_posterior(
    custom_prediction,
    trace,
    var_names=['alpha', 'beta'],
    X=X_new
)

Advanced Sampling Techniques

Custom Step Methods

from pymc.step_methods import BlockedStep

class CustomStep(BlockedStep):
    """
    Template for custom step methods.
    """
    
    def __init__(self, vars, model=None, **kwargs):
        self.vars = vars
        self.model = model
        super().__init__(vars, [model.compile_logp(vars)])
    
    def step(self, point):
        """Implement custom step logic."""
        # Custom sampling logic here
        return new_point, stats

Parallel Chain Sampling

# Parallel sampling with custom configuration
import multiprocessing as mp

# Set up parallel context
with mp.Pool(processes=4) as pool:
    trace = pm.sample(
        chains=4,
        cores=4,
        mp_ctx=pool
    )

# Control parallelization
trace = pm.sample(
    chains=8,           # More chains than cores
    cores=4,            # Limit parallel processes
    chain_method='sequential'  # Sequential within process
)

Memory-Efficient Sampling

# Use Zarr backend for large traces
trace = pm.sample(
    draws=100000,
    trace=pm.backends.ZarrTrace('large_trace.zarr')
)

# Streaming inference for very large models
with pm.Model() as streaming_model:
    # Large model definition...
    
    # Sample in batches
    for batch in range(10):
        batch_trace = pm.sample(
            draws=1000,
            trace=pm.backends.NDArray(),
            compute_convergence_checks=False
        )
        # Process batch_trace...

Sampling Diagnostics

Built-in Diagnostics

# Sample with full diagnostics
trace = pm.sample(
    compute_convergence_checks=True,  # Compute R-hat, ESS
    keep_warning_stat=True,           # Keep warning statistics
    progressbar=True                  # Show progress
)

# Access sampling statistics
print(trace.get_sampler_stats('diverging').sum())  # Divergences
print(trace.get_sampler_stats('energy'))           # Energy statistics

Custom Callbacks

def custom_callback(trace, draw):
    """Custom callback function called during sampling."""
    if draw % 100 == 0:
        print(f"Completed draw {draw}")
        # Custom diagnostics or early stopping logic

# Sample with callback
trace = pm.sample(callback=custom_callback)

Usage Patterns

Model Selection via Sampling

# Compare models using sampling
models = [model1, model2, model3]
traces = []

for model in models:
    with model:
        trace = pm.sample()
        traces.append(trace)

# Compare using information criteria
comparison = pm.compare({f'model_{i}': trace 
                        for i, trace in enumerate(traces)})

Robust Sampling Strategies

# Robust sampling for difficult models
with pm.Model() as difficult_model:
    # Complex model definition...
    
    # Start with MAP estimate
    map_estimate = pm.find_MAP()
    
    # Initialize sampler carefully
    step = pm.NUTS(
        target_accept=0.99,      # Very high acceptance
        max_treedepth=15,        # Deep trees
        step_scale=0.1           # Small initial steps
    )
    
    # Sample with more tuning
    trace = pm.sample(
        draws=2000,
        tune=3000,               # Extra tuning
        init='adapt_full',       # Full mass matrix
        initvals=map_estimate,   # Start from MAP
        step=step
    )

PyMC's sampling system provides flexible and efficient inference for a wide range of Bayesian models, from simple linear regression to complex hierarchical models with custom likelihood functions.

Install with Tessl CLI

npx tessl i tessl/pypi-pymc

docs

data.md

distributions.md

gp.md

index.md

math.md

model.md

ode.md

sampling.md

stats.md

variational.md

tile.json