feat: update script names and enhance iterative evaluation with table generation and plotting
This commit is contained in:
parent
a516408c4e
commit
51abbf1150
14 changed files with 198 additions and 155 deletions
|
|
@ -243,7 +243,7 @@ config/loss/ckpt recorded in the JSONs), and a no-op
|
||||||
|
|
||||||
The mirror of A's ablation on the MONAI backbone: C trained with every
|
The mirror of A's ablation on the MONAI backbone: C trained with every
|
||||||
hyperparameter at C's own default (its original study protocol from
|
hyperparameter at C's own default (its original study protocol from
|
||||||
`scripts_monai/02_monai_train.py`, nothing nnU-derived) — BatchNorm
|
`scripts_monai/07_train.py`, nothing nnU-derived) — BatchNorm
|
||||||
`UNet(16→128)` with MONAI default init, LeakyReLU (C's own default act),
|
`UNet(16→128)` with MONAI default init, LeakyReLU (C's own default act),
|
||||||
per-sample 0.5CE+0.5mean-dice loss (WeightedDiceCELoss, no deep
|
per-sample 0.5CE+0.5mean-dice loss (WeightedDiceCELoss, no deep
|
||||||
supervision), AdamW 3e-4 (wd 1e-4) + warmup/cosine, grad clip 5.0,
|
supervision), AdamW 3e-4 (wd 1e-4) + warmup/cosine, grad clip 5.0,
|
||||||
|
|
@ -314,7 +314,7 @@ Artifacts: `results/benchmark_full/benchmark_full_C_legacy_std_100ep.json`
|
||||||
0.4002 @100ep record; `--legacy` reverts). Checkpoint conventions: see the
|
0.4002 @100ep record; `--legacy` reverts). Checkpoint conventions: see the
|
||||||
A and C ablation sections.
|
A and C ablation sections.
|
||||||
- Study entrypoints match the record configs (2026-10-07): `scripts/07_train.py`
|
- Study entrypoints match the record configs (2026-10-07): `scripts/07_train.py`
|
||||||
(A) was already A-native all-defaults; `scripts_monai/02_monai_train.py`
|
(A) was already A-native all-defaults; `scripts_monai/07_train.py`
|
||||||
(C) now trains B-aligned by default (its record config) — `--legacy` reverts
|
(C) now trains B-aligned by default (its record config) — `--legacy` reverts
|
||||||
to the pre-2026-10-07 config. C checkpoints carry a `b_aligned` marker that
|
to the pre-2026-10-07 config. C checkpoints carry a `b_aligned` marker that
|
||||||
03/04 read for the Z-score input; in C's B-aligned study mode pseudo rows
|
03/04 read for the Z-score input; in C's B-aligned study mode pseudo rows
|
||||||
|
|
|
||||||
54
README.md
54
README.md
|
|
@ -95,6 +95,10 @@ Run the full study:
|
||||||
python scripts/09_run_iterative.py --rounds 4 --gpus 3
|
python scripts/09_run_iterative.py --rounds 4 --gpus 3
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Per-round test results are saved to `results/round{k}_test.json`, and
|
||||||
|
`results/iterative_table.jsonl` + `results/iterative_dice.png` are rewritten
|
||||||
|
after every round's evaluation.
|
||||||
|
|
||||||
Individual stages are standalone, e.g.:
|
Individual stages are standalone, e.g.:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -140,12 +144,12 @@ Dataset: `Dataset210_NTUH_T1C_PL`, single channel `t1c`, 2 classes
|
||||||
|
|
||||||
| # | Script | Purpose |
|
| # | Script | Purpose |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 01 | `01_nnu_prepare_dataset.py` | Build/rebuild the raw dataset (symlinked **native** `imagesTr`, `labelsTr` 0/1 + `dataset.json`) from a rows jsonl |
|
| 01 | `01_prepare_dataset.py` | Build/rebuild the raw dataset (symlinked **native** `imagesTr`, `labelsTr` 0/1 + `dataset.json`) from a rows jsonl |
|
||||||
| 02 | `02_nnu_plan_preprocess.py` | Plan + preprocess (`--clean`), then write subject-level `splits_final.json` |
|
| 02 | `02_plan_preprocess.py` | Plan + preprocess (`--clean`), then write subject-level `splits_final.json` |
|
||||||
| 03 | `03_nnu_train.py` | Train one round via `NTUHLPLTrainer` (DDP `-num_gpus`), optional warm start |
|
| 06 | `06_pseudo_label.py` | Multi-GPU `nnUNetv2_predict` on native pool volumes + selection gates (physical mm) |
|
||||||
| 04 | `04_nnu_pseudo_label.py` | Multi-GPU `nnUNetv2_predict` on native pool volumes + selection gates (physical mm) |
|
| 07 | `07_train.py` | Train one round via `NTUHLPLTrainer` (DDP `-num_gpus`), optional warm start |
|
||||||
| 05 | `05_nnu_eval_test.py` | Held-out test evaluation on native volumes (probability Dice@0.5 + hard-seg Dice) |
|
| 08 | `08_eval.py` | Held-out test evaluation on native volumes (probability Dice@0.5 + hard-seg Dice) |
|
||||||
| 06 | `06_nnu_run_iterative.py` | Orchestrates rounds 0…K, table + plot |
|
| 09 | `09_run_iterative.py` | Orchestrates rounds 0…K, table + plot |
|
||||||
| — | `trainers/ntuh_pl_trainer.py` | `NTUHLPLTrainer`: env-driven epochs/LR + full-weight warm start |
|
| — | `trainers/ntuh_pl_trainer.py` | `NTUHLPLTrainer`: env-driven epochs/LR + full-weight warm start |
|
||||||
| — | `nnu_common.py` | Shared paths, env, dataset/split helpers, native-grid selection + consistency |
|
| — | `nnu_common.py` | Shared paths, env, dataset/split helpers, native-grid selection + consistency |
|
||||||
|
|
||||||
|
|
@ -210,25 +214,26 @@ across visits.
|
||||||
Full study from the repo root:
|
Full study from the repo root:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python scripts_nnu/06_nnu_run_iterative.py --rounds 4 --gpus 3
|
python scripts_nnu/09_run_iterative.py --rounds 4 --gpus 3
|
||||||
```
|
```
|
||||||
|
|
||||||
Defaults: baseline 250 epochs @ 1e-2; warm-started rounds 75 epochs @ 1e-3.
|
Defaults: baseline 250 epochs @ 1e-2; warm-started rounds 75 epochs @ 1e-3.
|
||||||
Pseudo-label gates match Pipeline A (`--tau-pos 0.95`, `--min-cc-frac 0.2`,
|
Pseudo-label gates match Pipeline A (`--tau-pos 0.95`, `--min-cc-frac 0.2`,
|
||||||
`--neg-frac 0.9`, volume p2–p98). Optional flags: `--no-tta`,
|
`--neg-frac 0.9`, volume p2–p98). Optional flags: `--no-tta`,
|
||||||
`--no-neg-pseudo`, and the gate overrides. Outputs to `results/round{k}_test_nnu.json`,
|
`--no-neg-pseudo`, and the gate overrides. Outputs to `results/round{k}_test_nnu.json`,
|
||||||
`results/iterative_table_nnu.jsonl`, `results/iterative_dice_nnu.png`; per-round
|
`results/iterative_table_nnu.jsonl` (rewritten after every round's evaluation),
|
||||||
|
`results/iterative_dice_nnu.png`; per-round
|
||||||
checkpoints snapshotted to `runs_nnu/round{k}/best_nnu.pth`.
|
checkpoints snapshotted to `runs_nnu/round{k}/best_nnu.pth`.
|
||||||
|
|
||||||
Individual stages:
|
Individual stages:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python scripts_nnu/01_nnu_prepare_dataset.py --rows <rows.jsonl>
|
python scripts_nnu/01_prepare_dataset.py --rows <rows.jsonl>
|
||||||
python scripts_nnu/02_nnu_plan_preprocess.py --val data/manifests/split_val.jsonl
|
python scripts_nnu/02_plan_preprocess.py --val data/manifests/split_val.jsonl
|
||||||
python scripts_nnu/03_nnu_train.py --gpus 3 --epochs 250 --lr 1e-2
|
python scripts_nnu/07_train.py --gpus 3 --epochs 250 --lr 1e-2
|
||||||
python scripts_nnu/04_nnu_pseudo_label.py --pool data/manifests/unlabeled_pool.jsonl \
|
python scripts_nnu/06_pseudo_label.py --pool data/manifests/unlabeled_pool.jsonl \
|
||||||
--out data/pseudo_nnu/round1 --gpus 3
|
--out data/pseudo_nnu/round1 --gpus 3
|
||||||
python scripts_nnu/05_nnu_eval_test.py --rows data/manifests/split_test.jsonl \
|
python scripts_nnu/08_eval.py --rows data/manifests/split_test.jsonl \
|
||||||
--out results/round0_test_nnu.json --gpus 3
|
--out results/round0_test_nnu.json --gpus 3
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -278,11 +283,11 @@ directly comparable to Pipelines A and B.
|
||||||
|
|
||||||
| # | Script | Purpose |
|
| # | Script | Purpose |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 01 | `01_monai_build_rows.py` | Assemble a round's training manifest (labeled + accepted pseudo rows, weighted, de-duped by key) |
|
| 01 | `01_build_rows.py` | Assemble a round's training manifest (labeled + accepted pseudo rows, weighted, de-duped by key) |
|
||||||
| 02 | `02_monai_train.py` | DDP training of the MONAI UNet for one round (torchrun), full-weight warm start (same config mode), auto-resume |
|
| 06 | `06_pseudo_label.py` | Sliding-window + TTA prediction of the remaining pool, selection gates + consistency filter |
|
||||||
| 03 | `03_monai_pseudo_label.py` | Sliding-window + TTA prediction of the remaining pool, selection gates + consistency filter |
|
| 07 | `07_train.py` | DDP training of the MONAI UNet for one round (torchrun), full-weight warm start (same config mode), auto-resume |
|
||||||
| 04 | `04_monai_eval_test.py` | Held-out test evaluation (Dice from probability map @0.5) |
|
| 08 | `08_eval.py` | Held-out test evaluation (Dice from probability map @0.5) |
|
||||||
| 05 | `05_monai_run_iterative.py` | Orchestrates rounds 0…K, table + plot |
|
| 09 | `09_run_iterative.py` | Orchestrates rounds 0…K, table + plot |
|
||||||
| — | `monai_common.py` | Network (legacy + B-aligned) / losses / transform chain / inference + distributed helpers |
|
| — | `monai_common.py` | Network (legacy + B-aligned) / losses / transform chain / inference + distributed helpers |
|
||||||
|
|
||||||
### Running
|
### Running
|
||||||
|
|
@ -290,7 +295,7 @@ directly comparable to Pipelines A and B.
|
||||||
Full study from the repo root:
|
Full study from the repo root:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python scripts_monai/05_monai_run_iterative.py --rounds 4 --gpus 3
|
python scripts_monai/09_run_iterative.py --rounds 4 --gpus 3
|
||||||
```
|
```
|
||||||
|
|
||||||
Defaults: B-aligned protocol (SGD 1e-2 + PolyLR, restarted per round); baseline
|
Defaults: B-aligned protocol (SGD 1e-2 + PolyLR, restarted per round); baseline
|
||||||
|
|
@ -300,24 +305,25 @@ pre-2026-10-07 config (baseline 100 epochs @ 3e-4, warm-started rounds
|
||||||
Pipelines A/B (`--tau-pos 0.95`, `--min-cc-frac 0.2`, `--neg-frac 0.9`, volume
|
Pipelines A/B (`--tau-pos 0.95`, `--min-cc-frac 0.2`, `--neg-frac 0.9`, volume
|
||||||
p2–p98). Optional flags: `--no-tta`, `--no-neg-pseudo`, `--legacy`, and the gate
|
p2–p98). Optional flags: `--no-tta`, `--no-neg-pseudo`, `--legacy`, and the gate
|
||||||
overrides. Outputs to
|
overrides. Outputs to
|
||||||
`results/round{k}_test_monai.json`, `results/iterative_table_monai.jsonl`,
|
`results/round{k}_test_monai.json`, `results/iterative_table_monai.jsonl`
|
||||||
|
(rewritten after every round's evaluation),
|
||||||
`results/iterative_dice_monai.png`; per-round checkpoints in
|
`results/iterative_dice_monai.png`; per-round checkpoints in
|
||||||
`runs_monai/round{k}/best.pt`.
|
`runs_monai/round{k}/best.pt`.
|
||||||
|
|
||||||
Individual stages:
|
Individual stages:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python scripts_monai/01_monai_build_rows.py --train data/manifests/split_train.jsonl \
|
python scripts_monai/01_build_rows.py --train data/manifests/split_train.jsonl \
|
||||||
--accepted data/pseudo_monai/round1/accepted.jsonl \
|
--accepted data/pseudo_monai/round1/accepted.jsonl \
|
||||||
--out data/pseudo_monai/round1_rows.jsonl
|
--out data/pseudo_monai/round1_rows.jsonl
|
||||||
torchrun --standalone --nproc_per_node 3 scripts_monai/02_monai_train.py \
|
torchrun --standalone --nproc_per_node 3 scripts_monai/07_train.py \
|
||||||
--rows data/pseudo_monai/round1_rows.jsonl --val data/manifests/split_val.jsonl \
|
--rows data/pseudo_monai/round1_rows.jsonl --val data/manifests/split_val.jsonl \
|
||||||
--epochs 30 --batch 3 --ckpt-dir runs_monai/round1 \
|
--epochs 30 --batch 3 --ckpt-dir runs_monai/round1 \
|
||||||
--pretrained runs_monai/round0/best.pt
|
--pretrained runs_monai/round0/best.pt
|
||||||
torchrun --standalone --nproc_per_node 3 scripts_monai/03_monai_pseudo_label.py \
|
torchrun --standalone --nproc_per_node 3 scripts_monai/06_pseudo_label.py \
|
||||||
--ckpt runs_monai/round1/best.pt --pool data/manifests/unlabeled_pool.jsonl \
|
--ckpt runs_monai/round1/best.pt --pool data/manifests/unlabeled_pool.jsonl \
|
||||||
--out data/pseudo_monai/round2 --already data/pseudo_monai/added_keys.jsonl
|
--out data/pseudo_monai/round2 --already data/pseudo_monai/added_keys.jsonl
|
||||||
torchrun --standalone --nproc_per_node 3 scripts_monai/04_monai_eval_test.py \
|
torchrun --standalone --nproc_per_node 3 scripts_monai/08_eval.py \
|
||||||
--rows data/manifests/split_test.jsonl --ckpt runs_monai/round1/best.pt \
|
--rows data/manifests/split_test.jsonl --ckpt runs_monai/round1/best.pt \
|
||||||
--out results/round1_test_monai.json
|
--out results/round1_test_monai.json
|
||||||
```
|
```
|
||||||
|
|
@ -39,6 +39,54 @@ def torchrun(nproc, script, extra):
|
||||||
return f"{tr} --standalone --nproc_per_node {nproc} {script} {extra}"
|
return f"{tr} --standalone --nproc_per_node {nproc} {script} {extra}"
|
||||||
|
|
||||||
|
|
||||||
|
def build_table(args):
|
||||||
|
"""Scan per-round test results, rewrite results/iterative_table.jsonl + plot.
|
||||||
|
|
||||||
|
Called after every round's evaluation so each round's performance is saved
|
||||||
|
incrementally (survives interrupted runs).
|
||||||
|
"""
|
||||||
|
results_dir = f"{ROOT}/results"
|
||||||
|
table = []
|
||||||
|
for k in range(args.rounds + 1):
|
||||||
|
f = os.path.join(results_dir, f"round{k}_test.json")
|
||||||
|
if not os.path.exists(f):
|
||||||
|
continue
|
||||||
|
r = json.load(open(f))
|
||||||
|
row = {"round": k, "test_dice": round(r["dice"], 4), "n_test": r["n"],
|
||||||
|
"ckpt_epoch": r.get("epoch")}
|
||||||
|
pf = os.path.join(DATA, "pseudo", f"round{k}", "summary.json") if k > 0 else None
|
||||||
|
if pf and os.path.exists(pf):
|
||||||
|
s = json.load(open(pf))
|
||||||
|
row.update({"n_pos": s["n_pos"], "n_neg": s["n_neg"],
|
||||||
|
"n_rej_cons": s["n_rejected_consistency"],
|
||||||
|
"pos_vol_med_mm3": s["pos_vol_mm3"]["med"]})
|
||||||
|
sf = os.path.join(ROOT, "runs", f"round{k}", "final.pt")
|
||||||
|
row["ckpt"] = sf if os.path.exists(sf) else ""
|
||||||
|
table.append(row)
|
||||||
|
save_jsonl(table, os.path.join(results_dir, "iterative_table.jsonl"))
|
||||||
|
if not table:
|
||||||
|
return table
|
||||||
|
print(json.dumps(table, indent=1))
|
||||||
|
try:
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use("Agg")
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
rs = [t["round"] for t in table]
|
||||||
|
ds = [t["test_dice"] for t in table]
|
||||||
|
plt.figure(figsize=(6, 4))
|
||||||
|
plt.plot(rs, ds, "o-")
|
||||||
|
plt.xlabel("pseudo-labeling round")
|
||||||
|
plt.ylabel("holdout tumor Dice")
|
||||||
|
for x, y in zip(rs, ds):
|
||||||
|
plt.annotate(f"{y:.3f}", (x, y), textcoords="offset points", xytext=(0, 8), fontsize=8)
|
||||||
|
plt.grid(alpha=0.3)
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(os.path.join(results_dir, "iterative_dice.png"), dpi=150)
|
||||||
|
except Exception as e: # noqa
|
||||||
|
print("plot failed:", repr(e))
|
||||||
|
return table
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
ap = argparse.ArgumentParser()
|
ap = argparse.ArgumentParser()
|
||||||
ap.add_argument("--rounds", type=int, default=4)
|
ap.add_argument("--rounds", type=int, default=4)
|
||||||
|
|
@ -67,6 +115,7 @@ def main():
|
||||||
f"--rows {train_f} --val {val_f} --epochs {args.base_epochs} --lr {args.lr} "
|
f"--rows {train_f} --val {val_f} --epochs {args.base_epochs} --lr {args.lr} "
|
||||||
f"--batch {args.batch} --ckpt-dir runs/round0"), f"{main_log}")
|
f"--batch {args.batch} --ckpt-dir runs/round0"), f"{main_log}")
|
||||||
run(f"python scripts/08_eval.py --rows {test_f} --ckpt runs/round0/best.pt", f"{main_log}")
|
run(f"python scripts/08_eval.py --rows {test_f} --ckpt runs/round0/best.pt", f"{main_log}")
|
||||||
|
build_table(args)
|
||||||
|
|
||||||
for k in range(1, args.rounds + 1):
|
for k in range(1, args.rounds + 1):
|
||||||
pdir = os.path.join(DATA, "pseudo/round" + str(k))
|
pdir = os.path.join(DATA, "pseudo/round" + str(k))
|
||||||
|
|
@ -91,42 +140,10 @@ def main():
|
||||||
f"--lr {args.round_lr} --batch {args.batch} --resume runs/round{k-1}/best.pt "
|
f"--lr {args.round_lr} --batch {args.batch} --resume runs/round{k-1}/best.pt "
|
||||||
f"--ckpt-dir runs/round{k} --val-every 1"), f"{main_log}")
|
f"--ckpt-dir runs/round{k} --val-every 1"), f"{main_log}")
|
||||||
run(f"python scripts/08_eval.py --rows {test_f} --ckpt runs/round{k}/best.pt", f"{main_log}")
|
run(f"python scripts/08_eval.py --rows {test_f} --ckpt runs/round{k}/best.pt", f"{main_log}")
|
||||||
|
build_table(args)
|
||||||
|
|
||||||
# ---- report ----
|
# ---- report ----
|
||||||
table = []
|
build_table(args)
|
||||||
for k in list(range(args.rounds + 1)):
|
|
||||||
f = os.path.join(results_dir, f"round{k}_test.json")
|
|
||||||
if os.path.exists(f):
|
|
||||||
r = json.load(open(f))
|
|
||||||
table.append({"round": k, "test_dice": round(r["dice"], 4), "n_test": r["n"],
|
|
||||||
"ckpt_epoch": r.get("epoch")})
|
|
||||||
pf = os.path.join(DATA, f"pseudo/round{k}/summary.json") if k > 0 else None
|
|
||||||
if pf and os.path.exists(pf):
|
|
||||||
s = json.load(open(pf))
|
|
||||||
table[-1].update({"n_pos": s["n_pos"], "n_neg": s["n_neg"],
|
|
||||||
"n_rej_cons": s["n_rejected_consistency"],
|
|
||||||
"pos_vol_med_mm3": s["pos_vol_mm3"]["med"]})
|
|
||||||
sf = os.path.join(ROOT, f"runs/round{k}/final.pt")
|
|
||||||
table[-1]["ckpt"] = sf if os.path.exists(sf) else ""
|
|
||||||
save_jsonl(table, os.path.join(results_dir, "iterative_table.jsonl"))
|
|
||||||
print(json.dumps(table, indent=1))
|
|
||||||
try:
|
|
||||||
import matplotlib
|
|
||||||
matplotlib.use("Agg")
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
rs = [t["round"] for t in table]
|
|
||||||
ds = [t["test_dice"] for t in table]
|
|
||||||
plt.figure(figsize=(6, 4))
|
|
||||||
plt.plot(rs, ds, "o-")
|
|
||||||
plt.xlabel("pseudo-labeling round")
|
|
||||||
plt.ylabel("holdout tumor Dice")
|
|
||||||
for x, y in zip(rs, ds):
|
|
||||||
plt.annotate(f"{y:.3f}", (x, y), textcoords="offset points", xytext=(0, 8), fontsize=8)
|
|
||||||
plt.grid(alpha=0.3)
|
|
||||||
plt.tight_layout()
|
|
||||||
plt.savefig(os.path.join(results_dir, "iterative_dice.png"), dpi=150)
|
|
||||||
except Exception as e: # noqa
|
|
||||||
print("plot failed:", repr(e))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@
|
||||||
base = labeled patient-level train split (w=1.0) — the same train/val usage as
|
base = labeled patient-level train split (w=1.0) — the same train/val usage as
|
||||||
Pipeline A: the val split is never trained on (it is the internal-validation
|
Pipeline A: the val split is never trained on (it is the internal-validation
|
||||||
set). Accepted pseudo-label rows (pos + neg) are appended with w=<pseudo-weight>
|
set). Accepted pseudo-label rows (pos + neg) are appended with w=<pseudo-weight>
|
||||||
(negative rows point at the zero mask written by stage 03), de-duplicated by
|
(negative rows point at the zero mask written by stage 06), de-duplicated by
|
||||||
key (first occurrence wins). Every row must point at existing pimg/plabel
|
key (first occurrence wins). Every row must point at existing pimg/plabel
|
||||||
niftis.
|
niftis.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
python scripts_monai/01_monai_build_rows.py \
|
python scripts_monai/01_build_rows.py \
|
||||||
--train data/manifests/split_train.jsonl \
|
--train data/manifests/split_train.jsonl \
|
||||||
--accepted data/pseudo_monai/round1/accepted.jsonl \
|
--accepted data/pseudo_monai/round1/accepted.jsonl \
|
||||||
--pseudo-weight 0.3 --out data/pseudo_monai/round1_rows.jsonl
|
--pseudo-weight 0.3 --out data/pseudo_monai/round1_rows.jsonl
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"""Pseudo-label the unlabeled pool with the MONAI round model (torchrun; rank = GPU).
|
"""Pseudo-label the unlabeled pool with the MONAI round model (torchrun; rank = GPU).
|
||||||
|
|
||||||
torchrun --standalone --nproc_per_node 3 scripts_monai/03_monai_pseudo_label.py \
|
torchrun --standalone --nproc_per_node 3 scripts_monai/06_pseudo_label.py \
|
||||||
--ckpt runs_monai/round0/best.pt --pool data/manifests/unlabeled_pool.jsonl \
|
--ckpt runs_monai/round0/best.pt --pool data/manifests/unlabeled_pool.jsonl \
|
||||||
--out data/pseudo_monai/round1 [--already data/pseudo_monai/added_keys.jsonl]
|
--out data/pseudo_monai/round1 [--already data/pseudo_monai/added_keys.jsonl]
|
||||||
|
|
||||||
|
|
@ -18,8 +18,8 @@ mask for neg), <out>/accepted.jsonl, <out>/summary.json. Per-rank shards are
|
||||||
flushed to <out>/part{rank}.jsonl so interrupted runs resume.
|
flushed to <out>/part{rank}.jsonl so interrupted runs resume.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
python scripts_monai/03_monai_pseudo_label.py <same flags, single GPU>
|
python scripts_monai/06_pseudo_label.py <same flags, single GPU>
|
||||||
torchrun --standalone --nproc_per_node 3 scripts_monai/03_monai_pseudo_label.py ...
|
torchrun --standalone --nproc_per_node 3 scripts_monai/06_pseudo_label.py ...
|
||||||
"""
|
"""
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"""MONAI DDP training entrypoint for one pseudo-labeling round (torchrun).
|
"""MONAI DDP training entrypoint for one pseudo-labeling round (torchrun).
|
||||||
|
|
||||||
torchrun --standalone --nproc_per_node 3 scripts_monai/02_monai_train.py \
|
torchrun --standalone --nproc_per_node 3 scripts_monai/07_train.py \
|
||||||
--rows data/pseudo_monai/round0_rows.jsonl --val data/manifests/split_val.jsonl \
|
--rows data/pseudo_monai/round0_rows.jsonl --val data/manifests/split_val.jsonl \
|
||||||
--epochs 100 --batch 3 --ckpt-dir runs_monai/round0 \
|
--epochs 100 --batch 3 --ckpt-dir runs_monai/round0 \
|
||||||
[--pretrained runs_monai/round0/best.pt]
|
[--pretrained runs_monai/round0/best.pt]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"""Held-out test evaluation of a MONAI round model (torchrun; rank = GPU).
|
"""Held-out test evaluation of a MONAI round model (torchrun; rank = GPU).
|
||||||
|
|
||||||
torchrun --standalone --nproc_per_node 3 scripts_monai/04_monai_eval_test.py \
|
torchrun --standalone --nproc_per_node 3 scripts_monai/08_eval.py \
|
||||||
--rows data/manifests/split_test.jsonl --ckpt runs_monai/round0/best.pt \
|
--rows data/manifests/split_test.jsonl --ckpt runs_monai/round0/best.pt \
|
||||||
--out results/round0_test_monai.json [--no-tta]
|
--out results/round0_test_monai.json [--no-tta]
|
||||||
|
|
||||||
|
|
@ -12,8 +12,8 @@ Per-rank results are appended to <out>_part{rank}.jsonl for resume; rank 0
|
||||||
merges them into <out> (json) + <out>_per_row.jsonl.
|
merges them into <out> (json) + <out>_per_row.jsonl.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
python scripts_monai/04_monai_eval_test.py <same flags, single GPU>
|
python scripts_monai/08_eval.py <same flags, single GPU>
|
||||||
torchrun --standalone --nproc_per_node 3 scripts_monai/04_monai_eval_test.py ...
|
torchrun --standalone --nproc_per_node 3 scripts_monai/08_eval.py ...
|
||||||
"""
|
"""
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
"""Orchestrates the MONAI iterative pseudo-labeling study (parallel to
|
"""Orchestrates the MONAI iterative pseudo-labeling study (parallel to
|
||||||
scripts/09 and scripts_nnu/06).
|
scripts/09 and scripts_nnu/09).
|
||||||
|
|
||||||
Round 0: train the MONAI UNet on the labeled patient-level train split (internal
|
Round 0: train the MONAI UNet on the labeled patient-level train split (internal
|
||||||
validation = held-out patient-level val split, subject-disjoint).
|
validation = held-out patient-level val split, subject-disjoint).
|
||||||
|
|
@ -15,7 +15,7 @@ Config: rounds train with the B-aligned protocol (C's record benchmark config,
|
||||||
--legacy (pre-2026-10-07 config: AdamW at --base-lr/--round-lr +
|
--legacy (pre-2026-10-07 config: AdamW at --base-lr/--round-lr +
|
||||||
per-sample weighted Dice+CE with pseudo-row down-weighting).
|
per-sample weighted Dice+CE with pseudo-row down-weighting).
|
||||||
|
|
||||||
Usage: python scripts_monai/05_monai_run_iterative.py [--rounds 4] [--gpus 3] [--legacy]
|
Usage: python scripts_monai/09_run_iterative.py [--rounds 4] [--gpus 3] [--legacy]
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -101,7 +101,7 @@ def main():
|
||||||
extra += " --no-neg"
|
extra += " --no-neg"
|
||||||
for f in accepted:
|
for f in accepted:
|
||||||
extra += f" --accepted {f}"
|
extra += f" --accepted {f}"
|
||||||
py("01_monai_build_rows.py", extra)
|
py("01_build_rows.py", extra)
|
||||||
|
|
||||||
def train(round, rows_f, epochs, lr, warmstart, val_every):
|
def train(round, rows_f, epochs, lr, warmstart, val_every):
|
||||||
extra = (f"--rows {rows_f} --val {val_f} --epochs {epochs} --lr {lr} "
|
extra = (f"--rows {rows_f} --val {val_f} --epochs {epochs} --lr {lr} "
|
||||||
|
|
@ -111,7 +111,7 @@ def main():
|
||||||
extra += f" --pretrained {warmstart}"
|
extra += f" --pretrained {warmstart}"
|
||||||
if args.legacy:
|
if args.legacy:
|
||||||
extra += " --legacy"
|
extra += " --legacy"
|
||||||
trrun("02_monai_train.py", extra, os.path.join(logdir, f"train_r{round}.log"))
|
trrun("07_train.py", extra, os.path.join(logdir, f"train_r{round}.log"))
|
||||||
|
|
||||||
def predict_pool(round, ckpt, added=None):
|
def predict_pool(round, ckpt, added=None):
|
||||||
out = os.path.join(pdir_root, f"round{round}")
|
out = os.path.join(pdir_root, f"round{round}")
|
||||||
|
|
@ -123,7 +123,7 @@ def main():
|
||||||
extra += f" --already {added}"
|
extra += f" --already {added}"
|
||||||
if args.no_tta:
|
if args.no_tta:
|
||||||
extra += " --no-tta"
|
extra += " --no-tta"
|
||||||
trrun("03_monai_pseudo_label.py", extra, os.path.join(logdir, f"pseudo_r{round}.log"))
|
trrun("06_pseudo_label.py", extra, os.path.join(logdir, f"pseudo_r{round}.log"))
|
||||||
return os.path.join(out, "accepted.jsonl")
|
return os.path.join(out, "accepted.jsonl")
|
||||||
|
|
||||||
def eval_test(round, ckpt):
|
def eval_test(round, ckpt):
|
||||||
|
|
@ -131,29 +131,15 @@ def main():
|
||||||
extra = f"--rows {test_f} --ckpt {ckpt} --out {out}"
|
extra = f"--rows {test_f} --ckpt {ckpt} --out {out}"
|
||||||
if args.no_tta:
|
if args.no_tta:
|
||||||
extra += " --no-tta"
|
extra += " --no-tta"
|
||||||
trrun("04_monai_eval_test.py", extra, os.path.join(logdir, f"eval_r{round}.log"))
|
trrun("08_eval.py", extra, os.path.join(logdir, f"eval_r{round}.log"))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def best(round):
|
def best(round):
|
||||||
return os.path.join(ROOT, "runs_monai", f"round{round}", "best.pt")
|
return os.path.join(ROOT, "runs_monai", f"round{round}", "best.pt")
|
||||||
|
|
||||||
# ---- round 0: baseline ----
|
def build_table():
|
||||||
rows0 = os.path.join(pdir_root, "round0_rows.jsonl")
|
# Rewrite the per-round table + plot; called after every round's eval so
|
||||||
build_rows(rows0, [])
|
# each round's performance is saved incrementally (survives interruptions).
|
||||||
train(0, rows0, args.base_epochs, args.base_lr, None, args.val_every)
|
|
||||||
accepted_f = [predict_pool(1, best(0))]
|
|
||||||
save_jsonl(load_jsonl(accepted_f[0]), added_f)
|
|
||||||
eval_test(0, best(0))
|
|
||||||
|
|
||||||
for k in range(1, args.rounds + 1):
|
|
||||||
rows_f = os.path.join(pdir_root, f"round{k}_rows.jsonl")
|
|
||||||
build_rows(rows_f, accepted_f, no_neg=args.no_neg_pseudo)
|
|
||||||
train(k, rows_f, args.round_epochs, args.round_lr, best(k - 1), args.val_every)
|
|
||||||
accepted_f.append(predict_pool(k + 1, best(k), added_f))
|
|
||||||
save_jsonl([r for f in accepted_f for r in load_jsonl(f)], added_f)
|
|
||||||
eval_test(k, best(k))
|
|
||||||
|
|
||||||
# ---- report (same layout as scripts/09 and scripts_nnu/06) ----
|
|
||||||
table = []
|
table = []
|
||||||
for k in range(args.rounds + 1):
|
for k in range(args.rounds + 1):
|
||||||
resf = os.path.join(results_dir, f"round{k}_test_monai.json")
|
resf = os.path.join(results_dir, f"round{k}_test_monai.json")
|
||||||
|
|
@ -169,6 +155,8 @@ def main():
|
||||||
"pos_vol_med_mm3": s["pos_vol_mm3"]["med"]})
|
"pos_vol_med_mm3": s["pos_vol_mm3"]["med"]})
|
||||||
table.append(row)
|
table.append(row)
|
||||||
save_jsonl(table, os.path.join(results_dir, "iterative_table_monai.jsonl"))
|
save_jsonl(table, os.path.join(results_dir, "iterative_table_monai.jsonl"))
|
||||||
|
if not table:
|
||||||
|
return table
|
||||||
print(json.dumps(table, indent=1))
|
print(json.dumps(table, indent=1))
|
||||||
try:
|
try:
|
||||||
import matplotlib
|
import matplotlib
|
||||||
|
|
@ -187,6 +175,28 @@ def main():
|
||||||
plt.savefig(os.path.join(results_dir, "iterative_dice_monai.png"), dpi=150)
|
plt.savefig(os.path.join(results_dir, "iterative_dice_monai.png"), dpi=150)
|
||||||
except Exception as e: # noqa
|
except Exception as e: # noqa
|
||||||
print("plot failed:", repr(e))
|
print("plot failed:", repr(e))
|
||||||
|
return table
|
||||||
|
|
||||||
|
# ---- round 0: baseline ----
|
||||||
|
rows0 = os.path.join(pdir_root, "round0_rows.jsonl")
|
||||||
|
build_rows(rows0, [])
|
||||||
|
train(0, rows0, args.base_epochs, args.base_lr, None, args.val_every)
|
||||||
|
accepted_f = [predict_pool(1, best(0))]
|
||||||
|
save_jsonl(load_jsonl(accepted_f[0]), added_f)
|
||||||
|
eval_test(0, best(0))
|
||||||
|
build_table()
|
||||||
|
|
||||||
|
for k in range(1, args.rounds + 1):
|
||||||
|
rows_f = os.path.join(pdir_root, f"round{k}_rows.jsonl")
|
||||||
|
build_rows(rows_f, accepted_f, no_neg=args.no_neg_pseudo)
|
||||||
|
train(k, rows_f, args.round_epochs, args.round_lr, best(k - 1), args.val_every)
|
||||||
|
accepted_f.append(predict_pool(k + 1, best(k), added_f))
|
||||||
|
save_jsonl([r for f in accepted_f for r in load_jsonl(f)], added_f)
|
||||||
|
eval_test(k, best(k))
|
||||||
|
build_table()
|
||||||
|
|
||||||
|
# ---- report (same layout as scripts/09 and scripts_nnu/09) ----
|
||||||
|
build_table()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ preprocessing; imagesTr entries are symlinks to the native volumes, labelsTr
|
||||||
entries are symlinks when already 0/1 on the image grid, else nearest-warped
|
entries are symlinks when already 0/1 on the image grid, else nearest-warped
|
||||||
+ binarized copies (label fix-up only).
|
+ binarized copies (label fix-up only).
|
||||||
|
|
||||||
Usage: python scripts_nnu/01_nnu_prepare_dataset.py --rows <rows.jsonl>
|
Usage: python scripts_nnu/01_prepare_dataset.py --rows <rows.jsonl>
|
||||||
"""
|
"""
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
subject-level splits_final.json: val = split_val cases, train = the rest
|
subject-level splits_final.json: val = split_val cases, train = the rest
|
||||||
(all base + pseudo cases), same 5-fold content so -f 0 is the one we use.
|
(all base + pseudo cases), same 5-fold content so -f 0 is the one we use.
|
||||||
|
|
||||||
Usage: python scripts_nnu/02_nnu_plan_preprocess.py --val data/manifests/split_val.jsonl [--npp 8]
|
Usage: python scripts_nnu/02_plan_preprocess.py --val data/manifests/split_val.jsonl [--npp 8]
|
||||||
"""
|
"""
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
|
|
@ -23,7 +23,7 @@ def main():
|
||||||
run(plan_preprocess_cmd(args.npp), log, env=nnu_env())
|
run(plan_preprocess_cmd(args.npp), log, env=nnu_env())
|
||||||
val_keys = [r["key"] for r in load_jsonl(args.val)]
|
val_keys = [r["key"] for r in load_jsonl(args.val)]
|
||||||
all_keys = dataset_case_keys()
|
all_keys = dataset_case_keys()
|
||||||
assert all_keys, "raw dataset is empty; run 01_nnu_prepare_dataset.py first"
|
assert all_keys, "raw dataset is empty; run 01_prepare_dataset.py first"
|
||||||
assert set(val_keys) <= set(all_keys), f"val keys not in dataset: {set(val_keys) - set(all_keys)}"
|
assert set(val_keys) <= set(all_keys), f"val keys not in dataset: {set(val_keys) - set(all_keys)}"
|
||||||
n_tr, n_va = write_splits(all_keys, val_keys)
|
n_tr, n_va = write_splits(all_keys, val_keys)
|
||||||
print(f"[nnu:plan] splits_final.json written to {preproc_ds()}: train={n_tr} val={n_va}", flush=True)
|
print(f"[nnu:plan] splits_final.json written to {preproc_ds()}: train={n_tr} val={n_va}", flush=True)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ Writes <out>/rows.jsonl, <out>/<key>_label.nii.gz (native grid), <out>/accepted.
|
||||||
<out>/summary.json.
|
<out>/summary.json.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
python scripts_nnu/04_nnu_pseudo_label.py --pool data/manifests/unlabeled_pool.jsonl \
|
python scripts_nnu/06_pseudo_label.py --pool data/manifests/unlabeled_pool.jsonl \
|
||||||
--out data/pseudo_nnu/round1 --gpus 3
|
--out data/pseudo_nnu/round1 --gpus 3
|
||||||
"""
|
"""
|
||||||
import argparse
|
import argparse
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Epochs / initial lr / warm-start checkpoint are passed to NTUHLPLTrainer via
|
||||||
NNU_PL_* env vars (see scripts_nnu/trainers/ntuh_pl_trainer.py).
|
NNU_PL_* env vars (see scripts_nnu/trainers/ntuh_pl_trainer.py).
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
nnUNetv2_train wrapper: python scripts_nnu/03_nnu_train.py \
|
nnUNetv2_train wrapper: python scripts_nnu/07_train.py \
|
||||||
--gpus 3 --epochs 250 --lr 1e-2 [--warmstart runs_nnu/round0/best_nnu.pth]
|
--gpus 3 --epochs 250 --lr 1e-2 [--warmstart runs_nnu/round0/best_nnu.pth]
|
||||||
"""
|
"""
|
||||||
import argparse
|
import argparse
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ grid, so we score directly there:
|
||||||
Ground truth is the row's native label.
|
Ground truth is the row's native label.
|
||||||
Writes: <out> json + <out>.json -> per_row jsonl alongside.
|
Writes: <out> json + <out>.json -> per_row jsonl alongside.
|
||||||
|
|
||||||
Usage: python scripts_nnu/05_nnu_eval_test.py --rows data/manifests/split_test.jsonl \
|
Usage: python scripts_nnu/08_eval.py --rows data/manifests/split_test.jsonl \
|
||||||
--out results/round0_test_nnu.json --gpus 3
|
--out results/round0_test_nnu.json --gpus 3
|
||||||
"""
|
"""
|
||||||
import argparse
|
import argparse
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ Round k: dataset grows with accepted pseudo-labels (pos + neg) from rounds 1..k;
|
||||||
unlabeled pool (gates identical to scripts/06) and evaluate the model on
|
unlabeled pool (gates identical to scripts/06) and evaluate the model on
|
||||||
the held-out patient-level test split.
|
the held-out patient-level test split.
|
||||||
|
|
||||||
Usage: python scripts_nnu/06_nnu_run_iterative.py [--rounds 4] [--gpus 3]
|
Usage: python scripts_nnu/09_run_iterative.py [--rounds 4] [--gpus 3]
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -71,16 +71,16 @@ def main():
|
||||||
def prepare(rows):
|
def prepare(rows):
|
||||||
f = os.path.join(pdir_root, "current_rows.jsonl")
|
f = os.path.join(pdir_root, "current_rows.jsonl")
|
||||||
save_jsonl(rows, f)
|
save_jsonl(rows, f)
|
||||||
py("01_nnu_prepare_dataset.py", f"--rows {f}")
|
py("01_prepare_dataset.py", f"--rows {f}")
|
||||||
|
|
||||||
def plan():
|
def plan():
|
||||||
py("02_nnu_plan_preprocess.py", f"--val {val_f}")
|
py("02_plan_preprocess.py", f"--val {val_f}")
|
||||||
|
|
||||||
def train(round, epochs, lr, warmstart):
|
def train(round, epochs, lr, warmstart):
|
||||||
extra = f"--gpus {args.gpus} --epochs {epochs} --lr {lr} --log {os.path.join(logdir, f'nnu_train_r{round}.log')}"
|
extra = f"--gpus {args.gpus} --epochs {epochs} --lr {lr} --log {os.path.join(logdir, f'nnu_train_r{round}.log')}"
|
||||||
if warmstart:
|
if warmstart:
|
||||||
extra += f" --warmstart {warmstart}"
|
extra += f" --warmstart {warmstart}"
|
||||||
py("03_nnu_train.py", extra)
|
py("07_train.py", extra)
|
||||||
dst = os.path.join(ROOT, "runs_nnu", f"round{round}", "best_nnu.pth")
|
dst = os.path.join(ROOT, "runs_nnu", f"round{round}", "best_nnu.pth")
|
||||||
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
||||||
shutil.copy2(best_ckpt(), dst)
|
shutil.copy2(best_ckpt(), dst)
|
||||||
|
|
@ -95,40 +95,19 @@ def main():
|
||||||
extra += " --no-tta"
|
extra += " --no-tta"
|
||||||
if added:
|
if added:
|
||||||
extra += f" --already {added_f}"
|
extra += f" --already {added_f}"
|
||||||
py("04_nnu_pseudo_label.py", extra)
|
py("06_pseudo_label.py", extra)
|
||||||
return os.path.join(pdir_root, f"round{round}", "accepted.jsonl")
|
return os.path.join(pdir_root, f"round{round}", "accepted.jsonl")
|
||||||
|
|
||||||
def eval_test(round, out_name):
|
def eval_test(round, out_name):
|
||||||
extra = (f"--rows {test_f} --out {results_dir}/{out_name} --gpus {args.gpus}")
|
extra = (f"--rows {test_f} --out {results_dir}/{out_name} --gpus {args.gpus}")
|
||||||
if args.no_tta:
|
if args.no_tta:
|
||||||
extra += " --no-tta"
|
extra += " --no-tta"
|
||||||
py("05_nnu_eval_test.py", extra)
|
py("08_eval.py", extra)
|
||||||
return os.path.join(results_dir, out_name)
|
return os.path.join(results_dir, out_name)
|
||||||
|
|
||||||
# ---- round 0: baseline ----
|
def build_table():
|
||||||
prepare(base)
|
# Rewrite the per-round table + plot; called after every round's eval so
|
||||||
plan()
|
# each round's performance is saved incrementally (survives interruptions).
|
||||||
train(0, args.base_epochs, args.base_lr, None)
|
|
||||||
accepted_f = [predict_pool(1, None)]
|
|
||||||
save_jsonl(load_jsonl(accepted_f[0]), added_f)
|
|
||||||
eval_test(0, "round0_test_nnu.json")
|
|
||||||
|
|
||||||
for k in range(1, args.rounds + 1):
|
|
||||||
rows = list(base)
|
|
||||||
for f in accepted_f:
|
|
||||||
for r in load_jsonl(f):
|
|
||||||
if args.no_neg_pseudo and r["role"] == "neg":
|
|
||||||
continue
|
|
||||||
if all(r["key"] != x["key"] for x in rows):
|
|
||||||
rows.append(r)
|
|
||||||
prepare(rows)
|
|
||||||
plan()
|
|
||||||
train(k, args.round_epochs, args.round_lr, os.path.join(ROOT, "runs_nnu", f"round{k-1}", "best_nnu.pth"))
|
|
||||||
accepted_f.append(predict_pool(k + 1, added_f))
|
|
||||||
save_jsonl([r for f in accepted_f for r in load_jsonl(f)], added_f)
|
|
||||||
eval_test(k, f"round{k}_test_nnu.json")
|
|
||||||
|
|
||||||
# ---- report (same layout as scripts/09) ----
|
|
||||||
table = []
|
table = []
|
||||||
for k in range(args.rounds + 1):
|
for k in range(args.rounds + 1):
|
||||||
resf = os.path.join(results_dir, f"round{k}_test_nnu.json")
|
resf = os.path.join(results_dir, f"round{k}_test_nnu.json")
|
||||||
|
|
@ -145,6 +124,8 @@ def main():
|
||||||
"pos_vol_med_mm3": s["pos_vol_mm3"]["med"]})
|
"pos_vol_med_mm3": s["pos_vol_mm3"]["med"]})
|
||||||
table.append(row)
|
table.append(row)
|
||||||
save_jsonl(table, os.path.join(results_dir, "iterative_table_nnu.jsonl"))
|
save_jsonl(table, os.path.join(results_dir, "iterative_table_nnu.jsonl"))
|
||||||
|
if not table:
|
||||||
|
return table
|
||||||
print(json.dumps(table, indent=1))
|
print(json.dumps(table, indent=1))
|
||||||
try:
|
try:
|
||||||
import matplotlib
|
import matplotlib
|
||||||
|
|
@ -163,6 +144,35 @@ def main():
|
||||||
plt.savefig(os.path.join(results_dir, "iterative_dice_nnu.png"), dpi=150)
|
plt.savefig(os.path.join(results_dir, "iterative_dice_nnu.png"), dpi=150)
|
||||||
except Exception as e: # noqa
|
except Exception as e: # noqa
|
||||||
print("plot failed:", repr(e))
|
print("plot failed:", repr(e))
|
||||||
|
return table
|
||||||
|
|
||||||
|
# ---- round 0: baseline ----
|
||||||
|
prepare(base)
|
||||||
|
plan()
|
||||||
|
train(0, args.base_epochs, args.base_lr, None)
|
||||||
|
accepted_f = [predict_pool(1, None)]
|
||||||
|
save_jsonl(load_jsonl(accepted_f[0]), added_f)
|
||||||
|
eval_test(0, "round0_test_nnu.json")
|
||||||
|
build_table()
|
||||||
|
|
||||||
|
for k in range(1, args.rounds + 1):
|
||||||
|
rows = list(base)
|
||||||
|
for f in accepted_f:
|
||||||
|
for r in load_jsonl(f):
|
||||||
|
if args.no_neg_pseudo and r["role"] == "neg":
|
||||||
|
continue
|
||||||
|
if all(r["key"] != x["key"] for x in rows):
|
||||||
|
rows.append(r)
|
||||||
|
prepare(rows)
|
||||||
|
plan()
|
||||||
|
train(k, args.round_epochs, args.round_lr, os.path.join(ROOT, "runs_nnu", f"round{k-1}", "best_nnu.pth"))
|
||||||
|
accepted_f.append(predict_pool(k + 1, added_f))
|
||||||
|
save_jsonl([r for f in accepted_f for r in load_jsonl(f)], added_f)
|
||||||
|
eval_test(k, f"round{k}_test_nnu.json")
|
||||||
|
build_table()
|
||||||
|
|
||||||
|
# ---- report (same layout as scripts/09) ----
|
||||||
|
build_table()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue