We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[metrics] AUROC Metric can't handle 0 observations of a class with multiclass classifier
I'm attempting to calculate AUROC for a multiclass problem where some classes are very rare, occasionally never seen, and I'm getting the following error: `raise ValueError("No positive samples in targets, true positive value should be meaningless")`

In the case of 0 observations, I feel the `average='weighted'` should work, since the contribution to the final AUROC should be 0 regardless. One can think of other scenarios where there are a very high number of classes, some of which will happen to not be seen in some dataset.

_Originally posted by @BeyondTheProof in https://github.com/PyTorchLightning/pytorch-lightning/issues/2210#issuecomment-872440776_

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `torchmetrics/functional/classification/auroc.py`
Content:
```
1 # Copyright The PyTorch Lightning team.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 from typing import Optional, Sequence, Tuple
15 
16 import torch
17 from torch import Tensor, tensor
18 
19 from torchmetrics.functional.classification.auc import _auc_compute_without_check
20 from torchmetrics.functional.classification.roc import roc
21 from torchmetrics.utilities.checks import _input_format_classification
22 from torchmetrics.utilities.enums import AverageMethod, DataType
23 from torchmetrics.utilities.imports import _TORCH_LOWER_1_6
24 
25 
26 def _auroc_update(preds: Tensor, target: Tensor) -> Tuple[Tensor, Tensor, DataType]:
27     # use _input_format_classification for validating the input and get the mode of data
28     _, _, mode = _input_format_classification(preds, target)
29 
30     if mode == 'multi class multi dim':
31         n_classes = preds.shape[1]
32         preds = preds.transpose(0, 1).reshape(n_classes, -1).transpose(0, 1)
33         target = target.flatten()
34     if mode == 'multi-label' and preds.ndim > 2:
35         n_classes = preds.shape[1]
36         preds = preds.transpose(0, 1).reshape(n_classes, -1).transpose(0, 1)
37         target = target.transpose(0, 1).reshape(n_classes, -1).transpose(0, 1)
38 
39     return preds, target, mode
40 
41 
42 def _auroc_compute(
43     preds: Tensor,
44     target: Tensor,
45     mode: DataType,
46     num_classes: Optional[int] = None,
47     pos_label: Optional[int] = None,
48     average: Optional[str] = 'macro',
49     max_fpr: Optional[float] = None,
50     sample_weights: Optional[Sequence] = None,
51 ) -> Tensor:
52     # binary mode override num_classes
53     if mode == DataType.BINARY:
54         num_classes = 1
55 
56     # check max_fpr parameter
57     if max_fpr is not None:
58         if not isinstance(max_fpr, float) and 0 < max_fpr <= 1:
59             raise ValueError(f"`max_fpr` should be a float in range (0, 1], got: {max_fpr}")
60 
61         if _TORCH_LOWER_1_6:
62             raise RuntimeError(
63                 "`max_fpr` argument requires `torch.bucketize` which"
64                 " is not available below PyTorch version 1.6"
65             )
66 
67         # max_fpr parameter is only support for binary
68         if mode != DataType.BINARY:
69             raise ValueError(
70                 f"Partial AUC computation not available in"
71                 f" multilabel/multiclass setting, 'max_fpr' must be"
72                 f" set to `None`, received `{max_fpr}`."
73             )
74 
75     # calculate fpr, tpr
76     if mode == DataType.MULTILABEL:
77         if average == AverageMethod.MICRO:
78             fpr, tpr, _ = roc(preds.flatten(), target.flatten(), 1, pos_label, sample_weights)
79         elif num_classes:
80             # for multilabel we iteratively evaluate roc in a binary fashion
81             output = [
82                 roc(preds[:, i], target[:, i], num_classes=1, pos_label=1, sample_weights=sample_weights)
83                 for i in range(num_classes)
84             ]
85             fpr = [o[0] for o in output]
86             tpr = [o[1] for o in output]
87         else:
88             raise ValueError('Detected input to be `multilabel` but you did not provide `num_classes` argument')
89     else:
90         if mode != DataType.BINARY and num_classes is None:
91             raise ValueError('Detected input to `multiclass` but you did not provide `num_classes` argument')
92         fpr, tpr, _ = roc(preds, target, num_classes, pos_label, sample_weights)
93 
94     # calculate standard roc auc score
95     if max_fpr is None or max_fpr == 1:
96         if mode == DataType.MULTILABEL and average == AverageMethod.MICRO:
97             pass
98         elif num_classes != 1:
99             # calculate auc scores per class
100             auc_scores = [_auc_compute_without_check(x, y, 1.0) for x, y in zip(fpr, tpr)]
101 
102             # calculate average
103             if average == AverageMethod.NONE:
104                 return tensor(auc_scores)
105             if average == AverageMethod.MACRO:
106                 return torch.mean(torch.stack(auc_scores))
107             if average == AverageMethod.WEIGHTED:
108                 if mode == DataType.MULTILABEL:
109                     support = torch.sum(target, dim=0)
110                 else:
111                     support = torch.bincount(target.flatten(), minlength=num_classes)
112                 return torch.sum(torch.stack(auc_scores) * support / support.sum())
113 
114             allowed_average = (AverageMethod.NONE.value, AverageMethod.MACRO.value, AverageMethod.WEIGHTED.value)
115             raise ValueError(
116                 f"Argument `average` expected to be one of the following:"
117                 f" {allowed_average} but got {average}"
118             )
119 
120         return _auc_compute_without_check(fpr, tpr, 1.0)
121 
122     _device = fpr.device if isinstance(fpr, Tensor) else fpr[0].device
123     max_area: Tensor = tensor(max_fpr, device=_device)
124     # Add a single point at max_fpr and interpolate its tpr value
125     stop = torch.bucketize(max_area, fpr, out_int32=True, right=True)
126     weight = (max_area - fpr[stop - 1]) / (fpr[stop] - fpr[stop - 1])
127     interp_tpr: Tensor = torch.lerp(tpr[stop - 1], tpr[stop], weight)
128     tpr = torch.cat([tpr[:stop], interp_tpr.view(1)])
129     fpr = torch.cat([fpr[:stop], max_area.view(1)])
130 
131     # Compute partial AUC
132     partial_auc = _auc_compute_without_check(fpr, tpr, 1.0)
133 
134     # McClish correction: standardize result to be 0.5 if non-discriminant and 1 if maximal
135     min_area: Tensor = 0.5 * max_area**2
136     return 0.5 * (1 + (partial_auc - min_area) / (max_area - min_area))
137 
138 
139 def auroc(
140     preds: Tensor,
141     target: Tensor,
142     num_classes: Optional[int] = None,
143     pos_label: Optional[int] = None,
144     average: Optional[str] = 'macro',
145     max_fpr: Optional[float] = None,
146     sample_weights: Optional[Sequence] = None,
147 ) -> Tensor:
148     """ Compute `Area Under the Receiver Operating Characteristic Curve (ROC AUC)
149     <https://en.wikipedia.org/wiki/Receiver_operating_characteristic#Further_interpretations>`_
150 
151     Args:
152         preds: predictions from model (logits or probabilities)
153         target: Ground truth labels
154         num_classes: integer with number of classes for multi-label and multiclass problems.
155             Should be set to ``None`` for binary problems
156         pos_label: integer determining the positive class. Default is ``None``
157             which for binary problem is translate to 1. For multiclass problems
158             this argument should not be set as we iteratively change it in the
159             range [0,num_classes-1]
160         average:
161             - ``'micro'`` computes metric globally. Only works for multilabel problems
162             - ``'macro'`` computes metric for each class and uniformly averages them
163             - ``'weighted'`` computes metric for each class and does a weighted-average,
164               where each class is weighted by their support (accounts for class imbalance)
165             - ``None`` computes and returns the metric per class
166         max_fpr:
167             If not ``None``, calculates standardized partial AUC over the
168             range [0, max_fpr]. Should be a float between 0 and 1.
169         sample_weights: sample weights for each data point
170 
171     Raises:
172         ValueError:
173             If ``max_fpr`` is not a ``float`` in the range ``(0, 1]``.
174         RuntimeError:
175             If ``PyTorch version`` is ``below 1.6`` since max_fpr requires `torch.bucketize`
176             which is not available below 1.6.
177         ValueError:
178             If ``max_fpr`` is not set to ``None`` and the mode is ``not binary``
179             since partial AUC computation is not available in multilabel/multiclass.
180         ValueError:
181             If ``average`` is none of ``None``, ``"macro"`` or ``"weighted"``.
182 
183     Example (binary case):
184         >>> from torchmetrics.functional import auroc
185         >>> preds = torch.tensor([0.13, 0.26, 0.08, 0.19, 0.34])
186         >>> target = torch.tensor([0, 0, 1, 1, 1])
187         >>> auroc(preds, target, pos_label=1)
188         tensor(0.5000)
189 
190     Example (multiclass case):
191         >>> preds = torch.tensor([[0.90, 0.05, 0.05],
192         ...                       [0.05, 0.90, 0.05],
193         ...                       [0.05, 0.05, 0.90],
194         ...                       [0.85, 0.05, 0.10],
195         ...                       [0.10, 0.10, 0.80]])
196         >>> target = torch.tensor([0, 1, 1, 2, 2])
197         >>> auroc(preds, target, num_classes=3)
198         tensor(0.7778)
199     """
200     preds, target, mode = _auroc_update(preds, target)
201     return _auroc_compute(preds, target, mode, num_classes, pos_label, average, max_fpr, sample_weights)
202
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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
@@ -11,6 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+import warnings
 from typing import Optional, Sequence, Tuple
 
 import torch
@@ -87,8 +88,23 @@
         else:
             raise ValueError('Detected input to be `multilabel` but you did not provide `num_classes` argument')
     else:
-        if mode != DataType.BINARY and num_classes is None:
-            raise ValueError('Detected input to `multiclass` but you did not provide `num_classes` argument')
+        if mode != DataType.BINARY:
+            if num_classes is None:
+                raise ValueError("Detected input to `multiclass` but you did not provide `num_classes` argument")
+            if average == AverageMethod.WEIGHTED and len(torch.unique(target)) < num_classes:
+                # If one or more classes has 0 observations, we should exclude them, as its weight will be 0
+                target_bool_mat = torch.zeros((len(target), num_classes), dtype=bool)
+                target_bool_mat[torch.arange(len(target)), target.long()] = 1
+                class_observed = target_bool_mat.sum(axis=0) > 0
+                for c in range(num_classes):
+                    if not class_observed[c]:
+                        warnings.warn(f'Class {c} had 0 observations, omitted from AUROC calculation', UserWarning)
+                preds = preds[:, class_observed]
+                target = target_bool_mat[:, class_observed]
+                target = torch.where(target)[1]
+                num_classes = class_observed.sum()
+                if num_classes == 1:
+                    raise ValueError('Found 1 non-empty class in `multiclass` AUROC calculation')
         fpr, tpr, _ = roc(preds, target, num_classes, pos_label, sample_weights)
 
     # calculate standard roc auc score

{"golden_diff": "diff --git a/torchmetrics/functional/classification/auroc.py b/torchmetrics/functional/classification/auroc.py\n--- a/torchmetrics/functional/classification/auroc.py\n+++ b/torchmetrics/functional/classification/auroc.py\n@@ -11,6 +11,7 @@\n # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n # See the License for the specific language governing permissions and\n # limitations under the License.\n+import warnings\n from typing import Optional, Sequence, Tuple\n \n import torch\n@@ -87,8 +88,23 @@\n         else:\n             raise ValueError('Detected input to be `multilabel` but you did not provide `num_classes` argument')\n     else:\n-        if mode != DataType.BINARY and num_classes is None:\n-            raise ValueError('Detected input to `multiclass` but you did not provide `num_classes` argument')\n+        if mode != DataType.BINARY:\n+            if num_classes is None:\n+                raise ValueError(\"Detected input to `multiclass` but you did not provide `num_classes` argument\")\n+            if average == AverageMethod.WEIGHTED and len(torch.unique(target)) < num_classes:\n+                # If one or more classes has 0 observations, we should exclude them, as its weight will be 0\n+                target_bool_mat = torch.zeros((len(target), num_classes), dtype=bool)\n+                target_bool_mat[torch.arange(len(target)), target.long()] = 1\n+                class_observed = target_bool_mat.sum(axis=0) > 0\n+                for c in range(num_classes):\n+                    if not class_observed[c]:\n+                        warnings.warn(f'Class {c} had 0 observations, omitted from AUROC calculation', UserWarning)\n+                preds = preds[:, class_observed]\n+                target = target_bool_mat[:, class_observed]\n+                target = torch.where(target)[1]\n+                num_classes = class_observed.sum()\n+                if num_classes == 1:\n+                    raise ValueError('Found 1 non-empty class in `multiclass` AUROC calculation')\n         fpr, tpr, _ = roc(preds, target, num_classes, pos_label, sample_weights)\n \n     # calculate standard roc auc score\n", "issue": "[metrics] AUROC Metric can't handle 0 observations of a class with multiclass classifier\nI'm attempting to calculate AUROC for a multiclass problem where some classes are very rare, occasionally never seen, and I'm getting the following error: `raise ValueError(\"No positive samples in targets, true positive value should be meaningless\")`\r\n\r\nIn the case of 0 observations, I feel the `average='weighted'` should work, since the contribution to the final AUROC should be 0 regardless. One can think of other scenarios where there are a very high number of classes, some of which will happen to not be seen in some dataset.\r\n\r\n_Originally posted by @BeyondTheProof in https://github.com/PyTorchLightning/pytorch-lightning/issues/2210#issuecomment-872440776_\n", "before_files": [{"content": "# Copyright The PyTorch Lightning team.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nfrom typing import Optional, Sequence, Tuple\n\nimport torch\nfrom torch import Tensor, tensor\n\nfrom torchmetrics.functional.classification.auc import _auc_compute_without_check\nfrom torchmetrics.functional.classification.roc import roc\nfrom torchmetrics.utilities.checks import _input_format_classification\nfrom torchmetrics.utilities.enums import AverageMethod, DataType\nfrom torchmetrics.utilities.imports import _TORCH_LOWER_1_6\n\n\ndef _auroc_update(preds: Tensor, target: Tensor) -> Tuple[Tensor, Tensor, DataType]:\n    # use _input_format_classification for validating the input and get the mode of data\n    _, _, mode = _input_format_classification(preds, target)\n\n    if mode == 'multi class multi dim':\n        n_classes = preds.shape[1]\n        preds = preds.transpose(0, 1).reshape(n_classes, -1).transpose(0, 1)\n        target = target.flatten()\n    if mode == 'multi-label' and preds.ndim > 2:\n        n_classes = preds.shape[1]\n        preds = preds.transpose(0, 1).reshape(n_classes, -1).transpose(0, 1)\n        target = target.transpose(0, 1).reshape(n_classes, -1).transpose(0, 1)\n\n    return preds, target, mode\n\n\ndef _auroc_compute(\n    preds: Tensor,\n    target: Tensor,\n    mode: DataType,\n    num_classes: Optional[int] = None,\n    pos_label: Optional[int] = None,\n    average: Optional[str] = 'macro',\n    max_fpr: Optional[float] = None,\n    sample_weights: Optional[Sequence] = None,\n) -> Tensor:\n    # binary mode override num_classes\n    if mode == DataType.BINARY:\n        num_classes = 1\n\n    # check max_fpr parameter\n    if max_fpr is not None:\n        if not isinstance(max_fpr, float) and 0 < max_fpr <= 1:\n            raise ValueError(f\"`max_fpr` should be a float in range (0, 1], got: {max_fpr}\")\n\n        if _TORCH_LOWER_1_6:\n            raise RuntimeError(\n                \"`max_fpr` argument requires `torch.bucketize` which\"\n                \" is not available below PyTorch version 1.6\"\n            )\n\n        # max_fpr parameter is only support for binary\n        if mode != DataType.BINARY:\n            raise ValueError(\n                f\"Partial AUC computation not available in\"\n                f\" multilabel/multiclass setting, 'max_fpr' must be\"\n                f\" set to `None`, received `{max_fpr}`.\"\n            )\n\n    # calculate fpr, tpr\n    if mode == DataType.MULTILABEL:\n        if average == AverageMethod.MICRO:\n            fpr, tpr, _ = roc(preds.flatten(), target.flatten(), 1, pos_label, sample_weights)\n        elif num_classes:\n            # for multilabel we iteratively evaluate roc in a binary fashion\n            output = [\n                roc(preds[:, i], target[:, i], num_classes=1, pos_label=1, sample_weights=sample_weights)\n                for i in range(num_classes)\n            ]\n            fpr = [o[0] for o in output]\n            tpr = [o[1] for o in output]\n        else:\n            raise ValueError('Detected input to be `multilabel` but you did not provide `num_classes` argument')\n    else:\n        if mode != DataType.BINARY and num_classes is None:\n            raise ValueError('Detected input to `multiclass` but you did not provide `num_classes` argument')\n        fpr, tpr, _ = roc(preds, target, num_classes, pos_label, sample_weights)\n\n    # calculate standard roc auc score\n    if max_fpr is None or max_fpr == 1:\n        if mode == DataType.MULTILABEL and average == AverageMethod.MICRO:\n            pass\n        elif num_classes != 1:\n            # calculate auc scores per class\n            auc_scores = [_auc_compute_without_check(x, y, 1.0) for x, y in zip(fpr, tpr)]\n\n            # calculate average\n            if average == AverageMethod.NONE:\n                return tensor(auc_scores)\n            if average == AverageMethod.MACRO:\n                return torch.mean(torch.stack(auc_scores))\n            if average == AverageMethod.WEIGHTED:\n                if mode == DataType.MULTILABEL:\n                    support = torch.sum(target, dim=0)\n                else:\n                    support = torch.bincount(target.flatten(), minlength=num_classes)\n                return torch.sum(torch.stack(auc_scores) * support / support.sum())\n\n            allowed_average = (AverageMethod.NONE.value, AverageMethod.MACRO.value, AverageMethod.WEIGHTED.value)\n            raise ValueError(\n                f\"Argument `average` expected to be one of the following:\"\n                f\" {allowed_average} but got {average}\"\n            )\n\n        return _auc_compute_without_check(fpr, tpr, 1.0)\n\n    _device = fpr.device if isinstance(fpr, Tensor) else fpr[0].device\n    max_area: Tensor = tensor(max_fpr, device=_device)\n    # Add a single point at max_fpr and interpolate its tpr value\n    stop = torch.bucketize(max_area, fpr, out_int32=True, right=True)\n    weight = (max_area - fpr[stop - 1]) / (fpr[stop] - fpr[stop - 1])\n    interp_tpr: Tensor = torch.lerp(tpr[stop - 1], tpr[stop], weight)\n    tpr = torch.cat([tpr[:stop], interp_tpr.view(1)])\n    fpr = torch.cat([fpr[:stop], max_area.view(1)])\n\n    # Compute partial AUC\n    partial_auc = _auc_compute_without_check(fpr, tpr, 1.0)\n\n    # McClish correction: standardize result to be 0.5 if non-discriminant and 1 if maximal\n    min_area: Tensor = 0.5 * max_area**2\n    return 0.5 * (1 + (partial_auc - min_area) / (max_area - min_area))\n\n\ndef auroc(\n    preds: Tensor,\n    target: Tensor,\n    num_classes: Optional[int] = None,\n    pos_label: Optional[int] = None,\n    average: Optional[str] = 'macro',\n    max_fpr: Optional[float] = None,\n    sample_weights: Optional[Sequence] = None,\n) -> Tensor:\n    \"\"\" Compute `Area Under the Receiver Operating Characteristic Curve (ROC AUC)\n    <https://en.wikipedia.org/wiki/Receiver_operating_characteristic#Further_interpretations>`_\n\n    Args:\n        preds: predictions from model (logits or probabilities)\n        target: Ground truth labels\n        num_classes: integer with number of classes for multi-label and multiclass problems.\n            Should be set to ``None`` for binary problems\n        pos_label: integer determining the positive class. Default is ``None``\n            which for binary problem is translate to 1. For multiclass problems\n            this argument should not be set as we iteratively change it in the\n            range [0,num_classes-1]\n        average:\n            - ``'micro'`` computes metric globally. Only works for multilabel problems\n            - ``'macro'`` computes metric for each class and uniformly averages them\n            - ``'weighted'`` computes metric for each class and does a weighted-average,\n              where each class is weighted by their support (accounts for class imbalance)\n            - ``None`` computes and returns the metric per class\n        max_fpr:\n            If not ``None``, calculates standardized partial AUC over the\n            range [0, max_fpr]. Should be a float between 0 and 1.\n        sample_weights: sample weights for each data point\n\n    Raises:\n        ValueError:\n            If ``max_fpr`` is not a ``float`` in the range ``(0, 1]``.\n        RuntimeError:\n            If ``PyTorch version`` is ``below 1.6`` since max_fpr requires `torch.bucketize`\n            which is not available below 1.6.\n        ValueError:\n            If ``max_fpr`` is not set to ``None`` and the mode is ``not binary``\n            since partial AUC computation is not available in multilabel/multiclass.\n        ValueError:\n            If ``average`` is none of ``None``, ``\"macro\"`` or ``\"weighted\"``.\n\n    Example (binary case):\n        >>> from torchmetrics.functional import auroc\n        >>> preds = torch.tensor([0.13, 0.26, 0.08, 0.19, 0.34])\n        >>> target = torch.tensor([0, 0, 1, 1, 1])\n        >>> auroc(preds, target, pos_label=1)\n        tensor(0.5000)\n\n    Example (multiclass case):\n        >>> preds = torch.tensor([[0.90, 0.05, 0.05],\n        ...                       [0.05, 0.90, 0.05],\n        ...                       [0.05, 0.05, 0.90],\n        ...                       [0.85, 0.05, 0.10],\n        ...                       [0.10, 0.10, 0.80]])\n        >>> target = torch.tensor([0, 1, 1, 2, 2])\n        >>> auroc(preds, target, num_classes=3)\n        tensor(0.7778)\n    \"\"\"\n    preds, target, mode = _auroc_update(preds, target)\n    return _auroc_compute(preds, target, mode, num_classes, pos_label, average, max_fpr, sample_weights)\n", "path": "torchmetrics/functional/classification/auroc.py"}], "after_files": [{"content": "# Copyright The PyTorch Lightning team.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nimport warnings\nfrom typing import Optional, Sequence, Tuple\n\nimport torch\nfrom torch import Tensor, tensor\n\nfrom torchmetrics.functional.classification.auc import _auc_compute_without_check\nfrom torchmetrics.functional.classification.roc import roc\nfrom torchmetrics.utilities.checks import _input_format_classification\nfrom torchmetrics.utilities.enums import AverageMethod, DataType\nfrom torchmetrics.utilities.imports import _TORCH_LOWER_1_6\n\n\ndef _auroc_update(preds: Tensor, target: Tensor) -> Tuple[Tensor, Tensor, DataType]:\n    # use _input_format_classification for validating the input and get the mode of data\n    _, _, mode = _input_format_classification(preds, target)\n\n    if mode == 'multi class multi dim':\n        n_classes = preds.shape[1]\n        preds = preds.transpose(0, 1).reshape(n_classes, -1).transpose(0, 1)\n        target = target.flatten()\n    if mode == 'multi-label' and preds.ndim > 2:\n        n_classes = preds.shape[1]\n        preds = preds.transpose(0, 1).reshape(n_classes, -1).transpose(0, 1)\n        target = target.transpose(0, 1).reshape(n_classes, -1).transpose(0, 1)\n\n    return preds, target, mode\n\n\ndef _auroc_compute(\n    preds: Tensor,\n    target: Tensor,\n    mode: DataType,\n    num_classes: Optional[int] = None,\n    pos_label: Optional[int] = None,\n    average: Optional[str] = 'macro',\n    max_fpr: Optional[float] = None,\n    sample_weights: Optional[Sequence] = None,\n) -> Tensor:\n    # binary mode override num_classes\n    if mode == DataType.BINARY:\n        num_classes = 1\n\n    # check max_fpr parameter\n    if max_fpr is not None:\n        if not isinstance(max_fpr, float) and 0 < max_fpr <= 1:\n            raise ValueError(f\"`max_fpr` should be a float in range (0, 1], got: {max_fpr}\")\n\n        if _TORCH_LOWER_1_6:\n            raise RuntimeError(\n                \"`max_fpr` argument requires `torch.bucketize` which\"\n                \" is not available below PyTorch version 1.6\"\n            )\n\n        # max_fpr parameter is only support for binary\n        if mode != DataType.BINARY:\n            raise ValueError(\n                f\"Partial AUC computation not available in\"\n                f\" multilabel/multiclass setting, 'max_fpr' must be\"\n                f\" set to `None`, received `{max_fpr}`.\"\n            )\n\n    # calculate fpr, tpr\n    if mode == DataType.MULTILABEL:\n        if average == AverageMethod.MICRO:\n            fpr, tpr, _ = roc(preds.flatten(), target.flatten(), 1, pos_label, sample_weights)\n        elif num_classes:\n            # for multilabel we iteratively evaluate roc in a binary fashion\n            output = [\n                roc(preds[:, i], target[:, i], num_classes=1, pos_label=1, sample_weights=sample_weights)\n                for i in range(num_classes)\n            ]\n            fpr = [o[0] for o in output]\n            tpr = [o[1] for o in output]\n        else:\n            raise ValueError('Detected input to be `multilabel` but you did not provide `num_classes` argument')\n    else:\n        if mode != DataType.BINARY:\n            if num_classes is None:\n                raise ValueError(\"Detected input to `multiclass` but you did not provide `num_classes` argument\")\n            if average == AverageMethod.WEIGHTED and len(torch.unique(target)) < num_classes:\n                # If one or more classes has 0 observations, we should exclude them, as its weight will be 0\n                target_bool_mat = torch.zeros((len(target), num_classes), dtype=bool)\n                target_bool_mat[torch.arange(len(target)), target.long()] = 1\n                class_observed = target_bool_mat.sum(axis=0) > 0\n                for c in range(num_classes):\n                    if not class_observed[c]:\n                        warnings.warn(f'Class {c} had 0 observations, omitted from AUROC calculation', UserWarning)\n                preds = preds[:, class_observed]\n                target = target_bool_mat[:, class_observed]\n                target = torch.where(target)[1]\n                num_classes = class_observed.sum()\n                if num_classes == 1:\n                    raise ValueError('Found 1 non-empty class in `multiclass` AUROC calculation')\n        fpr, tpr, _ = roc(preds, target, num_classes, pos_label, sample_weights)\n\n    # calculate standard roc auc score\n    if max_fpr is None or max_fpr == 1:\n        if mode == DataType.MULTILABEL and average == AverageMethod.MICRO:\n            pass\n        elif num_classes != 1:\n            # calculate auc scores per class\n            auc_scores = [_auc_compute_without_check(x, y, 1.0) for x, y in zip(fpr, tpr)]\n\n            # calculate average\n            if average == AverageMethod.NONE:\n                return tensor(auc_scores)\n            if average == AverageMethod.MACRO:\n                return torch.mean(torch.stack(auc_scores))\n            if average == AverageMethod.WEIGHTED:\n                if mode == DataType.MULTILABEL:\n                    support = torch.sum(target, dim=0)\n                else:\n                    support = torch.bincount(target.flatten(), minlength=num_classes)\n                return torch.sum(torch.stack(auc_scores) * support / support.sum())\n\n            allowed_average = (AverageMethod.NONE.value, AverageMethod.MACRO.value, AverageMethod.WEIGHTED.value)\n            raise ValueError(\n                f\"Argument `average` expected to be one of the following:\"\n                f\" {allowed_average} but got {average}\"\n            )\n\n        return _auc_compute_without_check(fpr, tpr, 1.0)\n\n    _device = fpr.device if isinstance(fpr, Tensor) else fpr[0].device\n    max_area: Tensor = tensor(max_fpr, device=_device)\n    # Add a single point at max_fpr and interpolate its tpr value\n    stop = torch.bucketize(max_area, fpr, out_int32=True, right=True)\n    weight = (max_area - fpr[stop - 1]) / (fpr[stop] - fpr[stop - 1])\n    interp_tpr: Tensor = torch.lerp(tpr[stop - 1], tpr[stop], weight)\n    tpr = torch.cat([tpr[:stop], interp_tpr.view(1)])\n    fpr = torch.cat([fpr[:stop], max_area.view(1)])\n\n    # Compute partial AUC\n    partial_auc = _auc_compute_without_check(fpr, tpr, 1.0)\n\n    # McClish correction: standardize result to be 0.5 if non-discriminant and 1 if maximal\n    min_area: Tensor = 0.5 * max_area**2\n    return 0.5 * (1 + (partial_auc - min_area) / (max_area - min_area))\n\n\ndef auroc(\n    preds: Tensor,\n    target: Tensor,\n    num_classes: Optional[int] = None,\n    pos_label: Optional[int] = None,\n    average: Optional[str] = 'macro',\n    max_fpr: Optional[float] = None,\n    sample_weights: Optional[Sequence] = None,\n) -> Tensor:\n    \"\"\" Compute `Area Under the Receiver Operating Characteristic Curve (ROC AUC)\n    <https://en.wikipedia.org/wiki/Receiver_operating_characteristic#Further_interpretations>`_\n\n    Args:\n        preds: predictions from model (logits or probabilities)\n        target: Ground truth labels\n        num_classes: integer with number of classes for multi-label and multiclass problems.\n            Should be set to ``None`` for binary problems\n        pos_label: integer determining the positive class. Default is ``None``\n            which for binary problem is translate to 1. For multiclass problems\n            this argument should not be set as we iteratively change it in the\n            range [0,num_classes-1]\n        average:\n            - ``'micro'`` computes metric globally. Only works for multilabel problems\n            - ``'macro'`` computes metric for each class and uniformly averages them\n            - ``'weighted'`` computes metric for each class and does a weighted-average,\n              where each class is weighted by their support (accounts for class imbalance)\n            - ``None`` computes and returns the metric per class\n        max_fpr:\n            If not ``None``, calculates standardized partial AUC over the\n            range [0, max_fpr]. Should be a float between 0 and 1.\n        sample_weights: sample weights for each data point\n\n    Raises:\n        ValueError:\n            If ``max_fpr`` is not a ``float`` in the range ``(0, 1]``.\n        RuntimeError:\n            If ``PyTorch version`` is ``below 1.6`` since max_fpr requires `torch.bucketize`\n            which is not available below 1.6.\n        ValueError:\n            If ``max_fpr`` is not set to ``None`` and the mode is ``not binary``\n            since partial AUC computation is not available in multilabel/multiclass.\n        ValueError:\n            If ``average`` is none of ``None``, ``\"macro\"`` or ``\"weighted\"``.\n\n    Example (binary case):\n        >>> from torchmetrics.functional import auroc\n        >>> preds = torch.tensor([0.13, 0.26, 0.08, 0.19, 0.34])\n        >>> target = torch.tensor([0, 0, 1, 1, 1])\n        >>> auroc(preds, target, pos_label=1)\n        tensor(0.5000)\n\n    Example (multiclass case):\n        >>> preds = torch.tensor([[0.90, 0.05, 0.05],\n        ...                       [0.05, 0.90, 0.05],\n        ...                       [0.05, 0.05, 0.90],\n        ...                       [0.85, 0.05, 0.10],\n        ...                       [0.10, 0.10, 0.80]])\n        >>> target = torch.tensor([0, 1, 1, 2, 2])\n        >>> auroc(preds, target, num_classes=3)\n        tensor(0.7778)\n    \"\"\"\n    preds, target, mode = _auroc_update(preds, target)\n    return _auroc_compute(preds, target, mode, num_classes, pos_label, average, max_fpr, sample_weights)\n", "path": "torchmetrics/functional/classification/auroc.py"}]}