This repository contains a PyTorch implementation of a Deep State-Space Model (DSSM) for unsupervised discovery of discrete states in sequential data. The model is based on the principles of variational inference, utilizing a structured posterior and the Gumbel-Softmax reparameterization trick to enable end-to-end training.
The implementation is designed to be clear and modular, using Hydra for configuration and Weights & Biases for experiment tracking. It includes a synthetic data generator that creates time-series data conforming to the Hidden Markov Model (HMM) assumptions, which is used to train the model and verify the correctness of the implementation.
We model a sequence of observations
The generative process
This model consists of:
-
Initial State Distribution
$p(c_1)$ : A uniform categorical distribution over$K$ states. -
Neural Transition Model
$p_{\theta}(c_t | c_{t-1})$ : A neural network that takes the previous state$c_{t-1}$ and outputs the probability distribution for the current state$c_t$ . This allows the model to learn complex, non-linear dynamics between states. -
Emission Model
$p_{\theta}(x_t | c_t)$ : A neural network that takes the current state$c_t$ and outputs the parameters for the probability distribution of the observation$x_t$ . For this implementation, it outputs the mean of a Gaussian distribution with fixed variance.
Since the true posterior
This is implemented using Recurrent Neural Networks (RNNs). A Bi-directional RNN first processes the entire input sequence
We train the model by maximizing the Evidence Lower Bound (ELBO),
The derivation starts with the log-likelihood:
We introduce the inference model
By Jensen's inequality, we get the ELBO:
Expanding the ELBO gives:
Substituting our factorized models and simplifying leads to the final objective function:
The objective is composed of two main parts:
-
Reconstruction Term: $\mathbb{E}{q{\phi}(c_t|X)}[\log p_{\theta}(x_t | c_t)]$. This encourages the model to find latent states that can accurately reconstruct the observed data.
-
KL Divergence Term: This regularizer pushes the approximate posterior
$q_{\phi}$ to be close to the prior$p_{\theta}$ . A key advantage of using categorical latents is that the KL divergence between two categorical distributions,$D_{KL}(\text{Categorical} ,|, \text{Categorical})$ , has an analytic form:$D_{KL}(q ,|, p) = \sum_{k=1}^{K} q_k \log\frac{q_k}{p_k}$ . This allows for exact, low-variance gradient estimation for this part of the loss.
The reconstruction term requires sampling a discrete state
where
This section maps the mathematical terms to their corresponding implementation in dssm/model.py.
-
Inference Model
$q_{\phi}(C|X)$ :-
Context Generation: The Bi-directional GRU
self.birnntakes the input sequencexand produces the context vectorsh_birnn. -
Structured Posterior
$q_{\phi}(c_t | c_{t-1}, X)$ : This is implemented by the unidirectionalself.inference_rnnand the fully-connected layerself.fc_inference. At each timestept, theinference_rnntakes the concatenation of the contexth_birnn[:, t, :]and the previous soft stateprev_c_softas input. The output is passed tofc_inferenceto produce the logitsq_logits_tfor the categorical distribution$q_{\phi}(c_t|...)$ .
-
Context Generation: The Bi-directional GRU
-
Generative Model
$p_{\theta}(X, C)$ :-
Neural Transition Model
$p_{\theta}(c_t | c_{t-1})$ : This isself.transition_net, a simplenn.SequentialMLP. It takes the previous soft stateprev_c_softas input and outputs the logits for the prior distribution$p_{\theta}(c_t|c_{t-1})$ . -
Emission Model
$p_{\theta}(x_t | c_t)$ : This isself.emission_net, anothernn.SequentialMLP. It takes the Gumbel-Softmax samplec_t_softas input and outputs the meanemission_mean_tof the Gaussian distribution for reconstructing the observation$x_t$ .
-
Neural Transition Model
-
Loss Calculation (in
forwardmethod):-
Reconstruction Loss: The
emission_mean_tfrom the emission net is used to define atorch.distributions.Normaldistribution. The loss is the negativelog_probof the true datax[:, t, :]under this distribution, encouraging the model to maximize the probability of the observed data. -
KL Divergence: The logits from the inference net (
q_logits_t) and the transition net (p_logits_t) are used to create twotorch.distributions.Categoricalobjects. The KL divergence is then computed analytically usingtorch.distributions.kl.kl_divergence, providing a stable, exact gradient.
-
Reconstruction Loss: The
The script dssm/data.py generates a synthetic dataset that is ideal for testing this model. It creates data from a true Hidden Markov Model process.
-
State Transition: A transition matrix is created with a strong diagonal bias controlled by the
transition_biashyperparameter. A high value (e.g., 0.9) means that the process has a 90% chance of staying in the same state at each timestep. This creates temporally coherent sequences with clear state persistence. -
State Emission: Each of the
$K$ latent states is assigned a unique, randomly generated mean vector in the feature space. - Observation Generation: At each timestep, an observation is generated by taking the mean vector corresponding to the current true state and adding Gaussian noise.
This process yields a dataset where distinct, persistent hidden states generate the observed sequences, providing a clear and verifiable learning target for the DSSM. The dataloader provides both the observations x and the true latent states c, allowing for the calculation of clustering accuracy.
First, install the required dependencies:
pip install torch numpy wandb hydra-core omegaconf scipyAll hyperparameters for the data, model, and trainer are managed in configs/config.yaml. You can modify this file to experiment with different settings.
To run the training, execute the main script from the project's root directory:
python dssm/main.pyHydra allows you to override any configuration parameter from the command line. For example, to run on the CPU for 10 epochs:
python dssm/main.py trainer.device=cpu trainer.n_epochs=10Training progress, losses, and accuracy metrics will be logged to your Weights & Biases project.
A critical challenge in evaluating unsupervised clustering models is state permutation invariance. The model may learn the correct state clusters but assign them arbitrary integer labels (e.g., its learned state '2' might correspond to the true state '0').
To address this, the _calculate_accuracy function in dssm/main.py does not perform a naive comparison. Instead, at the end of each validation epoch, it:
- Computes a confusion matrix between all predicted states and all true states for the entire validation set.
- Uses the Hungarian algorithm (
scipy.optimize.linear_sum_assignment) to find the optimal one-to-one mapping between predicted and true state labels that maximizes the diagonal of the confusion matrix. - Calculates the accuracy based on this optimal mapping.
This provides a true measure of the model's ability to identify the latent states, regardless of the labels it assigns to them.