You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

metrics.py 15 kB

5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. import numpy as np
  2. def softmax_func(y):
  3. """Computes softmax activations.
  4. This function performs the equivalent of
  5. softmax = tf.exp(logits) / tf.reduce_sum(tf.exp(logits))
  6. another form: np.exp(y)/np.sum(np.exp(y),axis=1,keepdims=True)
  7. """
  8. b = y - np.max(y, axis=1, keepdims=True)
  9. expb = np.exp(b)
  10. softmax = expb / np.sum(expb, axis=1, keepdims=True)
  11. return softmax
  12. def confusion_matrix_at_thresholds(labels, predictions, thresholds, includes=None):
  13. """Computes true_positives, false_negatives, true_negatives, false_positives.
  14. Args:
  15. labels: A np.array whose shape matches `predictions`. Will be cast to
  16. `bool`.
  17. predictions: A floating point np.array of arbitrary shape and whose values
  18. are in the range `[0, 1]`.
  19. thresholds: A python list or tuple of float thresholds in `[0, 1]`.
  20. includes: Tuple of keys to return, from 'tp', 'fn', 'tn', fp'. If `None`,
  21. default to all four.
  22. Returns:
  23. values: Dict of variables of shape `[len(thresholds)]`. Keys are from
  24. `includes`.
  25. """
  26. all_includes = ('tp', 'fn', 'tn', 'fp')
  27. if includes is None:
  28. includes = all_includes
  29. else:
  30. for include in includes:
  31. if include not in all_includes:
  32. raise ValueError('Invaild key: %s.' % include)
  33. # Reshape predictions and labels.
  34. # This function is often used in dichotomies.
  35. # In multi-classification problems, we often stretch the dimensions directly into dichotomies.
  36. predictions_2d = np.reshape(predictions, [-1, 1])
  37. labels_2d = np.reshape(labels.astype(dtype=np.bool), [1, -1])
  38. num_predictions = predictions_2d.shape[0]
  39. num_thresholds = len(thresholds)
  40. # thresh_tiled's shape:[num_thresholds,num_predictions]
  41. thresh_tiled = np.tile(
  42. np.expand_dims(np.array(thresholds), axis=1), [1, num_predictions])
  43. pred_is_pos = np.greater(
  44. np.tile(np.transpose(predictions_2d), [num_thresholds, 1]),
  45. thresh_tiled)
  46. if ('fn' in includes) or ('tn' in includes):
  47. pred_is_neg = np.logical_not(pred_is_pos)
  48. # Tile labels by number of thresholds
  49. # label_is_pos's shape:[num_thresholds,num_predictions]
  50. label_is_pos = np.tile(labels_2d, [num_thresholds, 1])
  51. if ('fp' in includes) or ('tn' in includes):
  52. label_is_neg = np.logical_not(label_is_pos)
  53. values = {}
  54. if 'tp' in includes:
  55. is_true_positive = np.logical_and(
  56. label_is_pos, pred_is_pos).astype(np.float32)
  57. values['tp'] = np.sum(is_true_positive, axis=1)
  58. if 'fn' in includes:
  59. is_false_negative = np.logical_and(
  60. label_is_pos, pred_is_neg).astype(np.float32)
  61. values['fn'] = np.sum(is_false_negative, axis=1)
  62. if 'tn' in includes:
  63. is_true_negative = np.logical_and(
  64. label_is_neg, pred_is_neg).astype(np.float32)
  65. values['tn'] = np.sum(is_true_negative, axis=1)
  66. if 'fp' in includes:
  67. is_false_positive = np.logical_and(
  68. label_is_neg, pred_is_pos).astype(np.float32)
  69. values['fp'] = np.sum(is_false_positive, axis=1)
  70. return values
  71. def roc_pr_curve(values, curve='ROC'):
  72. """Computes the roc-auc or pr-auc based on confusion counts.
  73. Args:
  74. values: A dict from the func:confusion_matrix_at_thresholds and must have
  75. four keys:tp,fp,fn,tn
  76. curve: Specifies the name of the curve to be computed, 'ROC' [default] or
  77. 'PR' for the Precision-Recall-curve.
  78. Returns:
  79. x_axis: A python list of the curve's x-axis. In ROC it's fpr;In PR it's Recall.
  80. y_axis:A python list of the curve's y-axis. In ROC it's tpr;In PR it's Precision.
  81. fpr=fp/(fp+tn)
  82. tpr=tp/(tp+fn)
  83. Recall=tpr
  84. Precision=tp/(tp+fp)
  85. """
  86. if 'tp' not in values.keys():
  87. raise ValueError('values must have the key tp')
  88. if 'fp' not in values.keys():
  89. raise ValueError('values must have the key fp')
  90. if 'fn' not in values.keys():
  91. raise ValueError('values must have the key fn')
  92. if 'tn' not in values.keys():
  93. raise ValueError('values must have the key tn')
  94. tp = values['tp']
  95. fp = values['fp']
  96. fn = values['fn']
  97. tn = values['tn']
  98. # Add epsilons to avoid dividing by 0.
  99. epsilon = 1.0e-6
  100. rec = np.divide(tp + epsilon, tp + fn + epsilon)
  101. if curve == 'ROC':
  102. fp_rate = np.divide(fp + epsilon, fp + tn + epsilon)
  103. x_axis = fp_rate
  104. y_axis = rec
  105. else: # curve == 'PR'.
  106. prec = np.divide(tp + epsilon, tp + fp + epsilon)
  107. x_axis = rec
  108. y_axis = prec
  109. return x_axis, y_axis
  110. def auc(labels, predictions, num_thresholds=200,
  111. curve='ROC'):
  112. """Computes the approximate AUC via a Riemann sum.
  113. We get four variables `true_positives`,`true_negatives`, `false_positives`
  114. and `false_negatives` that are used to compute the AUC first.
  115. And then compute auc_curve using the function roc_pr_curve.
  116. The `num_thresholds` variable controls the degree of discretization with
  117. larger numbers of thresholds more closely approximating the true AUC.
  118. For best results, `predictions` should be distributed approximately uniformly
  119. in the range [0, 1] and not peaked around 0 or 1.
  120. Args:
  121. labels: A np.array whose shape matches `predictions`. Will be cast to
  122. `bool`.
  123. predictions: A floating point np.array of arbitrary shape and whose values
  124. are in the range `[0, 1]`.
  125. num_thresholds: The number of thresholds to use when discretizing the roc
  126. curve.
  127. curve: Specifies the name of the curve to be computed, 'ROC' [default] or
  128. 'PR' for the Precision-Recall-curve.
  129. Returns:
  130. auc: A scalar representing the current area-under-curve.
  131. """
  132. kepsilon = 1e-7 # to account for floating point imprecisions
  133. thresholds = [(i + 1) * 1.0 / (num_thresholds - 1)
  134. for i in range(num_thresholds - 2)]
  135. thresholds = [0.0 - kepsilon] + thresholds + [1.0 + kepsilon]
  136. values = confusion_matrix_at_thresholds(labels, predictions, thresholds)
  137. x_axis, y_axis = roc_pr_curve(values, curve=curve)
  138. auc_value = np.sum(np.multiply(
  139. x_axis[:num_thresholds - 1] - x_axis[1:],
  140. (y_axis[:num_thresholds - 1] + y_axis[1:]) / 2.))
  141. return auc_value
  142. def accuracy(labels, predictions):
  143. """Calculates the degree of `predictions` matches `labels`.
  144. Args:
  145. labels: A np.array whose shape matches `predictions`.
  146. predictions: A floating point np.array of arbitrary shape and it's the
  147. predicted value.
  148. returns:
  149. accuracy: A accuracy, the value of `total` divided by `count`.
  150. """
  151. acc_val = np.equal(
  152. np.argmax(labels, 1),
  153. np.argmax(predictions, 1)).astype(np.float32)
  154. accuracy = np.mean(acc_val)
  155. return accuracy
  156. def confusion_matrix_one_hot(labels, predictions):
  157. """Computes true_positives, false_negatives, true_negatives, false_positives.
  158. Args:
  159. labels: A np.array whose shape matches `predictions` and must be one_hot.
  160. Will be cast to `bool`.
  161. predictions: A floating point np.array of arbitrary shape.
  162. Returns:
  163. values: Dict of variables of shape `[predictions.shape[1]]`.
  164. example:
  165. labels:[[1,0,0]
  166. [0,1,0]
  167. [0,0,1]]
  168. predictions:
  169. [[9.1,5.0,7.8] true
  170. [0.3,0.7,1.4] false
  171. [4.3,1.3,5.3]] true
  172. returns:
  173. values{'tp':[1,0,1]
  174. 'tn':[2,2,1]
  175. 'fp':[0,0,1]
  176. 'fn':[0,1,0]
  177. }
  178. """
  179. # transpose prediction to one hot.for the example above,it will be:
  180. # [[1,0,0]
  181. # [0,0,1]
  182. # [0,0,1]]
  183. prediction_one_hot = np.eye(predictions.shape[1])[
  184. np.argmax(predictions, axis=1)]
  185. values = {}
  186. is_true_positive = np.logical_and(
  187. np.equal(labels, True), np.equal(prediction_one_hot, True))
  188. is_false_positive = np.logical_and(
  189. np.equal(labels, False), np.equal(prediction_one_hot, True))
  190. is_true_negatives = np.logical_and(
  191. np.equal(labels, False), np.equal(prediction_one_hot, False))
  192. is_false_negatives = np.logical_and(
  193. np.equal(labels, True), np.equal(prediction_one_hot, False))
  194. values['tp'] = np.sum(
  195. is_true_positive.astype(dtype=np.float32), axis=0)
  196. values['fp'] = np.sum(
  197. is_false_positive.astype(dtype=np.float32), axis=0)
  198. values['tn'] = np.sum(
  199. is_true_negatives.astype(dtype=np.float32), axis=0)
  200. values['fn'] = np.sum(
  201. is_false_negatives.astype(dtype=np.float32), axis=0)
  202. return values
  203. def precision_score_one_hot(labels, predictions, average=None):
  204. """compute precision score, precision=tp/(tp+fp)
  205. the labels must be one_hot.
  206. the predictions is prediction results.
  207. Args:
  208. labels: A np.array whose shape matches `predictions` and must be one_hot.
  209. Will be cast to `bool`.
  210. predictions: A floating point np.array of arbitrary shape.
  211. average : string, [None(default), 'micro', 'macro',]
  212. This parameter is required for multiclass/multilabel targets.
  213. If ``None``, the scores for each class are returned. Otherwise, this
  214. determines the type of averaging performed on the data:
  215. ``'micro'``:
  216. Calculate metrics globally by counting the total true positives,
  217. false negatives and false positives.
  218. ``'macro'``:
  219. Calculate metrics for each label, and find their unweighted
  220. mean. This does not take label imbalance into account.
  221. Returns:
  222. values: A score .
  223. References
  224. -----------------------
  225. [1] https://blog.csdn.net/sinat_28576553/article/details/80258619
  226. """
  227. # Add epsilons to avoid dividing by 0.
  228. epsilon = 1.0e-6
  229. values = confusion_matrix_one_hot(labels, predictions)
  230. if average is None:
  231. tp = values['tp']
  232. fp = values['fp']
  233. p = np.divide(tp+epsilon, tp + fp+epsilon)
  234. return p
  235. elif average == 'micro':
  236. tp = np.sum(values['tp'])
  237. fp = np.sum(values['fp'])
  238. return np.divide(tp+epsilon, tp + fp+epsilon)
  239. elif average == 'macro':
  240. tp = values['tp']
  241. fp = values['fp']
  242. p = np.divide(tp+epsilon, tp + fp+epsilon)
  243. return np.average(p)
  244. else:
  245. raise ValueError('Invaild average: %s.' % average)
  246. def recall_score_one_hot(labels, predictions, average=None):
  247. """compute recall score, precision=tp/(tp+fn)
  248. the labels must be one_hot.
  249. the predictions is prediction results.
  250. Args:
  251. labels: A np.array whose shape matches `predictions` and must be one_hot.
  252. Will be cast to `bool`.
  253. predictions: A floating point np.array of arbitrary shape.
  254. average : string, [None(default), 'micro', 'macro',]
  255. This parameter is required for multiclass/multilabel targets.
  256. If ``None``, the scores for each class are returned. Otherwise, this
  257. determines the type of averaging performed on the data:
  258. ``'micro'``:
  259. Calculate metrics globally by counting the total true positives,
  260. false negatives and false positives.
  261. ``'macro'``:
  262. Calculate metrics for each label, and find their unweighted
  263. mean. This does not take label imbalance into account.
  264. Returns:
  265. values: A score .
  266. References
  267. -----------------------
  268. [1] https://blog.csdn.net/sinat_28576553/article/details/80258619
  269. """
  270. # Add epsilons to avoid dividing by 0.
  271. epsilon = 1.0e-6
  272. values = confusion_matrix_one_hot(labels, predictions)
  273. if average is None:
  274. tp = values['tp']
  275. fn = values['fn']
  276. p = np.divide(tp+epsilon, tp + fn+epsilon)
  277. return p
  278. elif average == 'micro':
  279. tp = np.sum(values['tp'])
  280. fn = np.sum(values['fn'])
  281. return np.divide(tp+epsilon, tp + fn+epsilon)
  282. elif average == 'macro':
  283. tp = values['tp']
  284. fn = values['fn']
  285. p = np.divide(tp+epsilon, tp + fn+epsilon)
  286. return np.average(p)
  287. else:
  288. raise ValueError('Invaild average: %s.' % average)
  289. def f_score_one_hot(labels, predictions, beta=1.0, average=None):
  290. """compute f score, =(1+beta*beta)precision*recall/(beta*beta*precision+recall)
  291. the labels must be one_hot.
  292. the predictions is prediction results.
  293. Args:
  294. labels: A np.array whose shape matches `predictions` and must be one_hot.
  295. Will be cast to `bool`.
  296. predictions: A floating point np.array of arbitrary shape.
  297. average : string, [None(default), 'micro', 'macro',]
  298. This parameter is required for multiclass/multilabel targets.
  299. If ``None``, the scores for each class are returned. Otherwise, this
  300. determines the type of averaging performed on the data:
  301. ``'micro'``:
  302. Calculate metrics globally by counting the total true positives,
  303. false negatives and false positives.
  304. ``'macro'``:
  305. Calculate metrics for each label, and find their unweighted
  306. mean. This does not take label imbalance into account.
  307. Returns:
  308. values: A score float.
  309. References
  310. -----------------------
  311. [1] https://blog.csdn.net/sinat_28576553/article/details/80258619
  312. """
  313. if beta < 0:
  314. raise ValueError("beta should be >=0 in the F-beta score")
  315. beta2 = beta ** 2
  316. p = precision_score_one_hot(labels, predictions, average=average)
  317. r = recall_score_one_hot(labels, predictions, average=average)
  318. # In the functions:precision and recall,add a epsilon,so p and r will
  319. # not be zero.
  320. f = (1+beta2)*p*r/(beta2*p+r)
  321. if average is None or average == 'micro':
  322. p = precision_score_one_hot(labels, predictions, average=average)
  323. r = recall_score_one_hot(labels, predictions, average=average)
  324. f = (1 + beta2) * p * r / (beta2 * p + r)
  325. return f
  326. elif average == 'macro':
  327. p = precision_score_one_hot(labels, predictions, average=None)
  328. r = recall_score_one_hot(labels, predictions, average=None)
  329. f = (1 + beta2) * p * r / (beta2 * p + r)
  330. return np.average(f)
  331. else:
  332. raise ValueError('Invaild average: %s.' % average)