Logo
  • Home
  • Theory

Package Reference

  • API

Examples

  • Stochastic Polynomial Chaos Expansion
  • Stochastic Neural Network
  • Stochastic Mixture of Experts
  • Full-Covariance Polynomial Chaos
  • Priors Guide Ambiguous Line Fits
  • Low-Rank Covariance Polynomial Chaos
  • Langevin Polynomial Chaos
  • Langevin Polynomial Chaos with an Energy Model
  • Constitutive Model Surrogate

Acknowledgments

  • Acknowledgments
pypolymix
  • Model

In [ ]:
Copied!
"""Mixture-of-experts surrogate built from ``pypolymix`` components.

The model is a plain ``pypolymix.StochasticModel``: a
:class:`~pypolymix.surrogate_models.MixtureOfExperts` surrogate whose parameter
vector is supplied by a list of parameter groups. In the deterministic case every
group is a :class:`~pypolymix.parameter_groups.DeterministicGroup`, so the model
learns a single point estimate and ``distribution_loss()`` is never used.

Two details are worth understanding before changing anything here.

**Why one group per layer, rather than one group for the whole mixture.**
``DeterministicGroup`` initializes its parameters as ``torch.randn(n)``, which is
a unit-variance normal regardless of layer shape. A depth-4 network initialized
that way saturates immediately and does not train. Splitting the parameter vector
into one group per weight matrix and one per bias vector lets each group be seeded
with the same distribution ``torch.nn.Linear`` would have used. The split is also
the natural handle for making *part* of the model stochastic later: swap the
groups you care about for ``IIDGaussianGroup`` and leave the rest deterministic.

**Group order is load-bearing.** ``StochasticModel`` concatenates group samples in
list order to form the flat parameter vector, and ``MixtureOfExperts`` slices that
vector as ``[expert 0 | expert 1 | ... | expert N-1 | gate]``. Groups must be
appended in exactly that order. ``StochasticModel`` validates the total count, not
the order, so a permutation fails silently.
"""
"""Mixture-of-experts surrogate built from ``pypolymix`` components. The model is a plain ``pypolymix.StochasticModel``: a :class:`~pypolymix.surrogate_models.MixtureOfExperts` surrogate whose parameter vector is supplied by a list of parameter groups. In the deterministic case every group is a :class:`~pypolymix.parameter_groups.DeterministicGroup`, so the model learns a single point estimate and ``distribution_loss()`` is never used. Two details are worth understanding before changing anything here. **Why one group per layer, rather than one group for the whole mixture.** ``DeterministicGroup`` initializes its parameters as ``torch.randn(n)``, which is a unit-variance normal regardless of layer shape. A depth-4 network initialized that way saturates immediately and does not train. Splitting the parameter vector into one group per weight matrix and one per bias vector lets each group be seeded with the same distribution ``torch.nn.Linear`` would have used. The split is also the natural handle for making *part* of the model stochastic later: swap the groups you care about for ``IIDGaussianGroup`` and leave the rest deterministic. **Group order is load-bearing.** ``StochasticModel`` concatenates group samples in list order to form the flat parameter vector, and ``MixtureOfExperts`` slices that vector as ``[expert 0 | expert 1 | ... | expert N-1 | gate]``. Groups must be appended in exactly that order. ``StochasticModel`` validates the total count, not the order, so a permutation fails silently. """
In [ ]:
Copied!
from __future__ import annotations
from __future__ import annotations
In [ ]:
Copied!
import math
from typing import List, Sequence
import math from typing import List, Sequence
In [ ]:
Copied!
import torch
import torch
In [ ]:
Copied!
from pypolymix import StochasticModel
from pypolymix.parameter_groups import DeterministicGroup
from pypolymix.surrogate_models import (GatingNetwork, MixtureOfExperts,
                                        NeuralNetwork)
