from abc import ABC, abstractmethod from skopt.space import Categorical, Integer, Real [docs]class Tuner(ABC): """Defines API for Tuners. Tuners implement different strategies for sampling from a search space. They're used in EvalML to search the space of pipeline hyperparameters. """ [docs] def __init__(self, pipeline_hyperparameter_ranges, random_state=0): """Base Tuner class Arguments: pipeline_hyperparameter_ranges (dict): a set of hyperparameter ranges corresponding to a pipeline's parameters random_state (int): The random state. Defaults to 0. """ self._pipeline_hyperparameter_ranges = pipeline_hyperparameter_ranges self._parameter_names_map = dict() self._search_space_names = [] self._search_space_ranges = [] if not isinstance(pipeline_hyperparameter_ranges, dict): raise ValueError('pipeline_hyperparameter_ranges must be a dict but is of type {}'.format(type(pipeline_hyperparameter_ranges))) self._component_names = list(pipeline_hyperparameter_ranges.keys()) for component_name, component_ranges in pipeline_hyperparameter_ranges.items(): if not isinstance(component_ranges, dict): raise ValueError('pipeline_hyperparameter_ranges has invalid entry for {}: {}'.format(component_name, component_ranges)) for parameter_name, parameter_range in component_ranges.items(): if parameter_range is None: raise ValueError('pipeline_hyperparameter_ranges has invalid dimensions for ' + '{} parameter {}: None.'.format(component_name, parameter_name)) if not isinstance(parameter_range, (Real, Integer, Categorical, list, tuple)): continue flat_parameter_name = '{}: {}'.format(component_name, parameter_name) self._parameter_names_map[flat_parameter_name] = (component_name, parameter_name) self._search_space_names.append(flat_parameter_name) self._search_space_ranges.append(parameter_range) def _convert_to_flat_parameters(self, pipeline_parameters): """Convert from pipeline parameters to a flat list of values""" flat_parameter_values = [] for flat_parameter_name in self._search_space_names: component_name, parameter_name = self._parameter_names_map[flat_parameter_name] if component_name not in pipeline_parameters or parameter_name not in pipeline_parameters[component_name]: raise TypeError('Pipeline parameters missing required field "{}" for component "{}"'.format(parameter_name, component_name)) flat_parameter_values.append(pipeline_parameters[component_name][parameter_name]) return flat_parameter_values def _convert_to_pipeline_parameters(self, flat_parameters): """Convert from a flat list of values to a dict of pipeline parameters""" pipeline_parameters = {component_name: dict() for component_name in self._component_names} for flat_parameter_name, parameter_value in zip(self._search_space_names, flat_parameters): component_name, parameter_name = self._parameter_names_map[flat_parameter_name] pipeline_parameters[component_name][parameter_name] = parameter_value return pipeline_parameters [docs] @abstractmethod def add(self, pipeline_parameters, score): """ Register a set of hyperparameters with the score obtained from training a pipeline with those hyperparameters. Arguments: pipeline_parameters (dict): a dict of the parameters used to evaluate a pipeline score (float): the score obtained by evaluating the pipeline with the provided parameters Returns: None """ [docs] @abstractmethod def propose(self): """Returns a suggested set of parameters to train and score a pipeline with, based off the search space dimensions and prior samples. Returns: dict: proposed pipeline parameters """ [docs] def is_search_space_exhausted(self): """ Optional. If possible search space for tuner is finite, this method indicates whether or not all possible parameters have been scored. Returns: bool: Returns true if all possible parameters in a search space has been scored. """ return False