Malcolm Low | Practical AI, Computing & Quantum Research

Singapore-based practical guides, tutorials and experiments in AI, computing, modelling, simulation, optimisation and quantum computing, with research notes and hands-on workflows.

, , ,

Classification Diagnostics: Confusion Matrix, Precision, Recall & F1-Score

POCKET DATA SCIENCE • BACKGROUND READING • EVALUATION METRICS In classification modeling, raw accuracy is frequently a deceptive vanity metric. When class distributions are skewed—as is typical in fraud detection, medical triage, disaster alert filtering, and competitive Kaggle challenges—a naive baseline predicting only the majority class can easily score 95% to 99% accuracy while delivering…

·

Written by

POCKET DATA SCIENCE • BACKGROUND READING • EVALUATION METRICS

In classification modeling, raw accuracy is frequently a deceptive vanity metric. When class distributions are skewed—as is typical in fraud detection, medical triage, disaster alert filtering, and competitive Kaggle challenges—a naive baseline predicting only the majority class can easily score 95% to 99% accuracy while delivering zero predictive utility. To truly diagnose how a model behaves, data scientists rely on the Confusion Matrix and its derivative diagnostic metrics: Precision, Recall, and the F1-Score (along with generalized F-Beta formulations). Here is the complete mathematical, geometric, and operational breakdown of classification diagnostics: navigating Type I and Type II error trade-offs, computing Micro, Macro, and Weighted multi-class aggregations, and tuning probability decision thresholds on memory-constrained mobile hardware.

💻 Open-Source Code: The Python simulation scripts, vectorized confusion matrix implementations, multi-class aggregators, and optimal threshold search routines are open-sourced on GitHub: myhlow/pocket-data-science (05-mathematical-foundations).

💡 Core Principle: Accuracy hides mistakes; the confusion matrix exposes them. Decoupling False Positives (Type I errors) from False Negatives (Type II errors) is essential because in real-world systems and competitive benchmarks, the operational and statistical costs of these two failure modes are almost never symmetric.
1  ·  The Confusion Matrix Anatomy & The Four Fundamental Outcomes

A Confusion Matrix is a cross-tabulated 2×2 contingency table that compares the ground truth labels against the predictions produced by a binary classifier. Every prediction in the evaluation set maps onto exactly one of four mutually exclusive quadrants:

Diagnostic Quadrant Statistical Identity Condition / State Operational Meaning & Real-World Consequence
True Positive (TP) Hit / Detection Actual = 1, Predicted = 1 Correct positive alert. The model detected an event that genuinely occurred (e.g., fraudulent card transaction correctly blocked).
False Positive (FP) Type I Error (α) Actual = 0, Predicted = 1 False Alarm. The model flags a non-event as positive (e.g., legitimate user purchase declined; benign email sent to spam).
False Negative (FN) Type II Error (β) Actual = 1, Predicted = 0 Miss / Silent Failure. The model failed to detect an actual positive event (e.g., malignant tumor classified as benign; disaster tweet ignored).
True Negative (TN) Correct Rejection Actual = 0, Predicted = 0 Correct baseline pass. Normal operation correctly ignored by the detector.

Layout Convention Note: In machine learning frameworks like Scikit-Learn (sklearn.metrics.confusion_matrix), rows represent the ground truth classes while columns represent the predicted classes: row 0 corresponds to actual negatives, row 1 to actual positives. Be vigilant when reading academic papers or medical literature, where axes are sometimes reversed.

2  ·  The Accuracy Paradox & The Perils of Class Imbalance

Overall classification accuracy is computed as the proportion of all correct decisions divided by the total number of evaluations:

Accuracy = (TP + TN) / (TP + TN + FP + FN)

While intuitive, accuracy suffers from the Accuracy Paradox when class distributions are imbalanced. Consider a disease screening cohort of N = 1,000 patients, where only 50 patients (5.0%) have the condition, and 950 (95.0%) are healthy:

