[WIP] Support ScalarizedObjective children in MultiObjective#4951
Open
saitcakmak wants to merge 5 commits intomainfrom
Open
[WIP] Support ScalarizedObjective children in MultiObjective#4951saitcakmak wants to merge 5 commits intomainfrom
saitcakmak wants to merge 5 commits intomainfrom
Conversation
…onfig Change objective_weights from a 1D tensor to a 2D tensor where each row is one objective and each column is one modeled outcome. This eliminates the is_moo boolean field since MOO is inferred from shape[0] > 1, and adds outcome_mask as a cached property.
objective_weights is now always 2D (n_objectives, n_outcomes). Remove conditional reshaping and 1D fallbacks so callers that accidentally pass 1D tensors fail loudly.
Encode scalarization weights directly in the 2D objective_weights tensor rather than introducing a separate objective_weight_matrix field on TorchOptConfig. Standard MOO rows have one nonzero entry per row; scalarized rows have multiple nonzeros. Detection uses has_scalarized_objectives(W) = (W != 0).sum(dim=1).max() > 1. Key changes: - Remove NotImplementedError blocking ScalarizedObjective in MultiObjective - Update MultiObjective.metrics to flatten component metrics - Add has_scalarized_objectives() and _get_scalarized_mo_objective() helpers - Handle scalarized branches in infer_objective_thresholds, get_weighted_mc_objective_and_objective_thresholds, _objective_threshold_to_outcome_constraints, feasible_hypervolume - Guard objective threshold untransformation for scalarized case - Handle scalarized children in StandardizeY and Winsorize transforms - Add E2E test covering both-scalarized, mixed, and weighted cases
- stratified_standardize_y: Rescale ScalarizedObjective children's weights by strata-specific std. Update early-return guard. - power_transform_y: Raise NotImplementedError for ScalarizedObjective children whose metrics overlap with the power-transformed metrics. - objective_as_constraint: Create ScalarizedOutcomeConstraint for ScalarizedObjective children instead of accessing .metric.
6 tasks
|
@saitcakmak has imported this pull request. If you are a Meta employee, you can view this in D94424106. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds support for
ScalarizedObjectiveas children ofMultiObjective, enabling scalarized multi-objective optimization. For example, a user can optimize"0.5 * latency + 0.5 * cost, accuracy"where the first MOO objective is a scalarized combination of two metrics and the second is a plain metric.This PR depends on the
unify-objective-weight-matrixbranch (PR #4943) being merged first. It builds on the unified 2Dobjective_weightstensor to encode scalarization directly — no separateobjective_weight_matrixfield is needed.Design principle
objective_weightshas exactly one nonzero entry (e.g.,[1, 0, 0]).[0.5, 0.5, 0]).has_scalarized_objectives(W) = (W != 0).sum(dim=1).max() > 1.Changes by file
Core (3 files)
ax/core/objective.pyNotImplementedErrorblockingScalarizedObjectiveinMultiObjective. UpdateMultiObjective.metricsto flatten component metrics from scalarized children viao.metricsinstead ofo.metric.ax/core/optimization_config.pyobjectives_by_signaturedict to useobj.expressionas key forScalarizedObjectivechildren (since they have no single.metric).ax/core/tests/test_objective.pyMultiObjectiveacceptsScalarizedObjectivechildren and.metricsreturns all component metrics.Adapter (8 files)
ax/adapter/adapter_utils.pyextract_objective_weights: mark all component metrics of scalarized children as nonzero sosubset_modelkeeps them.feasible_hypervolume: compute scalarized objective values from component metrics when scalarized sub-objectives exist.ax/adapter/torch.py_untransform_objective_thresholdsto skip whenhas_scalarized_objectives()is true (scalarized thresholds don't map back to per-metric thresholds).ax/adapter/transforms/standardize_y.pyelif MultiObjectiveblock to rescaleScalarizedObjectivechildren's weights via_transform_scalarized_weights.ax/adapter/transforms/stratified_standardize_y.pyelif MultiObjectiveblock to rescale scalarized children's weights by strata-specific std.ax/adapter/transforms/power_transform_y.pyelif MultiObjectiveblock to raiseNotImplementedErrorif anyScalarizedObjectivechild's metrics overlap with power-transformed metrics (nonlinear transform corrupts linear scalarization).ax/adapter/transforms/objective_as_constraint.pyScalarizedObjectivechildren by creatingScalarizedOutcomeConstraintinstead of accessing.metric(which raisesNotImplementedError).ax/adapter/transforms/winsorize.pymetric_weightsand inferring direction asminimize XOR (weight < 0).ax/adapter/transforms/tests/test_standardize_y_transform.pyMultiObjective([ScalarizedObjective(...), Objective(...)])and verifying weights are rescaled by sigma.Generators (5 files)
ax/generators/torch/utils.pyhas_scalarized_objectives(W)helper. Add_get_scalarized_mo_objective(W)returningGenericMCMultiOutputObjectivethat computessamples @ W.T. Updateget_botorch_objective_and_transformMOO branch to use scalarized objective when detected. Fixextract_objectivesto handle 1D input.ax/generators/torch/botorch_moo_utils.pyget_weighted_mc_objective_and_objective_thresholds: add scalarized branch using_get_scalarized_mo_objectiveand computing scalarized thresholds viaclean_thresholds @ W.T.infer_objective_thresholds: add scalarized branch computingpred @ W.T, finding Pareto frontier, storing thresholds at first nonzero column positions.ax/generators/torch/botorch_modular/acquisition.pyax/generators/torch/botorch_modular/utils.py_objective_threshold_to_outcome_constraints: add scalarized branch where each row ofobjective_weightsbecomes one constraint rowA[i] = -W_i,b[i] = -scalarized_threshold_i.E2E test (1 file)
ax/api/tests/test_client.pytest_multi_objective_with_scalarized_sub_objectivescovering: both-scalarized, mixed (scalarized + plain), and weighted cases with Sobol + BoTorch trials.Remaining TODOs
infer_objective_thresholds. There's no user-facing API to specify thresholds for scalarized sub-objectives (what metric name/expression would theObjectiveThresholduse?). Thecheck_objective_thresholds_match_objectivesfunction usesobj.expressionas the key for scalarized sub-objectives, but user-specified thresholds on scalarized objectives haven't been tested end-to-end.get_pareto_frontier_and_configs(adapter_utils.py~line 648): This function callsextract_objective_weights(1D) instead ofextract_objective_weight_matrix(2D). It may need updating for scalarized MOO to correctly compute Pareto frontiers. Currently not tested with scalarized objectives.hypervolume(adapter_utils.py~line 907): Usesobj_w.nonzero().view(-1)which assumes 1D weights. May need updating for 2D weight matrix with scalarized rows.MultiObjectivewithScalarizedObjectivechildren may not serialize/deserialize correctly. The storage layer hasn't been tested with this new structure.stratified_standardize_y,power_transform_y,objective_as_constraint) pass existing tests but don't have dedicated tests for the scalarized MOO paths.pareto_frontier_evaluator(botorch_moo_utils.py): Usesobjective_weights.shapeassuming 1D in the zeros fallback (line ~159). May need updating.ax/service/andax/analysis/: These modules may accessMultiObjective.objectives[i].metricwhich would fail forScalarizedObjectivechildren.Test plan
python -m pytest ax/core/tests/test_objective.py(4 passed)python -m pytest ax/core/tests/test_optimization_config.py(13 passed)python -m pytest ax/adapter/transforms/tests/test_standardize_y_transform.py(5 passed)python -m pytest ax/adapter/transforms/tests/test_stratified_standardize_y_transform.py(8 passed)python -m pytest ax/adapter/transforms/tests/test_power_y_transform.py(3 passed)python -m pytest ax/adapter/transforms/tests/test_objective_as_constraint.py(27 passed)python -m pytest ax/generators/torch/tests/(186 passed)python -m pytest ax/adapter/tests/(144 passed)python -m pytest ax/api/tests/test_client.py::TestClient::test_multi_objective_with_scalarized_sub_objectives(1 passed)