Module neuralops.evaluation.label_quality

Label quality detection – inspired by Cleanlab, the industry standard for data‑centric AI. Finds label errors, outliers, and near‑duplicates.

Reference: https://github.com/cleanlab/cleanlab

Classes

class LabelQualityDetector (threshold: float = 0.1, seed: int = 42)
Expand source code
class LabelQualityDetector:
    """
    Detects label errors, outliers, and near‑duplicates using confident learning.

    Usage:
        detector = LabelQualityDetector()
        report = detector.analyze(labels=y_true, pred_probs=model_output_probs)

    No ground‑truth validation set is required – the method works entirely from
    model predictions and given labels.
    """

    def __init__(self, threshold: float = 0.1, seed: int = 42) -> None:
        self.threshold = threshold
        self.seed = seed

    # ── Main API ────────────────────────────────────────────────────
    def analyze(
        self,
        labels: np.ndarray,
        pred_probs: np.ndarray,
        class_names: list[str] | None = None,
    ) -> LabelQualityReport:
        """
        Run full label quality analysis.

        Args:
            labels: integer labels, shape (N,)
            pred_probs: model predicted probabilities, shape (N, K)
            class_names: optional list of class name strings
        """
        n_samples, n_classes = pred_probs.shape
        np.random.seed(self.seed)

        # 1. Compute confident joint (estimate of noise matrix)
        cj = self._compute_confident_joint(labels, pred_probs)

        # 2. Find label issues
        label_issues_mask, quality_scores, suggested = self._find_label_issues(
            labels, pred_probs, cj
        )

        # 3. Per‑class breakdown
        per_class = {}
        for k in range(n_classes):
            mask_k = labels == k
            per_class[k] = int(np.sum(label_issues_mask[mask_k]))

        # 4. Outlier detection (using prediction entropy)
        entropy = -np.sum(pred_probs * np.log(pred_probs + 1e-10), axis=1)
        outlier_thresh = np.percentile(entropy, 95)
        outlier_indices = np.where(entropy > outlier_thresh)[0]

        # 5. Near‑duplicate detection (using prediction cosine similarity)
        near_dupes = self._find_near_duplicates(pred_probs)

        # 6. Overall quality score
        overall = 1.0 - np.mean(label_issues_mask)

        return LabelQualityReport(
            n_samples=n_samples,
            n_label_issues=int(np.sum(label_issues_mask)),
            issue_rate=round(np.mean(label_issues_mask), 4),
            label_issue_indices=np.where(label_issues_mask)[0],
            label_quality_scores=quality_scores,
            suggested_labels=suggested,
            per_class_issues=per_class,
            outlier_indices=outlier_indices,
            near_duplicate_pairs=near_dupes,
            overall_quality_score=round(overall, 4),
        )

    # ── Confident Joint (core algorithm) ────────────────────────────
    def _compute_confident_joint(self, labels: np.ndarray, pred_probs: np.ndarray) -> np.ndarray:
        """
        Estimate the joint distribution of noisy labels and true labels.
        This is the key insight from Cleanlab / confident learning.
        """
        n_samples, n_classes = pred_probs.shape
        pred_classes = np.argmax(pred_probs, axis=1)

        # Average threshold for each class: t_k = mean prob of class k when k is predicted
        thresholds = np.zeros(n_classes)
        for k in range(n_classes):
            mask = pred_classes == k
            if mask.sum() > 0:
                thresholds[k] = np.mean(pred_probs[mask, k])
            else:
                thresholds[k] = 0.5

        # Confident joint: count how many times (given_label = i, true_label = j)
        cj = np.zeros((n_classes, n_classes), dtype=np.float64)
        for i in range(n_samples):
            true_label_guess = np.argmax(pred_probs[i])
            given_label = labels[i]
            if pred_probs[i, true_label_guess] >= thresholds[true_label_guess]:
                cj[given_label, true_label_guess] += 1

        # Normalise to probabilities
        cj = cj / max(cj.sum(), 1.0)
        return cj

    # ── Label Issue Detection ───────────────────────────────────────
    def _find_label_issues(
        self, labels: np.ndarray, pred_probs: np.ndarray, cj: np.ndarray
    ) -> tuple[np.ndarray, np.ndarray, np.ndarray | None]:
        """
        Return: (issue_mask, quality_scores, suggested_labels)
        """
        n_samples, n_classes = pred_probs.shape

        # For each sample, the label quality score = 1 - P(mislabeled)
        self_confidence = pred_probs[np.arange(n_samples), labels]
        quality_scores = self_confidence

        # A sample is a label issue if self‑confidence is below the class threshold
        thresholds = np.zeros(n_classes)
        for k in range(n_classes):
            mask = labels == k
            if mask.sum() > 0:
                thresholds[k] = np.mean(pred_probs[mask, k])
            else:
                thresholds[k] = 0.5

        issue_mask = np.zeros(n_samples, dtype=bool)
        for k in range(n_classes):
            mask_k = labels == k
            issue_mask[mask_k] = self_confidence[mask_k] < (thresholds[k] * (1 - self.threshold))

        # Suggested labels: what the model predicts instead
        suggested = np.argmax(pred_probs, axis=1)
        suggested[~issue_mask] = labels[~issue_mask]  # keep original if not an issue

        return issue_mask, quality_scores, suggested

    # ── Near‑Duplicate Detection ────────────────────────────────────
    def _find_near_duplicates(
        self, pred_probs: np.ndarray, threshold: float = 0.95
    ) -> list[tuple[int, int]]:
        """Find pairs of samples whose prediction vectors are nearly identical."""
        from sklearn.metrics.pairwise import cosine_similarity

        n = pred_probs.shape[0]
        if n > 5000:
            # Subsample for performance
            indices = np.random.choice(n, min(5000, n), replace=False)
            subset = pred_probs[indices]
            sim = cosine_similarity(subset)
        else:
            sim = cosine_similarity(pred_probs)
        np.fill_diagonal(sim, 0)
        pairs = np.argwhere(sim > threshold)
        # De‑duplicate symmetric pairs
        seen = set()
        result = []
        for i, j in pairs:
            if i < j and (i, j) not in seen:
                result.append((int(i), int(j)))
                seen.add((i, j))
        return result

    # ── Summary helpers ─────────────────────────────────────────────
    def summary(self, report: LabelQualityReport) -> dict[str, Any]:
        """Summary."""
        return {
            "total_samples": report.n_samples,
            "label_issues_found": report.n_label_issues,
            "issue_rate": report.issue_rate,
            "overall_quality": report.overall_quality_score,
            "n_outliers": len(report.outlier_indices),
            "n_near_duplicates": len(report.near_duplicate_pairs),
            "worst_classes": sorted(
                report.per_class_issues.items(), key=lambda x: x[1], reverse=True
            )[:5],
        }

