From 4633c2a6c0135c4fc41bb916a601543fd77e0ad8 Mon Sep 17 00:00:00 2001 From: Hendrik Date: Wed, 7 Apr 2021 10:24:52 +0100 Subject: [PATCH 1/4] adding one step ahead prediction for hmm --- .../distributions/hidden_markov_model.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tensorflow_probability/python/distributions/hidden_markov_model.py b/tensorflow_probability/python/distributions/hidden_markov_model.py index 4426801375..c244d240b6 100644 --- a/tensorflow_probability/python/distributions/hidden_markov_model.py +++ b/tensorflow_probability/python/distributions/hidden_markov_model.py @@ -1181,6 +1181,39 @@ def _reduce_one_step(): return ps.cond(self.num_steps > 1, _reduce_multiple_steps, _reduce_one_step) + def single_step_prediction(self, observation, prediction_distribution=None, initialisation_step=True): + """ + Function to run single prediction step based on incoming observation data point and the current prediction + distribution of the hmm model. + If initialisation_step is True, then no prediction distribution is required initially and the initial prediction + distribution is derived from the priors of the hmm (the initial_distribution over the states). In a forecasting + model that runs on live data, the first step would require initialisation while subsequent steps would use the + previous step's prediction distribution as input. + The prediction distribution is updated and returned from this function. + """ + observation = tf.convert_to_tensor(observation, name='observations') + + if not isinstance(initialisation_step, bool): + raise TypeError('initialisation_step must be of type bool, but saw: %s' % initialisation_step) + + if(initialisation_step): + num = self.initial_distribution.log_prob(range(self.num_states_static)) \ + + self.observation_distribution.log_prob(observation) + else: + if not isinstance(prediction_distribution, distribution.Distribution): + raise TypeError('If initialisation_step is False, then current prediction distribution must be provided.' + 'prediction_distribution must be a Distribution instance, but saw: %s' % + prediction_distribution) + num = tf.math.log(prediction_distribution) \ + + self.observation_distribution.log_prob(observation) + + filtering_distribution = tf.exp(num - tf.reduce_logsumexp(num)) + prediction_distribution = tf.tensordot(self.transition_distribution.probs_parameter(), + filtering_distribution, axes=1) + observation_prediction = tf.tensordot(prediction_distribution, self.observation_distribution.mean(), axes=1) + + return prediction_distribution, observation_prediction + # pylint: disable=protected-access def _default_event_space_bijector(self): return (self._observation_distribution. From 86bd350ae6724378426c9b63b6b0a3d4ddf57d0e Mon Sep 17 00:00:00 2001 From: Hendrik Date: Wed, 7 Apr 2021 12:26:09 +0100 Subject: [PATCH 2/4] return categorical distribution --- .../python/distributions/hidden_markov_model.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tensorflow_probability/python/distributions/hidden_markov_model.py b/tensorflow_probability/python/distributions/hidden_markov_model.py index c244d240b6..58db59120b 100644 --- a/tensorflow_probability/python/distributions/hidden_markov_model.py +++ b/tensorflow_probability/python/distributions/hidden_markov_model.py @@ -1196,7 +1196,7 @@ def single_step_prediction(self, observation, prediction_distribution=None, init if not isinstance(initialisation_step, bool): raise TypeError('initialisation_step must be of type bool, but saw: %s' % initialisation_step) - if(initialisation_step): + if (initialisation_step): num = self.initial_distribution.log_prob(range(self.num_states_static)) \ + self.observation_distribution.log_prob(observation) else: @@ -1204,13 +1204,13 @@ def single_step_prediction(self, observation, prediction_distribution=None, init raise TypeError('If initialisation_step is False, then current prediction distribution must be provided.' 'prediction_distribution must be a Distribution instance, but saw: %s' % prediction_distribution) - num = tf.math.log(prediction_distribution) \ + num = tf.math.log(prediction_distribution.probs) \ + self.observation_distribution.log_prob(observation) filtering_distribution = tf.exp(num - tf.reduce_logsumexp(num)) - prediction_distribution = tf.tensordot(self.transition_distribution.probs_parameter(), - filtering_distribution, axes=1) - observation_prediction = tf.tensordot(prediction_distribution, self.observation_distribution.mean(), axes=1) + prediction_distribution = categorical.Categorical(probs=tf.tensordot(self.transition_distribution.probs_parameter(), + filtering_distribution, axes=1)) + observation_prediction = tf.tensordot(prediction_distribution.probs, self.observation_distribution.mean(), axes=1) return prediction_distribution, observation_prediction From b7adce313d10f5c5e1309ae85186f29df0d3634e Mon Sep 17 00:00:00 2001 From: Hendrik Date: Wed, 7 Apr 2021 12:59:24 +0100 Subject: [PATCH 3/4] hmm.single_step_prediction: updated comment section --- .../python/distributions/hidden_markov_model.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tensorflow_probability/python/distributions/hidden_markov_model.py b/tensorflow_probability/python/distributions/hidden_markov_model.py index 58db59120b..c19fd4475f 100644 --- a/tensorflow_probability/python/distributions/hidden_markov_model.py +++ b/tensorflow_probability/python/distributions/hidden_markov_model.py @@ -1189,9 +1189,10 @@ def single_step_prediction(self, observation, prediction_distribution=None, init distribution is derived from the priors of the hmm (the initial_distribution over the states). In a forecasting model that runs on live data, the first step would require initialisation while subsequent steps would use the previous step's prediction distribution as input. - The prediction distribution is updated and returned from this function. + The prediction distribution (categorical distribution with probability of being in each of the hidden states) is + updated and returned from this function and can be used as input for the subsequent prediction step. """ - observation = tf.convert_to_tensor(observation, name='observations') + observation = tf.convert_to_tensor(observation, name='observation') if not isinstance(initialisation_step, bool): raise TypeError('initialisation_step must be of type bool, but saw: %s' % initialisation_step) From 6cb7cdad4efcf906f7161a6651bafd51f7cdb6be Mon Sep 17 00:00:00 2001 From: Hendrik Date: Tue, 11 May 2021 21:05:40 +0100 Subject: [PATCH 4/4] hmm.single_step_prediction: small refactoring --- .../distributions/hidden_markov_model.py | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/tensorflow_probability/python/distributions/hidden_markov_model.py b/tensorflow_probability/python/distributions/hidden_markov_model.py index c19fd4475f..bc30c5a8d8 100644 --- a/tensorflow_probability/python/distributions/hidden_markov_model.py +++ b/tensorflow_probability/python/distributions/hidden_markov_model.py @@ -1181,36 +1181,32 @@ def _reduce_one_step(): return ps.cond(self.num_steps > 1, _reduce_multiple_steps, _reduce_one_step) - def single_step_prediction(self, observation, prediction_distribution=None, initialisation_step=True): + def single_step_prediction(self, observation, prediction_distribution=None): """ Function to run single prediction step based on incoming observation data point and the current prediction distribution of the hmm model. - If initialisation_step is True, then no prediction distribution is required initially and the initial prediction - distribution is derived from the priors of the hmm (the initial_distribution over the states). In a forecasting + If no prediction_distribution is given (typically in the initial step), then the current distribution is derived + from the priors of the hmm (the initial_distribution over the states). In a forecasting model that runs on live data, the first step would require initialisation while subsequent steps would use the previous step's prediction distribution as input. - The prediction distribution (categorical distribution with probability of being in each of the hidden states) is - updated and returned from this function and can be used as input for the subsequent prediction step. + The prediction distribution is updated and returned from this function. """ - observation = tf.convert_to_tensor(observation, name='observation') + observation = tf.convert_to_tensor(observation, name='observations') - if not isinstance(initialisation_step, bool): - raise TypeError('initialisation_step must be of type bool, but saw: %s' % initialisation_step) - - if (initialisation_step): + if prediction_distribution is None: num = self.initial_distribution.log_prob(range(self.num_states_static)) \ + self.observation_distribution.log_prob(observation) else: if not isinstance(prediction_distribution, distribution.Distribution): - raise TypeError('If initialisation_step is False, then current prediction distribution must be provided.' - 'prediction_distribution must be a Distribution instance, but saw: %s' % - prediction_distribution) + raise TypeError('If prediction_distribution is provided, it must be a Distribution object, ' + 'but saw: %s' % prediction_distribution) num = tf.math.log(prediction_distribution.probs) \ + self.observation_distribution.log_prob(observation) filtering_distribution = tf.exp(num - tf.reduce_logsumexp(num)) - prediction_distribution = categorical.Categorical(probs=tf.tensordot(self.transition_distribution.probs_parameter(), - filtering_distribution, axes=1)) + prediction_distribution = tf.tensordot(self.transition_distribution.probs_parameter(), + filtering_distribution, axes=1) + prediction_distribution = categorical.Categorical(probs=prediction_distribution) observation_prediction = tf.tensordot(prediction_distribution.probs, self.observation_distribution.mean(), axes=1) return prediction_distribution, observation_prediction