Model Candidate Confusion Matrix [TN, FP, FN, TP] Raw Accuracy Recall (Sensitivity) F1-Score Clinical / Operational Verdict
Naive Majority Classifier [950, 0, 50, 0] 95.0% 0.0% 0.000 Catastrophic failure: Missed every single patient with the disease despite boasting a 95% accuracy score.
Trained Machine Learning Model [890, 60, 10, 40] 93.0% 80.0% 0.533 Clinically viable: Successfully caught 80% of affected patients. Sacrificed 2% accuracy to gain 40 lives.

Relying on accuracy would lead an automated model selection pipeline to discard the trained machine learning model in favor of the completely useless dummy classifier. This is why competitive Kaggle benchmarks and real-world production systems discard raw accuracy in favor of decoupled diagnostic metrics.

3  ·  Precision vs. Recall: The Fundamental Engineering Trade-Off

To resolve the accuracy paradox, we decompose performance into two complementary perspectives: quality of alerts (Precision) versus completeness of coverage (Recall).

Metric Mathematical Formula Core Question Answered When to Prioritize
Precision (PPV) TP / (TP + FP) “When the model predicts positive, how often is it right?” Measures alert purity. When False Positives are expensive or harmful (e.g., spam filters, automated stock buys, legal evidence flagging).
Recall (Sensitivity, TPR) TP / (TP + FN) “Out of all actual positive instances, how many did the model find?” Measures exhaustiveness. When False Negatives are intolerable or catastrophic (e.g., disease detection, airport security threats, emergency disaster tweets).
Specificity (TNR) TN / (TN + FP) “Out of all actual negative cases, how many were correctly left alone?” Measures true quietness. When confirming the absence of a condition to prevent invasive follow-up procedures.
Fall-Out (FPR) FP / (FP + TN) = 1 − Specificity “What percentage of benign samples are falsely alarmed?” Forms the x-axis of ROC curves. Monitoring alert volume ceilings in high-throughput telecommunications and intrusion detection.

The Precision-Recall Seesaw

Precision and Recall exist in intrinsic opposition. If you adjust your model to be more conservative (demanding 95% probability before flagging an instance as positive), False Positives drop to near zero, causing Precision to surge. However, many borderline positive cases will be classified as negative, increasing False Negatives and causing Recall to plummet.

Conversely, lowering the threshold to catch every marginal positive case boosts Recall toward 100%, but sweeps in hundreds of benign false alarms, cratering Precision.

4  ·  The Harmonic Mean: Mathematical Formulation of F1 & F-Beta

To optimize and compare models with a single scalar score, we require a balanced blend of Precision and Recall. Taking the standard arithmetic mean fails: a model with Precision = 1.0 and Recall = 0.0 would score (1.0 + 0.0) / 2 = 0.50, creating an illusion of acceptable performance for a model that catches zero positive instances.

Instead, machine learning relies on the Harmonic Mean, which punishes extreme imbalances by being dominated by the smaller of the two terms:

F1 = 2 · (Precision · Recall) / (Precision + Recall) = 2 · TP / (2 · TP + FP + FN)

Notice that True Negatives (TN) do not appear anywhere in the F1 formula. This property makes F1 completely invariant to massive floods of easy negatives, preserving its diagnostic sensitivity under extreme class skew.

The Generalized F-Beta (β) Family

In applications where one error type is objectively more damaging than the other, the generalized F-Beta score introduces a parameter β that governs the relative importance of Recall versus Precision:

Fβ = (1 + β2) · (Precision · Recall) / [ (β2 · Precision) + Recall ]
Beta Value Weighting Focus Formula Expansion Canonical Use Cases
β = 0.5 Weights Precision twice as heavily as Recall. 1.25 · (P · R) / (0.25P + R) Search engine recommendations, document summarization, automated customer email responses where false assertions ruin brand trust.
β = 1.0 Standard F1: Equal harmonic weight. 2.00 · (P · R) / (P + R) Default benchmark metric across Kaggle tabular competitions, NLP classification, and information extraction.
β = 2.0 Weights Recall twice as heavily as Precision. 5.00 · (P · R) / (4.0P + R) Emergency response, rare disease detection, Kaggle manufacturing defect detection (e.g., Severstal Steel Defect Detection).
5  ·  Multi-Class Aggregations: Macro, Micro, and Weighted Formulations

