Metrics
A metric aggregates the per-sample values a scorer emitted into the numbers that end up in the evaluation results. Metrics are nested under the scorer they aggregate:
definition:
scorers:
- type: string_equals
ground_truth: "{{ sample.expected_answer }}"
metrics:
- type: mean
field: is_correct
name: "Accuracy"This page is about choosing an aggregation. Every field of every metric type is documented in the Metrics reference.
Choosing a Metric
Choose by the shape of the score value, not by the name of the number you want.
| Metric | The score field holds… |
|---|---|
| Mean | A boolean or 0/1 correctness flag - the mean is the rate - or a continuous score. |
| Std Dev, Min, Max | A continuous score (judge score, BLEU, latency) whose spread matters, alongside its mean. |
| Frequency | A category or label. Emits the distribution over labels. |
| Weighted Average | A category whose values have different importance. |
| Precision, Recall, F1 Score | Per-sample TP / FP / FN counts. |
| Binary Classification | A ground-truth and a predicted label, two classes. Emits the whole report at once. |
| Multiclass Classification | A ground-truth and a predicted label, many classes. Emits the whole report, per class. |
| Python | Something none of the above expresses. |
Two families need highlighting because they are easy to mix up:
Counting metrics (precision, recall, f1_score) do not take a field. They take count fields - num_true_positives_field, num_false_positives_field, num_false_negatives_field - and sum them across samples before dividing. Use them when each sample contributes several decisions, for example an extraction task where one sample yields many entities.
metrics:
- key: entity_precision
type: precision
num_true_positives_field: num_correct_entities
num_false_positives_field: num_spurious_entitiesClassification report metrics (binary-classification, multiclass-classification) take a pair of label fields instead, and emit a whole report - accuracy, precision, recall, F1 and the confusion matrix - from one entry.
metrics:
- key: sentiment_report
type: binary-classification
field_gt: expected_label
field_pred: predicted_label
positive_answer: "positive"
negative_answer: "negative"If each sample is one classification decision, reach for these before hand-rolling precision and recall.
key vs name
Two different identifiers, and the distinction matters:
keyidentifies the metric within its scorer. It defaults to the metric’stypeand must be unique among that scorer’s metrics.nameis the human-readable label shown in results. It is optional and purely cosmetic.
metrics:
- key: accuracy # identity - must be unique per scorer
type: mean
field: is_correct
name: "Answer Accuracy" # what a reader seesBecause key defaults to type, two mean metrics under one scorer collide and the task fails with Metrics keys for scorer with key '<scorer>' must be unique.
# Fails - both keys default to `mean`.
metrics:
- type: mean
field: precision
- type: mean
field: recall
# Works.
metrics:
- key: precision
type: mean
field: precision
- key: recall
type: mean
field: recallSetting name does not resolve the collision; only key does.
Note that frequency takes no name at all - it emits one value per distinct label, so the labels are the names.
Python Metrics
Use type: python when the aggregation is not a per-field reduction - when it needs to look at several score fields together, or compute something over the whole set at once.
The snippet defines compute_metrics, receives every SampleScore the scorer produced, and returns a flat dictionary of metric names to numbers.
tasks/classification/compute_metrics.py
from __future__ import annotations
from latticeflow.core.dtypes import SampleScore
def compute_metrics(scores: list[SampleScore]) -> dict[str, int | float]:
if len(scores) == 0:
raise ValueError("Cannot compute metrics: received 0 sample scores.")
num_true_positives = 0
num_false_positives = 0
num_positives = 0
for sample_score in scores:
ground_truth = sample_score.values["gt"]
prediction = sample_score.values["pred"]
if ground_truth:
num_positives += 1
if prediction:
num_true_positives += 1
elif prediction:
num_false_positives += 1
predicted_positives = num_true_positives + num_false_positives
return {
"precision": num_true_positives / predicted_positives if predicted_positives > 0 else 1.0,
"recall": num_true_positives / num_positives if num_positives > 0 else 1.0,
}definition:
scorers:
- key: binary_classification
type: python
compute_scores_snippet: !include score_sample.py # emits `gt` and `pred`
metrics:
- type: python
compute_metrics_snippet: !include compute_metrics.pyRead score values through sample_score.values["<field>"]. Every key the metric returns becomes a metric in the results, so one python metric can emit several numbers - which is also a way to sidestep the key-collision problem.
The snippet runs in the standard server-side runtime; see Python Snippets.