from pypolymix import StochasticModel from pypolymix.parameter_groups import DeterministicGroup from pypolymix.surrogate_models import (GatingNetwork, MixtureOfExperts, NeuralNetwork)
In [ ]:
Copied!
def linear_init(shape: Sequence[int], fan_in: int, generator=None) -> torch.Tensor:
    """Draw a flat parameter vector the way ``torch.nn.Linear`` would.

    ``nn.Linear`` seeds weights with ``kaiming_uniform_(a=sqrt(5))`` and biases
    with ``U(-1/sqrt(fan_in), 1/sqrt(fan_in))``. For ``a = sqrt(5)`` the Kaiming
    bound reduces to ``1/sqrt(fan_in)`` as well, so both cases are the same
    uniform distribution and no ``nn.init`` call is needed.

    Args:
        shape: ``(in_features, out_features)`` for a weight, ``(out_features,)``
            for a bias.
        fan_in: Input width of the layer this parameter belongs to.
        generator: Optional ``torch.Generator`` for reproducibility.

    Returns:
        Flat tensor with ``prod(shape)`` entries.
    """
    bound = 1.0 / math.sqrt(max(int(fan_in), 1))
    values = torch.empty(math.prod(int(s) for s in shape))
    return values.uniform_(-bound, bound, generator=generator)
def linear_init(shape: Sequence[int], fan_in: int, generator=None) -> torch.Tensor: """Draw a flat parameter vector the way ``torch.nn.Linear`` would. ``nn.Linear`` seeds weights with ``kaiming_uniform_(a=sqrt(5))`` and biases with ``U(-1/sqrt(fan_in), 1/sqrt(fan_in))``. For ``a = sqrt(5)`` the Kaiming bound reduces to ``1/sqrt(fan_in)`` as well, so both cases are the same uniform distribution and no ``nn.init`` call is needed. Args: shape: ``(in_features, out_features)`` for a weight, ``(out_features,)`` for a bias. fan_in: Input width of the layer this parameter belongs to. generator: Optional ``torch.Generator`` for reproducibility. Returns: Flat tensor with ``prod(shape)`` entries. """ bound = 1.0 / math.sqrt(max(int(fan_in), 1)) values = torch.empty(math.prod(int(s) for s in shape)) return values.uniform_(-bound, bound, generator=generator)
In [ ]:
Copied!
def network_groups(network, prefix: str, generator=None) -> List[DeterministicGroup]:
    """Build one initialized :class:`DeterministicGroup` per weight and bias.

    Args:
        network: A ``pypolymix`` ``NeuralNetwork`` (or anything exposing the same
            ``param_slices`` of ``(start, end, shape)`` triples, weights first).
        prefix: Name prefix for the generated groups.
        generator: Optional ``torch.Generator``.

    Returns:
        Groups in the order the surrogate expects to slice them.
    """
    groups: List[DeterministicGroup] = []
    fan_in = network.num_inputs
    for index, (_, _, shape) in enumerate(network.param_slices):
        layer = index // 2
        if len(shape) == 2:
            fan_in = int(shape[0])
            name = f"{prefix}_layer{layer}_weight"
        else:
            name = f"{prefix}_layer{layer}_bias"
        group = DeterministicGroup(name, math.prod(int(s) for s in shape))
        with torch.no_grad():
            group.params.copy_(linear_init(shape, fan_in, generator=generator))
        groups.append(group)
    return groups
def network_groups(network, prefix: str, generator=None) -> List[DeterministicGroup]: """Build one initialized :class:`DeterministicGroup` per weight and bias. Args: network: A ``pypolymix`` ``NeuralNetwork`` (or anything exposing the same ``param_slices`` of ``(start, end, shape)`` triples, weights first). prefix: Name prefix for the generated groups. generator: Optional ``torch.Generator``. Returns: Groups in the order the surrogate expects to slice them. """ groups: List[DeterministicGroup] = [] fan_in = network.num_inputs for index, (_, _, shape) in enumerate(network.param_slices): layer = index // 2 if len(shape) == 2: fan_in = int(shape[0]) name = f"{prefix}_layer{layer}_weight" else: name = f"{prefix}_layer{layer}_bias" group = DeterministicGroup(name, math.prod(int(s) for s in shape)) with torch.no_grad(): group.params.copy_(linear_init(shape, fan_in, generator=generator)) groups.append(group) return groups
In [ ]:
Copied!
def build_moe(cfg) -> StochasticModel:
    """Assemble the deterministic mixture-of-experts surrogate from a ``Config``."""
    activation = cfg.activation_fn()
    generator = torch.Generator().manual_seed(cfg.seed)

    experts = [
        NeuralNetwork(
            num_inputs=cfg.num_inputs,
            num_outputs=cfg.num_outputs,
            width=cfg.expert_width,
            depth=cfg.expert_depth,
            activation=activation,
        )
        for _ in range(cfg.num_experts)
    ]
    gate = GatingNetwork(
        num_inputs=cfg.num_inputs,
        num_experts=cfg.num_experts,
        width=cfg.gate_width,
        depth=cfg.gate_depth,
        activation=activation,
    )
    surrogate = MixtureOfExperts(experts, gate)

    groups: List[DeterministicGroup] = []
    for index, expert in enumerate(experts):
        groups.extend(network_groups(expert, f"expert{index}", generator=generator))
    groups.extend(network_groups(gate.network, "gate", generator=generator))

    return StochasticModel(surrogate, groups)
