コード例 #1
0
def _maybe_validate_target_accept_prob(target_accept_prob, validate_args):
  """Validates that target_accept_prob is in (0, 1)."""
  if not validate_args:
    return target_accept_prob
  with tf.control_dependencies([
      tf.assert_greater(
          target_accept_prob, 0., message='`target_accept_prob` must be > 0.'
      ),
      tf.assert_less(
          target_accept_prob,
          tf.ones_like(target_accept_prob),
          message='`target_accept_prob` must be < 1.')]):
    return tf.identity(target_accept_prob)
コード例 #2
0
def _maybe_validate_target_accept_prob(target_accept_prob, validate_args):
    """Validates that target_accept_prob is in (0, 1)."""
    if not validate_args:
        return target_accept_prob
    assertions = [
        tf.assert_greater(target_accept_prob,
                          tf.zeros([], dtype=target_accept_prob.dtype),
                          message='`target_accept_prob` must be > 0.'),
        tf.assert_less(target_accept_prob,
                       tf.ones([], dtype=target_accept_prob.dtype),
                       message='`target_accept_prob` must be < 1.')
    ]
    with tf.control_dependencies(assertions):
        return tf.identity(target_accept_prob)
コード例 #3
0
    def _parameter_control_dependencies(self, is_init):
        assertions = []

        # For `logits` and `probs`, we only want to have an assertion on what the
        # user actually passed. For now, we access the underlying categorical's
        # _logits and _probs directly. After the 2019-10-01 deprecation, it would
        # also work to use .logits() and .probs().
        logits = self._categorical._logits
        probs = self._categorical._probs
        outcomes = self._outcomes
        validate_args = self._validate_args

        # Build all shape and dtype checks during the `is_init` call.
        if is_init:

            def validate_equal_last_dim(tensor_a, tensor_b, message):
                event_size_a = tf.compat.dimension_value(tensor_a.shape[-1])
                event_size_b = tf.compat.dimension_value(tensor_b.shape[-1])
                if event_size_a is not None and event_size_b is not None:
                    if event_size_a != event_size_b:
                        raise ValueError(message)
                elif validate_args:
                    return assert_util.assert_equal(tf.shape(tensor_a)[-1],
                                                    tf.shape(tensor_b)[-1],
                                                    message=message)

            message = 'Size of outcomes must be greater than 0.'
            if tensorshape_util.num_elements(outcomes.shape) is not None:
                if tensorshape_util.num_elements(outcomes.shape) == 0:
                    raise ValueError(message)
            elif validate_args:
                assertions.append(
                    tf.assert_greater(tf.size(outcomes), 0, message=message))

            if logits is not None:
                maybe_assert = validate_equal_last_dim(
                    outcomes,
                    # pylint: disable=protected-access
                    self._categorical._logits,
                    # pylint: enable=protected-access
                    message=
                    'Last dimension of outcomes and logits must be equal size.'
                )
                if maybe_assert:
                    assertions.append(maybe_assert)

            if probs is not None:
                maybe_assert = validate_equal_last_dim(
                    outcomes,
                    probs,
                    message=
                    'Last dimension of outcomes and probs must be equal size.')
                if maybe_assert:
                    assertions.append(maybe_assert)

            message = 'Rank of outcomes must be 1.'
            ndims = tensorshape_util.rank(outcomes.shape)
            if ndims is not None:
                if ndims != 1:
                    raise ValueError(message)
            elif validate_args:
                assertions.append(
                    assert_util.assert_rank(outcomes, 1, message=message))

        if not validate_args:
            assert not assertions  # Should never happen.
            return []

        if is_init != tensor_util.is_ref(outcomes):
            assertions.append(
                assert_util.assert_equal(
                    tf.math.is_strictly_increasing(outcomes),
                    True,
                    message='outcomes is not strictly increasing.'))

        return assertions
