feat: implement reproducible PROTEAN baseline pipeline
This commit is contained in:
1 parent
2cc6a9859e
commit
0aa8e2ba79
93 files changed
+8173
-658
No files matched your search
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python
|
||||
"""Generate deterministic Dirichlet partition manifests for all conditions.
|
||||
|
||||
This does NOT train anything. It loads each dataset once (labels only), computes
|
||||
the frozen stratified split, generates the Dirichlet partition for every
|
||||
``(alpha, seed)`` and persists a manifest plus characterization/rare-unseen
|
||||
metadata. Training later loads these manifests; it never regenerates a partition.
|
||||
|
||||
Usage::
|
||||
|
||||
python scripts/generate_partitions.py --datasets xiiotid 5gnidd \
|
||||
--alphas 0.75 0.5 0.25 --seeds 0 1 2 --clients 10
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(REPO_ROOT / "src"))
|
||||
|
||||
from protean_research.data import loading, manifest as manifest_mod, partition as partition_mod # noqa: E402
|
||||
from protean_research.data import rare_unseen, splits as splits_mod # noqa: E402
|
||||
from protean_research.data.characterization import characterize_support # noqa: E402
|
||||
from protean_research.data.pipeline import ( # noqa: E402
|
||||
TEST_FRACTION,
|
||||
TRAIN_FRACTION,
|
||||
VALIDATION_FRACTION,
|
||||
)
|
||||
from protean_research.utils.config import expand_path, load_datasets_config, load_yaml # noqa: E402
|
||||
|
||||
|
||||
def write_csv(path: Path, rows: list[dict]) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
if not rows:
|
||||
path.write_text("", encoding="utf-8")
|
||||
return
|
||||
with path.open("w", encoding="utf-8", newline="") as handle:
|
||||
writer = csv.DictWriter(handle, fieldnames=list(rows[0].keys()))
|
||||
writer.writeheader()
|
||||
writer.writerows(rows)
|
||||
|
||||
|
||||
def main(argv=None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--datasets", nargs="+", default=["xiiotid", "5gnidd"])
|
||||
parser.add_argument("--alphas", nargs="+", type=float, default=[0.75, 0.5, 0.25])
|
||||
parser.add_argument("--seeds", nargs="+", type=int, default=[0, 1, 2])
|
||||
parser.add_argument("--clients", type=int, default=10)
|
||||
parser.add_argument("--partitions-dir", default=None)
|
||||
parser.add_argument("--analysis-dir", default=None)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
datasets_cfg = load_datasets_config()
|
||||
protocol = load_yaml(REPO_ROOT / "configs" / "experiments" / "experiment_protocol.yaml")
|
||||
artifacts_cfg = protocol.get("artifacts", {})
|
||||
partitions_dir = expand_path(args.partitions_dir or artifacts_cfg.get("partition_manifest_dir", "artifacts/partitions"))
|
||||
analysis_dir = expand_path(args.analysis_dir or artifacts_cfg.get("analysis_dir", "artifacts/analysis"))
|
||||
|
||||
created = []
|
||||
for dataset_key in args.datasets:
|
||||
spec = loading.load_spec(dataset_key, datasets_cfg)
|
||||
cleaned = loading.clean_frame(loading.read_raw(spec), spec)
|
||||
for alpha in args.alphas:
|
||||
for seed in args.seeds:
|
||||
split = splits_mod.stratified_split(
|
||||
cleaned.labels, dataset_key, seed,
|
||||
train_fraction=TRAIN_FRACTION,
|
||||
validation_fraction=VALIDATION_FRACTION,
|
||||
test_fraction=TEST_FRACTION,
|
||||
)
|
||||
labels_train = cleaned.labels[split.train]
|
||||
class_indices = partition_mod.class_index_map(labels_train, len(cleaned.class_names))
|
||||
rng_seed = partition_mod.partition_rng_seed(dataset_key, alpha, seed)
|
||||
assignments = partition_mod.dirichlet_partition(
|
||||
class_indices, args.clients, alpha, rng_seed
|
||||
)
|
||||
manifest = manifest_mod.build_manifest(
|
||||
dataset_key=dataset_key,
|
||||
dataset_name=spec.name,
|
||||
alpha=alpha,
|
||||
seed=seed,
|
||||
num_clients=args.clients,
|
||||
class_names=cleaned.class_names,
|
||||
benign_class=spec.benign_label,
|
||||
labels=cleaned.labels,
|
||||
split=split,
|
||||
assignments=assignments,
|
||||
rng_seed=rng_seed,
|
||||
train_fraction=TRAIN_FRACTION,
|
||||
validation_fraction=VALIDATION_FRACTION,
|
||||
test_fraction=TEST_FRACTION,
|
||||
)
|
||||
path = manifest_mod.manifest_path(partitions_dir, dataset_key, alpha, seed)
|
||||
manifest_mod.save_manifest(path, manifest)
|
||||
|
||||
# Verify exact reload before declaring success.
|
||||
manifest_mod.reproduce_train_assignments(manifest, cleaned.labels)
|
||||
|
||||
support = manifest["support"]
|
||||
import numpy as np
|
||||
|
||||
support_arr = np.asarray(support, dtype=np.int64)
|
||||
tag = f"{dataset_key}__alpha{alpha:g}__seed{seed}"
|
||||
write_csv(analysis_dir / f"{tag}__characterization.csv",
|
||||
characterize_support(support_arr, cleaned.class_names))
|
||||
write_csv(analysis_dir / f"{tag}__rare_unseen.csv",
|
||||
rare_unseen.rare_unseen_rows(
|
||||
support_arr, cleaned.class_names, cleaned.benign_index,
|
||||
dataset_key, alpha, seed))
|
||||
created.append({"dataset": dataset_key, "alpha": alpha, "seed": seed,
|
||||
"path": str(path), "partition_hash": manifest["partition_hash"]})
|
||||
print(f"created {path.name}")
|
||||
|
||||
print(json.dumps({"created": len(created)}, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in new issue
Block a user