| Title: | Hidden Markov Models for Functional Data |
|---|---|
| Description: | Fits hidden Markov models to time-ordered sequences of curves, such as sample paths of stochastic processes or smoothed functional observations, without projecting the curves onto a finite basis. The emission functions are Onsager-Machlup functionals of Gaussian measures on function spaces, which allows for Brownian motion with drift, fractional Brownian motion, Ornstein-Uhlenbeck processes and non-parametric state means under a choice of Cameron-Martin norm. The Baum-Welch and Viterbi algorithms are implemented in C. Methods are described in Kashlak, Loliencar and Heo (2023) <https://jmlr.org/papers/v24/22-0685.html>. |
| Authors: | Adam B Kashlak [aut, cre] |
| Maintainer: | Adam B Kashlak <[email protected]> |
| License: | GPL (>= 3) |
| Version: | 0.1.0 |
| Built: | 2026-09-24 05:12:37 UTC |
| Source: | https://github.com/cran/funHMM |
Measures the agreement between two labellings of the same objects, e.g. the
decoded states of a thmm() fit and the true states of a simulation. A
value of one indicates identical partitions (up to relabelling) and values
near zero indicate agreement no better than chance.
ari(x, y)ari(x, y)
x, y
|
Two vectors of labels of the same length. |
A single number.
Hubert, L. and Arabie, P. (1985). Comparing partitions. Journal of Classification, 2, 193–218.
ari(c(1, 1, 2, 2, 3), c(2, 2, 1, 1, 3)) # 1: identical up to relabelling ari(c(1, 1, 2, 2), c(1, 2, 1, 2)) # negative: worse than chanceari(c(1, 1, 2, 2, 3), c(2, 2, 1, 1, 3)) # 1: identical up to relabelling ari(c(1, 1, 2, 2), c(1, 2, 1, 2)) # negative: worse than chance
Plots the observed curves coloured by their decoded (Viterbi) state. For the non-parametric model the fitted mean curves are overlaid; for Brownian motion with drift the fitted linear drifts are overlaid.
## S3 method for class 'thmm' plot( x, which = seq_len(dim(x$data)[3L]), col = NULL, lwd.mean = 3, legend = TRUE, ... )## S3 method for class 'thmm' plot( x, which = seq_len(dim(x$data)[3L]), col = NULL, lwd.mean = 3, legend = TRUE, ... )
x |
A fitted |
which |
For multi-dimensional curves, which coordinate(s) to plot. |
col |
Colours for the states (a vector of length |
lwd.mean |
Line width for the overlaid state means (0 suppresses them). |
legend |
Logical; add a legend? |
... |
Further arguments passed to |
x, invisibly.
set.seed(3) tt <- seq(0, 1, length.out = 40) sim <- rthmm(60, init.prob = c(1, 0), trans = matrix(c(.8, .2, .2, .8), 2), type = "nonpar", par = rbind(tt, 1 - tt), sigma = 0.3) fit <- thmm(sim$data, 2, type = "nonpar", norm = "L2", start = "kmeans") plot(fit)set.seed(3) tt <- seq(0, 1, length.out = 40) sim <- rthmm(60, init.prob = c(1, 0), trans = matrix(c(.8, .2, .2, .8), 2), type = "nonpar", par = rbind(tt, 1 - tt), sigma = 0.3) fit <- thmm(sim$data, 2, type = "nonpar", norm = "L2", start = "kmeans") plot(fit)
Runs the forward-backward and Viterbi algorithms on a new sequence of curves using the parameters of a fitted model.
## S3 method for class 'thmm' predict(object, newdata = NULL, type = c("states", "posterior", "loglik"), ...)## S3 method for class 'thmm' predict(object, newdata = NULL, type = c("states", "posterior", "loglik"), ...)
object |
A fitted |
newdata |
Curves in the same format as the |
type |
What to return: the Viterbi |
... |
Unused. |
An integer vector of states, a matrix of posterior probabilities, or a single number.
set.seed(2) A <- matrix(c(.9, .1, .1, .9), 2) sim <- rthmm(80, init.prob = c(1, 0), trans = A, type = "bmwd", par = c(-3, 3), len = 50) fit <- thmm(sim$data, 2, type = "bmwd") new <- rthmm(40, init.prob = c(1, 0), trans = A, type = "bmwd", par = c(-3, 3), len = 50) table(predict(fit, new$data), new$states)set.seed(2) A <- matrix(c(.9, .1, .1, .9), 2) sim <- rthmm(80, init.prob = c(1, 0), trans = A, type = "bmwd", par = c(-3, 3), len = 50) fit <- thmm(sim$data, 2, type = "bmwd") new <- rthmm(40, init.prob = c(1, 0), trans = A, type = "bmwd", par = c(-3, 3), len = 50) table(predict(fit, new$data), new$states)
Simulates n sample paths of on the
grid , , where
is fractional Brownian motion with Hurst parameter hurst
(standard Brownian motion for hurst = 0.5).
rbm(n, len = 100L, drift = 0, hurst = 0.5, sigma = 1)rbm(n, len = 100L, drift = 0, hurst = 0.5, sigma = 1)
n |
Number of sample paths. |
len |
Number of grid points per path. |
drift |
Drift coefficient; a single number or a vector of length |
hurst |
Hurst parameter in (0, 1). |
sigma |
Diffusion coefficient. |
For hurst != 0.5 the paths are generated from the covariance
function
using a Cholesky factor of the len x len covariance matrix, so large
values of len are slow.
An n x len matrix with one path per row.
x <- rbm(5, len = 100, drift = c(-2, -1, 0, 1, 2)) matplot(t(x), type = "l", lty = 1) y <- rbm(3, len = 100, hurst = 0.8)x <- rbm(5, len = 100, drift = c(-2, -1, 0, 1, 2)) matplot(t(x), type = "l", lty = 1) y <- rbm(3, len = 100, hurst = 0.8)
Simulates smooth random curves from a truncated Karhunen-Loeve expansion of
the Brownian bridge,
with independent . This is the error process
used in the non-parametric simulations of Kashlak, Loliencar and Heo (2023).
rbridge(n, len = 100L, sigma = 1, nterms = 16L)rbridge(n, len = 100L, sigma = 1, nterms = 16L)
n |
Number of curves. |
len |
Number of grid points |
sigma |
Standard deviation of the coefficients. |
nterms |
Number of terms |
An n x len matrix with one curve per row.
e <- rbridge(5, len = 100, sigma = 0.4) matplot(t(e), type = "l", lty = 1)e <- rbridge(5, len = 100, sigma = 0.4) matplot(t(e), type = "l", lty = 1)
Simulate a Markov chain
rmarkov(n, init.prob, trans)rmarkov(n, init.prob, trans)
n |
Length of the chain. |
init.prob |
Initial state probabilities. |
trans |
Transition matrix (rows sum to one). |
An integer vector of states in 1:length(init.prob).
rmarkov(20, c(1, 0), matrix(c(.9, .1, .1, .9), 2))rmarkov(20, c(1, 0), matrix(c(.9, .1, .1, .9), 2))
Simulates , ,
by an Euler scheme on the grid .
rou(n, len = 100L, mean = 0, rate = 1, sigma = 1)rou(n, len = 100L, mean = 0, rate = 1, sigma = 1)
n |
Number of sample paths. |
len |
Number of grid points per path. |
mean |
Long-run mean |
rate |
Mean-reversion rate |
sigma |
Diffusion coefficient. |
An n x len matrix with one path per row.
x <- rou(4, len = 100, mean = c(-2, 0, 2, 4), rate = c(2, 4, 8, 20)) matplot(t(x), type = "l", lty = 1)x <- rou(4, len = 100, mean = c(-2, 0, 2, 4), rate = c(2, 4, 8, 20)) matplot(t(x), type = "l", lty = 1)
Generates a hidden state sequence from a Markov chain and, conditional on
the states, a sequence of curves from one of the emission models of
thmm().
rthmm( n, init.prob, trans, type = c("bmwd", "ou", "nonpar"), par, len = 100L, hurst = 0.5, sigma = 1, nterms = 16L )rthmm( n, init.prob, trans, type = c("bmwd", "ou", "nonpar"), par, len = 100L, hurst = 0.5, sigma = 1, nterms = 16L )
n |
Number of time steps (curves). |
init.prob |
Initial state probabilities. |
trans |
Transition matrix. |
type |
Emission model, see |
par |
State parameters in the format described in |
len |
Number of grid points per curve (taken from |
hurst |
Hurst parameter for |
sigma |
Noise scale: the diffusion coefficient for |
nterms |
Number of terms passed to |
A list with components data (an n x len matrix, or an
n x len x d array) and states (integer vector of true states).
A <- matrix(0.09, 5, 5) + 0.55 * diag(5) # matrix A1 of the paper sim <- rthmm(200, init.prob = c(1, 0, 0, 0, 0), trans = A, type = "bmwd", par = c(-4, -2, 0, 2, 4), len = 100) matplot(t(sim$data), type = "l", lty = 1, col = sim$states + 1)A <- matrix(0.09, 5, 5) + 0.55 * diag(5) # matrix A1 of the paper sim <- rthmm(200, init.prob = c(1, 0, 0, 0, 0), trans = A, type = "bmwd", par = c(-4, -2, 0, 2, 4), len = 100) matplot(t(sim$data), type = "l", lty = 1, col = sim$states + 1)
Simulate from a fitted THMM
## S3 method for class 'thmm' simulate(object, nsim = 1L, seed = NULL, ...)## S3 method for class 'thmm' simulate(object, nsim = 1L, seed = NULL, ...)
object |
A fitted |
nsim |
Number of curves (time steps) to simulate. |
seed |
Optional random seed. |
... |
Further arguments passed to |
A list with components data and states, see rthmm().
set.seed(4) sim <- rthmm(60, init.prob = c(1, 0), trans = matrix(c(.8, .2, .2, .8), 2), type = "bmwd", par = c(-3, 3), len = 30) fit <- thmm(sim$data, 2, type = "bmwd") new <- simulate(fit, nsim = 10) dim(new$data)set.seed(4) sim <- rthmm(60, init.prob = c(1, 0), trans = matrix(c(.8, .2, .2, .8), 2), type = "bmwd", par = c(-3, 3), len = 30) fit <- thmm(sim$data, 2, type = "bmwd") new <- simulate(fit, nsim = 10) dim(new$data)
Fits a hidden Markov model to a time-ordered sequence of curves (functional observations) using the Baum-Welch algorithm, with emission functions given by Onsager-Machlup functionals as described in Kashlak, Loliencar and Heo (2023). The most likely state sequence is then decoded with the Viterbi algorithm. The heavy lifting is done in C.
thmm( data, nstates, type = c("bmwd", "ou", "nonpar"), norm = c("W21", "L2", "W22"), hurst = 0.5, sigma = NULL, par = NULL, trans = NULL, init.prob = NULL, start = c("kmeans", "random"), nstart = 1L, max.iter = 500L, min.iter = 10L, tol = 1e-06, verbose = FALSE )thmm( data, nstates, type = c("bmwd", "ou", "nonpar"), norm = c("W21", "L2", "W22"), hurst = 0.5, sigma = NULL, par = NULL, trans = NULL, init.prob = NULL, start = c("kmeans", "random"), nstart = 1L, max.iter = 500L, min.iter = 10L, tol = 1e-06, verbose = FALSE )
data |
A numeric matrix with one curve per row ( |
nstates |
Number of hidden states. |
type |
Emission model, see Details. One of |
norm |
Cameron-Martin norm for |
hurst |
Hurst parameter of the driving fractional Brownian motion for
|
sigma |
Scale (diffusion coefficient) of the driving Gaussian measure.
All log-emission functions are divided by |
par |
Optional starting values for the state parameters. For
|
trans |
Optional starting transition matrix ( |
init.prob |
Optional starting initial state probabilities. Defaults to uniform. |
start |
How to generate starting values when |
nstart |
Number of random starts. The fit with the largest final
log-likelihood is returned. Ignored (set to one) when |
max.iter, min.iter
|
Maximum and minimum number of Baum-Welch iterations. |
tol |
Convergence tolerance: iterations stop once the absolute change
in log-likelihood is below |
verbose |
Logical; print the log-likelihood at each iteration? |
Let be the observed curves and let
denote the emission function of state . Writing
for the derivative of the curve, the models are:
"bmwd"Brownian motion with drift, .
Up to a term that does not depend on ,
where
,
and
.
For hurst = 0.5 this reduces to and
. The drift is re-estimated as the posterior-weighted mean of
. For d-dimensional curves the coordinates are treated as
independent Brownian motions with a d-vector of drifts per state.
"ou"Ornstein-Uhlenbeck process
, parametrised
internally by and . Then
with , , and
(Stratonovich). Because this is quadratic
in the re-estimation step has a closed form, with
constrained to be non-negative.
"nonpar"Non-parametric mean curves with
where
is the chosen Cameron-Martin norm, approximated by a
Riemann sum on the observation grid: for "L2",
for "W21" and
for "W22". The mean curves are re-estimated as posterior-weighted
averages of the observed curves.
All forward and backward probabilities are computed on the log scale.
Iteration stops when the relative change in log-likelihood drops below
tol (after at least min.iter iterations) or after max.iter
iterations. Note that the "log-likelihood" is built from Onsager-Machlup
functionals rather than densities and may be positive.
Automatic choice of sigma. When sigma = NULL the diffusion
coefficient is estimated from second differences of the curves (the
realised quadratic variation, which does not depend on the drift or mean
curves). For "bmwd" and "ou" data generated from the standard model
this returns a value close to one; for the non-parametric model it returns
the scale of the noise in the chosen norm. For smooth curves under the
"L2" norm the estimate can be very small, in which case the emissions
dominate the transition probabilities and the fit behaves like a k-means
clustering that respects time ordering. Supplying a larger sigma
smooths the state assignments.
An object of class "thmm": a list with components
par |
Estimated state parameters, in the format described for the
|
trans |
Estimated transition matrix. |
init.prob |
Estimated initial state probabilities. |
states |
Integer vector: the Viterbi (most likely) state sequence. |
posterior |
|
loglik |
Final log-likelihood (of the returned parameters). |
loglik.trace |
Log-likelihood at every iteration. |
viterbi.loglik |
Log-likelihood of the Viterbi path. |
iter, converged
|
Number of iterations and convergence flag. |
nstates, type, norm, hurst, sigma, sigma.auto
|
Model settings. |
data |
The data, as an |
call |
The matched call. |
Kashlak, A. B., Loliencar, P. and Heo, G. (2023). Topological Hidden Markov Models. Journal of Machine Learning Research, 24(340), 1–49. https://jmlr.org/papers/v24/22-0685.html
rthmm() to simulate data, predict.thmm() to decode new
sequences, plot.thmm().
set.seed(1) A <- matrix(0.1, 3, 3); diag(A) <- 0.8 sim <- rthmm(100, init.prob = c(1, 0, 0), trans = A, type = "bmwd", par = c(-4, 0, 4), len = 50) fit <- thmm(sim$data, nstates = 3, type = "bmwd") fit table(fit$states, sim$states) ari(fit$states, sim$states) ## Ornstein-Uhlenbeck curves with two states sim <- rthmm(100, init.prob = c(1, 0), trans = matrix(c(.9, .1, .1, .9), 2), type = "ou", par = cbind(mean = c(-2, 2), rate = c(4, 4)), len = 50) fit <- thmm(sim$data, nstates = 2, type = "ou") fit$par ## non-parametric mean curves tt <- seq(0, 1, length.out = 50) mu <- rbind(sin(2 * pi * tt), cos(2 * pi * tt)) sim <- rthmm(100, init.prob = c(1, 0), trans = matrix(c(.9, .1, .1, .9), 2), type = "nonpar", par = mu, sigma = 0.4) fit <- thmm(sim$data, nstates = 2, type = "nonpar", norm = "L2") ari(fit$states, sim$states) plot(fit)set.seed(1) A <- matrix(0.1, 3, 3); diag(A) <- 0.8 sim <- rthmm(100, init.prob = c(1, 0, 0), trans = A, type = "bmwd", par = c(-4, 0, 4), len = 50) fit <- thmm(sim$data, nstates = 3, type = "bmwd") fit table(fit$states, sim$states) ari(fit$states, sim$states) ## Ornstein-Uhlenbeck curves with two states sim <- rthmm(100, init.prob = c(1, 0), trans = matrix(c(.9, .1, .1, .9), 2), type = "ou", par = cbind(mean = c(-2, 2), rate = c(4, 4)), len = 50) fit <- thmm(sim$data, nstates = 2, type = "ou") fit$par ## non-parametric mean curves tt <- seq(0, 1, length.out = 50) mu <- rbind(sin(2 * pi * tt), cos(2 * pi * tt)) sim <- rthmm(100, init.prob = c(1, 0), trans = matrix(c(.9, .1, .1, .9), 2), type = "nonpar", par = mu, sigma = 0.4) fit <- thmm(sim$data, nstates = 2, type = "nonpar", norm = "L2") ari(fit$states, sim$states) plot(fit)