def test_doesnt_raise_when_equal(self): with self.test_session(): small = constant_op.constant([1, 2], name="small") with ops.control_dependencies( [check_ops.assert_greater_equal(small, small)]): out = array_ops.identity(small) out.eval()
def _single_batch_sampler(self, sampler): # Enforce that there are at least as many data points as centers # remaining. This gives the provided sampler the chance to select all # remaining centers from a single batch. with ops.control_dependencies( [check_ops.assert_greater_equal(self._num_data, self._num_remaining)]): return sampler()
def _check_valid_event_ndims(self, min_event_ndims, event_ndims): """Check whether event_ndims is atleast min_event_ndims.""" event_ndims = ops.convert_to_tensor(event_ndims, name="event_ndims") event_ndims_ = tensor_util.constant_value(event_ndims) assertions = [] if not event_ndims.dtype.is_integer: raise ValueError("Expected integer dtype, got dtype {}".format( event_ndims.dtype)) if event_ndims_ is not None: if event_ndims.shape.ndims != 0: raise ValueError("Expected scalar event_ndims, got shape {}".format( event_ndims.shape)) if min_event_ndims > event_ndims_: raise ValueError("event_ndims ({}) must be larger than " "min_event_ndims ({})".format( event_ndims_, min_event_ndims)) elif self.validate_args: assertions += [ check_ops.assert_greater_equal(event_ndims, min_event_ndims)] if event_ndims.shape.is_fully_defined(): if event_ndims.shape.ndims != 0: raise ValueError("Expected scalar shape, got ndims {}".format( event_ndims.shape.ndims)) elif self.validate_args: assertions += [ check_ops.assert_rank(event_ndims, 0, message="Expected scalar.")] return assertions
def test_doesnt_raise_when_both_empty(self): larry = constant_op.constant([]) curly = constant_op.constant([]) with ops.control_dependencies( [check_ops.assert_greater_equal(larry, curly)]): out = array_ops.identity(larry) self.evaluate(out)
def test_doesnt_raise_when_greater_equal_and_broadcastable_shapes(self): small = constant_op.constant([1], name="small") big = constant_op.constant([3, 1], name="big") with ops.control_dependencies( [check_ops.assert_greater_equal(big, small)]): out = array_ops.identity(small) self.evaluate(out)
def test_raises_when_less(self): small = constant_op.constant([1, 2], name="small") big = constant_op.constant([3, 4], name="big") with self.assertRaisesOpError("fail"): with ops.control_dependencies( [check_ops.assert_greater_equal( small, big, message="fail")]): out = array_ops.identity(small) self.evaluate(out)
def test_raises_when_less_equal_but_non_broadcastable_shapes(self): with self.test_session(): small = constant_op.constant([1, 1, 1], name="big") big = constant_op.constant([3, 1], name="small") with self.assertRaisesRegexp(ValueError, "Dimensions must be equal"): with ops.control_dependencies( [check_ops.assert_greater_equal(big, small)]): out = array_ops.identity(small) out.eval()
def _validate_aux_loss_weight(aux_loss_weight, name='aux_loss_weight'): if isinstance(aux_loss_weight, ops.Tensor): aux_loss_weight.shape.assert_is_compatible_with([]) with ops.control_dependencies( [check_ops.assert_greater_equal(aux_loss_weight, 0.0)]): aux_loss_weight = array_ops.identity(aux_loss_weight) elif aux_loss_weight is not None and aux_loss_weight < 0: raise ValueError('`%s` must be greater than 0. Instead, was %s' % (name, aux_loss_weight)) return aux_loss_weight
def check(t): samples_batch_shape = array_ops.shape(samples)[1:] broadcasted_batch_shape = array_ops.broadcast_dynamic_shape( samples_batch_shape, array_ops.shape(t)) # This rank check ensures that I don't get a wrong answer from the # _shapes_ broadcasting against each other. samples_batch_ndims = array_ops.size(samples_batch_shape) ge = check_ops.assert_greater_equal( samples_batch_ndims, array_ops.rank(t)) eq = check_ops.assert_equal(samples_batch_shape, broadcasted_batch_shape) return ge, eq
def test_raises_when_less_equal_but_non_broadcastable_shapes(self): small = constant_op.constant([1, 1, 1], name="big") big = constant_op.constant([3, 1], name="small") # The exception in eager and non-eager mode is different because # eager mode relies on shape check done as part of the C++ op, while # graph mode does shape checks when creating the `Operation` instance. with self.assertRaisesRegexp( (errors.InvalidArgumentError, ValueError), (r"Incompatible shapes: \[2\] vs. \[3\]|" r"Dimensions must be equal, but are 2 and 3")): with ops.control_dependencies( [check_ops.assert_greater_equal(big, small)]): out = array_ops.identity(small) self.evaluate(out)
def _check_valid_event_ndims(self, min_event_ndims, event_ndims): """Check whether event_ndims is atleast min_event_ndims.""" assert_static(min_event_ndims) event_ndims_ = get_static_value(event_ndims, np.int32) assertions = [] if event_ndims_ is not None: if min_event_ndims > event_ndims_: raise ValueError("event_ndims ({}) must be larger than " "min_event_ndims ({})".format( event_ndims_, min_event_ndims)) elif self.validate_args: assertions += [ check_ops.assert_greater_equal(event_ndims, min_event_ndims)] return assertions
def _check_valid_event_ndims(self, min_event_ndims, event_ndims): """Check whether event_ndims is atleast min_event_ndims.""" min_event_ndims_ = (min_event_ndims if isinstance(min_event_ndims, int) else tensor_util.constant_value(min_event_ndims)) event_ndims_ = (event_ndims if isinstance(event_ndims, int) else tensor_util.constant_value(event_ndims)) if min_event_ndims_ is not None and event_ndims_ is not None: if min_event_ndims_ > event_ndims_: raise ValueError("event_ndims ({}) must be larger than " "min_event_ndims ({})".format( event_ndims_, min_event_ndims_)) return [] if self.validate_args: return [check_ops.assert_greater_equal(event_ndims, min_event_ndims)] return []
def _maybe_check_valid_shape(self, shape, validate_args): """Check that a shape Tensor is int-type and otherwise sane.""" if not shape.dtype.is_integer: raise TypeError("{} dtype ({}) should be `int`-like.".format( shape, shape.dtype.name)) assertions = [] ndims = array_ops.rank(shape) ndims_ = tensor_util.constant_value(ndims) if ndims_ is not None and ndims_ > 1: raise ValueError("`{}` rank ({}) should be <= 1.".format( shape, ndims_)) elif validate_args: assertions.append(check_ops.assert_less_equal( ndims, 1, message="`{}` rank should be <= 1.".format(shape))) shape_ = tensor_util.constant_value_as_shape(shape) if shape_.is_fully_defined(): es = np.int32(shape_.as_list()) if sum(es == -1) > 1: raise ValueError( "`{}` must have at most one `-1` (given {})" .format(shape, es)) if np.any(es < -1): raise ValueError( "`{}` elements must be either positive integers or `-1`" "(given {})." .format(shape, es)) elif validate_args: assertions.extend([ check_ops.assert_less_equal( math_ops.reduce_sum( math_ops.cast(math_ops.equal(shape, -1), dtypes.int32)), 1, message="`{}` elements must have at most one `-1`." .format(shape)), check_ops.assert_greater_equal( shape, -1, message="`{}` elements must be either positive integers or `-1`." .format(shape)), ]) return assertions
def _minimum_mean(samples, envelope, low, name=None): """Returns a stochastic lower bound on the mean of a scalar distribution. The idea is that if the true CDF is within an `eps`-envelope of the empirical CDF of the samples, and the support is bounded below, then the mean is bounded below as well. In symbols, ```none sup_x(|F_n(x) - F(x)|) < eps ``` The 0th dimension of `samples` is interpreted as independent and identically distributed samples. The remaining dimensions are broadcast together with `envelope` and `low`, and operated on separately. Args: samples: Floating-point tensor of samples from the distribution(s) of interest. Entries are assumed IID across the 0th dimension. The other dimensions must broadcast with `envelope` and `low`. envelope: Floating-point tensor of sizes of admissible CDF envelopes (i.e., the `eps` above). low: Floating-point tensor of lower bounds on the distributions' supports. name: A name for this operation (optional). Returns: bound: Floating-point tensor of lower bounds on the true means. Raises: InvalidArgumentError: If some `sample` is found to be smaller than the corresponding `low`. """ with ops.name_scope(name, "minimum_mean", [samples, envelope, low]): samples = ops.convert_to_tensor(samples, name="samples") envelope = ops.convert_to_tensor(envelope, name="envelope") low = ops.convert_to_tensor(low, name="low") xmin = math_ops.reduce_min(samples, axis=[-1]) msg = "Given sample minimum value falls below expectations" check_op = check_ops.assert_greater_equal(xmin, low, message=msg) with ops.control_dependencies([check_op]): return - _do_maximum_mean(-samples, envelope, -low)
def _validate_sample_arg(self, x): """Helper which validates sample arg, e.g., input to `log_prob`.""" with ops.name_scope(name="validate_sample_arg", values=[x]): x_ndims = (array_ops.rank(x) if x.shape.ndims is None else x.shape.ndims) event_ndims = (array_ops.size(self.event_shape_tensor()) if self.event_shape.ndims is None else self.event_shape.ndims) batch_ndims = ( array_ops.size(self._batch_shape_unexpanded) if self.batch_shape.ndims is None else self.batch_shape.ndims) expected_batch_event_ndims = batch_ndims + event_ndims if (isinstance(x_ndims, int) and isinstance(expected_batch_event_ndims, int)): if x_ndims < expected_batch_event_ndims: raise NotImplementedError( "Broadcasting is not supported; too few batch and event dims " "(expected at least {}, saw {}).".format( expected_batch_event_ndims, x_ndims)) ndims_assertion = [] elif self.validate_args: ndims_assertion = [ check_ops.assert_greater_equal( x_ndims, expected_batch_event_ndims, message=("Broadcasting is not supported; too few " "batch and event dims."), name="assert_batch_and_event_ndims_large_enough"), ] if (self.batch_shape.is_fully_defined() and self.event_shape.is_fully_defined()): expected_batch_event_shape = np.int32(self.batch_shape.concatenate( self.event_shape).as_list()) else: expected_batch_event_shape = array_ops.concat([ self.batch_shape_tensor(), self.event_shape_tensor(), ], axis=0) sample_ndims = x_ndims - expected_batch_event_ndims if isinstance(sample_ndims, int): sample_ndims = max(sample_ndims, 0) if (isinstance(sample_ndims, int) and x.shape[sample_ndims:].is_fully_defined()): actual_batch_event_shape = np.int32(x.shape[sample_ndims:].as_list()) else: sample_ndims = math_ops.maximum(sample_ndims, 0) actual_batch_event_shape = array_ops.shape(x)[sample_ndims:] if (isinstance(expected_batch_event_shape, np.ndarray) and isinstance(actual_batch_event_shape, np.ndarray)): if any(expected_batch_event_shape != actual_batch_event_shape): raise NotImplementedError("Broadcasting is not supported; " "unexpected batch and event shape " "(expected {}, saw {}).".format( expected_batch_event_shape, actual_batch_event_shape)) # We need to set the final runtime-assertions to `ndims_assertion` since # its possible this assertion was created. We could add a condition to # only do so if `self.validate_args == True`, however this is redundant # as `ndims_assertion` already encodes this information. runtime_assertions = ndims_assertion elif self.validate_args: # We need to make the `ndims_assertion` a control dep because otherwise # TF itself might raise an exception owing to this assertion being # ill-defined, ie, one cannot even compare different rank Tensors. with ops.control_dependencies(ndims_assertion): shape_assertion = check_ops.assert_equal( expected_batch_event_shape, actual_batch_event_shape, message=("Broadcasting is not supported; " "unexpected batch and event shape."), name="assert_batch_and_event_shape_same") runtime_assertions = [shape_assertion] else: runtime_assertions = [] return runtime_assertions
def update_state(self, y_true, y_pred, sample_weight=None): # Cast inputs y_pred = tf.convert_to_tensor(y_pred) y_true = tf.cast(y_true, dtype=y_pred.dtype) # Transform inputs [y_pred, y_true ], _ = metrics_utils.ragged_assert_compatible_and_get_flat_values( [y_pred, y_true], sample_weight) # Get threshold properties if isinstance(self.thresholds, list): num_thresholds = len(self.thresholds) else: num_thresholds = len(list(self.thresholds)) # Check input values and adjust shapes with ops.control_dependencies([ check_ops.assert_greater_equal( y_pred, tf.cast(0.0, dtype=y_pred.dtype), message='predictions must be >= 0'), check_ops.assert_less_equal(y_pred, tf.cast(1.0, dtype=y_pred.dtype), message='predictions must be <= 1') ]): if sample_weight is None: y_pred, y_true = tf_losses_utils.squeeze_or_expand_dimensions( y_pred, y_true) else: y_pred, y_true, sample_weight = ( tf_losses_utils.squeeze_or_expand_dimensions( y_pred, y_true, sample_weight=sample_weight)) # Check shape compatibility y_pred.shape.assert_is_compatible_with(y_true.shape) # Check if num_classes corresponds to y_pred if self.average != 'micro': tf.debugging.assert_shapes( shapes=[(y_pred, (..., self.num_classes))], data=y_pred, summarize=10, message='num_classes must correspond to the prediction') # Filter top k if self.top_k is not None: y_pred = metrics_utils._filter_top_k(y_pred, self.top_k) # Select class id if self.class_id is not None: y_true = y_true[..., self.class_id] y_pred = y_pred[..., self.class_id] # Get prediction shape pred_shape = tf.shape(y_pred) num_predictions = pred_shape[0] # Set label shapes if y_pred.shape.ndims == 1: num_labels = 1 else: num_labels = K.prod(pred_shape[1:], axis=0) # Flatten predicitons and labels predictions_extra_dim = tf.reshape(y_pred, [1, -1]) labels_extra_dim = tf.reshape(tf.cast(y_true, dtype=tf.bool), [1, -1]) # Tile the thresholds for every prediction thresh_pretile_shape = [num_thresholds, -1] thresh_tiles = [1, num_predictions * num_labels] data_tiles = [num_thresholds, 1] thresh_tiled = tf.tile( tf.reshape(tf.constant(self.thresholds, dtype=y_pred.dtype), thresh_pretile_shape), tf.stack(thresh_tiles)) # Tile the predictions for every threshold preds_tiled = tf.tile(predictions_extra_dim, data_tiles) # Compare predictions and threshold pred_is_pos = tf.greater(preds_tiled, thresh_tiled) # Tile labels by number of thresholds label_is_pos = tf.tile(labels_extra_dim, data_tiles) # Set sample weights if sample_weight is not None: sample_weight = weights_broadcast_ops.broadcast_weights( tf.cast(sample_weight, dtype=y_pred.dtype), y_pred) weights_tiled = tf.tile(tf.reshape(sample_weight, thresh_tiles), data_tiles) else: weights_tiled = None def _weighted_assign_add(label, pred, weights, var): label_and_pred = tf.cast(tf.logical_and(label, pred), dtype=y_pred.dtype) if weights is not None: label_and_pred *= weights if self.average != 'micro': label_and_pred = tf.reshape(label_and_pred, shape=[-1, self.num_classes]) return var.assign_add(tf.reduce_sum(label_and_pred, self.axis)) # Set return value update_ops = [] # Update true positives update_ops.append( _weighted_assign_add(label_is_pos, pred_is_pos, weights_tiled, self.true_positives)) # Update false negatives pred_is_neg = tf.logical_not(pred_is_pos) update_ops.append( _weighted_assign_add(label_is_pos, pred_is_neg, weights_tiled, self.false_negatives)) # Update false positives label_is_neg = tf.logical_not(label_is_pos) update_ops.append( _weighted_assign_add(label_is_neg, pred_is_pos, weights_tiled, self.false_positives)) return tf.group(update_ops)
def assert_true_mean_in_interval_by_dkwm( samples, low, high, expected_low, expected_high, false_fail_rate=1e-6, name=None): """Asserts the mean of the given distribution is in the given interval. More precisely, fails if there is enough evidence (using the [Dvoretzky-Kiefer-Wolfowitz-Massart inequality] (https://en.wikipedia.org/wiki/CDF-based_nonparametric_confidence_interval)) that the mean of the distribution from which the given samples are drawn is _outside_ the given interval with statistical significance `false_fail_rate` or stronger, otherwise passes. If you also want to check that you are gathering enough evidence that a pass is not spurious, see `min_num_samples_for_dkwm_mean_test` and `min_discrepancy_of_true_means_detectable_by_dkwm`. Note that `false_fail_rate` is a total false failure rate for all the assertions in the batch. As such, if the batch is nontrivial, the assertion will insist on stronger evidence to fail any one member. Args: samples: Floating-point `Tensor` of samples from the distribution(s) of interest. Entries are assumed IID across the 0th dimension. The other dimensions must broadcast with `low` and `high`. The support is bounded: `low <= samples <= high`. low: Floating-point `Tensor` of lower bounds on the distributions' supports. high: Floating-point `Tensor` of upper bounds on the distributions' supports. expected_low: Floating-point `Tensor` of lower bounds on the expected true means. expected_high: Floating-point `Tensor` of upper bounds on the expected true means. false_fail_rate: *Scalar* floating-point `Tensor` admissible total rate of mistakes. name: A name for this operation (optional). Returns: check: Op that raises `InvalidArgumentError` if any expected mean interval does not overlap with the corresponding confidence interval. """ with ops.name_scope( name, "assert_true_mean_in_interval_by_dkwm", [samples, low, high, expected_low, expected_high, false_fail_rate]): samples = ops.convert_to_tensor(samples, name="samples") low = ops.convert_to_tensor(low, name="low") high = ops.convert_to_tensor(high, name="high") expected_low = ops.convert_to_tensor(expected_low, name="expected_low") expected_high = ops.convert_to_tensor(expected_high, name="expected_high") false_fail_rate = ops.convert_to_tensor( false_fail_rate, name="false_fail_rate") samples = _check_shape_dominates( samples, [low, high, expected_low, expected_high]) min_mean, max_mean = true_mean_confidence_interval_by_dkwm( samples, low, high, false_fail_rate) # Assert that the interval [min_mean, max_mean] intersects the # interval [expected_low, expected_high]. This is true if # max_mean >= expected_low and min_mean <= expected_high. # By DeMorgan's law, that's also equivalent to # not (max_mean < expected_low or min_mean > expected_high), # which is a way of saying the two intervals are not disjoint. check_confidence_interval_can_intersect = check_ops.assert_greater_equal( max_mean, expected_low, message="Confidence interval does not " "intersect: true mean smaller than expected") with ops.control_dependencies([check_confidence_interval_can_intersect]): return check_ops.assert_less_equal( min_mean, expected_high, message="Confidence interval does not " "intersect: true mean greater than expected")
def assert_true_mean_equal_by_dkwm_two_sample( samples1, low1, high1, samples2, low2, high2, false_fail_rate=1e-6, name=None): """Asserts the means of the given distributions are equal. More precisely, fails if there is enough evidence (using the [Dvoretzky-Kiefer-Wolfowitz-Massart inequality] (https://en.wikipedia.org/wiki/CDF-based_nonparametric_confidence_interval)) that the means of the distributions from which the given samples are drawn are _not_ equal with statistical significance `false_fail_rate` or stronger, otherwise passes. If you also want to check that you are gathering enough evidence that a pass is not spurious, see `min_num_samples_for_dkwm_mean_two_sample_test` and `min_discrepancy_of_true_means_detectable_by_dkwm_two_sample`. Note that `false_fail_rate` is a total false failure rate for all the assertions in the batch. As such, if the batch is nontrivial, the assertion will insist on stronger evidence to fail any one member. Args: samples1: Floating-point tensor of samples from the distribution(s) A. Entries are assumed IID across the 0th dimension. The other dimensions must broadcast with `low1`, `high1`, `low2`, and `high2`. low1: Floating-point tensor of lower bounds on the supports of the distributions A. high1: Floating-point tensor of upper bounds on the supports of the distributions A. samples2: Floating-point tensor of samples from the distribution(s) B. Entries are assumed IID across the 0th dimension. The other dimensions must broadcast with `low1`, `high1`, `low2`, and `high2`. low2: Floating-point tensor of lower bounds on the supports of the distributions B. high2: Floating-point tensor of upper bounds on the supports of the distributions B. false_fail_rate: *Scalar* admissible total rate of mistakes. name: A name for this operation (optional). Returns: check: Op that raises `InvalidArgumentError` if any pair of confidence intervals true for corresponding true means do not overlap. """ with ops.name_scope( name, "assert_true_mean_equal_by_dkwm_two_sample", [samples1, low1, high1, samples2, low2, high2, false_fail_rate]): samples1 = ops.convert_to_tensor(samples1, name="samples1") low1 = ops.convert_to_tensor(low1, name="low1") high1 = ops.convert_to_tensor(high1, name="high1") samples2 = ops.convert_to_tensor(samples2, name="samples2") low2 = ops.convert_to_tensor(low2, name="low2") high2 = ops.convert_to_tensor(high2, name="high2") false_fail_rate = ops.convert_to_tensor( false_fail_rate, name="false_fail_rate") samples1 = _check_shape_dominates(samples1, [low1, high1]) samples2 = _check_shape_dominates(samples2, [low2, high2]) compatible_samples = check_ops.assert_equal( array_ops.shape(samples1)[1:], array_ops.shape(samples2)[1:]) with ops.control_dependencies([compatible_samples]): # Could in principle play games with cleverly allocating # significance instead of the even split below. It may be possible # to get tighter intervals, in order to obtain a higher power test. # Any allocation strategy that depends only on the support bounds # and sample counts should be valid; however, because the intervals # scale as O(-log(false_fail_rate)), there doesn't seem to be much # room to win. min_mean_1, max_mean_1 = true_mean_confidence_interval_by_dkwm( samples1, low1, high1, false_fail_rate / 2.) min_mean_2, max_mean_2 = true_mean_confidence_interval_by_dkwm( samples2, low2, high2, false_fail_rate / 2.) # I want to assert # not (max_mean_1 < min_mean_2 or min_mean_1 > max_mean_2), # but I think I only have and-combination of asserts, so use DeMorgan. check_confidence_intervals_can_intersect = check_ops.assert_greater_equal( max_mean_1, min_mean_2, message="Confidence intervals do not " "intersect: samples1 has a smaller mean than samples2") with ops.control_dependencies([check_confidence_intervals_can_intersect]): return check_ops.assert_less_equal( min_mean_1, max_mean_2, message="Confidence intervals do not " "intersect: samples2 has a smaller mean than samples1")
def update_confusion_matrix_variables(variables_to_update, y_true, y_pred, thresholds, top_k=None, class_id=None, sample_weight=None): """Returns op to update the given confusion matrix variables. For every pair of values in y_true and y_pred: true_positive: y_true == True and y_pred > thresholds false_negatives: y_true == True and y_pred <= thresholds true_negatives: y_true == False and y_pred <= thresholds false_positive: y_true == False and y_pred > thresholds The results will be weighted and added together. When multiple thresholds are provided, we will repeat the same for every threshold. For estimation of these metrics over a stream of data, the function creates an `update_op` operation that updates the given variables. If `sample_weight` is `None`, weights default to 1. Use weights of 0 to mask values. Args: variables_to_update: Dictionary with 'tp', 'fn', 'tn', 'fp' as valid keys and corresponding variables to update as values. y_true: A `Tensor` whose shape matches `y_pred`. Will be cast to `bool`. y_pred: A floating point `Tensor` of arbitrary shape and whose values are in the range `[0, 1]`. thresholds: A float value or a python list or tuple of float thresholds in `[0, 1]`, or NEG_INF (used when top_k is set). top_k: Optional int, indicates that the positive labels should be limited to the top k predictions. class_id: Optional int, limits the prediction and labels to the class specified by this argument. sample_weight: Optional `Tensor` whose rank is either 0, or the same rank as `y_true`, and must be broadcastable to `y_true` (i.e., all dimensions must be either `1`, or the same as the corresponding `y_true` dimension). Returns: Update op. Raises: ValueError: If `y_pred` and `y_true` have mismatched shapes, or if `sample_weight` is not `None` and its shape doesn't match `y_pred`, or if `variables_to_update` contains invalid keys. """ if variables_to_update is None: return y_true = math_ops.cast(y_true, dtype=dtypes.float32) y_pred = math_ops.cast(y_pred, dtype=dtypes.float32) y_pred.shape.assert_is_compatible_with(y_true.shape) if not any( key for key in variables_to_update if key in list(ConfusionMatrix)): raise ValueError( 'Please provide at least one valid confusion matrix ' 'variable to update. Valid variable key options are: "{}". ' 'Received: "{}"'.format( list(ConfusionMatrix), variables_to_update.keys())) invalid_keys = [ key for key in variables_to_update if key not in list(ConfusionMatrix) ] if invalid_keys: raise ValueError( 'Invalid keys: {}. Valid variable key options are: "{}"'.format( invalid_keys, list(ConfusionMatrix))) with ops.control_dependencies([ check_ops.assert_greater_equal( y_pred, math_ops.cast(0.0, dtype=y_pred.dtype), message='predictions must be >= 0'), check_ops.assert_less_equal( y_pred, math_ops.cast(1.0, dtype=y_pred.dtype), message='predictions must be <= 1') ]): y_pred, y_true, sample_weight = squeeze_or_expand_dimensions( y_pred, y_true, sample_weight) if top_k is not None: y_pred = _filter_top_k(y_pred, top_k) if class_id is not None: y_true = y_true[..., class_id] y_pred = y_pred[..., class_id] thresholds = to_list(thresholds) num_thresholds = len(thresholds) num_predictions = array_ops.size(y_pred) # Reshape predictions and labels. predictions_2d = array_ops.reshape(y_pred, [1, -1]) labels_2d = array_ops.reshape( math_ops.cast(y_true, dtype=dtypes.bool), [1, -1]) # Tile the thresholds for every prediction. thresh_tiled = array_ops.tile( array_ops.expand_dims(array_ops.constant(thresholds), 1), array_ops.stack([1, num_predictions])) # Tile the predictions for every threshold. preds_tiled = array_ops.tile(predictions_2d, [num_thresholds, 1]) # Compare predictions and threshold. pred_is_pos = math_ops.greater(preds_tiled, thresh_tiled) # Tile labels by number of thresholds label_is_pos = array_ops.tile(labels_2d, [num_thresholds, 1]) if sample_weight is not None: weights = weights_broadcast_ops.broadcast_weights( math_ops.cast(sample_weight, dtype=dtypes.float32), y_pred) weights_tiled = array_ops.tile( array_ops.reshape(weights, [1, -1]), [num_thresholds, 1]) else: weights_tiled = None update_ops = [] def weighted_assign_add(label, pred, weights, var): label_and_pred = math_ops.cast( math_ops.logical_and(label, pred), dtype=dtypes.float32) if weights is not None: label_and_pred *= weights return var.assign_add(math_ops.reduce_sum(label_and_pred, 1)) loop_vars = { ConfusionMatrix.TRUE_POSITIVES: (label_is_pos, pred_is_pos), } update_tn = ConfusionMatrix.TRUE_NEGATIVES in variables_to_update update_fp = ConfusionMatrix.FALSE_POSITIVES in variables_to_update update_fn = ConfusionMatrix.FALSE_NEGATIVES in variables_to_update if update_fn or update_tn: pred_is_neg = math_ops.logical_not(pred_is_pos) loop_vars[ConfusionMatrix.FALSE_NEGATIVES] = (label_is_pos, pred_is_neg) if update_fp or update_tn: label_is_neg = math_ops.logical_not(label_is_pos) loop_vars[ConfusionMatrix.FALSE_POSITIVES] = (label_is_neg, pred_is_pos) if update_tn: loop_vars[ConfusionMatrix.TRUE_NEGATIVES] = (label_is_neg, pred_is_neg) for matrix_cond, (label, pred) in loop_vars.items(): if matrix_cond in variables_to_update: update_ops.append( weighted_assign_add(label, pred, weights_tiled, variables_to_update[matrix_cond])) return control_flow_ops.group(update_ops)
def _ragged_getitem_inner_dimensions(rt_input, key_list): """Retrieve inner dimensions, keeping outermost dimension unchanged. Args: rt_input: The `RaggedTensor` or `Tensor` from which a piece should be extracted. key_list: The __getitem__ keys for slicing the inner dimensions. Returns: A `RaggedTensor`. Raises: ValueError: If key_list is not supported. """ if not key_list: return rt_input if isinstance(rt_input, ops.Tensor): return rt_input.__getitem__([slice(None, None, None)] + key_list) column_key = key_list[0] if column_key is Ellipsis: expanded_key_list = _expand_ellipsis(key_list, rt_input.values.shape.ndims) return _ragged_getitem_inner_dimensions(rt_input, expanded_key_list) # Adding a new axis to a ragged inner dimension: recursively get the inner # dimensions of rt_input with key_list[1:], and then wrap the result in a # RaggedTensor that puts each value in its own row. if column_key is array_ops.newaxis: inner_rt = _ragged_getitem_inner_dimensions(rt_input, key_list[1:]) nsplits = tensor_shape.dimension_at_index(inner_rt.row_splits.shape, 0) if nsplits.value is not None: nsplits = nsplits.value else: nsplits = array_ops.shape(inner_rt.row_splits, out_type=inner_rt.row_splits.dtype)[0] return ragged_tensor.RaggedTensor.from_uniform_row_length( inner_rt, 1, nrows=nsplits - 1, validate=False) # Slicing a range of columns in a ragged inner dimension. We use a # recursive call to process the values, and then assemble a RaggedTensor # with those values. if isinstance(column_key, slice): if (column_key.start is None and column_key.stop is None and column_key.step is None): # Trivial slice: recursively process all values, & splits is unchanged. return rt_input.with_values( _ragged_getitem_inner_dimensions(rt_input.values, key_list[1:])) else: if not (isinstance(column_key.start, (ops.Tensor, int, type(None))) and isinstance(column_key.stop, (ops.Tensor, int, type(None)))): raise TypeError("slice offsets must be integers or None") # Nontrivial slice: use ragged_gather to extract the indicated slice as # a new RaggedTensor (inner_rt), and then recursively process its values. starts = rt_input.row_splits[:-1] limits = rt_input.row_splits[1:] step = 1 if column_key.step is None else column_key.step lower_bound = _if_ge_zero(step, lambda: starts, lambda: starts - 1) upper_bound = _if_ge_zero(step, lambda: limits, lambda: limits - 1) # inner_rt_starts[i] = index to start gathering for row i. if column_key.start is None: inner_rt_starts = _if_ge_zero(step, lambda: starts, lambda: limits - 1) else: start_offset = math_ops.cast(column_key.start, starts.dtype) inner_rt_starts = _if_ge_zero( column_key.start, lambda: math_ops.minimum( starts + start_offset, upper_bound), lambda: math_ops. maximum(limits + start_offset, lower_bound)) # inner_rt_limits[i] = index to stop gathering for row i. if column_key.stop is None: inner_rt_limits = _if_ge_zero(step, lambda: limits, lambda: starts - 1) else: stop_offset = math_ops.cast(column_key.stop, starts.dtype) inner_rt_limits = _if_ge_zero( column_key.stop, lambda: math_ops.minimum( starts + stop_offset, upper_bound), lambda: math_ops. maximum(limits + stop_offset, lower_bound)) inner_rt = _build_ragged_tensor_from_value_ranges( inner_rt_starts, inner_rt_limits, column_key.step, rt_input.values) # If the row dimension is uniform, then calculate the new # uniform_row_length, and rebuild inner_rt using that uniform_row_lengths. if rt_input.uniform_row_length is not None: new_row_length = _slice_length(rt_input.uniform_row_length, column_key) inner_rt = ragged_tensor.RaggedTensor.from_uniform_row_length( inner_rt.values, new_row_length, rt_input.nrows()) return inner_rt.with_values( _ragged_getitem_inner_dimensions(inner_rt.values, key_list[1:])) # Indexing a single column in a ragged inner dimension: raise an Exception. # See RaggedTensor.__getitem__.__doc__ for an explanation of why indexing # into a ragged inner dimension is problematic. if rt_input.uniform_row_length is None: raise ValueError("Cannot index into an inner ragged dimension.") # Indexing a single column in a uniform inner dimension: check that the # given index is in-bounds, and then use a strided slice over rt_input.values # to take the indicated element from each row. row_length = rt_input.uniform_row_length column_key = math_ops.cast(column_key, row_length.dtype) oob_err_msg = "Index out of bounds when indexing into a ragged tensor" oob_checks = [ check_ops.assert_greater_equal(column_key, -row_length, message=oob_err_msg), check_ops.assert_less(column_key, row_length, message=oob_err_msg), ] with ops.control_dependencies(oob_checks): offset = _if_ge_zero(column_key, lambda: column_key, lambda: row_length + column_key) sliced_rt = rt_input.values[offset::row_length] return _ragged_getitem_inner_dimensions(sliced_rt, key_list[1:])
def assert_true_mean_equal_by_dkwm_two_sample( samples1, low1, high1, samples2, low2, high2, false_fail_rate=1e-6, name=None): """Asserts the means of the given distributions are equal. More precisely, fails if there is enough evidence (using the [Dvoretzky-Kiefer-Wolfowitz-Massart inequality] (https://en.wikipedia.org/wiki/CDF-based_nonparametric_confidence_interval)) that the means of the distributions from which the given samples are drawn are _not_ equal with statistical significance `false_fail_rate` or stronger, otherwise passes. If you also want to check that you are gathering enough evidence that a pass is not spurious, see `min_num_samples_for_dkwm_mean_two_sample_test` and `min_discrepancy_of_true_means_detectable_by_dkwm_two_sample`. Note that `false_fail_rate` is a total false failure rate for all the assertions in the batch. As such, if the batch is nontrivial, the assertion will insist on stronger evidence to fail any one member. Args: samples1: Floating-point tensor of samples from the distribution(s) A. Entries are assumed IID across the 0th dimension. The other dimensions must broadcast with `low1`, `high1`, `low2`, and `high2`. low1: Floating-point tensor of lower bounds on the supports of the distributions A. high1: Floating-point tensor of upper bounds on the supports of the distributions A. samples2: Floating-point tensor of samples from the distribution(s) B. Entries are assumed IID across the 0th dimension. The other dimensions must broadcast with `low1`, `high1`, `low2`, and `high2`. low2: Floating-point tensor of lower bounds on the supports of the distributions B. high2: Floating-point tensor of upper bounds on the supports of the distributions B. false_fail_rate: *Scalar* admissible total rate of mistakes. name: A name for this operation (optional). Returns: check: Op that raises `InvalidArgumentError` if any pair of confidence intervals true for corresponding true means do not overlap. """ with ops.name_scope( name, "assert_true_mean_equal_by_dkwm_two_sample", [samples1, low1, high1, samples2, low2, high2, false_fail_rate]): samples1 = ops.convert_to_tensor(samples1, name="samples1") low1 = ops.convert_to_tensor(low1, name="low1") high1 = ops.convert_to_tensor(high1, name="high1") samples2 = ops.convert_to_tensor(samples2, name="samples2") low2 = ops.convert_to_tensor(low2, name="low2") high2 = ops.convert_to_tensor(high2, name="high2") false_fail_rate = ops.convert_to_tensor( false_fail_rate, name="false_fail_rate") samples1 = _check_shape_dominates(samples1, [low1, high1]) samples2 = _check_shape_dominates(samples2, [low2, high2]) compatible_samples = check_ops.assert_equal( array_ops.shape(samples1)[1:], array_ops.shape(samples2)[1:]) with ops.control_dependencies([compatible_samples]): # Could in principle play games with cleverly allocating # significance instead of the even split below. It may be possible # to get tighter intervals, in order to obtain a higher power test. # Any allocation strategy that depends only on the support bounds # and sample counts should be valid; however, because the intervals # scale as O(-log(false_fail_rate)), there doesn't seem to be much # room to win. min_mean_1, max_mean_1 = true_mean_confidence_interval_by_dkwm( samples1, low1, high1, false_fail_rate / 2.) min_mean_2, max_mean_2 = true_mean_confidence_interval_by_dkwm( samples2, low2, high2, false_fail_rate / 2.) # I want to assert # not (max_mean_1 < min_mean_2 or min_mean_1 > max_mean_2), # but I think I only have and-combination of asserts, so use DeMorgan. clause1_op = check_ops.assert_greater_equal(max_mean_1, min_mean_2) with ops.control_dependencies([clause1_op]): return check_ops.assert_less_equal(min_mean_1, max_mean_2)
def embed_check_integer_casting_closed( x, target_dtype, assert_nonnegative=True, name="embed_check_casting_closed"): """Ensures integers remain unaffected despite casting to/from int/float types. Example integer-types: `uint8`, `int32`, `bool`. Example floating-types: `float32`, `float64`. The largest possible integer representable by an IEEE754 floating-point is `2**(1 + mantissa_bits)` yet the largest possible integer as an int-type is `2**(bits - 1) - 1`. This function ensures that a `Tensor` purporting to have integer-form values can be cast to some other type without loss of precision. The smallest representable integer is the negative of the largest representable integer, except for types: `uint8`, `uint16`, `bool`. For these types, the smallest representable integer is `0`. Args: x: `Tensor` representing integer-form values. target_dtype: TF `dtype` under which `x` should have identical values. assert_nonnegative: `bool` indicating `x` should contain nonnegative values. name: A name for this operation (optional). Returns: x: Input `Tensor` with appropriate assertions embedded. Raises: TypeError: if `x` is neither integer- nor floating-type. TypeError: if `target_dtype` is neither integer- nor floating-type. TypeError: if neither `x` nor `target_dtype` are integer-type. """ with ops.name_scope(name, values=[x]): x = ops.convert_to_tensor(x, name="x") if (not _is_integer_like_by_dtype(x.dtype) and not x.dtype.is_floating): raise TypeError("{}.dtype must be floating- or " "integer-type.".format(x.dtype.name)) if (not _is_integer_like_by_dtype(target_dtype) and not target_dtype.is_floating): raise TypeError("target_dtype ({}) must be floating- or " "integer-type.".format(target_dtype.name)) if (not _is_integer_like_by_dtype(x.dtype) and not _is_integer_like_by_dtype(target_dtype)): raise TypeError("At least one of {}.dtype ({}) and target_dtype ({}) " "must be integer-type.".format( x.op.name, x.dtype.name, target_dtype.name)) assertions = [] if assert_nonnegative: assertions += [ check_ops.assert_non_negative( x, message="Elements must be non-negative."), ] if x.dtype.is_floating: # Being here means _is_integer_like_by_dtype(target_dtype) = True. # Since this check implies the magnitude check below, we need only it. assertions += [ assert_integer_form( x, int_dtype=target_dtype, message="Elements must be {}-equivalent.".format( target_dtype.name)), ] else: if (_largest_integer_by_dtype(x.dtype) > _largest_integer_by_dtype(target_dtype)): # Cast may lose integer precision. assertions += [ check_ops.assert_less_equal( x, _largest_integer_by_dtype(target_dtype), message=("Elements cannot exceed {}.".format( _largest_integer_by_dtype(target_dtype)))), ] if (not assert_nonnegative and (_smallest_integer_by_dtype(x.dtype) < _smallest_integer_by_dtype(target_dtype))): assertions += [ check_ops.assert_greater_equal( x, _smallest_integer_by_dtype(target_dtype), message=("Elements cannot be smaller than {}.".format( _smallest_integer_by_dtype(target_dtype)))), ] if not assertions: return x return control_flow_ops.with_dependencies(assertions, x)
def percentile(x, q, axis=None, interpolation=None, keep_dims=False, validate_args=False, name=None): """Compute the `q`-th percentile of `x`. Given a vector `x`, the `q`-th percentile of `x` is the value `q / 100` of the way from the minimum to the maximum in a sorted copy of `x`. The values and distances of the two nearest neighbors as well as the `interpolation` parameter will determine the percentile if the normalized ranking does not match the location of `q` exactly. This function is the same as the median if `q = 50`, the same as the minimum if `q = 0` and the same as the maximum if `q = 100`. ```python # Get 30th percentile with default ('nearest') interpolation. x = [1., 2., 3., 4.] percentile(x, q=30.) ==> 2.0 # Get 30th percentile with 'lower' interpolation x = [1., 2., 3., 4.] percentile(x, q=30., interpolation='lower') ==> 1.0 # Get 100th percentile (maximum). By default, this is computed over every dim x = [[1., 2.] [3., 4.]] percentile(x, q=100.) ==> 4.0 # Treat the leading dim as indexing samples, and find the 100th quantile (max) # over all such samples. x = [[1., 2.] [3., 4.]] percentile(x, q=100., axis=[0]) ==> [3., 4.] ``` Compare to `numpy.percentile`. Args: x: Floating point `N-D` `Tensor` with `N > 0`. If `axis` is not `None`, `x` must have statically known number of dimensions. q: Scalar `Tensor` in `[0, 100]`. The percentile. axis: Optional `0-D` or `1-D` integer `Tensor` with constant values. The axis that hold independent samples over which to return the desired percentile. If `None` (the default), treat every dimension as a sample dimension, returning a scalar. interpolation : {"lower", "higher", "nearest"}. Default: "nearest" This optional parameter specifies the interpolation method to use when the desired quantile lies between two data points `i < j`: * lower: `i`. * higher: `j`. * nearest: `i` or `j`, whichever is nearest. keep_dims: Python `bool`. If `True`, the last dimension is kept with size 1 If `False`, the last dimension is removed from the output shape. validate_args: Whether to add runtime checks of argument validity. If False, and arguments are incorrect, correct behavior is not guaranteed. name: A Python string name to give this `Op`. Default is "percentile" Returns: A `(N - len(axis))` dimensional `Tensor` of same dtype as `x`, or, if `axis` is `None`, a scalar. Raises: ValueError: If argument 'interpolation' is not an allowed type. """ name = name or "percentile" allowed_interpolations = {"lower", "higher", "nearest"} if interpolation is None: interpolation = "nearest" else: if interpolation not in allowed_interpolations: raise ValueError("Argument 'interpolation' must be in %s. Found %s" % (allowed_interpolations, interpolation)) with ops.name_scope(name, [x, q]): x = ops.convert_to_tensor(x, name="x") # Double is needed here and below, else we get the wrong index if the array # is huge along axis. q = math_ops.to_double(q, name="q") _get_static_ndims(q, expect_ndims=0) if validate_args: q = control_flow_ops.with_dependencies([ check_ops.assert_rank(q, 0), check_ops.assert_greater_equal(q, math_ops.to_double(0.)), check_ops.assert_less_equal(q, math_ops.to_double(100.)) ], q) if axis is None: y = array_ops.reshape(x, [-1]) else: axis = ops.convert_to_tensor(axis, name="axis") check_ops.assert_integer(axis) axis_ndims = _get_static_ndims( axis, expect_static=True, expect_ndims_no_more_than=1) axis_const = tensor_util.constant_value(axis) if axis_const is None: raise ValueError( "Expected argument 'axis' to be statically available. Found: %s" % axis) axis = axis_const if axis_ndims == 0: axis = [axis] axis = [int(a) for a in axis] x_ndims = _get_static_ndims( x, expect_static=True, expect_ndims_at_least=1) axis = _make_static_axis_non_negative(axis, x_ndims) y = _move_dims_to_flat_end(x, axis, x_ndims) frac_at_q_or_above = 1. - q / 100. d = math_ops.to_double(array_ops.shape(y)[-1]) if interpolation == "lower": index = math_ops.ceil((d - 1) * frac_at_q_or_above) elif interpolation == "higher": index = math_ops.floor((d - 1) * frac_at_q_or_above) elif interpolation == "nearest": index = math_ops.round((d - 1) * frac_at_q_or_above) # If d is gigantic, then we would have d == d - 1, even in double... So # let's use max/min to avoid out of bounds errors. d = array_ops.shape(y)[-1] # d - 1 will be distinct from d in int32. index = clip_ops.clip_by_value(math_ops.to_int32(index), 0, d - 1) # Sort everything, not just the top 'k' entries, which allows multiple calls # to sort only once (under the hood) and use CSE. sorted_y = _sort_tensor(y) # result.shape = B result = sorted_y[..., index] result.set_shape(y.get_shape()[:-1]) if keep_dims: if axis is None: # ones_vec = [1, 1,..., 1], total length = len(S) + len(B). ones_vec = array_ops.ones( shape=[_get_best_effort_ndims(x)], dtype=dtypes.int32) result *= array_ops.ones(ones_vec, dtype=x.dtype) else: result = _insert_back_keep_dims(result, axis) return result
def batch_gather_with_default(params, indices, default_value='', name=None): """Same as `batch_gather` but inserts `default_value` for invalid indices. This operation is similar to `batch_gather` except that it will substitute the value for invalid indices with `default_value` as the contents. See `batch_gather` for more details. Args: params: A potentially ragged tensor with shape `[B1...BN, P1...PM]` (`N>=0`, `M>0`). indices: A potentially ragged tensor with shape `[B1...BN, I]` (`N>=0`). default_value: A value to be inserted in places where `indices` are out of bounds. Must be the same dtype as params and either a scalar or rank 1. name: A name for the operation (optional). Returns: A potentially ragged tensor with shape `[B1...BN, I, P2...PM]`. `result.ragged_rank = max(indices.ragged_rank, params.ragged_rank)`. #### Example: ```python >>> params = tf.ragged.constant([ ['a', 'b', 'c'], ['d'], [], ['e']]) >>> indices = tf.ragged.constant([[1, 2, -1], [], [], [0, 10]]) >>> batch_gather_with_default(params, indices, 'FOO') [['b', 'c', 'FOO'], [], [], ['e', 'FOO']] ``` """ with ops.name_scope(name, 'RaggedBatchGatherWithDefault'): params = ragged_tensor.convert_to_tensor_or_ragged_tensor( params, name='params', ) indices = ragged_tensor.convert_to_tensor_or_ragged_tensor( indices, name='indices', ) default_value = ragged_tensor.convert_to_tensor_or_ragged_tensor( default_value, name='default_value', ) # TODO(hterry): lift this restriction and support default_values of # of rank > 1 if (default_value.shape.ndims is not 0 and default_value.shape.ndims is not 1): raise ValueError('"default_value" must be a scalar or vector') upper_bounds = None if indices.shape.ndims is None: raise ValueError('Indices must have a known rank.') if params.shape.ndims is None: raise ValueError('Params must have a known rank.') num_batch_dimensions = indices.shape.ndims - 1 pad = None # The logic for this works as follows: # - create a padded params, where: # padded_params[b1...bn, 0] = default_value # padded_params[b1...bn, i] = params[b1...bn, i-1] (i>0) # - create an `upper_bounds` Tensor that contains the number of elements # in each innermost rank. Broadcast `upper_bounds` to be the same shape # as `indices`. # - check to see which index in `indices` are out of bounds and substitute # it with the index containing `default_value` (the first). # - call batch_gather with the indices adjusted. with ops.control_dependencies([ check_ops.assert_greater_equal(array_ops.rank(params), array_ops.rank(indices))]): if ragged_tensor.is_ragged(params): row_lengths = ragged_array_ops.expand_dims( params.row_lengths(axis=num_batch_dimensions), axis=-1) upper_bounds = math_ops.cast(row_lengths, indices.dtype) pad_shape = _get_pad_shape(params, indices) pad = ragged_tensor_shape.broadcast_to( default_value, pad_shape) else: params_shape = array_ops.shape(params) pad_shape = array_ops.concat([ params_shape[:num_batch_dimensions], [1], params_shape[num_batch_dimensions + 1:params.shape.ndims] ], 0) upper_bounds = params_shape[num_batch_dimensions] pad = array_ops.broadcast_to(default_value, pad_shape) # Add `default_value` as the first value in the innermost (ragged) rank. pad = math_ops.cast(pad, params.dtype) padded_params = array_ops.concat( [pad, params], axis=num_batch_dimensions) # Adjust the indices by substituting out-of-bound indices to the # default-value index (which is the first element) shifted_indices = indices + 1 is_out_of_bounds = (indices < 0) | (indices > upper_bounds) adjusted_indices = ragged_where_op.where( is_out_of_bounds, x=array_ops.zeros_like(indices), y=shifted_indices, ) return array_ops.batch_gather( params=padded_params, indices=adjusted_indices, name=name)
def update_confusion_matrix_variables(variables_to_update, y_true, y_pred, thresholds, top_k=None, class_id=None, sample_weight=None): """Returns op to update the given confusion matrix variables. For every pair of values in y_true and y_pred: true_positive: y_true == True and y_pred > thresholds false_negatives: y_true == True and y_pred <= thresholds true_negatives: y_true == False and y_pred <= thresholds false_positive: y_true == False and y_pred > thresholds The results will be weighted and added together. When multiple thresholds are provided, we will repeat the same for every threshold. For estimation of these metrics over a stream of data, the function creates an `update_op` operation that updates the given variables. If `sample_weight` is `None`, weights default to 1. Use weights of 0 to mask values. Args: variables_to_update: Dictionary with 'tp', 'fn', 'tn', 'fp' as valid keys and corresponding variables to update as values. y_true: A `Tensor` whose shape matches `y_pred`. Will be cast to `bool`. y_pred: A floating point `Tensor` of arbitrary shape and whose values are in the range `[0, 1]`. thresholds: A float value or a python list or tuple of float thresholds in `[0, 1]`, or NEG_INF (used when top_k is set). top_k: Optional int, indicates that the positive labels should be limited to the top k predictions. class_id: Optional int, limits the prediction and labels to the class specified by this argument. sample_weight: Optional `Tensor` whose rank is either 0, or the same rank as `y_true`, and must be broadcastable to `y_true` (i.e., all dimensions must be either `1`, or the same as the corresponding `y_true` dimension). Returns: Update op. Raises: ValueError: If `y_pred` and `y_true` have mismatched shapes, or if `sample_weight` is not `None` and its shape doesn't match `y_pred`, or if `variables_to_update` contains invalid keys. """ if variables_to_update is None: return y_true = ops.convert_to_tensor(y_true) y_pred = ops.convert_to_tensor(y_pred) y_pred.shape.assert_is_compatible_with(y_true.shape) if not any( key for key in variables_to_update if key in list(ConfusionMatrix)): raise ValueError( 'Please provide at least one valid confusion matrix ' 'variable to update. Valid variable key options are: "{}". ' 'Received: "{}"'.format( list(ConfusionMatrix), variables_to_update.keys())) invalid_keys = [ key for key in variables_to_update if key not in list(ConfusionMatrix) ] if invalid_keys: raise ValueError( 'Invalid keys: {}. Valid variable key options are: "{}"'.format( invalid_keys, list(ConfusionMatrix))) with ops.control_dependencies([ check_ops.assert_greater_equal( y_pred, math_ops.cast(0.0, dtype=y_pred.dtype), message='predictions must be >= 0'), check_ops.assert_less_equal( y_pred, math_ops.cast(1.0, dtype=y_pred.dtype), message='predictions must be <= 1') ]): y_pred, y_true, sample_weight = squeeze_or_expand_dimensions( math_ops.cast(y_pred, dtype=dtypes.float32), math_ops.cast(y_true, dtype=dtypes.bool), sample_weight) if top_k is not None: y_pred = _filter_top_k(y_pred, top_k) if class_id is not None: y_true = y_true[..., class_id] y_pred = y_pred[..., class_id] thresholds = to_list(thresholds) num_thresholds = len(thresholds) num_predictions = array_ops.size(y_pred) # Reshape predictions and labels. predictions_2d = array_ops.reshape(y_pred, [1, -1]) labels_2d = array_ops.reshape( math_ops.cast(y_true, dtype=dtypes.bool), [1, -1]) # Tile the thresholds for every prediction. thresh_tiled = array_ops.tile( array_ops.expand_dims(array_ops.constant(thresholds), 1), array_ops.stack([1, num_predictions])) # Tile the predictions for every threshold. preds_tiled = array_ops.tile(predictions_2d, [num_thresholds, 1]) # Compare predictions and threshold. pred_is_pos = math_ops.greater(preds_tiled, thresh_tiled) # Tile labels by number of thresholds label_is_pos = array_ops.tile(labels_2d, [num_thresholds, 1]) if sample_weight is not None: weights = weights_broadcast_ops.broadcast_weights( math_ops.cast(sample_weight, dtype=dtypes.float32), y_pred) weights_tiled = array_ops.tile( array_ops.reshape(weights, [1, -1]), [num_thresholds, 1]) else: weights_tiled = None update_ops = [] def weighted_assign_add(label, pred, weights, var): label_and_pred = math_ops.cast( math_ops.logical_and(label, pred), dtype=dtypes.float32) if weights is not None: label_and_pred *= weights return state_ops.assign_add(var, math_ops.reduce_sum(label_and_pred, 1)) loop_vars = { ConfusionMatrix.TRUE_POSITIVES: (label_is_pos, pred_is_pos), } update_tn = ConfusionMatrix.TRUE_NEGATIVES in variables_to_update update_fp = ConfusionMatrix.FALSE_POSITIVES in variables_to_update update_fn = ConfusionMatrix.FALSE_NEGATIVES in variables_to_update if update_fn or update_tn: pred_is_neg = math_ops.logical_not(pred_is_pos) loop_vars[ConfusionMatrix.FALSE_NEGATIVES] = (label_is_pos, pred_is_neg) if update_fp or update_tn: label_is_neg = math_ops.logical_not(label_is_pos) loop_vars[ConfusionMatrix.FALSE_POSITIVES] = (label_is_neg, pred_is_pos) if update_tn: loop_vars[ConfusionMatrix.TRUE_NEGATIVES] = (label_is_neg, pred_is_neg) for matrix_cond, (label, pred) in loop_vars.items(): if matrix_cond in variables_to_update: update_ops.append( weighted_assign_add(label, pred, weights_tiled, variables_to_update[matrix_cond])) return control_flow_ops.group(update_ops)
def predict(self, features): """Computes predictions multiple steps into the future. Args: features: A dictionary with the following key/value pairs: PredictionFeatures.TIMES: A [batch size, predict window size] integer Tensor of times, after the window of data indicated by `STATE_TUPLE`, to make predictions for. PredictionFeatures.STATE_TUPLE: A tuple of (times, values), times with shape [batch size, self.input_window_size], values with shape [batch size, self.input_window_size, self.num_features] representing a segment of the time series before `TIMES`. This data is used to start of the autoregressive computation. This should have data for at least self.input_window_size timesteps. Returns: A dictionary with keys, "mean", "covariance". The values are Tensors of shape [batch_size, predict window size, num_features] and correspond to the values passed in `TIMES`. """ predict_times = math_ops.cast( ops.convert_to_tensor(features[PredictionFeatures.TIMES]), dtypes.int32) batch_size = array_ops.shape(predict_times)[0] num_predict_values = array_ops.shape(predict_times)[1] prediction_iterations = ( (num_predict_values + self.output_window_size - 1) // self.output_window_size) # Pad predict_times so as to have exact multiple of self.output_window_size # values per example. padding_size = (prediction_iterations * self.output_window_size - num_predict_values) padding = array_ops.zeros([batch_size, padding_size], predict_times.dtype) predict_times = control_flow_ops.cond( padding_size > 0, lambda: array_ops.concat([predict_times, padding], 1), lambda: predict_times) state = features[PredictionFeatures.STATE_TUPLE] (state_times, state_values) = state state_times = math_ops.cast(ops.convert_to_tensor(state_times), dtypes.int32) state_values = ops.convert_to_tensor(state_values, dtype=self.dtype) initial_input_times = predict_times[:, :self.output_window_size] if self.input_window_size > 0: initial_input_times = array_ops.concat([ state_times[:, -self.input_window_size:], initial_input_times ], 1) values_size = array_ops.shape(state_values)[1] times_size = array_ops.shape(state_times)[1] with ops.control_dependencies([ check_ops.assert_greater_equal(values_size, self.input_window_size), check_ops.assert_equal(values_size, times_size) ]): initial_input_values = state_values[:, -self. input_window_size:, :] else: initial_input_values = 0 # Iterate over the predict_times, predicting self.output_window_size values # in each iteration. def _while_condition(iteration_number, *unused_args): return math_ops.less(iteration_number, prediction_iterations) def _while_body(iteration_number, input_times, input_values, mean_ta, covariance_ta): """Predict self.output_window_size values.""" prediction_ops = self.prediction_ops(input_times, input_values) predicted_mean = prediction_ops["mean"] predicted_covariance = prediction_ops["covariance"] offset = self.output_window_size * gen_math_ops.minimum( iteration_number + 1, prediction_iterations - 1) if self.input_window_size > 0: if self.output_window_size < self.input_window_size: new_input_values = array_ops.concat([ input_values[:, self.output_window_size:, :], predicted_mean ], 1) new_input_times = array_ops.concat([ input_times[:, self.output_window_size:], predict_times[:, offset:offset + self.output_window_size] ], 1) else: new_input_values = predicted_mean[:, -self. input_window_size:, :] new_input_times = predict_times[:, offset - self. input_window_size:offset + self.output_window_size] else: new_input_values = input_values new_input_times = predict_times[:, offset:offset + self.output_window_size] new_input_times.set_shape(initial_input_times.get_shape()) new_mean_ta = mean_ta.write(iteration_number, predicted_mean) if isinstance(covariance_ta, tensor_array_ops.TensorArray): new_covariance_ta = covariance_ta.write( iteration_number, predicted_covariance) else: new_covariance_ta = covariance_ta return (iteration_number + 1, new_input_times, new_input_values, new_mean_ta, new_covariance_ta) # Note that control_flow_ops.while_loop doesn't seem happy with None. Hence # using 0 for cases where we don't want to predict covariance. covariance_ta_init = (tensor_array_ops.TensorArray( dtype=self.dtype, size=prediction_iterations) if self.loss != ARModel.SQUARED_LOSS else 0.) mean_ta_init = tensor_array_ops.TensorArray(dtype=self.dtype, size=prediction_iterations) _, _, _, mean_ta, covariance_ta = control_flow_ops.while_loop( _while_condition, _while_body, [ 0, initial_input_times, initial_input_values, mean_ta_init, covariance_ta_init ]) def _parse_ta(values_ta): """Helper function to parse the returned TensorArrays.""" if not isinstance(values_ta, tensor_array_ops.TensorArray): return None predictions_length = prediction_iterations * self.output_window_size # Shape [prediction_iterations, batch_size, self.output_window_size, # self.num_features] values_packed = values_ta.stack() # Transpose to move batch dimension outside. output_values = array_ops.reshape( array_ops.transpose(values_packed, [1, 0, 2, 3]), array_ops.stack([batch_size, predictions_length, -1])) # Clip to desired size return output_values[:, :num_predict_values, :] predicted_mean = _parse_ta(mean_ta) predicted_covariance = _parse_ta(covariance_ta) if predicted_covariance is None: predicted_covariance = array_ops.ones_like(predicted_mean) # Transform and scale the mean and covariance appropriately. predicted_mean = self._scale_back_data(predicted_mean) predicted_covariance = self._scale_back_variance(predicted_covariance) return {"mean": predicted_mean, "covariance": predicted_covariance}
def batch_gather_with_default(params, indices, default_value='', name=None): """Same as `batch_gather` but inserts `default_value` for invalid indices. This operation is similar to `batch_gather` except that it will substitute the value for invalid indices with `default_value` as the contents. See `batch_gather` for more details. Args: params: A potentially ragged tensor with shape `[B1...BN, P1...PM]` (`N>=0`, `M>0`). indices: A potentially ragged tensor with shape `[B1...BN, I]` (`N>=0`). default_value: A value to be inserted in places where `indices` are out of bounds. Must be the same dtype as params and either a scalar or rank 1. name: A name for the operation (optional). Returns: A potentially ragged tensor with shape `[B1...BN, I, P2...PM]`. `result.ragged_rank = max(indices.ragged_rank, params.ragged_rank)`. #### Example: ```python >>> params = tf.ragged.constant([ ['a', 'b', 'c'], ['d'], [], ['e']]) >>> indices = tf.ragged.constant([[1, 2, -1], [], [], [0, 10]]) >>> batch_gather_with_default(params, indices, 'FOO') [['b', 'c', 'FOO'], [], [], ['e', 'FOO']] ``` """ with ops.name_scope(name, 'RaggedBatchGatherWithDefault'): params = ragged_tensor.convert_to_tensor_or_ragged_tensor( params, name='params', ) indices = ragged_tensor.convert_to_tensor_or_ragged_tensor( indices, name='indices', ) default_value = ragged_tensor.convert_to_tensor_or_ragged_tensor( default_value, name='default_value', ) row_splits_dtype, (params, indices, default_value) = ( ragged_tensor.match_row_splits_dtypes(params, indices, default_value, return_dtype=True)) # TODO(hterry): lift this restriction and support default_values of # of rank > 1 if default_value.shape.ndims not in (0, 1): raise ValueError('"default_value" must be a scalar or vector') upper_bounds = None if indices.shape.ndims is None: raise ValueError('Indices must have a known rank.') if params.shape.ndims is None: raise ValueError('Params must have a known rank.') num_batch_dimensions = indices.shape.ndims - 1 pad = None # The logic for this works as follows: # - create a padded params, where: # padded_params[b1...bn, 0] = default_value # padded_params[b1...bn, i] = params[b1...bn, i-1] (i>0) # - create an `upper_bounds` Tensor that contains the number of elements # in each innermost rank. Broadcast `upper_bounds` to be the same shape # as `indices`. # - check to see which index in `indices` are out of bounds and substitute # it with the index containing `default_value` (the first). # - call batch_gather with the indices adjusted. with ops.control_dependencies([ check_ops.assert_greater_equal(array_ops.rank(params), array_ops.rank(indices))]): if ragged_tensor.is_ragged(params): row_lengths = ragged_array_ops.expand_dims( params.row_lengths(axis=num_batch_dimensions), axis=-1) upper_bounds = math_ops.cast(row_lengths, indices.dtype) pad_shape = _get_pad_shape(params, indices, row_splits_dtype) pad = ragged_tensor_shape.broadcast_to( default_value, pad_shape) else: params_shape = array_ops.shape(params) pad_shape = array_ops.concat([ params_shape[:num_batch_dimensions], [1], params_shape[num_batch_dimensions + 1:params.shape.ndims] ], 0) upper_bounds = params_shape[num_batch_dimensions] pad = array_ops.broadcast_to(default_value, pad_shape) # Add `default_value` as the first value in the innermost (ragged) rank. pad = math_ops.cast(pad, params.dtype) padded_params = array_ops.concat( [pad, params], axis=num_batch_dimensions) # Adjust the indices by substituting out-of-bound indices to the # default-value index (which is the first element) shifted_indices = indices + 1 is_out_of_bounds = (indices < 0) | (indices > upper_bounds) adjusted_indices = ragged_where_op.where( is_out_of_bounds, x=array_ops.zeros_like(indices), y=shifted_indices, ) return array_ops.batch_gather( params=padded_params, indices=adjusted_indices, name=name)
def assert_true_mean_in_interval_by_dkwm(samples, low, high, expected_low, expected_high, false_fail_rate=1e-6, name=None): """Asserts the mean of the given distribution is in the given interval. More precisely, fails if there is enough evidence (using the [Dvoretzky-Kiefer-Wolfowitz-Massart inequality] (https://en.wikipedia.org/wiki/CDF-based_nonparametric_confidence_interval)) that the mean of the distribution from which the given samples are drawn is _outside_ the given interval with statistical significance `false_fail_rate` or stronger, otherwise passes. If you also want to check that you are gathering enough evidence that a pass is not spurious, see `min_num_samples_for_dkwm_mean_test` and `min_discrepancy_of_true_means_detectable_by_dkwm`. Note that `false_fail_rate` is a total false failure rate for all the assertions in the batch. As such, if the batch is nontrivial, the assertion will insist on stronger evidence to fail any one member. Args: samples: Floating-point `Tensor` of samples from the distribution(s) of interest. Entries are assumed IID across the 0th dimension. The other dimensions must broadcast with `low` and `high`. The support is bounded: `low <= samples <= high`. low: Floating-point `Tensor` of lower bounds on the distributions' supports. high: Floating-point `Tensor` of upper bounds on the distributions' supports. expected_low: Floating-point `Tensor` of lower bounds on the expected true means. expected_high: Floating-point `Tensor` of upper bounds on the expected true means. false_fail_rate: *Scalar* floating-point `Tensor` admissible total rate of mistakes. name: A name for this operation (optional). Returns: check: Op that raises `InvalidArgumentError` if any expected mean interval does not overlap with the corresponding confidence interval. """ with ops.name_scope( name, "assert_true_mean_in_interval_by_dkwm", [samples, low, high, expected_low, expected_high, false_fail_rate]): samples = ops.convert_to_tensor(samples, name="samples") low = ops.convert_to_tensor(low, name="low") high = ops.convert_to_tensor(high, name="high") expected_low = ops.convert_to_tensor(expected_low, name="expected_low") expected_high = ops.convert_to_tensor(expected_high, name="expected_high") false_fail_rate = ops.convert_to_tensor(false_fail_rate, name="false_fail_rate") samples = _check_shape_dominates( samples, [low, high, expected_low, expected_high]) min_mean, max_mean = true_mean_confidence_interval_by_dkwm( samples, low, high, false_fail_rate) # Assert that the interval [min_mean, max_mean] intersects the # interval [expected_low, expected_high]. This is true if # max_mean >= expected_low and min_mean <= expected_high. # By DeMorgan's law, that's also equivalent to # not (max_mean < expected_low or min_mean > expected_high), # which is a way of saying the two intervals are not disjoint. check_confidence_interval_can_intersect = check_ops.assert_greater_equal( max_mean, expected_low, message="Confidence interval does not " "intersect: true mean smaller than expected") with ops.control_dependencies( [check_confidence_interval_can_intersect]): return check_ops.assert_less_equal( min_mean, expected_high, message="Confidence interval does not " "intersect: true mean greater than expected")
def _validate_sample_arg(self, x): """Helper which validates sample arg, e.g., input to `log_prob`.""" with ops.name_scope(name="validate_sample_arg", values=[x]): x_ndims = (array_ops.rank(x) if x.shape.ndims is None else x.shape.ndims) event_ndims = (array_ops.size(self.event_shape_tensor()) if self.event_shape.ndims is None else self.event_shape.ndims) batch_ndims = (array_ops.size(self._batch_shape_unexpanded) if self.batch_shape.ndims is None else self.batch_shape.ndims) expected_batch_event_ndims = batch_ndims + event_ndims if (isinstance(x_ndims, int) and isinstance(expected_batch_event_ndims, int)): if x_ndims < expected_batch_event_ndims: raise NotImplementedError( "Broadcasting is not supported; too few batch and event dims " "(expected at least {}, saw {}).".format( expected_batch_event_ndims, x_ndims)) ndims_assertion = [] elif self.validate_args: ndims_assertion = [ check_ops.assert_greater_equal( x_ndims, expected_batch_event_ndims, message=("Broadcasting is not supported; too few " "batch and event dims."), name="assert_batch_and_event_ndims_large_enough"), ] if (self.batch_shape.is_fully_defined() and self.event_shape.is_fully_defined()): expected_batch_event_shape = np.int32( self.batch_shape.concatenate(self.event_shape).as_list()) else: expected_batch_event_shape = array_ops.concat([ self.batch_shape_tensor(), self.event_shape_tensor(), ], axis=0) sample_ndims = x_ndims - expected_batch_event_ndims if isinstance(sample_ndims, int): sample_ndims = max(sample_ndims, 0) if (isinstance(sample_ndims, int) and x.shape[sample_ndims:].is_fully_defined()): actual_batch_event_shape = np.int32( x.shape[sample_ndims:].as_list()) else: sample_ndims = math_ops.maximum(sample_ndims, 0) actual_batch_event_shape = array_ops.shape(x)[sample_ndims:] if (isinstance(expected_batch_event_shape, np.ndarray) and isinstance(actual_batch_event_shape, np.ndarray)): if any(expected_batch_event_shape != actual_batch_event_shape): raise NotImplementedError( "Broadcasting is not supported; " "unexpected batch and event shape " "(expected {}, saw {}).".format( expected_batch_event_shape, actual_batch_event_shape)) # We need to set the final runtime-assertions to `ndims_assertion` since # its possible this assertion was created. We could add a condition to # only do so if `self.validate_args == True`, however this is redundant # as `ndims_assertion` already encodes this information. runtime_assertions = ndims_assertion elif self.validate_args: # We need to make the `ndims_assertion` a control dep because otherwise # TF itself might raise an exception owing to this assertion being # ill-defined, ie, one cannot even compare different rank Tensors. with ops.control_dependencies(ndims_assertion): shape_assertion = check_ops.assert_equal( expected_batch_event_shape, actual_batch_event_shape, message=("Broadcasting is not supported; " "unexpected batch and event shape."), name="assert_batch_and_event_shape_same") runtime_assertions = [shape_assertion] else: runtime_assertions = [] return runtime_assertions
def percentile(x, q, axis=None, interpolation=None, keep_dims=False, validate_args=False, name=None): """Compute the `q`-th percentile of `x`. Given a vector `x`, the `q`-th percentile of `x` is the value `q / 100` of the way from the minimum to the maximum in a sorted copy of `x`. The values and distances of the two nearest neighbors as well as the `interpolation` parameter will determine the percentile if the normalized ranking does not match the location of `q` exactly. This function is the same as the median if `q = 50`, the same as the minimum if `q = 0` and the same as the maximum if `q = 100`. ```python # Get 30th percentile with default ('nearest') interpolation. x = [1., 2., 3., 4.] percentile(x, q=30.) ==> 2.0 # Get 30th percentile with 'lower' interpolation x = [1., 2., 3., 4.] percentile(x, q=30., interpolation='lower') ==> 1.0 # Get 100th percentile (maximum). By default, this is computed over every dim x = [[1., 2.] [3., 4.]] percentile(x, q=100.) ==> 4.0 # Treat the leading dim as indexing samples, and find the 100th quantile (max) # over all such samples. x = [[1., 2.] [3., 4.]] percentile(x, q=100., axis=[0]) ==> [3., 4.] ``` Compare to `numpy.percentile`. Args: x: Floating point `N-D` `Tensor` with `N > 0`. If `axis` is not `None`, `x` must have statically known number of dimensions. q: Scalar `Tensor` in `[0, 100]`. The percentile. axis: Optional `0-D` or `1-D` integer `Tensor` with constant values. The axis that hold independent samples over which to return the desired percentile. If `None` (the default), treat every dimension as a sample dimension, returning a scalar. interpolation : {"lower", "higher", "nearest"}. Default: "nearest" This optional parameter specifies the interpolation method to use when the desired quantile lies between two data points `i < j`: * lower: `i`. * higher: `j`. * nearest: `i` or `j`, whichever is nearest. keep_dims: Python `bool`. If `True`, the last dimension is kept with size 1 If `False`, the last dimension is removed from the output shape. validate_args: Whether to add runtime checks of argument validity. If False, and arguments are incorrect, correct behavior is not guaranteed. name: A Python string name to give this `Op`. Default is "percentile" Returns: A `(N - len(axis))` dimensional `Tensor` of same dtype as `x`, or, if `axis` is `None`, a scalar. Raises: ValueError: If argument 'interpolation' is not an allowed type. """ name = name or "percentile" allowed_interpolations = {"lower", "higher", "nearest"} if interpolation is None: interpolation = "nearest" else: if interpolation not in allowed_interpolations: raise ValueError( "Argument 'interpolation' must be in %s. Found %s" % (allowed_interpolations, interpolation)) with ops.name_scope(name, [x, q]): x = ops.convert_to_tensor(x, name="x") q = math_ops.to_float(q, name="q") _get_static_ndims(q, expect_ndims=0) if validate_args: q = control_flow_ops.with_dependencies([ check_ops.assert_rank(q, 0), check_ops.assert_greater_equal(q, 0.), check_ops.assert_less_equal(q, 100.) ], q) if axis is None: y = array_ops.reshape(x, [-1]) else: axis = ops.convert_to_tensor(axis, name="axis") check_ops.assert_integer(axis) axis_ndims = _get_static_ndims(axis, expect_static=True, expect_ndims_no_more_than=1) axis_const = tensor_util.constant_value(axis) if axis_const is None: raise ValueError( "Expected argument 'axis' to be statically available. Found: %s" % axis) axis = axis_const if axis_ndims == 0: axis = [axis] axis = [int(a) for a in axis] x_ndims = _get_static_ndims(x, expect_static=True, expect_ndims_at_least=1) axis = _make_static_axis_non_negative(axis, x_ndims) y = _move_dims_to_flat_end(x, axis, x_ndims) frac_at_q_or_above = 1. - q / 100. d = math_ops.to_float(array_ops.shape(y)[-1]) if interpolation == "lower": index = math_ops.ceil((d - 1) * frac_at_q_or_above) elif interpolation == "higher": index = math_ops.floor((d - 1) * frac_at_q_or_above) elif interpolation == "nearest": index = math_ops.round((d - 1) * frac_at_q_or_above) # Sort everything, not just the top 'k' entries, which allows multiple calls # to sort only once (under the hood) and use CSE. sorted_y = _sort_tensor(y) # result.shape = B result = sorted_y[..., math_ops.to_int32(index)] result.set_shape(y.get_shape()[:-1]) if keep_dims: if axis is None: # ones_vec = [1, 1,..., 1], total length = len(S) + len(B). ones_vec = array_ops.ones(shape=[_get_best_effort_ndims(x)], dtype=dtypes.int32) result *= array_ops.ones(ones_vec, dtype=x.dtype) else: result = _insert_back_keep_dims(result, axis) return result
def _interpolate_bilinear(grid, query_points, name='interpolate_bilinear', indexing='ij'): """Similar to Matlab's interp2 function. Finds values for query points on a grid using bilinear interpolation. Args: grid: a 4-D float `Tensor` of shape `[batch, height, width, channels]`. query_points: a 3-D float `Tensor` of N points with shape `[batch, N, 2]`. name: a name for the operation (optional). indexing: whether the query points are specified as row and column (ij), or Cartesian coordinates (xy). Returns: values: a 3-D `Tensor` with shape `[batch, N, channels]` Raises: ValueError: if the indexing mode is invalid, or if the shape of the inputs invalid. """ if indexing != 'ij' and indexing != 'xy': raise ValueError('Indexing mode must be \'ij\' or \'xy\'') with ops.name_scope(name): grid = ops.convert_to_tensor(grid) query_points = ops.convert_to_tensor(query_points) shape = grid.get_shape().as_list() if len(shape) != 4: msg = 'Grid must be 4 dimensional. Received size: ' raise ValueError(msg + str(grid.get_shape())) batch_size, height, width, channels = (array_ops.shape(grid)[0], array_ops.shape(grid)[1], array_ops.shape(grid)[2], array_ops.shape(grid)[3]) shape = [batch_size, height, width, channels] query_type = query_points.dtype grid_type = grid.dtype with ops.control_dependencies([ check_ops.assert_equal( len(query_points.get_shape()), 3, message='Query points must be 3 dimensional.'), check_ops.assert_equal( array_ops.shape(query_points)[2], 2, message='Query points must be size 2 in dim 2.') ]): num_queries = array_ops.shape(query_points)[1] with ops.control_dependencies([ check_ops.assert_greater_equal( height, 2, message='Grid height must be at least 2.'), check_ops.assert_greater_equal( width, 2, message='Grid width must be at least 2.') ]): alphas = [] floors = [] ceils = [] index_order = [0, 1] if indexing == 'ij' else [1, 0] unstacked_query_points = array_ops.unstack(query_points, axis=2) for dim in index_order: with ops.name_scope('dim-' + str(dim)): queries = unstacked_query_points[dim] size_in_indexing_dimension = shape[dim + 1] # max_floor is size_in_indexing_dimension - 2 so that max_floor + 1 # is still a valid index into the grid. max_floor = math_ops.cast(size_in_indexing_dimension - 2, query_type) min_floor = constant_op.constant(0.0, dtype=query_type) floor = math_ops.minimum( math_ops.maximum(min_floor, math_ops.floor(queries)), max_floor) int_floor = math_ops.cast(floor, dtypes.int32) floors.append(int_floor) ceil = int_floor + 1 ceils.append(ceil) # alpha has the same type as the grid, as we will directly use alpha # when taking linear combinations of pixel values from the image. alpha = math_ops.cast(queries - floor, grid_type) min_alpha = constant_op.constant(0.0, dtype=grid_type) max_alpha = constant_op.constant(1.0, dtype=grid_type) alpha = math_ops.minimum(math_ops.maximum(min_alpha, alpha), max_alpha) # Expand alpha to [b, n, 1] so we can use broadcasting # (since the alpha values don't depend on the channel). alpha = array_ops.expand_dims(alpha, 2) alphas.append(alpha) with ops.control_dependencies([ check_ops.assert_less_equal( math_ops.cast(batch_size * height * width, dtype=dtypes.float32), np.iinfo(np.int32).max / 8, message="""The image size or batch size is sufficiently large that the linearized addresses used by array_ops.gather may exceed the int32 limit.""") ]): flattened_grid = array_ops.reshape( grid, [batch_size * height * width, channels]) batch_offsets = array_ops.reshape( math_ops.range(batch_size) * height * width, [batch_size, 1]) # This wraps array_ops.gather. We reshape the image data such that the # batch, y, and x coordinates are pulled into the first dimension. # Then we gather. Finally, we reshape the output back. It's possible this # code would be made simpler by using array_ops.gather_nd. def gather(y_coords, x_coords, name): with ops.name_scope('gather-' + name): linear_coordinates = batch_offsets + y_coords * width + x_coords gathered_values = array_ops.gather(flattened_grid, linear_coordinates) return array_ops.reshape(gathered_values, [batch_size, num_queries, channels]) # grab the pixel values in the 4 corners around each query point top_left = gather(floors[0], floors[1], 'top_left') top_right = gather(floors[0], ceils[1], 'top_right') bottom_left = gather(ceils[0], floors[1], 'bottom_left') bottom_right = gather(ceils[0], ceils[1], 'bottom_right') # now, do the actual interpolation with ops.name_scope('interpolate'): interp_top = alphas[1] * (top_right - top_left) + top_left interp_bottom = alphas[1] * (bottom_right - bottom_left) + bottom_left interp = alphas[0] * (interp_bottom - interp_top) + interp_top return interp
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 update_confusion_matrix_variables(variables_to_update, y_true, y_pred, thresholds, top_k=None, class_id=None, sample_weight=None, multi_label=False, label_weights=None): """Returns op to update the given confusion matrix variables. For every pair of values in y_true and y_pred: true_positive: y_true == True and y_pred > thresholds false_negatives: y_true == True and y_pred <= thresholds true_negatives: y_true == False and y_pred <= thresholds false_positive: y_true == False and y_pred > thresholds The results will be weighted and added together. When multiple thresholds are provided, we will repeat the same for every threshold. For estimation of these metrics over a stream of data, the function creates an `update_op` operation that updates the given variables. If `sample_weight` is `None`, weights default to 1. Use weights of 0 to mask values. Args: variables_to_update: Dictionary with 'tp', 'fn', 'tn', 'fp' as valid keys and corresponding variables to update as values. y_true: A `Tensor` whose shape matches `y_pred`. Will be cast to `bool`. y_pred: A floating point `Tensor` of arbitrary shape and whose values are in the range `[0, 1]`. thresholds: A float value, float tensor, python list, or tuple of float thresholds in `[0, 1]`, or NEG_INF (used when top_k is set). top_k: Optional int, indicates that the positive labels should be limited to the top k predictions. class_id: Optional int, limits the prediction and labels to the class specified by this argument. sample_weight: Optional `Tensor` whose rank is either 0, or the same rank as `y_true`, and must be broadcastable to `y_true` (i.e., all dimensions must be either `1`, or the same as the corresponding `y_true` dimension). multi_label: Optional boolean indicating whether multidimensional prediction/labels should be treated as multilabel responses, or flattened into a single label. When True, the valus of `variables_to_update` must have a second dimension equal to the number of labels in y_true and y_pred, and those tensors must not be RaggedTensors. label_weights: (optional) tensor of non-negative weights for multilabel data. The weights are applied when calculating TP, FP, FN, and TN without explicit multilabel handling (i.e. when the data is to be flattened). Returns: Update op. Raises: ValueError: If `y_pred` and `y_true` have mismatched shapes, or if `sample_weight` is not `None` and its shape doesn't match `y_pred`, or if `variables_to_update` contains invalid keys. """ if multi_label and label_weights is not None: raise ValueError('`label_weights` for multilabel data should be handled ' 'outside of `update_confusion_matrix_variables` when ' '`multi_label` is True.') if variables_to_update is None: return if not any( key for key in variables_to_update if key in list(ConfusionMatrix)): raise ValueError( 'Please provide at least one valid confusion matrix ' 'variable to update. Valid variable key options are: "{}". ' 'Received: "{}"'.format( list(ConfusionMatrix), variables_to_update.keys())) variable_dtype = list(variables_to_update.values())[0].dtype y_true = math_ops.cast(y_true, dtype=variable_dtype) y_pred = math_ops.cast(y_pred, dtype=variable_dtype) thresholds = ops.convert_to_tensor_v2_with_dispatch( thresholds, dtype=variable_dtype) num_thresholds = thresholds.shape[0] if multi_label: one_thresh = math_ops.equal( math_ops.cast(1, dtype=dtypes.int32), array_ops.rank(thresholds), name='one_set_of_thresholds_cond') else: [y_pred, y_true], _ = ragged_assert_compatible_and_get_flat_values([y_pred, y_true], sample_weight) one_thresh = math_ops.cast(True, dtype=dtypes.bool) invalid_keys = [ key for key in variables_to_update if key not in list(ConfusionMatrix) ] if invalid_keys: raise ValueError( 'Invalid keys: {}. Valid variable key options are: "{}"'.format( invalid_keys, list(ConfusionMatrix))) with ops.control_dependencies([ check_ops.assert_greater_equal( y_pred, math_ops.cast(0.0, dtype=y_pred.dtype), message='predictions must be >= 0'), check_ops.assert_less_equal( y_pred, math_ops.cast(1.0, dtype=y_pred.dtype), message='predictions must be <= 1') ]): if sample_weight is None: y_pred, y_true = losses_utils.squeeze_or_expand_dimensions( y_pred, y_true) else: sample_weight = math_ops.cast(sample_weight, dtype=variable_dtype) y_pred, y_true, sample_weight = ( losses_utils.squeeze_or_expand_dimensions( y_pred, y_true, sample_weight=sample_weight)) y_pred.shape.assert_is_compatible_with(y_true.shape) if top_k is not None: y_pred = _filter_top_k(y_pred, top_k) if class_id is not None: y_true = y_true[..., class_id] y_pred = y_pred[..., class_id] pred_shape = array_ops.shape(y_pred) num_predictions = pred_shape[0] if y_pred.shape.ndims == 1: num_labels = 1 else: num_labels = gen_math_ops.Prod(input=pred_shape[1:], axis=0) thresh_label_tile = array_ops.where_v2(one_thresh, num_labels, array_ops.ones([], dtype=dtypes.int32)) # Reshape predictions and labels, adding a dim for thresholding. if multi_label: predictions_extra_dim = array_ops.expand_dims(y_pred, 0) labels_extra_dim = array_ops.expand_dims( math_ops.cast(y_true, dtype=dtypes.bool), 0) else: # Flatten predictions and labels when not multilabel. predictions_extra_dim = array_ops.reshape(y_pred, [1, -1]) labels_extra_dim = array_ops.reshape( math_ops.cast(y_true, dtype=dtypes.bool), [1, -1]) # Tile the thresholds for every prediction. if multi_label: thresh_pretile_shape = [num_thresholds, 1, -1] thresh_tiles = [1, num_predictions, thresh_label_tile] data_tiles = [num_thresholds, 1, 1] else: thresh_pretile_shape = [num_thresholds, -1] thresh_tiles = [1, num_predictions * num_labels] data_tiles = [num_thresholds, 1] thresh_tiled = array_ops.tile( array_ops.reshape(thresholds, thresh_pretile_shape), array_ops.stack(thresh_tiles)) # Tile the predictions for every threshold. preds_tiled = array_ops.tile(predictions_extra_dim, data_tiles) # Compare predictions and threshold. pred_is_pos = math_ops.greater(preds_tiled, thresh_tiled) # Tile labels by number of thresholds label_is_pos = array_ops.tile(labels_extra_dim, data_tiles) if sample_weight is not None: sample_weight = weights_broadcast_ops.broadcast_weights( math_ops.cast(sample_weight, dtype=variable_dtype), y_pred) weights_tiled = array_ops.tile( array_ops.reshape(sample_weight, thresh_tiles), data_tiles) else: weights_tiled = None if label_weights is not None and not multi_label: label_weights = array_ops.expand_dims(label_weights, 0) label_weights = weights_broadcast_ops.broadcast_weights(label_weights, y_pred) label_weights_tiled = array_ops.tile( array_ops.reshape(label_weights, thresh_tiles), data_tiles) if weights_tiled is None: weights_tiled = label_weights_tiled else: weights_tiled = math_ops.multiply(weights_tiled, label_weights_tiled) update_ops = [] def weighted_assign_add(label, pred, weights, var): label_and_pred = math_ops.cast( math_ops.logical_and(label, pred), dtype=var.dtype) if weights is not None: label_and_pred *= math_ops.cast(weights, dtype=var.dtype) return var.assign_add(math_ops.reduce_sum(label_and_pred, 1)) loop_vars = { ConfusionMatrix.TRUE_POSITIVES: (label_is_pos, pred_is_pos), } update_tn = ConfusionMatrix.TRUE_NEGATIVES in variables_to_update update_fp = ConfusionMatrix.FALSE_POSITIVES in variables_to_update update_fn = ConfusionMatrix.FALSE_NEGATIVES in variables_to_update if update_fn or update_tn: pred_is_neg = math_ops.logical_not(pred_is_pos) loop_vars[ConfusionMatrix.FALSE_NEGATIVES] = (label_is_pos, pred_is_neg) if update_fp or update_tn: label_is_neg = math_ops.logical_not(label_is_pos) loop_vars[ConfusionMatrix.FALSE_POSITIVES] = (label_is_neg, pred_is_pos) if update_tn: loop_vars[ConfusionMatrix.TRUE_NEGATIVES] = (label_is_neg, pred_is_neg) for matrix_cond, (label, pred) in loop_vars.items(): if matrix_cond in variables_to_update: update_ops.append( weighted_assign_add(label, pred, weights_tiled, variables_to_update[matrix_cond])) return control_flow_ops.group(update_ops)
def from_uniform_row_length(cls, uniform_row_length, nvals, nrows=None, validate=True, preferred_dtype=None): """Creates a `RowPartition` with rows partitioned by `uniform_row_length`. This `RowPartition` divides a sequence `values` into rows that all have the same length: ```python partitioned_rows = [[values.pop(0) for _ in range(uniform_row_length)] for _ in range(nrows)] ``` Args: uniform_row_length: A scalar integer tensor. Must be nonnegative. The size of the outer axis of `values` must be evenly divisible by `uniform_row_length`. nvals: a non-negative scalar integer tensor for the number of values. nrows: The number of rows in the constructed RowPartition. If not specified, then it defaults to `nvals/uniform_row_length` (or `0` if `uniform_row_length==0`). `nrows` only needs to be specified if `uniform_row_length` might be zero. `uniform_row_length*nrows` must be `nvals`. validate: If true, then use assertions to check that the arguments form a valid `RowPartition`. preferred_dtype: if uniform_row_length has no dtype, use this one. Returns: A `RowPartition`. """ if not isinstance(validate, bool): raise TypeError("validate must have type bool") with ops.name_scope(None, "RowPartitionFromUniformRowLength", [uniform_row_length, nrows]): uniform_row_length = cls._convert_row_partition(uniform_row_length, "uniform_row_length", preferred_dtype) uniform_row_length.shape.assert_has_rank(0) # Find nrows. const_row_length = tensor_util.constant_value(uniform_row_length) if nrows is None: if const_row_length is None: # Avoid division by zero if uniform_row_length==0 (and nvals==0). rowlen_or_1 = math_ops.maximum( uniform_row_length, constant_op.constant(1, uniform_row_length.dtype)) nrows = nvals // rowlen_or_1 elif const_row_length == 0: nrows = 0 else: nrows = nvals // const_row_length nrows = ops.convert_to_tensor( nrows, uniform_row_length.dtype, name="nrows") const_nrows = tensor_util.constant_value(nrows) const_nvals = tensor_util.constant_value(nvals) # Find row_splits. if const_nrows is not None and const_row_length is not None: row_splits = [v * const_row_length for v in range(const_nrows + 1)] row_splits = constant_op.constant(row_splits, uniform_row_length.dtype) else: row_splits = math_ops.range(nrows + 1) * uniform_row_length if validate: checks = [] if (const_nrows is None or const_row_length is None or const_nvals is None): checks.append( check_ops.assert_equal( nrows * uniform_row_length, nvals, ("uniform_row_length", uniform_row_length, "times nrows", nrows, "must equal nvals", nvals))) else: if const_nrows * const_row_length != const_nvals: raise ValueError( "uniform_row_length=%d times nrows=%d must equal nvals=%d" % (const_row_length, const_nrows, const_nvals)) if uniform_row_length.shape.rank is None: checks.append( check_ops.assert_rank( uniform_row_length, 0, message="uniform_row_length must be a scalar.")) const_row_length = tensor_util.constant_value(uniform_row_length) if const_row_length is None: checks.append( check_ops.assert_greater_equal( uniform_row_length, constant_op.constant(0, uniform_row_length.dtype), message="uniform_row_length must be >= 0.")) else: if const_row_length < 0: raise ValueError("uniform_row_length must be >= 0.") row_splits = control_flow_ops.with_dependencies(checks, row_splits) return cls( row_splits=row_splits, uniform_row_length=uniform_row_length, nrows=nrows, internal=_row_partition_factory_key)
def _interpolate_bilinear(grid, query_points, name='interpolate_bilinear', indexing='ij'): """Similar to Matlab's interp2 function. Finds values for query points on a grid using bilinear interpolation. Args: grid: a 4-D float `Tensor` of shape `[batch, height, width, channels]`. query_points: a 3-D float `Tensor` of N points with shape `[batch, N, 2]`. name: a name for the operation (optional). indexing: whether the query points are specified as row and column (ij), or Cartesian coordinates (xy). Returns: values: a 3-D `Tensor` with shape `[batch, N, channels]` Raises: ValueError: if the indexing mode is invalid, or if the shape of the inputs invalid. """ if indexing != 'ij' and indexing != 'xy': raise ValueError('Indexing mode must be \'ij\' or \'xy\'') with ops.name_scope(name): grid = ops.convert_to_tensor(grid) query_points = ops.convert_to_tensor(query_points) shape = grid.get_shape().as_list() if len(shape) != 4: msg = 'Grid must be 4 dimensional. Received size: ' raise ValueError(msg + str(grid.get_shape())) batch_size, height, width, channels = (array_ops.shape(grid)[0], array_ops.shape(grid)[1], array_ops.shape(grid)[2], array_ops.shape(grid)[3]) shape = [batch_size, height, width, channels] query_type = query_points.dtype grid_type = grid.dtype with ops.control_dependencies([ check_ops.assert_equal( len(query_points.get_shape()), 3, message='Query points must be 3 dimensional.'), check_ops.assert_equal( array_ops.shape(query_points)[2], 2, message='Query points must be size 2 in dim 2.') ]): num_queries = array_ops.shape(query_points)[1] with ops.control_dependencies([ check_ops.assert_greater_equal( height, 2, message='Grid height must be at least 2.'), check_ops.assert_greater_equal( width, 2, message='Grid width must be at least 2.') ]): alphas = [] floors = [] ceils = [] index_order = [0, 1] if indexing == 'ij' else [1, 0] unstacked_query_points = array_ops.unstack(query_points, axis=2) for dim in index_order: with ops.name_scope('dim-' + str(dim)): queries = unstacked_query_points[dim] size_in_indexing_dimension = shape[dim + 1] # max_floor is size_in_indexing_dimension - 2 so that max_floor + 1 # is still a valid index into the grid. max_floor = math_ops.cast(size_in_indexing_dimension - 2, query_type) min_floor = constant_op.constant(0.0, dtype=query_type) floor = math_ops.minimum( math_ops.maximum(min_floor, math_ops.floor(queries)), max_floor) int_floor = math_ops.cast(floor, dtypes.int32) floors.append(int_floor) ceil = int_floor + 1 ceils.append(ceil) # alpha has the same type as the grid, as we will directly use alpha # when taking linear combinations of pixel values from the image. alpha = math_ops.cast(queries - floor, grid_type) min_alpha = constant_op.constant(0.0, dtype=grid_type) max_alpha = constant_op.constant(1.0, dtype=grid_type) alpha = math_ops.minimum(math_ops.maximum(min_alpha, alpha), max_alpha) # Expand alpha to [b, n, 1] so we can use broadcasting # (since the alpha values don't depend on the channel). alpha = array_ops.expand_dims(alpha, 2) alphas.append(alpha) with ops.control_dependencies([ check_ops.assert_less_equal( math_ops.cast(batch_size * height * width, dtype=dtypes.float32), np.iinfo(np.int32).max / 8, message="""The image size or batch size is sufficiently large that the linearized addresses used by array_ops.gather may exceed the int32 limit.""") ]): flattened_grid = array_ops.reshape( grid, [batch_size * height * width, channels]) batch_offsets = array_ops.reshape( math_ops.range(batch_size) * height * width, [batch_size, 1]) # This wraps array_ops.gather. We reshape the image data such that the # batch, y, and x coordinates are pulled into the first dimension. # Then we gather. Finally, we reshape the output back. It's possible this # code would be made simpler by using array_ops.gather_nd. def gather(y_coords, x_coords, name): with ops.name_scope('gather-' + name): linear_coordinates = batch_offsets + y_coords * width + x_coords gathered_values = array_ops.gather(flattened_grid, linear_coordinates) return array_ops.reshape(gathered_values, [batch_size, num_queries, channels]) # grab the pixel values in the 4 corners around each query point top_left = gather(floors[0], floors[1], 'top_left') top_right = gather(floors[0], ceils[1], 'top_right') bottom_left = gather(ceils[0], floors[1], 'bottom_left') bottom_right = gather(ceils[0], ceils[1], 'bottom_right') # now, do the actual interpolation with ops.name_scope('interpolate'): interp_top = alphas[1] * (top_right - top_left) + top_left interp_bottom = alphas[1] * (bottom_right - bottom_left) + bottom_left interp = alphas[0] * (interp_bottom - interp_top) + interp_top return interp
def predict(self, features): """Computes predictions multiple steps into the future. Args: features: A dictionary with the following key/value pairs: PredictionFeatures.TIMES: A [batch size, predict window size] integer Tensor of times, after the window of data indicated by `STATE_TUPLE`, to make predictions for. PredictionFeatures.STATE_TUPLE: A tuple of (times, values), times with shape [batch size, self.input_window_size], values with shape [batch size, self.input_window_size, self.num_features] representing a segment of the time series before `TIMES`. This data is used to start of the autoregressive computation. This should have data for at least self.input_window_size timesteps. And any exogenous features, with shapes prefixed by shape of `TIMES`. Returns: A dictionary with keys, "mean", "covariance". The values are Tensors of shape [batch_size, predict window size, num_features] and correspond to the values passed in `TIMES`. """ if not self._graph_initialized: self.initialize_graph() predict_times = math_ops.cast( ops.convert_to_tensor(features[PredictionFeatures.TIMES]), dtypes.int32) exogenous_regressors = self._process_exogenous_features( times=predict_times, features={key: value for key, value in features.items() if key not in [TrainEvalFeatures.TIMES, TrainEvalFeatures.VALUES, PredictionFeatures.STATE_TUPLE]}) with ops.control_dependencies( [check_ops.assert_equal(array_ops.shape(predict_times)[1], array_ops.shape(exogenous_regressors)[1])]): exogenous_regressors = array_ops.identity(exogenous_regressors) batch_size = array_ops.shape(predict_times)[0] num_predict_values = array_ops.shape(predict_times)[1] prediction_iterations = ((num_predict_values + self.output_window_size - 1) // self.output_window_size) # Pad predict_times and exogenous regressors so as to have exact multiple of # self.output_window_size values per example. padding_size = (prediction_iterations * self.output_window_size - num_predict_values) predict_times = array_ops.pad( predict_times, [[0, 0], [0, padding_size]]) exogenous_regressors = array_ops.pad( exogenous_regressors, [[0, 0], [0, padding_size], [0, 0]]) state = features[PredictionFeatures.STATE_TUPLE] (state_times, state_values, state_exogenous_regressors) = state state_times = math_ops.cast( ops.convert_to_tensor(state_times), dtypes.int32) state_values = ops.convert_to_tensor(state_values, dtype=self.dtype) state_exogenous_regressors = ops.convert_to_tensor( state_exogenous_regressors, dtype=self.dtype) initial_input_times = predict_times[:, :self.output_window_size] initial_input_exogenous_regressors = ( exogenous_regressors[:, :self.output_window_size, :]) if self.input_window_size > 0: initial_input_times = array_ops.concat( [state_times[:, -self.input_window_size:], initial_input_times], 1) values_size = array_ops.shape(state_values)[1] times_size = array_ops.shape(state_times)[1] with ops.control_dependencies([ check_ops.assert_greater_equal(values_size, self.input_window_size), check_ops.assert_equal(values_size, times_size) ]): initial_input_values = state_values[:, -self.input_window_size:, :] initial_input_exogenous_regressors = array_ops.concat( [state_exogenous_regressors[:, -self.input_window_size:, :], initial_input_exogenous_regressors[ :, :self.output_window_size, :]], axis=1) else: initial_input_values = 0 # Iterate over the predict_times, predicting self.output_window_size values # in each iteration. def _while_condition(iteration_number, *unused_args): return math_ops.less(iteration_number, prediction_iterations) def _while_body(iteration_number, input_times, input_values, input_exogenous_regressors, mean_ta, covariance_ta): """Predict self.output_window_size values.""" prediction_ops = self.prediction_ops( input_times, input_values, input_exogenous_regressors) predicted_mean = prediction_ops["mean"] predicted_covariance = prediction_ops["covariance"] offset = self.output_window_size * gen_math_ops.minimum( iteration_number + 1, prediction_iterations - 1) if self.input_window_size > 0: if self.output_window_size < self.input_window_size: new_input_values = array_ops.concat( [input_values[:, self.output_window_size:, :], predicted_mean], 1) new_input_exogenous_regressors = array_ops.concat( [input_exogenous_regressors[:, -self.input_window_size:, :], exogenous_regressors[ :, offset:offset + self.output_window_size, :]], axis=1) new_input_times = array_ops.concat([ input_times[:, -self.input_window_size:], predict_times[:, offset:offset + self.output_window_size] ], 1) else: new_input_values = predicted_mean[:, -self.input_window_size:, :] new_input_exogenous_regressors = exogenous_regressors[ :, offset - self.input_window_size:offset + self.output_window_size, :] new_input_times = predict_times[ :, offset - self.input_window_size:offset + self.output_window_size] else: new_input_values = input_values new_input_exogenous_regressors = exogenous_regressors[ :, offset:offset + self.output_window_size, :] new_input_times = predict_times[:, offset:offset + self.output_window_size] new_input_times.set_shape(initial_input_times.get_shape()) new_input_exogenous_regressors.set_shape( initial_input_exogenous_regressors.get_shape()) new_mean_ta = mean_ta.write(iteration_number, predicted_mean) if isinstance(covariance_ta, tensor_array_ops.TensorArray): new_covariance_ta = covariance_ta.write(iteration_number, predicted_covariance) else: new_covariance_ta = covariance_ta return (iteration_number + 1, new_input_times, new_input_values, new_input_exogenous_regressors, new_mean_ta, new_covariance_ta) # Note that control_flow_ops.while_loop doesn't seem happy with None. Hence # using 0 for cases where we don't want to predict covariance. covariance_ta_init = (tensor_array_ops.TensorArray( dtype=self.dtype, size=prediction_iterations) if self.loss != ARModel.SQUARED_LOSS else 0.) mean_ta_init = tensor_array_ops.TensorArray( dtype=self.dtype, size=prediction_iterations) _, _, _, _, mean_ta, covariance_ta = control_flow_ops.while_loop( _while_condition, _while_body, [ 0, initial_input_times, initial_input_values, initial_input_exogenous_regressors, mean_ta_init, covariance_ta_init ]) def _parse_ta(values_ta): """Helper function to parse the returned TensorArrays.""" if not isinstance(values_ta, tensor_array_ops.TensorArray): return None predictions_length = prediction_iterations * self.output_window_size # Shape [prediction_iterations, batch_size, self.output_window_size, # self.num_features] values_packed = values_ta.stack() # Transpose to move batch dimension outside. output_values = array_ops.reshape( array_ops.transpose(values_packed, [1, 0, 2, 3]), array_ops.stack([batch_size, predictions_length, -1])) # Clip to desired size return output_values[:, :num_predict_values, :] predicted_mean = _parse_ta(mean_ta) predicted_covariance = _parse_ta(covariance_ta) if predicted_covariance is None: predicted_covariance = array_ops.ones_like(predicted_mean) # Transform and scale the mean and covariance appropriately. predicted_mean = self._scale_back_data(predicted_mean) predicted_covariance = self._scale_back_variance(predicted_covariance) return {"mean": predicted_mean, "covariance": predicted_covariance}
def _confusion_matrix_at_thresholds(labels, predictions, thresholds, weights=None): with ops.control_dependencies([ check_ops.assert_greater_equal( predictions, math_ops.cast(0.0, dtype=predictions.dtype), message='predictions must be in [0, 1]'), check_ops.assert_less_equal( predictions, math_ops.cast(1.0, dtype=predictions.dtype), message='predictions must be in [0, 1]') ]): predictions, labels, weights = _remove_squeezable_dimensions( predictions=math_ops.to_float(predictions), labels=math_ops.cast(labels, dtype=dtypes.bool), weights=weights) num_thresholds = len(thresholds) # Reshape predictions and labels. predictions_2d = array_ops.reshape(predictions, [-1, 1]) labels_2d = array_ops.reshape(math_ops.cast(labels, dtype=dtypes.bool), [1, -1]) # Use static shape if known. num_predictions = predictions_2d.get_shape().as_list()[0] # Otherwise use dynamic shape. if num_predictions is None: num_predictions = array_ops.shape(predictions_2d)[0] thresh_tiled = array_ops.tile( array_ops.expand_dims(array_ops.constant(thresholds), [1]), array_ops.stack([1, num_predictions])) # Tile the predictions after threshold them across different thresholds. pred_is_pos = math_ops.greater( array_ops.tile(array_ops.transpose(predictions_2d), [num_thresholds, 1]), thresh_tiled) pred_is_neg = math_ops.logical_not(pred_is_pos) label_is_pos = array_ops.tile(labels_2d, [num_thresholds, 1]) label_is_neg = math_ops.logical_not(label_is_pos) if weights is not None: weights = weights_broadcast_ops.broadcast_weights( math_ops.to_float(weights), predictions) weights_tiled = array_ops.tile(array_ops.reshape(weights, [1, -1]), [num_thresholds, 1]) thresh_tiled.get_shape().assert_is_compatible_with( weights_tiled.get_shape()) else: weights_tiled = None values = {} # tp is_true_positive = math_ops.to_float( math_ops.logical_and(label_is_pos, pred_is_pos)) if weights_tiled is not None: is_true_positive *= weights_tiled values['tp'] = math_ops.reduce_sum(is_true_positive, 1) # fn is_false_negative = math_ops.to_float( math_ops.logical_and(label_is_pos, pred_is_neg)) if weights_tiled is not None: is_false_negative *= weights_tiled values['fn'] = math_ops.reduce_sum(is_false_negative, 1) # tn is_true_negative = math_ops.to_float( math_ops.logical_and(label_is_neg, pred_is_neg)) if weights_tiled is not None: is_true_negative *= weights_tiled values['tn'] = math_ops.reduce_sum(is_true_negative, 1) # fp is_false_positive = math_ops.to_float( math_ops.logical_and(label_is_neg, pred_is_pos)) if weights_tiled is not None: is_false_positive *= weights_tiled values['fp'] = math_ops.reduce_sum(is_false_positive, 1) return values
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 update_state(self, y_true, y_pred, sample_weight=None): # Cast inputs y_pred = tf.convert_to_tensor(y_pred) y_true = tf.cast(y_true, dtype=y_pred.dtype) # Transform inputs [y_pred, y_true ], _ = metrics_utils.ragged_assert_compatible_and_get_flat_values( [y_pred, y_true], sample_weight) # Check input values and adjust shapes with ops.control_dependencies([ check_ops.assert_greater_equal( y_pred, tf.cast(0.0, dtype=y_pred.dtype), message='predictions must be >= 0'), check_ops.assert_less_equal(y_pred, tf.cast(1.0, dtype=y_pred.dtype), message='predictions must be <= 1') ]): if sample_weight is None: y_pred, y_true = tf_losses_utils.squeeze_or_expand_dimensions( y_pred, y_true) else: y_pred, y_true, sample_weight = ( tf_losses_utils.squeeze_or_expand_dimensions( y_pred, y_true, sample_weight=sample_weight)) # Check shape compatibility y_pred.shape.assert_is_compatible_with(y_true.shape) # Get prediction shape pred_shape = tf.shape(y_pred) num_predictions = pred_shape[0] # Get lables (decode one-hot) y_pred_labels = K.flatten(tf.argmax(y_pred, axis=-1)) y_true_labels = K.flatten(tf.argmax(y_true, axis=-1)) # Set sample weights if sample_weight is not None: sample_weight = weights_broadcast_ops.broadcast_weights( tf.cast(sample_weight, dtype=y_pred.dtype), y_pred) weights_tiled = tf.gather( K.flatten(sample_weight), tf.range(start=0, limit=num_predictions * self.num_classes, delta=self.num_classes, dtype=tf.int64)) else: weights_tiled = None def _weighted_assign_add(label, pred, weights, var): return var.assign_add( tf.math.confusion_matrix(labels=label, predictions=pred, num_classes=self.num_classes, weights=weights, dtype=self.dtype)) # Set return value update_ops = [] # Update confusion matrix update_ops.append( _weighted_assign_add(y_true_labels, y_pred_labels, weights_tiled, self.confusion_matrix)) return tf.group(update_ops)
def embed_check_integer_casting_closed(x, target_dtype, assert_nonnegative=True, name="embed_check_casting_closed"): """Ensures integers remain unaffected despite casting to/from int/float types. Example integer-types: `uint8`, `int32`, `bool`. Example floating-types: `float32`, `float64`. The largest possible integer representable by an IEEE754 floating-point is `2**(1 + mantissa_bits)` yet the largest possible integer as an int-type is `2**(bits - 1) - 1`. This function ensures that a `Tensor` purporting to have integer-form values can be cast to some other type without loss of precision. The smallest representable integer is the negative of the largest representable integer, except for types: `uint8`, `uint16`, `bool`. For these types, the smallest representable integer is `0`. Args: x: `Tensor` representing integer-form values. target_dtype: TF `dtype` under which `x` should have identical values. assert_nonnegative: `bool` indicating `x` should contain nonnegative values. name: A name for this operation (optional). Returns: x: Input `Tensor` with appropriate assertions embedded. Raises: TypeError: if `x` is neither integer- nor floating-type. TypeError: if `target_dtype` is neither integer- nor floating-type. TypeError: if neither `x` nor `target_dtype` are integer-type. """ with ops.name_scope(name, values=[x]): x = ops.convert_to_tensor(x, name="x") if (not _is_integer_like_by_dtype(x.dtype) and not x.dtype.is_floating): raise TypeError("{}.dtype must be floating- or " "integer-type.".format(x.dtype.name)) if (not _is_integer_like_by_dtype(target_dtype) and not target_dtype.is_floating): raise TypeError("target_dtype ({}) must be floating- or " "integer-type.".format(target_dtype.name)) if (not _is_integer_like_by_dtype(x.dtype) and not _is_integer_like_by_dtype(target_dtype)): raise TypeError( "At least one of {}.dtype ({}) and target_dtype ({}) " "must be integer-type.".format(x.op.name, x.dtype.name, target_dtype.name)) assertions = [] if assert_nonnegative: assertions += [ check_ops.assert_non_negative( x, message="Elements must be non-negative."), ] if x.dtype.is_floating: # Being here means _is_integer_like_by_dtype(target_dtype) = True. # Since this check implies the magnitude check below, we need only it. assertions += [ assert_integer_form( x, int_dtype=target_dtype, message="Elements must be {}-equivalent.".format( target_dtype.name)), ] else: if (_largest_integer_by_dtype(x.dtype) > _largest_integer_by_dtype(target_dtype)): # Cast may lose integer precision. assertions += [ check_ops.assert_less_equal( x, _largest_integer_by_dtype(target_dtype), message=("Elements cannot exceed {}.".format( _largest_integer_by_dtype(target_dtype)))), ] if (not assert_nonnegative and (_smallest_integer_by_dtype( x.dtype) < _smallest_integer_by_dtype(target_dtype))): assertions += [ check_ops.assert_greater_equal( x, _smallest_integer_by_dtype(target_dtype), message=("Elements cannot be smaller than {}.".format( _smallest_integer_by_dtype(target_dtype)))), ] if not assertions: return x return control_flow_ops.with_dependencies(assertions, x)
def _interpolate_nearest3D(grid, query_points, name='interpolate_nearest3D', indexing='ij'): """ 3D nearest-neighbor interpolation based on tensorflow's _interpolate_bilinear Author: Junyu Chen Email: [email protected] """ if indexing != 'ij' and indexing != 'xy': raise ValueError('Indexing mode must be \'ij\' or \'xy\'') with ops.name_scope(name): grid = ops.convert_to_tensor(grid) query_points = ops.convert_to_tensor(query_points) shape = grid.get_shape().as_list() if len(shape) != 5: msg = 'Grid must be 5 dimensional. Received size: ' raise ValueError(msg + str(grid.get_shape())) batch_size, height, width, length, channels = (array_ops.shape(grid)[0], array_ops.shape(grid)[1], array_ops.shape(grid)[2], array_ops.shape(grid)[3], array_ops.shape(grid)[4]) shape = [batch_size, length, height, width, channels] query_type = query_points.dtype grid_type = grid.dtype with ops.control_dependencies([ check_ops.assert_equal( len(query_points.get_shape()), 3, message='Query points must be 3 dimensional.'), check_ops.assert_equal( array_ops.shape(query_points)[2], 3, message='Query points must be size 2 in dim 2.') ]): num_queries = array_ops.shape(query_points)[1] with ops.control_dependencies([ check_ops.assert_greater_equal( height, 2, message='Grid height must be at least 2.'), check_ops.assert_greater_equal( width, 2, message='Grid width must be at least 2.') ]): alphas = [] floors = [] ceils = [] index_order = [0, 1, 2] if indexing == 'ij' else [2, 1, 0] unstacked_query_points = array_ops.unstack(query_points, axis=2) for dim in index_order: with ops.name_scope('dim-' + str(dim)): queries = unstacked_query_points[dim] size_in_indexing_dimension = shape[dim + 1] # max_floor is size_in_indexing_dimension - 2 so that max_floor + 1 # is still a valid index into the grid. max_floor = math_ops.cast(size_in_indexing_dimension - 2, query_type) min_floor = constant_op.constant(0.0, dtype=query_type) floor = math_ops.minimum( math_ops.maximum(min_floor, math_ops.floor(queries)), max_floor) int_floor = math_ops.cast(floor, dtypes.int32) floors.append(int_floor) ceil = int_floor + 1 ceils.append(ceil) # alpha has the same type as the grid, as we will directly use alpha # when taking linear combinations of pixel values from the image. alpha = math_ops.cast(queries - floor, grid_type) min_alpha = constant_op.constant(0.0, dtype=grid_type) max_alpha = constant_op.constant(1.0, dtype=grid_type) alpha = math_ops.minimum(math_ops.maximum(min_alpha, alpha), max_alpha) # Expand alpha to [b, n, 1] so we can use broadcasting # (since the alpha values don't depend on the channel). alpha = array_ops.expand_dims(alpha, 2) alphas.append(alpha) with ops.control_dependencies([ check_ops.assert_less_equal( math_ops.cast(batch_size * height * width * length, dtype=dtypes.float32), np.iinfo(np.int32).max / 8, message="""The image size or batch size is sufficiently large that the linearized addresses used by array_ops.gather may exceed the int32 limit.""") ]): flattened_grid = array_ops.reshape( grid, [batch_size * height * width * length, channels]) batch_offsets = array_ops.reshape( math_ops.range(batch_size) * height * width * length, [batch_size, 1]) # This wraps array_ops.gather. We reshape the image data such that the # batch, y, and x coordinates are pulled into the first dimension. # Then we gather. Finally, we reshape the output back. It's possible this # code would be made simpler by using array_ops.gather_nd. def gather(z_coords, x_coords, y_coords, name): with ops.name_scope('gather-' + name): linear_coordinates = batch_offsets + (y_coords*width+z_coords)*length + x_coords gathered_values = array_ops.gather(flattened_grid, linear_coordinates) return array_ops.reshape(gathered_values, [batch_size, num_queries, channels]) # grab the pixel values in the 8 corners around each query point btop_left = gather(floors[2], floors[0], floors[1], 'btop_left') btop_right = gather(floors[2], floors[0], ceils[1], 'btop_right') bbottom_left = gather(floors[2], ceils[0], floors[1], 'bbottom_left') bbottom_right = gather(floors[2], ceils[0], ceils[1], 'bbottom_right') ttop_left = gather(ceils[2], floors[0], floors[1], 'ttop_left') ttop_right = gather(ceils[2], floors[0], ceils[1], 'ttop_right') tbottom_left = gather(ceils[2], ceils[0], floors[1], 'tbottom_left') tbottom_right = gather(ceils[2], ceils[0], ceils[1], 'tbottom_right') # now, do the actual interpolation #alphas = [0.1,0.1,0.1] with ops.name_scope('interpolate'): interp_top = K.round(alphas[1]) * (btop_right - btop_left) + btop_left interp_bottom = K.round(alphas[1]) * (bbottom_right - bbottom_left) + bbottom_left interp1 = K.round(alphas[0]) * (interp_bottom - interp_top) + interp_top interp_top = K.round(alphas[1]) * (ttop_right - ttop_left) + ttop_left interp_bottom = K.round(alphas[1]) * (tbottom_right - tbottom_left) + tbottom_left interp2 = K.round(alphas[0]) * (interp_bottom - interp_top) + interp_top interp = K.round(alphas[2]) * (interp2 - interp1) + interp1 return interp