mlpack
hmm_impl.hpp
Go to the documentation of this file.
1 
14 #ifndef MLPACK_METHODS_HMM_HMM_IMPL_HPP
15 #define MLPACK_METHODS_HMM_HMM_IMPL_HPP
16 
17 // Just in case...
18 #include "hmm.hpp"
20 
21 namespace mlpack {
22 namespace hmm {
23 
28 template<typename Distribution>
29 HMM<Distribution>::HMM(const size_t states,
30  const Distribution emissions,
31  const double tolerance) :
32  emission(states, /* default distribution */ emissions),
33  transitionProxy(arma::randu<arma::mat>(states, states)),
34  initialProxy(arma::randu<arma::vec>(states) / (double) states),
35  dimensionality(emissions.Dimensionality()),
36  tolerance(tolerance),
37  recalculateInitial(false),
38  recalculateTransition(false)
39 {
40  // Normalize the transition probabilities and initial state probabilities.
41  initialProxy /= arma::accu(initialProxy);
42  for (size_t i = 0; i < transitionProxy.n_cols; ++i)
43  transitionProxy.col(i) /= arma::accu(transitionProxy.col(i));
44 
45  logTransition = log(transitionProxy);
46  logInitial = log(initialProxy);
47 }
48 
53 template<typename Distribution>
54 HMM<Distribution>::HMM(const arma::vec& initial,
55  const arma::mat& transition,
56  const std::vector<Distribution>& emission,
57  const double tolerance) :
58  emission(emission),
59  transitionProxy(transition),
60  logTransition(log(transition)),
61  initialProxy(initial),
62  logInitial(log(initial)),
63  tolerance(tolerance),
64  recalculateInitial(false),
65  recalculateTransition(false)
66 {
67  // Set the dimensionality, if we can.
68  if (emission.size() > 0)
69  dimensionality = emission[0].Dimensionality();
70  else
71  {
72  Log::Warn << "HMM::HMM(): no emission distributions given; assuming a "
73  << "dimensionality of 0 and hoping it gets set right later."
74  << std::endl;
75  dimensionality = 0;
76  }
77 }
78 
94 template<typename Distribution>
95 double HMM<Distribution>::Train(const std::vector<arma::mat>& dataSeq)
96 {
97  // We should allow a guess at the transition and emission matrices.
98  double loglik = 0;
99  double oldLoglik = 0;
100 
101  // Maximum iterations?
102  size_t iterations = 1000;
103 
104  // Find length of all sequences and ensure they are the correct size.
105  size_t totalLength = 0;
106  for (size_t seq = 0; seq < dataSeq.size(); seq++)
107  {
108  totalLength += dataSeq[seq].n_cols;
109 
110  if (dataSeq[seq].n_rows != dimensionality)
111  Log::Fatal << "HMM::Train(): data sequence " << seq << " has "
112  << "dimensionality " << dataSeq[seq].n_rows << " (expected "
113  << dimensionality << " dimensions)." << std::endl;
114  }
115 
116  // These are used later for training of each distribution. We initialize it
117  // all now so we don't have to do any allocation later on.
118  std::vector<arma::vec> emissionProb(logTransition.n_cols,
119  arma::vec(totalLength));
120  arma::mat emissionList(dimensionality, totalLength);
121 
122  // This should be the Baum-Welch algorithm (EM for HMM estimation). This
123  // follows the procedure outlined in Elliot, Aggoun, and Moore's book "Hidden
124  // Markov Models: Estimation and Control", pp. 36-40.
125  for (size_t iter = 0; iter < iterations; iter++)
126  {
127  // Clear new transition matrix and emission probabilities.
128  arma::vec newLogInitial(logTransition.n_rows);
129  newLogInitial.fill(-std::numeric_limits<double>::infinity());
130  arma::mat newLogTransition(logTransition.n_rows, logTransition.n_cols);
131  newLogTransition.fill(-std::numeric_limits<double>::infinity());
132 
133  // Reset log likelihood.
134  loglik = 0;
135 
136  // Sum over time.
137  size_t sumTime = 0;
138 
139  // Loop over each sequence.
140  for (size_t seq = 0; seq < dataSeq.size(); seq++)
141  {
142  arma::mat stateLogProb;
143  arma::mat forwardLog;
144  arma::mat backwardLog;
145  arma::vec logScales;
146 
147  // Add the log-likelihood of this sequence. This is the E-step.
148  loglik += LogEstimate(dataSeq[seq], stateLogProb, forwardLog,
149  backwardLog, logScales);
150 
151  // Add to estimate of initial probability for state j.
152  math::LogSumExp<arma::vec, true>(stateLogProb.unsafe_col(0),
153  newLogInitial);
154 
155  // Define a variable to store the value of log-probability for data.
156  arma::mat logProbs(dataSeq[seq].n_cols, logTransition.n_rows);
157  // Save the values of log-probability to logProbs.
158  for (size_t i = 0; i < logTransition.n_rows; i++)
159  {
160  // Define alias of desired column.
161  arma::vec alias(logProbs.colptr(i), logProbs.n_rows, false, true);
162  // Use advanced constructor for using logProbs directly.
163  emission[i].LogProbability(dataSeq[seq], alias);
164  }
165 
166  // Now re-estimate the parameters. This is the M-step.
167  // pi_i = sum_d ((1 / P(seq[d])) sum_t (f(i, 0) b(i, 0))
168  // T_ij = sum_d ((1 / P(seq[d])) sum_t (f(i, t) T_ij E_i(seq[d][t]) b(i,
169  // t + 1)))
170  // E_ij = sum_d ((1 / P(seq[d])) sum_{t | seq[d][t] = j} f(i, t) b(i, t)
171  // We store the new estimates in a different matrix.
172  for (size_t t = 0; t < dataSeq[seq].n_cols; ++t)
173  {
174  // Assemble temporary vector that's used in log-sum computation.
175  if (t < dataSeq[seq].n_cols - 1)
176  {
177  // This term is the same across all states, so compute it once and
178  // cache it.
179  const arma::vec tmp = backwardLog.col(t + 1) +
180  logProbs.row(t + 1).t() - logScales[t + 1];
181  arma::vec output;
182  math::LogSumExp(tmp, output);
183 
184  for (size_t j = 0; j < logTransition.n_cols; ++j)
185  {
186  // Compute the estimate of T_ij (probability of transition from
187  // state j to state i). We postpone multiplication of the old T_ij
188  // until later.
189  arma::vec tmp2 = output + forwardLog(j, t);
190  arma::vec alias = newLogTransition.unsafe_col(j);
191  math::LogSumExp<arma::vec, true>(tmp2, alias);
192  }
193  }
194 
195  // Add to list of emission observations, for Distribution::Train().
196  for (size_t j = 0; j < logTransition.n_cols; ++j)
197  emissionProb[j][sumTime] = exp(stateLogProb(j, t));
198  emissionList.col(sumTime) = dataSeq[seq].col(t);
199  sumTime++;
200  }
201  }
202 
203  if (std::abs(oldLoglik - loglik) < tolerance)
204  {
205  Log::Debug << "Converged after " << iter << " iterations." << std::endl;
206  break;
207  }
208 
209  oldLoglik = loglik;
210 
211  // Normalize the new initial probabilities.
212  if (dataSeq.size() > 1)
213  logInitial = newLogInitial - std::log(dataSeq.size());
214  else
215  logInitial = newLogInitial;
216 
217  // Assign the new transition matrix. We use %= (element-wise
218  // multiplication) because every element of the new transition matrix must
219  // still be multiplied by the old elements (this is the multiplication we
220  // earlier postponed).
221  logTransition += newLogTransition;
222 
223  // Now we normalize the transition matrix.
224  for (size_t i = 0; i < logTransition.n_cols; i++)
225  {
226  const double sum = math::AccuLog(logTransition.col(i));
227  if (std::isfinite(sum))
228  logTransition.col(i) -= sum;
229  else
230  logTransition.col(i).fill(-log((double) logTransition.n_rows));
231  }
232 
233  initialProxy = exp(logInitial);
234  transitionProxy = exp(logTransition);
235  // Now estimate emission probabilities.
236  for (size_t state = 0; state < logTransition.n_cols; state++)
237  emission[state].Train(emissionList, emissionProb[state]);
238 
239  Log::Debug << "Iteration " << iter << ": log-likelihood " << loglik
240  << "." << std::endl;
241  }
242  return loglik;
243 }
244 
249 template<typename Distribution>
250 void HMM<Distribution>::Train(const std::vector<arma::mat>& dataSeq,
251  const std::vector<arma::Row<size_t> >& stateSeq)
252 {
253  // Simple error checking.
254  if (dataSeq.size() != stateSeq.size())
255  {
256  Log::Fatal << "HMM::Train(): number of data sequences (" << dataSeq.size()
257  << ") not equal to number of state sequences (" << stateSeq.size()
258  << ")." << std::endl;
259  }
260 
261  arma::mat initial = arma::zeros(logInitial.n_elem);
262  arma::mat transition = arma::zeros(logTransition.n_rows,
263  logTransition.n_cols);
264 
265  // Estimate the transition and emission matrices directly from the
266  // observations. The emission list holds the time indices for observations
267  // from each state.
268  std::vector<std::vector<std::pair<size_t, size_t> > >
269  emissionList(transition.n_cols);
270  for (size_t seq = 0; seq < dataSeq.size(); seq++)
271  {
272  // Simple error checking.
273  if (dataSeq[seq].n_cols != stateSeq[seq].n_elem)
274  {
275  Log::Fatal << "HMM::Train(): number of observations ("
276  << dataSeq[seq].n_cols << ") in sequence " << seq
277  << " not equal to number of states (" << stateSeq[seq].n_cols
278  << ") in sequence " << seq << "." << std::endl;
279  }
280 
281  if (dataSeq[seq].n_rows != dimensionality)
282  {
283  Log::Fatal << "HMM::Train(): data sequence " << seq << " has "
284  << "dimensionality " << dataSeq[seq].n_rows << " (expected "
285  << dimensionality << " dimensions)." << std::endl;
286  }
287 
288  // Loop over each observation in the sequence. For estimation of the
289  // transition matrix, we must ignore the last observation.
290  initial[stateSeq[seq][0]]++;
291  for (size_t t = 0; t < dataSeq[seq].n_cols - 1; t++)
292  {
293  transition(stateSeq[seq][t + 1], stateSeq[seq][t])++;
294  emissionList[stateSeq[seq][t]].push_back(std::make_pair(seq, t));
295  }
296 
297  // Last observation.
298  emissionList[stateSeq[seq][stateSeq[seq].n_elem - 1]].push_back(
299  std::make_pair(seq, stateSeq[seq].n_elem - 1));
300  }
301 
302  // Normalize initial weights.
303  initial /= accu(initial);
304 
305  // Normalize transition matrix.
306  for (size_t col = 0; col < transition.n_cols; col++)
307  {
308  // If the transition probability sum is greater than 0 in this column, the
309  // emission probability sum will also be greater than 0. We want to avoid
310  // division by 0.
311  double sum = accu(transition.col(col));
312  if (sum > 0)
313  transition.col(col) /= sum;
314  }
315 
316  initialProxy = initial;
317  transitionProxy = transition;
318  logTransition = log(transition);
319  logInitial = log(initial);
320 
321  // Estimate emission matrix.
322  for (size_t state = 0; state < transition.n_cols; state++)
323  {
324  // Generate full sequence of observations for this state from the list of
325  // emissions that are from this state.
326  if (emissionList[state].size() > 0)
327  {
328  arma::mat emissions(dimensionality, emissionList[state].size());
329  for (size_t i = 0; i < emissions.n_cols; i++)
330  {
331  emissions.col(i) = dataSeq[emissionList[state][i].first].col(
332  emissionList[state][i].second);
333  }
334 
335  emission[state].Train(emissions);
336  }
337  else
338  {
339  Log::Warn << "There are no observations in training data with hidden "
340  << "state " << state << "! The corresponding emission distribution "
341  << "is likely to be meaningless." << std::endl;
342  }
343  }
344 }
345 
350 template<typename Distribution>
351 double HMM<Distribution>::LogEstimate(const arma::mat& dataSeq,
352  arma::mat& stateLogProb,
353  arma::mat& forwardLogProb,
354  arma::mat& backwardLogProb,
355  arma::vec& logScales) const
356 {
357  arma::mat logProbs(dataSeq.n_cols, logTransition.n_rows);
358 
359  // Save the values of log-probability to logProbs.
360  for (size_t i = 0; i < logTransition.n_rows; i++)
361  {
362  // Define alias of desired column.
363  arma::vec alias(logProbs.colptr(i), logProbs.n_rows, false, true);
364  // Use advanced constructor for using logProbs directly.
365  emission[i].LogProbability(dataSeq, alias);
366  }
367 
368  // First run the forward-backward algorithm.
369  Forward(dataSeq, logScales, forwardLogProb, logProbs);
370  Backward(dataSeq, logScales, backwardLogProb, logProbs);
371 
372  // Now assemble the state probability matrix based on the forward and backward
373  // probabilities.
374  stateLogProb = forwardLogProb + backwardLogProb;
375 
376  // Finally assemble the log-likelihood and return it.
377  return accu(logScales);
378 }
379 
384 template<typename Distribution>
385 double HMM<Distribution>::Estimate(const arma::mat& dataSeq,
386  arma::mat& stateProb,
387  arma::mat& forwardProb,
388  arma::mat& backwardProb,
389  arma::vec& scales) const
390 {
391  arma::mat stateLogProb;
392  arma::mat forwardLogProb;
393  arma::mat backwardLogProb;
394  arma::vec logScales;
395 
396  const double loglikelihood = LogEstimate(dataSeq, stateLogProb,
397  forwardLogProb, backwardLogProb, logScales);
398 
399  stateProb = exp(stateLogProb);
400  forwardProb = exp(forwardLogProb);
401  backwardProb = exp(backwardLogProb);
402  scales = exp(logScales);
403 
404  return loglikelihood;
405 }
406 
411 template<typename Distribution>
412 double HMM<Distribution>::Estimate(const arma::mat& dataSeq,
413  arma::mat& stateProb) const
414 {
415  // We don't need to save these.
416  arma::mat stateLogProb;
417  arma::mat forwardLogProb;
418  arma::mat backwardLogProb;
419  arma::vec logScales;
420 
421  const double loglikelihood = LogEstimate(dataSeq, stateLogProb,
422  forwardLogProb, backwardLogProb, logScales);
423 
424  stateProb = exp(stateLogProb);
425 
426  return loglikelihood;
427 }
428 
434 template<typename Distribution>
435 void HMM<Distribution>::Generate(const size_t length,
436  arma::mat& dataSequence,
437  arma::Row<size_t>& stateSequence,
438  const size_t startState) const
439 {
440  // Set vectors to the right size.
441  stateSequence.set_size(length);
442  dataSequence.set_size(dimensionality, length);
443 
444  // Set start state (default is 0).
445  stateSequence[0] = startState;
446 
447  // Choose first emission state.
448  double randValue = math::Random();
449 
450  // We just have to find where our random value sits in the probability
451  // distribution of emissions for our starting state.
452  dataSequence.col(0) = emission[startState].Random();
453 
454  ConvertToLogSpace();
455 
456  // Now choose the states and emissions for the rest of the sequence.
457  for (size_t t = 1; t < length; t++)
458  {
459  // First choose the hidden state.
460  randValue = math::Random();
461 
462  // Now find where our random value sits in the probability distribution of
463  // state changes.
464  double probSum = 0;
465  for (size_t st = 0; st < logTransition.n_rows; st++)
466  {
467  probSum += exp(logTransition(st, stateSequence[t - 1]));
468  if (randValue <= probSum)
469  {
470  stateSequence[t] = st;
471  break;
472  }
473  }
474 
475  // Now choose the emission.
476  dataSequence.col(t) = emission[stateSequence[t]].Random();
477  }
478 }
479 
485 template<typename Distribution>
486 double HMM<Distribution>::Predict(const arma::mat& dataSeq,
487  arma::Row<size_t>& stateSeq) const
488 {
489  // This is an implementation of the Viterbi algorithm for finding the most
490  // probable sequence of states to produce the observed data sequence. We
491  // don't use log-likelihoods to save that little bit of time, but we'll
492  // calculate the log-likelihood at the end of it all.
493  stateSeq.set_size(dataSeq.n_cols);
494  arma::mat logStateProb(logTransition.n_rows, dataSeq.n_cols);
495  arma::mat stateSeqBack(logTransition.n_rows, dataSeq.n_cols);
496 
497  ConvertToLogSpace();
498 
499  // The calculation of the first state is slightly different; the probability
500  // of the first state being state j is the maximum probability that the state
501  // came to be j from another state.
502  logStateProb.col(0).zeros();
503  for (size_t state = 0; state < logTransition.n_rows; state++)
504  {
505  logStateProb(state, 0) = logInitial[state] +
506  emission[state].LogProbability(dataSeq.unsafe_col(0));
507  stateSeqBack(state, 0) = state;
508  }
509 
510  // Store the best first state.
511  arma::uword index;
512 
513  // Define a variable to store the value of log-probability for dataSeq.
514  arma::mat logProbs(dataSeq.n_cols, logTransition.n_rows);
515 
516  // Save the values of log-probability to logProbs.
517  for (size_t i = 0; i < logTransition.n_rows; i++)
518  {
519  // Define alias of desired column.
520  arma::vec alias(logProbs.colptr(i), logProbs.n_rows, false, true);
521  // Use advanced constructor for using logProbs directly.
522  emission[i].LogProbability(dataSeq, alias);
523  }
524 
525  for (size_t t = 1; t < dataSeq.n_cols; t++)
526  {
527  // Assemble the state probability for this element.
528  // Given that we are in state j, we use state with the highest probability
529  // of being the previous state.
530  for (size_t j = 0; j < logTransition.n_rows; j++)
531  {
532  arma::vec prob = logStateProb.col(t - 1) + logTransition.row(j).t();
533  logStateProb(j, t) = prob.max(index) + logProbs(t, j);
534  stateSeqBack(j, t) = index;
535  }
536  }
537 
538  // Backtrack to find the most probable state sequence.
539  logStateProb.unsafe_col(dataSeq.n_cols - 1).max(index);
540  stateSeq[dataSeq.n_cols - 1] = index;
541  for (size_t t = 2; t <= dataSeq.n_cols; t++)
542  {
543  stateSeq[dataSeq.n_cols - t] =
544  stateSeqBack(stateSeq[dataSeq.n_cols - t + 1], dataSeq.n_cols - t + 1);
545  }
546 
547  return logStateProb(stateSeq(dataSeq.n_cols - 1), dataSeq.n_cols - 1);
548 }
549 
553 template<typename Distribution>
554 double HMM<Distribution>::LogLikelihood(const arma::mat& dataSeq) const
555 {
556  arma::mat forwardLog;
557  arma::vec logScales;
558 
559  // This is needed here.
560  arma::mat logProbs(dataSeq.n_cols, logTransition.n_rows);
561 
562  // Save the values of log-probability to logProbs.
563  for (size_t i = 0; i < logTransition.n_rows; i++)
564  {
565  // Define alias of desired column.
566  arma::vec alias(logProbs.colptr(i), logProbs.n_rows, false, true);
567  // Use advanced constructor for using logProbs directly.
568  emission[i].LogProbability(dataSeq, alias);
569  }
570 
571  Forward(dataSeq, logScales, forwardLog, logProbs);
572 
573  // The log-likelihood is the log of the scales for each time step.
574  return accu(logScales);
575 }
576 
582 template<typename Distribution>
584  const arma::vec& emissionLogProb,
585  arma::vec& forwardLogProb) const
586 {
587  double curLogScale;
588  if (forwardLogProb.empty())
589  {
590  // We are at the start of the sequence (i.e. time t=0).
591  forwardLogProb = ForwardAtT0(emissionLogProb, curLogScale);
592  }
593  else
594  {
595  forwardLogProb = ForwardAtTn(emissionLogProb, curLogScale,
596  forwardLogProb);
597  }
598 
599  return curLogScale;
600 }
601 
605 template<typename Distribution>
607  const arma::vec& emissionLogProb,
608  double& logLikelihood,
609  arma::vec& forwardLogProb) const
610 {
611  bool isStartOfSeq = forwardLogProb.empty();
612  double curLogScale = EmissionLogScaleFactor(emissionLogProb, forwardLogProb);
613  logLikelihood = isStartOfSeq ? curLogScale : curLogScale + logLikelihood;
614  return logLikelihood;
615 }
616 
622 template<typename Distribution>
623 double HMM<Distribution>::LogScaleFactor(const arma::vec &data,
624  arma::vec& forwardLogProb) const
625 {
626  arma::vec emissionLogProb(logTransition.n_rows);
627 
628  for (size_t state = 0; state < logTransition.n_rows; state++)
629  {
630  emissionLogProb(state) = emission[state].LogProbability(data);
631  }
632 
633  return EmissionLogScaleFactor(emissionLogProb, forwardLogProb);
634 }
635 
639 template<typename Distribution>
640 double HMM<Distribution>::LogLikelihood(const arma::vec& data,
641  double& logLikelihood,
642  arma::vec& forwardLogProb) const
643 {
644  bool isStartOfSeq = forwardLogProb.empty();
645  double curLogScale = LogScaleFactor(data, forwardLogProb);
646  logLikelihood = isStartOfSeq ? curLogScale : curLogScale + logLikelihood;
647  return logLikelihood;
648 }
649 
653 template<typename Distribution>
654 void HMM<Distribution>::Filter(const arma::mat& dataSeq,
655  arma::mat& filterSeq,
656  size_t ahead) const
657 {
658  // First run the forward algorithm.
659  arma::mat forwardLogProb;
660  arma::vec logScales;
661  // This is needed here.
662  arma::mat logProbs(dataSeq.n_cols, logTransition.n_rows);
663 
664  // Save the values of log-probability to logProbs.
665  for (size_t i = 0; i < logTransition.n_rows; i++)
666  {
667  // Define alias of desired column.
668  arma::vec alias(logProbs.colptr(i), logProbs.n_rows, false, true);
669  // Use advanced constructor for using logProbs directly.
670  emission[i].LogProbability(dataSeq, alias);
671  }
672 
673  Forward(dataSeq, logScales, forwardLogProb, logProbs);
674 
675  // Propagate state ahead.
676  if (ahead != 0)
677  forwardLogProb += ahead * logTransition;
678 
679  arma::mat forwardProb = exp(forwardLogProb);
680 
681  // Compute expected emissions.
682  // Will not work for distributions without a Mean() function.
683  filterSeq.zeros(dimensionality, dataSeq.n_cols);
684  for (size_t i = 0; i < emission.size(); i++)
685  filterSeq += emission[i].Mean() * forwardProb.row(i);
686 }
687 
691 template<typename Distribution>
692 void HMM<Distribution>::Smooth(const arma::mat& dataSeq,
693  arma::mat& smoothSeq) const
694 {
695  // First run the forward algorithm.
696  arma::mat stateLogProb;
697  arma::mat forwardLogProb;
698  arma::mat backwardLogProb;
699  arma::mat logScales;
700  LogEstimate(dataSeq, stateLogProb, forwardLogProb, backwardLogProb,
701  logScales);
702 
703  // Compute expected emissions.
704  // Will not work for distributions without a Mean() function.
705  smoothSeq.zeros(dimensionality, dataSeq.n_cols);
706  for (size_t i = 0; i < emission.size(); i++)
707  smoothSeq += emission[i].Mean() * exp(stateLogProb.row(i));
708 }
709 
713 template<typename Distribution>
714 arma::vec HMM<Distribution>::ForwardAtT0(const arma::vec& emissionLogProb,
715  double& logScales) const
716 {
717  // Our goal is to calculate the forward probabilities:
718  // P(X_k | o_{1:k}) for all possible states X_k, for each time point k.
719  ConvertToLogSpace();
720 
721  // The first entry in the forward algorithm uses the initial state
722  // probabilities. Note that MATLAB assumes that the starting state (at
723  // t = -1) is state 0; this is not our assumption here. To force that
724  // behavior, you could append a single starting state to every single data
725  // sequence and that should produce results in line with MATLAB.
726  arma::vec forwardLogProb = logInitial + emissionLogProb;
727 
728  // Normalize probability.
729  logScales = math::AccuLog(forwardLogProb);
730  if (std::isfinite(logScales))
731  forwardLogProb -= logScales;
732 
733  return forwardLogProb;
734 }
735 
739 template<typename Distribution>
740 arma::vec HMM<Distribution>::ForwardAtTn(const arma::vec& emissionLogProb,
741  double& logScales,
742  const arma::vec& prevForwardLogProb)
743  const
744 {
745  // Our goal is to calculate the forward probabilities:
746  // P(X_k | o_{1:k}) for all possible states X_k, for each time point k.
747 
748  // The forward probability of state j at time t is the sum over all states of
749  // the probability of the previous state transitioning to the current state
750  // and emitting the given observation. To do this computation in log-space,
751  // we can use LogSumExp().
752  arma::vec forwardLogProb;
753  arma::mat tmp = logTransition + repmat(prevForwardLogProb.t(),
754  logTransition.n_rows, 1);
755  math::LogSumExp(tmp, forwardLogProb);
756  forwardLogProb += emissionLogProb;
757 
758  // Normalize probability.
759  logScales = math::AccuLog(forwardLogProb);
760  if (std::isfinite(logScales))
761  forwardLogProb -= logScales;
762 
763  return forwardLogProb;
764 }
765 
769 template<typename Distribution>
770 void HMM<Distribution>::Forward(const arma::mat& dataSeq,
771  arma::vec& logScales,
772  arma::mat& forwardLogProb,
773  arma::mat& logProbs) const
774 {
775  // Our goal is to calculate the forward probabilities:
776  // P(X_k | o_{1:k}) for all possible states X_k, for each time point k.
777  forwardLogProb.resize(logTransition.n_rows, dataSeq.n_cols);
778  forwardLogProb.fill(-std::numeric_limits<double>::infinity());
779  logScales.resize(dataSeq.n_cols);
780  logScales.fill(-std::numeric_limits<double>::infinity());
781 
782  // The first entry in the forward algorithm uses the initial state
783  // probabilities. Note that MATLAB assumes that the starting state (at
784  // t = -1) is state 0; this is not our assumption here. To force that
785  // behavior, you could append a single starting state to every single data
786  // sequence and that should produce results in line with MATLAB.
787 
788  forwardLogProb.col(0) = ForwardAtT0(logProbs.row(0).t(), logScales(0));
789 
790  // Now compute the probabilities for each successive observation.
791  for (size_t t = 1; t < dataSeq.n_cols; t++)
792  {
793  forwardLogProb.col(t) = ForwardAtTn(logProbs.row(t).t(), logScales(t),
794  forwardLogProb.col(t - 1));
795  }
796 }
797 
798 template<typename Distribution>
799 void HMM<Distribution>::Backward(const arma::mat& dataSeq,
800  const arma::vec& logScales,
801  arma::mat& backwardLogProb,
802  arma::mat& logProbs) const
803 {
804  // Our goal is to calculate the backward probabilities:
805  // P(X_k | o_{k + 1:T}) for all possible states X_k, for each time point k.
806  backwardLogProb.resize(logTransition.n_rows, dataSeq.n_cols);
807  backwardLogProb.fill(-std::numeric_limits<double>::infinity());
808 
809  // The last element probability is 1.
810  backwardLogProb.col(dataSeq.n_cols - 1).fill(0);
811 
812  // Now step backwards through all other observations.
813  for (size_t t = dataSeq.n_cols - 2; t + 1 > 0; t--)
814  {
815  // The backward probability of state j at time t is the sum over all
816  // states of the probability of the next state having been a transition
817  // from the current state multiplied by the probability of each of those
818  // states emitting the given observation. To compute this in log-space, we
819  // can use LogSumExpT().
820  const arma::mat tmp = logTransition +
821  repmat(backwardLogProb.col(t + 1), 1, logTransition.n_cols) +
822  repmat(logProbs.row(t + 1).t(), 1, logTransition.n_cols);
823  arma::vec alias = backwardLogProb.unsafe_col(t);
824  math::LogSumExpT<arma::mat, true>(tmp, alias);
825 
826  // Normalize by the weights from the forward algorithm.
827  if (std::isfinite(logScales[t + 1]))
828  backwardLogProb.col(t) -= logScales[t + 1];
829  }
830 }
831 
836 template<typename Distribution>
838 {
839  if (recalculateInitial)
840  {
841  logInitial = log(initialProxy);
842  recalculateInitial = false;
843  }
844 
845  if (recalculateTransition)
846  {
847  logTransition = log(transitionProxy);
848  recalculateTransition = false;
849  }
850 }
851 
853 template<typename Distribution>
854 template<typename Archive>
855 void HMM<Distribution>::load(Archive& ar, const uint32_t /* version */)
856 {
857  arma::mat transition;
858  arma::vec initial;
859  ar(CEREAL_NVP(dimensionality));
860  ar(CEREAL_NVP(tolerance));
861  ar(CEREAL_NVP(transition));
862  ar(CEREAL_NVP(initial));
863 
864  // Now serialize each emission. If we are loading, we must resize the vector
865  // of emissions correctly.
866  emission.resize(transition.n_rows);
867  // Load the emissions; generate the correct name for each one.
868  ar(CEREAL_NVP(emission));
869 
870  logTransition = log(transition);
871  logInitial = log(initial);
872  initialProxy = std::move(initial);
873  transitionProxy = std::move(transition);
874 }
875 
877 template<typename Distribution>
878 template<typename Archive>
879 void HMM<Distribution>::save(Archive& ar,
880  const uint32_t /* version */) const
881 {
882  arma::mat transition = exp(logTransition);
883  arma::vec initial = exp(logInitial);
884  ar(CEREAL_NVP(dimensionality));
885  ar(CEREAL_NVP(tolerance));
886  ar(CEREAL_NVP(transition));
887  ar(CEREAL_NVP(initial));
888  ar(CEREAL_NVP(emission));
889 }
890 
891 } // namespace hmm
892 } // namespace mlpack
893 
894 #endif
void LogSumExp(const T &x, arma::Col< typename T::elem_type > &y)
Compute the sum of exponentials of each element in each column, then compute the log of that...
Definition: log_add_impl.hpp:78
Linear algebra utility functions, generally performed on matrices or vectors.
Definition: cv.hpp:1
Definition: hmm_train_main.cpp:300
A class that represents a Hidden Markov Model with an arbitrary type of emission distribution.
Definition: hmm.hpp:85
T::elem_type AccuLog(const T &x)
Log-sum a vector of log values.
Definition: log_add_impl.hpp:63
double Random()
Generates a uniform random number between 0 and 1.
Definition: random.hpp:83
HMM(const size_t states=0, const Distribution emissions=Distribution(), const double tolerance=1e-5)
Create the Hidden Markov Model with the given number of hidden states and the given default distribut...
Definition: hmm_impl.hpp:29