When extending classification from binary to C ≥ 3 classes (such as 3-class LLM preference modeling in Chatbot Arena or 10-class MNIST digit recognition), we construct a C × C confusion matrix. For each individual class k, we compute its One-vs-Rest (OvR) metrics:

  • TPk: Diagonal element M[k, k].
  • FPk: Sum of column k minus M[k, k] (instances falsely labeled as class k).
  • FNk: Sum of row k minus M[k, k] (actual class k instances assigned to other classes).

To summarize the entire model with a single diagnostic score, we choose among three aggregation strategies:

Aggregation Type Mathematical Formula Behavioral Characteristic Competition & Practical Use
Macro-Averaged F1 (1 / C) ∑k=1..C F1k Treats all classes identically regardless of sample frequency. Punishes models that neglect rare minority classes. Standard for multi-class NLP and fine-grained vision where minority classes are just as vital as major ones.
Weighted-Averaged F1 (1 / N) ∑k=1..C Nk · F1k Weights each per-class F1 by that class’s sample support Nk. Mitigates penalty from rare outliers. Used when class imbalance reflects natural production frequency and you want an overall population estimate.
Micro-Averaged F1 2∑TPk / [2∑TPk + ∑FPk + ∑FNk] Pools global TP, FP, and FN across all classes. In single-label multi-class, Micro-F1 is mathematically identical to Accuracy. Common in multi-label classification (where each sample can possess multiple tags simultaneously).

Worked Numerical Example: 3-Class Animal Classifier (100 Samples)

Consider an edge-deployed animal vision classifier evaluated on N = 100 test images across three categories: Cat (30 samples), Dog (50 samples), and Bird (20 samples). The resulting confusion matrix is:

Actual Class (Ground Truth) Predicted Class (Model Decision) Actual Support (Nk)
Pred Cat Pred Dog Pred Bird
Act Cat
25
Hit (TP0)
3 2 30
Act Dog 5
42
Hit (TP1)
3 50
Act Bird 4 2
14
Hit (TP2)
20
Pred Total 34 47 19 N = 100

Main Diagonal (Green): Correctly classified hits (∑ TPk = 25 + 42 + 14 = 81 observations → 81.0% overall accuracy). Off-diagonal red numbers represent the 19 misclassifications.

Computing the per-class diagnostics:

  • Cat (Class 0): TP = 25, FP = 9, FN = 5 → Precision = 25/34 = 0.7353, Recall = 25/30 = 0.8333 → F1 = 0.7812
  • Dog (Class 1): TP = 42, FP = 5, FN = 8 → Precision = 42/47 = 0.8936, Recall = 42/50 = 0.8400 → F1 = 0.8660
  • Bird (Class 2): TP = 14, FP = 5, FN = 6 → Precision = 14/19 = 0.7368, Recall = 14/20 = 0.7000 → F1 = 0.7179

Aggregating the overall metrics across the three classes:

  • Macro F1: (0.7812 + 0.8660 + 0.7179) / 3 = 0.7884
  • Weighted F1: (30×0.7812 + 50×0.8660 + 20×0.7179) / 100 = 0.8110
  • Micro F1 (Accuracy): (25 + 42 + 14) / 100 = 0.8100

Notice that Macro F1 is significantly lower (0.7884) than Weighted F1 (0.8110). Because Bird is the minority class (only 20 samples) and suffered the lowest F1 score (0.7179), the unweighted Macro calculation penalizes the model fairly for poor minority performance, whereas Weighted F1 and Accuracy let the dominant Dog class mask the deficiency.

6  ·  Probability Threshold Tuning & PR Curves on Mobile RAM Budgets

Most classification algorithms (LightGBM, XGBoost, CatBoost, Logistic Regression, Neural Networks) do not output discrete 0 or 1 labels; they output continuous predicted probabilities p = P(y = 1 | x) ∈ [0.0, 1.0]. By default, virtually all frameworks convert these probabilities into labels using an arbitrary threshold τ = 0.50:

