def _check_alpha(self, alpha): alpha = ops.convert_to_tensor(alpha, name='alpha') if not self.strict: return alpha return control_flow_ops.with_dependencies( [check_ops.assert_rank_at_least(alpha, 1), check_ops.assert_positive(alpha)], alpha)
def _assert_valid_alpha(self, alpha, validate_args): alpha = ops.convert_to_tensor(alpha, name="alpha") if not validate_args: return alpha return control_flow_ops.with_dependencies( [check_ops.assert_rank_at_least(alpha, 1), check_ops.assert_positive(alpha)], alpha)
def _check_alpha(alpha): """Check alpha for proper shape, values, then return tensor version.""" alpha = ops.convert_to_tensor(alpha, name='alpha_before_deps') return control_flow_ops.with_dependencies([ check_ops.assert_rank_at_least(alpha, 1), check_ops.assert_positive(alpha) ], alpha)
def maybe_check_quadrature_param(param, name, validate_args): """Helper which checks validity of `loc` and `scale` init args.""" with ops.name_scope(name="check_" + name, values=[param]): assertions = [] if param.shape.ndims is not None: if param.shape.ndims == 0: raise ValueError("Mixing params must be a (batch of) vector; " "{}.rank={} is not at least one.".format( name, param.shape.ndims)) elif validate_args: assertions.append( check_ops.assert_rank_at_least( param, 1, message=("Mixing params must be a (batch of) vector; " "{}.rank is not at least one.".format(name)))) # TODO(jvdillon): Remove once we support k-mixtures. if param.shape.with_rank_at_least(1)[-1] is not None: if param.shape[-1].value != 1: raise NotImplementedError( "Currently only bimixtures are supported; " "{}.shape[-1]={} is not 1.".format(name, param.shape[-1].value)) elif validate_args: assertions.append( check_ops.assert_equal( array_ops.shape(param)[-1], 1, message=("Currently only bimixtures are supported; " "{}.shape[-1] is not 1.".format(name)))) if assertions: return control_flow_ops.with_dependencies(assertions, param) return param
def __init__(self, params, labels, event_index, model_hparams=None): del model_hparams del labels del event_index params_shape = array_ops.shape(params) assert_rank = check_ops.assert_rank_at_least( params, 2, data=[params_shape], message='Cox model params shape must be [batch_size, num_feature]') with ops.control_dependencies([assert_rank]): logits_shape = params.get_shape()[1] # We assume the base rate is constant in this implementation: # lambda = lambda_0 * exp(X * weights) = exp (bias + logits * weights) with tf.variable_scope('logit_to_parameter', reuse=tf.AUTO_REUSE): self._weights = tf.get_variable( 'weights', [logits_shape, 1], initializer=tf.initializers.truncated_normal(0, 0.01)) self._bias = tf.get_variable( 'bias', [1], initializer=tf.initializers.truncated_normal(0.01, 0.01)) weighted_logits = tf.matmul(params, self._weights) + self._bias self._rate_param = tf.exp(weighted_logits) self._distribution = tfp.distributions.Exponential( rate=self._rate_param)
def test_rank_one_tensor_raises_if_rank_too_small_static_rank(self): tensor = constant_op.constant([1, 2], name="my_tensor") desired_rank = 2 with self.assertRaisesRegexp(ValueError, "rank at least 2"): with ops.control_dependencies( [check_ops.assert_rank_at_least(tensor, desired_rank)]): self.evaluate(array_ops.identity(tensor))
def maybe_check_quadrature_param(param, name, validate_args): """Helper which checks validity of `loc` and `scale` init args.""" with ops.name_scope(name="check_" + name, values=[param]): assertions = [] if param.shape.ndims is not None: if param.shape.ndims == 0: raise ValueError("Mixing params must be a (batch of) vector; " "{}.rank={} is not at least one.".format( name, param.shape.ndims)) elif validate_args: assertions.append(check_ops.assert_rank_at_least( param, 1, message=("Mixing params must be a (batch of) vector; " "{}.rank is not at least one.".format( name)))) # TODO(jvdillon): Remove once we support k-mixtures. if param.shape.with_rank_at_least(1)[-1] is not None: if param.shape[-1].value != 1: raise NotImplementedError("Currently only bimixtures are supported; " "{}.shape[-1]={} is not 1.".format( name, param.shape[-1].value)) elif validate_args: assertions.append(check_ops.assert_equal( array_ops.shape(param)[-1], 1, message=("Currently only bimixtures are supported; " "{}.shape[-1] is not 1.".format(name)))) if assertions: return control_flow_ops.with_dependencies(assertions, param) return param
def test_rank_one_tensor_doesnt_raise_if_rank_just_right_static_rank(self): with self.test_session(): tensor = constant_op.constant([1, 2], name="my_tensor") desired_rank = 1 with ops.control_dependencies( [check_ops.assert_rank_at_least(tensor, desired_rank)]): array_ops.identity(tensor).eval()
def test_rank_one_tensor_doesnt_raise_if_rank_just_right_dynamic_rank(self): with self.test_session(): tensor = array_ops.placeholder(dtypes.float32, name="my_tensor") desired_rank = 1 with ops.control_dependencies( [check_ops.assert_rank_at_least(tensor, desired_rank)]): array_ops.identity(tensor).eval(feed_dict={tensor: [1, 2]})
def check_logits_final_dim(logits, expected_logits_dimension): """Checks that logits shape is [D0, D1, ... DN, logits_dimension].""" with ops.name_scope('logits', values=(logits,)) as scope: logits = math_ops.to_float(logits) # Eager mode if context.executing_eagerly(): logits_shape = logits._shape_tuple() # pylint: disable=protected-access logits_rank = logits._rank() # pylint: disable=protected-access if logits_rank < 2: raise ValueError('logits must have rank at least 2. Received rank {}, ' 'shape {}'.format(logits_rank, logits_shape)) if logits_shape[-1] != expected_logits_dimension: raise ValueError( 'logits shape must be [D0, D1, ... DN, logits_dimension], ' 'got {}.'.format(logits_shape)) return logits # Graph mode logits_shape = array_ops.shape(logits) assert_rank = check_ops.assert_rank_at_least( logits, 2, data=[logits_shape], message='logits shape must be [D0, D1, ... DN, logits_dimension]') with ops.control_dependencies([assert_rank]): static_shape = logits.shape if static_shape.ndims is not None and static_shape[-1] is not None: if static_shape[-1] != expected_logits_dimension: raise ValueError( 'logits shape must be [D0, D1, ... DN, logits_dimension], ' 'got {}.'.format(static_shape)) return logits assert_dimension = check_ops.assert_equal( expected_logits_dimension, logits_shape[-1], data=[logits_shape], message='logits shape must be [D0, D1, ... DN, logits_dimension]') with ops.control_dependencies([assert_dimension]): return array_ops.identity(logits, name=scope)
def test_rank_one_tensor_raises_if_rank_too_small_dynamic_rank(self): with self.test_session(): tensor = array_ops.placeholder(dtypes.float32, name="my_tensor") desired_rank = 2 with ops.control_dependencies( [check_ops.assert_rank_at_least(tensor, desired_rank)]): with self.assertRaisesOpError("my_tensor.*rank"): array_ops.identity(tensor).eval(feed_dict={tensor: [1, 2]})
def test_rank_zero_tensor_raises_if_rank_too_small_static_rank(self): with self.test_session(): tensor = constant_op.constant(1, name="my_tensor") desired_rank = 1 with self.assertRaisesRegexp(ValueError, "my_tensor.*rank at least 1"): with ops.control_dependencies( [check_ops.assert_rank_at_least(tensor, desired_rank)]): array_ops.identity(tensor).eval()
def test_rank_one_tensor_raises_if_rank_too_small_static_rank(self): with self.test_session(): tensor = constant_op.constant([1, 2], name="my_tensor") desired_rank = 2 with self.assertRaisesRegexp(ValueError, "my_tensor.*rank"): with ops.control_dependencies( [check_ops.assert_rank_at_least(tensor, desired_rank)]): array_ops.identity(tensor).eval()
def lbeta(x, name='lbeta'): r"""Computes `ln(|Beta(x)|)`, reducing along the last dimension. Given one-dimensional `z = [z_0,...,z_{K-1}]`, we define ```Beta(z) = \prod_j Gamma(z_j) / Gamma(\sum_j z_j)``` And for `n + 1` dimensional `x` with shape `[N1, ..., Nn, K]`, we define `lbeta(x)[i1, ..., in] = Log(|Beta(x[i1, ..., in, :])|)`. In other words, the last dimension is treated as the `z` vector. Note that if `z = [u, v]`, then `Beta(z) = int_0^1 t^{u-1} (1 - t)^{v-1} dt`, which defines the traditional bivariate beta function. Args: x: A rank `n + 1` `Tensor` with type `float`, or `double`. name: A name for the operation (optional). Returns: The logarithm of `|Beta(x)|` reducing along the last dimension. Raises: ValueError: If `x` is empty with rank one or less. """ with ops.op_scope([x], name): x = ops.convert_to_tensor(x, name='x') x = control_flow_ops.with_dependencies( [check_ops.assert_rank_at_least(x, 1)], x) is_empty = math_ops.equal(0, array_ops.size(x)) def nonempty_lbeta(): last_index = array_ops.size(array_ops.shape(x)) - 1 log_prod_gamma_x = math_ops.reduce_sum( math_ops.lgamma(x), reduction_indices=last_index) sum_x = math_ops.reduce_sum(x, reduction_indices=last_index) log_gamma_sum_x = math_ops.lgamma(sum_x) result = log_prod_gamma_x - log_gamma_sum_x result.set_shape(x.get_shape()[:-1]) return result def empty_lbeta(): # If x is empty, return version with one less dimension. # Can only do this if rank >= 2. assertion = check_ops.assert_rank_at_least(x, 2) with ops.control_dependencies([assertion]): return array_ops.squeeze(x, squeeze_dims=[0]) static_size = x.get_shape().num_elements() if static_size is not None: if static_size > 0: return nonempty_lbeta() else: return empty_lbeta() else: return control_flow_ops.cond(is_empty, empty_lbeta, nonempty_lbeta)
def _forward(self, x): if self.validate_args: is_matrix = check_ops.assert_rank_at_least(x, 2) shape = array_ops.shape(x) is_square = check_ops.assert_equal(shape[-2], shape[-1]) x = control_flow_ops.with_dependencies([is_matrix, is_square], x) # For safety, explicitly zero-out the upper triangular part. x = array_ops.matrix_band_part(x, -1, 0) return math_ops.matmul(x, x, adjoint_b=True)
def __init__(self, alpha, validate_args=False, allow_nan_stats=True, name="Dirichlet"): """Initialize a batch of Dirichlet distributions. Args: alpha: Positive floating point tensor with shape broadcastable to `[N1,..., Nm, k]` `m >= 0`. Defines this as a batch of `N1 x ... x Nm` different `k` class Dirichlet distributions. validate_args: `Boolean`, default `False`. Whether to assert valid values for parameters `alpha` and `x` in `prob` and `log_prob`. If `False`, 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 prefix Ops created by this distribution class. Examples: ```python # Define 1-batch of 2-class Dirichlet distributions, # also known as a Beta distribution. dist = Dirichlet([1.1, 2.0]) # Define a 2-batch of 3-class distributions. dist = Dirichlet([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) ``` """ parameters = locals() parameters.pop("self") with ops.name_scope(name, values=[alpha]) as ns: alpha = ops.convert_to_tensor(alpha, name="alpha") with ops.control_dependencies([ check_ops.assert_positive(alpha), check_ops.assert_rank_at_least(alpha, 1) ] if validate_args else []): self._alpha = array_ops.identity(alpha, name="alpha") self._alpha_sum = math_ops.reduce_sum(alpha, reduction_indices=[-1], keep_dims=False) super(Dirichlet, self).__init__( dtype=self._alpha.dtype, validate_args=validate_args, allow_nan_stats=allow_nan_stats, is_continuous=True, is_reparameterized=False, parameters=parameters, graph_parents=[self._alpha, self._alpha_sum], name=ns)
def __init__(self, alpha, validate_args=False, allow_nan_stats=True, name="Dirichlet"): """Initialize a batch of Dirichlet distributions. Args: alpha: Positive floating point tensor with shape broadcastable to `[N1,..., Nm, k]` `m >= 0`. Defines this as a batch of `N1 x ... x Nm` different `k` class Dirichlet distributions. validate_args: `Boolean`, default `False`. Whether to assert valid values for parameters `alpha` and `x` in `prob` and `log_prob`. If `False`, 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 prefix Ops created by this distribution class. Examples: ```python # Define 1-batch of 2-class Dirichlet distributions, # also known as a Beta distribution. dist = Dirichlet([1.1, 2.0]) # Define a 2-batch of 3-class distributions. dist = Dirichlet([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) ``` """ parameters = locals() parameters.pop("self") with ops.name_scope(name, values=[alpha]) as ns: alpha = ops.convert_to_tensor(alpha, name="alpha") with ops.control_dependencies([ check_ops.assert_positive(alpha), check_ops.assert_rank_at_least(alpha, 1) ] if validate_args else []): self._alpha = array_ops.identity(alpha, name="alpha") self._alpha_sum = math_ops.reduce_sum(alpha, reduction_indices=[-1], keep_dims=False) super(Dirichlet, self).__init__(dtype=self._alpha.dtype, validate_args=validate_args, allow_nan_stats=allow_nan_stats, is_continuous=True, is_reparameterized=False, parameters=parameters, graph_parents=[self._alpha, self._alpha_sum], name=ns)
def __init__(self, alpha, validate_args=True, allow_nan_stats=False, name="Dirichlet"): """Initialize a batch of Dirichlet distributions. Args: alpha: Positive floating point tensor with shape broadcastable to `[N1,..., Nm, k]` `m >= 0`. Defines this as a batch of `N1 x ... x Nm` different `k` class Dirichlet distributions. validate_args: Whether to assert valid values for parameters `alpha` and `x` in `prob` and `log_prob`. If `False`, correct behavior is not guaranteed. allow_nan_stats: Boolean, default `False`. 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 prefix Ops created by this distribution class. Examples: ```python # Define 1-batch of 2-class Dirichlet distributions, # also known as a Beta distribution. dist = Dirichlet([1.1, 2.0]) # Define a 2-batch of 3-class distributions. dist = Dirichlet([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) ``` """ with ops.op_scope([alpha], name): alpha = ops.convert_to_tensor(alpha, name="alpha_before_deps") with ops.control_dependencies([ check_ops.assert_positive(alpha), check_ops.assert_rank_at_least(alpha, 1) ] if validate_args else []): alpha = array_ops.identity(alpha, name="alpha") self._alpha = alpha self._name = name # Used for mean/mode/variance/entropy computations self._alpha_0 = math_ops.reduce_sum(alpha, reduction_indices=[-1], keep_dims=False) self._get_batch_shape = self._alpha_0.get_shape() self._get_event_shape = self._alpha.get_shape().with_rank_at_least( 1)[-1:] self._validate_args = validate_args self._allow_nan_stats = allow_nan_stats
def __init__(self, alpha, validate_args=True, allow_nan_stats=False, name="Dirichlet"): """Initialize a batch of Dirichlet distributions. Args: alpha: Positive floating point tensor with shape broadcastable to `[N1,..., Nm, k]` `m >= 0`. Defines this as a batch of `N1 x ... x Nm` different `k` class Dirichlet distributions. validate_args: Whether to assert valid values for parameters `alpha` and `x` in `prob` and `log_prob`. If `False`, correct behavior is not guaranteed. allow_nan_stats: Boolean, default `False`. 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 prefix Ops created by this distribution class. Examples: ```python # Define 1-batch of 2-class Dirichlet distributions, # also known as a Beta distribution. dist = Dirichlet([1.1, 2.0]) # Define a 2-batch of 3-class distributions. dist = Dirichlet([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) ``` """ with ops.op_scope([alpha], name): alpha = ops.convert_to_tensor(alpha, name="alpha_before_deps") with ops.control_dependencies([ check_ops.assert_positive(alpha), check_ops.assert_rank_at_least( alpha, 1) ] if validate_args else []): alpha = array_ops.identity(alpha, name="alpha") self._alpha = alpha self._name = name # Used for mean/mode/variance/entropy computations self._alpha_0 = math_ops.reduce_sum(alpha, reduction_indices=[-1], keep_dims=False) self._get_batch_shape = self._alpha_0.get_shape() self._get_event_shape = self._alpha.get_shape().with_rank_at_least(1)[-1:] self._validate_args = validate_args self._allow_nan_stats = allow_nan_stats
def _prob(self, x): if self.validate_args: is_vector_check = check_ops.assert_rank_at_least(x, 1) right_vec_space_check = check_ops.assert_equal( self.event_shape_tensor(), array_ops.gather(array_ops.shape(x), array_ops.rank(x) - 1), message= "Argument 'x' not defined in the same space R^k as this distribution") with ops.control_dependencies([is_vector_check]): with ops.control_dependencies([right_vec_space_check]): x = array_ops.identity(x) return math_ops.cast( math_ops.reduce_all(math_ops.abs(x - self.loc) <= self._slack, axis=-1), dtype=self.dtype)
def _maybe_assert_valid_concentration(self, concentration, validate_args): """Checks the validity of the concentration parameter.""" if not validate_args: return concentration return control_flow_ops.with_dependencies([ check_ops.assert_positive( concentration, message="Concentration parameter must be positive."), check_ops.assert_rank_at_least( concentration, 1, message="Concentration parameter must have >=1 dimensions."), check_ops.assert_less( 1, array_ops.shape(concentration)[-1], message="Concentration parameter must have event_size >= 2."), ], concentration)
def _maybe_validate_matrix(a, validate_args): """Checks that input is a `float` matrix.""" assertions = [] if not a.dtype.is_floating: raise TypeError('Input `a` must have `float`-like `dtype` ' '(saw {}).'.format(a.dtype.name)) if a.shape is not None and a.shape.rank is not None: if a.shape.rank < 2: raise ValueError('Input `a` must have at least 2 dimensions ' '(saw: {}).'.format(a.shape.rank)) elif validate_args: assertions.append( check_ops.assert_rank_at_least( a, rank=2, message='Input `a` must have at least 2 dimensions.')) return assertions
def __init__(self, params, labels, event_index, model_hparams=None): del model_hparams del labels del event_index params_shape = array_ops.shape(params) assert_rank = check_ops.assert_rank_at_least( params, 2, data=[params_shape], message='Exponential model params shape must be [batch_size, 1]') with ops.control_dependencies([assert_rank]): # TODO(yuanxue,gafm): Experiment with tf.softplus. self._rate_param = tf.exp(params + INITIAL_LN_RATE) self._distribution = tfp.distributions.Exponential( rate=self._rate_param)
def _check_chol(self, chol): """Verify that `chol` is proper.""" chol = ops.convert_to_tensor(chol, name='chol') if not self.verify_pd: return chol shape = array_ops.shape(chol) rank = array_ops.rank(chol) is_matrix = check_ops.assert_rank_at_least(chol, 2) is_square = check_ops.assert_equal(array_ops.gather(shape, rank - 2), array_ops.gather(shape, rank - 1)) deps = [is_matrix, is_square] deps.append(check_ops.assert_positive(self._diag)) return control_flow_ops.with_dependencies(deps, chol)
def _check_chol(self, chol): """Verify that `chol` is proper.""" chol = ops.convert_to_tensor(chol, name='chol') if not self.verify_pd: return chol shape = array_ops.shape(chol) rank = array_ops.rank(chol) is_matrix = check_ops.assert_rank_at_least(chol, 2) is_square = check_ops.assert_equal( array_ops.gather(shape, rank - 2), array_ops.gather(shape, rank - 1)) deps = [is_matrix, is_square] deps.append(check_ops.assert_positive(self._diag)) return control_flow_ops.with_dependencies(deps, chol)
def _check_logits_final_dim(logits, expected_logits_dimension): """Checks that logits shape is [D0, D1, ... DN, logits_dimension].""" with ops.name_scope(None, 'logits', (logits,)) as scope: logits = math_ops.to_float(logits) logits_shape = array_ops.shape(logits) assert_rank = check_ops.assert_rank_at_least( logits, 2, data=[logits_shape], message='logits shape must be [D0, D1, ... DN, logits_dimension]') with ops.control_dependencies([assert_rank]): static_shape = logits.shape if static_shape.ndims is not None and static_shape[-1] is not None: if static_shape[-1] != expected_logits_dimension: raise ValueError( 'logits shape must be [D0, D1, ... DN, logits_dimension], ' 'got %s.' % (static_shape,)) return logits assert_dimension = check_ops.assert_equal( expected_logits_dimension, logits_shape[-1], data=[logits_shape], message='logits shape must be [D0, D1, ... DN, logits_dimension]') with ops.control_dependencies([assert_dimension]): return array_ops.identity(logits, name=scope)
def _assertions(self, x): if not self.validate_args: return [] shape = array_ops.shape(x) is_matrix = check_ops.assert_rank_at_least( x, 2, message="Input must have rank at least 2.") is_square = check_ops.assert_equal( shape[-2], shape[-1], message="Input must be a square matrix.") above_diagonal = array_ops.matrix_band_part( array_ops.matrix_set_diag( x, array_ops.zeros(shape[:-1], dtype=dtypes.float32)), 0, -1) is_lower_triangular = check_ops.assert_equal( above_diagonal, array_ops.zeros_like(above_diagonal), message="Input must be lower triangular.") # A lower triangular matrix is nonsingular iff all its diagonal entries are # nonzero. diag_part = array_ops.matrix_diag_part(x) is_nonsingular = check_ops.assert_none_equal( diag_part, array_ops.zeros_like(diag_part), message="Input must have all diagonal entries nonzero.") return [is_matrix, is_square, is_lower_triangular, is_nonsingular]
def _lu_solve_assertions(lower_upper, perm, rhs, validate_args): """Returns list of assertions related to `lu_solve` assumptions.""" assertions = lu_reconstruct_assertions(lower_upper, perm, validate_args) message = 'Input `rhs` must have at least 2 dimensions.' if rhs.shape.ndims is not None: if rhs.shape.ndims < 2: raise ValueError(message) elif validate_args: assertions.append( check_ops.assert_rank_at_least(rhs, rank=2, message=message)) message = '`lower_upper.shape[-1]` must equal `rhs.shape[-1]`.' if (lower_upper.shape[-1] is not None and rhs.shape[-2] is not None): if lower_upper.shape[-1] != rhs.shape[-2]: raise ValueError(message) elif validate_args: assertions.append( check_ops.assert_equal(array_ops.shape(lower_upper)[-1], array_ops.shape(rhs)[-2], message=message)) return assertions
def embed_check_categorical_event_shape( categorical_param, name="embed_check_categorical_event_shape"): """Embeds checks that categorical distributions don't have too many classes. A categorical-type distribution is one which, e.g., returns the class label rather than a one-hot encoding. E.g., `Categorical(probs)`. Since distributions output samples in the same dtype as the parameters, we must ensure that casting doesn't lose precision. That is, the `parameter.dtype` implies a maximum number of classes. However, since shape is `int32` and categorical variables are presumed to be indexes into a `Tensor`, we must also ensure that the number of classes is no larger than the largest possible `int32` index, i.e., `2**31-1`. In other words the number of classes, `K`, must satisfy the following condition: ```python K <= min( int(2**31 - 1), # Largest float as an index. { dtypes.float16: int(2**11), # Largest int as a float16. dtypes.float32: int(2**24), dtypes.float64: int(2**53), }.get(categorical_param.dtype.base_dtype, 0)) ``` Args: categorical_param: Floating-point `Tensor` representing parameters of distribution over categories. The rightmost shape is presumed to be the number of categories. name: A name for this operation (optional). Returns: categorical_param: Input `Tensor` with appropriate assertions embedded. Raises: TypeError: if `categorical_param` has an unknown `dtype`. ValueError: if we can statically identify `categorical_param` as being too large (for being closed under int32/float casting). """ with ops.name_scope(name, values=[categorical_param]): x = ops.convert_to_tensor(categorical_param, name="categorical_param") # The size must not exceed both of: # - The largest possible int32 (since categorical values are presumed to be # indexes into a Tensor). # - The largest possible integer exactly representable under the given # floating-point dtype (since we need to cast to/from). # # The chosen floating-point thresholds are 2**(1 + mantissa_bits). # For more details, see: # https://en.wikipedia.org/wiki/Floating-point_arithmetic#Internal_representation x_dtype = x.dtype.base_dtype max_event_size = (_largest_integer_by_dtype(x_dtype) if x_dtype.is_floating else 0) if max_event_size is 0: raise TypeError("Unable to validate size of unrecognized dtype " "({}).".format(x_dtype.name)) try: x_shape_static = x.get_shape().with_rank_at_least(1) except ValueError: raise ValueError("A categorical-distribution parameter must have " "at least 1 dimension.") if x_shape_static[-1].value is not None: event_size = x_shape_static[-1].value if event_size < 2: raise ValueError( "A categorical-distribution parameter must have at " "least 2 events.") if event_size > max_event_size: raise ValueError( "Number of classes exceeds `dtype` precision, i.e., " "{} implies shape ({}) cannot exceed {}.".format( x_dtype.name, event_size, max_event_size)) return x else: event_size = array_ops.shape(x, name="x_shape")[-1] return control_flow_ops.with_dependencies([ check_ops.assert_rank_at_least( x, 1, message=("A categorical-distribution parameter must have " "at least 1 dimension.")), check_ops.assert_greater_equal( array_ops.shape(x)[-1], 2, message=( "A categorical-distribution parameter must have at " "least 2 events.")), check_ops.assert_less_equal( event_size, max_event_size, message="Number of classes exceeds `dtype` precision, " "i.e., {} dtype cannot exceed {} shape.".format( x_dtype.name, max_event_size)), ], x)
def _check_alpha(alpha): """Check alpha for proper shape, values, then return tensor version.""" alpha = ops.convert_to_tensor(alpha, name='alpha_before_deps') return control_flow_ops.with_dependencies( [check_ops.assert_rank_at_least(alpha, 1), check_ops.assert_positive(alpha)], alpha)
def empty_lbeta(): # If x is empty, return version with one less dimension. # Can only do this if rank >= 2. assertion = check_ops.assert_rank_at_least(x, 2) with ops.control_dependencies([assertion]): return array_ops.squeeze(x, squeeze_dims=[0])
def _check_dense_labels_match_logits_and_reshape( labels, logits, expected_labels_dimension): """Checks that labels shape matches logits and reshapes if needed. Consider logits of shape [D0, D1, ... DN, logits_dimension]. Then labels shape must be [D0, D1, ... DN, expected_labels_dimension]. If expected_labels_dimension=1, labels could be [D0, D1, ... DN] and this method reshapes them to [D0, D1, ... DN, 1]. Args: labels: labels Tensor. logits: logits Tensor. expected_labels_dimension: Integer. Returns: Validated and reshaped labels Tensor. Raises: ValueError: If labels is a SparseTensor. ValueError: If labels shape is statically defined and fails validation. OpError: If labels shape is not statically defined and fails validation. """ if labels is None: raise ValueError( 'You must provide a labels Tensor. Given: None. ' 'Suggested troubleshooting steps: Check that your data contain ' 'your label feature. Check that your input_fn properly parses and ' 'returns labels.') with ops.name_scope(None, 'labels', (labels, logits)) as scope: labels = sparse_tensor.convert_to_tensor_or_sparse_tensor(labels) if isinstance(labels, sparse_tensor.SparseTensor): raise ValueError( 'SparseTensor labels are not supported. ' 'labels must be a Tensor of shape [D0, D1, ..., DN, %s], ' 'e.g. [batch_size, %s]. ' 'Suggested Fix (1): Check the label feature in your data. ' 'Each example must contain %s value(s). If not, your choice of label ' 'was probably incorrect. ' 'Suggested Fix (2): In your input_fn, use ' 'tf.sparse_tensor_to_dense() to turn labels into a Tensor.' '' % (expected_labels_dimension, expected_labels_dimension, expected_labels_dimension)) if (labels.shape.ndims is not None and logits.shape.ndims is not None and labels.shape.ndims == logits.shape.ndims - 1): labels = array_ops.expand_dims(labels, -1) labels_shape = array_ops.shape(labels) logits_shape = array_ops.shape(logits) err_msg = ( 'labels shape must be [D0, D1, ... DN, {}]. ' 'Suggested Fix: check your n_classes argument to the estimator ' 'and/or the shape of your label.'.format(expected_labels_dimension)) assert_rank = check_ops.assert_rank_at_least(labels, 2, message=err_msg) with ops.control_dependencies([assert_rank]): static_shape = labels.shape if static_shape.ndims is not None: dim1 = static_shape[-1] if (dim1 is not None) and (dim1 != expected_labels_dimension): raise ValueError( 'Mismatched label shape. ' 'Classifier configured with n_classes=%s. Received %s. ' 'Suggested Fix: check your n_classes argument to the estimator ' 'and/or the shape of your label.' % (expected_labels_dimension, dim1)) expected_labels_shape = array_ops.concat( [logits_shape[:-1], [expected_labels_dimension]], axis=0) assert_dimension = check_ops.assert_equal( expected_labels_shape, labels_shape, message=err_msg, data=['expected_labels_shape: ', expected_labels_shape, 'labels_shape: ', labels_shape]) with ops.control_dependencies([assert_dimension]): return array_ops.identity(labels, name=scope)
def check_dense_labels_match_logits_and_reshape(labels, logits, expected_labels_dimension): """Checks labels shape matches logits, and reshapes if needed. Consider logits of shape [D0, D1, ... DN, logits_dimension]. Then labels shape must be [D0, D1, ... DN, expected_labels_dimension]. If expected_labels_dimension=1, labels could be [D0, D1, ... DN] and this method reshapes them to [D0, D1, ... DN, 1]. Args: labels: labels Tensor. logits: logits Tensor. expected_labels_dimension: Integer. Returns: Validated and reshaped labels Tensor. Raises: ValueError: If labels is a SparseTensor. ValueError: If labels shape is statically defined and fails validation. OpError: If labels shape is not statically defined and fails validation. """ if labels is None: raise ValueError(_LABEL_NONE_ERR_MSG) with ops.name_scope('labels', values=(labels, logits)) as scope: labels = sparse_tensor.convert_to_tensor_or_sparse_tensor(labels) if isinstance(labels, sparse_tensor.SparseTensor): raise ValueError( _SPARSE_LABEL_ERR_MSG.format(expected_labels_dimension, expected_labels_dimension, expected_labels_dimension)) # Eager mode. if context.executing_eagerly(): labels_rank = labels._rank() # pylint: disable=protected-access logits_rank = logits._rank() # pylint: disable=protected-access if (labels_rank is not None and logits_rank is not None and labels_rank == logits_rank - 1): labels = array_ops.expand_dims(labels, -1) labels_rank += 1 labels_shape = labels._shape_tuple() # pylint: disable=protected-access if labels_rank < 2: raise ValueError( 'labels must have rank at least 2. Received rank {}, ' 'shape {}'.format(labels_rank, labels_shape)) if labels_shape[-1] != expected_labels_dimension: raise ValueError( _MISMATCHED_LABEL_DIM_ERR_MSG.format( expected_labels_dimension, labels_shape[-1])) logits_shape = logits._shape_tuple() # pylint: disable=protected-access expected_labels_shape = logits_shape[:-1] + ( expected_labels_dimension, ) if expected_labels_shape != labels_shape: raise ValueError( '{}, expected_labels_shape: {}. labels_shape: {}.'.format( _LABEL_SHAPE_ERR_MSG.format(expected_labels_dimension), expected_labels_shape, labels_shape)) return labels # Graph mode. if (labels.shape.ndims is not None and logits.shape.ndims is not None and labels.shape.ndims == logits.shape.ndims - 1): labels = array_ops.expand_dims(labels, -1) assert_rank = check_ops.assert_rank_at_least( labels, 2, message=_LABEL_SHAPE_ERR_MSG.format(expected_labels_dimension)) with ops.control_dependencies([assert_rank]): static_shape = labels.shape if static_shape.ndims is not None: final_dim = static_shape[-1] if (final_dim is not None) and (final_dim != expected_labels_dimension): raise ValueError( _MISMATCHED_LABEL_DIM_ERR_MSG.format( expected_labels_dimension, final_dim)) logits_shape = array_ops.shape(logits) expected_labels_shape = array_ops.concat( [logits_shape[:-1], [expected_labels_dimension]], axis=0) labels_shape = array_ops.shape(labels) assert_dimension = check_ops.assert_equal( expected_labels_shape, labels_shape, message=_LABEL_SHAPE_ERR_MSG.format(expected_labels_dimension), data=[ 'expected_labels_shape: ', expected_labels_shape, 'labels_shape: ', labels_shape ]) with ops.control_dependencies([assert_dimension]): return array_ops.identity(labels, name=scope)
def __init__(self, loc, atol=None, rtol=None, is_vector=False, validate_args=False, allow_nan_stats=True, name="_BaseDeterministic"): """Initialize a batch of `_BaseDeterministic` distributions. The `atol` and `rtol` parameters allow for some slack in `pmf`, `cdf` computations, e.g. due to floating-point error. ``` pmf(x; loc) = 1, if Abs(x - loc) <= atol + rtol * Abs(loc), = 0, otherwise. ``` Args: loc: Numeric `Tensor`. The point (or batch of points) on which this distribution is supported. atol: Non-negative `Tensor` of same `dtype` as `loc` and broadcastable shape. The absolute tolerance for comparing closeness to `loc`. Default is `0`. rtol: Non-negative `Tensor` of same `dtype` as `loc` and broadcastable shape. The relative tolerance for comparing closeness to `loc`. Default is `0`. is_vector: Python `bool`. If `True`, this is for `VectorDeterministic`, else `Deterministic`. 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 `loc` is a scalar. """ parameters = locals() with ops.name_scope(name, values=[loc, atol, rtol]): loc = ops.convert_to_tensor(loc, name="loc") if is_vector and validate_args: msg = "Argument loc must be at least rank 1." if loc.get_shape().ndims is not None: if loc.get_shape().ndims < 1: raise ValueError(msg) else: loc = control_flow_ops.with_dependencies( [check_ops.assert_rank_at_least(loc, 1, message=msg)], loc) self._loc = loc super(_BaseDeterministic, self).__init__( dtype=self._loc.dtype, reparameterization_type=distribution.NOT_REPARAMETERIZED, validate_args=validate_args, allow_nan_stats=allow_nan_stats, parameters=parameters, graph_parents=[self._loc], name=name) self._atol = self._get_tol(atol) self._rtol = self._get_tol(rtol) # Avoid using the large broadcast with self.loc if possible. if rtol is None: self._slack = self.atol else: self._slack = self.atol + self.rtol * math_ops.abs(self.loc)
def _forward_log_det_jacobian(self, x): # Let Y be a symmetric, positive definite matrix and write: # Y = X X.T # where X is lower-triangular. # # Observe that, # dY[i,j]/dX[a,b] # = d/dX[a,b] { X[i,:] X[j,:] } # = sum_{d=1}^p { I[i=a] I[d=b] X[j,d] + I[j=a] I[d=b] X[i,d] } # # To compute the Jacobian dX/dY we must represent X,Y as vectors. Since Y is # symmetric and X is lower-triangular, we need vectors of dimension: # d = p (p + 1) / 2 # where X, Y are p x p matrices, p > 0. We use a row-major mapping, i.e., # k = { i (i + 1) / 2 + j i>=j # { undef i<j # and assume zero-based indexes. When k is undef, the element is dropped. # Example: # j k # 0 1 2 3 / # 0 [ 0 . . . ] # i 1 [ 1 2 . . ] # 2 [ 3 4 5 . ] # 3 [ 6 7 8 9 ] # Write vec[.] to indicate transforming a matrix to vector via k(i,j). (With # slight abuse: k(i,j)=undef means the element is dropped.) # # We now show d vec[Y] / d vec[X] is lower triangular. Assuming both are # defined, observe that k(i,j) < k(a,b) iff (1) i<a or (2) i=a and j<b. # In both cases dvec[Y]/dvec[X]@[k(i,j),k(a,b)] = 0 since: # (1) j<=i<a thus i,j!=a. # (2) i=a>j thus i,j!=a. # # Since the Jacobian is lower-triangular, we need only compute the product # of diagonal elements: # d vec[Y] / d vec[X] @[k(i,j), k(i,j)] # = X[j,j] + I[i=j] X[i,j] # = 2 X[j,j]. # Since there is a 2 X[j,j] term for every lower-triangular element of X we # conclude: # |Jac(d vec[Y]/d vec[X])| = 2^p prod_{j=0}^{p-1} X[j,j]^{p-j}. if self._static_event_ndims == 0: if self.validate_args: is_positive = check_ops.assert_positive( x, message="All elements must be positive.") x = control_flow_ops.with_dependencies([is_positive], x) return np.log(2.) + math_ops.log(x) diag = array_ops.matrix_diag_part(x) # We now ensure diag is columnar. Eg, if `diag = [1, 2, 3]` then the output # is `[[1], [2], [3]]` and if `diag = [[1, 2, 3], [4, 5, 6]]` then the # output is unchanged. diag = self._make_columnar(diag) if self.validate_args: is_matrix = check_ops.assert_rank_at_least( x, 2, message="Input must be a (batch of) matrix.") shape = array_ops.shape(x) is_square = check_ops.assert_equal( shape[-2], shape[-1], message="Input must be a (batch of) square matrix.") # Assuming lower-triangular means we only need check diag>0. is_positive_definite = check_ops.assert_positive( diag, message="Input must be positive definite.") x = control_flow_ops.with_dependencies( [is_matrix, is_square, is_positive_definite], x) # Create a vector equal to: [p, p-1, ..., 2, 1]. if x.get_shape().ndims is None or x.get_shape()[-1].value is None: p_int = array_ops.shape(x)[-1] p_float = math_ops.cast(p_int, dtype=x.dtype) else: p_int = x.get_shape()[-1].value p_float = np.array(p_int, dtype=x.dtype.as_numpy_dtype) exponents = math_ops.linspace(p_float, 1., p_int) sum_weighted_log_diag = array_ops.squeeze( math_ops.matmul(math_ops.log(diag), exponents[..., array_ops.newaxis]), squeeze_dims=-1) fldj = p_float * np.log(2.) + sum_weighted_log_diag return fldj
def test_rank_zero_tensor_doesnt_raise_if_rank_just_right_static_rank(self): tensor = constant_op.constant(1, name="my_tensor") desired_rank = 0 with ops.control_dependencies( [check_ops.assert_rank_at_least(tensor, desired_rank)]): self.evaluate(array_ops.identity(tensor))
def embed_check_categorical_event_shape( categorical_param, name="embed_check_categorical_event_shape"): """Embeds checks that categorical distributions don't have too many classes. A categorical-type distribution is one which, e.g., returns the class label rather than a one-hot encoding. E.g., `Categorical(probs)`. Since distributions output samples in the same dtype as the parameters, we must ensure that casting doesn't lose precision. That is, the `parameter.dtype` implies a maximum number of classes. However, since shape is `int32` and categorical variables are presumed to be indexes into a `Tensor`, we must also ensure that the number of classes is no larger than the largest possible `int32` index, i.e., `2**31-1`. In other words the number of classes, `K`, must satisfy the following condition: ```python K <= min( int(2**31 - 1), # Largest float as an index. { dtypes.float16: int(2**11), # Largest int as a float16. dtypes.float32: int(2**24), dtypes.float64: int(2**53), }.get(categorical_param.dtype.base_dtype, 0)) ``` Args: categorical_param: Floating-point `Tensor` representing parameters of distribution over categories. The rightmost shape is presumed to be the number of categories. name: A name for this operation (optional). Returns: categorical_param: Input `Tensor` with appropriate assertions embedded. Raises: TypeError: if `categorical_param` has an unknown `dtype`. ValueError: if we can statically identify `categorical_param` as being too large (for being closed under int32/float casting). """ with ops.name_scope(name, values=[categorical_param]): x = ops.convert_to_tensor(categorical_param, name="categorical_param") # The size must not exceed both of: # - The largest possible int32 (since categorical values are presumed to be # indexes into a Tensor). # - The largest possible integer exactly representable under the given # floating-point dtype (since we need to cast to/from). # # The chosen floating-point thresholds are 2**(1 + mantissa_bits). # For more details, see: # https://en.wikipedia.org/wiki/Floating-point_arithmetic#Internal_representation x_dtype = x.dtype.base_dtype max_event_size = (_largest_integer_by_dtype(x_dtype) if x_dtype.is_floating else 0) if max_event_size is 0: raise TypeError("Unable to validate size of unrecognized dtype " "({}).".format(x_dtype.name)) try: x_shape_static = x.get_shape().with_rank_at_least(1) except ValueError: raise ValueError("A categorical-distribution parameter must have " "at least 1 dimension.") if x_shape_static[-1].value is not None: event_size = x_shape_static[-1].value if event_size < 2: raise ValueError("A categorical-distribution parameter must have at " "least 2 events.") if event_size > max_event_size: raise ValueError( "Number of classes exceeds `dtype` precision, i.e., " "{} implies shape ({}) cannot exceed {}.".format( x_dtype.name, event_size, max_event_size)) return x else: event_size = array_ops.shape(x, name="x_shape")[-1] return control_flow_ops.with_dependencies([ check_ops.assert_rank_at_least( x, 1, message=("A categorical-distribution parameter must have " "at least 1 dimension.")), check_ops.assert_greater_equal( array_ops.shape(x)[-1], 2, message=("A categorical-distribution parameter must have at " "least 2 events.")), check_ops.assert_less_equal( event_size, max_event_size, message="Number of classes exceeds `dtype` precision, " "i.e., {} dtype cannot exceed {} shape.".format( x_dtype.name, max_event_size)), ], x)
def test_rank_one_ten_doesnt_raise_raise_if_rank_too_large_static_rank(self): tensor = constant_op.constant([1, 2], name="my_tensor") desired_rank = 0 with ops.control_dependencies( [check_ops.assert_rank_at_least(tensor, desired_rank)]): self.evaluate(array_ops.identity(tensor))