• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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