| """ |
| xp2: effect of training-data quality on CombiLatent performance. |
| |
| For each percentile p in {5, 10, 15, ..., 95}: |
| 1. Build a dataset from the xp1 exhaustive_9_6 raw data by removing |
| the top-p% best schedules (lowest makespan values). |
| 2. Train the surrogate (n_embd=40, n_head=4, n_layer=2, all other |
| hyper-parameters identical to xp1's best config). |
| 3. Run Phase-2 latent optimisation (no metropolized filling). |
| |
| Run from: src/xp2/ |
| """ |
|
|
| import os |
| import sys |
| import json |
| import shutil |
| import math |
|
|
| import numpy as np |
| from tqdm import tqdm |
|
|
| |
| XP1_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../xp1")) |
| sys.path.insert(0, XP1_DIR) |
|
|
| from train import train |
| from recover_schedules import recover_schedules |
|
|
| |
| RAW_DATA_DIR = os.path.abspath(os.path.join(XP1_DIR, "outputs/exhaustive_9_6")) |
| XP2_OUT_ROOT = os.path.join(os.path.dirname(__file__), "outputs/exhaustive_9_6") |
|
|
| N_EMBD = 40 |
| N_HEAD = 4 |
| N_LAYER = 2 |
|
|
| PERCENTILES = list(range(5, 100, 5)) |
|
|
|
|
| |
|
|
| def process_dataset_pct(raw_data_dir, output_dir, top_pct, train_ratio=0.9, seed=97): |
| """ |
| Build a processed (train/val split) dataset from the raw exhaustive data |
| by removing the best `top_pct`% schedules (lowest makespan values). |
| |
| Files written to output_dir: |
| schedules_train.npy, makespans_train.npy |
| schedules_val.npy, makespans_val.npy |
| metadata.json, pfsp_instance.npy |
| neh_makespan.npy, cds_makespan.npy, palmer_makespan.npy, min_makespan.npy |
| """ |
| if os.path.exists(os.path.join(output_dir, "metadata.json")): |
| print(f" [process] already done β skipping {output_dir}") |
| return |
|
|
| os.makedirs(output_dir, exist_ok=True) |
|
|
| |
| schedules = np.load(os.path.join(raw_data_dir, "schedules.npy")) |
| makespans = np.load(os.path.join(raw_data_dir, "makespans.npy")) |
| with open(os.path.join(raw_data_dir, "metadata.json")) as f: |
| metadata = json.load(f) |
|
|
| ms = makespans[:, -1] |
|
|
| |
| n_total = len(ms) |
| n_remove = math.floor(top_pct / 100 * n_total) |
| sorted_idx = np.argsort(ms) |
| remove_set = set(sorted_idx[:n_remove].tolist()) |
| keep_mask = np.array([i not in remove_set for i in range(n_total)]) |
|
|
| schedules = schedules[keep_mask] |
| makespans = makespans[keep_mask] |
| ms = makespans[:, -1] |
|
|
| n_kept = len(schedules) |
| print(f" [process] top_pct={top_pct}%: removed {n_remove}, kept {n_kept} schedules") |
|
|
| |
| np.save(os.path.join(output_dir, "min_makespan.npy"), ms.min()) |
|
|
| |
| np.random.seed(seed) |
| unique_ms = np.unique(ms) |
| train_idx_list, val_idx_list = [], [] |
| for m in unique_ms: |
| group = np.where(ms == m)[0] |
| np.random.shuffle(group) |
| n_train = max(1, int(len(group) * train_ratio)) |
| if n_train >= len(group): |
| train_idx_list.append(group) |
| else: |
| train_idx_list.append(group[:n_train]) |
| val_idx_list.append(group[n_train:]) |
|
|
| train_indices = np.concatenate(train_idx_list) |
| val_indices = np.concatenate(val_idx_list) if val_idx_list else np.array([], dtype=int) |
|
|
| np.save(os.path.join(output_dir, "schedules_train.npy"), schedules[train_indices]) |
| np.save(os.path.join(output_dir, "makespans_train.npy"), makespans[train_indices]) |
| np.save(os.path.join(output_dir, "schedules_val.npy"), schedules[val_indices]) |
| np.save(os.path.join(output_dir, "makespans_val.npy"), makespans[val_indices]) |
|
|
| |
| split_meta = metadata.copy() |
| split_meta["nb_train_samples"] = len(train_indices) |
| split_meta["nb_val_samples"] = len(val_indices) |
| split_meta["data_source"] = raw_data_dir |
| split_meta["top_pct_removed"] = top_pct |
| split_meta["n_removed"] = int(n_remove) |
| split_meta["n_kept"] = int(n_kept) |
| with open(os.path.join(output_dir, "metadata.json"), "w") as f: |
| json.dump(split_meta, f, indent=4) |
|
|
| |
| for fname in ["pfsp_instance.npy", "neh_makespan.npy", |
| "cds_makespan.npy", "palmer_makespan.npy"]: |
| shutil.copy(os.path.join(raw_data_dir, fname), |
| os.path.join(output_dir, fname)) |
|
|
| print(f" [process] done β {n_train} train / {len(val_indices)} val samples") |
|
|
|
|
| |
|
|
| for pct in (outer:=tqdm(PERCENTILES, desc="Percentiles")): |
| outer.set_description(f"top_pct={pct}%") |
|
|
| data_dir = os.path.join(XP2_OUT_ROOT, f"top_pct_{pct}") |
| model_dir = os.path.join(data_dir, f"train_nEmbd{N_EMBD}_nHead{N_HEAD}_nLayer{N_LAYER}") |
| rs_dir = os.path.join(model_dir, "recover_no_mf") |
|
|
| |
| process_dataset_pct( |
| raw_data_dir=RAW_DATA_DIR, |
| output_dir=data_dir, |
| top_pct=pct, |
| ) |
|
|
| |
| train( |
| testing=False, |
| seed=97, |
| data_dir=data_dir, |
| n_embd=N_EMBD, |
| n_head=N_HEAD, |
| n_layer=N_LAYER, |
| intermediate_schedules=True, |
| dropout=0.0, |
| ff_width=4, |
| train_batch_size=512, |
| val_batch_size=256, |
| nb_epochs=5, |
| early_stopping_patience=15, |
| checkpoint_interval_ratio=1.0, |
| decay_lr=True, |
| lr_partitions_ratios=[0.66], |
| init_lr=1e-4, |
| max_lr=1e-3, |
| min_lr=5e-5, |
| lr_warmup_iters_ratio=0.1, |
| lr_decay_iters_ratio=0.95, |
| beta1=0.9, |
| beta2=0.95, |
| weight_decay=10.0, |
| grad_clip=1.0, |
| compile=False, |
| compile_mode="default", |
| save_only_last_checkpoint=True, |
| output_dir=model_dir, |
| ) |
|
|
| |
| recover_schedules( |
| testing=False, |
| seed=97, |
| init_mode="random", |
| n_optimization_steps=2000, |
| epsilon=0.01, |
| nb_sinkhorn_iters=40, |
| decay_ls=False, |
| ls_partitions_ratios=[0.66], |
| init_ls=2.0, |
| max_ls=10, |
| min_ls=0.1, |
| ls_warmup_iters_ratio=0.2, |
| ls_decay_iters_ratio=0.90, |
| decay_lr=True, |
| lr_partitions_ratios=[0.66], |
| init_lr=1e-2, |
| max_lr=1e-1, |
| min_lr=1e-4, |
| lr_warmup_iters_ratio=0.1, |
| lr_decay_iters_ratio=0.90, |
| checkpoint_interval=1, |
| data_dir=data_dir, |
| model_dir=model_dir, |
| output_dir=rs_dir, |
| apply_metropolized_filling=False, |
| mf_patience=10, |
| mf_fifo_size=50, |
| mf_penalty_strength=1.0, |
| mf_min_delta=0.05, |
| ) |
|
|