Closed vlad451101 closed 9 months ago
Just a minor update on my part. Exactly as I expected, the same error occurs in the case of simple HVKG.
Any help or guidance on this matter will be highly appreciated!
Below you can find the repro code for the qHypervolumeKnowledgeGradient
case. Training data are listed in the original post.
## Libraries
########################################################################################################################
import sys
import warnings
import torch
import gpytorch
from botorch.exceptions import BadInitialCandidatesWarning, InputDataWarning
from botorch.utils.transforms import normalize, unnormalize
from botorch.models.transforms.outcome import Standardize
from gpytorch.mlls.sum_marginal_log_likelihood import SumMarginalLogLikelihood
from botorch.models.model_list_gp_regression import ModelListGP
from botorch.acquisition.multi_objective.objective import IdentityMCMultiOutputObjective
from botorch import fit_gpytorch_mll
from botorch.models.gp_regression import SingleTaskGP
from botorch.acquisition.cost_aware import InverseCostWeightedUtility
from botorch.acquisition.multi_objective.hypervolume_knowledge_gradient import (
_get_hv_value_function,
qHypervolumeKnowledgeGradient,
)
from botorch.optim.optimize import optimize_acqf
from botorch.models.cost import FixedCostModel
import pandas as pd
from copy import deepcopy
import botorch
warnings.filterwarnings("ignore", category=BadInitialCandidatesWarning)
warnings.filterwarnings("ignore", category=InputDataWarning)
warnings.filterwarnings("ignore", category=RuntimeWarning)
warnings.filterwarnings("ignore", category=FutureWarning)
########################################################################################################################
## All functions
########################################################################################################################
def changeObjective(tensor, objectives):
# Copy tensor
newTensor = deepcopy(tensor)
# Select columns
for col,objective in enumerate(objectives):
# Change absolute value if objective is minimized
if objective == 'Minimize':
# Extract the selected column
column = newTensor[:, col]
# Negate the values in the column
negated_column = -column
# Update the tensor with the negated column
newTensor[:, col] = negated_column
return newTensor
def initialize_model(train_x_list, train_obj_list, bounds):
# define models for objective and constraint
train_x_list = [normalize(train_x, bounds) for train_x in train_x_list]
# Create models
models = []
for x_data, y_data in zip(train_x_list, train_obj_list):
model = SingleTaskGP(x_data, y_data,
covar_module=gpytorch.kernels.ScaleKernel(gpytorch.kernels.MaternKernel(nu=2.5, ard_num_dims=len(x_data[0]))),
likelihood=gpytorch.likelihoods.GaussianLikelihood(noise_constraint=gpytorch.constraints.Positive()),
outcome_transform=Standardize(m=1))
# Append models
models.append(model)
# Setup model list
model = ModelListGP(*models)
mll = SumMarginalLogLikelihood(model.likelihood, model)
return mll, model
def get_current_value(model, ref_point, bounds,):
"""Helper to get the hypervolume of the current hypervolume
maximizing set.
"""
curr_val_acqf = _get_hv_value_function(
model=model,
ref_point=ref_point,
use_posterior_mean=True,
objective=IdentityMCMultiOutputObjective(outcomes=[0, 1, 2, 3]),
)
_, current_value = optimize_acqf(
acq_function=curr_val_acqf,
bounds=bounds,
q=NUM_PARETO,
num_restarts=20,
raw_samples=1024,
return_best_only=True,
options={"batch_limit": 6},
)
return current_value
def optimize_HVKG_and_get_obs_decoupled(model, ref_points):
"""Utility to initialize and optimize HVKG."""
cost_aware_utility = InverseCostWeightedUtility(cost_model=cost_model)
current_value = get_current_value(
model=model,
ref_point=torch.tensor(ref_points, **tkwargs),
bounds=standard_bounds,
)
acq_func = qHypervolumeKnowledgeGradient(
model=model,
ref_point=torch.tensor(ref_points, **tkwargs), # use known reference point
num_fantasies=NUM_FANTASIES,
num_pareto=NUM_PARETO,
current_value=current_value,
cost_aware_utility=cost_aware_utility,
objective=IdentityMCMultiOutputObjective(outcomes=[0, 1, 2, 3]),
)
# optimize acquisition functions and get new observations
objective_vals = []
objective_candidates = []
for objective_idx in objective_indices:
# set evaluation index to only condition on one objective
# this could be multiple objectives
X_evaluation_mask = torch.zeros(BATCH_SIZE, len(objective_indices), dtype=torch.bool, device=standard_bounds.device,)
for i in range(BATCH_SIZE):
X_evaluation_mask[i, objective_idx] = 1
acq_func.X_evaluation_mask = X_evaluation_mask
candidates, vals = optimize_acqf(
acq_function=acq_func,
num_restarts=NUM_HVKG_RESTARTS,
raw_samples=RAW_SAMPLES,
bounds=standard_bounds,
q=BATCH_SIZE,
sequential=False,
options={"batch_limit": 5},
)
objective_vals.append(vals.view(-1))
objective_candidates.append(candidates)
best_objective_index = torch.cat(objective_vals, dim=-1).argmax().item()
eval_objective_indices = [best_objective_index]
candidates = objective_candidates[best_objective_index]
vals = objective_vals[best_objective_index]
# observe new values
new_x = unnormalize(candidates.detach(), bounds=bounds)
return new_x, eval_objective_indices
########################################################################################################################
## Inputs and others
########################################################################################################################
# Path to Excel file
pathResults = r'training_data.txt'
# Selected input variables for Bayesian optimization loop
inputs = ['input1', 'input2', 'input3', 'input4']
# Selected input variables for Bayesian optimization loop
outputs = ['output1', 'output2', 'output3', 'output4', 'output5']
# define the cost model
objective_costs = {0 : 1.0, 1 : 2.0, 2 : 2.0, 3 : 3.0, 4 : 1.0}
# objective_costs = {0 : 1.0, 1 : 2.0, 2 : 2.0, 3 : 3.0}
# Dimensions for MOMF optimization
dim_x = len(inputs) # Input Dimension for MOMF optimization
dim_y = len(outputs) # Output Dimension for MO-only optimization
# Output optimization objectives
objectives = ['Maximize', 'Maximize', 'Maximize', 'Minimize', 'None']
# Kwargs for torch values
tkwargs = {
"dtype": torch.double,
"device": torch.device("cuda" if torch.cuda.is_available() else "cpu"),
}
# Selected bounds for variables
bounds = torch.tensor([[120, 9, 0.6, 23.333333333],
[240, 21, 2.7, 46.666666667]], **tkwargs) # For MO only
# Bounds for MOMF optimization
standard_bounds = torch.tensor([[0.0] * dim_x, [1.0] * dim_x], **tkwargs)
# Selected reference points for objectives
ref_points = [4.2, 68, 0.43, 0]
# Bayesian optimization loop variables
INIT_SIZE = 100 # Number of randomly generated paralel calculactions for initial training data
BATCH_SIZE = 19 # Number of parallel calculations for Bayesian optimization loop
NUM_RESTARTS = 10 # Number of restarts of acquisition optimizer
RAW_SAMPLES = 256 # Number of samples for acquisition optimizer
MC_SAMPLES = 128 # Number of samples for monte-carlo samplerer
NUM_PARETO = 10
NUM_FANTASIES = 8
NUM_HVKG_RESTARTS = 1
########################################################################################################################
## Optimization loop
########################################################################################################################
# Define the cost model
objective_indices = list(objective_costs.keys())
objective_costs = {int(k): v for k, v in objective_costs.items()}
objective_costs_t = torch.tensor([objective_costs[k] for k in sorted(objective_costs.keys())], **tkwargs)
cost_model = FixedCostModel(fixed_cost=objective_costs_t)
# Get all data
data = pd.read_csv(pathResults, sep=' ', header=0)
# Get together input data
train_x = torch.tensor(data[inputs].values, **tkwargs)
# Calls the objective wrapper to load train_obj
train_obj = torch.tensor(data[outputs].values, **tkwargs)
# Change absolute value of training data - for optimization purposes
train_obj_C = changeObjective(train_obj, objectives)
# Create lists for input and output data
train_obj_C_list = list(train_obj_C.split(1, dim=-1))
train_x_list = [train_x] * len(train_obj_C_list)
# Calculate total cost of input data
total_cost = 0
cost_hvkg = cost_model(train_x).sum(dim=-1)
total_cost += cost_hvkg.sum().item()
# Initilize GP models on random data
mll, model = initialize_model(train_x_list, train_obj_C_list, bounds)
# Fit the model
fit_gpytorch_mll(mll)
# generate candidates
new_x_hvkg, eval_objective_indices_hvkg = optimize_HVKG_and_get_obs_decoupled(model, ref_points)
print(new_x_hvkg)
print(eval_objective_indices_hvkg)
Stack trace/error message
Traceback (most recent call last):
File "C:\Users\Uziv\Desktop\Botorch_algorithms\MC_BO_HVKG\HVKG_test_case.py", line 221, in <module>
new_x_hvkg, eval_objective_indices_hvkg = optimize_HVKG_and_get_obs_decoupled(model, ref_points)
File "C:\Users\Uziv\Desktop\Botorch_algorithms\MC_BO_HVKG\HVKG_test_case.py", line 119, in optimize_HVKG_and_get_obs_decoupled
candidates, vals = optimize_acqf(
File "C:\Users\Uziv\AppData\Roaming\Python\Python310\site-packages\botorch\optim\optimize.py", line 563, in optimize_acqf
return _optimize_acqf(opt_acqf_inputs)
File "C:\Users\Uziv\AppData\Roaming\Python\Python310\site-packages\botorch\optim\optimize.py", line 584, in _optimize_acqf
return _optimize_acqf_batch(opt_inputs=opt_inputs)
File "C:\Users\Uziv\AppData\Roaming\Python\Python310\site-packages\botorch\optim\optimize.py", line 274, in _optimize_acqf_batch
batch_initial_conditions = opt_inputs.get_ic_generator()(
File "C:\Users\Uziv\AppData\Roaming\Python\Python310\site-packages\botorch\optim\initializers.py", line 736, in gen_one_shot_hvkg_initial_conditions
ics = gen_batch_initial_conditions(
File "C:\Users\Uziv\AppData\Roaming\Python\Python310\site-packages\botorch\optim\initializers.py", line 417, in gen_batch_initial_conditions
Y_rnd_curr = acq_function(
File "C:\Users\Uziv\AppData\Roaming\Python\Python310\site-packages\torch\nn\modules\module.py", line 1501, in _call_impl
return forward_call(*args, **kwargs)
File "C:\Users\Uziv\AppData\Roaming\Python\Python310\site-packages\botorch\utils\transforms.py", line 259, in decorated
output = method(acqf, X, *args, **kwargs)
File "C:\Users\Uziv\AppData\Roaming\Python\Python310\site-packages\botorch\acquisition\multi_objective\hypervolume_knowledge_gradient.py", line 231, in forward
fantasy_model = self.model.fantasize(
File "C:\Users\Uziv\AppData\Roaming\Python\Python310\site-packages\botorch\models\model.py", line 661, in fantasize
sampler_i = sampler.samplers[i]
File "C:\Users\Uziv\AppData\Roaming\Python\Python310\site-packages\torch\nn\modules\container.py", line 295, in __getitem__
return self._modules[self._get_abs_string_index(idx)]
File "C:\Users\Uziv\AppData\Roaming\Python\Python310\site-packages\torch\nn\modules\container.py", line 285, in _get_abs_string_index
raise IndexError('index {} is out of range'.format(idx))
IndexError: index 4 is out of range
@sdaulton can you help take a look at this?
Hi @vlad451101, This is an edge case we haven't considered (where the objectives are only a subset of the outcomes. Here is a fix for that: https://github.com/pytorch/botorch/pull/2160. Let me know if you have any issues.
Currently, fantasies are generated for all outcomes, even if only a subset of the outcomes are objectives. Hence, you just want to optimize a subset of the outcomes, it would be more efficient to fit the model to only a subset of the outcomes.
Regarding constraints, it worth noting that KG-based methods typically are not used on constrained problems. There is not much work in the literature on KG with constraints.
Hi, @sdaulton Thank you very much, the fix is working very well.
The reason why I want to use only part of the outputs as targets for optimization is that it is much more convenient for me. Normally I work with surrogate models that are even larger, but only a part of them are intended as objectives for optimization.
I was also thinking of fitting the model with only selected objectives. However, my original code is very complex and I wanted to avoid extra unnecessary lines of code. Although, in this case, I think it may not be too challenging to implement it.
For HVKG, I didn't know that it is not used for constrained problems. But thank you for this information.
🐛 Bug
I have a constrained 4 input and 5 output multi-objective optimization problem that I have successfully applied to the constrained qNEHVI BO loop. Here I was able to define the first 4 outputs as the main objectives of the optimization and the last one as a constrained-based surrogate model.
I wanted to try the same optimization by using the MF-HVKG. I would like to try both constrained and unconstrained optimization of MF-HVKG. However, for some reason, I can't apply the optimization weights for the first 4 objectives in the acquisition function, and, in the case of constrained optimization, define the last output objective as the constraint of the BO optimization. Here is a little snippet code of what I mean by this using it with qNEHVI on my test case:
Unfortunately, if I setup the
objective
in theqMultiFidelityHypervolumeKnowledgeGradient
acquisition function, I get anIndexError
. I assume that the same problem may occur with theqHypervolumeKnowledgeGradient
, but I have not tried it yet.My question is whether the MF-HVKG is designed for applying the optimization weights on the selected objectives and output-based constraints like in qNEHVI. If so, am I doing something wrong or is this a bug?
To reproduce
Below you should find a repro code for unconstrained optimization. The following text file training_data.txt contains all the training data.
Code snippet to reproduce
Stack trace/error message
Expected Behavior
I should be able to apply optimization weigths for selected output parameters using
objective
argument in theqMultiFidelityHypervolumeKnowledgeGradient
acquisition function and additionally define constraint for acquisition function.System information
Please complete the following information:
Additional context
Add any other context about the problem here.