Repository: Lightning-AI/torchmetrics

Issue:
Micro average for auroc
## 🚀 Feature
Add 'micro' to list of allowed averages.

### Motivation
For now, 'average' must be in [None, 'macro', 'weighted].
For multi-label classification it would be nice to allow micro averaging. 
This comes down to calculating `auroc(preds.flatten(), target.flatten())`.

Similarly to https://scikit-learn.org/stable/modules/generated/sklearn.metrics.roc_auc_score.html.
One could also consider adding 'samples' to the list of accepted averages.

What do you think?


Discussion:

Resolving patch:
diff --git a/torchmetrics/classification/auroc.py b/torchmetrics/classification/auroc.py
--- a/torchmetrics/classification/auroc.py
+++ b/torchmetrics/classification/auroc.py
@@ -48,6 +48,7 @@ class AUROC(Metric):
            this argument should not be set as we iteratively change it in the
            range [0,num_classes-1]
        average:
+           - ``'micro'`` computes metric globally. Only works for multilabel problems
            - ``'macro'`` computes metric for each class and uniformly averages them
            - ``'weighted'`` computes metric for each class and does a weighted-average,
              where each class is weighted by their support (accounts for class imbalance)
@@ -122,7 +123,7 @@ def __init__(
         self.average = average
         self.max_fpr = max_fpr
 
-        allowed_average = (None, 'macro', 'weighted')
+        allowed_average = (None, 'macro', 'weighted', 'micro')
         if self.average not in allowed_average:
             raise ValueError(
                 f'Argument `average` expected to be one of the following: {allowed_average} but got {average}'
diff --git a/torchmetrics/functional/classification/auroc.py b/torchmetrics/functional/classification/auroc.py
--- a/torchmetrics/functional/classification/auroc.py
+++ b/torchmetrics/functional/classification/auroc.py
@@ -74,19 +74,24 @@ def _auroc_compute(
 
     # calculate fpr, tpr
     if mode == 'multi-label':
-        # for multilabel we iteratively evaluate roc in a binary fashion
-        output = [
-            roc(preds[:, i], target[:, i], num_classes=1, pos_label=1, sample_weights=sample_weights)
-            for i in range(num_classes)
-        ]
-        fpr = [o[0] for o in output]
-        tpr = [o[1] for o in output]
+        if average == AverageMethod.MICRO:
+            fpr, tpr, _ = roc(preds.flatten(), target.flatten(), num_classes, pos_label, sample_weights)
+        else:
+            # for multilabel we iteratively evaluate roc in a binary fashion
+            output = [
+                roc(preds[:, i], target[:, i], num_classes=1, pos_label=1, sample_weights=sample_weights)
+                for i in range(num_classes)
+            ]
+            fpr = [o[0] for o in output]
+            tpr = [o[1] for o in output]
     else:
         fpr, tpr, _ = roc(preds, target, num_classes, pos_label, sample_weights)
 
     # calculate standard roc auc score
     if max_fpr is None or max_fpr == 1:
-        if num_classes != 1:
+        if mode == 'multi-label' and average == AverageMethod.MICRO:
+            pass
+        elif num_classes != 1:
             # calculate auc scores per class
             auc_scores = [auc(x, y) for x, y in zip(fpr, tpr)]
 
@@ -150,6 +155,7 @@ def auroc(
             this argument should not be set as we iteratively change it in the
             range [0,num_classes-1]
         average:
+            - ``'micro'`` computes metric globally. Only works for multilabel problems
             - ``'macro'`` computes metric for each class and uniformly averages them
             - ``'weighted'`` computes metric for each class and does a weighted-average,
               where each class is weighted by their support (accounts for class imbalance)


Test patch:
diff --git a/tests/classification/test_auroc.py b/tests/classification/test_auroc.py
--- a/tests/classification/test_auroc.py
+++ b/tests/classification/test_auroc.py
@@ -93,7 +93,7 @@ def _sk_auroc_multilabel_multidim_prob(preds, target, num_classes, average='macr
      (_input_mlb_prob.preds, _input_mlb_prob.target, _sk_auroc_multilabel_prob, NUM_CLASSES),
      (_input_mlmd_prob.preds, _input_mlmd_prob.target, _sk_auroc_multilabel_multidim_prob, NUM_CLASSES)]
 )
-@pytest.mark.parametrize("average", ['macro', 'weighted'])
+@pytest.mark.parametrize("average", ['macro', 'weighted', 'micro'])
 @pytest.mark.parametrize("max_fpr", [None, 0.8, 0.5])
 class TestAUROC(MetricTester):
 
@@ -108,6 +108,10 @@ def test_auroc(self, preds, target, sk_metric, num_classes, average, max_fpr, dd
         if max_fpr is not None and _TORCH_LOWER_1_6:
             pytest.skip('requires torch v1.6 or higher to test max_fpr argument')
 
+        # average='micro' only supported for multilabel
+        if average == 'micro' and preds.ndim > 2 and preds.ndim == target.ndim + 1:
+            pytest.skip('micro argument only support for multilabel input')
+
         self.run_class_metric_test(
             ddp=ddp,
             preds=preds,
@@ -131,6 +135,10 @@ def test_auroc_functional(self, preds, target, sk_metric, num_classes, average,
         if max_fpr is not None and _TORCH_LOWER_1_6:
             pytest.skip('requires torch v1.6 or higher to test max_fpr argument')
 
+        # average='micro' only supported for multilabel
+        if average == 'micro' and preds.ndim > 2 and preds.ndim == target.ndim + 1:
+            pytest.skip('micro argument only support for multilabel input')
+
         self.run_functional_metric_test(
             preds,
             target,