Detects label errors, outliers, and near‑duplicates using confident learning.

Usage -----= detector = LabelQualityDetector() report = detector.analyze(labels=y_true, pred_probs=model_output_probs)

No ground‑truth validation set is required – the method works entirely from model predictions and given labels.

Methods

def analyze(self,
labels: numpy.ndarray,
pred_probs: numpy.ndarray,
class_names: list[str] | None = None) ‑> LabelQualityReport
Expand source code
def analyze(
    self,
    labels: np.ndarray,
    pred_probs: np.ndarray,
    class_names: list[str] | None = None,
) -> LabelQualityReport:
    """
    Run full label quality analysis.

    Args:
        labels: integer labels, shape (N,)
        pred_probs: model predicted probabilities, shape (N, K)
        class_names: optional list of class name strings
    """
    n_samples, n_classes = pred_probs.shape
    np.random.seed(self.seed)

    # 1. Compute confident joint (estimate of noise matrix)
    cj = self._compute_confident_joint(labels, pred_probs)

    # 2. Find label issues
    label_issues_mask, quality_scores, suggested = self._find_label_issues(
        labels, pred_probs, cj
    )

    # 3. Per‑class breakdown
    per_class = {}
    for k in range(n_classes):
        mask_k = labels == k
        per_class[k] = int(np.sum(label_issues_mask[mask_k]))

    # 4. Outlier detection (using prediction entropy)
    entropy = -np.sum(pred_probs * np.log(pred_probs + 1e-10), axis=1)
    outlier_thresh = np.percentile(entropy, 95)
    outlier_indices = np.where(entropy > outlier_thresh)[0]

    # 5. Near‑duplicate detection (using prediction cosine similarity)
    near_dupes = self._find_near_duplicates(pred_probs)

    # 6. Overall quality score
    overall = 1.0 - np.mean(label_issues_mask)

    return LabelQualityReport(
        n_samples=n_samples,
        n_label_issues=int(np.sum(label_issues_mask)),
        issue_rate=round(np.mean(label_issues_mask), 4),
        label_issue_indices=np.where(label_issues_mask)[0],
        label_quality_scores=quality_scores,
        suggested_labels=suggested,
        per_class_issues=per_class,
        outlier_indices=outlier_indices,
        near_duplicate_pairs=near_dupes,
        overall_quality_score=round(overall, 4),
    )

