nur-dev's picture
Add files using upload-large-folder tool
e69b72a verified
Raw
History Blame Contribute Delete
7.25 kB
"""Zero-shot task adapters + metrics for the fetched eval sets.
Two task shapes, both scored purely by LM likelihood:
* Minimal pairs (BLiMP): accuracy = fraction where the grammatical sentence gets
a higher total log-probability than the ungrammatical one.
* Multiple choice (COPA / XCOPA): accuracy = fraction where the gold choice gets
the highest (length-normalised) continuation log-probability.
Loaders read the immutable raw parquet directly (pyarrow), so they do not depend
on dataset loading scripts.
"""
from __future__ import annotations
import glob
from dataclasses import dataclass, field
from pathlib import Path
import torch
from strata.eval.scoring import encode_with_continuation, score_sequences
# Cause/effect connectors for building COPA/XCOPA prompts, per language.
_CONNECTORS = {
"en": {"cause": "because", "effect": "so"},
"zh": {"cause": "因为", "effect": "所以"},
}
@dataclass(frozen=True, slots=True)
class MinimalPairExample:
good: str
bad: str
tag: str = ""
@dataclass(frozen=True, slots=True)
class MultipleChoiceExample:
context: str
choices: tuple[str, ...]
gold: int
@dataclass(slots=True)
class TaskResult:
task: str
metric: str
value: float
n_examples: int
subscores: dict[str, float] = field(default_factory=dict)
def to_dict(self) -> dict[str, object]:
return {
"task": self.task,
"metric": self.metric,
"value": self.value,
"n_examples": self.n_examples,
"subscores": self.subscores,
}
# ---------------------------------------------------------------------------
# Evaluation drivers
# ---------------------------------------------------------------------------
def evaluate_minimal_pairs(
model,
tokenizer,
examples: list[MinimalPairExample],
*,
task: str,
device: torch.device,
pad_id: int,
batch_size: int = 16,
precision: str = "bf16",
predicate_memory_intervention: str = "none",
predicate_memory_residual_scale: float | None = None,
graph_object_residual_scale: float | None = None,
) -> TaskResult:
"""Total-log-prob comparison of good vs. bad sentences."""
if not examples:
raise ValueError(f"{task}: no examples to evaluate")
seqs: list[list[int]] = []
ctx: list[int] = []
for ex in examples:
for text in (ex.good, ex.bad):
ids = list(tokenizer.encode(text, add_bos=True).input_ids)
seqs.append(ids)
ctx.append(1) # score every real token (position 0 is BOS)
scores = score_sequences(
model, seqs, ctx, device=device, pad_id=pad_id, batch_size=batch_size, precision=precision,
predicate_memory_intervention=predicate_memory_intervention,
predicate_memory_residual_scale=predicate_memory_residual_scale,
graph_object_residual_scale=graph_object_residual_scale,
)
correct = 0
per_tag: dict[str, list[int]] = {}
for i, ex in enumerate(examples):
good_score, bad_score = scores[2 * i], scores[2 * i + 1]
hit = int(good_score > bad_score)
correct += hit
if ex.tag:
per_tag.setdefault(ex.tag, []).append(hit)
subscores = {tag: sum(v) / len(v) for tag, v in sorted(per_tag.items())}
return TaskResult(task, "accuracy", correct / len(examples), len(examples), subscores)
def evaluate_multiple_choice(
model,
tokenizer,
examples: list[MultipleChoiceExample],
*,
task: str,
device: torch.device,
pad_id: int,
batch_size: int = 16,
precision: str = "bf16",
length_normalize: bool = True,
predicate_memory_intervention: str = "none",
predicate_memory_residual_scale: float | None = None,
graph_object_residual_scale: float | None = None,
) -> TaskResult:
"""Pick the choice with the highest (length-normalised) continuation log-prob."""
if not examples:
raise ValueError(f"{task}: no examples to evaluate")
seqs: list[list[int]] = []
ctx: list[int] = []
spans: list[tuple[int, int]] = [] # (start, end) index into seqs per example
for ex in examples:
start = len(seqs)
for choice in ex.choices:
ids, context_len = encode_with_continuation(tokenizer, ex.context, choice)
seqs.append(ids)
ctx.append(context_len)
spans.append((start, len(seqs)))
scores = score_sequences(
model, seqs, ctx, device=device, pad_id=pad_id, batch_size=batch_size,
precision=precision, length_normalize=length_normalize,
predicate_memory_intervention=predicate_memory_intervention,
predicate_memory_residual_scale=predicate_memory_residual_scale,
graph_object_residual_scale=graph_object_residual_scale,
)
correct = 0
for ex, (start, end) in zip(examples, spans):
choice_scores = scores[start:end]
pred = max(range(len(choice_scores)), key=lambda k: choice_scores[k])
correct += int(pred == ex.gold)
return TaskResult(task, "accuracy", correct / len(examples), len(examples))
# ---------------------------------------------------------------------------
# Loaders (read raw parquet directly)
# ---------------------------------------------------------------------------
def _read_parquet_rows(path: str) -> list[dict]:
import pyarrow.parquet as pq
return pq.ParquetFile(path).read().to_pylist()
def load_blimp(raw_dir: Path, *, configs: list[str] | None = None, max_per_config: int | None = None) -> list[MinimalPairExample]:
examples: list[MinimalPairExample] = []
paths = sorted(glob.glob(str(raw_dir / "*" / "*.parquet")))
if not paths:
raise FileNotFoundError(f"no BLiMP parquet under {raw_dir}")
for path in paths:
uid = Path(path).parent.name
if configs is not None and uid not in configs:
continue
rows = _read_parquet_rows(path)
if max_per_config is not None:
rows = rows[:max_per_config]
for row in rows:
examples.append(MinimalPairExample(good=row["sentence_good"], bad=row["sentence_bad"], tag=uid))
return examples
def _copa_context(premise: str, question: str, language: str) -> str:
premise = premise.rstrip().rstrip(".。").strip()
connectors = _CONNECTORS.get(language, _CONNECTORS["en"])
connector = connectors.get(str(question), connectors["effect"])
joiner = "" if language == "zh" else " "
return f"{premise}{joiner}{connector}{joiner}"
def load_copa(parquet_path: str, *, language: str = "en", max_examples: int | None = None) -> list[MultipleChoiceExample]:
rows = _read_parquet_rows(parquet_path)
rows = [r for r in rows if int(r.get("label", -1)) in (0, 1)] # drop unlabeled test rows
if max_examples is not None:
rows = rows[:max_examples]
examples: list[MultipleChoiceExample] = []
for row in rows:
context = _copa_context(row["premise"], row["question"], language)
examples.append(
MultipleChoiceExample(
context=context,
choices=(str(row["choice1"]), str(row["choice2"])),
gold=int(row["label"]),
)
)
return examples