ŷpred
=
{

1,    if   p ≥ τ
0,    if   p < τ
The decision threshold τ maps continuous probability p = P(y = 1 | x) to discrete predicted state ŷpred ∈ {0, 1}

Under class imbalance, τ = 0.50 is almost never optimal. By performing a vectorized grid search over τ ∈ [0.01, 0.99] across Out-of-Fold (OOF) cross-validation predictions, you can locate the exact decision boundary that maximizes your evaluation metric.

Empirical Demonstration: Threshold Optimization on Imbalanced Data

Evaluating 500 samples containing a 10% positive rate under a calibrated GBDT model:

Decision Threshold τ Precision Recall F1-Score Operational Analysis
τ = 0.50 (Default) 1.0000 0.3636 0.5333 Severely over-conservative: achieves zero false alarms, but misses 63.6% of positive instances.
τ* = 0.37 (Optimized) 0.7162 0.9636 0.8217 Global F1 Optimum: F1 jumps by +28.84 points (0.5333 → 0.8217) by lowering the threshold to accept minor false alarms in exchange for 96.4% recall.
τ = 0.15 (High Recall) 0.3240 1.0000 0.4894 Exhaustive recall achieved, but precision degraded by excessive false positive triggers.

PR Curve vs. ROC Curve Under Severe Skew

Why do experienced Kaggle practitioners plot the Precision-Recall (PR) Curve rather than the standard Receiver Operating Characteristic (ROC) curve on imbalanced datasets?

  • The ROC-AUC Flaw: ROC plots True Positive Rate vs. False Positive Rate (FPR = FP / (FP + TN)). When True Negatives are vast (e.g., 99,000 negatives and 1,000 positives), adding 500 false alarms barely moves FPR (from 0 to 0.005), giving the illusion that the model is near-flawless with an ROC-AUC of 0.98.
  • The PR-AUC Clarity: The PR curve plots Precision (TP / (TP + FP)) vs. Recall (TP / (TP + FN)). Those exact same 500 false positives cut Precision in half (from 100% to 66%), immediately exposing the degradation on the PR-AUC plot.
7  ·  Robust Vectorized Python Implementation (Pure NumPy)

On mobile edge hardware (such as Android Termux running within limited memory envelopes), loading heavy scientific suites like full Scikit-Learn pipelines for metric evaluation introduces unnecessary memory overhead. Here is a production-grade, zero-dependency, vectorized NumPy diagnostic suite:

import numpy as np

def compute_confusion_matrix(y_true, y_pred, num_classes=None):
    \"\"\"Compute C x C confusion matrix using vectorized indexing.\"\"\"
    if num_classes is None:
        num_classes = max(np.max(y_true), np.max(y_pred)) + 1
    
    # Vectorized 2D bincount: row = true, col = pred
    indices = y_true * num_classes + y_pred
    cm = np.bincount(indices, minlength=num_classes**2).reshape(num_classes, num_classes)
    return cm

def classification_diagnostics(y_true, y_pred, average=\"macro\", beta=1.0):
    \"\"\"Vectorized computation of Precision, Recall, and F-Beta metrics.\"\"\"
    cm = compute_confusion_matrix(y_true, y_pred)
    num_classes = cm.shape[0]
    
    tp = np.diag(cm).astype(float)
    fp = cm.sum(axis=0) - tp
    fn = cm.sum(axis=1) - tp
    support = cm.sum(axis=1)
    total_samples = support.sum()
    
    # Per-class metrics with zero-division handling
    precision_per_class = np.divide(tp, tp + fp, out=np.zeros_like(tp), where=(tp + fp) != 0)
    recall_per_class = np.divide(tp, tp + fn, out=np.zeros_like(tp), where=(tp + fn) != 0)
    
    beta_sq = beta ** 2
    denom = (beta_sq * precision_per_class) + recall_per_class
    f_beta_per_class = np.divide((1 + beta_sq) * (precision_per_class * recall_per_class),
                                 denom, out=np.zeros_like(tp), where=denom != 0)
    
    # Aggregation
    if average == \"macro\":
        agg_precision = float(np.mean(precision_per_class))
        agg_recall = float(np.mean(recall_per_class))
        agg_f_beta = float(np.mean(f_beta_per_class))
    elif average == \"weighted\":
        agg_precision = float(np.sum(precision_per_class * support) / total_samples)
        agg_recall = float(np.sum(recall_per_class * support) / total_samples)
        agg_f_beta = float(np.sum(f_beta_per_class * support) / total_samples)
    elif average == \"micro\":
        total_tp = tp.sum()
        total_fp = fp.sum()
        total_fn = fn.sum()
        agg_precision = float(total_tp / (total_tp + total_fp)) if (total_tp + total_fp) > 0 else 0.0
        agg_recall = float(total_tp / (total_tp + total_fn)) if (total_tp + total_fn) > 0 else 0.0
        agg_f_beta = float(2 * agg_precision * agg_recall / (agg_precision + agg_recall)) if (agg_precision + agg_recall) > 0 else 0.0
    
    accuracy = float(np.trace(cm) / total_samples)
    return {
        \"confusion_matrix\": cm,
        \"accuracy\": accuracy,
        \"precision\": agg_precision,
        \"recall\": agg_recall,
        \"f_beta\": agg_f_beta,
        \"per_class\": {
            \"precision\": precision_per_class,
            \"recall\": recall_per_class,
            \"f_beta\": f_beta_per_class,
            \"support\": support
        }
    }

def optimize_binary_threshold(y_true, y_prob, metric=\"f1\", steps=100):
    \"\"\"Vectorized search to locate optimal probability decision threshold.\"\"\"
    thresholds = np.linspace(0.01, 0.99, steps)
    best_score, best_thresh = -1.0, 0.50
    
    for t in thresholds:
        pred = (y_prob >= t).astype(int)
        tp = np.sum((y_true == 1) & (pred == 1))
        fp = np.sum((y_true == 0) & (pred == 1))
        fn = np.sum((y_true == 1) & (pred == 0))
        
        prec = tp / (tp + fp) if (tp + fp) > 0 else 0.0
        rec = tp / (tp + fn) if (tp + fn) > 0 else 0.0
        score = 2 * (prec * rec) / (prec + rec) if (prec + rec) > 0 else 0.0
        
        if score > best_score:
            best_score = score
            best_thresh = float(t)
            
    return best_thresh, best_score
8  ·  Practical Takeaways for Machine Learning Competitions & Edge Systems

When designing machine learning models for edge deployment or competing on Kaggle leaderboards, apply these four battle-tested diagnostic principles:

  • Rule 1: Always Normalize the Confusion Matrix: Raw counts can deceive. Plotting the row-normalized confusion matrix (where rows sum to 1.0) displays recall per class directly, immediately revealing which minority classes are being silently absorbed by the majority.
  • Rule 2: Tune Thresholds Strictly Out-of-Fold (OOF): Never optimize your decision threshold on the entire training set. Threshold searching on in-sample predictions leads to severe overfitting. Tune your optimal threshold τ* across validation folds, then average or ensemble the fold thresholds before applying to test data.
  • Rule 3: Match Loss Functions to Diagnostic Objectives: When evaluating on F1 or F-Beta under heavy skew, standard binary cross-entropy loss can pull probabilities toward zero. Adopting Focal Loss (downweighting easy negatives) or class-weighted log loss during training produces better probability distributions for threshold tuning.
  • Rule 4: Beware the Multi-Class Micro-F1 Trap: If a competition specifies Micro-F1 as the evaluation metric on single-label data, recognize that it is mathematically identical to Accuracy. Spend your modeling effort maximizing raw classification accuracy rather than balancing per-class precision and recall.

Recommended Machine Learning Reading

Explore related hands-on engineering case studies in our mobile data science series:

Continue the Pocket Data Science Series

Comments

2 responses to “Classification Diagnostics: Confusion Matrix, Precision, Recall & F1-Score”

  1. Pocket Data Science: Full Mobile ML & Kaggle Roadmap – Malcolm Low | Practical AI, Computing & Quantum Research Avatar

    […] Classification Diagnostics: Confusion Matrix, Precision, Recall & F1-Score […]

    Like

  2. Multi-Class Log Loss Explained: Math, MLE & Python Code – Malcolm Low | Practical AI, Computing & Quantum Research Avatar

    […] the Pocket Data Science Series → Next: Classification Diagnostics: Confusion Matrix, Precision, Recall & F1-Score → Learning path: View the complete Pocket Data Science Roadmap (6-Track Master Curriculum) […]

    Like

Leave a Reply