Skip to content

Commit 1bfcce7

Browse files
committed
tuning: fix pow2 in default setup
1 parent 4a3b5ab commit 1bfcce7

1 file changed

Lines changed: 69 additions & 9 deletions

File tree

src/lenskit/tuning/_optuna.py

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from typing_extensions import override
2727

2828
from lenskit.data import unflatten_dict
29-
from lenskit.logging import Task, get_logger, item_progress
29+
from lenskit.logging import Task, get_logger, item_progress, trace
3030
from lenskit.logging.tasks import add_context_task
3131
from lenskit.parallel import NestedPool
3232
from lenskit.pipeline import Pipeline, PipelineBuilder
@@ -38,7 +38,7 @@
3838
from ._base import BasePipelineTuner, TuneResults
3939
from ._measure import measure_pipeline
4040
from ._stopping import PlateauStopRule
41-
from .spec import SearchSpace, TuningSpec
41+
from .spec import SearchParam, SearchSpace, TuningSpec
4242

4343
_log = get_logger(__name__)
4444

@@ -126,6 +126,7 @@ def _run_trial(self, study: Study, *, nested_pool: bool = False):
126126
tags=["tune", "trial"],
127127
reset_hwm=True,
128128
):
129+
self.log.debug("beginning search", config=config)
129130
try:
130131
if self.iterative:
131132
self._run_iter_trial(study, trial, config)
@@ -260,13 +261,15 @@ def _enqueue_defaults(self, study: Study):
260261
"instantiating pipeline to extract defaults", component=self.spec.component_name
261262
)
262263
pipe = Pipeline.from_config(self.pipeline)
264+
assert self.spec.component_name is not None
263265
comp = pipe.component(self.spec.component_name)
264266
assert comp is not None
265267
if not isinstance(comp, Component):
266268
self.log.warn("component is not pipeline", component=self.spec.component_name)
267269
return
268270

269-
config = _extract_defaults(self.spec.space, comp.dump_config())
271+
config = _extract_defaults(self.spec.space[self.spec.component_name], comp.dump_config())
272+
self.log.info("enqueueing default point", config=config)
270273
study.enqueue_trial(
271274
config,
272275
user_attrs={
@@ -279,7 +282,7 @@ def _ask_config(self, trial: Trial):
279282
# we have exactly one
280283
for space in self.spec.space.values():
281284
cfg = _ask_space(trial, space)
282-
self.log.info("obtained search point", cfg=cfg)
285+
self.log.info("sampled search point", config=cfg)
283286
return cfg
284287

285288

@@ -292,37 +295,94 @@ def _ask_space(trial: Trial, space: SearchSpace, *, prefix: str = ""):
292295
assert isinstance(spec.min, int)
293296
assert isinstance(spec.max, int)
294297
out[name] = trial.suggest_int(prefix + name, spec.min, spec.max)
298+
trace(
299+
_log, "sampled int", name=prefix + name, min=spec.min, max=spec.max, value=out[name]
300+
)
295301
elif spec.type == "int" and spec.scale == "log":
296302
assert isinstance(spec.min, int)
297303
assert isinstance(spec.max, int)
298304
out[name] = trial.suggest_int(prefix + name, spec.min, spec.max, log=True)
305+
trace(
306+
_log,
307+
"sampled int/log2",
308+
name=prefix + name,
309+
min=spec.min,
310+
max=spec.max,
311+
value=out[name],
312+
)
299313
elif spec.type == "int" and spec.scale == "pow2":
300314
min = int(math.log2(spec.min))
301315
max = int(math.log2(spec.max))
302-
out[name] = 2 ** trial.suggest_int(prefix + name, min, max)
316+
p = trial.suggest_int(prefix + name, min, max)
317+
out[name] = 2**p
318+
trace(
319+
_log,
320+
"sampled int/pow2",
321+
name=prefix + name,
322+
min=min,
323+
max=max,
324+
power=p,
325+
value=out[name],
326+
)
303327
elif spec.type == "float" and spec.scale == "uniform":
304328
out[name] = trial.suggest_float(prefix + name, spec.min, spec.max)
329+
trace(
330+
_log,
331+
"sampled float",
332+
name=prefix + name,
333+
min=spec.min,
334+
max=spec.max,
335+
value=out[name],
336+
)
305337
elif spec.type == "float" and spec.scale == "log":
306338
out[name] = trial.suggest_float(prefix + name, spec.min, spec.max, log=True)
339+
trace(
340+
_log,
341+
"sampled float/log",
342+
name=prefix + name,
343+
min=spec.min,
344+
max=spec.max,
345+
value=out[name],
346+
)
307347
elif spec.type == "bool":
308348
out[name] = trial.suggest_categorical(prefix + name, [False, True])
349+
trace(_log, "sampled bool", name=prefix + name, value=out[name])
309350
elif spec.type == "choice":
310351
out[name] = trial.suggest_categorical(prefix + name, spec.choices)
352+
trace(_log, "sampled choice", name=prefix + name, value=out[name])
311353
else: # pragma: nocover
312354
raise ValueError(f"unsupported configuration {space}")
313355

314356
return out
315357

316358

317359
def _extract_defaults(
318-
space: dict[str, SearchSpace], config: dict[str, JsonValue]
360+
space: dict[str, SearchSpace],
361+
config: dict[str, JsonValue],
362+
*,
363+
out: dict[str, JsonValue] | None = None,
364+
prefix: str = "",
319365
) -> dict[str, JsonValue]:
320-
out = {}
366+
# FIXME this whole function is ugly but seems to work
367+
if out is None:
368+
out = {}
321369
for k, v in space.items():
322370
if isinstance(v, dict):
323-
out[k] = _extract_defaults(v, config[k]) # type: ignore
371+
_extract_defaults(v, config[k], out=out, prefix=f"{prefix}{k}.") # type: ignore
372+
elif isinstance(config, dict):
373+
assert isinstance(v, SearchParam)
374+
try:
375+
out[k] = config[k]
376+
except KeyError as e:
377+
if k.endswith("_exp"):
378+
out[k] = int(math.log2(config[k[:-4]])) # type: ignore
379+
else:
380+
raise e
381+
if v.scale == "pow2":
382+
out[k] = int(math.log2(out[k])) # type: ignore
324383
else:
325-
out[k] = config[k]
384+
# FIXME this is an ugly hack for single / multiple configs
385+
out[k] = config
326386
return out
327387

328388

0 commit comments

Comments
 (0)