diff --git a/pyproject.toml b/pyproject.toml index 53dd0eb..7165143 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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", ] diff --git a/scripts/posthoc_statistical_audit.py b/scripts/posthoc_statistical_audit.py index 5f115ab..7f5f137 100644 --- a/scripts/posthoc_statistical_audit.py +++ b/scripts/posthoc_statistical_audit.py @@ -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())) diff --git a/src/protean_research/data/partition.py b/src/protean_research/data/partition.py index 797fca0..6f4e3e8 100644 --- a/src/protean_research/data/partition.py +++ b/src/protean_research/data/partition.py @@ -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 diff --git a/src/protean_research/models/protean_cnn.py b/src/protean_research/models/protean_cnn.py index cacd99d..7260c2c 100644 --- a/src/protean_research/models/protean_cnn.py +++ b/src/protean_research/models/protean_cnn.py @@ -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)