feat: implement reproducible PROTEAN baseline pipeline

This commit is contained in:
willtanoe committed 2026-09-24 17:39:00 +00:00
1 parent 2cc6a9859e
commit 0aa8e2ba79
93 files changed
+8173 -658

No files matched your search

+210
View File
@@ -0,0 +1,210 @@
# Baseline Implementation
Status: minimum reproducible baseline implemented and smoke-tested (2026-09-24).
Scope: the pipeline needed to run original-PROTEAN experiments. The proposed
beta-weighted aggregation and the `equal_available` control are **NOT implemented**
in this checkpoint; they are deferred to the later beta/controls phase. No beta
sweep was launched.
Companion documents:
- `docs/baseline/PROTEAN_BASELINE_INVESTIGATION.md` — evidence for baseline semantics.
- `docs/research/EXPERIMENT_PROTOCOL.md` — protocol and provenance tags.
- `docs/research/IMPLEMENTATION_PLAN.md` — architecture.
- `docs/handoff/SESSION_HANDOFF.md` — continuation point.
## 0. Provenance legend
Every decision below is classified as one of:
- **CODE** — confirmed from original PROTEAN code. **None exist**: no official
implementation is public (`PROTEAN_BASELINE_INVESTIGATION.md` §2).
- **PAPER** — stated in the paper and/or the author's PhD thesis.
- **INFERRED** — not written verbatim but a defensible reading of the paper/thesis.
- **PROJECT DECISION** — unspecified by the paper; chosen here and reported as a
reproduction deviation.
- **UNRESOLVED** — still unknown.
## 1. Implemented modules
| Module | Responsibility |
|---|---|
| `data/loading.py` | spec resolution, read-only CSV read, label canonicalisation, duplicate drop, feature selection |
| `data/preprocessing.py` | train-fitted median imputation + standardisation, one-hot encoding, serialisable state |
| `data/splits.py` | deterministic stratified 70/10/20 split, leakage guard |
| `data/partition.py` | Dirichlet non-IID partition, `N_ij`, content hash, eval-split partition |
| `data/manifest.py` | manifest save/load, exact reproduction with hash verification |
| `data/characterization.py` | per-class and per-client non-IID statistics |
| `data/rare_unseen.py` | per-client rare/unseen class derivation |
| `data/pipeline.py` | end-to-end deterministic data wiring |
| `models/protean_cnn.py` | baseline CNN, parameter get/set/average, embedding extraction |
| `models/prototypes.py` | local prototypes, global prototype collection, nearest-prototype inference |
| `aggregation/prototype_aggregation.py` | baseline `original_protean` aggregation only |
| `federated/client.py` | local update (CE + prototype alignment + model alignment) |
| `federated/server.py` | model averaging (1/M), prototype aggregation, global inference |
| `federated/round.py` | round orchestration + per-round validation metrics |
| `evaluation/metrics.py` | accuracy, macro/weighted F1, per-class P/R/F1, rare/unseen metrics |
| `utils/seeding.py`, `utils/integrity.py` | deterministic seed derivation, hashing |
| `scripts/run_baseline.py` | one-condition baseline runner / smoke test |
| `scripts/generate_partitions.py` | generate + verify all `(dataset, alpha, seed)` manifests |
## 2. Preprocessing decisions (all **PROJECT DECISION**)
The paper specifies no preprocessing. Frozen recipe:
1. Raw CSVs read read-only; paths resolve from config with env overrides.
2. Column names stripped; the empty 5G-NIDD header becomes `Unnamed: 0`.
3. Exact duplicate rows dropped (`keep="first"`): X-IIoTID 6,222; 5G-NIDD 1.
4. `crypto-ransomware` dropped from X-IIoTID (**PAPER**).
5. Labels canonicalised (whitespace collapse, whitespace around `_` removed), fixing
`Lateral _movement` -> `Lateral_movement`.
6. Feature columns exclude the label column, its aliases and configured non-feature
columns.
7. `categorical_columns` one-hot encoded with a `__MISSING__` category; all other
columns coerced to numeric, missing tokens -> NaN, median-imputed.
8. Imputation medians and standardisation statistics fitted on the **train split only**;
fitted state persisted and reused for validation/test.
9. Deterministic `float32` output matrix.
No statistic from validation or test data enters preprocessing.
## 3. Split decisions
| Decision | Value | Provenance |
|---|---|---|
| Test fraction | 0.20 | PAPER (80/20) |
| Validation fraction | 0.10 | PROJECT DECISION (paper has no validation set) |
| Train fraction | 0.70 | PROJECT DECISION (remainder) |
| Stratification | per class | PROJECT DECISION (ensures rare classes in val/test) |
| Split seed | derived from `(dataset, "split", seed)` | PROJECT DECISION |
## 4. Partition decisions
| Decision | Value | Provenance |
|---|---|---|
| Algorithm | per-class Dirichlet + multinomial allocation | PROJECT DECISION (paper unspecified) |
| Partition source | training split only | PAPER |
| RNG seed | derived from `(dataset, "dirichlet", alpha, seed)` | PROJECT DECISION |
| Missing classes | `N_ij = 0`, no prototype | PAPER (only observed classes shared) |
| Persistence | JSON manifest + content hash, indices regenerated & verified | PROJECT DECISION |
Observed non-IID behavior (seed 0): X-IIoTID `alpha=0.75` -> at most 1 missing class per
client; `alpha=0.25` -> up to 3. Qualitatively consistent with the paper setting.
## 5. Model provenance
| Element | Value | Provenance |
|---|---|---|
| Layer set | 2 Conv1D (64, 128) + ReLU + max-pool + dropout (0.2/0.5) + flatten + dense 128 + log-softmax | PAPER |
| Conv kernel / padding | 3 / 1 | PROJECT DECISION (paper silent) |
| Embedding `phi` | dense 128 output | PAPER (paper: "first eight of ten layers") |
| Prototype source | embedding | PAPER / INFERRED |
## 6. Local training and loss provenance
| Element | Value | Provenance |
|---|---|---|
| Objective form | CE + `lambda * prototype alignment` + `(mu/2) * model alignment` | PAPER Eq. (3) |
| `mu` | 0.1 | PAPER |
| `lambda` | 1.0 | PROJECT DECISION (paper gives no value) |
| Prototype alignment form | per-sample `\|\|phi(x) - Cbar_y\|\|^2` for classes with a global prototype | INFERRED (FedProto-style reading; paper writes class-mean to class-mean) |
| Model alignment target | global model of the round | INFERRED |
| Optimiser / lr / batch size | Adam / 1e-3 / 256 | PROJECT DECISION |
| Rounds / local epochs | 10 / 3 | PAPER |
| Local prototype | end-of-round mean embedding per observed class | PAPER Eq. (1) |
| Round 0 prototypes | none; prototype term skipped; first prototypes from round-0 aggregation | INFERRED / PROJECT DECISION |
## 7. Aggregation provenance — `original_protean` status
**`original_protean` is implemented, but its participant rule is a REPRODUCTION
ASSUMPTION, not verified original behavior.**
- **PAPER**: Eq. (2)/(5.5) writes `Cbar_j = (1/M) * sum_{i} C_ij` and model averaging
`omega = (1/M) * sum_i omega_i`.
- **PAPER**: the thesis states prototypes are shared only for locally observed classes.
- **UNRESOLVED**: the paper never defines how a class missing on a client (`N_ij = 0`) is
handled, nor whether the denominator is `M` or the number of contributors.
- **REPRODUCTION ASSUMPTION (PROJECT DECISION)**: the server receives prototypes only from
`S_j = { i | N_ij > 0 and a valid C_ij exists }` and averages them with **equal weights
`1/|S_j|`**. This follows the thesis sentence above but is not source-confirmed.
Consequences:
- `original_protean` is therefore a *documented interpretation* of the baseline.
It must be validated in the baseline-verification phase before any between-method
comparison.
- The model-aggregation rule (equal `1/M`) is implemented separately and is closer to the
paper text.
Whether a literal `1/M` reading with omitted classes coincides with this assumption is an
open question for the verification phase; the `equal_available` control that could
distinguish them is deliberately deferred.
## 8. Inference provenance
| Element | Value | Provenance |
|---|---|---|
| Rule | nearest global prototype by squared L2 | PAPER (thesis Eqs. 5.11–5.13) |
| Classes considered | those with a global prototype | INFERRED |
## 9. Seeds
- Exact values: **`{0, 1, 2}`**, frozen in `configs/experiments/experiment_protocol.yaml`.
- Rationale: the paper averages over three random seeds but names none, so the values are
**not source-confirmed**. `{0, 1, 2}` is the minimal three-seed set and is an explicit
**PROJECT DECISION**.
- Each seed deterministically controls the split, Dirichlet partition, model
initialisation and batch order, via
`protean_research.utils.seeding.derive_seed` (never Python `hash()`).
- The seed set is identical across all baseline conditions.
## 10. Manifests
There are **18** partition manifests under `artifacts/partitions/`, from
`2 datasets × 3 alphas × 3 seeds = 18` combinations of `(dataset, alpha, seed)`. Each is
content-hashed, reload-verified, and intended to be reused by every later aggregation
variant so that comparisons stay paired. Full sample-index arrays are regenerated
deterministically from the stored recipe/RNG seed and checked against the stored `N_ij`
and hash, so manifests stay small.
## 11. Tests
`python -m pytest -q` -> **39 tests** (see run result in the handoff). Coverage includes
dataset config resolution, deterministic label mapping, deterministic/train-only
preprocessing, deterministic stratified split, deterministic Dirichlet partition,
manifest reload identity and corruption detection, rare/unseen semantics, local prototype
means, baseline aggregation (including that `support_weighted` is not implemented), metrics
on known fixtures, and a small end-to-end federated smoke test.
## 12. Smoke test
```
.venv/bin/python scripts/run_baseline.py --dataset xiiotid --alpha 0.5 --seed 0 \
--max-rows 3000 --sample-from 60000 --clients 4 --rounds 2 --local-epochs 1
```
Runs on both datasets through the full path (load -> clean -> split -> preprocess ->
partition -> local training -> prototypes -> baseline aggregation -> global
redistribution -> evaluation -> artifacts). Smoke runs write only to ignored directories
(`artifacts/processed/smoke/`, `results/smoke/`) and are **not** thesis results. The
smoke test does not invoke the proposed method.
## 13. Structured outputs
Per run under `results/<run_name>/`: `metadata.json`, `history.json`,
`test_global_metrics.json`, `test_confusion_matrix.json`, `test_per_class.csv`,
`per_client.csv`, `train_support_Nij.csv`, `characterization.csv`, `rare_unseen.csv`.
Preprocessor state -> `artifacts/processed/`; round-0 prototype snapshot ->
`artifacts/processed/smoke/analysis/` (smoke) or `artifacts/analysis/` (full run);
manifests -> `artifacts/partitions/`.
## 14. Remaining assumptions and unresolved issues
1. **Participant rule** (`S_j`, equal weights) is a reproduction assumption (see §7).
2. `lambda = 1.0`, optimiser and schedule are unverified against the paper.
3. Prototype-alignment form and round-0 handling are inferred.
4. Per-client evaluation partitioning (val/test assigned to clients via Dirichlet) is a
project decision; the paper does not describe it.
5. No full-scale run has been timed; compute feasibility of a real sweep is unmeasured.
6. Whether the literal `1/M` reading differs from the `S_j` assumption is open and must
be tested with a deliberate control in the verification phase.
+197 -262
View File
@@ -1,11 +1,11 @@
# Session Handoff
This is the canonical continuation point for the next agent session. It is written to be
readable without any prior chat history, terminal scrollback, or git history.
readable without prior chat history, terminal scrollback, or git history.
Session date: 2026-09-24
Session type: research study, baseline investigation, architecture planning, dataset
inspection, minimal bootstrap. **No training was run and no experimental sweep was launched.**
Last updated: 2026-09-24 (Session 2, baseline-only checkpoint).
Session 2 type: minimum reproducible baseline implementation + smoke test, then cleanup so
the checkpoint contains **baseline only**. **No full-scale training and no beta sweep.**
---
@@ -13,7 +13,7 @@ inspection, minimal bootstrap. **No training was run and no experimental sweep w
Build the code and experiment infrastructure for a thesis study that modifies **only the
server-side prototype aggregation** of PROTEAN (Chennoufi et al., 2025), a federated
intrusion-detection framework. The proposed mechanism weights each contributing client's
intrusion-detection framework. The planned mechanism weights each contributing client's
class prototype by its class sample support:
```
@@ -22,325 +22,260 @@ S_j = { i | N_ij > 0 and a valid local prototype C_ij exists }
beta in {0.0, 0.25, 0.5, 0.75, 1.0}
```
Goal: characterise when support weighting helps or hurts rare attack detection under
non-IID data, selecting beta on a validation set. `beta = 0` is a control, **not** assumed
identical to the original baseline.
**This checkpoint contains the baseline only.** The proposed beta-weighted aggregation and
the `equal_available` control are intentionally NOT implemented yet; they are deferred to
the later beta/controls phase, which must not start until the baseline is verified.
## 2. Authoritative source
- `planningawal/PROTEAN_Revisi_Lurus.md` — the authoritative research specification.
- The entire `planningawal/` directory is **immutable / read-only**. Do not edit, create,
rename, move, delete, reformat, overwrite, or generate reports inside it. It must not
appear in any "files modified" list.
- Verified unchanged this session: SHA-256
`9ba1d7beb94354db0c4b588199fb1e2823aec8a23be1625ec09a756507fbcb89` (12,050 bytes).
- `planningawal/PROTEAN_Revisi_Lurus.md` — authoritative research specification.
- `planningawal/` is **immutable / read-only**. Verified unchanged:
SHA-256 `9ba1d7beb94354db0c4b588199fb1e2823aec8a23be1625ec09a756507fbcb89`
(only file in `planningawal/`).
## 3. Dataset locations (read-only source data)
- `~/datasets/X-IIoTID/X-IIoTID-dataset.csv`
- `~/datasets/5G-NIDD/5G-NIDD.csv`
- `~/datasets/X-IIoTID/X-IIoTID-dataset.csv` — SHA-256
`7b9290057ee42e784da3c0d84b781815502c9205c74175c96374e71a5ffd98a0` (mtime 2021-10-07)
- `~/datasets/5G-NIDD/5G-NIDD.csv` — SHA-256
`fa36f80859585f474504ca69eb951a079b35145537976c86b00aae3aab46ee59` (mtime 2023-02-18)
Do not move, rename, overwrite, modify, or copy the full raw files into the repository. Use
configurable external paths (`configs/datasets/datasets.yaml`, with env overrides).
Re-verified unchanged. Paths resolve from `configs/datasets/datasets.yaml` with env
overrides (`PROTEAN_XIIOTID_RAW_PATH`, `PROTEAN_5GNIDD_RAW_PATH`). Raw files are never
modified or copied into the repo.
Recorded SHA-256:
## 4. Current repository state
- X-IIoTID: `7b9290057ee42e784da3c0d84b781815502c9205c74175c96374e71a5ffd98a0`
- 5G-NIDD: `fa36f80859585f474504ca69eb951a079b35145537976c86b00aae3aab46ee59`
## 4. Current repository tree
Baseline code:
```
perdalam-protean/
├── README.md
├── pyproject.toml
├── .gitignore
├── planningawal/ # IMMUTABLE (read-only)
│ └── PROTEAN_Revisi_Lurus.md
├── configs/
│ ├── datasets/datasets.yaml
│ ├── experiments/experiment_protocol.yaml
│ └── models/protean_cnn.yaml
├── docs/
│ ├── research/
│ │ ├── RESEARCH_SPEC_INTERPRETATION.md
│ │ ├── DATASET_INSPECTION.md
│ │ ├── IMPLEMENTATION_PLAN.md
│ │ └── EXPERIMENT_PROTOCOL.md
│ ├── baseline/
│ │ └── PROTEAN_BASELINE_INVESTIGATION.md
│ └── handoff/
│ └── SESSION_HANDOFF.md
├── src/protean_research/
│ ├── __init__.py
│ ├── aggregation/
│ │ ├── __init__.py
│ │ └── prototype_aggregation.py
│ └── utils/
│ ├── __init__.py
│ └── config.py
├── tests/
│ └── test_prototype_aggregation.py
├── scripts/ # .gitkeep only (tracked)
├── artifacts/
│ ├── partitions/ # tracked: deterministic manifests (.gitkeep now)
│ ├── analysis/ # tracked: small analysis outputs (.gitkeep now)
│ ├── processed/ # ignored (regenerable)
│ └── checkpoints/ # ignored (regenerable)
└── results/ # ignored (regenerable)
src/protean_research/
├── data/ loading, preprocessing, splits, partition, manifest,
│ characterization, rare_unseen, pipeline
├── models/ protean_cnn, prototypes
├── aggregation/ prototype_aggregation (baseline original_protean ONLY)
├── federated/ client, server, round
├── evaluation/ metrics
└── utils/ config, seeding, integrity
scripts/ run_baseline.py, generate_partitions.py
tests/ 34 baseline-focused tests
artifacts/partitions/ 18 full-data manifests (tracked, small, content-hashed)
artifacts/analysis/ 18 characterization + 18 rare_unseen CSVs (tracked, small)
artifacts/processed/ ignored: preprocessor state + smoke partitions/snapshots
results/ ignored: smoke run outputs
```
`.gitignore` deliberately keeps `artifacts/partitions/` and `artifacts/analysis/`
version-controlled (partition manifests are deterministic, content-hashed, and required for
paired comparisons), while ignoring `artifacts/processed/`, `artifacts/checkpoints/`,
`results/`, `logs/`, and `*.log`. `scripts/` is preserved with a `.gitkeep` placeholder.
`planningawal/` is never ignored.
Environment: `.venv/` exists (gitignored) with numpy 2.5.3, pandas 3.0.6,
torch 2.14.0+cpu, pyyaml 6.0.3, pytest 9.1.1. Use `.venv/bin/python`.
Note: the repository was **not** empty at session start. A partial scaffold already existed
(`configs/*`, `src/protean_research/aggregation/*`, `src/protean_research/utils/config.py`,
`__init__.py` files, and empty `docs/`, `scripts/`, `tests/`, `artifacts/`, `results/`
directories). `docs/` was empty and there was no handoff. That scaffold was kept and verified;
it is not the product of this session.
## 5. What was completed this session
## 5. Work completed this session
1. Froze every previously-PROVISIONAL protocol value in
`configs/experiments/experiment_protocol.yaml` and `docs/research/EXPERIMENT_PROTOCOL.md`.
2. Dataset config: explicit `categorical_columns`, `drop_duplicate_rows`, missing tokens,
fixed 5G-NIDD `Unnamed: 0` (quoted).
3. Data foundation (`data/`): read-only loading, deterministic canonicalisation, leak-free
train-fitted preprocessing, deterministic stratified 70/10/20 split, Dirichlet partition
with content-hashed manifests and `N_ij`, characterization, rare/unseen metadata.
4. Model (`models/`): baseline CNN, parameter averaging, embedding extraction, local
prototypes, global prototype collection, nearest-prototype inference.
5. Baseline aggregation (`aggregation/`): `original_protean` only. The proposed
`support_weighted` and the `equal_available` control were **removed** so the checkpoint
is baseline only; an unavailable-mode guard raises `ValueError`.
6. Federated loop (`federated/`): local update (CE + prototype alignment + model
alignment), server model/prototype aggregation, round orchestration, round-0 snapshot.
7. Evaluation (`evaluation/`): accuracy, macro/weighted F1, per-class P/R/F1, rare/unseen.
8. Scripts: `generate_partitions.py` (18 manifests, full data, reload-verified);
`run_baseline.py` (one baseline condition; no beta/mode options).
9. Tests: 34 baseline-only tests, all passing.
10. Smoke test: baseline-only on both datasets.
11. Docs: `docs/baseline/BASELINE_IMPLEMENTATION.md` rewritten with provenance tags.
1. Read the full specification and produced a tagged interpretation
(`docs/research/RESEARCH_SPEC_INTERPRETATION.md`).
2. Investigated authoritative PROTEAN sources (paper, ESORICS/Springer, HAL, author PhD
thesis) and searched for official code.
3. Established baseline aggregation semantics question-by-question with evidence tags
(`docs/baseline/PROTEAN_BASELINE_INVESTIGATION.md`).
4. Inspected both raw datasets in full (read-only) and recorded shapes, label distributions,
missingness, duplicates, identifiers, and mismatches
(`docs/research/DATASET_INSPECTION.md`).
5. Verified the pre-existing config/path-resolution scaffold and aggregation module by running
them.
6. Wrote the implementation architecture (`docs/research/IMPLEMENTATION_PLAN.md`) and the
experiment protocol (`docs/research/EXPERIMENT_PROTOCOL.md`).
7. Added a minimal bootstrap: `pyproject.toml`, `README.md`, and dependency-free unit tests
for the aggregation math (`tests/test_prototype_aggregation.py`, 9 tests, all passing).
8. Recorded dataset and source hashes for integrity verification.
9. Wrote this handoff.
## 6. Baseline decisions and provenance
## 6. Evidence and findings
Full detail in `docs/baseline/BASELINE_IMPLEMENTATION.md`. Summary:
### 6.1 Official PROTEAN source status
- **No official PROTEAN code exists**, so no decision is source-confirmed by code.
- **PAPER**: architecture, `mu = 0.1`, 10 rounds / 3 local epochs, 80/20 test, equal `1/M`
model averaging, end-of-round mean-embedding prototypes, nearest-prototype inference.
- **REPRODUCTION ASSUMPTION (project decision)**: `original_protean` participant rule is
`S_j` with equal `1/|S_j|` weights. The paper writes `1/M` but is ambiguous for classes
missing on a client; this assumption follows the thesis sentence that prototypes are
shared only for locally observed classes. It is **not** treated as verified.
- **PROJECT DECISION**: `lambda = 1.0`; Adam `lr = 1e-3`; batch size 256; kernel size 3;
stratified 70/10/20 split with an added validation set; seeds `{0,1,2}`;
preprocessing recipe; per-class Dirichlet allocation; per-client eval partitioning.
- **INFERRED**: per-sample prototype alignment (FedProto-style reading of Eq. 3); round-0
handling (no prototypes, prototype term skipped).
- **UNRESOLVED**: whether a literal `1/M` missing-class reading differs from `S_j`.
- **No official code exists publicly (as of 2026-09-24).**
- Author GitHub `sarachn` (Sara Chennoufi) has only `FATE-LLM` (fork) and `SecLLMHolmes` —
neither is PROTEAN.
- GitHub repository search `PROTEAN federated prototype` → 0 results; `grep.app` → none.
- The unrelated GitHub user `Chennoufi` has only a `septembre` repo — do not chase it.
- No code/artifact/supplementary link in the arXiv HTML, HAL record, or thesis text.
- **Consequence: the baseline must be reproduced from the paper/thesis.**
## 7. Seeds (explicit)
Primary sources:
- Exact values: **`{0, 1, 2}`**.
- Rationale: the paper averages over three random seeds but names none, so the values are
**not source-confirmed**. `{0, 1, 2}` is the minimal three-seed set and is an explicit
**project decision**.
- Each seed deterministically controls the split, Dirichlet partition, model
initialisation and batch order, via `protean_research.utils.seeding.derive_seed`.
- The set is identical across all baseline conditions.
- arXiv:2507.05524v1 (7 Jul 2025), ESORICS 2025 pp. 103–125,
DOI `10.1007/978-3-032-07884-1_6`.
- HAL `hal-05379203`.
- Author PhD thesis `tel-05490927` (195 pages, 2025) — restates PROTEAN with extra detail.
## 8. Manifests (18)
### 6.2 Baseline semantics discovered
`artifacts/partitions/` holds **18** manifests from `2 datasets × 3 alphas × 3 seeds`.
They are content-hashed, reload-verified, and reused by later variants for pairing. Sample
indices are regenerated deterministically from the stored recipe/RNG seed and checked
against the stored `N_ij` and hash, keeping manifests small.
Confirmed from the paper/thesis:
## 9. Verification performed this session
- Local prototype (Eq. 1 / 5.4): mean embedding over the client's class-`j` samples.
- Server aggregation (Eq. 2 / 5.5): `omega = (1/M) sum_i omega_i`,
`Cbar_j = (1/M) sum_i C_ij` (equal, no `N_ij`).
- Local loss (Eq. 3 / 5.6): cross-entropy + `lambda * ||C_ij - Cbar_j||^2` +
`(mu/2) * ||omega_i - omega^{t-1}||^2`, with `mu = 0.1`.
- Inference: nearest global prototype by squared L2 (thesis Eqs. 5.11–5.13).
- Model: 2 Conv1D (64, 128 filters) + ReLU + maxpool + dropout (0.2, 0.5), flatten,
dense 128, log-softmax head.
- Setup: 10 clients, 80/20 train/test, 10 rounds, 3 local epochs,
`alpha in {0.75, 0.5, 0.25}`.
- Thesis (Sec. 5.3.6): "prototypes are shared only for locally observed classes".
- `python -m pytest -q` -> **34 passed**.
- Full partition generation on both datasets: 18 manifests, each reload-verified.
- Non-IID check (seed 0): X-IIoTID `alpha=0.75` max 1 missing class/client, `alpha=0.25`
up to 3; 5G-NIDD similar. Qualitatively consistent with the paper setting.
- Baseline-only smoke on X-IIoTID (3000 rows, 4 clients, 2 rounds, 1 epoch): full path
succeeds, ~4.5 s.
- Baseline-only smoke on 5G-NIDD (4000 rows): succeeds.
- No beta/support_weighted code is invoked or present.
- Integrity: `planningawal/` and both raw datasets retain their recorded SHA-256 values.
### 6.3 Missing-class findings (critical)
## 10. Files added this session
- Equation (2)/5.5 sums over all `M` participants, but the paper never states how a missing
class prototype is handled. The thesis implies absent classes are not shared, but the
server denominator (`M` vs number of contributors) is undefined.
- Therefore `beta = 0` over `S_j` is **not** automatically equal to the original baseline;
equality requires the same participant rule. This is the central baseline ambiguity.
- `original_protean` is deliberately **not implemented** in
`src/protean_research/aggregation/prototype_aggregation.py`; it raises
`NotImplementedError` with an explanatory message.
- Additional unknowns: round-0 global-prototype initialisation; how the alignment loss sums
over `j = 1..K` when a client has `N_ij = 0`; per-batch vs end-of-round prototypes;
whether model aggregation is equal `1/M` or FedAvg sample-weighted (the thesis intro says
FedAvg is a weighted average, contradicting Eq. 5.5); numeric value of `lambda`; seed values;
feature preprocessing.
Source: `src/protean_research/data/{__init__,loading,preprocessing,splits,partition,manifest,
characterization,rare_unseen,pipeline}.py`,
`src/protean_research/models/{__init__,protean_cnn,prototypes}.py`,
`src/protean_research/federated/{__init__,client,server,round}.py`,
`src/protean_research/evaluation/{__init__,metrics}.py`,
`src/protean_research/utils/{seeding,integrity}.py`.
### 6.4 Dataset findings
Scripts: `scripts/run_baseline.py`, `scripts/generate_partitions.py`.
- X-IIoTID: 820,834 × 68; labels `class1` (19), `class2` (10 general), `class3` (binary).
`class2` benign = `Normal`. After dropping `crypto-ransomware`: 9 classes. `'-'`
placeholders in 42 columns; 6,222 duplicate rows; label typo `Lateral _movement`.
Identifiers: `Date`, `Timestamp`, `Scr_IP`, `Scr_port`, `Des_IP`, `Des_port`.
- 5G-NIDD: 1,215,890 × 52; `Attack Type` (9 values, benign = `Benign`), `Label` binary,
`Attack Tool` (6). 8 attack classes + Benign = 9. Heavy structural missingness (e.g. `dVid`
99.83%, `dTos` 77.56%); 1 duplicate row. Identifiers: empty-header index, `Seq`, `Offset`.
- Both map to PROTEAN's 9-class setup, but PROTEAN's exact preprocessing is unspecified and
must be frozen here.
Tests: `tests/test_data_pipeline.py`, `tests/test_partition.py`, `tests/test_prototypes.py`,
`tests/test_baseline_aggregation.py`, `tests/test_metrics.py`, `tests/test_federated_smoke.py`.
## 7. Decisions made
Docs: `docs/baseline/BASELINE_IMPLEMENTATION.md`.
1. **Treat `planningawal/` as immutable** and keep all outputs elsewhere.
2. **Do not implement `original_protean` until its semantics are verified.** Encode only the
unambiguous modes (`support_weighted`, `equal_available`). Rationale: avoid silently
assuming baseline behaviour.
3. **Keep `equal_available` as a dormant control**, introduced only if baseline investigation
shows the participant rule differs from `S_j` (per the specification).
4. **Centralise dataset paths** in `configs/datasets/datasets.yaml` with env overrides; never
hardcode in source.
5. **Separate raw and generated data**; generated outputs under `artifacts/`.
6. **Use `class2` for X-IIoTID and `Attack Type` for 5G-NIDD** as the multi-class labels, drop
`crypto-ransomware`, treat `Normal`/`Benign` as benign. Matches the paper.
7. **Keep the pre-existing scaffold** (configs + aggregation + utils) and extend it minimally
rather than restructuring; no evidence justified a different layout.
8. **Add dependency-free unit tests** for aggregation invariants now; defer data/federated
tests until those modules exist.
9. **Do not initialize git during the planning session** (not requested at the time). A
follow-up checkpoint task then initialized Git and added remote `origin`; see Section 11.
10. **Mark seeds, `lambda`, validation fraction, Dirichlet allocation, and preprocessing as
PROVISIONAL**; they must be frozen before any sweep.
Artifacts (tracked, small): `artifacts/partitions/*.json` (18),
`artifacts/analysis/*.csv` (36).
## 8. Unresolved questions
## 11. Files modified this session
1. Baseline missing-class rule and prototype-aggregation denominator (`M` vs `|S_j|`).
2. Round-0 initialisation of global prototypes for all `K` classes.
3. `lambda` value (paper gives `mu = 0.1` only).
4. Whether model-parameter aggregation is equal or FedAvg sample-weighted.
5. How the prototype alignment loss treats `N_ij = 0` classes.
6. Per-batch vs end-of-round local prototype computation.
7. Validation split fraction and procedure (the paper has no validation set).
8. Exact Dirichlet allocation algorithm (affects realised `N_ij`).
9. Exact seed values and whether metrics are averaged per client then per seed.
10. Feature preprocessing: encoding, scaling, `'-'`/NaN handling, duplicate-row policy.
11. ~~Whether to initialize git to protect the immutable source.~~ Resolved: Git was
initialized in the follow-up checkpoint task (see Section 11).
12. Compute budget: no timing measured; a profiling smoke test is required before any sweep.
## 9. Files created this session
- `docs/research/RESEARCH_SPEC_INTERPRETATION.md`
- `docs/research/DATASET_INSPECTION.md`
- `docs/research/IMPLEMENTATION_PLAN.md`
- `configs/datasets/datasets.yaml`
- `configs/experiments/experiment_protocol.yaml`
- `docs/research/EXPERIMENT_PROTOCOL.md`
- `docs/baseline/PROTEAN_BASELINE_INVESTIGATION.md`
- `docs/handoff/SESSION_HANDOFF.md`
- `docs/research/IMPLEMENTATION_PLAN.md`
- `README.md`
- `pyproject.toml`
- `.gitignore`
- `tests/test_prototype_aggregation.py`
- `src/protean_research/aggregation/__init__.py`
- `src/protean_research/aggregation/prototype_aggregation.py`
- `src/protean_research/models/prototypes.py`
- `src/protean_research/federated/server.py`
- `src/protean_research/federated/round.py`
- `scripts/run_baseline.py`
- tests (baseline-only aggregation suite; beta tests removed)
## 10. Files modified this session
`planningawal/` was not touched.
- None. No existing file was edited. `planningawal/` was not touched (and must not be).
## 12. Removed from this checkpoint (deferred to beta/controls phase)
Pre-existing files (present at session start, verified but not modified):
`configs/datasets/datasets.yaml`, `configs/experiments/experiment_protocol.yaml`,
`configs/models/protean_cnn.yaml`, `src/protean_research/__init__.py`,
`src/protean_research/aggregation/__init__.py`,
`src/protean_research/aggregation/prototype_aggregation.py`,
`src/protean_research/utils/__init__.py`, `src/protean_research/utils/config.py`.
- `support_weighted` aggregation code and `beta` parameters.
- `equal_available` mode (redundant with the baseline `S_j` rule; only needed if a later
investigation shows the baseline participant rule differs).
- `--mode` / `--beta` CLI options in `run_baseline.py`.
- Beta-specific tests (`beta = 0/1`, single-contributor/equal-support beta invariance,
normalised beta weights, the `original_protean == support_weighted(beta=0)` equivalence
test). The former `tests/test_prototype_aggregation.py` was removed and folded into a
baseline-only `tests/test_baseline_aggregation.py`.
- Any claim that `original_protean == support_weighted(beta=0)` demonstrates equivalence
with original PROTEAN. That was at most an internal implementation-equivalence check and
is no longer present in code or docs.
## 11. Git status
## 13. Git status
The planning session ran with **no Git repository**. A follow-up checkpoint task then
initialized Git:
Not committed (no commit was requested). `git status --short` shows modified tracked files
(configs, docs, aggregation package, federated modules, README, run script) plus untracked
source/tests/scripts/docs, the 18 partition manifests and 36 analysis CSVs. `AGENTS.md` was
already untracked before this session. `.venv/`, `artifacts/processed/`, `results/`,
`logs/`, and `*.log` are ignored. No checkpoints or raw datasets are tracked.
- Branch: `main`
- Remote `origin`: `git@github.com:willtanoe/prototype-agregation.git`
- Initial checkpoint commit: `chore: checkpoint PROTEAN research planning and architecture`
(exact hash reported in the checkpoint task's terminal summary; deliberately not embedded
here to avoid a recursive "hash inside the commit" loop).
- `.gitkeep` placeholders were added for `scripts/`, `artifacts/partitions/`, and
`artifacts/analysis/`. `artifacts/processed/`, `artifacts/checkpoints/`, `results/`,
`logs/`, and `*.log` are ignored.
- `planningawal/PROTEAN_Revisi_Lurus.md` is tracked exactly as-is and was not modified.
- Raw datasets are outside the repository and are not tracked.
## 14. Unresolved issues / blockers
Because this is the initial commit, `planningawal/` was verified with SHA-256 hashing and
directory listing rather than `git diff`.
1. **Baseline is implemented but not yet VALIDATED.** The `original_protean` participant
rule is a reproduction assumption, not verified behavior.
2. `lambda`, optimiser/schedule, and round-0 handling are unverified.
3. No full-scale run has been timed; compute feasibility of a real sweep is unmeasured
(X-IIoTID ~570k train rows, 5G-NIDD ~851k; CPU-only).
4. Per-client evaluation partitioning is a project decision.
5. Prototype-alignment form (per-sample vs class-mean) is a documented inference.
6. Whether the literal `1/M` missing-class reading differs from the `S_j` assumption is
open; a deliberate control is needed in the verification phase.
## 12. Verification performed
## 15. Exact next task
- Read the full specification (118 lines).
- Read the full paper HTML and extracted the author PhD thesis text (195 pages).
- GitHub API searches (author account, repositories), DuckDuckGo, `grep.app`.
- Full pandas load of both datasets; recorded shape, dtypes, label counts, missingness,
duplicates, identifier columns.
- SHA-256 of both datasets and of `planningawal/PROTEAN_Revisi_Lurus.md`.
- Ran the pre-existing aggregation module and config resolver (modes, weights, path
resolution, `original_protean` raises `NotImplementedError`).
- Ran `python -m pytest -q` in a temporary venv: **9 passed**.
- Confirmed no training or sweep was launched.
- Final check: `planningawal/` contains only `PROTEAN_Revisi_Lurus.md` with the same hash;
raw datasets retain the same hashes and mtimes.
**Baseline validation and reproduction verification — NOT beta weighting.**
Checkpoint task (follow-up):
Next session should:
- `git rev-parse --is-inside-work-tree` before init: not a repository.
- Searched the whole tree for `*.csv`, `*.parquet`, `*.feather`, `*.pkl`, `*.pickle`, model
checkpoints: none present; largest file is the 12 KB spec.
- Scanned tracked text for secret patterns (`API_KEY`, `SECRET`, `TOKEN`, `PASSWORD`,
`PRIVATE KEY`, `BEGIN OPENSSH`, `BEGIN RSA`, `sk-`, `ghp_`, `github_pat_`): none found.
- `find planningawal -maxdepth 2 -type f` → only `PROTEAN_Revisi_Lurus.md`.
- Re-ran `pytest`: 9 passed.
1. Run one full-data baseline condition (e.g. X-IIoTID, `alpha=0.5`, `seed=0`,
`original_protean`) with measured wall-clock and memory; record it.
2. Inspect the run: per-class precision/recall/F1, per-client rare metrics, confusion
matrix. Check plausibility and missing-class-client behavior.
3. Stress-test the `S_j` reproduction assumption: compare against a literal `1/M`
omitted-class reading to quantify the effect of the assumption (introduce the control
only for this investigation, clearly marked).
4. Add a deterministic end-to-end baseline regression test pinning a small result.
5. Only after the baseline is demonstrably verified and recorded, begin the paired beta
comparison on a single `(dataset, alpha, seed)` before any grid.
## 13. Exact next task
Do **not** launch the full grid, and do not change the architecture, loss coefficients, or
inference rule.
**Implement the deterministic data foundation, and freeze the remaining protocol choices
before coding.**
## 16. Important commands
Concretely, the next session should:
```bash
# Tests
.venv/bin/python -m pytest -q
1. In `docs/research/EXPERIMENT_PROTOCOL.md`, replace the PROVISIONAL entries with explicit
frozen decisions and rationale for: preprocessing recipe, duplicate-row policy,
`'-'`/NaN handling, the Dirichlet allocation algorithm, the train/validation/test split
procedure and fraction, the seed set, `lambda`, batch size, and learning rate. These are
project decisions because the paper does not specify them; label them as reproduction
deviations.
2. Create `src/protean_research/data/` with `loading.py`, `preprocessing.py`, `splits.py`,
`partition.py`, `manifest.py`, `characterization.py`, `rare_unseen.py` implementing the
flow in `docs/research/IMPLEMENTATION_PLAN.md` Section 3.
3. Persist one partition manifest per `(dataset, alpha, seed)` under `artifacts/partitions/`
with a content hash, and derive rare/unseen metadata from the training split.
4. Add tests for determinism of partition generation (strategy item 8) and identical manifest
reload (item 9), plus the non-IID characterization fields.
# Generate / verify all partition manifests (full data, ~30 s)
.venv/bin/python scripts/generate_partitions.py
Do **not** start model training or a beta sweep. The baseline missing-class rule is still
unresolved, so `original_protean` remains unimplemented.
# Baseline smoke on both datasets (writes only to ignored dirs)
.venv/bin/python scripts/run_baseline.py --dataset xiiotid --alpha 0.5 --seed 0 \
--max-rows 3000 --sample-from 60000 --clients 4 --rounds 2 --local-epochs 1
.venv/bin/python scripts/run_baseline.py --dataset 5gnidd --alpha 0.25 --seed 1 \
--max-rows 4000 --sample-from 60000 --clients 4 --rounds 2 --local-epochs 1
## 14. Recommended next-session prompt
# Full-data baseline for one condition (next session; expect long CPU runtime)
.venv/bin/python scripts/run_baseline.py --dataset xiiotid --alpha 0.5 --seed 0 \
--clients 10 --rounds 10 --local-epochs 3
```
## 17. Recommended next-session prompt
```
You are continuing the perdalam-protean research repository.
You are continuing the perdalam-protean research repository (Session 3).
FIRST, read these two files completely:
FIRST, read completely:
1. docs/handoff/SESSION_HANDOFF.md
2. planningawal/PROTEAN_Revisi_Lurus.md
2. docs/baseline/BASELINE_IMPLEMENTATION.md
3. docs/research/EXPERIMENT_PROTOCOL.md
Then read, as needed:
- docs/research/IMPLEMENTATION_PLAN.md
- docs/research/EXPERIMENT_PROTOCOL.md
- docs/research/DATASET_INSPECTION.md
Then read as needed:
- planningawal/PROTEAN_Revisi_Lurus.md
- docs/baseline/PROTEAN_BASELINE_INVESTIGATION.md
- docs/research/IMPLEMENTATION_PLAN.md
Critical rules:
- planningawal/ is immutable. Do not edit/create/move/rename/delete anything inside it.
- ~/datasets/X-IIoTID/X-IIoTID-dataset.csv and ~/datasets/5G-NIDD/5G-NIDD.csv are
read-only external source data. Do not modify or copy them into the repo.
- Do NOT run full training or an experimental sweep.
- This checkpoint is BASELINE ONLY. Do NOT add the proposed support_weighted/beta code
or the equal_available control until the baseline is verified.
- Do NOT launch the full alpha x seed x dataset x beta sweep.
Your task is Section 13 of the handoff: first freeze the PROVISIONAL protocol decisions in
docs/research/EXPERIMENT_PROTOCOL.md, then implement the deterministic data foundation in
src/protean_research/data/ with partition-manifest persistence, rare/unseen metadata, and
tests for partition determinism and manifest reload.
Your task is Section 15 of the handoff: BASELINE VALIDATION AND REPRODUCTION
VERIFICATION. Run one full-data baseline condition with measured runtime, inspect its
per-class/per-client behavior, stress-test the S_j participant-rule assumption, and add a
deterministic end-to-end baseline regression test. Record everything in
docs/baseline/BASELINE_IMPLEMENTATION.md and update docs/handoff/SESSION_HANDOFF.md.
Start by reading the handoff, then continue from the documented state. Update
docs/handoff/SESSION_HANDOFF.md at the end of your session.
Use .venv/bin/python for all Python commands.
```
+141 -76
View File
@@ -1,10 +1,15 @@
# Experiment Protocol
Status: frozen design (to be executed in later sessions)
Machine-readable counterpart: `configs/experiments/experiment_protocol.yaml`
Status: **FROZEN** reproduction protocol (2026-09-24).
Machine-readable counterpart: `configs/experiments/experiment_protocol.yaml`.
Values marked **PROVISIONAL** require confirmation before any scientific sweep. Values
marked **FROZEN** must remain identical across every aggregation variant.
Values marked **FROZEN** must remain identical across every aggregation variant, beta
candidate and seed. Values marked **PROJECT DECISION** are choices this project had to make
because the paper/thesis leave them unspecified; they are reproduction deviations and must
be reported as such. No value is PROVISIONAL any more.
See `docs/baseline/PROTEAN_BASELINE_INVESTIGATION.md` for the evidence behind each choice and
`docs/baseline/BASELINE_IMPLEMENTATION.md` for how the protocol is implemented.
---
@@ -24,59 +29,79 @@ Details and mismatches: `docs/research/DATASET_INSPECTION.md`.
FROZEN: symmetric Dirichlet over classes, `num_clients = 10`, `alpha ∈ {0.75, 0.50, 0.25}`.
- Partitions are generated from the **training split only**.
- Class proportions are drawn per client from `Dirichlet(alpha)` and used to allocate
training samples per class to clients.
- The exact Dirichlet implementation (per-class client allocation vs per-client class
sampling) must be fixed and documented, because it changes realised `N_ij`. `PROVISIONAL`.
- Success criterion for reproducing the paper's setting: at low alpha, several clients miss
several classes, consistent with the paper's reported counts (e.g. X-IIoTID `alpha=0.25`:
8/10 clients miss 1–5 classes).
FROZEN algorithm (**PROJECT DECISION**, paper does not specify the allocation procedure):
1. Partitions are generated from the **training split only**.
2. For each class `c`, draw client proportions `p ~ Dirichlet(alpha * 1_K_clients)`.
3. Allocate that class's training samples to clients with
`counts ~ Multinomial(N_c, p)`, so the counts sum exactly to `N_c`.
4. Split the class's shuffled sample positions into consecutive blocks of size `counts`.
The shuffle is a permutation drawn from the same deterministic RNG.
5. A client may receive zero samples of a class; that class is then *missing* on the client
(`N_ij = 0`), which is the intended source of non-IIDness.
Determinism: the RNG seed is derived as
`rng_seed = sha256(f"{dataset}|dirichlet|alpha={alpha}|seed={seed}")[:16] mod 2**31`.
The manifest stores `rng_seed`, so the same partition is reproduced exactly on reload.
Success criterion for reproducing the paper's setting: at low alpha several clients miss
several classes.
## 3. Splits
- Test: 20% of each dataset (paper uses 80/20). FROZEN.
- Validation: a separate split used only for beta selection. The paper has **no** validation
set, so this is an explicit project addition. `PROVISIONAL` fraction and procedure.
- Requirement: train/validation/test come from a single deterministic split with a recorded
seed, and the same split is reused for every beta variant.
FROZEN: a single deterministic **stratified** split per `(dataset, seed)`.
- `test = 0.20` of the cleaned dataset (paper uses 80/20). FROZEN.
- `validation = 0.10`; `train = 0.70`. **PROJECT DECISION**: the paper has no validation set;
this project adds one for beta selection. The test fraction keeps the paper's 20%.
- Stratification is per class: each class's samples are shuffled with a deterministic RNG and
split 70/10/20. This guarantees every class is represented in validation and test, which is
required for rare-class evaluation.
- Split seed: `sha256(f"{dataset}|split|seed={seed}")[:16] mod 2**31`.
- The exact same split is reused for every aggregation variant and beta candidate.
## 4. Seeds
- The paper averages over three runs with unnamed seeds. `PROVISIONAL` set: `{0, 1, 2}`.
- Once frozen, the seed set is identical across all aggregation variants.
- Each seed deterministically controls: split, Dirichlet partition, model initialisation,
batch order/shuffling.
- A beta run MUST NOT derive a new partition; it loads the manifest for
`(dataset, alpha, seed)`.
FROZEN (`PROJECT DECISION`): `{0, 1, 2}` (the paper averages over three unnamed seeds).
## 5. Beta candidates and modes
Each seed deterministically controls: split, Dirichlet partition, model initialisation, batch
order/shuffling. All RNG use is isolated via `protean_research.utils.seeding.derive_seed`.
A beta run MUST NOT derive a new partition; it loads the manifest for `(dataset, alpha, seed)`.
FROZEN: `beta ∈ {0.00, 0.25, 0.50, 0.75, 1.00}`, one beta for all classes and clients.
## 5. Aggregation modes
Modes:
**Baseline checkpoint: only `original_protean` is implemented.** The proposed
support-weighted aggregation (`support_weighted`) and the `equal_available` control are
**DEFERRED** to the later beta/controls phase and are not present in executable code.
- `original_protean` — verified baseline semantics. Not yet encodable (see baseline
investigation). This is the baseline to reproduce.
- `support_weighted` — `w_ij = N_ij^beta`, normalised over `S_j` (proposed mechanism).
- `equal_available` — equal weights over `S_j`; a methodological control introduced only if
the baseline participant rule differs from `S_j`. Not to be presented as a novelty.
- `original_protean` — reproduced baseline. **REPRODUCTION ASSUMPTION (project decision)**:
because Eq. (2)/(5.5) is internally inconsistent about missing classes, the baseline
participant rule is assumed to be `S_j = { i | N_ij > 0 and a valid C_ij exists }` with
**equal weights `1/|S_j|`**, following the thesis statement that prototypes are shared
only for locally observed classes. This is not verified against code and must be
validated in the baseline-verification phase.
- `support_weighted` (deferred) — `w_ij = N_ij^beta`, normalised over `S_j`.
- `equal_available` (deferred) — equal weights over `S_j`; only introduced later if the
baseline participant rule is shown to differ from `S_j`.
`S_j = { i | N_ij > 0 and a valid local prototype C_ij exists }`.
FROZEN candidates for the later phase: `beta ∈ {0.00, 0.25, 0.50, 0.75, 1.00}`, one beta for
all classes and clients. They are **not** part of the baseline checkpoint.
## 6. Partition persistence
Each `(dataset, alpha, seed)` produces one persisted manifest containing at least:
Each `(dataset, alpha, seed)` produces one persisted JSON manifest under
`artifacts/partitions/` containing:
- dataset name, alpha, seed, num_clients, K;
- per-client sample indices or a reproducible reference plus a content hash;
- per-client `N_ij` matrix `[client][class]`;
- class label mapping;
- split indices for train/validation/test;
- preprocessing/config hash and code version.
- dataset key, alpha, seed, num_clients, num_classes, class names;
- the Dirichlet `algorithm` id and `rng_seed`;
- per-client `N_ij` matrix `[client][class]` (training split);
- split metadata (seed, per-split sizes, content hash) and partition content hash;
- preprocessing/config/code hashes.
Manifests live under `artifacts/partitions/`. Reloading a manifest must reproduce the same
partition (tested). Training reads manifests; it does not regenerate them.
Manifests are small and version-controlled. Full sample-index arrays are **not** stored by
default (they would be tens of MB per manifest); they are regenerated deterministically from
the stored recipe and RNG seed and verified against the stored `N_ij` and content hash. This
is the tested reload path: `load_manifest` -> `reproduce_partition` -> hash match.
## 7. Rare and unseen classes
@@ -96,18 +121,17 @@ Persisted metadata (minimum one row per `(dataset, alpha, seed, client, class)`)
Procedure, strictly ordered:
1. **Candidate evaluation (validation only).** For each beta candidate, train with the
paired manifest and compute validation metrics. Primary criterion: rare-class macro-F1,
computed per client that has rare classes, then averaged over those clients.
2. **Beta selection.** Choose the beta with the best mean validation rare-class macro-F1
(ties broken by a pre-declared rule, e.g. smaller beta). The result is
"best among tested candidates under this procedure", never a global optimum.
1. **Candidate evaluation (validation only).** For each beta candidate, train with the paired
manifest and compute validation metrics. Primary criterion: rare-class macro-F1, computed
per client that has rare classes, then averaged over those clients.
2. **Beta selection.** Choose the beta with the best mean validation rare-class macro-F1 (ties
broken by smaller beta). The result is "best among tested candidates under this
procedure", never a global optimum.
3. **Final test evaluation.** Evaluate the selected beta on the test split. Report overall
macro-F1, rare-class recall, rare-class macro-F1, and per-client results, plus the test
results of all candidates for transparency.
Test data is never used in steps 1–2. The selection code must not have access to test
outputs; test evaluation is a separate step keyed on the selected beta.
Test data is never used in steps 1–2.
## 9. Required metrics
@@ -117,29 +141,28 @@ Per dataset/alpha/seed/beta and per client, record:
- rare-class recall;
- overall macro-F1;
- accuracy (for comparison with the paper);
- per-class precision/recall/F1 and confusion counts;
- per-class precision/recall/F1 and support (confusion counts persisted);
- per-client metrics.
Also record the validation/test rare-class support counts, so rare-class metrics are never
read without their sample sizes. Results are reported as mean and variation across seeds.
Also record the validation/test rare-class support counts. Results are reported as mean and
variation across seeds.
## 10. Non-IID characterization
For every manifest, record at least: `N_ij`, class total, number of clients containing class
`j`, `min N_ij`, `max N_ij`, `mean N_ij`, `std N_ij`, coefficient of variation, and
`max/min` ratio where defined. Alpha alone is insufficient; different seeds realise
different distributions.
`max/min` ratio where defined. Alpha alone is insufficient.
## 11. Direct prototype analysis
Separate the direct mathematical aggregation effect from the end-to-end training effect.
- During a run, persist at least one round's fixed `C_ij` and `N_ij` snapshot.
- Persist at least one round's fixed `C_ij` and `N_ij` snapshot.
- Reusing those snapshots, compute `Cbar_j(beta)` for several beta without retraining.
- Report `|| Cbar_j(beta) - Cbar_j(0) ||`; optionally the normalised aggregation weights,
distances to contributing prototypes, pairwise global-prototype displacement, and support
- Report `|| Cbar_j(beta) - Cbar_j(0) ||`; optionally normalised aggregation weights,
distances to contributing prototypes, pairwise global-prototype displacement, support
imbalance.
- This analysis complements, and never replaces, end-to-end evaluation.
- This complements, and never replaces, end-to-end evaluation.
## 12. Paired comparison guarantee
@@ -154,24 +177,66 @@ For each `(dataset, alpha, seed)`, every beta variant and every mode reuses:
A beta run that regenerates a fresh Dirichlet partition is a protocol violation.
## 13. Frozen vs provisional summary
## 13. Preprocessing (frozen)
| Item | Value | Status |
**PROJECT DECISION** — the paper specifies no preprocessing. Frozen recipe:
1. Read the raw CSV read-only (`pandas.read_csv`, `low_memory=False`).
2. Column names are stripped; the empty 5G-NIDD header becomes `Unnamed: 0`.
3. Exact duplicate rows are dropped (`keep="first"`) before splitting.
4. Rows whose label is in `drop_labels` are dropped.
5. Labels are canonicalised: surrounding whitespace removed, runs of whitespace collapsed,
and whitespace around `_` removed (fixes `Lateral _movement`).
6. Feature columns = all columns except the label column, its aliases, and
`non_feature_columns`.
7. `missing_tokens` are replaced with NaN. Categorical missing values become an explicit
`__MISSING__` category.
8. Categorical columns (`categorical_columns`) are one-hot encoded with categories fitted on
the **train split only**; unseen categories at transform time become all-zero.
9. Numeric columns are coerced to numeric, median-imputed and standardized using **train
statistics only**.
10. Output is a deterministic `float32` matrix with a fixed column order.
No statistic from validation or test data ever enters preprocessing.
## 14. Model, local training, inference (frozen)
- Model: 2 Conv1D (64, 128 filters, ReLU, max-pool, dropout 0.2/0.5), flatten, dense 128
(embedding), log-softmax head. **PROJECT DECISION**: kernel size 3, padding 1.
- Local loss: `cross_entropy + lambda * ||phi(x) - Cbar_{y}||^2 + (mu/2) * ||omega_i - omega||^2`,
with `mu = 0.1` (paper), `lambda = 1.0` (**PROJECT DECISION**, paper silent). The prototype
term is applied per sample only for classes that currently have a global prototype.
- Optimizer Adam, `lr = 1e-3`, batch size 256, 3 local epochs, 10 rounds
(**PROJECT DECISION** where not stated).
- Local prototypes: mean embedding over the client's full class-`j` training samples,
computed at the **end of the local round** (paper Eq. 1). Missing classes produce no
prototype.
- Server: model parameters averaged with equal `1/M` weights (paper Eq. 2/5.5); class
prototypes aggregated with the active mode.
- Round 0: no global prototypes exist; the prototype-alignment term is skipped; the first
global prototypes are produced by aggregating round-0 local prototypes.
- Inference: embedding `z = phi(x)`, prediction `argmin_j ||z - Cbar_j||^2` over classes that
have a global prototype.
## 15. Frozen vs decision summary
| Item | Value | Source |
|---|---|---|
| Datasets | X-IIoTID, 5G-NIDD | FROZEN |
| Clients | 10 | FROZEN |
| Alpha | 0.75, 0.50, 0.25 | FROZEN |
| Beta candidates | 0, 0.25, 0.50, 0.75, 1.00 | FROZEN |
| Test fraction | 0.20 | FROZEN |
| Rounds / local epochs | 10 / 3 | FROZEN |
| `mu` | 0.1 | FROZEN |
| `lambda` | unknown | PROVISIONAL |
| Batch size / learning rate | unknown | PROVISIONAL |
| Validation fraction / procedure | unknown | PROVISIONAL |
| Seeds | 0, 1, 2 | PROVISIONAL |
| Dirichlet allocation algorithm | unknown | PROVISIONAL |
| Preprocessing recipe | unknown | PROVISIONAL |
| Missing-class baseline rule | unknown | PROVISIONAL / blocked on baseline |
| Datasets | X-IIoTID, 5G-NIDD | paper |
| Clients | 10 | paper |
| Alpha | 0.75, 0.50, 0.25 | paper |
| Beta candidates | 0, 0.25, 0.50, 0.75, 1.00 | spec |
| Test fraction | 0.20 | paper |
| Rounds / local epochs | 10 / 3 | paper |
| `mu` | 0.1 | paper |
| `lambda` | 1.0 | project decision |
| Batch size / learning rate | 256 / 1e-3 (Adam) | project decision |
| Validation fraction | 0.10 (stratified) | project decision |
| Seeds | 0, 1, 2 | project decision |
| Dirichlet allocation | per-class Dirichlet + multinomial | project decision |
| Preprocessing recipe | §13 | project decision |
| Baseline participant rule | `S_j`, equal `1/|S_j|` | project decision |
| Model aggregation | equal `1/M` | paper Eq. 2/5.5 |
| Prototype computation | end-of-round mean embedding | paper Eq. 1 |
The provisional items must be resolved and recorded here before the first sweep. Until then
only smoke tests are permitted.
Until the baseline is verified, only smoke tests are permitted; no sweep may be launched.
+41 -34
View File
@@ -1,6 +1,9 @@
# Implementation Plan
Status: design document (no full training implemented in this session)
Status: design document. The data, model, federated and evaluation modules
described below are now IMPLEMENTED for the baseline (see
`docs/baseline/BASELINE_IMPLEMENTATION.md`); the analysis/direct-prototype module
is still future work and beta experiments were not run in this session.
Scope: architecture for the server-side prototype-aggregation study on top of a reproduced
PROTEAN baseline.
@@ -107,19 +110,18 @@ regenerate a partition.
- `round.py`: one communication round; records per-round prototype snapshots for direct
analysis.
### `aggregation/` (exists)
### `aggregation/` (baseline implemented; proposed method deferred)
Pure, dependency-free functions implementing only the unambiguous modes:
Pure, dependency-free functions implementing only the reproduced baseline:
- `beta_weights(supports, beta)` → `N_ij ** beta` (unnormalised).
- `aggregation_weights(prototypes, mode, beta, supports)` → normalised weights summing to 1.
- `aggregate_prototypes(...)` → `Cbar_j` for one class.
- `AggregationMode`: `original_protean` (raises `NotImplementedError` until verified),
`support_weighted`, `equal_available`.
- `aggregate_prototypes(prototypes, mode)` → `Cbar_j` for one class (equal mean over `S_j`).
- `aggregate_class_prototypes(contributions, mode)` → per-class global prototypes; builds
`S_j` by dropping clients with `N_ij = 0` or a missing prototype.
- `AggregationMode`: only `original_protean` in this checkpoint.
Planned addition: a server-facing helper that accepts `{client_id: (C_ij, N_ij)}` for class
`j`, constructs `S_j` (drop clients with `N_ij = 0` or missing prototype), and delegates to
`aggregate_prototypes`. This keeps all weighting logic in one tested place.
The proposed `support_weighted` mode (`N_ij ** beta`) and the `equal_available` control are
**deferred** to the later beta/controls phase and are not present in executable code. The
baseline participant rule (`S_j`, equal weights) is a documented reproduction assumption.
### `evaluation/` (planned)
@@ -139,20 +141,23 @@ Planned addition: a server-facing helper that accepts `{client_id: (C_ij, N_ij)}
`Cbar_j(beta)` for several beta without retraining and report
`||Cbar_j(beta) - Cbar_j(0)||`, weights, distances, displacement, imbalance.
## 5. Aggregation API concept
## 5. Aggregation API (baseline)
```
aggregate_prototypes(prototypes, mode, beta=None, supports=None) -> list[float]
aggregate_prototypes(prototypes, mode="original_protean") -> list[float]
aggregate_class_prototypes(contributions, mode="original_protean") -> dict[int, list[float]]
```
Contract:
Contract (implemented baseline):
- `prototypes[i]` is `C_ij`; `supports[i]` is `N_ij > 0`; order is client order.
- `support_weighted`: weights `N_ij ** beta`, normalised over `S_j`.
- `equal_available`: equal weights over `S_j` (control; not for `beta > 0` claims).
- `original_protean`: only after the baseline missing-class rule is verified and encoded.
- Validates finiteness, strictly positive supports, equal dimensions; normalised weights sum
to 1; never emits NaN/Inf for valid inputs.
- `prototypes[i]` is `C_ij`; order is client order.
- `original_protean`: equal mean over the supplied contributor set `S_j`.
- `aggregate_class_prototypes` builds `S_j` by dropping clients with `N_ij <= 0` or a
missing prototype; missing classes are never fabricated.
- Validates finiteness and equal dimensions; never emits NaN/Inf for valid inputs.
The proposed `support_weighted(beta, supports)` and `equal_available` modes are **planned**
for the later beta/controls phase and are not implemented in this checkpoint.
## 6. Experiment orchestration
@@ -169,22 +174,24 @@ Contract:
## 7. Testing strategy
Required tests (from the specification), to be implemented as the code is built:
Implemented in the baseline checkpoint (see `tests/`):
1. `beta = 0` equals the arithmetic mean over the same `S_j`.
2. `beta = 1` equals sample-count weighting (equals pooling of included embeddings).
3. One contributing client gives the same output for all beta.
4. Equal supports give the same output for all beta.
5. A missing class does not enter `S_j`.
6. Normalised weights sum to one.
7. Valid inputs never produce NaN/Inf.
8. Partition generation is deterministic for a fixed seed.
9. Saved partition manifests reload identically.
10. Baseline behaviour is unchanged by adding the proposed mode.
11. Validation/test separation is maintained.
- baseline aggregation is the equal mean over `S_j`;
- a missing class does not enter `S_j` and is never fabricated;
- valid inputs never produce NaN/Inf; dimension/finiteness validation;
- partition generation is deterministic for a fixed seed;
- saved partition manifests reload identically (hash-verified);
- rare/unseen semantics; prototype means; metrics on known fixtures;
- a small end-to-end federated smoke test.
This session adds the dependency-free subset (1–7) in `tests/test_prototype_aggregation.py`.
Items 8–11 require the future data/federated modules and are deferred.
Deferred to the beta/controls phase (proposed-method invariants):
- `beta = 0` equals the arithmetic mean over the same `S_j`;
- `beta = 1` equals sample-count weighting;
- single-contributor and equal-support invariance across beta;
- normalised beta weights sum to one;
- baseline behaviour unchanged by adding the proposed mode;
- paired beta/validation/test separation.
## 8. Compute feasibility
@@ -88,9 +88,10 @@ must still be verified.
the participant rule from the effect of the weights. The spec says such a control is only
introduced if needed.
`IMPLEMENTATION DECISION` `equal_available` is kept as a dormant, documented control mode.
It must not be reported as a result unless baseline investigation demonstrates its
necessity (see `docs/baseline/PROTEAN_BASELINE_INVESTIGATION.md`).
`IMPLEMENTATION DECISION` The `equal_available` control is **deferred** to the later
beta/controls phase and is not implemented in the baseline-only checkpoint. It is
introduced only if baseline investigation demonstrates that the baseline participant rule
differs from `S_j` (see `docs/baseline/PROTEAN_BASELINE_INVESTIGATION.md`).
## 5. Rare class and unseen class definitions