diffusion_baseline / evaluation /unigram_entropy.py
jyliuAI's picture
Add files using upload-large-folder tool
784f572 verified
Raw History Blame Contribute Delete
2.5 kB
"""Mean per-sample empirical unigram entropy in natural-log units."""
from __future__ import annotations
from collections import Counter
from dataclasses import dataclass
import math
from typing import Iterable, Mapping, Sequence
@dataclass(frozen=True)
class EntropyResult:
mean_entropy: float
sample_count: int
token_count: int
excluded_token_ids: tuple[int, ...]
aggregation: str = "arithmetic_mean_of_per_sample_entropy"
logarithm: str = "natural"
unit: str = "nats"
def unigram_entropy(token_ids: Sequence[int]) -> float:
"""Return the natural-log empirical entropy of one non-empty token row."""
if not token_ids:
raise ValueError("token row has no tokens")
if not all(type(token_id) is int and token_id >= 0 for token_id in token_ids):
raise ValueError("token IDs must be non-negative integers")
total = len(token_ids)
return -sum(
(count / total) * math.log(count / total) for count in Counter(token_ids).values()
)
def _token_ids(record: object, index: int) -> Sequence[int]:
if isinstance(record, Mapping):
value = record.get("token_ids")
else:
value = getattr(record, "token_ids", None)
if not isinstance(value, (list, tuple)):
raise ValueError(f"record {index} has invalid token_ids")
return value
def mean_unigram_entropy(
records: Iterable[object], special_ids: Iterable[int] = ()
) -> EntropyResult:
"""Remove exactly the caller-declared padding IDs, then average row entropy."""
excluded = frozenset(special_ids)
if not all(type(token_id) is int and token_id >= 0 for token_id in excluded):
raise ValueError("special token IDs must be non-negative integers")
entropies: list[float] = []
token_count = 0
for index, record in enumerate(records):
row = [token_id for token_id in _token_ids(record, index) if token_id not in excluded]
if not row:
raise ValueError(f"record {index} has no tokens after exclusions")
entropies.append(unigram_entropy(row))
token_count += len(row)
if not entropies:
raise ValueError("no sample records were provided")
result = math.fsum(entropies) / len(entropies)
if not math.isfinite(result):
raise ValueError("mean entropy is not finite")
return EntropyResult(
mean_entropy=result,
sample_count=len(entropies),
token_count=token_count,
excluded_token_ids=tuple(sorted(excluded)),
)