def build_moe(cfg) -> StochasticModel: """Assemble the deterministic mixture-of-experts surrogate from a ``Config``.""" activation = cfg.activation_fn() generator = torch.Generator().manual_seed(cfg.seed) experts = [ NeuralNetwork( num_inputs=cfg.num_inputs, num_outputs=cfg.num_outputs, width=cfg.expert_width, depth=cfg.expert_depth, activation=activation, ) for _ in range(cfg.num_experts) ] gate = GatingNetwork( num_inputs=cfg.num_inputs, num_experts=cfg.num_experts, width=cfg.gate_width, depth=cfg.gate_depth, activation=activation, ) surrogate = MixtureOfExperts(experts, gate) groups: List[DeterministicGroup] = [] for index, expert in enumerate(experts): groups.extend(network_groups(expert, f"expert{index}", generator=generator)) groups.extend(network_groups(gate.network, "gate", generator=generator)) return StochasticModel(surrogate, groups)
In [ ]:
Copied!
def predict(model: StochasticModel, z: torch.Tensor) -> torch.Tensor:
    """Evaluate the model on transformed inputs, returning ``(batch, num_outputs)``.

    Every group is deterministic, so all draws are identical and one suffices.
    """
    return model(z, num_samples=1).squeeze(0)
def predict(model: StochasticModel, z: torch.Tensor) -> torch.Tensor: """Evaluate the model on transformed inputs, returning ``(batch, num_outputs)``. Every group is deterministic, so all draws are identical and one suffices. """ return model(z, num_samples=1).squeeze(0)
In [ ]:
Copied!
def gating_weights(model: StochasticModel, z: torch.Tensor) -> torch.Tensor:
    """Return the mixture weights ``(batch, num_experts)`` for transformed inputs."""
    params = model.sample_parameters(1)
    return model.surrogate_model.get_gating_weights(z, params).squeeze(0)
def gating_weights(model: StochasticModel, z: torch.Tensor) -> torch.Tensor: """Return the mixture weights ``(batch, num_experts)`` for transformed inputs.""" params = model.sample_parameters(1) return model.surrogate_model.get_gating_weights(z, params).squeeze(0)
In [ ]:
Copied!
def parameter_summary(model: StochasticModel) -> str:
    """One-line description of the parameter layout, for the training log."""
    groups = list(model.parameter_groups)
    return (
        f"{model.num_params():,d} parameters in {len(groups)} groups "
        f"({model.surrogate_model.num_experts} experts + gate)"
    )
def parameter_summary(model: StochasticModel) -> str: """One-line description of the parameter layout, for the training log.""" groups = list(model.parameter_groups) return ( f"{model.num_params():,d} parameters in {len(groups)} groups " f"({model.surrogate_model.num_experts} experts + gate)" )
In [ ]:
Copied!
__all__ = [
    "build_moe",
    "gating_weights",
    "linear_init",
    "network_groups",
    "parameter_summary",
    "predict",
]
__all__ = [ "build_moe", "gating_weights", "linear_init", "network_groups", "parameter_summary", "predict", ]

Built with MkDocs using a theme provided by Read the Docs.