kinaar111's picture
Production: mystery shell + physics-audio from staging
d46bde8 verified
Raw
History Blame Contribute Delete
8.58 kB
# 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 ====================
@dataclass
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()