Download evaluation/protocols.py from Anonymous1611/Lattice_Spike: direct link, hf CLI and curl.
- Browser
- Download file 4.93 kB
-
https://huggingface.co/Anonymous1611/Lattice_Spike/resolve/main/evaluation/protocols.py
- Command line
-
hf download hf://Anonymous1611/Lattice_Spike/evaluation/protocols.py
-
curl -L -o protocols.py https://huggingface.co/Anonymous1611/Lattice_Spike/resolve/main/evaluation/protocols.py
4.93 kB
| """Versioned evaluation protocols and canonical comparison support. | |
| The reportable token-level comparison is deliberately narrower than the raw | |
| prediction artifact: it uses answer-token indices ``t >= 1`` with binary G/H | |
| labels and valid finite scores. Token zero, uncertain labels, and invalid score | |
| slots remain in artifacts so exclusions are auditable. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import asdict, dataclass | |
| from enum import Enum | |
| import hashlib | |
| import json | |
| from math import isfinite | |
| from typing import Iterable, Sequence | |
| PREDICTION_SCHEMA_VERSION = 2 | |
| class ProtocolSuite(str, Enum): | |
| """Top-level evaluation suites; each method also records a protocol id.""" | |
| CAUSAL_MATCHED = "causal_matched" | |
| METHOD_FAITHFUL = "method_faithful" | |
| OFFLINE = "offline" | |
| LEGACY_FAIR_V0 = "legacy_fair_v0" | |
| class EvaluationProtocol: | |
| """Machine-readable method protocol bound into predictions and reports.""" | |
| suite: str | |
| protocol_id: str | |
| unit: str | |
| information_horizon: str | |
| label_semantics: str | |
| token_zero_policy: str | |
| uncertain_policy: str | |
| prediction_schema_version: int = PREDICTION_SCHEMA_VERSION | |
| def to_json_dict(self) -> dict: | |
| return asdict(self) | |
| class SupportEntry: | |
| """One canonical token in the common comparison population.""" | |
| row_index: int | |
| row_id: str | None | |
| token_index: int | |
| token_id: int | |
| label: int | |
| def to_json_dict(self) -> dict: | |
| return { | |
| "row_index": int(self.row_index), | |
| "row_id": self.row_id, | |
| "token_index": int(self.token_index), | |
| "token_id": int(self.token_id), | |
| "label": int(self.label), | |
| } | |
| def build_comparison_mask( | |
| labels: Sequence[int] | None, | |
| *, | |
| token_indices: Sequence[int] | None = None, | |
| score_valid_mask: Sequence[bool] | None = None, | |
| exclude_token_zero: bool = True, | |
| ) -> list[bool]: | |
| """Build the reportable G/H support mask without dropping artifact slots.""" | |
| if labels is None: | |
| return [] | |
| n = len(labels) | |
| indices = list(range(n)) if token_indices is None else [int(v) for v in token_indices] | |
| valid = [True] * n if score_valid_mask is None else [bool(v) for v in score_valid_mask] | |
| if len(indices) != n or len(valid) != n: | |
| raise ValueError("labels, token_indices, and score_valid_mask must align") | |
| mask = [] | |
| for label, token_index, score_valid in zip(labels, indices, valid): | |
| label = int(label) | |
| if label not in {0, 1, 2}: | |
| raise ValueError("labels must contain only 0/1/2 values") | |
| mask.append( | |
| bool(score_valid) | |
| and label in {0, 1} | |
| and (not exclude_token_zero or token_index >= 1) | |
| ) | |
| return mask | |
| def support_entries( | |
| *, | |
| row_index: int, | |
| row_id: str | None, | |
| token_ids: Sequence[int], | |
| labels: Sequence[int], | |
| token_indices: Sequence[int] | None = None, | |
| comparison_mask: Sequence[bool] | None = None, | |
| ) -> list[SupportEntry]: | |
| """Construct canonical entries for one prediction row.""" | |
| n = len(labels) | |
| indices = list(range(n)) if token_indices is None else [int(v) for v in token_indices] | |
| mask = ( | |
| build_comparison_mask(labels, token_indices=indices) | |
| if comparison_mask is None | |
| else [bool(v) for v in comparison_mask] | |
| ) | |
| if len(token_ids) != n or len(indices) != n or len(mask) != n: | |
| raise ValueError("token ids, labels, indices, and comparison mask must align") | |
| return [ | |
| SupportEntry(row_index, row_id, token_index, int(token_id), int(label)) | |
| for token_id, label, token_index, keep in zip(token_ids, labels, indices, mask) | |
| if keep | |
| ] | |
| def support_hash(entries: Iterable[SupportEntry]) -> str: | |
| """SHA256 of deterministically serialized, canonically sorted support.""" | |
| payload = [entry.to_json_dict() for entry in sorted(entries)] | |
| encoded = json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")) | |
| return hashlib.sha256(encoded.encode("utf-8")).hexdigest() | |
| def support_summary(entries: Iterable[SupportEntry]) -> dict: | |
| values = list(entries) | |
| labels = [entry.label for entry in values] | |
| return { | |
| "n_tokens": len(values), | |
| "n_grounded": labels.count(0), | |
| "n_hallucinated": labels.count(1), | |
| "positive_prevalence": labels.count(1) / len(labels) if labels else None, | |
| "support_sha256": support_hash(values), | |
| } | |
| def validate_finite_scores(scores: Sequence[float], mask: Sequence[bool]) -> None: | |
| if len(scores) != len(mask): | |
| raise ValueError("scores and mask must align") | |
| bad = [index for index, (score, keep) in enumerate(zip(scores, mask)) if keep and not isfinite(float(score))] | |
| if bad: | |
| raise ValueError(f"comparison support contains non-finite scores at indices {bad[:10]}") | |