# Copyright The Lightning team. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # 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. from typing import Optional import torch from torch import Tensor from torch.nn.functional import pad from torchmetrics.utilities.checks import _check_retrieval_functional_inputs from torchmetrics.utilities.data import _cumsum def retrieval_precision_recall_curve( preds: Tensor, target: Tensor, max_k: Optional[int] = None, adaptive_k: bool = False ) -> tuple[Tensor, Tensor, Tensor]: """Compute precision-recall pairs for different k (from 1 to `max_k`). In a ranked retrieval context, appropriate sets of retrieved documents are naturally given by the top k retrieved documents. Recall is the fraction of relevant documents retrieved among all the relevant documents. Precision is the fraction of relevant documents among all the retrieved documents. For each such set, precision and recall values can be plotted to give a recall-precision curve. ``preds`` and ``target`` should be of the same shape and live on the same device. If no ``target`` is ``True``, ``0`` is returned. ``target`` must be either `bool` or `integers` and ``preds`` must be ``float``, otherwise an error is raised. Args: preds: estimated probabilities of each document to be relevant. target: ground truth about each document being relevant or not. max_k: Calculate recall and precision for all possible top k from 1 to max_k (default: `None`, which considers all possible top k) adaptive_k: adjust `max_k` to `min(max_k, number of documents)` for each query Returns: Tensor with the precision values for each k (at ``top_k``) from 1 to `max_k` Tensor with the recall values for each k (at ``top_k``) from 1 to `max_k` Tensor with all possibles k Raises: ValueError: If ``max_k`` is not `None` or an integer larger than 0. ValueError: If ``adaptive_k`` is not boolean. Example: >>> from torch import tensor >>> from torchmetrics.functional import retrieval_precision_recall_curve >>> preds = tensor([0.2, 0.3, 0.5]) >>> target = tensor([True, False, True]) >>> precisions, recalls, top_k = retrieval_precision_recall_curve(preds, target, max_k=2) >>> precisions tensor([1.0000, 0.5000]) >>> recalls tensor([0.5000, 0.5000]) >>> top_k tensor([1, 2]) """ preds, target = _check_retrieval_functional_inputs(preds, target) if not isinstance(adaptive_k, bool): raise ValueError("`adaptive_k` has to be a boolean") if max_k is None: max_k = preds.shape[-1] if not (isinstance(max_k, int) and max_k > 0): raise ValueError("`max_k` has to be a positive integer or None") if adaptive_k and max_k > preds.shape[-1]: topk = torch.arange(1, preds.shape[-1] + 1, device=preds.device) topk = pad(topk, (0, max_k - preds.shape[-1]), "constant", float(preds.shape[-1])) else: topk = torch.arange(1, max_k + 1, device=preds.device) if not target.sum(): return torch.zeros(max_k, device=preds.device), torch.zeros(max_k, device=preds.device), topk relevant = target[preds.topk(min(max_k, preds.shape[-1]), dim=-1)[1]].float() relevant = _cumsum(pad(relevant, (0, max(0, max_k - len(relevant))), "constant", 0.0), dim=0) recall = relevant / target.sum() precision = relevant / topk return precision, recall, topk