HMMs and Latent Structure¶
Latent models in mixle are distributions that wrap other distributions.
They add hidden variables without changing the outer query surface:
score observations with
log_density;fit with
optimize;inspect posteriors when supported;
sample or enumerate when the model has the capability.
The two most common latent wrappers are mixtures and HMMs.
Mixtures¶
A mixture adds one latent component assignment per observation.
from mixle.inference import best_of, optimize
from mixle.stats import GaussianEstimator, MixtureEstimator
est = MixtureEstimator([GaussianEstimator(), GaussianEstimator()])
model = optimize(data, est, max_its=100, out=None)
responsibilities = model.posterior(data)
Use best_of when local optima matter:
import numpy as np
score, model = best_of(
train,
valid,
est,
trials=8,
max_its=100,
init_p=0.1,
delta=1e-8,
rng=np.random.RandomState(0),
out=None,
)
The component can be any estimator with a compatible shape: a scalar distribution, a record, a sequence model, or a neural leaf.
Mixture of Heterogeneous Records¶
from mixle.stats import (
CategoricalEstimator,
CompositeEstimator,
GammaEstimator,
MixtureEstimator,
PoissonEstimator,
)
component = CompositeEstimator(
(
CategoricalEstimator(), # event type
GammaEstimator(), # wait time
PoissonEstimator(), # count
)
)
est = MixtureEstimator([component, component, component])
The latent component clusters whole records. Each component owns its own child distributions.
PPL Markov Models¶
For compact HMMs, mixle.ppl is often the clearest surface:
from mixle.ppl import Markov, Normal, free
hmm = Markov(Normal(free, free), states=3).fit(sequences, how="auto")
post = hmm.posterior(sequences)
Markov lowers to the same latent estimator machinery as the explicit stats
surface.
Default HMM Execution¶
When Numba is installed, HMM distributions now default to the Numba encoder and
Baum-Welch path, matching the estimator default. This matters for the common
workflow where an initialized HMM is passed as prev_estimate: the encoder
comes from the distribution, so the distribution and estimator must agree on
the fast path.
Explicit settings still win. Pass use_numba=False on the HMM family when
you need the pure NumPy path for debugging, parity checks, or an environment
where compiled kernels are not desirable.
Structured HMMs¶
StructuredHMM separates the HMM algorithm from the transition
representation. A transition operator supplies forward products, backward
products, and expected-mass updates. The same forward-backward and EM code can
then use dense, low-rank, sparse, Kronecker, duration, or input-output
structure.
import numpy as np
import mixle.stats as S
from mixle.inference import optimize
from mixle.stats.latent.structured_hmm import (
LowRankTransition,
StructuredHMM,
_row_normalize,
)
rng = np.random.RandomState(0)
k, rank = 8, 2
transition = LowRankTransition(
_row_normalize(rng.rand(k, rank)),
_row_normalize(rng.rand(rank, k)),
)
init = StructuredHMM(
[S.GaussianDistribution(float(i), 1.0) for i in range(k)],
np.ones(k) / k,
transition,
)
model = optimize(sequences, init.estimator(), prev_estimate=init, max_its=40, out=None)
Why use a structured transition?
Transition |
Use when |
|---|---|
Dense |
every state can move to every other state |
Low-rank |
many states but transition structure has fewer degrees of freedom |
Sparse |
left-to-right, skip-limited, or graph-constrained motion |
Kronecker |
factorial states such as |
Sticky |
segmentation should prefer staying in the same state |
Explicit-duration |
state durations are not geometric |
Input-output |
an exogenous input controls which transition applies |
Terminal states |
absorbing states determine sequence length as a stopping time |
Decoding¶
HMMs are useful because they expose latent paths, not just likelihoods.
path = model.viterbi(sequence)
segments = model.viterbi_segments(sequence) # explicit-duration models
state_posteriors = model.posterior(sequence)
Exact method names vary by HMM family; use mixle.describe(model) to see
which latent queries are available.
Enumeration¶
When the support is discrete and the model advertises enumeration, you can ask for top sequences or paths:
enum = model.enumerator()
top = enum.top_k(5)
nucleus = enum.nucleus_size(0.9)
For decomposable supports, ranking and seek can be exact. For hard latent marginals, mixle reports bounded or approximate routes rather than pretending they are exact.
HMMs with Neural or Heterogeneous Emissions¶
An HMM emission is just another distribution. That means the emission can be:
a Gaussian;
a categorical token model;
a composite record;
a sequence model;
a Transformer or other neural leaf, where supported by the estimator shape.
The HMM parent supplies expected state responsibilities. Each child emission uses those responsibilities in its own M-step.
Run the Structured HMM Tour¶
python examples/structured_hmm_example.py
python examples/lookback_hmm_example.py
The structured tour demonstrates low-rank transitions, factorial Kronecker transitions, sparse left-to-right transitions, sticky priors, decoding, enumeration, terminal states, explicit-duration HMMs, and input-output HMMs.
API Map¶
Import |
Purpose |
|---|---|
|
latent clusters over observations |
|
restart latent fitting and select by validation score |
|
compact HMM expression surface |
|
HMM with pluggable transition operator |
|
ordinary dense transition matrix |
|
factorized transition matrix |
|
edge-constrained transition graph |
|
factorial state-space transition |
|
HSMM with duration distributions |
|
transition chosen by exogenous input |