Baum-Welch filter/smoother for hidden Markov models.


A hidden Markov model (HMM) is a state-space model with a finite state-space, {1, …, K}. The Baum-Welch algorithm allows to compute (exactly) the filtering and smoothing distributions of such a model; i.e. the probabilities that X_t=k given data Y_{0:t} (filtering) or the complete data Y_{0:T} (smoothing). In addition, one may sample trajectories from the complete smoothing distribution (the distribution of all the states, X_{0:T}, given all the data).

Hidden Markov models and the Baum-Welch algorithm are covered in Chapter 6 of the book.

Defining a hidden Markov model

Hidden Markov models are represented as HMM objects; HMM is a subclass of StateSpaceModel (from module state_space_models), which assigns:

  • A categorical distribution to X_0 (parameter init_dist)

  • A categorical distribution to X_t given X_{t-1} (parameter trans_mat)

The distributions of Y_t|X_t must be defined by sub-classing HMM. For instance, this module defines a GaussianHMM class as follows:

class GaussianHMM(HMM):
    default_params = {'mus': None, 'sigmas': None}

    def PY(self, t, xp, x):
        return dists.Normal(loc=self.mus[x], scale=self.sigmas[x])

One may now define a particular model in the usual way:

tm = np.array([[0.9 0.1], [0.2, 0.8]])
my_hmm = hmm.GaussianHMM(mus=np.array([0., 1.], sigmas=np.ones(2),

and e.g. sample data from this model:

true_states, y = my_hmm.simulate(100)

(This works because, again, HMM is a subclass of StateSpaceModels).


Since HMM is a subclass of StateSpaceModel, method PY has the same signature as in its parent class, but argument xp is not used. In other words, you cannot specify a HMM model where $Y_t$ would depend on both $X_t$ and $X_{t-1}$ (unlike in the general case).

Running the Baum-Welch algorithm

Class BaumWelch is instantiated as follows:

bw = BaumWelch(hmm=my_hmm, data=y)

To actually run the algorithm, one must invoke the appropriate methods, e.g.:

bw.forward()  # computes the filtering probs
bw.backward()  # computes the marginal smoothing probs
bw.sample(N=30)  # generate 30 smoothing trajectories

If you invoke either backward or sample directly, the forward pass will be run first. The output of the forward and backward passes are attributes of object bw, which are lists of K-length numpy arrays. For instance, self.filt is a list of arrays containing the filtering probabilities; see the documentation of BaumWelch for more details.

Running the forward pass step by step

A BaumWelch object is an iterator; each iteration performs a single step of the forward pass. It is thus possible for the user to run the forward pass step by step:

next(bw)  # performs one step of the forward pass

This may be useful in a variety of scenarios, such as when data are acquired on the fly (in that case, modify attribute directly), or when one wants to perform the smoothing pass at different times; in particular:

bw = BaumWelch(hmm=mh_hmm, data=y)
for t, _ in enumerate(y):
    ## save the results in bw.smth somewhere

would compute all the intermediate smoothing distributions (for data $Y_0$, then $Y_{0:1}$, and so on). This is expensive, of course (cost is O(T^2)).


BaumWelch([hmm, data])

Baum-Welch filtering/smoothing algorithm.


Gaussian HMM: \(Y_t|X_t=k \sim N(\mu_k, \sigma_k^2)\)


Base class for hidden Markov models.