Metric
A metric is a single evaluation measure computed by the metrics service, such as an error or an accuracy. Metrics are selected by name in the
service’s select list and reported per prediction head.
Usage
Each entry of services.metrics.select selects a metric.
services:
metrics:
select:
- name: rmse
kwargs: {}
- name: top-k-acc
kwargs: { k: 1 }
How it Works
A metric accumulates state incrementally: it starts from an empty accumulator, folds each batch into it, and resolves the accumulator to a per-head value on demand. Accumulators also merge across processes, so a metric is exact over the full evaluation set and correct under distributed execution.
A metric may resolve to more than one number per head. Most report a single value, but per-class recall (for example) reports one per class, so a head’s slot is a 1-D tensor whose width the metric chooses and then keeps fixed for the run.
Subclasses
The two families differ in what they read. A regression metric compares the model output against a continuous target columnwise, while a classification metric reads a head’s columns as class scores and its single target column as a class index. A run should select from the family matching its policy. Pointing a classification metric at continuous targets raises rather than reporting a meaningless number.
Variants
The metric slot has no variants registered directly on the base; every selectable metric is provided by one of the subclasses above.
Registering a new metric
Most new metrics belong in one of the families above, which supply the
accumulator and leave only the part that differs. A metric that fits neither is a
subclass of Metric that declares a name and version and expresses its
accumulation over a freely chosen state type S through the abstract methods
below. Register it with MetricFactory.
optimum(self) -> floatThe best attainable value, as a property (used for reporting direction).
repr(self) -> strA short string label for the metric.
initial(self) -> SCreate an empty accumulator.
update_state(self, state, out, target) -> SFold one batch into the accumulator and return it.
combine(self, a, b) -> SMerge two accumulators (associative, with
initial()as identity).finalize(self, state) -> HeadValuesResolve the accumulator to per-head values, one tuple entry per head.
from typing import Any, ClassVar
from icegraph.common.tensors import SegmentedTensor
from icegraph.engine.services.metrics.metric import Metric, MetricFactory, HeadValues
from .config import MyMetricConfig
class MyMetric(Metric[MyMetricConfig, MyState]):
name: ClassVar[str] = "my-metric"
version: ClassVar[int] = 1
@classmethod
def validate_config(cls, config: dict[str, Any]) -> MyMetricConfig:
return MyMetricConfig(**config)
@property
def optimum(self) -> float:
...
def repr(self) -> str:
...
def initial(self) -> MyState:
...
def update_state(self, state: MyState, out: SegmentedTensor, target: SegmentedTensor) -> MyState:
...
def combine(self, a: MyState, b: MyState) -> MyState:
...
def finalize(self, state: MyState) -> HeadValues:
...
MetricFactory.register(MyMetric)