7.3 KiB
Hidden Markov Model (HMM) API
Overview
The Hidden Markov Model (HMM) module provides efficient implementations of the Baum-Welch algorithm for parameter estimation and the Viterbi algorithm for state sequence decoding. This is particularly useful for regime detection in time series, speech recognition, biological sequence analysis, and financial market state identification.
Class: HMM
from optimizr import HMM
Constructor
HMM(n_states: int = 2)
Parameters:
n_states(int): Number of hidden states. Must be at least 2. Default is 2.
Raises:
ValueError: Ifn_states < 2
Attributes
After fitting, the following attributes are populated:
-
transition_matrix_(np.ndarray): State transition probabilities matrix of shape(n_states, n_states). Entry[i, j]represents the probability of transitioning from stateito statej. -
emission_means_(np.ndarray): Mean parameters of Gaussian emissions for each state. Array of shape(n_states,). -
emission_stds_(np.ndarray): Standard deviation parameters of Gaussian emissions for each state. Array of shape(n_states,).
Methods
fit(X, n_iterations=100, tolerance=1e-6)
Fit HMM parameters using the Baum-Welch (Expectation-Maximization) algorithm.
Parameters:
X(np.ndarray): Time series observations as a 1D array.n_iterations(int, optional): Maximum number of EM iterations. Default is 100.tolerance(float, optional): Convergence threshold for log-likelihood change. Default is 1e-6.
Returns:
self(HMM): The fitted model instance.
Raises:
ValueError: IfXis empty.
Example:
import numpy as np
from optimizr import HMM
# Generate sample data with regime changes
returns = np.concatenate([
np.random.normal(0.01, 0.02, 500), # Bull market
np.random.normal(-0.01, 0.03, 500), # Bear market
])
# Create and fit HMM
hmm = HMM(n_states=2)
hmm.fit(returns, n_iterations=100)
print("Transition Matrix:")
print(hmm.transition_matrix_)
print("\nEmission Means:", hmm.emission_means_)
print("Emission Stds:", hmm.emission_stds_)
predict(X)
Predict the most likely state sequence using the Viterbi algorithm.
Parameters:
X(np.ndarray): Time series observations as a 1D array.
Returns:
states(np.ndarray): Array of integers representing the most likely state at each time step. Same length asX.
Raises:
ValueError: If the model has not been fitted yet.
Example:
# Decode most likely state sequence
states = hmm.predict(returns)
print(f"Detected states: {np.unique(states)}")
print(f"State distribution: {np.bincount(states)}")
# Visualize regime changes
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 6))
plt.plot(returns, alpha=0.6, label='Returns')
plt.scatter(range(len(returns)), returns, c=states, cmap='viridis',
alpha=0.3, s=1, label='States')
plt.legend()
plt.title('HMM Regime Detection')
plt.show()
score(X)
Compute the log-likelihood of observations given the model.
Parameters:
X(np.ndarray): Time series observations as a 1D array.
Returns:
log_likelihood(float): The log probability of the observations given the model parameters.
Raises:
ValueError: If the model has not been fitted yet.
Example:
# Calculate model fit quality
ll = hmm.score(returns)
print(f"Log-likelihood: {ll:.2f}")
# Compare different numbers of states
for n in [2, 3, 4]:
hmm_temp = HMM(n_states=n)
hmm_temp.fit(returns)
ll = hmm_temp.score(returns)
print(f"States: {n}, Log-likelihood: {ll:.2f}")
Complete Example
import numpy as np
from optimizr import HMM
# Simulate financial returns with regime switching
np.random.seed(42)
# Create synthetic data with 3 regimes
n_samples = 1000
regime_1 = np.random.normal(0.02, 0.01, 300) # High return, low vol
regime_2 = np.random.normal(0.00, 0.02, 400) # Neutral return, medium vol
regime_3 = np.random.normal(-0.01, 0.03, 300) # Negative return, high vol
returns = np.concatenate([regime_1, regime_2, regime_3])
# Fit HMM
hmm = HMM(n_states=3)
hmm.fit(returns, n_iterations=100, tolerance=1e-6)
# Decode states
states = hmm.predict(returns)
# Analyze results
print("Transition Matrix:")
print(hmm.transition_matrix_)
print("\nState Statistics:")
for i in range(3):
mask = states == i
print(f"State {i}:")
print(f" Mean: {hmm.emission_means_[i]:.4f}")
print(f" Std: {hmm.emission_stds_[i]:.4f}")
print(f" Count: {np.sum(mask)} ({100*np.sum(mask)/len(states):.1f}%)")
# Calculate model quality
ll = hmm.score(returns)
print(f"\nLog-likelihood: {ll:.2f}")
Performance Notes
-
Rust Backend: When the Rust backend is available, HMM operations are 50-100x faster than pure Python implementations.
-
Python Fallback: If the Rust backend is not available, a pure Python implementation using NumPy is automatically used. A warning will be issued.
-
Memory Efficiency: The implementation uses log-space computations to prevent numerical underflow for long sequences.
-
Numerical Stability: Forward-backward probabilities are normalized at each time step to maintain numerical stability.
Algorithm Details
Baum-Welch (EM) Algorithm
The Baum-Welch algorithm iteratively refines HMM parameters:
- E-step: Compute expected state occupancies using the Forward-Backward algorithm
- M-step: Update transition and emission parameters to maximize expected log-likelihood
- Convergence: Repeat until log-likelihood change is below tolerance
Viterbi Algorithm
The Viterbi algorithm finds the most likely state sequence:
- Initialization: Set initial state probabilities
- Recursion: For each time step, find the most likely path to each state
- Backtracking: Trace back the optimal path from the final state
Common Use Cases
1. Financial Regime Detection
# Detect bull/bear markets in stock returns
hmm = HMM(n_states=2)
hmm.fit(stock_returns)
market_regimes = hmm.predict(stock_returns)
2. Volatility Clustering
# Identify high/low volatility periods
abs_returns = np.abs(returns)
hmm = HMM(n_states=2)
hmm.fit(abs_returns)
volatility_regimes = hmm.predict(abs_returns)
3. Multi-State Analysis
# Analyze complex market dynamics
hmm = HMM(n_states=4)
hmm.fit(returns)
states = hmm.predict(returns)
# States might represent: crash, bear, normal, bull
Tips and Best Practices
-
Choosing n_states: Start with 2-3 states. Use cross-validation or information criteria (AIC/BIC) to select the optimal number.
-
Data Preprocessing: Standardize or normalize data before fitting, especially when combining multiple time series.
-
Initialization: The algorithm initializes parameters based on data quantiles. For better results, you can manually initialize parameters.
-
Convergence: If the algorithm doesn't converge, try:
- Increasing
n_iterations - Adjusting
tolerance - Preprocessing the data to remove outliers
- Increasing
-
Overfitting: Too many states can lead to overfitting. Use a validation set to assess generalization.
See Also
- MCMC API - For Bayesian parameter estimation
- HMM Theory - Mathematical background and references
- Examples - Complete working examples and tutorials