chore: declare matplotlib and apply three behavior-preserving nits

- pyproject.toml: add a figures extra with matplotlib (used by the two figure
  scripts) so the environment is reproducible from pyproject alone.
- partition.py: correct the partition_eval_split docstring (simulated per-client
  eval sets from the global split, not client-owned held-out data).
- protean_cnn.py: extract_embeddings now moves batches to the model device.
- posthoc_statistical_audit.py: refuse to write an empty table.
This commit is contained in:
willtanoe committed 2026-10-17 04:08:38 +07:00
1 parent 783127296a
commit 91b0e371f7
4 files changed
+16 -5

No files matched your search

+4
View File
@@ -20,6 +20,10 @@ research = [
"pandas>=2.0",
"torch>=2.0",
]
figures = [
# Used by scripts/make_final_figures.py and scripts/make_comparison_figures.py.
"matplotlib>=3.8",
]
dev = [
"pytest>=7.0",
]
+2
View File
@@ -55,6 +55,8 @@ def write_csv(name: str, rows: list[dict]) -> None:
# never touch any pre-existing analysis artifact.
if not name.startswith("posthoc__"):
raise SystemExit(f"refusing to write non-posthoc artifact: {name}")
if not rows:
raise SystemExit(f"refusing to write empty table: {name}")
path = ANALYSIS / name
with path.open("w", newline="") as fh:
w = csv.DictWriter(fh, fieldnames=list(rows[0].keys()))
+6 -4
View File
@@ -103,11 +103,13 @@ def partition_eval_split(
seed: int,
split_name: str,
):
"""Partition a validation/test split into per-client local eval sets.
"""Partition a validation/test split into per-client simulated eval sets.
The baseline evaluation is per client (each client scores its local held-out
data). This uses the same frozen Dirichlet algorithm with a split-specific
seed, so it is deterministic and paired across variants.
Clients do not own held-out data in this design; instead the *global*
validation/test split is divided into per-client eval sets with the same
frozen Dirichlet algorithm, using a split-specific seed. The partition is
therefore deterministic and identical across every aggregation variant of a
condition, which keeps per-client comparisons paired.
"""
import numpy as np
+4 -1
View File
@@ -105,11 +105,14 @@ def extract_embeddings(model, features, batch_size: int = 1024):
import numpy as np
import torch
device = next(model.parameters()).device
model.eval()
outputs: list[np.ndarray] = []
with torch.no_grad():
for start in range(0, len(features), batch_size):
batch = torch.as_tensor(features[start : start + batch_size], dtype=torch.float32)
batch = torch.as_tensor(
features[start : start + batch_size], dtype=torch.float32, device=device
)
outputs.append(model.embedding(batch).cpu().numpy())
if not outputs:
return np.empty((0, model.cfg.dense_units), dtype=np.float32)