Coverage for mlos_bench/mlos_bench/optimizers/manual_optimizer.py: 95%
22 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-01 00:52 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-01 00:52 +0000
1#
2# Copyright (c) Microsoft Corporation.
3# Licensed under the MIT License.
4#
5"""
6Manual config suggester (Optimizer) for mlos_bench that proposes an explicit sequence of
7configurations.
9This is useful for testing and validation, as it allows you to run a sequence of
10configurations in a cyclic fashion.
12Examples
13--------
14>>> # Load tunables from a JSON string.
15>>> # Note: normally these would be automatically loaded from the Environment(s)'s
16>>> # `include_tunables` config parameter.
17>>> #
18>>> import json5 as json
19>>> from mlos_bench.environments.status import Status
20>>> from mlos_bench.services.config_persistence import ConfigPersistenceService
21>>> service = ConfigPersistenceService()
22>>> json_config = '''
23... {
24... "group_1": {
25... "cost": 1,
26... "params": {
27... "colors": {
28... "type": "categorical",
29... "values": ["red", "blue", "green"],
30... "default": "green",
31... },
32... "int_param": {
33... "type": "int",
34... "range": [1, 3],
35... "default": 2,
36... },
37... "float_param": {
38... "type": "float",
39... "range": [0, 1],
40... "default": 0.5,
41... // Quantize the range into 3 bins
42... "quantization_bins": 3,
43... }
44... }
45... }
46... }
47... '''
48>>> tunables = service.load_tunables(jsons=[json_config])
49>>> # Check the defaults:
50>>> tunables.get_param_values()
51{'colors': 'green', 'int_param': 2, 'float_param': 0.5}
53>>> # Now create a ManualOptimizer from a JSON config string.
54>>> optimizer_json_config = '''
55... {
56... "class": "mlos_bench.optimizers.manual_optimizer.ManualOptimizer",
57... "description": "ManualOptimizer",
58... "config": {
59... "max_cycles": 3,
60... "tunable_values_cycle": [
61... {"colors": "red", "int_param": 1, "float_param": 0.0},
62... {"colors": "blue", "int_param": 3, "float_param": 1.0},
63... // special case: {} - represents the defaults, without
64... // having to copy them from the tunables JSON
65... // (which is presumably specified elsewhere)
66... {},
67... ],
68... }
69... }
70... '''
71>>> config = json.loads(optimizer_json_config)
72>>> optimizer = service.build_optimizer(
73... tunables=tunables,
74... service=service,
75... config=config,
76... )
77>>> # Run the optimizer.
78>>> # Note that the cycle will repeat 3 times, as specified in the config.
79>>> while optimizer.not_converged():
80... suggestion = optimizer.suggest()
81... print(suggestion.get_param_values())
82{'colors': 'red', 'int_param': 1, 'float_param': 0.0}
83{'colors': 'blue', 'int_param': 3, 'float_param': 1.0}
84{'colors': 'green', 'int_param': 2, 'float_param': 0.5}
85{'colors': 'red', 'int_param': 1, 'float_param': 0.0}
86{'colors': 'blue', 'int_param': 3, 'float_param': 1.0}
87{'colors': 'green', 'int_param': 2, 'float_param': 0.5}
88{'colors': 'red', 'int_param': 1, 'float_param': 0.0}
89{'colors': 'blue', 'int_param': 3, 'float_param': 1.0}
90{'colors': 'green', 'int_param': 2, 'float_param': 0.5}
91"""
93import logging
95from mlos_bench.optimizers.mock_optimizer import MockOptimizer
96from mlos_bench.services.base_service import Service
97from mlos_bench.tunables.tunable_groups import TunableGroups
98from mlos_bench.tunables.tunable_types import TunableValue
100_LOG = logging.getLogger(__name__)
103class ManualOptimizer(MockOptimizer):
104 """Optimizer that proposes an explicit sequence of tunable values."""
106 def __init__(
107 self,
108 tunables: TunableGroups,
109 config: dict,
110 global_config: dict | None = None,
111 service: Service | None = None,
112 ):
113 super().__init__(tunables, config, global_config, service)
114 self._tunable_values_cycle: list[dict[str, TunableValue]] = config.get(
115 "tunable_values_cycle", []
116 )
117 assert len(self._tunable_values_cycle) > 0, "No tunable values provided."
118 max_cycles = int(config.get("max_cycles", 1))
119 self._max_suggestions = min(
120 self._max_suggestions,
121 max_cycles * len(self._tunable_values_cycle),
122 )
124 def suggest(self) -> TunableGroups:
125 """Always produce the same sequence of explicit suggestions, in a cycle."""
126 tunables = super().suggest()
127 cycle_index = (self._iter - 1) % len(self._tunable_values_cycle)
128 tunables.assign(self._tunable_values_cycle[cycle_index])
129 _LOG.info("Iteration %d :: Suggest: %s", self._iter, tunables)
130 return tunables
132 @property
133 def supports_preload(self) -> bool:
134 return False