Before you start
Two ways to average F1 across classes, which disagree exactly when it matters.
For each class c, count TP_c, FP_c and FN_c treating that class as the
positive one.
Macro F1 computes precision, recall and F1 per class, then takes the unweighted mean of the per-class F1 scores:
macro = mean over c of f1_c
Micro F1 pools the counts across all classes first, then computes a single F1:
micro = f1 of (sum TP_c, sum FP_c, sum FN_c)
Return (macro, micro). Any metric with a zero denominator is 0.0.
The two answer different questions. Micro weights every sample equally, so in a single-label problem where every sample gets exactly one prediction it reduces to plain accuracy. Macro weights every class equally, so a rare class the model always gets wrong drags it down hard while barely moving micro. Report macro when the rare classes are the point.
Classes are integers in [0, num_classes).
def macro_micro_f1(y_true, y_pred, num_classes):
Input
y_true = np.array([0, 1, 2, 0])
y_pred = np.array([0, 2, 2, 0])
macro_micro_f1(y_true, y_pred, 3)
Output
(0.5555555555555555, 0.75)
Reasoning
Per class F1 is 1 for class 0, 0 for class 1 (never predicted), and 2/3 for class 2. Their mean is 0.5556. Pooled over classes, TP = 3, FP = 1 and FN = 1, so micro F1 is 0.75.
0.0.Check runs the visible cases against a fresh kernel. Submit runs the hidden ones too.
⇧↵run cell