def auc_using_histogram(boolean_labels, scores, score_range, nbins=100, collections=None, check_shape=True, name=None): """AUC computed by maintaining histograms. Rather than computing AUC directly, this Op maintains Variables containing histograms of the scores associated with `True` and `False` labels. By comparing these the AUC is generated, with some discretization error. See: "Efficient AUC Learning Curve Calculation" by Bouckaert. This AUC Op updates in `O(batch_size + nbins)` time and works well even with large class imbalance. The accuracy is limited by discretization error due to finite number of bins. If scores are concentrated in a fewer bins, accuracy is lower. If this is a concern, we recommend trying different numbers of bins and comparing results. Args: boolean_labels: 1-D boolean `Tensor`. Entry is `True` if the corresponding record is in class. scores: 1-D numeric `Tensor`, same shape as boolean_labels. score_range: `Tensor` of shape `[2]`, same dtype as `scores`. The min/max values of score that we expect. Scores outside range will be clipped. nbins: Integer number of bins to use. Accuracy strictly increases as the number of bins increases. collections: List of graph collections keys. Internal histogram Variables are added to these collections. Defaults to `[GraphKeys.LOCAL_VARIABLES]`. check_shape: Boolean. If `True`, do a runtime shape check on the scores and labels. name: A name for this Op. Defaults to "auc_using_histogram". Returns: auc: `float32` scalar `Tensor`. Fetching this converts internal histograms to auc value. update_op: `Op`, when run, updates internal histograms. """ if collections is None: collections = [ops.GraphKeys.LOCAL_VARIABLES] with variable_scope.variable_scope( name, 'auc_using_histogram', [boolean_labels, scores, score_range]): scores, boolean_labels = tensor_util.remove_squeezable_dimensions( scores, boolean_labels) score_range = ops.convert_to_tensor(score_range, name='score_range') boolean_labels, scores = _check_labels_and_scores( boolean_labels, scores, check_shape) hist_true, hist_false = _make_auc_histograms(boolean_labels, scores, score_range, nbins) hist_true_acc, hist_false_acc, update_op = _auc_hist_accumulate(hist_true, hist_false, nbins, collections) auc = _auc_convert_hist_to_auc(hist_true_acc, hist_false_acc, nbins) return auc, update_op
def auc_using_histogram(boolean_labels, scores, score_range, nbins=100, collections=None, check_shape=True, name=None): """AUC computed by maintaining histograms. Rather than computing AUC directly, this Op maintains Variables containing histograms of the scores associated with `True` and `False` labels. By comparing these the AUC is generated, with some discretization error. See: "Efficient AUC Learning Curve Calculation" by Bouckaert. This AUC Op updates in `O(batch_size + nbins)` time and works well even with large class imbalance. The accuracy is limited by discretization error due to finite number of bins. If scores are concentrated in a fewer bins, accuracy is lower. If this is a concern, we recommend trying different numbers of bins and comparing results. Args: boolean_labels: 1-D boolean `Tensor`. Entry is `True` if the corresponding record is in class. scores: 1-D numeric `Tensor`, same shape as boolean_labels. score_range: `Tensor` of shape `[2]`, same dtype as `scores`. The min/max values of score that we expect. Scores outside range will be clipped. nbins: Integer number of bins to use. Accuracy strictly increases as the number of bins increases. collections: List of graph collections keys. Internal histogram Variables are added to these collections. Defaults to `[GraphKeys.LOCAL_VARIABLES]`. check_shape: Boolean. If `True`, do a runtime shape check on the scores and labels. name: A name for this Op. Defaults to "auc_using_histogram". Returns: auc: `float32` scalar `Tensor`. Fetching this converts internal histograms to auc value. update_op: `Op`, when run, updates internal histograms. """ if collections is None: collections = [ops.GraphKeys.LOCAL_VARIABLES] with variable_scope.variable_scope(name, 'auc_using_histogram', [boolean_labels, scores, score_range]): scores, boolean_labels = tensor_util.remove_squeezable_dimensions( scores, boolean_labels) score_range = ops.convert_to_tensor(score_range, name='score_range') boolean_labels, scores = _check_labels_and_scores( boolean_labels, scores, check_shape) hist_true, hist_false = _make_auc_histograms(boolean_labels, scores, score_range, nbins) hist_true_acc, hist_false_acc, update_op = _auc_hist_accumulate( hist_true, hist_false, nbins, collections) auc = _auc_convert_hist_to_auc(hist_true_acc, hist_false_acc, nbins) return auc, update_op
def _remove_squeezable_dimensions(predictions, labels, weights): """Squeeze last dim if needed. Squeezes `predictions` and `labels` if their rank differs by 1. Squeezes `weights` if its rank is 1 more than the new rank of `predictions` This will use static shape if available. Otherwise, it will add graph operations, which could result in a performance hit. Args: predictions: Predicted values, a `Tensor` of arbitrary dimensions. labels: Label values, a `Tensor` whose dimensions match `predictions`. weights: optional `weights` tensor. It will be squeezed if its rank is 1 more than the new rank of `predictions` Returns: Tuple of `predictions`, `labels` and `weights`, possibly with the last dimension squeezed. """ predictions, labels = tensor_util.remove_squeezable_dimensions( predictions, labels) predictions.get_shape().assert_is_compatible_with(labels.get_shape()) if weights is not None: predictions_shape = predictions.get_shape() predictions_rank = predictions_shape.ndims weights_shape = weights.get_shape() weights_rank = weights_shape.ndims if (predictions_rank is not None) and (weights_rank is not None): # Use static rank. if weights_rank - predictions_rank == 1: weights = array_ops.squeeze(weights, [-1]) elif (weights_rank is None) or ( weights_shape.dims[-1].is_compatible_with(1)): # Use dynamic rank weights = control_flow_ops.cond( math_ops.equal(array_ops.rank(weights), math_ops.add(array_ops.rank(predictions), 1)), lambda: array_ops.squeeze(weights, [-1]), lambda: weights) return predictions, labels, weights
def confusion_matrix(predictions, labels, num_classes=None, dtype=dtypes.int32, name=None, weights=None): """Computes the confusion matrix from predictions and labels. Calculate the Confusion Matrix for a pair of prediction and label 1-D int arrays. The matrix rows represent the prediction labels and the columns represents the real labels. The confusion matrix is always a 2-D array of shape `[n, n]`, where `n` is the number of valid labels for a given classification task. Both prediction and labels must be 1-D arrays of the same shape in order for this function to work. If `num_classes` is None, then `num_classes` will be set to the one plus the maximum value in either predictions or labels. Class labels are expected to start at 0. E.g., if `num_classes` was three, then the possible labels would be `[0, 1, 2]`. If `weights` is not `None`, then each prediction contributes its corresponding weight to the total value of the confusion matrix cell. For example: ```python tf.contrib.metrics.confusion_matrix([1, 2, 4], [2, 2, 4]) ==> [[0 0 0 0 0] [0 0 1 0 0] [0 0 1 0 0] [0 0 0 0 0] [0 0 0 0 1]] ``` Note that the possible labels are assumed to be `[0, 1, 2, 3, 4]`, resulting in a 5x5 confusion matrix. Args: predictions: A 1-D array representing the predictions for a given classification. labels: A 1-D representing the real labels for the classification task. num_classes: The possible number of labels the classification task can have. If this value is not provided, it will be calculated using both predictions and labels array. dtype: Data type of the confusion matrix. name: Scope name. weights: An optional `Tensor` whose shape matches `predictions`. Returns: A k X k matrix representing the confusion matrix, where k is the number of possible labels in the classification task. Raises: ValueError: If both predictions and labels are not 1-D vectors and have mismatched shapes, or if `weights` is not `None` and its shape doesn't match `predictions`. """ with ops.name_scope(name, 'confusion_matrix', [predictions, labels, num_classes]) as name: predictions, labels = tensor_util.remove_squeezable_dimensions( ops.convert_to_tensor( predictions, name='predictions'), ops.convert_to_tensor(labels, name='labels')) predictions = math_ops.cast(predictions, dtypes.int64) labels = math_ops.cast(labels, dtypes.int64) if num_classes is None: num_classes = math_ops.maximum(math_ops.reduce_max(predictions), math_ops.reduce_max(labels)) + 1 if weights is not None: predictions.get_shape().assert_is_compatible_with(weights.get_shape()) weights = math_ops.cast(weights, dtype) shape = array_ops.pack([num_classes, num_classes]) indices = array_ops.transpose(array_ops.pack([predictions, labels])) values = (array_ops.ones_like(predictions, dtype) if weights is None else weights) cm_sparse = sparse_tensor.SparseTensor( indices=indices, values=values, shape=math_ops.to_int64(shape)) zero_matrix = array_ops.zeros(math_ops.to_int32(shape), dtype) return sparse_ops.sparse_add(zero_matrix, cm_sparse)
def confusion_matrix(predictions, labels, num_classes=None, dtype=dtypes.int32, name=None, weights=None): """Computes the confusion matrix from predictions and labels. Calculate the Confusion Matrix for a pair of prediction and label 1-D int arrays. Considering a prediction array such as: `[1, 2, 3]` And a label array such as: `[2, 2, 3]` The confusion matrix returned would be the following one: [[0, 0, 0] [0, 1, 0] [0, 1, 0] [0, 0, 1]] If `weights` is not None, then the confusion matrix elements are the corresponding `weights` elements. Where the matrix rows represent the prediction labels and the columns represents the real labels. The confusion matrix is always a 2-D array of shape [n, n], where n is the number of valid labels for a given classification task. Both prediction and labels must be 1-D arrays of the same shape in order for this function to work. Args: predictions: A 1-D array represeting the predictions for a given classification. labels: A 1-D represeting the real labels for the classification task. num_classes: The possible number of labels the classification task can have. If this value is not provided, it will be calculated using both predictions and labels array. dtype: Data type of the confusion matrix. name: Scope name. weights: An optional `Tensor` whose shape matches `predictions`. Returns: A k X k matrix represeting the confusion matrix, where k is the number of possible labels in the classification task. Raises: ValueError: If both predictions and labels are not 1-D vectors and have mismatched shapes, or if `weights` is not `None` and its shape doesn't match `predictions`. """ with ops.name_scope(name, 'confusion_matrix', [predictions, labels, num_classes]) as name: predictions, labels = tensor_util.remove_squeezable_dimensions( ops.convert_to_tensor(predictions, name='predictions'), ops.convert_to_tensor(labels, name='labels')) predictions = math_ops.cast(predictions, dtypes.int64) labels = math_ops.cast(labels, dtypes.int64) if num_classes is None: num_classes = math_ops.maximum(math_ops.reduce_max(predictions), math_ops.reduce_max(labels)) + 1 if weights is not None: predictions.get_shape().assert_is_compatible_with( weights.get_shape()) weights = math_ops.cast(weights, dtype) shape = array_ops.pack([num_classes, num_classes]) indices = array_ops.transpose(array_ops.pack([predictions, labels])) values = (array_ops.ones_like(predictions, dtype) if weights is None else weights) cm_sparse = ops.SparseTensor(indices=indices, values=values, shape=math_ops.to_int64(shape)) zero_matrix = array_ops.zeros(math_ops.to_int32(shape), dtype) return sparse_ops.sparse_add(zero_matrix, cm_sparse)
def confusion_matrix(predictions, labels, num_classes=None, dtype=dtypes.int32, name=None, weights=None): """Computes the confusion matrix from predictions and labels. Calculate the Confusion Matrix for a pair of prediction and label 1-D int arrays. Considering a prediction array such as: `[1, 2, 3]` And a label array such as: `[2, 2, 3]` The confusion matrix returned would be the following one: [[0, 0, 0] [0, 1, 0] [0, 1, 0] [0, 0, 1]] If `weights` is not None, then the confusion matrix elements are the corresponding `weights` elements. Where the matrix rows represent the prediction labels and the columns represents the real labels. The confusion matrix is always a 2-D array of shape [n, n], where n is the number of valid labels for a given classification task. Both prediction and labels must be 1-D arrays of the same shape in order for this function to work. Args: predictions: A 1-D array represeting the predictions for a given classification. labels: A 1-D represeting the real labels for the classification task. num_classes: The possible number of labels the classification task can have. If this value is not provided, it will be calculated using both predictions and labels array. dtype: Data type of the confusion matrix. name: Scope name. weights: An optional `Tensor` whose shape matches `predictions`. Returns: A k X k matrix represeting the confusion matrix, where k is the number of possible labels in the classification task. Raises: ValueError: If both predictions and labels are not 1-D vectors and have mismatched shapes, or if `weights` is not `None` and its shape doesn't match `predictions`. """ with ops.name_scope(name, "confusion_matrix", [predictions, labels, num_classes]) as name: predictions, labels = tensor_util.remove_squeezable_dimensions( ops.convert_to_tensor(predictions, name="predictions"), ops.convert_to_tensor(labels, name="labels") ) predictions = math_ops.cast(predictions, dtypes.int64) labels = math_ops.cast(labels, dtypes.int64) if num_classes is None: num_classes = math_ops.maximum(math_ops.reduce_max(predictions), math_ops.reduce_max(labels)) + 1 if weights is not None: predictions.get_shape().assert_is_compatible_with(weights.get_shape()) weights = math_ops.cast(weights, dtype) shape = array_ops.pack([num_classes, num_classes]) indices = array_ops.transpose(array_ops.pack([predictions, labels])) values = array_ops.ones_like(predictions, dtype) if weights is None else weights cm_sparse = ops.SparseTensor(indices=indices, values=values, shape=math_ops.to_int64(shape)) zero_matrix = array_ops.zeros(math_ops.to_int32(shape), dtype) return sparse_ops.sparse_add(zero_matrix, cm_sparse)