コード例 #4
0
def sample(dim: int,
           drift_fn: Callable[..., types.RealTensor],
           volatility_fn: Callable[..., types.RealTensor],
           times: types.RealTensor,
           time_step: Optional[types.RealTensor] = None,
           num_time_steps: Optional[types.IntTensor] = None,
           num_samples: types.IntTensor = 1,
           initial_state: Optional[types.RealTensor] = None,
           random_type: Optional[random.RandomType] = None,
           seed: Optional[types.IntTensor] = None,
           swap_memory: bool = True,
           skip: types.IntTensor = 0,
           precompute_normal_draws: bool = True,
           times_grid: Optional[types.RealTensor] = None,
           normal_draws: Optional[types.RealTensor] = None,
           watch_params: Optional[List[types.RealTensor]] = None,
           validate_args: bool = False,
           tolerance: Optional[types.RealTensor] = None,
           dtype: Optional[tf.DType] = None,
           name: Optional[str] = None) -> types.RealTensor:
    """Returns a sample paths from the process using Euler method.

  For an Ito process,

  ```
    dX = a(t, X_t) dt + b(t, X_t) dW_t
    X(t=0) = x0
  ```
  with given drift `a` and volatility `b` functions Euler method generates a
  sequence {X_n} as

  ```
  X_{n+1} = X_n + a(t_n, X_n) dt + b(t_n, X_n) (N(0, t_{n+1}) - N(0, t_n)),
  X_0 = x0
  ```
  where `dt = t_{n+1} - t_n` and `N` is a sample from the Normal distribution.
  See [1] for details.

  #### Example
  Sampling from 2-dimensional Ito process of the form:

  ```none
  dX_1 = mu_1 * sqrt(t) dt + s11 * dW_1 + s12 * dW_2
  dX_2 = mu_2 * sqrt(t) dt + s21 * dW_1 + s22 * dW_2
  ```

  ```python
  import tensorflow as tf
  import tf_quant_finance as tff

  import numpy as np

  mu = np.array([0.2, 0.7])
  s = np.array([[0.3, 0.1], [0.1, 0.3]])
  num_samples = 10000
  dim = 2
  dtype = tf.float64

  # Define drift and volatility functions
  def drift_fn(t, x):
    return mu * tf.sqrt(t) * tf.ones([num_samples, dim], dtype=dtype)

  def vol_fn(t, x):
    return s * tf.ones([num_samples, dim, dim], dtype=dtype)

  # Set starting location
  x0 = np.array([0.1, -1.1])
  # Sample `num_samples` paths at specified `times` using Euler scheme.
  times = [0.1, 1.0, 2.0]
  paths = tff.models.euler_sampling.sample(
            dim=dim,
            drift_fn=drift_fn,
            volatility_fn=vol_fn,
            times=times,
            num_samples=num_samples,
            initial_state=x0,
            time_step=0.01,
            seed=42,
            dtype=dtype)
  # Expected: paths.shape = [10000, 3, 2]
  ```

  #### References
  [1]: Wikipedia. Euler-Maruyama method:
  https://en.wikipedia.org/wiki/Euler-Maruyama_method

  Args:
    dim: Python int greater than or equal to 1. The dimension of the Ito
      Process.
    drift_fn: A Python callable to compute the drift of the process. The
      callable should accept two real `Tensor` arguments of the same dtype.
      The first argument is the scalar time t, the second argument is the
      value of Ito process X - tensor of shape
      `batch_shape + [num_samples, dim]`. `batch_shape` is the shape of the
      independent stochastic processes being modelled and is inferred from the
      initial state `x0`.
      The result is value of drift a(t, X). The return value of the callable
      is a real `Tensor` of the same dtype as the input arguments and of shape
      `batch_shape + [num_samples, dim]`.
    volatility_fn: A Python callable to compute the volatility of the process.
      The callable should accept two real `Tensor` arguments of the same dtype
      and shape `times_shape`. The first argument is the scalar time t, the
      second argument is the value of Ito process X - tensor of shape
      `batch_shape + [num_samples, dim]`. The result is value of drift b(t, X).
      The return value of the callable is a real `Tensor` of the same dtype as
      the input arguments and of shape `batch_shape + [num_samples, dim, dim]`.
    times: Rank 1 `Tensor` of increasing positive real values. The times at
      which the path points are to be evaluated.
    time_step: An optional scalar real `Tensor` - maximal distance between
      points in grid in Euler schema.
      Either this or `num_time_steps` should be supplied.
      Default value: `None`.
    num_time_steps: An optional Scalar integer `Tensor` - a total number of time
      steps performed by the algorithm. The maximal distance betwen points in
      grid is bounded by `times[-1] / (num_time_steps - times.shape[0])`.
      Either this or `time_step` should be supplied.
      Default value: `None`.
    num_samples: Positive scalar `int`. The number of paths to draw.
      Default value: 1.
    initial_state: `Tensor` of shape broadcastable with
      `batch_shape + [num_samples, dim]`. The initial state of the process.
      `batch_shape` represents the shape of the independent batches of the
      stochastic process. Note that `batch_shape` is inferred from
      the `initial_state` and hence when sampling is requested for a batch of
      stochastic processes, the shape of `initial_state` should be at least
      `batch_shape + [1, 1]`.
      Default value: None which maps to a zero initial state.
    random_type: Enum value of `RandomType`. The type of (quasi)-random
      number generator to use to generate the paths.
      Default value: None which maps to the standard pseudo-random numbers.
    seed: Seed for the random number generator. The seed is
      only relevant if `random_type` is one of
      `[STATELESS, PSEUDO, HALTON_RANDOMIZED, PSEUDO_ANTITHETIC,
        STATELESS_ANTITHETIC]`. For `PSEUDO`, `PSEUDO_ANTITHETIC` and
      `HALTON_RANDOMIZED` the seed should be a Python integer. For
      `STATELESS` and  `STATELESS_ANTITHETIC `must be supplied as an integer
      `Tensor` of shape `[2]`.
      Default value: `None` which means no seed is set.
    swap_memory: A Python bool. Whether GPU-CPU memory swap is enabled for this
      op. See an equivalent flag in `tf.while_loop` documentation for more
      details. Useful when computing a gradient of the op since `tf.while_loop`
      is used to propagate stochastic process in time.
      Default value: True.
    skip: `int32` 0-d `Tensor`. The number of initial points of the Sobol or
      Halton sequence to skip. Used only when `random_type` is 'SOBOL',
      'HALTON', or 'HALTON_RANDOMIZED', otherwise ignored.
      Default value: `0`.
    precompute_normal_draws: Python bool. Indicates whether the noise increments
      `N(0, t_{n+1}) - N(0, t_n)` are precomputed. For `HALTON` and `SOBOL`
      random types the increments are always precomputed. While the resulting
      graph consumes more memory, the performance gains might be significant.
      Default value: `True`.
    times_grid: An optional rank 1 `Tensor` representing time discretization
      grid. If `times` are not on the grid, then the nearest points from the
      grid are used. When supplied, `num_time_steps` and `time_step` are
      ignored.
      Default value: `None`, which means that times grid is computed using
      `time_step` and `num_time_steps`.
    normal_draws: A `Tensor` of shape broadcastable with
      `batch_shape + [num_samples, num_time_points, dim]` and the same
      `dtype` as `times`. Represents random normal draws to compute increments
      `N(0, t_{n+1}) - N(0, t_n)`. When supplied, `num_samples` argument is
      ignored and the first dimensions of `normal_draws` is used instead.
      Default value: `None` which means that the draws are generated by the
      algorithm. By default normal_draws for each model in the batch are
      independent.
    watch_params: An optional list of zero-dimensional `Tensor`s of the same
      `dtype` as `initial_state`. If provided, specifies `Tensor`s with respect
      to which the differentiation of the sampling function will happen.
      A more efficient algorithm is used when `watch_params` are specified.
      Note the the function becomes differentiable onlhy wrt to these `Tensor`s
      and the `initial_state`. The gradient wrt any other `Tensor` is set to be
      zero.
    validate_args: Python `bool`. When `True` performs multiple checks:
      * That `times`  are increasing with the minimum increments of the
        specified tolerance.
      * If `normal_draws` are supplied, checks that `normal_draws.shape[1]` is
      equal to `num_time_steps` that is either supplied as an argument or
      computed from `time_step`.
      When `False` invalid dimension may silently render incorrect outputs.
      Default value: `False`.
    tolerance: A non-negative scalar `Tensor` specifying the minimum tolerance
      for discernible times on the time grid. Times that are closer than the
      tolerance are perceived to be the same.
      Default value: `None` which maps to `1-e6` if the for single precision
        `dtype` and `1e-10` for double precision `dtype`.
    dtype: `tf.Dtype`. If supplied the dtype for the input and output `Tensor`s.
      Default value: None which means that the dtype implied by `times` is
      used.
    name: Python string. The name to give this op.
      Default value: `None` which maps to `euler_sample`.

  Returns:
   A real `Tensor` of shape batch_shape_process + [num_samples, k, n] where `k`
     is the size of the `times`, `n` is the dimension of the process.

  Raises:
    ValueError:
      (a) When `times_grid` is not supplied, and neither `num_time_steps` nor
        `time_step` are supplied or if both are supplied.
      (b) If `normal_draws` is supplied and `dim` is mismatched.
    tf.errors.InvalidArgumentError: If `normal_draws` is supplied and
      `num_time_steps` is mismatched.
  """
    name = name or 'euler_sample'
    with tf.name_scope(name):
        times = tf.convert_to_tensor(times, dtype=dtype)
        if dtype is None:
            dtype = times.dtype
        asserts = []
        if tolerance is None:
            tolerance = 1e-10 if dtype == tf.float64 else 1e-6
        tolerance = tf.convert_to_tensor(tolerance, dtype=dtype)
        if validate_args:
            asserts.append(
                tf.assert_greater(
                    times[1:],
                    times[:-1] + tolerance,
                    message='`times` increments should be greater '
                    'than tolerance {0}'.format(tolerance)))
        if initial_state is None:
            initial_state = tf.zeros(dim, dtype=dtype)
        initial_state = tf.convert_to_tensor(initial_state,
                                             dtype=dtype,
                                             name='initial_state')
        batch_shape = tff_utils.get_shape(initial_state)[:-2]
        num_requested_times = tff_utils.get_shape(times)[0]
        # Create a time grid for the Euler scheme.
        if num_time_steps is not None and time_step is not None:
            raise ValueError(
                'When `times_grid` is not supplied only one of either '
                '`num_time_steps` or `time_step` should be defined but not both.'
            )
        if times_grid is None:
            if time_step is None:
                if num_time_steps is None:
                    raise ValueError(
                        'When `times_grid` is not supplied, either `num_time_steps` '
                        'or `time_step` should be defined.')
                num_time_steps = tf.convert_to_tensor(num_time_steps,
                                                      dtype=tf.int32,
                                                      name='num_time_steps')
                time_step = times[-1] / tf.cast(num_time_steps, dtype=dtype)
            else:
                time_step = tf.convert_to_tensor(time_step,
                                                 dtype=dtype,
                                                 name='time_step')
        else:
            times_grid = tf.convert_to_tensor(times_grid,
                                              dtype=dtype,
                                              name='times_grid')
            if validate_args:
                asserts.append(
                    tf.assert_greater(
                        times_grid[1:],
                        times_grid[:-1] + tolerance,
                        message='`times_grid` increments should be greater '
                        'than tolerance {0}'.format(tolerance)))
        times, keep_mask, time_indices = utils.prepare_grid(
            times=times,
            time_step=time_step,
            num_time_steps=num_time_steps,
            times_grid=times_grid,
            tolerance=tolerance,
            dtype=dtype)

        if normal_draws is not None:
            normal_draws = tf.convert_to_tensor(normal_draws,
                                                dtype=dtype,
                                                name='normal_draws')
            # Shape [num_time_points] + batch_shape + [num_samples, dim]
            normal_draws_rank = normal_draws.shape.rank
            perm = tf.concat(
                [[normal_draws_rank - 2],
                 tf.range(normal_draws_rank - 2), [normal_draws_rank - 1]],
                axis=0)
            normal_draws = tf.transpose(normal_draws, perm=perm)
            num_samples = tf.shape(normal_draws)[-2]
            draws_dim = normal_draws.shape[-1]
            if dim != draws_dim:
                raise ValueError(
                    '`dim` should be equal to `normal_draws.shape[2]` but are '
                    '{0} and {1} respectively'.format(dim, draws_dim))
            if validate_args:
                draws_times = tff_utils.get_shape(normal_draws)[0]
                asserts.append(
                    tf.assert_equal(
                        draws_times,
                        tf.shape(keep_mask)[0] - 1,
                        message='`num_time_steps` should be equal to '
                        '`tf.shape(normal_draws)[1]`'))
        if validate_args:
            with tf.control_dependencies(asserts):
                times = tf.identity(times)
        if watch_params is not None:
            watch_params = [
                tf.convert_to_tensor(param, dtype=dtype)
                for param in watch_params
            ]
        return _sample(dim=dim,
                       batch_shape=batch_shape,
                       drift_fn=drift_fn,
                       volatility_fn=volatility_fn,
                       times=times,
                       keep_mask=keep_mask,
                       num_requested_times=num_requested_times,
                       num_samples=num_samples,
                       initial_state=initial_state,
                       random_type=random_type,
                       seed=seed,
                       swap_memory=swap_memory,
                       skip=skip,
                       precompute_normal_draws=precompute_normal_draws,
                       normal_draws=normal_draws,
                       watch_params=watch_params,
                       time_indices=time_indices,
                       dtype=dtype)