def test_sqrt_to_dense(self): with self.test_session(): chol = self._random_cholesky_array((2, 3, 3)) chol_2 = chol.copy() chol_2[0, 0, 2] = 1000 # Make sure upper triangular part makes no diff. operator = operator_pd_cholesky.OperatorPDCholesky(chol_2) self.assertAllClose(chol, operator.sqrt_to_dense().eval())
def testNonPositiveDefiniteMatrixDoesNotRaiseIfNotVerifyPd(self): # Singlular matrix with one positive eigenvalue and one zero eigenvalue. with self.test_session(): lower_mat = [[1.0, 0.0], [2.0, 0.0]] operator = operator_pd_cholesky.OperatorPDCholesky(lower_mat, verify_pd=False) operator.to_dense().eval() # Should not raise.
def test_non_positive_definite_matrix_raises(self): # Singlular matrix with one positive eigenvalue and one zero eigenvalue. with self.test_session(): lower_mat = [[1.0, 0.0], [2.0, 0.0]] operator = operator_pd_cholesky.OperatorPDCholesky(lower_mat) with self.assertRaisesOpError('x > 0 did not hold'): operator.to_dense().eval()
def __init__( self, mu, chol, strict=True, strict_statistics=True, name="MultivariateNormalCholesky"): """Multivariate Normal distributions on `R^k`. User must provide means `mu` and `chol` which holds the (batch) Cholesky factors `S`, such that the covariance of each batch member is `S S^*`. Args: mu: `(N+1)-D` `float` or `double` tensor with shape `[N1,...,Nb, k]`, `b >= 0`. chol: `(N+2)-D` `Tensor` with same `dtype` as `mu` and shape `[N1,...,Nb, k, k]`. strict: Whether to validate input with asserts. If `strict` is `False`, and the inputs are invalid, correct behavior is not guaranteed. strict_statistics: Boolean, default True. If True, raise an exception if a statistic (e.g. mean/mode/etc...) is undefined for any batch member. If False, batch members with valid parameters leading to undefined statistics will return NaN for this statistic. name: The name to give Ops created by the initializer. Raises: TypeError: If `mu` and `chol` are different dtypes. """ cov = operator_pd_cholesky.OperatorPDCholesky(chol, verify_pd=strict) super(MultivariateNormalCholesky, self).__init__( mu, cov, strict_statistics=strict_statistics, strict=strict, name=name)
def test_non_positive_definite_matrix_does_not_raise_if_not_verify_pd(self): # Singlular matrix with one positive eigenvalue and one zero eigenvalue. with self.test_session(): lower_mat = [[1.0, 0.0], [2.0, 0.0]] operator = operator_pd_cholesky.OperatorPDCholesky( lower_mat, verify_pd=False) operator.to_dense().eval() # Should not raise.
def testNotHavingTwoIdenticalLastDimsRaises(self): # Unless the last two dims are equal, this cannot represent a matrix, and it # should raise. with self.test_session(): batch_vec = [[1.0], [2.0]] # shape 2 x 1 with self.assertRaisesOpError("x == y did not hold"): operator = operator_pd_cholesky.OperatorPDCholesky(batch_vec) operator.to_dense().eval()
def test_shape(self): # All other shapes are defined by the abstractmethod shape, so we only need # to test this. with self.test_session(): for shape in [(3, 3), (2, 3, 3), (1, 2, 3, 3)]: chol = self._random_cholesky_array(shape) operator = operator_pd_cholesky.OperatorPDCholesky(chol) self.assertAllEqual(shape, operator.shape().eval())
def test_not_having_two_identical_last_dims_raises(self): # Unless the last two dims are equal, this cannot represent a matrix, and it # should raise. with self.test_session(): batch_vec = [[1.0], [2.0]] # shape 2 x 1 with self.assertRaisesRegexp(ValueError, '.*Dimensions.*'): operator = operator_pd_cholesky.OperatorPDCholesky(batch_vec) operator.to_dense().eval()
def test_log_det(self): with self.test_session(): batch_shape = () for k in [1, 4]: chol_shape = batch_shape + (k, k) chol = self._random_cholesky_array(chol_shape) operator = operator_pd_cholesky.OperatorPDCholesky(chol) log_det = operator.log_det() expected_log_det = np.log(np.prod(np.diag(chol))**2) self.assertEqual(batch_shape, log_det.get_shape()) self.assertAllClose(expected_log_det, log_det.eval())
def test_log_det_batch_matrix(self): with self.test_session(): batch_shape = (2, 3) for k in [1, 4]: chol_shape = batch_shape + (k, k) chol = self._random_cholesky_array(chol_shape) operator = operator_pd_cholesky.OperatorPDCholesky(chol) log_det = operator.log_det() self.assertEqual(batch_shape, log_det.get_shape()) # Test the log-determinant of the [1, 1] matrix. chol_11 = chol[1, 1, :, :] expected_log_det = np.log(np.prod(np.diag(chol_11))**2) self.assertAllClose(expected_log_det, log_det.eval()[1, 1])
def test_matmul_batch_matrix(self): with self.test_session(): batch_shape = (2, 3) for k in [1, 4]: x_shape = batch_shape + (k, 5) x = self._rng.rand(*x_shape) chol_shape = batch_shape + (k, k) chol = self._random_cholesky_array(chol_shape) matrix = tf.batch_matmul(chol, chol, adj_y=True) operator = operator_pd_cholesky.OperatorPDCholesky(chol) expected = tf.batch_matmul(matrix, x) self.assertEqual(expected.get_shape(), operator.matmul(x).get_shape()) self.assertAllClose(expected.eval(), operator.matmul(x).eval())
def test_sqrt_matmul_single_matrix(self): with self.test_session(): batch_shape = () for k in [1, 4]: x_shape = batch_shape + (k, 3) x = self._rng.rand(*x_shape) chol_shape = batch_shape + (k, k) chol = self._random_cholesky_array(chol_shape) operator = operator_pd_cholesky.OperatorPDCholesky(chol) sqrt_operator_times_x = operator.sqrt_matmul(x) expected = tf.batch_matmul(chol, x) self.assertEqual(expected.get_shape(), sqrt_operator_times_x.get_shape()) self.assertAllClose(expected.eval(), sqrt_operator_times_x.eval())
def testSqrtMatmulBatchMatrixWithTranspose(self): with self.test_session(): batch_shape = (2, 3) for k in [1, 4]: x_shape = batch_shape + (5, k) x = self._rng.rand(*x_shape) chol_shape = batch_shape + (k, k) chol = self._random_cholesky_array(chol_shape) operator = operator_pd_cholesky.OperatorPDCholesky(chol) sqrt_operator_times_x = operator.sqrt_matmul(x, transpose_x=True) # tf.batch_matmul is defined x * y, so "y" is on the right, not "x". expected = math_ops.matmul(chol, x, adjoint_b=True) self.assertEqual(expected.get_shape(), sqrt_operator_times_x.get_shape()) self.assertAllClose(expected.eval(), sqrt_operator_times_x.eval())
def test_matmul_batch_matrix_with_transpose(self): with self.test_session(): batch_shape = (2, 3) for k in [1, 4]: x_shape = batch_shape + (5, k) x = self._rng.rand(*x_shape) chol_shape = batch_shape + (k, k) chol = self._random_cholesky_array(chol_shape) matrix = tf.batch_matmul(chol, chol, adj_y=True) operator = operator_pd_cholesky.OperatorPDCholesky(chol) operator_times_x = operator.matmul(x, transpose_x=True) # tf.batch_matmul is defined x * y, so "y" is on the right, not "x". expected = tf.batch_matmul(matrix, x, adj_y=True) self.assertEqual(expected.get_shape(), operator_times_x.get_shape()) self.assertAllClose(expected.eval(), operator_times_x.eval())
def __init__(self, mu, chol, validate_args=False, allow_nan_stats=True, name="MultivariateNormalCholesky"): """Multivariate Normal distributions on `R^k`. User must provide means `mu` and `chol` which holds the (batch) Cholesky factors, such that the covariance of each batch member is `chol chol^T`. Args: mu: `(N+1)-D` floating point tensor with shape `[N1,...,Nb, k]`, `b >= 0`. chol: `(N+2)-D` `Tensor` with same `dtype` as `mu` and shape `[N1,...,Nb, k, k]`. The upper triangular part is ignored (treated as though it is zero), and the diagonal must be positive. validate_args: `Boolean`, default `False`. Whether to validate input with asserts. If `validate_args` is `False`, and the inputs are invalid, correct behavior is not guaranteed. allow_nan_stats: `Boolean`, default `True`. If `False`, raise an exception if a statistic (e.g. mean/mode/etc...) is undefined for any batch member If `True`, batch members with valid parameters leading to undefined statistics will return NaN for this statistic. name: The name to give Ops created by the initializer. Raises: TypeError: If `mu` and `chol` are different dtypes. """ parameters = locals() parameters.pop("self") with ops.name_scope(name, values=[chol]) as ns: cov = operator_pd_cholesky.OperatorPDCholesky( chol, verify_pd=validate_args) super(MultivariateNormalCholesky, self).__init__(mu, cov, allow_nan_stats=allow_nan_stats, validate_args=validate_args, name=ns) self._parameters = parameters
def __init__(self, df, scale, cholesky_input_output_matrices=False, validate_args=False, allow_nan_stats=True, name="WishartCholesky"): """Construct Wishart distributions. Args: df: `float` or `double` `Tensor`. Degrees of freedom, must be greater than or equal to dimension of the scale matrix. scale: `float` or `double` `Tensor`. The Cholesky factorization of the symmetric positive definite scale matrix of the distribution. cholesky_input_output_matrices: Python `bool`. Any function which whose input or output is a matrix assumes the input is Cholesky and returns a Cholesky factored matrix. Example `log_prob` input takes a Cholesky and `sample_n` returns a Cholesky when `cholesky_input_output_matrices=True`. validate_args: Python `bool`, default `False`. When `True` distribution parameters are checked for validity despite possibly degrading runtime performance. When `False` invalid inputs may silently render incorrect outputs. allow_nan_stats: Python `bool`, default `True`. When `True`, statistics (e.g., mean, mode, variance) use the value "`NaN`" to indicate the result is undefined. When `False`, an exception is raised if one or more of the statistic's batch members are undefined. name: Python `str` name prefixed to Ops created by this class. """ parameters = locals() with ops.name_scope(name, values=[scale]): super(WishartCholesky, self).__init__( df=df, scale_operator_pd=operator_pd_cholesky.OperatorPDCholesky( scale, verify_pd=validate_args), cholesky_input_output_matrices=cholesky_input_output_matrices, validate_args=validate_args, allow_nan_stats=allow_nan_stats, name=name) self._parameters = parameters
def __init__(self, df, scale, cholesky_input_output_matrices=False, validate_args=False, allow_nan_stats=True, name="WishartCholesky"): """Construct Wishart distributions. Args: df: `float` or `double` `Tensor`. Degrees of freedom, must be greater than or equal to dimension of the scale matrix. scale: `float` or `double` `Tensor`. The Cholesky factorization of the symmetric positive definite scale matrix of the distribution. cholesky_input_output_matrices: `Boolean`. Any function which whose input or output is a matrix assumes the input is Cholesky and returns a Cholesky factored matrix. Example`log_pdf` input takes a Cholesky and `sample_n` returns a Cholesky when `cholesky_input_output_matrices=True`. validate_args: `Boolean`, default `False`. Whether to validate input with asserts. If `validate_args` is `False`, and the inputs are invalid, correct behavior is not guaranteed. allow_nan_stats: `Boolean`, default `True`. If `False`, raise an exception if a statistic (e.g., mean, mode) is undefined for any batch member. If True, batch members with valid parameters leading to undefined statistics will return `NaN` for this statistic. name: The name scope to give class member ops. """ parameters = locals() parameters.pop("self") with ops.name_scope(name, values=[scale]) as ns: super(WishartCholesky, self).__init__( df=df, scale_operator_pd=operator_pd_cholesky.OperatorPDCholesky( scale, verify_pd=validate_args), cholesky_input_output_matrices=cholesky_input_output_matrices, validate_args=validate_args, allow_nan_stats=allow_nan_stats, name=ns) self._parameters = parameters
def _create_scale_operator(self, identity_multiplier, diag, tril, perturb_diag, perturb_factor, event_ndims, validate_args): """Construct `scale` from various components. Args: identity_multiplier: floating point rank 0 `Tensor` representing a scaling done to the identity matrix. diag: Floating-point `Tensor` representing the diagonal matrix. `scale_diag` has shape [N1, N2, ... k], which represents a k x k diagonal matrix. tril: Floating-point `Tensor` representing the diagonal matrix. `scale_tril` has shape [N1, N2, ... k], which represents a k x k lower triangular matrix. perturb_diag: Floating-point `Tensor` representing the diagonal matrix of the low rank update. perturb_factor: Floating-point `Tensor` representing factor matrix. event_ndims: Scalar `int32` `Tensor` indicating the number of dimensions associated with a particular draw from the distribution. Must be 0 or 1 validate_args: Python `bool` indicating whether arguments should be checked for correctness. Returns: scale. In the case of scaling by a constant, scale is a floating point `Tensor`. Otherwise, scale is an `OperatorPD`. Raises: ValueError: if all of `tril`, `diag` and `identity_multiplier` are `None`. """ identity_multiplier = _as_tensor(identity_multiplier, "identity_multiplier") diag = _as_tensor(diag, "diag") tril = _as_tensor(tril, "tril") perturb_diag = _as_tensor(perturb_diag, "perturb_diag") perturb_factor = _as_tensor(perturb_factor, "perturb_factor") identity_multiplier = self._maybe_validate_identity_multiplier( identity_multiplier, validate_args) if perturb_factor is not None: perturb_factor = self._process_matrix(perturb_factor, min_rank=2, event_ndims=event_ndims) if perturb_diag is not None: perturb_diag = self._process_matrix(perturb_diag, min_rank=1, event_ndims=event_ndims) # The following if-statments are ordered by increasingly stronger # assumptions in the base matrix, i.e., we process in the order: # TriL, Diag, Identity. if tril is not None: tril = self._preprocess_tril(identity_multiplier, diag, tril, event_ndims) if perturb_factor is None: return operator_pd_cholesky.OperatorPDCholesky( tril, verify_pd=validate_args) return _TriLPlusVDVTLightweightOperatorPD( tril=tril, v=perturb_factor, diag=perturb_diag, validate_args=validate_args) if diag is not None: diag = self._preprocess_diag(identity_multiplier, diag, event_ndims) if perturb_factor is None: return operator_pd_diag.OperatorPDSqrtDiag( diag, verify_pd=validate_args) return operator_pd_vdvt_update.OperatorPDSqrtVDVTUpdate( operator=operator_pd_diag.OperatorPDDiag( diag, verify_pd=validate_args), v=perturb_factor, diag=perturb_diag, verify_pd=validate_args) if identity_multiplier is not None: if perturb_factor is None: return identity_multiplier # Infer the shape from the V and D. v_shape = array_ops.shape(perturb_factor) identity_shape = array_ops.concat([v_shape[:-1], [v_shape[-2]]], 0) scaled_identity = operator_pd_identity.OperatorPDIdentity( identity_shape, perturb_factor.dtype.base_dtype, scale=identity_multiplier, verify_pd=validate_args) return operator_pd_vdvt_update.OperatorPDSqrtVDVTUpdate( operator=scaled_identity, v=perturb_factor, diag=perturb_diag, verify_pd=validate_args) raise ValueError( "One of tril, diag and/or identity_multiplier must be " "specified.")