Principle:Pyro ppl Pyro Autoname Scoping
| Knowledge Sources | |
|---|---|
| Domains | Probabilistic Programming, Software Engineering, Modular Design |
| Last Updated | 2026-02-09 09:00 GMT |
Overview
Automatic naming and scoping provides a systematic mechanism for generating unique, hierarchical names for sample sites and parameters in probabilistic programs, enabling modular model composition without name conflicts.
Description
In probabilistic programming, every sample site and learnable parameter must have a unique name. These names serve as keys for:
- Matching model sites to guide sites during variational inference.
- Recording values in execution traces.
- Conditioning on observed data.
- Replaying recorded values.
When building complex models from reusable components, manual naming becomes error-prone:
- Two components might use the same name for different parameters.
- Recursive or repeated components need distinct names for each invocation.
- Renaming is tedious and brittle when refactoring.
Automatic naming (autoname) solves this by:
- Assigning names to sample sites based on the function they appear in.
- Using scoping to create hierarchical namespaces (e.g., "encoder/layer1/weight").
- Automatically appending indices when the same function is called multiple times.
Scoping mechanisms include:
- Function-based scoping: Each model function creates a scope; sample sites within are prefixed with the function name.
- Plate-based scoping: Sites inside plates are indexed by the plate iteration.
- Explicit scope markers: Users can manually create named scopes for organizational purposes.
- Tree-structured data: When model components correspond to tree-structured data (e.g., phylogenetic trees), the naming system generates names that reflect the tree structure.
These mechanisms enable modular model composition: a user can define a reusable component (e.g., a Bayesian linear layer) and use it multiple times in a model without worrying about name conflicts, because each invocation is automatically scoped.
Usage
Use autoname scoping when:
- Building models from reusable probabilistic components.
- Using the same model function multiple times (e.g., mixture components, repeated layers).
- Working with hierarchical or tree-structured models where naming must reflect structure.
- Composing independently developed model modules into a larger model.
- Debugging name conflicts or matching errors between model and guide.
Theoretical Basis
Naming convention:
# Flat naming (manual, error-prone):
# sample("weight", Normal(0, 1)) -- name: "weight"
# sample("weight", Normal(0, 1)) -- ERROR: duplicate name!
# Scoped naming (automatic):
# with scope("layer1"):
# sample("weight", Normal(0, 1)) -- name: "layer1/weight"
# with scope("layer2"):
# sample("weight", Normal(0, 1)) -- name: "layer2/weight"
# No conflict!
Autoname mechanism:
# Decorator that auto-scopes a function:
def autoname(fn):
def wrapper(*args, **kwargs):
with scope(fn.__name__):
return fn(*args, **kwargs)
return wrapper
# Usage:
@autoname
def bayesian_linear(x, in_features, out_features):
weight = sample("weight", Normal(0, 1).expand([out_features, in_features]))
bias = sample("bias", Normal(0, 1).expand([out_features]))
return x @ weight.T + bias
# First call: names are "bayesian_linear/weight", "bayesian_linear/bias"
# But second call would conflict! Solution: add counter
Counter-based disambiguation:
# When the same function is called multiple times:
# Append an index to the scope
# Call 1: scope = "bayesian_linear"
# -> "bayesian_linear/weight", "bayesian_linear/bias"
# Call 2: scope = "bayesian_linear__1"
# -> "bayesian_linear__1/weight", "bayesian_linear__1/bias"
# Or with explicit naming:
# model.linear1 = bayesian_linear # scope: "linear1"
# model.linear2 = bayesian_linear # scope: "linear2"
Tree-structured naming:
# For tree-structured models (e.g., phylogenetics):
# Each tree node gets a scope:
def tree_model(node):
with scope(node.name):
state = sample("state", prior)
for child in node.children:
transition = sample(f"transition_to_{child.name}", transition_dist)
tree_model(child)
# Results in names like:
# "root/state"
# "root/transition_to_left"
# "root/left/state"
# "root/left/transition_to_leaf1"
# etc.
Mixture model naming example:
# Mixture of K components, each with the same structure:
def component(x):
mu = sample("mu", Normal(0, 10))
sigma = sample("sigma", LogNormal(0, 1))
return Normal(mu, sigma).log_prob(x)
# With autoname:
# for k in range(K):
# with scope(f"component_{k}"):
# component(x)
# Names: "component_0/mu", "component_0/sigma",
# "component_1/mu", "component_1/sigma", ...