Run full label quality analysis.

Args
-----=
labels
integer labels, shape (N,)
pred_probs
model predicted probabilities, shape (N, K)
class_names
optional list of class name strings
def summary(self,
report: LabelQualityReport) ‑> dict[str, typing.Any]
Expand source code
def summary(self, report: LabelQualityReport) -> dict[str, Any]:
    """Summary."""
    return {
        "total_samples": report.n_samples,
        "label_issues_found": report.n_label_issues,
        "issue_rate": report.issue_rate,
        "overall_quality": report.overall_quality_score,
        "n_outliers": len(report.outlier_indices),
        "n_near_duplicates": len(report.near_duplicate_pairs),
        "worst_classes": sorted(
            report.per_class_issues.items(), key=lambda x: x[1], reverse=True
        )[:5],
    }

Summary.

class LabelQualityReport (n_samples: int,
n_label_issues: int,
issue_rate: float,
label_issue_indices: numpy.ndarray,
label_quality_scores: numpy.ndarray,
suggested_labels: numpy.ndarray | None = None,
per_class_issues: dict[int, int] = <factory>,
outlier_indices: numpy.ndarray = <factory>,
near_duplicate_pairs: list[tuple[int, int]] = <factory>,
overall_quality_score: float = 0.0)
Expand source code
@dataclass
class LabelQualityReport:
    """Complete label quality analysis for a dataset."""

    n_samples: int
    n_label_issues: int
    issue_rate: float
    label_issue_indices: np.ndarray
    label_quality_scores: np.ndarray  # per‑sample confidence (0 = bad, 1 = perfect)
    suggested_labels: np.ndarray | None = None  # what cleanlab thinks the label should be
    per_class_issues: dict[int, int] = field(default_factory=dict)
    outlier_indices: np.ndarray = field(default_factory=lambda: np.array([]))
    near_duplicate_pairs: list[tuple[int, int]] = field(default_factory=list)
    overall_quality_score: float = 0.0

Complete label quality analysis for a dataset.

Instance variables

var issue_rate : float

The type of the None singleton.

var label_issue_indices : numpy.ndarray

The type of the None singleton.

var label_quality_scores : numpy.ndarray

The type of the None singleton.

var n_label_issues : int

The type of the None singleton.

var n_samples : int

The type of the None singleton.

var near_duplicate_pairs : list[tuple[int, int]]

The type of the None singleton.

var outlier_indices : numpy.ndarray

The type of the None singleton.

var overall_quality_score : float

The type of the None singleton.

var per_class_issues : dict[int, int]

The type of the None singleton.

var suggested_labels : numpy.ndarray | None

The type of the None singleton.