From 6576bc81e2b0c6fd97bcbba37bb747e9dccd8f12 Mon Sep 17 00:00:00 2001 From: Thomas Morris Date: Fri, 10 Nov 2023 09:55:52 -0800 Subject: [PATCH] better targeting method --- bloptools/bayesian/agent.py | 60 ++++++++------------- bloptools/bayesian/objectives.py | 4 +- bloptools/bayesian/transforms.py | 25 +++++++-- bloptools/tests/conftest.py | 8 +-- bloptools/tests/test_passive_dofs.py | 2 +- docs/source/tutorials/himmelblau.ipynb | 6 +-- docs/source/tutorials/hyperparameters.ipynb | 4 +- docs/source/tutorials/passive-dofs.ipynb | 4 +- docs/wip/constrained-himmelblau copy.ipynb | 2 +- 9 files changed, 57 insertions(+), 58 deletions(-) diff --git a/bloptools/bayesian/agent.py b/bloptools/bayesian/agent.py index 24f0aba..18f4215 100644 --- a/bloptools/bayesian/agent.py +++ b/bloptools/bayesian/agent.py @@ -49,6 +49,7 @@ def __init__( tolerate_acquisition_errors=False, sample_center_on_init=False, trigger_delay: float = 0, + train_every: int = 1, ): """ A Bayesian optimization agent. @@ -107,6 +108,7 @@ def __init__( self.tolerate_acquisition_errors = tolerate_acquisition_errors + self.train_every = train_every self.trigger_delay = trigger_delay self.sample_center_on_init = sample_center_on_init @@ -135,12 +137,17 @@ def tell(self, x: Mapping, y: Mapping, metadata=None, append=True, train_models= A dict of hyperparameters for the model to assume a priori. """ + # n_before_tell = len(self.table) + new_table = pd.DataFrame({**x, **y, **metadata} if metadata is not None else {**x, **y}) self.table = pd.concat([self.table, new_table]) if append else new_table self.table.index = np.arange(len(self.table)) + # n_after_tell = len(self.table) + # TODO: should be a check per model if len(self.table) > 2: + # if n_before_tell % self.train_every != n_after_tell % self.train_every: self._update_models(train=train_models, a_priori_hypers=hypers) def _update_models(self, train=True, skew_dims=None, a_priori_hypers=None): @@ -257,13 +264,14 @@ def ask(self, acq_func_identifier="qei", n=1, route=True, sequential=True): raw_samples=RAW_SAMPLES, # used for intialization heuristic ) - x = candidates.numpy().astype(float) + # this includes both RO and non-RO DOFs + candidates = candidates.numpy() active_dofs_are_read_only = np.array([dof.read_only for dof in self.dofs.subset(active=True)]) - acq_points = x[..., ~active_dofs_are_read_only] - read_only_X = x[..., active_dofs_are_read_only] - acq_func_meta["read_only_values"] = read_only_X + acq_points = candidates[..., ~active_dofs_are_read_only] + read_only_values = candidates[..., active_dofs_are_read_only] + acq_func_meta["read_only_values"] = read_only_values else: acqf_obj = None @@ -484,51 +492,25 @@ def get_objective_targets(self, i): return targets - # def _get_objective_targets(self, i): - # """Returns the targets (what we fit to) for an objective, given the objective index.""" - # obj = self.objectives[i] - - # targets = self.table.loc[:, obj.name].values.copy() - - # # check that targets values are inside acceptable values - # valid = (targets > obj.limits[0]) & (targets < obj.limits[1]) - # targets = np.where(valid, targets, np.nan) - - # # transform if needed - # if obj.log: - # targets = np.where(valid, np.log(targets), np.nan) - # if obj.target not in ["min", "max"]: - # targets = -np.square(np.log(targets) - np.log(obj.target)) - - # else: - # if obj.target not in ["min", "max"]: - # targets = -np.square(targets - obj.target) - - # if obj.target == "min": - # targets *= -1 - - # return targets - @property def scalarizing_transform(self): return ScalarizedPosteriorTransform(weights=self.objective_weights_torch, offset=0) @property def targeting_transform(self): - return TargetingPosteriorTransform(weights=self.objective_weights_torch, targets=self.pseudo_targets) + return TargetingPosteriorTransform(weights=self.objective_weights_torch, targets=self.objectives.targets) @property def pseudo_targets(self): """Targets for the posterior transform""" return torch.tensor( [ - 1.e32 - if obj.target == "max" - else -1.e32 - if obj.target == "min" - else np.log(obj.target) if obj.log - else obj.target - for i, obj in enumerate(self.objectives) + self.objectives_targets[..., i].max() + if t == "max" + else self.objectives_targets[..., i].min() + if t == "min" + else t + for i, t in enumerate(self.objectives.targets) ] ) @@ -677,7 +659,7 @@ def save_hypers(self, filepath): """Save the agent's fitted hyperparameters to a given filepath.""" hypers = self.hypers with h5py.File(filepath, "w") as f: - for model_key in hypers.names(): + for model_key in hypers.keys(): f.create_group(model_key) for param_key, param_value in hypers[model_key].items(): f[model_key].create_dataset(param_key, data=param_value) @@ -687,7 +669,7 @@ def load_hypers(filepath): """Load hyperparameters from a file.""" hypers = {} with h5py.File(filepath, "r") as f: - for model_key in f.names(): + for model_key in f.keys(): hypers[model_key] = OrderedDict() for param_key, param_value in f[model_key].items(): hypers[model_key][param_key] = torch.tensor(np.atleast_1d(param_value[()])) diff --git a/bloptools/bayesian/objectives.py b/bloptools/bayesian/objectives.py index 4eeb8da..7f15752 100644 --- a/bloptools/bayesian/objectives.py +++ b/bloptools/bayesian/objectives.py @@ -88,9 +88,9 @@ def __len__(self): @property def summary(self): summary = pd.DataFrame(columns=OBJ_FIELDS) - for i, obj in enumerate(self.objectives): + for obj in self.objectives: for col in summary.columns: - summary.loc[i, col] = getattr(obj, col) + summary.loc[obj.name, col] = getattr(obj, col) # convert dtypes for attr in ["log"]: diff --git a/bloptools/bayesian/transforms.py b/bloptools/bayesian/transforms.py index 2f02ff8..9f64d9c 100644 --- a/bloptools/bayesian/transforms.py +++ b/bloptools/bayesian/transforms.py @@ -20,12 +20,29 @@ def __init__(self, weights: Tensor, targets: Tensor) -> None: offset: An offset to be added to posterior mean. """ super().__init__() - self.register_buffer("targets", targets) + self.targets = targets self.register_buffer("weights", weights) - self.sampled_transform = lambda y: -(y - self.targets).abs() @ self.weights.unsqueeze(-1) - self.mean_transform = lambda mean, var: -(mean - self.targets).abs() @ self.weights.unsqueeze(-1) - self.variance_transform = lambda mean, var: -var @ self.weights.unsqueeze(-1) + def sampled_transform(self, y): + for i, target in enumerate(self.targets): + if target == "min": + y[..., i] = -y[..., i] + elif target != "max": + y[..., i] = -(y[..., i] - target).abs() + + return y @ self.weights.unsqueeze(-1) + + def mean_transform(self, mean, var): + for i, target in enumerate(self.targets): + if target == "min": + mean[..., i] = -mean[..., i] + elif target != "max": + mean[..., i] = -(mean[..., i] - target).abs() + + return mean @ self.weights.unsqueeze(-1) + + def variance_transform(self, mean, var): + return var @ self.weights.unsqueeze(-1) def evaluate(self, Y: Tensor) -> Tensor: r"""Evaluate the transform on a set of outcomes. diff --git a/bloptools/tests/conftest.py b/bloptools/tests/conftest.py index fa88e09..0fad97d 100644 --- a/bloptools/tests/conftest.py +++ b/bloptools/tests/conftest.py @@ -54,7 +54,7 @@ def agent(db): DOF(name="x2", limits=(-8.0, 8.0)), ] - objectives = [Objective(key="himmelblau", target="min")] + objectives = [Objective(name="himmelblau", target="min")] agent = Agent( dofs=dofs, @@ -78,8 +78,8 @@ def digestion(db, uid): products = db[uid].table() for index, entry in products.iterrows(): - products.loc[index, "ST1"] = functions.styblinski_tang(entry.x1, entry.x2) - products.loc[index, "ST2"] = functions.styblinski_tang(entry.x1, -entry.x2) + products.loc[index, "obj1"] = functions.himmelblau(entry.x1, entry.x2) + products.loc[index, "obj2"] = functions.himmelblau(entry.x2, entry.x1) return products @@ -88,7 +88,7 @@ def digestion(db, uid): DOF(name="x2", limits=(-5.0, 5.0)), ] - objectives = [Objective(key="ST1", target="min"), Objective(key="ST2", target="min")] + objectives = [Objective(name="obj1", target="min"), Objective(name="obj2", target="min")] agent = Agent( dofs=dofs, diff --git a/bloptools/tests/test_passive_dofs.py b/bloptools/tests/test_passive_dofs.py index 5b13b87..92cfda4 100644 --- a/bloptools/tests/test_passive_dofs.py +++ b/bloptools/tests/test_passive_dofs.py @@ -15,7 +15,7 @@ def test_passive_dofs(RE, db): ] objectives = [ - Objective(key="himmelblau", target="min"), + Objective(name="himmelblau", target="min"), ] agent = Agent( diff --git a/docs/source/tutorials/himmelblau.ipynb b/docs/source/tutorials/himmelblau.ipynb index 1ac5c6b..29ceb16 100644 --- a/docs/source/tutorials/himmelblau.ipynb +++ b/docs/source/tutorials/himmelblau.ipynb @@ -94,7 +94,7 @@ "source": [ "from bloptools.bayesian import Objective\n", "\n", - "objectives = [Objective(key=\"himmelblau\", target=\"min\")]" + "objectives = [Objective(name=\"himmelblau\", target=\"min\")]" ] }, { @@ -295,7 +295,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3.10.0 ('nsls2')", "language": "python", "name": "python3" }, @@ -313,7 +313,7 @@ }, "vscode": { "interpreter": { - "hash": "eee21ccc240bdddd7cf04478199e20f7257541e2f592ca1a4d34ebdc0225d742" + "hash": "857d19a2fd370900ed798add63a0e418d98c0c9c9169a1442a8e3b86b5805755" } } }, diff --git a/docs/source/tutorials/hyperparameters.ipynb b/docs/source/tutorials/hyperparameters.ipynb index 02f2a9f..5ec7645 100644 --- a/docs/source/tutorials/hyperparameters.ipynb +++ b/docs/source/tutorials/hyperparameters.ipynb @@ -80,7 +80,7 @@ "]\n", "\n", "objectives = [\n", - " Objective(key=\"booth\", target=\"min\"),\n", + " Objective(name=\"booth\", target=\"min\"),\n", "]\n", "\n", "\n", @@ -145,7 +145,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.0" + "version": "3.11.5" }, "vscode": { "interpreter": { diff --git a/docs/source/tutorials/passive-dofs.ipynb b/docs/source/tutorials/passive-dofs.ipynb index 3af9c40..71330a6 100644 --- a/docs/source/tutorials/passive-dofs.ipynb +++ b/docs/source/tutorials/passive-dofs.ipynb @@ -51,7 +51,7 @@ "]\n", "\n", "objectives = [\n", - " Objective(key=\"himmelblau\", target=\"min\"),\n", + " Objective(name=\"himmelblau\", target=\"min\"),\n", "]\n", "\n", "agent = Agent(\n", @@ -93,7 +93,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.0" + "version": "3.11.5" }, "vscode": { "interpreter": { diff --git a/docs/wip/constrained-himmelblau copy.ipynb b/docs/wip/constrained-himmelblau copy.ipynb index 4efff8a..10b3a2c 100644 --- a/docs/wip/constrained-himmelblau copy.ipynb +++ b/docs/wip/constrained-himmelblau copy.ipynb @@ -35,7 +35,7 @@ "X1, X2 = np.meshgrid(x1, x2)\n", "from bloptools.tasks import Task\n", "\n", - "task = Task(key=\"himmelblau\", kind=\"min\")\n", + "task = Task(name=\"himmelblau\", kind=\"min\")\n", "F = functions.constrained_himmelblau(X1, X2)\n", "\n", "plt.pcolormesh(x1, x2, F, norm=mpl.colors.LogNorm(), cmap=\"gnuplot\")\n",