Home | History | Annotate | Download | only in state_space_models
      1 # Copyright 2017 The TensorFlow Authors. All Rights Reserved.
      2 #
      3 # Licensed under the Apache License, Version 2.0 (the "License");
      4 # you may not use this file except in compliance with the License.
      5 # You may obtain a copy of the License at
      6 #
      7 #     http://www.apache.org/licenses/LICENSE-2.0
      8 #
      9 # Unless required by applicable law or agreed to in writing, software
     10 # distributed under the License is distributed on an "AS IS" BASIS,
     11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 # See the License for the specific language governing permissions and
     13 # limitations under the License.
     14 # ==============================================================================
     15 """Implements a state space model with level and local linear trends."""
     16 
     17 from __future__ import absolute_import
     18 from __future__ import division
     19 from __future__ import print_function
     20 
     21 from tensorflow.contrib.timeseries.python.timeseries.state_space_models import state_space_model
     22 
     23 from tensorflow.python.framework import constant_op
     24 from tensorflow.python.framework import dtypes
     25 from tensorflow.python.framework import ops
     26 from tensorflow.python.ops import array_ops
     27 from tensorflow.python.ops import init_ops
     28 from tensorflow.python.ops import linalg_ops
     29 from tensorflow.python.ops import math_ops
     30 from tensorflow.python.ops import variable_scope
     31 
     32 
     33 class AdderStateSpaceModel(state_space_model.StateSpaceModel):
     34   """A state space model component with level and slope.
     35 
     36   At each timestep, level <- level + slope. Level is observed, slope is not.
     37   """
     38 
     39   def __init__(
     40       self,
     41       use_level_noise=True,
     42       configuration=state_space_model.StateSpaceModelConfiguration()):
     43     """Configure the model.
     44 
     45     Args:
     46       use_level_noise: Whether to model the time series as having level noise.
     47       configuration: A StateSpaceModelConfiguration object.
     48     """
     49     self.use_level_noise = use_level_noise
     50     super(AdderStateSpaceModel, self).__init__(
     51         configuration=configuration)
     52 
     53   def get_prior_mean(self):
     54     """If un-chunked data is available, set initial level to the first value."""
     55     with variable_scope.variable_scope(self._variable_scope):
     56       if self._input_statistics is not None:
     57         # TODO(allenl): Better support for multivariate series here.
     58         initial_value = array_ops.stack([
     59             math_ops.reduce_mean(
     60                 self._scale_data(
     61                     self._input_statistics.series_start_moments.mean)),
     62             0.
     63         ])
     64         return initial_value + variable_scope.get_variable(
     65             name="prior_state_mean",
     66             shape=initial_value.get_shape(),
     67             initializer=init_ops.zeros_initializer(),
     68             dtype=self.dtype,
     69             trainable=self._configuration.trainable_start_state)
     70       else:
     71         return super(AdderStateSpaceModel, self).get_prior_mean()
     72 
     73   def transition_to_powers(self, powers):
     74     """Computes powers of the adder transition matrix efficiently.
     75 
     76     Args:
     77       powers: An integer Tensor, shape [...], with powers to raise the
     78         transition matrix to.
     79     Returns:
     80       A floating point Tensor with shape [..., 2, 2] containing:
     81         transition^power = [[1., power],
     82                             [0., 1.]]
     83     """
     84     paddings = array_ops.concat(
     85         [
     86             array_ops.zeros([array_ops.rank(powers), 2], dtype=dtypes.int32),
     87             [(0, 1), (1, 0)]
     88         ],
     89         axis=0)
     90     powers_padded = array_ops.pad(powers[..., None, None], paddings=paddings)
     91     identity_matrices = linalg_ops.eye(
     92         num_rows=2, batch_shape=array_ops.shape(powers), dtype=self.dtype)
     93     return identity_matrices + math_ops.cast(powers_padded, self.dtype)
     94 
     95   def transition_power_noise_accumulator(self, num_steps):
     96     """Computes power sums in closed form."""
     97     def _pack_and_reshape(*values):
     98       return array_ops.reshape(
     99           array_ops.stack(axis=1, values=values),
    100           array_ops.concat(values=[array_ops.shape(num_steps), [2, 2]], axis=0))
    101 
    102     num_steps = math_ops.cast(num_steps, self.dtype)
    103     noise_transitions = num_steps - 1
    104     noise_transform = ops.convert_to_tensor(self.get_noise_transform(),
    105                                             self.dtype)
    106     noise_covariance_transformed = math_ops.matmul(
    107         math_ops.matmul(noise_transform,
    108                         self.state_transition_noise_covariance),
    109         noise_transform,
    110         adjoint_b=True)
    111     # Un-packing the transformed noise as:
    112     # [[a b]
    113     #  [c d]]
    114     a, b, c, d = array_ops.unstack(
    115         array_ops.reshape(noise_covariance_transformed, [-1, 4]), axis=1)
    116     sum_of_first_n = noise_transitions * (noise_transitions + 1) / 2
    117     sum_of_first_n_squares = sum_of_first_n * (2 * noise_transitions + 1) / 3
    118     return _pack_and_reshape(
    119         num_steps * a + sum_of_first_n * (b + c) + sum_of_first_n_squares * d,
    120         num_steps * b + sum_of_first_n * d,
    121         num_steps * c + sum_of_first_n * d,
    122         num_steps * d)
    123 
    124   def get_state_transition(self):
    125     return [[1., 1.],  # Add slope to level
    126             [0., 1.]]  # Maintain slope
    127 
    128   def get_noise_transform(self):
    129     if self.use_level_noise:
    130       return [[1., 0.],
    131               [0., 1.]]
    132     else:
    133       return [[0.],
    134               [1.]]
    135 
    136   def get_observation_model(self, times):
    137     """Observe level but not slope.
    138 
    139     See StateSpaceModel.get_observation_model.
    140 
    141     Args:
    142       times: Unused. See the parent class for details.
    143     Returns:
    144       A static, univariate observation model for later broadcasting.
    145     """
    146     del times  # Does not rely on times. Uses broadcasting from the parent.
    147     return constant_op.constant([1., 0.], dtype=self.dtype)
    148