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 17from __future__ import absolute_import 18from __future__ import division 19from __future__ import print_function 20 21from tensorflow.contrib.timeseries.python.timeseries.state_space_models import state_space_model 22 23from tensorflow.python.framework import constant_op 24from tensorflow.python.framework import dtypes 25from tensorflow.python.framework import ops 26from tensorflow.python.ops import array_ops 27from tensorflow.python.ops import init_ops 28from tensorflow.python.ops import linalg_ops 29from tensorflow.python.ops import math_ops 30from tensorflow.python.ops import variable_scope 31 32 33class 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