Spaces:
Sleeping
Sleeping
| # tuner.py | |
| # Multi-Fidelity BoTorch Bayesian Optimization (MFKG) tuner for physics_audio.py | |
| # Updated: added tuning for inharm_l2_lambda & inharm_ceiling_threshold (now 8D space), | |
| # improved cost model (exact 1:30 ratio), increased final exploitation candidates, | |
| # added checkpointing of BO history and best config. | |
| import torch | |
| from botorch.models.gp_regression_fidelity import SingleTaskMultiFidelityGP | |
| from botorch.fit import fit_gpytorch_mll | |
| from botorch.acquisition.knowledge_gradient import qMultiFidelityKnowledgeGradient | |
| from botorch.acquisition.fixed_feature import FixedFeatureAcquisitionFunction | |
| from botorch.acquisition import PosteriorMean | |
| from botorch.acquisition.cost_aware import InverseCostWeightedUtility | |
| from botorch.models.cost import AffineFidelityCostModel | |
| from botorch.acquisition.utils import project_to_target_fidelity | |
| from botorch.optim import optimize_acqf | |
| from botorch.utils.sampling import draw_sobol_samples | |
| from gpytorch.mlls import ExactMarginalLogLikelihood | |
| from dataclasses import dataclass | |
| import numpy as np | |
| import time | |
| from pathlib import Path | |
| import importlib.util | |
| import multiprocessing | |
| import os | |
| import pickle # Added for checkpointing | |
| # ==================== Hyperparameter Space ==================== | |
| class HyperConfig: | |
| coupling_prior_lambda: float = 0.10 | |
| speed_uniform_lambda: float = 10.0 | |
| damping_prior_lambda: float = 2.0 | |
| inharm_ceiling_lambda: float = 200.0 | |
| JUMP_SIGMA_MIN: float = 4.0 | |
| JUMP_SIGMA_MAX: float = 12.0 | |
| inharm_l2_lambda: float = 20.0 # New: now tuned | |
| inharm_ceiling_threshold: float = 0.002 # New: now tuned | |
| HYPER_FIELDS = list(HyperConfig.__dataclass_fields__.keys()) | |
| HYPER_DIM = len(HYPER_FIELDS) | |
| HYPER_BOUNDS = [ | |
| (0.01, 1.0), # coupling_prior_lambda | |
| (5.0, 20.0), # speed_uniform_lambda | |
| (0.5, 5.0), # damping_prior_lambda | |
| (100.0, 500.0), # inharm_ceiling_lambda | |
| (2.0, 8.0), # JUMP_SIGMA_MIN | |
| (8.0, 16.0), # JUMP_SIGMA_MAX | |
| (5.0, 50.0), # inharm_l2_lambda | |
| (0.0005, 0.005), # inharm_ceiling_threshold | |
| ] | |
| DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| # Multi-fidelity settings | |
| MAX_NUM_SEEDS = 30 | |
| NOISE_AMP = 0.06 | |
| FIDELITY_DIM_INDEX = HYPER_DIM | |
| TARGET_FIDELITY = 1.0 | |
| FIDELITY_BOUNDS = torch.tensor([[1.0 / MAX_NUM_SEEDS], [1.0]], device=DEVICE) # Exact 1–30 seeds | |
| hyper_bounds_tensor = torch.tensor(HYPER_BOUNDS, device=DEVICE).t() | |
| BOUNDS = torch.cat([hyper_bounds_tensor, FIDELITY_BOUNDS], dim=1) | |
| # BO settings | |
| INITIAL_POINTS = 30 | |
| MAX_BO_ITER = 80 | |
| MAX_PARALLEL = 60 | |
| NUM_FANTASIES = 64 | |
| TARGET_FIDELITIES_DICT = {FIDELITY_DIM_INDEX: TARGET_FIDELITY} | |
| # ==================== Module Loading ==================== | |
| def load_optimizer(): | |
| opt_path = Path(__file__).parent / "physics_audio.py" | |
| if not opt_path.exists(): | |
| raise FileNotFoundError(f"Optimizer not found at {opt_path}") | |
| spec = importlib.util.spec_from_file_location("optimizer", opt_path) | |
| module = importlib.util.module_from_spec(spec) | |
| spec.loader.exec_module(module) | |
| return module | |
| # ==================== Config Utilities ==================== | |
| def config_from_tensor(tensor: torch.Tensor) -> HyperConfig: | |
| values = tensor.cpu().numpy() | |
| return HyperConfig(**dict(zip(HYPER_FIELDS, values))) | |
| def config_to_dict(config: HyperConfig) -> dict: | |
| return {field: getattr(config, field) for field in HYPER_FIELDS} | |
| # ==================== Parallel Evaluation ==================== | |
| def worker_eval(seed: int, config_dict: dict, num_seeds_eval: int): | |
| opt_module = load_optimizer() | |
| config = HyperConfig(**config_dict) | |
| for field in HYPER_FIELDS: | |
| if hasattr(opt_module, field): | |
| setattr(opt_module, field, getattr(config, field)) | |
| res = opt_module.run_single_seed( | |
| seed=seed, | |
| noise_amp=NOISE_AMP, | |
| force_punctuated=True | |
| ) | |
| return res['strict_success'] | |
| def evaluate_config(candidate: torch.Tensor) -> float: | |
| hyper_tensor = candidate[:HYPER_DIM] | |
| s = candidate[FIDELITY_DIM_INDEX].item() | |
| config = config_from_tensor(hyper_tensor) | |
| num_seeds_eval = max(1, min(MAX_NUM_SEEDS, int(round(s * MAX_NUM_SEEDS)))) | |
| print(f"\nEvaluating fidelity s={s:.3f} → {num_seeds_eval} seeds | {config}") | |
| config_dict = config_to_dict(config) | |
| seed_offset = 54321 | |
| effective_parallel = min(MAX_PARALLEL, num_seeds_eval) | |
| start_time = time.time() | |
| if effective_parallel > 1: | |
| with multiprocessing.Pool(processes=effective_parallel) as pool: | |
| args = [(i + seed_offset, config_dict, num_seeds_eval) for i in range(num_seeds_eval)] | |
| results = pool.starmap(worker_eval, args) | |
| else: | |
| results = [worker_eval(i + seed_offset, config_dict, num_seeds_eval) for i in range(num_seeds_eval)] | |
| strict_rate = np.mean(results) | |
| eval_time = time.time() - start_time | |
| print(f"Result: {strict_rate:.3f} ({int(strict_rate * num_seeds_eval)}/{num_seeds_eval}) | Time: {eval_time:.1f}s\n") | |
| return strict_rate | |
| # ==================== BoTorch Optimization ==================== | |
| def run_botorch_tuning(): | |
| train_x = draw_sobol_samples(bounds=BOUNDS, n=INITIAL_POINTS, q=1).squeeze(1) | |
| train_y = torch.tensor([evaluate_config(x) for x in train_x], device=DEVICE, dtype=torch.float64).unsqueeze(-1) | |
| best_y = train_y.max().item() | |
| print(f"Initial best: {best_y:.3f}") | |
| project_func = lambda X: project_to_target_fidelity(X=X, target_fidelities=TARGET_FIDELITIES_DICT) | |
| # Improved cost model: exact proportionality to number of seeds (1 seed ≈ cost 1, 30 seeds ≈ cost 30) | |
| cost_model = AffineFidelityCostModel( | |
| fidelity_weights={FIDELITY_DIM_INDEX: float(MAX_NUM_SEEDS)}, | |
| fixed_cost=0.0 | |
| ) | |
| cost_aware_utility = InverseCostWeightedUtility(cost_model=cost_model) | |
| for iteration in range(MAX_BO_ITER): | |
| print(f"\n=== Iteration {iteration + 1}/{MAX_BO_ITER} ===") | |
| model = SingleTaskMultiFidelityGP(train_x, train_y, data_fidelity_dim=FIDELITY_DIM_INDEX) | |
| fit_gpytorch_mll(ExactMarginalLogLikelihood(model.likelihood, model)) | |
| current_value = FixedFeatureAcquisitionFunction( | |
| PosteriorMean(model), d=BOUNDS.shape[1], fixed_features=TARGET_FIDELITIES_DICT | |
| )(train_x.unsqueeze(-2)).max().item() | |
| mfkg = qMultiFidelityKnowledgeGradient( | |
| model=model, | |
| num_fantasies=NUM_FANTASIES, | |
| cost_aware_utility=cost_aware_utility, | |
| project=project_func, | |
| current_value=torch.tensor(current_value, device=DEVICE, dtype=torch.float64), | |
| ) | |
| candidate, _ = optimize_acqf(mfkg, bounds=BOUNDS, q=1, num_restarts=20, raw_samples=512) | |
| new_y = evaluate_config(candidate.squeeze(0)) | |
| new_y_tensor = torch.tensor([[new_y]], device=DEVICE, dtype=torch.float64) | |
| train_x = torch.cat([train_x, candidate]) | |
| train_y = torch.cat([train_y, new_y_tensor]) | |
| if new_y > best_y: | |
| best_y = new_y | |
| print(f"*** NEW BEST: {best_y:.3f} ***") | |
| # Final exploitation: find predicted best at full fidelity | |
| final_model = SingleTaskMultiFidelityGP(train_x, train_y, data_fidelity_dim=FIDELITY_DIM_INDEX) | |
| fit_gpytorch_mll(ExactMarginalLogLikelihood(final_model.likelihood, final_model)) | |
| best_candidate, _ = optimize_acqf( | |
| FixedFeatureAcquisitionFunction(PosteriorMean(final_model), d=BOUNDS.shape[1], fixed_features=TARGET_FIDELITIES_DICT), | |
| bounds=BOUNDS[:, :HYPER_DIM], | |
| q=1, | |
| num_restarts=60, # Increased for better coverage | |
| raw_samples=2048 # Increased for better coverage | |
| ) | |
| best_config = config_from_tensor(best_candidate.squeeze(0)) | |
| # Checkpointing | |
| os.makedirs('checkpoints', exist_ok=True) | |
| torch.save({ | |
| 'train_x': train_x.cpu(), | |
| 'train_y': train_y.cpu(), | |
| 'best_observed_y': best_y | |
| }, 'checkpoints/mfkg_bo_history.pt') | |
| with open('checkpoints/mfkg_best_hyperconfig.pkl', 'wb') as f: | |
| pickle.dump(config_to_dict(best_config), f) | |
| print("\nSaved BO history to checkpoints/mfkg_bo_history.pt") | |
| print("Saved best recommended config to checkpoints/mfkg_best_hyperconfig.pkl") | |
| print("\n=== TUNING COMPLETE ===") | |
| print(f"Best observed strict rate: {best_y:.3f}") | |
| print("Recommended hyperparameters (paste into physics_audio.py):") | |
| for field in HYPER_FIELDS: | |
| print(f" {field} = {getattr(best_config, field):.6f}") | |
| if __name__ == "__main__": | |
| multiprocessing.set_start_method('spawn', force=True) | |
| run_botorch_tuning() |