Source code for newsreclib.metrics.functional

from typing import Optional

import torch
import torch.nn.functional as F
from torchmetrics.utilities.checks import _check_retrieval_functional_inputs


[docs]def diversity( preds: torch.Tensor, target: torch.Tensor, num_classes: int, top_k: Optional[int] = None ) -> torch.Tensor: """Computes `Aspect-based Diversity`. Reference: Iana, Andreea, Goran Glavaš, and Heiko Paulheim. "Train Once, Use Flexibly: A Modular Framework for Multi-Aspect Neural News Recommendation." arXiv preprint arXiv:2307.16089 (2023). `https://arxiv.org/pdf/2307.16089.pdf` Args: preds: Estimated probabilities of each candidate news to be clicked. target: Ground truth about the aspect :math:`A_p` of the news being relevant or not. num_classes: Number of classes of the aspect :math:`A_p`. top_k: Consider only the top k elements for each query (default: ``None``, which considers them all). Returns: A single-value tensor with the aspect-based diversity (:math:`D_{A_p}`) of the predictions ``preds`` wrt the labels ``target``. """ preds, target = _check_retrieval_functional_inputs(preds, target, allow_non_binary_target=True) top_k = preds.shape[-1] if top_k is None else top_k if not (isinstance(top_k, int) and top_k > 0): raise ValueError("`top_k` has to be a positive integer or None") sorted_target = target[torch.argsort(preds, dim=-1, descending=True)][:top_k] target_count = torch.bincount(sorted_target) padded_target_count = F.pad(target_count, pad=(0, num_classes - target_count.shape[0])) target_prob = padded_target_count / padded_target_count.shape[0] target_dist = torch.distributions.Categorical(target_prob) diversity = torch.div( target_dist.entropy(), torch.log(torch.tensor(num_classes, device=preds.device)) ) return diversity
[docs]def personalization( preds: torch.Tensor, predicted_aspects: torch.Tensor, target_aspects: torch.Tensor, num_classes: int, top_k: Optional[int] = None, ) -> torch.Tensor: """Computes `Aspect-based Personalization`. Reference: Iana, Andreea, Goran Glavaš, and Heiko Paulheim. "Train Once, Use Flexibly: A Modular Framework for Multi-Aspect Neural News Recommendation." arXiv preprint arXiv:2307.16089 (2023). `https://arxiv.org/pdf/2307.16089.pdf` Args: preds: Estimated probabilities of each candidate news to be clicked. predicted_aspects: Aspects of the news predicted to be clicked. target_aspects: Ground truth about the aspect :math:`A_p` of the news being relevant or not. num_classes: Number of classes of the aspect :math:`A_p`. top_k: Consider only the top k elements for each query (default: ``None``, which considers them all). Returns: A single-value tensor with the aspect-based personalization (:math:`PS_{A_p}`) of the predictions ``preds`` and ``predicted_aspects`` wrt the labels ``target_aspects``. """ preds, predicted_aspects = _check_retrieval_functional_inputs( preds, predicted_aspects, allow_non_binary_target=True ) top_k = preds.shape[-1] if top_k is None else top_k if not (isinstance(top_k, int) and top_k > 0): raise ValueError("`top_k` has to be a positive integer or None") sorted_predicted_aspects = predicted_aspects[torch.argsort(preds, dim=-1, descending=True)][ :top_k ] predicted_aspects_count = torch.bincount(sorted_predicted_aspects) padded_predicted_aspects_count = F.pad( predicted_aspects_count, pad=(0, num_classes - predicted_aspects_count.shape[0]) ) target_aspects_count = torch.bincount(target_aspects) padded_target_aspects_count = F.pad( target_aspects_count, pad=(0, num_classes - target_aspects_count.shape[0]) ) personalization = generalized_jaccard( padded_predicted_aspects_count, padded_target_aspects_count ) return personalization
[docs]def generalized_jaccard(pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor: """Computes the Generalized Jaccard metric. Reference: Bonnici, Vincenzo. "Kullback-Leibler divergence between quantum distributions, and its upper-bound." arXiv preprint arXiv:2008.05932 (2020). Args: preds: Estimated probability distribution. target: Target probability distribution. Returns: A single-value tensor with the generalized Jaccard of the predictions ``preds`` wrt the labels ``target``. """ assert pred.shape == target.shape jaccard = torch.min(pred, target).sum(dim=0) / torch.max(pred, target).sum(dim=0) return jaccard
[docs]def harmonic_mean(scores: torch.Tensor) -> torch.Tensor: """Computes the harmonic mean of `N` scores. Args: scores: A tensor of scores. Returns: A single-value tensor with the harmonic mean of the scores. """ weights = torch.ones(scores.shape, device=scores.device) harmonic_mean = torch.div(torch.sum(weights), torch.sum(torch.div(weights, scores))) return harmonic_mean