Source code for mtlearn.layers.cfp.normalization.statistics_snapshot

"""Small, detached snapshots of CFP normalization statistics."""
from __future__ import annotations

from dataclasses import dataclass, field
from types import MappingProxyType
from typing import Mapping, Any

from .attribute_normalizer import AttributeNormalizer, validate_summary


[docs] @dataclass(frozen=True, eq=False) class StatisticsSnapshot: """An immutable statistical contract with defensive copies of CPU scalars. ``statistics`` returns copies: callers cannot change the installed snapshot by mutating returned tensors. Source sample_count may be unknown for legacy checkpoints; it counts logical images, not tree nodes or unique contents. """ contract: Mapping[str, Any] _statistics: Mapping[str, Any] = field(repr=False) sample_count: int | None = None def __post_init__(self): contract = dict(self.contract) expected = {"scale_mode", "eps", "clipped_zscore_radius", "clipped_zscore_floor", "attribute_dtype", "stat_keys"} if set(contract) != expected: raise ValueError("Snapshot has an invalid statistical contract.") if not isinstance(contract["stat_keys"], tuple) or any(not isinstance(k, str) for k in contract["stat_keys"]): raise ValueError("Snapshot stat_keys must be an immutable tuple of strings.") if contract["attribute_dtype"] not in ("float32", "float64") or len(set(contract["stat_keys"])) != len(contract["stat_keys"]): raise ValueError("Snapshot requires a supported attribute dtype and unique statistic keys.") if self.sample_count is not None and (type(self.sample_count) is not int or self.sample_count < 0): raise ValueError("sample_count must be a nonnegative integer or None.") AttributeNormalizer(contract["scale_mode"], contract["eps"], clipped_zscore_radius=contract["clipped_zscore_radius"], clipped_zscore_floor=contract["clipped_zscore_floor"]) copied = validate_summary(self._statistics, contract["scale_mode"]) required = set() if contract["scale_mode"] == "none" else set(contract["stat_keys"]) if set(copied) != required: raise ValueError("Snapshot statistics do not match the required tree/attribute keys.") object.__setattr__(self, "contract", MappingProxyType(contract)) object.__setattr__(self, "_statistics", MappingProxyType({k: MappingProxyType(v) for k, v in copied.items()})) @property def statistics(self): return {key: {name: value.clone() for name, value in values.items()} for key, values in self._statistics.items()}
[docs] @classmethod def combine(cls, snapshots): """Combine snapshots in iterable order without retaining per-image data.""" iterator = iter(snapshots) first = next(iterator, None) if not isinstance(first, cls): raise ValueError("combine requires at least one StatisticsSnapshot.") normalizer = AttributeNormalizer(first.contract["scale_mode"]) normalizer.merge(first.statistics) count = first.sample_count for snapshot in iterator: if not isinstance(snapshot, cls) or snapshot.contract != first.contract: raise ValueError("Cannot combine incompatible statistical snapshots.") normalizer.merge(snapshot.statistics) count = None if count is None or snapshot.sample_count is None else count + snapshot.sample_count return cls(first.contract, normalizer.ds_stats, sample_count=count)