def testBijectiveAndFiniteSkewnessNeg1Tailweight0p5(self): bijector = tfb.SinhArcsinh( skewness=-1., tailweight=0.5, validate_args=True) x = np.concatenate((-np.logspace(-2, 10, 1000), [0], np.logspace( -2, 10, 1000))).astype(np.float32) bijector_test_util.assert_bijective_and_finite( bijector, x, x, eval_func=self.evaluate, event_ndims=0, rtol=1e-3)
def testBijectorOverRange(self): for dtype in (np.float32, np.float64): skewness = np.array([1.2, 5.], dtype=dtype) tailweight = np.array([2., 10.], dtype=dtype) # The inverse will be defined up to where sinh is valid, which is # arcsinh(np.finfo(dtype).max). log_boundary = np.log( np.sinh( np.arcsinh(np.finfo(dtype).max) / tailweight - skewness)) x = np.array([ np.logspace(-2, log_boundary[0], base=np.e, num=1000), np.logspace(-2, log_boundary[1], base=np.e, num=1000) ], dtype=dtype) # Ensure broadcasting works. x = np.swapaxes(x, 0, 1) multiplier = 2. / np.sinh(np.arcsinh(2.) * tailweight) y = np.sinh((np.arcsinh(x) + skewness) * tailweight) * multiplier bijector = tfb.SinhArcsinh(skewness=skewness, tailweight=tailweight, validate_args=True) self.assertAllClose(y, self.evaluate(bijector.forward(x)), rtol=1e-4, atol=0.) self.assertAllClose(x, self.evaluate(bijector.inverse(y)), rtol=1e-4, atol=0.) # On IBM PPC systems, longdouble (np.float128) is same as double except # that it can have more precision. Type double being of 8 bytes, can't # hold square of max of float64 (which is also 8 bytes). # Below test fails due to overflow error giving inf. This check avoids # that error by skipping square calculation and corresponding assert. if (np.amax(y) <= np.sqrt(np.finfo(np.float128).max) and np.fabs(np.amin(y)) <= np.sqrt( np.fabs(np.finfo(np.float128).min))): # Do the numpy calculation in float128 to avoid inf/nan. y_float128 = np.float128(y) self.assertAllClose(np.log( np.cosh( np.arcsinh(y_float128 / multiplier) / tailweight - skewness) / np.sqrt((y_float128 / multiplier)**2 + 1)) - np.log(tailweight) - np.log(multiplier), self.evaluate( bijector.inverse_log_det_jacobian( y, event_ndims=0)), rtol=1e-4, atol=0.) self.assertAllClose(self.evaluate( -bijector.inverse_log_det_jacobian(y, event_ndims=0)), self.evaluate( bijector.forward_log_det_jacobian( x, event_ndims=0)), rtol=1e-4, atol=0.)
def testBijectorVersusNumpyRewriteOfBasicFunctions(self): with self.test_session(): skewness = 0.2 tailweight = 2.0 bijector = tfb.SinhArcsinh(skewness=skewness, tailweight=tailweight, validate_args=True) self.assertEqual("SinhArcsinh", bijector.name) x = np.array([[[-2.01], [2.], [1e-4]]]).astype(np.float32) y = np.sinh((np.arcsinh(x) + skewness) * tailweight) self.assertAllClose(y, self.evaluate(bijector.forward(x))) self.assertAllClose(x, self.evaluate(bijector.inverse(y))) self.assertAllClose( np.sum(np.log(np.cosh(np.arcsinh(y) / tailweight - skewness)) - np.log(tailweight) - np.log(np.sqrt(y**2 + 1)), axis=-1), self.evaluate( bijector.inverse_log_det_jacobian(y, event_ndims=1))) self.assertAllClose(self.evaluate( -bijector.inverse_log_det_jacobian(y, event_ndims=1)), self.evaluate( bijector.forward_log_det_jacobian( x, event_ndims=1)), rtol=1e-4, atol=0.)
def testBijectorVersusNumpyRewriteOfBasicFunctions(self): skewness = 0.2 tailweight = 2.0 multiplier = 2.0 / np.sinh(np.arcsinh(2.0) * tailweight) bijector = tfb.SinhArcsinh(skewness=skewness, tailweight=tailweight, validate_args=True) self.assertStartsWith(bijector.name, "sinh_arcsinh") x = np.array([[[-2.01], [2.], [1e-4]]]).astype(np.float32) y = np.sinh((np.arcsinh(x) + skewness) * tailweight) * multiplier self.assertAllClose(y, self.evaluate(bijector.forward(x))) self.assertAllClose(x, self.evaluate(bijector.inverse(y))) self.assertAllClose(np.sum( np.log(np.cosh(np.arcsinh(y / multiplier) / tailweight - skewness)) - np.log(tailweight) - np.log(np.sqrt((y / multiplier)**2 + 1)) - np.log(multiplier), axis=-1), self.evaluate( bijector.inverse_log_det_jacobian( y, event_ndims=1)), rtol=2e-6) self.assertAllClose( self.evaluate( -bijector.inverse_log_det_jacobian(y, event_ndims=1)), self.evaluate(bijector.forward_log_det_jacobian(x, event_ndims=1)), rtol=1e-4, atol=0.)
def testBijectiveAndFiniteSkewness1Tailweight3(self): with self.test_session(): bijector = tfb.SinhArcsinh(skewness=1., tailweight=3., validate_args=True) x = np.concatenate((-np.logspace(-2, 5, 1000), [0], np.logspace( -2, 5, 1000))).astype(np.float32) assert_bijective_and_finite( bijector, x, x, event_ndims=0, rtol=1e-3)
def testScalarCongruencySkewness1Tailweight0p5(self): with self.test_session(): bijector = tfb.SinhArcsinh(skewness=1.0, tailweight=0.5, validate_args=True) assert_scalar_congruency(bijector, lower_x=-2., upper_x=2.0, rtol=0.05)
def testKurtosis(self): x = np.logspace(-2, 2, 1000).astype(np.float32) tailweight = [[0.5], [1.0], [2.0]] bijector = tfb.SinhArcsinh(tailweight=tailweight, validate_args=True) y = self.evaluate(bijector.forward(x)) mean = np.mean(x, axis=-1) stddev = np.std(x, axis=-1, ddof=0) kurtosis = np.mean((y - mean)**4, axis=-1) / (stddev**4) self.assertAllClose(kurtosis, np.sort(kurtosis))
def testScalarCongruencySkewnessNeg1Tailweight1p5(self): bijector = tfb.SinhArcsinh(skewness=-1.0, tailweight=1.5, validate_args=True) bijector_test_util.assert_scalar_congruency(bijector, lower_x=-2., upper_x=2.0, eval_func=self.evaluate, rtol=0.05)
def testVariableTailweight(self): x = tf.Variable(1.) b = tfb.SinhArcsinh(tailweight=x, validate_args=True) self.evaluate(x.initializer) self.assertIs(x, b.tailweight) self.assertEqual((), self.evaluate(b.forward(0.5)).shape) with self.assertRaisesOpError('Argument `tailweight` must be positive.'): # pylint:disable=g-error-prone-assert-raises with tf.control_dependencies([x.assign(-1.)]): self.assertEqual((), self.evaluate(b.forward(0.5)).shape)
def testVariableTailweight(self): x = tf.Variable(1.) b = tfb.SinhArcsinh(tailweight=x, validate_args=True) self.evaluate(tf1.global_variables_initializer()) self.assertIs(x, b.tailweight) self.assertEqual((), self.evaluate(b.forward(0.5)).shape) with self.assertRaisesOpError( "Argument `tailweight` must be positive."): with tf.control_dependencies([x.assign(-1.)]): self.assertEqual((), self.evaluate(b.forward(0.5)).shape)
def testBijectorEndpoints(self): for dtype in (np.float32, np.float64): bijector = tfb.SinhArcsinh( skewness=dtype(0.), tailweight=dtype(1.), validate_args=True) bounds = np.array( [np.finfo(dtype).min, np.finfo(dtype).max], dtype=dtype) # Note that the above bijector is the identity bijector. Hence, the # log_det_jacobian will be 0. Because of this we use atol. bijector_test_util.assert_bijective_and_finite( bijector, bounds, bounds, eval_func=self.evaluate, event_ndims=0, atol=2e-6)
def testBijectorEndpoints(self, dtype): bijector = tfb.SinhArcsinh( skewness=dtype(0.), tailweight=dtype(1.), validate_args=True) # Use bounds that are very large to check that the transformation remains # bijective. We stray away from the largest/smallest value to avoid issues # at the boundary since XLA sinh will return `inf` for the largest value. bounds = np.array( [np.nextafter(np.finfo(dtype).min, 0.) / 10., np.nextafter(np.finfo(dtype).max, 0.) / 10.], dtype=dtype) # Note that the above bijector is the identity bijector. Hence, the # log_det_jacobian will be 0. Because of this we use atol. bijector_test_util.assert_bijective_and_finite( bijector, bounds, bounds, eval_func=self.evaluate, event_ndims=0, atol=2e-6)
def testBijectorOverRange(self): with self.test_session(): for dtype in (np.float32, np.float64): skewness = np.array([1.2, 5.], dtype=dtype) tailweight = np.array([2., 10.], dtype=dtype) # The inverse will be defined up to where sinh is valid, which is # arcsinh(np.finfo(dtype).max). log_boundary = np.log( np.sinh( np.arcsinh(np.finfo(dtype).max) / tailweight - skewness)) x = np.array([ np.logspace(-2, log_boundary[0], base=np.e, num=1000), np.logspace(-2, log_boundary[1], base=np.e, num=1000) ], dtype=dtype) # Ensure broadcasting works. x = np.swapaxes(x, 0, 1) y = np.sinh((np.arcsinh(x) + skewness) * tailweight) bijector = tfb.SinhArcsinh(skewness=skewness, tailweight=tailweight, validate_args=True) self.assertAllClose(y, self.evaluate(bijector.forward(x)), rtol=1e-4, atol=0.) self.assertAllClose(x, self.evaluate(bijector.inverse(y)), rtol=1e-4, atol=0.) # Do the numpy calculation in float128 to avoid inf/nan. y_float128 = np.float128(y) self.assertAllClose(np.log( np.cosh(np.arcsinh(y_float128) / tailweight - skewness) / np.sqrt(y_float128**2 + 1)) - np.log(tailweight), self.evaluate( bijector.inverse_log_det_jacobian( y, event_ndims=0)), rtol=1e-4, atol=0.) self.assertAllClose(self.evaluate( -bijector.inverse_log_det_jacobian(y, event_ndims=0)), self.evaluate( bijector.forward_log_det_jacobian( x, event_ndims=0)), rtol=1e-4, atol=0.)
def testLargerTailWeightPutsMoreWeightInTails(self): # Will broadcast together to shape [3, 2]. x = [-1., 1.] tailweight = [[0.5], [1.0], [2.0]] bijector = tfb.SinhArcsinh(tailweight=tailweight, validate_args=True) y = self.evaluate(bijector.forward(x)) # x = -1, 1 should be mapped to points symmetric about 0 self.assertAllClose(y[:, 0], -1. * y[:, 1]) # forward(1) should increase as tailweight increases, since higher # tailweight should map 1 to a larger number. forward_1 = y[:, 1] # The positive values of y. self.assertLess(forward_1[0], forward_1[1]) self.assertLess(forward_1[1], forward_1[2])
def testSkew(self): # Will broadcast together to shape [3, 2]. x = [-1., 1.] skewness = [[-1.], [0.], [1.]] bijector = tfb.SinhArcsinh(skewness=skewness, validate_args=True) y = self.evaluate(bijector.forward(x)) # For skew < 0, |forward(-1)| > |forward(1)| self.assertGreater(np.abs(y[0, 0]), np.abs(y[0, 1])) # For skew = 0, |forward(-1)| = |forward(1)| self.assertAllClose(np.abs(y[1, 0]), np.abs(y[1, 1])) # For skew > 0, |forward(-1)| < |forward(1)| self.assertLess(np.abs(y[2, 0]), np.abs(y[2, 1]))
def __init__(self, loc=None, scale_diag=None, scale_identity_multiplier=None, skewness=None, tailweight=None, distribution=None, validate_args=False, allow_nan_stats=True, name="MultivariateNormalLinearOperator"): """Construct VectorSinhArcsinhDiag distribution on `R^k`. The arguments `scale_diag` and `scale_identity_multiplier` combine to define the diagonal `scale` referred to in this class docstring: ```none scale = diag(scale_diag + scale_identity_multiplier * ones(k)) ``` The `batch_shape` is the broadcast shape between `loc` and `scale` arguments. The `event_shape` is given by last dimension of the matrix implied by `scale`. The last dimension of `loc` (if provided) must broadcast with this Additional leading dimensions (if any) will index batches. Args: loc: Floating-point `Tensor`. If this is set to `None`, `loc` is implicitly `0`. When specified, may have shape `[B1, ..., Bb, k]` where `b >= 0` and `k` is the event size. scale_diag: Non-zero, floating-point `Tensor` representing a diagonal matrix added to `scale`. May have shape `[B1, ..., Bb, k]`, `b >= 0`, and characterizes `b`-batches of `k x k` diagonal matrices added to `scale`. When both `scale_identity_multiplier` and `scale_diag` are `None` then `scale` is the `Identity`. scale_identity_multiplier: Non-zero, floating-point `Tensor` representing a scale-identity-matrix added to `scale`. May have shape `[B1, ..., Bb]`, `b >= 0`, and characterizes `b`-batches of scale `k x k` identity matrices added to `scale`. When both `scale_identity_multiplier` and `scale_diag` are `None` then `scale` is the `Identity`. skewness: Skewness parameter. floating-point `Tensor` with shape broadcastable with `event_shape`. tailweight: Tailweight parameter. floating-point `Tensor` with shape broadcastable with `event_shape`. distribution: `tf.Distribution`-like instance. Distribution from which `k` iid samples are used as input to transformation `F`. Default is `tf.distributions.Normal(loc=0., scale=1.)`. Must be a scalar-batch, scalar-event distribution. Typically `distribution.reparameterization_type = FULLY_REPARAMETERIZED` or it is a function of non-trainable parameters. WARNING: If you backprop through a VectorSinhArcsinhDiag sample and `distribution` is not `FULLY_REPARAMETERIZED` yet is a function of trainable variables, then the gradient will be incorrect! 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. Raises: ValueError: if at most `scale_identity_multiplier` is specified. """ parameters = dict(locals()) with tf.name_scope(name, values=[ loc, scale_diag, scale_identity_multiplier, skewness, tailweight ]) as name: loc = tf.convert_to_tensor(loc, name="loc") if loc is not None else loc tailweight = 1. if tailweight is None else tailweight has_default_skewness = skewness is None skewness = 0. if skewness is None else skewness # Recall, with Z a random variable, # Y := loc + C * F(Z), # F(Z) := Sinh( (Arcsinh(Z) + skewness) * tailweight ) # F_0(Z) := Sinh( Arcsinh(Z) * tailweight ) # C := 2 * scale / F_0(2) # Construct shapes and 'scale' out of the scale_* and loc kwargs. # scale_linop is only an intermediary to: # 1. get shapes from looking at loc and the two scale args. # 2. combine scale_diag with scale_identity_multiplier, which gives us # 'scale', which in turn gives us 'C'. scale_linop = distribution_util.make_diag_scale( loc=loc, scale_diag=scale_diag, scale_identity_multiplier=scale_identity_multiplier, validate_args=False, assert_positive=False) batch_shape, event_shape = distribution_util.shapes_from_loc_and_scale( loc, scale_linop) # scale_linop.diag_part() is efficient since it is a diag type linop. scale_diag_part = scale_linop.diag_part() dtype = scale_diag_part.dtype if distribution is None: distribution = tf.distributions.Normal( loc=tf.zeros([], dtype=dtype), scale=tf.ones([], dtype=dtype), allow_nan_stats=allow_nan_stats) else: asserts = distribution_util.maybe_check_scalar_distribution( distribution, dtype, validate_args) if asserts: scale_diag_part = control_flow_ops.with_dependencies( asserts, scale_diag_part) # Make the SAS bijector, 'F'. skewness = tf.convert_to_tensor(skewness, dtype=dtype, name="skewness") tailweight = tf.convert_to_tensor(tailweight, dtype=dtype, name="tailweight") f = bijectors.SinhArcsinh(skewness=skewness, tailweight=tailweight) if has_default_skewness: f_noskew = f else: f_noskew = bijectors.SinhArcsinh( skewness=skewness.dtype.as_numpy_dtype(0.), tailweight=tailweight) # Make the Affine bijector, Z --> loc + C * Z. c = 2 * scale_diag_part / f_noskew.forward( tf.convert_to_tensor(2, dtype=dtype)) affine = bijectors.Affine(shift=loc, scale_diag=c, validate_args=validate_args) bijector = bijectors.Chain([affine, f]) super(VectorSinhArcsinhDiag, self).__init__(distribution=distribution, bijector=bijector, batch_shape=batch_shape, event_shape=event_shape, validate_args=validate_args, name=name) self._parameters = parameters self._loc = loc self._scale = scale_linop self._tailweight = tailweight self._skewness = skewness
def testVariableEq(self): # Testing for b/186021261, bijector equality in the presence of TF # Variables. v1 = tf.Variable(3, dtype=tf.float32) v2 = tf.Variable(4, dtype=tf.float32) self.assertNotEqual(tfb.SinhArcsinh(v1), tfb.SinhArcsinh(v2))
def __init__(self, loc, scale, skewness=None, tailweight=None, distribution=None, validate_args=False, allow_nan_stats=True, name="SinhArcsinh"): """Construct SinhArcsinh distribution on `(-inf, inf)`. Arguments `(loc, scale, skewness, tailweight)` must have broadcastable shape (indexing batch dimensions). They must all have the same `dtype`. Args: loc: Floating-point `Tensor`. scale: `Tensor` of same `dtype` as `loc`. skewness: Skewness parameter. Default is `0.0` (no skew). tailweight: Tailweight parameter. Default is `1.0` (unchanged tailweight) distribution: `tf.Distribution`-like instance. Distribution that is transformed to produce this distribution. Default is `tfd.Normal(0., 1.)`. Must be a scalar-batch, scalar-event distribution. Typically `distribution.reparameterization_type = FULLY_REPARAMETERIZED` or it is a function of non-trainable parameters. WARNING: If you backprop through a `SinhArcsinh` sample and `distribution` is not `FULLY_REPARAMETERIZED` yet is a function of trainable variables, then the gradient will be incorrect! 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 = dict(locals()) with tf.name_scope(name, values=[loc, scale, skewness, tailweight]) as name: dtype = dtype_util.common_dtype([loc, scale, skewness, tailweight], tf.float32) loc = tf.convert_to_tensor(loc, name="loc", dtype=dtype) scale = tf.convert_to_tensor(scale, name="scale", dtype=dtype) tailweight = 1. if tailweight is None else tailweight has_default_skewness = skewness is None skewness = 0. if skewness is None else skewness tailweight = tf.convert_to_tensor(tailweight, name="tailweight", dtype=dtype) skewness = tf.convert_to_tensor(skewness, name="skewness", dtype=dtype) batch_shape = distribution_util.get_broadcast_shape( loc, scale, tailweight, skewness) # Recall, with Z a random variable, # Y := loc + C * F(Z), # F(Z) := Sinh( (Arcsinh(Z) + skewness) * tailweight ) # F_0(Z) := Sinh( Arcsinh(Z) * tailweight ) # C := 2 * scale / F_0(2) if distribution is None: distribution = normal.Normal(loc=tf.zeros([], dtype=dtype), scale=tf.ones([], dtype=dtype), allow_nan_stats=allow_nan_stats) else: asserts = distribution_util.maybe_check_scalar_distribution( distribution, dtype, validate_args) if asserts: loc = control_flow_ops.with_dependencies(asserts, loc) # Make the SAS bijector, 'F'. f = bijectors.SinhArcsinh(skewness=skewness, tailweight=tailweight) if has_default_skewness: f_noskew = f else: f_noskew = bijectors.SinhArcsinh( skewness=skewness.dtype.as_numpy_dtype(0.), tailweight=tailweight) # Make the AffineScalar bijector, Z --> loc + scale * Z (2 / F_0(2)) c = 2 * scale / f_noskew.forward( tf.convert_to_tensor(2, dtype=dtype)) affine = bijectors.AffineScalar(shift=loc, scale=c, validate_args=validate_args) bijector = bijectors.Chain([affine, f]) super(SinhArcsinh, self).__init__(distribution=distribution, bijector=bijector, batch_shape=batch_shape, validate_args=validate_args, name=name) self._parameters = parameters self._loc = loc self._scale = scale self._tailweight = tailweight self._skewness = skewness
def testZeroTailweightRaises(self): with self.test_session(): with self.assertRaisesOpError("not positive"): self.evaluate( tfb.SinhArcsinh(tailweight=0., validate_args=True).forward(1.0))
def testZeroTailweightRaises(self): with self.assertRaisesOpError( "Argument `tailweight` must be positive"): self.evaluate( tfb.SinhArcsinh(tailweight=0., validate_args=True).forward(1.0))
def testDefaultDtypeIsFloat32(self): bijector = tfb.SinhArcsinh() self.assertEqual(bijector.tailweight.dtype, np.float32) self.assertEqual(bijector.skewness.dtype, np.float32)