File size: 2,919 Bytes
583e46a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
"""
Evaluation metrics module.
Computes accuracy, precision, recall, F1 (macro and per-class).
"""

from typing import Optional

import numpy as np
from sklearn.metrics import (
    accuracy_score,
    precision_score,
    recall_score,
    f1_score,
    classification_report,
    confusion_matrix,
)


def compute_metrics(
    y_true: list[int],
    y_pred: list[int],
    class_names: Optional[list[str]] = None,
    average: str = 'macro',
) -> dict:
    """
    Compute comprehensive classification metrics.

    Returns:
        dict with accuracy, macro/weighted F1, precision, recall,
        per-class metrics, and classification report.
    """
    y_true = np.array(y_true)
    y_pred = np.array(y_pred)

    metrics = {
        'accuracy': float(accuracy_score(y_true, y_pred)),
        'macro_precision': float(precision_score(y_true, y_pred, average='macro', zero_division=0)),
        'macro_recall': float(recall_score(y_true, y_pred, average='macro', zero_division=0)),
        'macro_f1': float(f1_score(y_true, y_pred, average='macro', zero_division=0)),
        'weighted_precision': float(precision_score(y_true, y_pred, average='weighted', zero_division=0)),
        'weighted_recall': float(recall_score(y_true, y_pred, average='weighted', zero_division=0)),
        'weighted_f1': float(f1_score(y_true, y_pred, average='weighted', zero_division=0)),
    }

    # Per-class metrics
    per_class_precision = precision_score(y_true, y_pred, average=None, zero_division=0)
    per_class_recall = recall_score(y_true, y_pred, average=None, zero_division=0)
    per_class_f1 = f1_score(y_true, y_pred, average=None, zero_division=0)

    per_class = {}
    for i, f1_val in enumerate(per_class_f1):
        name = class_names[i] if class_names and i < len(class_names) else str(i)
        per_class[name] = {
            'precision': float(per_class_precision[i]),
            'recall': float(per_class_recall[i]),
            'f1': float(f1_val),
            'support': int(np.sum(y_true == i)),
        }

    metrics['per_class'] = per_class

    # Confusion matrix
    cm = confusion_matrix(y_true, y_pred)
    metrics['confusion_matrix'] = cm.tolist()

    # Classification report string
    if class_names:
        report = classification_report(
            y_true, y_pred, target_names=class_names, zero_division=0,
        )
    else:
        report = classification_report(y_true, y_pred, zero_division=0)
    metrics['classification_report'] = report

    return metrics


def compute_top_k_accuracy(
    probabilities: list[list[float]],
    y_true: list[int],
    k: int = 3,
) -> float:
    """Compute top-k accuracy from probability distributions."""
    correct = 0
    for probs, true_label in zip(probabilities, y_true):
        top_k_preds = np.argsort(probs)[-k:]
        if true_label in top_k_preds:
            correct += 1
    return correct / len(y_true) if y_true else 0.0