import numpy as np def softmax_func(y): """Computes softmax activations. This function performs the equivalent of softmax = tf.exp(logits) / tf.reduce_sum(tf.exp(logits)) another form: np.exp(y)/np.sum(np.exp(y),axis=1,keepdims=True) """ b = y - np.max(y, axis=1, keepdims=True) expb = np.exp(b) softmax = expb / np.sum(expb, axis=1, keepdims=True) return softmax def confusion_matrix_at_thresholds(labels, predictions, thresholds, includes=None): """Computes true_positives, false_negatives, true_negatives, false_positives. Args: labels: A np.array whose shape matches `predictions`. Will be cast to `bool`. predictions: A floating point np.array of arbitrary shape and whose values are in the range `[0, 1]`. thresholds: A python list or tuple of float thresholds in `[0, 1]`. includes: Tuple of keys to return, from 'tp', 'fn', 'tn', fp'. If `None`, default to all four. Returns: values: Dict of variables of shape `[len(thresholds)]`. Keys are from `includes`. """ all_includes = ('tp', 'fn', 'tn', 'fp') if includes is None: includes = all_includes else: for include in includes: if include not in all_includes: raise ValueError('Invaild key: %s.' % include) # Reshape predictions and labels. # This function is often used in dichotomies. # In multi-classification problems, we often stretch the dimensions directly into dichotomies. predictions_2d = np.reshape(predictions, [-1, 1]) labels_2d = np.reshape(labels.astype(dtype=np.bool), [1, -1]) num_predictions = predictions_2d.shape[0] num_thresholds = len(thresholds) # thresh_tiled's shape:[num_thresholds,num_predictions] thresh_tiled = np.tile( np.expand_dims(np.array(thresholds), axis=1), [1, num_predictions]) pred_is_pos = np.greater( np.tile(np.transpose(predictions_2d), [num_thresholds, 1]), thresh_tiled) if ('fn' in includes) or ('tn' in includes): pred_is_neg = np.logical_not(pred_is_pos) # Tile labels by number of thresholds # label_is_pos's shape:[num_thresholds,num_predictions] label_is_pos = np.tile(labels_2d, [num_thresholds, 1]) if ('fp' in includes) or ('tn' in includes): label_is_neg = np.logical_not(label_is_pos) values = {} if 'tp' in includes: is_true_positive = np.logical_and( label_is_pos, pred_is_pos).astype(np.float32) values['tp'] = np.sum(is_true_positive, axis=1) if 'fn' in includes: is_false_negative = np.logical_and( label_is_pos, pred_is_neg).astype(np.float32) values['fn'] = np.sum(is_false_negative, axis=1) if 'tn' in includes: is_true_negative = np.logical_and( label_is_neg, pred_is_neg).astype(np.float32) values['tn'] = np.sum(is_true_negative, axis=1) if 'fp' in includes: is_false_positive = np.logical_and( label_is_neg, pred_is_pos).astype(np.float32) values['fp'] = np.sum(is_false_positive, axis=1) return values def roc_pr_curve(values, curve='ROC'): """Computes the roc-auc or pr-auc based on confusion counts. Args: values: A dict from the func:confusion_matrix_at_thresholds and must have four keys:tp,fp,fn,tn curve: Specifies the name of the curve to be computed, 'ROC' [default] or 'PR' for the Precision-Recall-curve. Returns: x_axis: A python list of the curve's x-axis. In ROC it's fpr;In PR it's Recall. y_axis:A python list of the curve's y-axis. In ROC it's tpr;In PR it's Precision. fpr=fp/(fp+tn) tpr=tp/(tp+fn) Recall=tpr Precision=tp/(tp+fp) """ if 'tp' not in values.keys(): raise ValueError('values must have the key tp') if 'fp' not in values.keys(): raise ValueError('values must have the key fp') if 'fn' not in values.keys(): raise ValueError('values must have the key fn') if 'tn' not in values.keys(): raise ValueError('values must have the key tn') tp = values['tp'] fp = values['fp'] fn = values['fn'] tn = values['tn'] # Add epsilons to avoid dividing by 0. epsilon = 1.0e-6 rec = np.divide(tp + epsilon, tp + fn + epsilon) if curve == 'ROC': fp_rate = np.divide(fp + epsilon, fp + tn + epsilon) x_axis = fp_rate y_axis = rec else: # curve == 'PR'. prec = np.divide(tp + epsilon, tp + fp + epsilon) x_axis = rec y_axis = prec return x_axis, y_axis def auc(labels, predictions, num_thresholds=200, curve='ROC'): """Computes the approximate AUC via a Riemann sum. We get four variables `true_positives`,`true_negatives`, `false_positives` and `false_negatives` that are used to compute the AUC first. And then compute auc_curve using the function roc_pr_curve. The `num_thresholds` variable controls the degree of discretization with larger numbers of thresholds more closely approximating the true AUC. For best results, `predictions` should be distributed approximately uniformly in the range [0, 1] and not peaked around 0 or 1. Args: labels: A np.array whose shape matches `predictions`. Will be cast to `bool`. predictions: A floating point np.array of arbitrary shape and whose values are in the range `[0, 1]`. num_thresholds: The number of thresholds to use when discretizing the roc curve. curve: Specifies the name of the curve to be computed, 'ROC' [default] or 'PR' for the Precision-Recall-curve. Returns: auc: A scalar representing the current area-under-curve. """ kepsilon = 1e-7 # to account for floating point imprecisions thresholds = [(i + 1) * 1.0 / (num_thresholds - 1) for i in range(num_thresholds - 2)] thresholds = [0.0 - kepsilon] + thresholds + [1.0 + kepsilon] values = confusion_matrix_at_thresholds(labels, predictions, thresholds) x_axis, y_axis = roc_pr_curve(values, curve=curve) auc_value = np.sum(np.multiply( x_axis[:num_thresholds - 1] - x_axis[1:], (y_axis[:num_thresholds - 1] + y_axis[1:]) / 2.)) return auc_value def accuracy(labels, predictions): """Calculates the degree of `predictions` matches `labels`. Args: labels: A np.array whose shape matches `predictions`. predictions: A floating point np.array of arbitrary shape and it's the predicted value. returns: accuracy: A accuracy, the value of `total` divided by `count`. """ acc_val = np.equal( np.argmax(labels, 1), np.argmax(predictions, 1)).astype(np.float32) accuracy = np.mean(acc_val) return accuracy def confusion_matrix_one_hot(labels, predictions): """Computes true_positives, false_negatives, true_negatives, false_positives. Args: labels: A np.array whose shape matches `predictions` and must be one_hot. Will be cast to `bool`. predictions: A floating point np.array of arbitrary shape. Returns: values: Dict of variables of shape `[predictions.shape[1]]`. example: labels:[[1,0,0] [0,1,0] [0,0,1]] predictions: [[9.1,5.0,7.8] true [0.3,0.7,1.4] false [4.3,1.3,5.3]] true returns: values{'tp':[1,0,1] 'tn':[2,2,1] 'fp':[0,0,1] 'fn':[0,1,0] } """ # transpose prediction to one hot.for the example above,it will be: # [[1,0,0] # [0,0,1] # [0,0,1]] prediction_one_hot = np.eye(predictions.shape[1])[ np.argmax(predictions, axis=1)] values = {} is_true_positive = np.logical_and( np.equal(labels, True), np.equal(prediction_one_hot, True)) is_false_positive = np.logical_and( np.equal(labels, False), np.equal(prediction_one_hot, True)) is_true_negatives = np.logical_and( np.equal(labels, False), np.equal(prediction_one_hot, False)) is_false_negatives = np.logical_and( np.equal(labels, True), np.equal(prediction_one_hot, False)) values['tp'] = np.sum( is_true_positive.astype(dtype=np.float32), axis=0) values['fp'] = np.sum( is_false_positive.astype(dtype=np.float32), axis=0) values['tn'] = np.sum( is_true_negatives.astype(dtype=np.float32), axis=0) values['fn'] = np.sum( is_false_negatives.astype(dtype=np.float32), axis=0) return values def precision_score_one_hot(labels, predictions, average=None): """compute precision score, precision=tp/(tp+fp) the labels must be one_hot. the predictions is prediction results. Args: labels: A np.array whose shape matches `predictions` and must be one_hot. Will be cast to `bool`. predictions: A floating point np.array of arbitrary shape. average : string, [None(default), 'micro', 'macro',] This parameter is required for multiclass/multilabel targets. If ``None``, the scores for each class are returned. Otherwise, this determines the type of averaging performed on the data: ``'micro'``: Calculate metrics globally by counting the total true positives, false negatives and false positives. ``'macro'``: Calculate metrics for each label, and find their unweighted mean. This does not take label imbalance into account. Returns: values: A score . References ----------------------- [1] https://blog.csdn.net/sinat_28576553/article/details/80258619 """ # Add epsilons to avoid dividing by 0. epsilon = 1.0e-6 values = confusion_matrix_one_hot(labels, predictions) if average is None: tp = values['tp'] fp = values['fp'] p = np.divide(tp+epsilon, tp + fp+epsilon) return p elif average == 'micro': tp = np.sum(values['tp']) fp = np.sum(values['fp']) return np.divide(tp+epsilon, tp + fp+epsilon) elif average == 'macro': tp = values['tp'] fp = values['fp'] p = np.divide(tp+epsilon, tp + fp+epsilon) return np.average(p) else: raise ValueError('Invaild average: %s.' % average) def recall_score_one_hot(labels, predictions, average=None): """compute recall score, precision=tp/(tp+fn) the labels must be one_hot. the predictions is prediction results. Args: labels: A np.array whose shape matches `predictions` and must be one_hot. Will be cast to `bool`. predictions: A floating point np.array of arbitrary shape. average : string, [None(default), 'micro', 'macro',] This parameter is required for multiclass/multilabel targets. If ``None``, the scores for each class are returned. Otherwise, this determines the type of averaging performed on the data: ``'micro'``: Calculate metrics globally by counting the total true positives, false negatives and false positives. ``'macro'``: Calculate metrics for each label, and find their unweighted mean. This does not take label imbalance into account. Returns: values: A score . References ----------------------- [1] https://blog.csdn.net/sinat_28576553/article/details/80258619 """ # Add epsilons to avoid dividing by 0. epsilon = 1.0e-6 values = confusion_matrix_one_hot(labels, predictions) if average is None: tp = values['tp'] fn = values['fn'] p = np.divide(tp+epsilon, tp + fn+epsilon) return p elif average == 'micro': tp = np.sum(values['tp']) fn = np.sum(values['fn']) return np.divide(tp+epsilon, tp + fn+epsilon) elif average == 'macro': tp = values['tp'] fn = values['fn'] p = np.divide(tp+epsilon, tp + fn+epsilon) return np.average(p) else: raise ValueError('Invaild average: %s.' % average) def f_score_one_hot(labels, predictions, beta=1.0, average=None): """compute f score, =(1+beta*beta)precision*recall/(beta*beta*precision+recall) the labels must be one_hot. the predictions is prediction results. Args: labels: A np.array whose shape matches `predictions` and must be one_hot. Will be cast to `bool`. predictions: A floating point np.array of arbitrary shape. average : string, [None(default), 'micro', 'macro',] This parameter is required for multiclass/multilabel targets. If ``None``, the scores for each class are returned. Otherwise, this determines the type of averaging performed on the data: ``'micro'``: Calculate metrics globally by counting the total true positives, false negatives and false positives. ``'macro'``: Calculate metrics for each label, and find their unweighted mean. This does not take label imbalance into account. Returns: values: A score float. References ----------------------- [1] https://blog.csdn.net/sinat_28576553/article/details/80258619 """ if beta < 0: raise ValueError("beta should be >=0 in the F-beta score") beta2 = beta ** 2 p = precision_score_one_hot(labels, predictions, average=average) r = recall_score_one_hot(labels, predictions, average=average) # In the functions:precision and recall,add a epsilon,so p and r will # not be zero. f = (1+beta2)*p*r/(beta2*p+r) if average is None or average == 'micro': p = precision_score_one_hot(labels, predictions, average=average) r = recall_score_one_hot(labels, predictions, average=average) f = (1 + beta2) * p * r / (beta2 * p + r) return f elif average == 'macro': p = precision_score_one_hot(labels, predictions, average=None) r = recall_score_one_hot(labels, predictions, average=None) f = (1 + beta2) * p * r / (beta2 * p + r) return np.average(f) else: raise ValueError('Invaild average: %s.' % average)