2626from typing_extensions import override
2727
2828from 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
3030from lenskit .logging .tasks import add_context_task
3131from lenskit .parallel import NestedPool
3232from lenskit .pipeline import Pipeline , PipelineBuilder
3838from ._base import BasePipelineTuner , TuneResults
3939from ._measure import measure_pipeline
4040from ._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
317359def _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