diff --git a/examples/hyperparams/plot_hyperparams.py b/examples/hyperparams/plot_hyperparams.py index bdb58bcf..a7459348 100644 --- a/examples/hyperparams/plot_hyperparams.py +++ b/examples/hyperparams/plot_hyperparams.py @@ -45,16 +45,16 @@ def main(): ]) ]) - p.set_hyperparams_space(HyperparameterSpace({ + p.set_hyperparams_space({ 'step1__multiply_by': RandInt(42, 50), 'step2__multiply_by': RandInt(-10, 0), 'Pipeline__PCA__n_components': RandInt(2, 3) - })) + }) samples = p.get_hyperparams_space().rvs() p.set_hyperparams(samples) - samples = p.get_hyperparams() + samples = p.get_hyperparams().to_flat_as_dict_primitive() assert 42 <= samples['step1__multiply_by'] <= 50 assert -10 <= samples['step2__multiply_by'] <= 0 assert samples['Pipeline__PCA__n_components'] in [2, 3] diff --git a/neuraxle/base.py b/neuraxle/base.py index dee0f17f..41f3b974 100644 --- a/neuraxle/base.py +++ b/neuraxle/base.py @@ -28,6 +28,7 @@ import inspect import os import pprint +import traceback import warnings from abc import ABC, abstractmethod from collections import OrderedDict @@ -39,7 +40,7 @@ from sklearn.base import BaseEstimator from neuraxle.data_container import DataContainer -from neuraxle.hyperparams.space import HyperparameterSpace, HyperparameterSamples +from neuraxle.hyperparams.space import HyperparameterSpace, HyperparameterSamples, RecursiveDict DEFAULT_CACHE_FOLDER = os.path.join(os.getcwd(), 'cache') @@ -485,6 +486,68 @@ def __len__(self): return len(self.parents) +class _RecursiveArguments: + """ + This class is used by :func:`~neuraxle.base.BaseStep.apply`, and :class:`_HasChildrenMixin` to pass the right arguments to steps with children. + + .. seealso:: + :class:`_HasChildrenMixin`, + :func:`~neuraxle.base.BaseStep.set_hyperparams_space`, + :func:`~neuraxle.base.BaseStep.get_hyperparams_space`, + :func:`~neuraxle.base.BaseStep.get_hyperparams`, + :func:`~neuraxle.base.BaseStep.set_hyperparams`, + :func:`~neuraxle.base.BaseStep.update_hyperparams`, + :func:`~neuraxle.base.BaseStep.update_hyperparams_space`, + :func:`~neuraxle.base.BaseStep.invalidate` + """ + def __init__(self, ra=None, *kargs, **kwargs): + if ra is not None: + kargs = ra.kargs + kwargs = ra.kwargs + self.kargs = kargs + self.kwargs = kwargs + + def __getitem__(self, child_step_name: str): + """ + Return recursive arguments for the given child step name. + If child step name is None, return the root values. + + :param child_step_name: child step name, or None if we want to get root values. + :return: recursive argument for the given child step name + """ + if child_step_name is None: + arguments = list() + keyword_arguments = dict() + for arg in self.kargs: + if isinstance(arg, RecursiveDict): + arguments.append(arg[child_step_name]) + else: + arguments.append(arg) + for key, arg in self.kwargs.items(): + if isinstance(arg, RecursiveDict): + keyword_arguments[key] = arg[child_step_name] + else: + keyword_arguments[key] = arg + return _RecursiveArguments(*arguments, **keyword_arguments) + else: + arguments = list() + keyword_arguments = dict() + for arg in self.kargs: + if isinstance(arg, RecursiveDict): + arguments.append(arg[child_step_name]) + else: + arguments.append(arg) + for key, arg in self.kwargs.items(): + if isinstance(arg, RecursiveDict): + keyword_arguments[key] = arg[child_step_name] + else: + keyword_arguments[key] = arg + return _RecursiveArguments(*arguments, **keyword_arguments) + + def __iter__(self): + return self.kwargs + + class BaseStep(ABC): """ Base class for a pipeline step. @@ -581,7 +644,7 @@ def __init__( self.pending_mutate: ('BaseStep', str, str) = (None, None, None) self.is_initialized = False - self.invalidate() + self._invalidate() self.is_train: bool = True def summary_hash(self, data_container: DataContainer) -> str: @@ -638,10 +701,27 @@ def setup(self) -> 'BaseStep': def invalidate(self) -> 'BaseStep': """ - Invalidate step. + Invalidate a step, and all of its children. Invalidating a step makes it eligible to be saved again. + + A step is invalidated when any of the following things happen : + * a mutation has been performed on the step : func:`~.mutate` + * an hyperparameter has changed func:`~.set_hyperparams` + * an hyperparameter space has changed func:`~.set_hyperparams_space` + * a call to the fit method func:`~.handle_fit` + * a call to the fit_transform method func:`~.handle_fit_transform` + * the step name has changed func:`~neuraxle.base.BaseStep.set_name` :return: self + .. note:: + This is a recursive method used in :class:̀_HasChildrenMixin`. + .. seealso:: + :func:`BaseStep.apply`, + :func:`_HasChildrenMixin._apply` """ + self.apply(method='_invalidate') + return self + + def _invalidate(self): self.is_invalidated = True return self @@ -655,7 +735,7 @@ def teardown(self) -> 'BaseStep': self.is_initialized = False return self - def set_train(self, is_train: bool = True): + def set_train(self, is_train: bool = True) -> 'BaseStep': """ This method overrides the method of BaseStep to also consider the wrapped step as well as self. Set pipeline step mode to train or test. @@ -663,12 +743,20 @@ def set_train(self, is_train: bool = True): :param is_train: is training mode or not :return: + .. note:: + This is a recursive method used in :class:̀_HasChildrenMixin`. .. seealso:: - :func:`BaseStep.set_train` + :func:`BaseStep.apply`, + :func:`_HasChildrenMixin._apply`, + :func:`_HasChildrenMixin._set_train` """ - self.is_train = is_train + self.apply(method='_set_train', is_train=is_train) return self + def _set_train(self, is_train) -> RecursiveDict: + self.is_train = is_train + return RecursiveDict() + def set_name(self, name: str): """ Set the name of the pipeline step. @@ -680,7 +768,7 @@ def set_name(self, name: str): A step name is the same value as the one in the keys of :py:attr:`~neuraxle.pipeline.Pipeline.steps_as_tuple` """ self.name = name - self.invalidate() + self._invalidate() return self def get_name(self) -> str: @@ -716,7 +804,7 @@ def set_savers(self, savers: List[BaseSaver]) -> 'BaseStep': self.savers: List[BaseSaver] = savers return self - def set_hyperparams(self, hyperparams: HyperparameterSamples) -> 'BaseStep': + def set_hyperparams(self, hyperparams: Union[HyperparameterSamples, Dict]) -> 'BaseStep': """ Set the step hyperparameters. @@ -731,14 +819,25 @@ def set_hyperparams(self, hyperparams: HyperparameterSamples) -> 'BaseStep': :param hyperparams: hyperparameters :return: self + .. note:: + This is a recursive method that will call :func:`BaseStep._set_hyperparams` in the end. .. seealso:: - :class:`~neuraxle.hyperparams.space.HyperparameterSamples` + :class:`~neuraxle.hyperparams.space.HyperparameterSamples`, + :class:̀_HasChildrenMixin`, + :func:`BaseStep.apply`, + :func:`_HasChildrenMixin._apply`, + :func:`_HasChildrenMixin._set_train` """ - self.invalidate() - self.hyperparams = HyperparameterSamples(hyperparams).to_flat() + self.apply(method='_set_hyperparams', hyperparams=HyperparameterSamples(hyperparams).to_flat()) return self - def update_hyperparams(self, hyperparams: HyperparameterSamples) -> 'BaseStep': + def _set_hyperparams(self, hyperparams: Union[HyperparameterSamples, Dict]) -> HyperparameterSamples: + self._invalidate() + hyperparams = HyperparameterSamples(hyperparams).to_flat() + self.hyperparams = hyperparams if len(hyperparams) > 0 else self.hyperparams + return self.hyperparams + + def update_hyperparams(self, hyperparams: Union[Dict, HyperparameterSamples]) -> 'BaseStep': """ Update the step hyperparameters without removing the already-set hyperparameters. This can be useful to add more hyperparameters to the existing ones without flushing the ones that were already set. @@ -762,24 +861,42 @@ def update_hyperparams(self, hyperparams: HyperparameterSamples) -> 'BaseStep': :param hyperparams: hyperparameters :return: self + .. note:: + This is a recursive method that will call :func:`BaseStep._update_hyperparams` in the end. .. seealso:: - :func:`~BaseStep.update_hyperparams`, - :class:`~neuraxle.hyperparams.space.HyperparameterSamples` + :class:`~neuraxle.hyperparams.space.HyperparameterSamples`, + :class:̀_HasChildrenMixin`, + :func:`BaseStep.apply`, + :func:`_HasChildrenMixin._apply`, + :func:`_HasChildrenMixin._update_hyperparams` """ - self.hyperparams.update(hyperparams) - self.hyperparams = HyperparameterSamples(self.hyperparams).to_flat() + self.apply(method='_update_hyperparams', hyperparams=HyperparameterSamples(hyperparams).to_flat()) return self + def _update_hyperparams(self, hyperparams: Union[Dict, HyperparameterSamples]) -> HyperparameterSamples: + self.hyperparams.update(HyperparameterSamples(hyperparams).to_flat()) + return self.hyperparams + def get_hyperparams(self) -> HyperparameterSamples: """ Get step hyperparameters as :class:`~neuraxle.hyperparams.space.HyperparameterSamples`. :return: step hyperparameters + .. note:: + This is a recursive method that will call :func:`BaseStep._get_hyperparams` in the end. .. seealso:: - * :class:`~neuraxle.hyperparams.space.HyperparameterSamples` + :class:`~neuraxle.hyperparams.space.HyperparameterSamples`, + :class:̀_HasChildrenMixin`, + :func:`BaseStep.apply`, + :func:`_HasChildrenMixin._apply`, + :func:`_HasChildrenMixin._get_hyperparams` """ - return self.hyperparams + results: HyperparameterSamples = self.apply(method='_get_hyperparams') + return results.to_flat() + + def _get_hyperparams(self) -> HyperparameterSamples: + return HyperparameterSamples(self.hyperparams.to_flat_as_dict_primitive()) def set_params(self, **params) -> 'BaseStep': """ @@ -795,12 +912,23 @@ def set_params(self, **params) -> 'BaseStep': :param **params: arbitrary number of arguments for hyperparameters + .. note:: + This is a recursive method that will call :func:`BaseStep._set_params` in the end. .. seealso:: - :class:`~neuraxle.hyperparams.space.HyperparameterSamples` + :class:`~neuraxle.hyperparams.space.HyperparameterSamples`, + :class:̀_HasChildrenMixin`, + :func:`BaseStep.apply`, + :func:`_HasChildrenMixin._apply`, + :func:`_HasChildrenMixin._set_params` """ - return self.set_hyperparams(HyperparameterSamples(params)) + self.apply(method='_set_params', params=HyperparameterSamples(params).to_flat()) + return self + + def _set_params(self, params: dict) -> HyperparameterSamples: + self.set_hyperparams(HyperparameterSamples(params)) + return self.hyperparams - def get_params(self) -> dict: + def get_params(self) -> HyperparameterSamples: """ Get step hyperparameters as a flat primitive dict. @@ -814,15 +942,41 @@ def get_params(self) -> dict: :return: hyperparameters + .. note:: + This is a recursive method that will call :func:`BaseStep._get_params` in the end. .. seealso:: - :class:`~neuraxle.hyperparams.space.HyperparameterSamples` + :class:`~neuraxle.hyperparams.space.HyperparameterSamples`, + :class:̀_HasChildrenMixin`, + :func:`BaseStep.apply`, + :func:`_HasChildrenMixin._apply`, + :func:`_HasChildrenMixin._get_params` """ - return self.get_hyperparams().to_flat_as_ordered_dict_primitive() + results: HyperparameterSamples = self.apply(method='_get_params') + return results + + def _get_params(self) -> HyperparameterSamples: + return self.get_hyperparams().to_flat() - def set_hyperparams_space(self, hyperparams_space: HyperparameterSpace) -> 'BaseStep': + def set_hyperparams_space(self, hyperparams_space: Union[Dict, HyperparameterSpace]) -> 'BaseStep': """ Set step hyperparameters space. + Example : + + .. code-block:: python + + step.set_hyperparams_space(HyperparameterSpace({ + 'learning_rate': LogNormal(0.5, 0.5) + 'weight_decay': LogNormal(0.001, 0.0005) + })) + + step.update_hyperparams_space(HyperparameterSpace({ + 'learning_rate': LogNormal(0.5, 0.1) + })) + + assert step.get_hyperparams_space()['learning_rate'] == LogNormal(0.5, 0.1) + assert step.get_hyperparams_space()['weight_decay'] == LogNormal(0.001, 0.0005) + Example : .. code-block:: python @@ -837,12 +991,27 @@ def set_hyperparams_space(self, hyperparams_space: HyperparameterSpace) -> 'Base .. seealso:: :class:`~neuraxle.hyperparams.space.HyperparameterSpace`, :class:`~neuraxle.hyperparams.distributions.HyperparameterDistribution` + + .. note:: + This is a recursive method that will call :func:`BaseStep._set_hyperparams_space` in the end. + .. seealso:: + :class:`~neuraxle.hyperparams.space.HyperparameterSamples`, + :class:̀_HasChildrenMixin`, + :func:`BaseStep.apply`, + :func:`_HasChildrenMixin._apply`, + :func:`_HasChildrenMixin._get_params` """ - self.invalidate() - self.hyperparams_space = HyperparameterSpace(hyperparams_space).to_flat() + self.apply(method='_set_hyperparams_space', hyperparams_space=HyperparameterSpace(hyperparams_space).to_flat()) return self - def update_hyperparams_space(self, hyperparams_space: HyperparameterSpace) -> 'BaseStep': + def _set_hyperparams_space(self, hyperparams_space: Union[Dict, HyperparameterSpace]) -> HyperparameterSpace: + self._invalidate() + hyperparams_space = HyperparameterSamples(hyperparams_space).to_flat() + self.hyperparams_space = HyperparameterSpace(hyperparams_space) if len( + hyperparams_space) > 0 else self.hyperparams_space + return self.hyperparams_space + + def update_hyperparams_space(self, hyperparams_space: Union[Dict, HyperparameterSpace]) -> 'BaseStep': """ Update the step hyperparameter spaces without removing the already-set hyperparameters. This can be useful to add more hyperparameter spaces to the existing ones without flushing the ones that were already set. @@ -866,14 +1035,22 @@ def update_hyperparams_space(self, hyperparams_space: HyperparameterSpace) -> 'B :param hyperparams_space: hyperparameters space :return: self + .. note:: + This is a recursive method that will call :func:`BaseStep._update_hyperparams_space` in the end. .. seealso:: :func:`~BaseStep.update_hyperparams`, :class:`~neuraxle.hyperparams.space.HyperparameterSpace` """ - self.hyperparams_space.update(hyperparams_space) - self.hyperparams_space = HyperparameterSamples(self.hyperparams_space).to_flat() + self.apply(method='_update_hyperparams_space', hyperparams_space=HyperparameterSpace( + hyperparams_space).to_flat()) return self + def _update_hyperparams_space(self, hyperparams_space: Union[Dict, HyperparameterSpace]) -> HyperparameterSpace: + self._invalidate() + hyperparams_space = HyperparameterSamples(hyperparams_space).to_flat() + self.hyperparams_space.update(HyperparameterSpace(hyperparams_space).to_flat()) + return self.hyperparams_space + def get_hyperparams_space(self) -> HyperparameterSpace: """ Get step hyperparameters space. @@ -884,13 +1061,20 @@ def get_hyperparams_space(self) -> HyperparameterSpace: step.get_hyperparams_space() + :return: step hyperparams space + .. note:: + This is a recursive method that will call :func:`BaseStep._get_hyperparams_space` in the end. .. seealso:: :class:`~neuraxle.hyperparams.space.HyperparameterSpace`, :class:`~neuraxle.hyperparams.distributions.HyperparameterDistribution` """ - return self.hyperparams_space + results: HyperparameterSpace = self.apply(method='_get_hyperparams_space') + return results.to_flat() + + def _get_hyperparams_space(self) -> HyperparameterSpace: + return HyperparameterSpace(self.hyperparams_space.to_flat_as_dict_primitive()) def handle_inverse_transform(self, data_container: DataContainer, context: ExecutionContext) -> DataContainer: """ @@ -911,53 +1095,51 @@ def handle_inverse_transform(self, data_container: DataContainer, context: Execu return data_container - def _inverse_transform_data_container( - self, data_container: DataContainer, context: ExecutionContext) -> DataContainer: + def _inverse_transform_data_container(self, data_container: DataContainer, context: ExecutionContext) -> DataContainer: processed_outputs = self.inverse_transform(data_container.data_inputs) data_container.set_data_inputs(processed_outputs) return data_container - def apply_method(self, method: Callable, step_name=None, *kargs, **kwargs) -> Dict: + def apply(self, method: Union[str, Callable], ra: _RecursiveArguments = None, *args, **kwargs) -> RecursiveDict: """ Apply a method to a step and its children. - :param method: method to call with self - :param step_name: current pipeline step name - :param kargs: any additional arguments to be passed to the method + :param method: method name that need to be called on all steps + :param ra: recursive arguments + :param args: any additional arguments to be passed to the method :param kwargs: any additional positional arguments to be passed to the method - :return: accumulated results - """ - if step_name is not None: - step_name = "{}__{}".format(step_name, self.name) - else: - step_name = self.name - - return { - step_name: method(self, *kargs, **kwargs) - } + :return: method outputs, or None if no method has been applied - def apply(self, method_name: str, step_name=None, *kargs, **kwargs) -> Dict: + .. seealso:: + :class:`_RecursiveArguments`, + :class:`_HasChildrenMixin` """ - Apply a method to a step and its children. + if ra is None: + ra = _RecursiveArguments(*args, **kwargs) - :param method_name: method name that need to be called on all steps - :param step_name: current pipeline step name - :param kargs: any additional arguments to be passed to the method - :param kwargs: any additional positional arguments to be passed to the method - :return: accumulated results - """ - results = {} + kargs = ra.kargs - if step_name is not None: - step_name = "{}__{}".format(step_name, self.name) - else: - step_name = self.name + def _return_empty(*args, **kwargs): + return RecursiveDict() - if hasattr(self, method_name) and callable(getattr(self, method_name)): - results[step_name] = getattr(self, method_name)(*kargs, **kwargs) + _method = _return_empty + if isinstance(method, str) and hasattr(self, method) and callable(getattr(self, method)): + _method = getattr(self, method) - return results + if not isinstance(method, str): + _method = method + kargs = [self] + list(kargs) + + try: + results = _method(*kargs, **ra.kwargs) + if not isinstance(results, RecursiveDict): + raise ValueError('Method {} must return a RecursiveDict because it is applied recursively.'.format(method)) + return results + except Exception as err: + print('{}: Failed to apply method {}.'.format(self.name, method)) + print(traceback.format_stack()) + raise err def get_step_by_name(self, name): if self.name == name: @@ -1049,7 +1231,7 @@ def _will_fit(self, data_container: DataContainer, context: ExecutionContext) -> :param context: execution context :return: (data container, execution context) """ - self.invalidate() + self._invalidate() return data_container, context.push(self) def _did_fit(self, data_container: DataContainer, context: ExecutionContext) -> DataContainer: @@ -1081,7 +1263,7 @@ def _will_fit_transform(self, data_container: DataContainer, context: ExecutionC :param context: execution context :return: (data container, execution context) """ - self.invalidate() + self._invalidate() return data_container, context.push(self) def _did_fit_transform(self, data_container: DataContainer, context: ExecutionContext) -> DataContainer: @@ -1191,7 +1373,7 @@ def fit_transform(self, data_inputs, expected_outputs=None) -> ('BaseStep', Any) :param expected_outputs: expected outputs to fit on :return: (fitted self, tranformed data inputs) """ - self.invalidate() + self._invalidate() new_self = self.fit(data_inputs, expected_outputs) out = new_self.transform(data_inputs) @@ -1301,15 +1483,16 @@ def save(self, context: ExecutionContext, full_dump=False) -> 'BaseStep': def _initialize_if_needed(step): if not step.is_initialized: step.setup() - return step + return RecursiveDict() def _invalidate(step): - step.invalidate() + step._invalidate() + return RecursiveDict() if full_dump: # initialize and invalidate steps to make sure that all steps will be saved - self.apply_method(_initialize_if_needed) - self.apply_method(_invalidate) + self.apply(method=_initialize_if_needed) + self.apply(method=_invalidate) context.mkdir() stripped_step = copy(self) @@ -1396,7 +1579,7 @@ def mutate(self, new_method="inverse_transform", method_to_assign_to="transform" :param warn: (verbose) wheter or not to warn about the inexistence of the method. :return: self, a copy of self, or even perhaps a new or different BaseStep object. """ - self.invalidate() + self._invalidate() pending_new_base_step, pending_new_method, pending_method_to_assign_to = self.pending_mutate # Use everything that is pending if they are not none (ternaries). @@ -1417,7 +1600,7 @@ def mutate(self, new_method="inverse_transform", method_to_assign_to="transform" # 3. assign new method to old method setattr(new_base_step, method_to_assign_to, new_method) - self.invalidate() + self._invalidate() except AttributeError as e: if warn: @@ -1461,7 +1644,7 @@ def will_mutate_to( :param new_method: if it is not None, upon calling ``mutate``, the new_method will be the one that is used on the provided new_base_step. :return: self """ - self.invalidate() + self._invalidate() if new_method is None or method_to_assign_to is None: new_method = method_to_assign_to = "transform" # No changes will be applied (transform will stay transform). @@ -1556,7 +1739,53 @@ def _sklearn_to_neuraxle_step(step) -> BaseStep: return step -class MetaStepMixin: +class _HasChildrenMixin: + """ + Mixin to add behavior to the steps that have children (sub steps). + + .. seealso:: + :class:`~neuraxle.base.MetaStepMixin`, + :class:`~neuraxle.base.TruncableSteps`, + :class:`~neuraxle.base.TruncableSteps` + """ + def apply(self, method: Union[str, Callable], ra: _RecursiveArguments = None, *args, **kwargs) -> RecursiveDict: + """ + Apply method to root, and children steps. + Split the root, and children values inside the arguments of type RecursiveDict. + + :param method: str or callable function to apply + :param ra: recursive arguments + :return: + """ + ra: _RecursiveArguments = _RecursiveArguments(ra=ra, *args, **kwargs) + results: RecursiveDict = self._apply_self(method=method, ra=ra) + results: RecursiveDict = self._apply_childrens(results=results, method=method, ra=ra) + + return results + + def _apply_self(self, method: Union[str, Callable], ra: _RecursiveArguments): + terminal_ra: _RecursiveArguments = ra[None] + self_results: RecursiveDict = BaseStep.apply(self, method=method, ra=terminal_ra) + return self_results + + def _apply_childrens(self, results: RecursiveDict, method: Union[str, Callable], ra: _RecursiveArguments) -> RecursiveDict: + for children in self.get_children(): + children_results = children.apply(method=method, ra=ra[children.get_name()]) + results[children.get_name()] = RecursiveDict(children_results) + + return results + + @abstractmethod + def get_children(self) -> List[BaseStep]: + """ + Get the list of all the childs for that step. + + :return: + """ + pass + + +class MetaStepMixin(_HasChildrenMixin): """ A class to represent a step that wraps another step. It can be used for many things. @@ -1632,7 +1861,7 @@ def set_step(self, step: BaseStep) -> BaseStep: :param step: new wrapped step :return: self """ - self.invalidate() + self._invalidate() self.wrapped: BaseStep = _sklearn_to_neuraxle_step(step) return self @@ -1658,168 +1887,6 @@ def teardown(self) -> BaseStep: self.is_initialized = False return self - def set_train(self, is_train: bool = True): - """ - Set pipeline step mode to train or test. Also set wrapped step mode to train or test. - - For instance, you can add a simple if statement to direct to the right implementation: - - .. code-block:: python - - def transform(self, data_inputs): - if self.is_train: - self.transform_train_(data_inputs) - else: - self.transform_test_(data_inputs) - - def fit_transform(self, data_inputs, expected_outputs=None): - if self.is_train: - self.fit_transform_train_(data_inputs, expected_outputs) - else: - self.fit_transform_test_(data_inputs, expected_outputs) - - :param is_train: bool - :return: - """ - self.is_train = is_train - self.wrapped.set_train(is_train) - return self - - def set_hyperparams(self, hyperparams: HyperparameterSamples) -> BaseStep: - """ - Set step hyperparameters, and wrapped step hyperparams with the given hyperparams. - - Example : - - .. code-block:: python - - step.set_hyperparams(HyperparameterSamples({ - 'learning_rate': 0.10 - 'wrapped__learning_rate': 0.10 # this will set the wrapped step 'learning_rate' hyperparam - })) - - :param hyperparams: hyperparameters - :return: self - - .. seealso:: - :class:`~neuraxle.hyperparams.space.HyperparameterSamples` - """ - self.invalidate() - - hyperparams: HyperparameterSamples = HyperparameterSamples(hyperparams).to_nested_dict() - - remainders = dict() - for name, hparams in hyperparams.items(): - if name == self.wrapped.name: - self.wrapped.set_hyperparams(hparams) - else: - remainders[name] = hparams - - self.hyperparams = HyperparameterSamples(remainders) - - return self - - def update_hyperparams(self, hyperparams: HyperparameterSamples) -> BaseStep: - """ - Update the step, and the wrapped step hyperparams without removing the already set hyperparameters. - Please refer to :func:`~BaseStep.update_hyperparams`. - - :param hyperparams: hyperparameters - :return: self - - .. seealso:: - :func:`~BaseStep.update_hyperparams`, - :class:`~neuraxle.hyperparams.space.HyperparameterSamples` - """ - self.invalidate() - - hyperparams: HyperparameterSamples = HyperparameterSamples(hyperparams).to_nested_dict() - - remainders = dict() - for name, hparams in hyperparams.items(): - if name == self.wrapped.name: - self.wrapped.update_hyperparams(hparams) - else: - remainders[name] = hparams - - self.hyperparams.update(remainders) - - return self - - def get_hyperparams(self) -> HyperparameterSamples: - """ - Get step hyperparameters as :class:`~neuraxle.hyperparams.space.HyperparameterSamples` with flattened hyperparams. - - :return: step hyperparameters - - .. seealso:: - :class:`~neuraxle.hyperparams.space.HyperparameterSamples` - """ - return HyperparameterSamples({ - **self.hyperparams.to_flat_as_dict_primitive(), - self.wrapped.name: self.wrapped.get_hyperparams().to_flat_as_dict_primitive() - }).to_flat() - - def set_hyperparams_space(self, hyperparams_space: HyperparameterSpace) -> 'BaseStep': - """ - Set meta step and wrapped step hyperparams space using the given hyperparams space. - - :param hyperparams_space: ordered dict containing all hyperparameter spaces - :return: self - """ - self.invalidate() - - hyperparams_space: HyperparameterSpace = HyperparameterSpace(hyperparams_space).to_nested_dict() - - remainders = dict() - for name, hparams in hyperparams_space.items(): - if name == self.wrapped.name: - self.wrapped.set_hyperparams_space(hparams) - else: - remainders[name] = hparams - - self.hyperparams_space = HyperparameterSpace(remainders) - - return self - - def update_hyperparams_space(self, hyperparams_space: HyperparameterSpace) -> BaseStep: - """ - Update the step, and the wrapped step hyperparams without removing the already set hyperparameters. - Please refer to :func:`~BaseStep.update_hyperparams`. - - :param hyperparams_space: hyperparameters - :return: self - - .. seealso:: - :func:`~BaseStep.update_hyperparams`, - :class:`~neuraxle.hyperparams.space.HyperparameterSamples` - """ - self.is_invalidated = True - - hyperparams_space: HyperparameterSpace = HyperparameterSpace(hyperparams_space).to_nested_dict() - - remainders = dict() - for name, hparams_space in hyperparams_space.items(): - if name == self.wrapped.name: - self.wrapped.update_hyperparams_space(hparams_space) - else: - remainders[name] = hparams_space - - self.hyperparams_space.update(remainders) - - return self - - def get_hyperparams_space(self) -> HyperparameterSpace: - """ - Get meta step and wrapped step hyperparams as a flat hyperparameter space - - :return: hyperparameters_space - """ - return HyperparameterSpace({ - **self.hyperparams_space.to_flat_as_dict_primitive(), - self.wrapped.name: self.wrapped.get_hyperparams_space().to_flat_as_dict_primitive() - }).to_flat() - def get_step(self) -> BaseStep: """ Get wrapped step @@ -1910,51 +1977,17 @@ def resume(self, data_container: DataContainer, context: ExecutionContext): data_container = self._did_process(data_container, context) return data_container - def apply(self, method_name: str, step_name=None, *kargs, **kwargs) -> Dict: - """ - Apply the method name to the meta step and its wrapped step. - - :param method_name: method name that need to be called on all steps - :param step_name: step name to apply the method to - :param kargs: any additional arguments to be passed to the method - :param kwargs: any additional positional arguments to be passed to the method - :return: accumulated results + def get_children(self) -> List[BaseStep]: """ - results = BaseStep.apply(self, method_name=method_name, step_name=step_name, *kargs, **kwargs) - - if step_name is not None: - step_name = "{}__{}".format(step_name, self.name) - else: - step_name = self.name + Get the list of all the childs for that step. + :class:`_HasChildrenMixin` calls this method to apply methods to all of the childs for that step. - if self.wrapped is not None: - wrapped_results = self.wrapped.apply(method_name=method_name, step_name=step_name, *kargs, **kwargs) - results.update(wrapped_results) + :return: list of child steps - return results - - def apply_method(self, method: Callable, step_name=None, *kargs, **kwargs) -> Union[Dict, Iterable]: - """ - Apply method to the meta step and its wrapped step. - - :param method: method to call with self - :param step_name: step name to apply the method to - :param kargs: any additional arguments to be passed to the method - :param kwargs: any additional positional arguments to be passed to the method - :return: accumulated results + .. seealso:: + :class:`_HasChildrenMixin` """ - results = BaseStep.apply_method(self, method=method, step_name=step_name, *kargs, **kwargs) - - if step_name is not None: - step_name = "{}__{}".format(step_name, self.name) - else: - step_name = self.name - - if self.wrapped is not None: - wrapped_results = self.wrapped.apply_method(method=method, step_name=step_name, *kargs, **kwargs) - results.update(wrapped_results) - - return results + return [self.wrapped] def get_step_by_name(self, name): if self.wrapped.name == name: @@ -2206,7 +2239,7 @@ def load_step(self, step: 'TruncableSteps', context: ExecutionContext) -> 'Trunc return step -class TruncableSteps(BaseStep, ABC): +class TruncableSteps(_HasChildrenMixin, BaseStep, ABC): """ Step that contains multiple steps. :class:`Pipeline` inherits form this class. It is possible to truncate this step * :func:`~neuraxle.base.TruncableSteps.__getitem__` @@ -2226,6 +2259,7 @@ def __init__( hyperparams_space: HyperparameterSpace = dict() ): BaseStep.__init__(self, hyperparams=hyperparams, hyperparams_space=hyperparams_space) + _HasChildrenMixin.__init__(self) self.set_steps(steps_as_tuple) self.set_savers([TruncableJoblibStepSaver()] + self.savers) @@ -2302,51 +2336,13 @@ def teardown(self) -> 'BaseStep': return self - def apply(self, method_name: str, step_name=None, *kargs, **kwargs) -> Dict: - """ - Apply the method name to the pipeline step and all of its children. - - :param method_name: method name that need to be called on all steps - :param step_name: current pipeline step name - :param kargs: any additional arguments to be passed to the method - :param kwargs: any additional positional arguments to be passed to the method - :return: accumulated results - """ - results = BaseStep.apply(self, method_name, step_name=step_name, *kargs, **kwargs) - - if step_name is not None: - step_name = "{}__{}".format(step_name, self.name) - else: - step_name = self.name - - for step in self.values(): - sub_step_results = step.apply(method_name=method_name, step_name=step_name, *kargs, **kwargs) - results.update(sub_step_results) - - return results - - def apply_method(self, method: Callable, step_name=None, *kargs, **kwargs) -> Dict: + def get_children(self) -> List[BaseStep]: """ - Apply a method to the pipeline step and all of its children. + Get the list of sub step inside the step with children. - :param method: method to call with self - :param step_name: current pipeline step name - :param kargs: any additional arguments to be passed to the method - :param kwargs: any additional positional arguments to be passed to the method - :return: accumulated results + :return: children steps """ - results = BaseStep.apply_method(self, method=method, step_name=step_name, *kargs, **kwargs) - - if step_name is not None: - step_name = "{}__{}".format(step_name, self.name) - else: - step_name = self.name - - for step in self.values(): - sub_step_results = step.apply_method(method=method, step_name=step_name, *kargs, **kwargs) - results.update(sub_step_results) - - return results + return list(self.values()) def get_step_by_name(self, name): for step in self.values(): @@ -2407,7 +2403,7 @@ def _patch_missing_names(self, steps_as_tuple: NamedTupleList) -> NamedTupleList step = (_name, step) names_yet.add(step[0]) patched.append(step) - self.invalidate() + self._invalidate() return patched def _rename_step(self, step_name, class_name, names_yet: set): @@ -2425,7 +2421,7 @@ def _rename_step(self, step_name, class_name, names_yet: set): while step_name in names_yet: step_name = class_name + str(i) i += 1 - self.invalidate() + self._invalidate() return step_name def _refresh_steps(self): @@ -2433,203 +2429,11 @@ def _refresh_steps(self): Private method to refresh inner state after having edited ``self.steps_as_tuple`` (recreate ``self.steps`` from ``self.steps_as_tuple``). """ - self.invalidate() + self._invalidate() self.steps: OrderedDict = OrderedDict(self.steps_as_tuple) for name, step in self.items(): step.name = name - def get_hyperparams(self) -> HyperparameterSamples: - """ - Get step hyperparameters as :class:`~neuraxle.space.HyperparameterSamples`. - - Example : - - .. code-block:: python - - p = Pipeline([SomeStep()]) - p.set_hyperparams(HyperparameterSamples({ - 'learning_rate': 0.1, - 'some_step__learning_rate': 0.2 # will set SomeStep() hyperparam 'learning_rate' to 0.2 - })) - - hp = p.get_hyperparams() - # hp ==> { 'learning_rate': 0.1, 'some_step__learning_rate': 0.2 } - - :return: step hyperparameters - - .. seealso:: - :class:`~neuraxle.hyperparams.space.HyperparameterSamples` - """ - hyperparams = dict() - - for k, v in self.steps.items(): - hparams = v.get_hyperparams() # TODO: oop diamond problem? - if hasattr(v, "hyperparams"): - hparams.update(v.hyperparams) - if len(hparams) > 0: - hyperparams[k] = hparams - - hyperparams = HyperparameterSamples(hyperparams) - - hyperparams.update( - BaseStep.get_hyperparams(self) - ) - - return hyperparams.to_flat() - - def set_hyperparams(self, hyperparams: Union[HyperparameterSamples, OrderedDict, dict]) -> BaseStep: - """ - Set step hyperparameters to the given :class:`~neuraxle.space.HyperparameterSamples`. - - Example : - - .. code-block:: python - - p = Pipeline([SomeStep()]) - p.set_hyperparams(HyperparameterSamples({ - 'learning_rate': 0.1, - 'some_step__learning_rate': 0.2 # will set SomeStep() hyperparam 'learning_rate' to 0.2 - })) - - :return: step hyperparameters - - .. seealso:: - :class:`~neuraxle.hyperparams.space.HyperparameterSamples` - """ - self.invalidate() - - hyperparams: HyperparameterSamples = HyperparameterSamples(hyperparams).to_nested_dict() - - remainders = dict() - for name, hparams in hyperparams.items(): - if name in self.steps.keys(): - self.steps[name].set_hyperparams(HyperparameterSamples(hparams)) - else: - remainders[name] = hparams - self.hyperparams = HyperparameterSamples(remainders) - - return self - - def update_hyperparams(self, hyperparams: Union[HyperparameterSamples, OrderedDict, dict]) -> BaseStep: - """ - Update the steps hyperparameters without removing the already-set hyperparameters. - Please refer to :func:`~BaseStep.update_hyperparams`. - - :param hyperparams: hyperparams to update - :return: step - - .. seealso:: - :func:`~BaseStep.update_hyperparams`, - :class:`~neuraxle.hyperparams.space.HyperparameterSamples` - """ - self.invalidate() - - hyperparams: HyperparameterSamples = HyperparameterSamples(hyperparams).to_nested_dict() - - remainders = dict() - for name, hparams in hyperparams.items(): - if name in self.steps.keys(): - self.steps[name].update_hyperparams(HyperparameterSamples(hparams)) - else: - remainders[name] = hparams - self.hyperparams.update(remainders) - - return self - - def get_hyperparams_space(self): - """ - Get step hyperparameters space as :class:`~neuraxle.space.HyperparameterSpace`. - - Example : - - .. code-block:: python - - p = Pipeline([SomeStep()]) - p.set_hyperparams_space(HyperparameterSpace({ - 'learning_rate': RandInt(0,5), - 'some_step__learning_rate': RandInt(0, 10) # will set SomeStep() 'learning_rate' hyperparam space to RandInt(0, 10) - })) - - hp = p.get_hyperparams_space() - # hp ==> { 'learning_rate': RandInt(0,5), 'some_step__learning_rate': RandInt(0,10) } - - :return: step hyperparameters space - - .. seealso:: - :class:`~neuraxle.hyperparams.space.HyperparameterSpace` - """ - all_hyperparams = HyperparameterSpace() - for step_name, step in self.steps_as_tuple: - hspace = step.get_hyperparams_space() - all_hyperparams.update({ - step_name: hspace - }) - all_hyperparams.update( - BaseStep.get_hyperparams_space(self) - ) - - return all_hyperparams.to_flat() - - def update_hyperparams_space(self, hyperparams_space: Union[HyperparameterSpace, OrderedDict, dict]) -> BaseStep: - """ - Update the steps hyperparameters without removing the already-set hyperparameters. - Please refer to :func:`~BaseStep.update_hyperparams`. - - :param hyperparams_space: hyperparams_space to update - :return: step - - .. seealso:: - :func:`~BaseStep.update_hyperparams`, - :class:`~neuraxle.space.HyperparameterSamples` - """ - self.is_invalidated = True - - hyperparams_space: HyperparameterSpace = HyperparameterSpace(hyperparams_space).to_nested_dict() - - remainders = dict() - for name, hparams_space in hyperparams_space.items(): - if name in self.steps.keys(): - self.steps[name].update_hyperparams_space(HyperparameterSamples(hparams_space)) - else: - remainders[name] = hparams_space - self.hyperparams_space.update(remainders) - - return self - - def set_hyperparams_space(self, hyperparams_space: Union[HyperparameterSpace, OrderedDict, dict]) -> BaseStep: - """ - Set step hyperparameters space as :class:`~neuraxle.hyperparams.space.HyperparameterSpace`. - - Example : - - .. code-block:: python - - p = Pipeline([SomeStep()]) - p.set_hyperparams_space(HyperparameterSpace({ - 'learning_rate': RandInt(0,5), - 'some_step__learning_rate': RandInt(0, 10) # will set SomeStep() 'learning_rate' hyperparam space to RandInt(0, 10) - })) - - :param hyperparams_space: hyperparameters space - :return: self - - .. seealso:: - :class:`~neuraxle.hyperparams.space.HyperparameterSpace` - """ - self.invalidate() - - hyperparams_space: HyperparameterSpace = HyperparameterSpace(hyperparams_space).to_nested_dict() - - remainders = dict() - for name, hparams in hyperparams_space.items(): - if name in self.keys(): - self.steps[name].set_hyperparams_space(HyperparameterSpace(hparams)) - else: - remainders[name] = hparams - self.hyperparams_space = HyperparameterSpace(remainders) - - return self - def should_save(self): """ Returns if the step needs to be saved or not. @@ -2934,34 +2738,6 @@ def ends_with(self, step_type: type): """ return isinstance(self[-1], step_type) - def set_train(self, is_train: bool = True) -> 'BaseStep': - """ - Set pipeline step mode to train or test. - - In the pipeline steps functions, you can add a simple if statement to direct to the right implementation: - - .. code-block:: python - - def transform(self, data_inputs): - if self.is_train: - self.transform_train_(data_inputs) - else: - self.transform_test_(data_inputs) - - def fit_transform(self, data_inputs, expected_outputs): - if self.is_train: - self.fit_transform_train_(data_inputs, expected_outputs) - else: - self.fit_transform_test_(data_inputs, expected_outputs) - - :param is_train: if the step is in train mode (True) or test mode (False) - :return: self - """ - self.is_train = is_train - for _, step in self.items(): - step.set_train(is_train) - return self - def __repr__(self): output = self.__class__.__name__ + '\n' \ @@ -3157,7 +2933,8 @@ def fit_transform(self, data_inputs, expected_outputs=None) -> Tuple['HandleOnly return new_self, data_container.data_inputs - def _encapsulate_data(self, data_inputs, expected_outputs=None, execution_mode=None) -> Tuple[ExecutionContext, DataContainer]: + def _encapsulate_data(self, data_inputs, expected_outputs=None, execution_mode=None) -> Tuple[ + ExecutionContext, DataContainer]: """ Encapsulate data with :class:`~neuraxle.data_container.DataContainer`. @@ -3227,6 +3004,7 @@ class FullDumpLoader(Identity): :class:`BaseStep`, :class:`Identity` """ + def __init__(self, name, stripped_saver=None): if stripped_saver is None: stripped_saver = JoblibStepSaver() diff --git a/neuraxle/hyperparams/space.py b/neuraxle/hyperparams/space.py index eb45aafe..243022bc 100644 --- a/neuraxle/hyperparams/space.py +++ b/neuraxle/hyperparams/space.py @@ -66,73 +66,61 @@ from neuraxle.hyperparams.distributions import HyperparameterDistribution -PARAMS_SPLIT_SEQ = "__" - -def nested_dict_to_flat(nested_hyperparams, dict_ctor=OrderedDict): +class RecursiveDict(OrderedDict): """ - Convert a nested hyperparameter dictionary to a flat one. + Wraps an hyperparameter nested dict or flat dict, and offer a few more functions. + + This can be set on a Pipeline with the method ``set_hyperparams``. - :param nested_hyperparams: a nested hyperparameter dictionary. - :param dict_ctor: ``OrderedDict`` by default. Will use this as a class to create the new returned dict. - :return: a flat hyperparameter dictionary. + HyperparameterSamples are often the result of calling ``.rvs()`` on an HyperparameterSpace. """ - ret = dict_ctor() - for k, v in nested_hyperparams.items(): - if isinstance(v, dict) or isinstance(v, OrderedDict) or isinstance(v, dict_ctor): - _ret = nested_dict_to_flat(v) - for key, val in _ret.items(): - ret[k + PARAMS_SPLIT_SEQ + key] = val + DEFAULT_SEPARATOR = '__' + + def __init__(self, *args, separator=None, **kwds): + if len(args) == 1 and isinstance(args[0], RecursiveDict) and len(kwds) == 0: + super().__init__(args[0].items()) + separator = args[0].separator else: - ret[k] = v - return ret + super().__init__(*args, **kwds) + if separator is None: + separator = self.DEFAULT_SEPARATOR -def flat_to_nested_dict(flat_hyperparams, dict_ctor=OrderedDict): - """ - Convert a flat hyperparameter dictionary to a nested one. + self.separator = separator - :param flat_hyperparams: a flat hyperparameter dictionary. - :param dict_ctor: ``OrderedDict`` by default. Will use this as a class to create the new returned dict. - :return: a nested hyperparameter dictionary. - """ - pre_ret = dict_ctor() - ret = dict_ctor() - for k, v in flat_hyperparams.items(): - k, _, key = k.partition(PARAMS_SPLIT_SEQ) - if len(key) > 0: - if k not in pre_ret.keys(): - pre_ret[k] = dict_ctor() - pre_ret[k][key] = v - else: - ret[k] = v - for k, v in pre_ret.items(): - ret[k] = flat_to_nested_dict(v) - return ret + def __getitem__(self, item: str = None): + item_values = type(self)() + for name, values in self.items(): + if item is None and not self.separator in name: + item_values[name] = values -class HyperparameterSamples(OrderedDict): - """Wraps an hyperparameter nested dict or flat dict, and offer a few more functions. + if self.separator in name: + name_split = name.split(self.separator) + if str(name_split[0]) == item: + item_values[self.separator.join(name_split[1:])] = values - This can be set on a Pipeline with the method ``set_hyperparams``. + if item == name: + item_values = values - HyperparameterSamples are often the result of calling ``.rvs()`` on an HyperparameterSpace.""" + return item_values - def to_flat(self) -> 'HyperparameterSamples': + def to_flat(self) -> 'RecursiveDict': """ Will create an equivalent flat HyperparameterSamples. :return: an HyperparameterSamples like self, flattened. """ - return nested_dict_to_flat(self, dict_ctor=HyperparameterSamples) + return self.nested_dict_to_flat(dict_ctor=type(self)) - def to_nested_dict(self) -> 'HyperparameterSamples': + def to_nested_dict(self) -> 'RecursiveDict': """ Will create an equivalent nested dict HyperparameterSamples. :return: an HyperparameterSamples like self, as a nested dict. """ - return flat_to_nested_dict(self, dict_ctor=HyperparameterSamples) + return self.flat_to_nested_dict(dict_ctor=type(self)) def to_flat_as_dict_primitive(self) -> dict: """ @@ -140,7 +128,7 @@ def to_flat_as_dict_primitive(self) -> dict: :return: an HyperparameterSpace like self, flattened. """ - return nested_dict_to_flat(self, dict_ctor=dict) + return self.nested_dict_to_flat(dict_ctor=dict) def to_nested_dict_as_dict_primitive(self) -> dict: """ @@ -148,7 +136,7 @@ def to_nested_dict_as_dict_primitive(self) -> dict: :return: a nested primitive dict type of self. """ - return flat_to_nested_dict(self, dict_ctor=dict) + return self.flat_to_nested_dict(dict_ctor=dict) def to_flat_as_ordered_dict_primitive(self) -> OrderedDict: """ @@ -156,7 +144,7 @@ def to_flat_as_ordered_dict_primitive(self) -> OrderedDict: :return: an HyperparameterSpace like self, flattened. """ - return nested_dict_to_flat(self, dict_ctor=OrderedDict) + return self.nested_dict_to_flat(dict_ctor=OrderedDict) def to_nested_dict_as_ordered_dict_primitive(self) -> OrderedDict: """ @@ -164,16 +152,101 @@ def to_nested_dict_as_ordered_dict_primitive(self) -> OrderedDict: :return: a nested primitive dict type of self. """ - return flat_to_nested_dict(self, dict_ctor=OrderedDict) + return self.flat_to_nested_dict(dict_ctor=OrderedDict) + + def nested_dict_to_flat(self, dict_ctor): + """ + Convert a nested hyperparameter dictionary to a flat one. + :param dict_ctor: ``OrderedDict`` by default. Will use this as a class to create the new returned dict. + :return: a flat hyperparameter dictionary. + """ + if isinstance(dict_ctor, RecursiveDict): + ret = dict_ctor(self.separator) + else: + ret = dict_ctor() + + for k, v in self.items(): + if isinstance(v, dict) or isinstance(v, OrderedDict) or isinstance(v, dict_ctor): + nested_dict = v + if not isinstance(v, RecursiveDict): + nested_dict = RecursiveDict(v, separator=self.separator) + _ret = nested_dict.nested_dict_to_flat(dict_ctor=type(nested_dict)) + for key, val in _ret.items(): + ret[k + nested_dict.separator + key] = val + else: + ret[k] = v + return ret + + def flat_to_nested_dict(self, dict_ctor=dict): + """ + Convert a flat hyperparameter dictionary to a nested one. + + :param dict_ctor: ``OrderedDict`` by default. Will use this as a class to create the new returned dict. + :return: a nested hyperparameter dictionary. + """ + if isinstance(dict_ctor, RecursiveDict): + pre_ret = dict_ctor(self.separator) + ret = dict_ctor(self.separator) + else: + pre_ret = dict_ctor() + ret = dict_ctor() + + for k, v in self.items(): + k, _, key = k.partition(self.separator) + if len(key) > 0: + if k not in pre_ret.keys(): + pre_ret[k] = dict_ctor() + pre_ret[k][key] = v + else: + ret[k] = v + for k, v in pre_ret.items(): + flat_dict = v + if not isinstance(v, RecursiveDict): + flat_dict = RecursiveDict(v, separator=self.separator) + ret[k] = flat_dict.flat_to_nested_dict(dict_ctor=dict_ctor) + return ret + + def with_separator(self, separator): + """ + Create a new recursive dict that uses the given separator at each level. + + :param separator: + :return: + """ + return type(self)( + separator=separator, + **{ + key.replace(self.separator, separator): value if not isinstance(value, RecursiveDict) \ + else value.with_separator(separator) for key, value in self.items() + }) -class HyperparameterSpace(HyperparameterSamples): - """Wraps an hyperparameter nested dict or flat dict, and offer a few more functions to process + +class HyperparameterSamples(RecursiveDict): + """ + Wraps an hyperparameter nested dict or flat dict, and offer a few more functions. + + This can be set on a Pipeline with the method ``set_hyperparams``. + + HyperparameterSamples are often the result of calling ``.rvs()`` on an HyperparameterSpace. + """ + + def __init__(self, *args, separator=None, **kwds): + super().__init__(*args, separator=separator, **kwds) + + +class HyperparameterSpace(RecursiveDict): + """ + Wraps an hyperparameter nested dict or flat dict, and offer a few more functions to process all contained HyperparameterDistribution. This can be set on a Pipeline with the method ``set_hyperparams_space``. - Calling ``.rvs()`` on an ``HyperparameterSpace`` results in ``HyperparameterSamples``.""" + Calling ``.rvs()`` on an ``HyperparameterSpace`` results in ``HyperparameterSamples``. + """ + + def __init__(self, *args, separator=None, **kwds): + super().__init__(*args, separator=separator, **kwds) def rvs(self) -> 'HyperparameterSamples': """ @@ -196,7 +269,6 @@ def nullify(self): new_items.append((k, v)) return HyperparameterSamples(new_items) - def narrow_space_from_best_guess( self, best_guesses: 'HyperparameterSpace', kept_space_ratio: float = 0.5 ) -> 'HyperparameterSpace': @@ -228,19 +300,3 @@ def unnarrow(self) -> 'HyperparameterSpace': v = v.unnarrow() new_items.append((k, v)) return HyperparameterSpace(new_items) - - def to_flat(self) -> 'HyperparameterSpace': - """ - Will create an equivalent flat HyperparameterSpace. - - :return: an HyperparameterSpace like self, flattened. - """ - return nested_dict_to_flat(self, dict_ctor=HyperparameterSpace) - - def to_nested_dict(self) -> 'HyperparameterSpace': - """ - Will create an equivalent nested dict HyperparameterSpace. - - :return: an HyperparameterSpace like self, as a nested dict. - """ - return flat_to_nested_dict(self, dict_ctor=HyperparameterSpace) diff --git a/neuraxle/metaopt/random.py b/neuraxle/metaopt/random.py index a6378328..eaa34dc1 100644 --- a/neuraxle/metaopt/random.py +++ b/neuraxle/metaopt/random.py @@ -34,6 +34,7 @@ from neuraxle.base import MetaStepMixin, BaseStep, ExecutionContext, HandleOnlyMixin, ForceHandleOnlyMixin, \ EvaluableStepMixin from neuraxle.data_container import DataContainer +from neuraxle.hyperparams.space import RecursiveDict from neuraxle.steps.loop import StepClonerForEachDataInput from neuraxle.steps.numpy import NumpyConcatenateOuterBatch, NumpyConcatenateOnCustomAxis @@ -327,11 +328,13 @@ def disable_metrics(self): self.metrics_enabled = False if self.wrapped is not None: self.wrapped.apply('disable_metrics') + return RecursiveDict() def enable_metrics(self): self.metrics_enabled = True if self.wrapped is not None: self.wrapped.apply('enable_metrics') + return RecursiveDict() def _get_index_split(self, data_inputs): return math.floor(len(data_inputs) * (1 - self.test_size)) diff --git a/neuraxle/metrics.py b/neuraxle/metrics.py index 20035dac..45e3dfa5 100644 --- a/neuraxle/metrics.py +++ b/neuraxle/metrics.py @@ -23,6 +23,7 @@ from neuraxle.base import MetaStepMixin, BaseStep, ExecutionContext from neuraxle.data_container import DataContainer +from neuraxle.hyperparams.space import RecursiveDict class MetricsWrapper(MetaStepMixin, BaseStep): @@ -141,25 +142,26 @@ def _calculate_metrics_results(self, data_container: DataContainer): if self.print_metrics: self.print_fun(result) - def get_metrics(self) -> Dict: + def get_metrics(self) -> RecursiveDict: """ Get all metrics results using the transformed data container, and the metrics function dict. To be used with :func:`neuraxle.base.BaseStep.apply` method. :return: dict with the step name as key, and all of the training, and validation metrics as values """ - return { + return RecursiveDict({ 'train': self.metrics_results_train, 'validation': self.metrics_results_validation - } + }) - def toggle_metrics(self): + def toggle_metrics(self) -> RecursiveDict: """ Toggle metrics wrapper on and off to temporarily disable metrics if needed.. :return: """ self.enabled = not self.enabled + return RecursiveDict({self.name: self.enabled}) def disable_metrics(self): """ @@ -168,6 +170,7 @@ def disable_metrics(self): :return: """ self.enabled = False + return RecursiveDict({self.name: self.enabled}) def enable_metrics(self): """ @@ -176,3 +179,4 @@ def enable_metrics(self): :return: """ self.enabled = True + return RecursiveDict({self.name: self.enabled}) diff --git a/neuraxle/steps/flow.py b/neuraxle/steps/flow.py index 61d33f0c..1c4ddfac 100644 --- a/neuraxle/steps/flow.py +++ b/neuraxle/steps/flow.py @@ -295,12 +295,12 @@ def __init__(self, steps, hyperparams=None): if hyperparams is None: choices = list(self.keys())[:-1] - self.set_hyperparams(HyperparameterSamples({ + self.set_hyperparams({ CHOICE_HYPERPARAM: choices[0] - })) - self.set_hyperparams_space(HyperparameterSpace({ + }) + self.set_hyperparams_space({ CHOICE_HYPERPARAM: Choice(choices) - })) + }) def set_hyperparams(self, hyperparams: Union[HyperparameterSamples, dict]): """ diff --git a/neuraxle/steps/loop.py b/neuraxle/steps/loop.py index adf2df09..3094aa7c 100644 --- a/neuraxle/steps/loop.py +++ b/neuraxle/steps/loop.py @@ -23,12 +23,12 @@ """ import copy -from typing import List +from typing import List, Callable, Dict, Iterable, Union import numpy as np from neuraxle.base import MetaStepMixin, BaseStep, DataContainer, ExecutionContext, ResumableStepMixin, \ - ForceHandleOnlyMixin, ForceHandleMixin, TruncableJoblibStepSaver, NamedTupleList + ForceHandleOnlyMixin, ForceHandleMixin, TruncableJoblibStepSaver, NamedTupleList, _HasChildrenMixin from neuraxle.data_container import ListDataContainer from neuraxle.hyperparams.space import HyperparameterSamples, HyperparameterSpace @@ -157,38 +157,16 @@ def __init__(self, wrapped: BaseStep, copy_op=copy.deepcopy, cache_folder_when_n self.steps_as_tuple: List[NamedTupleList] = [] self.copy_op = copy_op - def set_train(self, is_train: bool = True): - MetaStepMixin.set_train(self, is_train) - [step.set_train(is_train) for _, step in self] - return self - - def set_hyperparams(self, hyperparams: HyperparameterSamples) -> BaseStep: - MetaStepMixin.set_hyperparams(self, hyperparams) - self.steps_as_tuple = [(name, step.set_hyperparams(self.wrapped.get_hyperparams())) for name, step in self] - return self - - def update_hyperparams(self, hyperparams: HyperparameterSamples) -> BaseStep: + def get_children(self) -> List[BaseStep]: """ - Update the step hyperparameters without removing the already-set hyperparameters. - Please refer to :func:`~BaseStep.update_hyperparams`. + Get the list of all the children for that step. - :param hyperparams: hyperparams to update - :type hyperparams: HyperparameterSamples - :return: self - :rtype: BaseStep - - .. seealso:: - :func:`~BaseStep.update_hyperparams`, - :class:`~neuraxle.hyperparams.space.HyperparameterSamples` + :return: list of children """ - MetaStepMixin.update_hyperparams(self, hyperparams) - self.steps_as_tuple = [(name, step.set_hyperparams(self.wrapped.get_hyperparams())) for name, step in self.steps_as_tuple] - return self - - def set_hyperparams_space(self, hyperparams_space: HyperparameterSpace) -> 'BaseStep': - MetaStepMixin.set_hyperparams_space(self, hyperparams_space) - self.steps_as_tuple = [(name, step.set_hyperparams_space(self.wrapped.get_hyperparams_space())) for name, step in self] - return self + children: List[BaseStep] = MetaStepMixin.get_children(self) + cloned_children = [step for _, step in self.steps_as_tuple] + children.extend(cloned_children) + return children def _will_process(self, data_container: DataContainer, context: ExecutionContext) -> ('BaseStep', DataContainer): data_container, context = BaseStep._will_process(self, data_container, context) @@ -202,10 +180,9 @@ def _copy_one_step_per_data_input(self, data_container): # One copy of step per data input: steps = [self.copy_op(self.wrapped).set_name('{}[{}]'.format(self.wrapped.name, i)) for i in range(len(data_container))] self.steps_as_tuple = [(step.name, step) for step in steps] - self.invalidate() + self._invalidate() - def _fit_transform_data_container(self, data_container: DataContainer, context: ExecutionContext) -> ( - 'BaseStep', DataContainer): + def _fit_transform_data_container(self, data_container: DataContainer, context: ExecutionContext) -> ('BaseStep', DataContainer): fitted_steps_data_containers = [] for i, (current_ids, data_inputs, expected_outputs) in enumerate(data_container): fitted_step_data_container = self[i].handle_fit_transform( @@ -285,6 +262,14 @@ def __iter__(self): """ return iter(self.steps_as_tuple) + def __len__(self): + """ + Get number of steps cloned for each data input. + + :return: len(self.steps_as_tuple) + """ + return len(self.steps_as_tuple) + class FlattenForEach(ForceHandleMixin, ResumableStepMixin, MetaStepMixin, BaseStep): """ diff --git a/neuraxle/steps/misc.py b/neuraxle/steps/misc.py index 603699a2..38034c1d 100644 --- a/neuraxle/steps/misc.py +++ b/neuraxle/steps/misc.py @@ -27,6 +27,8 @@ import time from abc import ABC +from neuraxle.hyperparams.space import RecursiveDict + VALUE_CACHING = 'value_caching' from typing import List, Any @@ -185,7 +187,7 @@ def clear_callbacks(self): self.fit_callback_function.data = [] self.fit_callback_function.name_tape = [] - return cleared_callbacks + return RecursiveDict(cleared_callbacks) class CallbackWrapper(HandleOnlyMixin, MetaStepMixin, BaseStep): diff --git a/neuraxle/steps/sklearn.py b/neuraxle/steps/sklearn.py index 5b331afb..6b9e9ac2 100644 --- a/neuraxle/steps/sklearn.py +++ b/neuraxle/steps/sklearn.py @@ -24,14 +24,14 @@ """ import inspect -from typing import Any +from typing import Any, List from sklearn.base import BaseEstimator from sklearn.linear_model import Ridge -from neuraxle.base import BaseStep +from neuraxle.base import BaseStep, _HasChildrenMixin from neuraxle.hyperparams.distributions import LogUniform, Boolean -from neuraxle.hyperparams.space import HyperparameterSpace, HyperparameterSamples +from neuraxle.hyperparams.space import HyperparameterSpace, HyperparameterSamples, RecursiveDict from neuraxle.steps.numpy import NumpyTranspose from neuraxle.union import ModelStacking @@ -78,16 +78,27 @@ def transform(self, data_inputs): return self.wrapped_sklearn_predictor.predict(data_inputs) return self.wrapped_sklearn_predictor.transform(data_inputs) - def set_hyperparams(self, flat_hyperparams: HyperparameterSamples) -> BaseStep: - BaseStep.set_hyperparams(self, flat_hyperparams) - self.wrapped_sklearn_predictor.set_params(**HyperparameterSamples(flat_hyperparams).to_flat_as_dict_primitive()) - return self + def _set_hyperparams(self, hyperparams: HyperparameterSamples) -> BaseStep: + """ + Set hyperparams for base step, and the wrapped sklearn_predictor. + + :param hyperparams: + :return: self + """ + # flatten the step hyperparams, and set the wrapped sklearn predictor params + hyperparams = HyperparameterSamples(hyperparams) + BaseStep._set_hyperparams(self, hyperparams.to_flat()) + self.wrapped_sklearn_predictor.set_params( + **hyperparams.with_separator(RecursiveDict.DEFAULT_SEPARATOR).to_flat_as_dict_primitive() + ) + + return self.hyperparams.to_flat() - def get_hyperparams(self): + def _get_hyperparams(self): if self.return_all_sklearn_default_params_on_get: return HyperparameterSamples(self.wrapped_sklearn_predictor.get_params()).to_flat() else: - return BaseStep.get_hyperparams(self) + return BaseStep._get_hyperparams(self) def get_wrapped_sklearn_predictor(self): return self.wrapped_sklearn_predictor diff --git a/testing/hyperparams/test_get_set_hyperparams.py b/testing/hyperparams/test_get_set_hyperparams.py index 5af68b03..49d33783 100644 --- a/testing/hyperparams/test_get_set_hyperparams.py +++ b/testing/hyperparams/test_get_set_hyperparams.py @@ -1,6 +1,7 @@ from neuraxle.base import MetaStepMixin, BaseStep, NonFittableMixin, NonTransformableMixin from neuraxle.hyperparams.distributions import RandInt, Boolean from neuraxle.hyperparams.space import HyperparameterSpace, HyperparameterSamples +from neuraxle.pipeline import Pipeline from neuraxle.steps.loop import StepClonerForEachDataInput from testing.test_pipeline import SomeStep @@ -23,7 +24,11 @@ class SomeMetaStepMixin(NonTransformableMixin, NonFittableMixin, MetaStepMixin, BaseStep): - pass + def __init__(self, wrapped: BaseStep): + BaseStep.__init__(self) + NonTransformableMixin.__init__(self) + NonFittableMixin.__init__(self) + MetaStepMixin.__init__(self, wrapped) class SomeStepInverseTransform(SomeStep): @@ -36,10 +41,10 @@ def inverse_transform(self, processed_outputs): def test_step_cloner_should_get_hyperparams(): p = StepClonerForEachDataInput(SomeStep()) - p.set_hyperparams(HyperparameterSamples({ + p.set_hyperparams({ META_STEP_HP: META_STEP_HP_VALUE, SOME_STEP_HP: SOME_STEP_HP_VALUE - })) + }) hyperparams = p.get_hyperparams() @@ -50,10 +55,10 @@ def test_step_cloner_should_get_hyperparams(): def test_step_cloner_should_set_hyperparams(): p = StepClonerForEachDataInput(SomeStep()) - p.set_hyperparams(HyperparameterSamples({ + p.set_hyperparams({ META_STEP_HP: META_STEP_HP_VALUE, SOME_STEP_HP: SOME_STEP_HP_VALUE - })) + }) assert isinstance(p.hyperparams, HyperparameterSamples) assert p.hyperparams[META_STEP_HP] == META_STEP_HP_VALUE @@ -62,14 +67,14 @@ def test_step_cloner_should_set_hyperparams(): def test_step_cloner_update_hyperparams_should_update_step_cloner_hyperparams(): p = StepClonerForEachDataInput(SomeStep()) - p.set_hyperparams(HyperparameterSamples({ + p.set_hyperparams({ META_STEP_HP: META_STEP_HP_VALUE, SOME_STEP_HP: SOME_STEP_HP_VALUE - })) + }) - p.update_hyperparams(HyperparameterSamples({ + p.update_hyperparams({ META_STEP_HP: META_STEP_HP_VALUE + 1, - })) + }) assert isinstance(p.hyperparams, HyperparameterSamples) assert p.hyperparams[META_STEP_HP] == META_STEP_HP_VALUE + 1 @@ -78,14 +83,14 @@ def test_step_cloner_update_hyperparams_should_update_step_cloner_hyperparams(): def test_step_cloner_update_hyperparams_should_update_wrapped_step_hyperparams(): p = StepClonerForEachDataInput(SomeStep()) - p.set_hyperparams(HyperparameterSamples({ + p.set_hyperparams({ META_STEP_HP: META_STEP_HP_VALUE, SOME_STEP_HP: SOME_STEP_HP_VALUE - })) + }) - p.update_hyperparams(HyperparameterSamples({ - SOME_STEP_HP: SOME_STEP_HP_VALUE + 1, - })) + p.update_hyperparams({ + SOME_STEP_HP: SOME_STEP_HP_VALUE + 1 + }) assert isinstance(p.hyperparams, HyperparameterSamples) assert p.hyperparams[META_STEP_HP] == META_STEP_HP_VALUE @@ -95,15 +100,15 @@ def test_step_cloner_update_hyperparams_should_update_wrapped_step_hyperparams() def test_step_cloner_update_hyperparams_space_should_update_step_cloner_hyperparams(): p = StepClonerForEachDataInput(SomeStep()) - p.set_hyperparams_space(HyperparameterSpace({ + p.set_hyperparams_space({ META_STEP_HP: RAND_INT_META_STEP, SOME_STEP_HP: RAND_INT_SOME_STEP - })) + }) update_meta_step_hp_space = RandInt(0, 40) - p.update_hyperparams_space(HyperparameterSpace({ + p.update_hyperparams_space({ META_STEP_HP: update_meta_step_hp_space - })) + }) assert isinstance(p.hyperparams_space, HyperparameterSpace) assert p.hyperparams_space[META_STEP_HP] == update_meta_step_hp_space @@ -118,9 +123,9 @@ def test_step_cloner_update_hyperparams_space_should_update_wrapped_step_hyperpa })) updated_some_step_hp_space = RandInt(0, 400) - p.update_hyperparams_space(HyperparameterSpace({ + p.update_hyperparams_space({ SOME_STEP_HP: updated_some_step_hp_space - })) + }) assert isinstance(p.hyperparams, HyperparameterSamples) assert p.hyperparams_space[META_STEP_HP] == RAND_INT_META_STEP @@ -130,10 +135,10 @@ def test_step_cloner_update_hyperparams_space_should_update_wrapped_step_hyperpa def test_step_cloner_should_set_steps_hyperparams(): p = StepClonerForEachDataInput(SomeStep()) - p.set_hyperparams(HyperparameterSamples({ + p.set_hyperparams({ META_STEP_HP: META_STEP_HP_VALUE, SOME_STEP_HP: SOME_STEP_HP_VALUE - })) + }) assert isinstance(p.hyperparams, HyperparameterSamples) assert isinstance(p.get_step().hyperparams, HyperparameterSamples) @@ -143,10 +148,10 @@ def test_step_cloner_should_set_steps_hyperparams(): def test_step_cloner_should_set_steps_hyperparams_space(): p = StepClonerForEachDataInput(SomeStep()) - p.set_hyperparams_space(HyperparameterSpace({ + p.set_hyperparams_space({ META_STEP_HP: RAND_INT_STEP_CLONER, SOME_STEP_HP: RAND_INT_SOME_STEP - })) + }) assert isinstance(p.get_step().hyperparams_space, HyperparameterSpace) assert p.get_step().hyperparams_space[SOME_STEP_HP_KEY] == RAND_INT_SOME_STEP @@ -155,10 +160,10 @@ def test_step_cloner_should_set_steps_hyperparams_space(): def test_step_cloner_should_set_hyperparams_space(): p = StepClonerForEachDataInput(SomeStep()) - p.set_hyperparams_space(HyperparameterSpace({ + p.set_hyperparams_space({ META_STEP_HP: RAND_INT_STEP_CLONER, SOME_STEP_HP: RAND_INT_SOME_STEP - })) + }) assert isinstance(p.hyperparams_space, HyperparameterSpace) assert p.hyperparams_space[META_STEP_HP] == RAND_INT_STEP_CLONER @@ -181,12 +186,125 @@ def test_step_cloner_should_get_hyperparams_space(): RAND_INT_META_STEP = RandInt(0, 10) +def test_pipeline_should_set_hyperparams(): + p = Pipeline([ + SomeStep().set_name('step_1'), + SomeStep().set_name('step_2') + ]) + + p.set_hyperparams({ + 'hp': 1, + 'step_1__hp': 2, + 'step_2__hp': 3 + }) + + assert isinstance(p.hyperparams, HyperparameterSamples) + assert p.hyperparams['hp'] == 1 + assert p[0].hyperparams['hp'] == 2 + assert p[1].hyperparams['hp'] == 3 + + +def test_pipeline_should_get_hyperparams(): + p = Pipeline([ + SomeStep().set_name('step_1'), + SomeStep().set_name('step_2') + ]) + p.set_hyperparams({ + 'hp': 1, + 'step_1__hp': 2, + 'step_2__hp': 3 + }) + + hyperparams = p.get_hyperparams() + + assert isinstance(hyperparams, HyperparameterSamples) + assert hyperparams['hp'] == 1 + assert hyperparams['step_1__hp'] == 2 + assert hyperparams['step_2__hp'] == 3 + + +def test_pipeline_should_get_hyperparams_space(): + p = Pipeline([ + SomeStep().set_name('step_1'), + SomeStep().set_name('step_2') + ]) + p.set_hyperparams_space({ + 'hp': RandInt(1, 2), + 'step_1__hp': RandInt(2, 3), + 'step_2__hp': RandInt(3, 4) + }) + + hyperparams_space = p.get_hyperparams_space() + + assert isinstance(hyperparams_space, HyperparameterSpace) + + assert hyperparams_space['hp'].min_included == 1 + assert hyperparams_space['hp'].max_included == 2 + + assert hyperparams_space['step_1__hp'].min_included == 2 + assert hyperparams_space['step_1__hp'].max_included == 3 + + assert hyperparams_space['step_2__hp'].min_included == 3 + assert hyperparams_space['step_2__hp'].max_included == 4 + + +def test_pipeline_should_update_hyperparams(): + p = Pipeline([ + SomeStep().set_name('step_1'), + SomeStep().set_name('step_2') + ]) + + p.set_hyperparams({ + 'hp': 1, + 'step_1__hp': 2, + 'step_2__hp': 3 + }) + + p.update_hyperparams({ + 'hp': 4, + 'step_2__hp': 6 + }) + + assert isinstance(p.hyperparams, HyperparameterSamples) + assert p.hyperparams['hp'] == 4 + assert p[0].hyperparams['hp'] == 2 + assert p[1].hyperparams['hp'] == 6 + + +def test_pipeline_should_update_hyperparams_space(): + p = Pipeline([ + SomeStep().set_name('step_1'), + SomeStep().set_name('step_2') + ]) + + p.set_hyperparams_space({ + 'hp': RandInt(1, 2), + 'step_1__hp': RandInt(2, 3), + 'step_2__hp': RandInt(3, 4) + }) + p.update_hyperparams_space({ + 'hp': RandInt(4, 6), + 'step_2__hp': RandInt(6, 8) + }) + + assert isinstance(p.hyperparams_space, HyperparameterSpace) + + assert p.hyperparams_space['hp'].min_included == 4 + assert p.hyperparams_space['hp'].max_included == 6 + + assert p[0].hyperparams_space['hp'].min_included == 2 + assert p[0].hyperparams_space['hp'].max_included == 3 + + assert p[1].hyperparams_space['hp'].min_included == 6 + assert p[1].hyperparams_space['hp'].max_included == 8 + + def test_meta_step_mixin_should_get_hyperparams(): p = SomeMetaStepMixin(SomeStep()) - p.set_hyperparams(HyperparameterSamples({ + p.set_hyperparams({ META_STEP_HP: META_STEP_HP_VALUE, SOME_STEP_HP: SOME_STEP_HP_VALUE - })) + }) hyperparams = p.get_hyperparams() @@ -197,10 +315,10 @@ def test_meta_step_mixin_should_get_hyperparams(): def test_meta_step_mixin_should_set_hyperparams(): p = SomeMetaStepMixin(SomeStep()) - p.set_hyperparams(HyperparameterSamples({ + p.set_hyperparams({ META_STEP_HP: META_STEP_HP_VALUE, SOME_STEP_HP: SOME_STEP_HP_VALUE - })) + }) assert isinstance(p.hyperparams, HyperparameterSamples) assert p.hyperparams[META_STEP_HP] == META_STEP_HP_VALUE @@ -209,14 +327,14 @@ def test_meta_step_mixin_should_set_hyperparams(): def test_meta_step_mixin_update_hyperparams_should_update_meta_step_hyperparams(): p = SomeMetaStepMixin(SomeStep()) - p.set_hyperparams(HyperparameterSamples({ + p.set_hyperparams({ META_STEP_HP: META_STEP_HP_VALUE, SOME_STEP_HP: SOME_STEP_HP_VALUE - })) + }) - p.update_hyperparams(HyperparameterSamples({ + p.update_hyperparams({ META_STEP_HP: META_STEP_HP_VALUE + 1 - })) + }) assert p.hyperparams[META_STEP_HP] == META_STEP_HP_VALUE + 1 assert p.get_step().get_hyperparams()['somestep_hyperparam'] == SOME_STEP_HP_VALUE @@ -224,14 +342,14 @@ def test_meta_step_mixin_update_hyperparams_should_update_meta_step_hyperparams( def test_meta_step_mixin_update_hyperparams_should_update_wrapped_step_hyperparams(): p = SomeMetaStepMixin(SomeStep()) - p.set_hyperparams(HyperparameterSamples({ + p.set_hyperparams({ META_STEP_HP: META_STEP_HP_VALUE, SOME_STEP_HP: SOME_STEP_HP_VALUE - })) + }) - p.update_hyperparams(HyperparameterSamples({ + p.update_hyperparams({ SOME_STEP_HP: SOME_STEP_HP_VALUE + 1 - })) + }) assert p.hyperparams[META_STEP_HP] == META_STEP_HP_VALUE assert p.get_step().get_hyperparams()['somestep_hyperparam'] == SOME_STEP_HP_VALUE + 1 @@ -245,9 +363,9 @@ def test_meta_step_mixin_update_hyperparams_space_should_update_meta_step_hyperp })) update_meta_step_hp_space = RandInt(0, 100) - p.update_hyperparams_space(HyperparameterSpace({ + p.update_hyperparams_space({ META_STEP_HP: update_meta_step_hp_space - })) + }) assert p.hyperparams_space[META_STEP_HP] == update_meta_step_hp_space assert p.wrapped.get_hyperparams_space()['somestep_hyperparam'] == RAND_INT_SOME_STEP @@ -255,15 +373,15 @@ def test_meta_step_mixin_update_hyperparams_space_should_update_meta_step_hyperp def test_meta_step_mixin_update_hyperparams_space_should_update_wrapped_step_hyperparams(): p = SomeMetaStepMixin(SomeStep()) - p.set_hyperparams_space(HyperparameterSpace({ + p.set_hyperparams_space({ META_STEP_HP: RAND_INT_META_STEP, SOME_STEP_HP: RAND_INT_SOME_STEP - })) + }) updated_some_step_hp_space = RandInt(0, 100) - p.update_hyperparams_space(HyperparameterSpace({ + p.update_hyperparams_space({ SOME_STEP_HP: updated_some_step_hp_space - })) + }) assert p.hyperparams_space[META_STEP_HP] == RAND_INT_META_STEP assert p.wrapped.get_hyperparams_space()['somestep_hyperparam'] == updated_some_step_hp_space @@ -271,10 +389,10 @@ def test_meta_step_mixin_update_hyperparams_space_should_update_wrapped_step_hyp def test_meta_step_mixin_should_set_hyperparams_space(): p = SomeMetaStepMixin(SomeStep()) - p.set_hyperparams_space(HyperparameterSpace({ + p.set_hyperparams_space({ META_STEP_HP: RAND_INT_META_STEP, SOME_STEP_HP: RAND_INT_SOME_STEP - })) + }) assert p.hyperparams_space[META_STEP_HP] == RAND_INT_META_STEP assert p.get_step().hyperparams_space[SOME_STEP_HP_KEY] == RAND_INT_SOME_STEP @@ -282,10 +400,10 @@ def test_meta_step_mixin_should_set_hyperparams_space(): def test_meta_step_mixin_should_get_hyperparams_space(): p = SomeMetaStepMixin(SomeStep()) - p.set_hyperparams_space(HyperparameterSpace({ + p.set_hyperparams_space({ META_STEP_HP: RAND_INT_META_STEP, SOME_STEP_HP: RAND_INT_SOME_STEP - })) + }) hyperparams_space = p.get_hyperparams_space() diff --git a/testing/hyperparams/test_space.py b/testing/hyperparams/test_space.py index 87ad9de3..9b83cff1 100644 --- a/testing/hyperparams/test_space.py +++ b/testing/hyperparams/test_space.py @@ -24,8 +24,7 @@ import pytest from neuraxle.hyperparams.distributions import * -from neuraxle.hyperparams.space import flat_to_nested_dict, nested_dict_to_flat, HyperparameterSpace, \ - HyperparameterSamples +from neuraxle.hyperparams.space import HyperparameterSpace, HyperparameterSamples, RecursiveDict hyperparams_flat_and_dict_pairs = [ # Pair 1: @@ -55,14 +54,14 @@ @pytest.mark.parametrize("flat,expected_dic", hyperparams_flat_and_dict_pairs) def test_flat_to_dict_hyperparams(flat: dict, expected_dic: dict): - dic = flat_to_nested_dict(flat) + dic = RecursiveDict(flat).to_nested_dict_as_dict_primitive() assert dict(dic) == dict(expected_dic) @pytest.mark.parametrize("expected_flat,dic", hyperparams_flat_and_dict_pairs) def test_dict_to_flat_hyperparams(expected_flat: dict, dic: dict): - flat = nested_dict_to_flat(dic) + flat = RecursiveDict(dic).to_flat_as_dict_primitive() pprint(dict(flat)) pprint(expected_flat) diff --git a/testing/test_apply.py b/testing/test_apply.py index 99c29a29..9203e98b 100644 --- a/testing/test_apply.py +++ b/testing/test_apply.py @@ -1,43 +1,29 @@ -import numpy as np - +from neuraxle.base import Identity from neuraxle.hyperparams.space import HyperparameterSamples from neuraxle.pipeline import Pipeline -from neuraxle.steps.misc import TapeCallbackFunction from neuraxle.steps.numpy import MultiplyByN from neuraxle.steps.output_handlers import OutputTransformerWrapper -DATA_INPUTS = np.array([1]) -EXPECTED_OUTPUTS = np.array([1]) - -tape_transform = TapeCallbackFunction() -tape_fit = TapeCallbackFunction() - - -def test_apply_on_pipeline_should_call_method_on_each_steps(): - pipeline = Pipeline([MultiplyByN(1), MultiplyByN(1)]) - - pipeline.apply('set_hyperparams', hyperparams=HyperparameterSamples({'multiply_by': 2})) - - assert pipeline.get_hyperparams()['multiply_by'] == 2 - assert pipeline['MultiplyByN'].get_hyperparams()['multiply_by'] == 2 - assert pipeline['MultiplyByN1'].get_hyperparams()['multiply_by'] == 2 - def test_apply_on_pipeline_with_positional_argument_should_call_method_on_each_steps(): pipeline = Pipeline([MultiplyByN(1), MultiplyByN(1)]) - pipeline.apply('set_hyperparams', hyperparams=HyperparameterSamples({'multiply_by': 2})) + pipeline.apply('_set_hyperparams', hyperparams=HyperparameterSamples({ + 'multiply_by': 2, + 'MultiplyByN__multiply_by': 3, + 'MultiplyByN1__multiply_by': 4 + })) assert pipeline.get_hyperparams()['multiply_by'] == 2 - assert pipeline['MultiplyByN'].get_hyperparams()['multiply_by'] == 2 - assert pipeline['MultiplyByN1'].get_hyperparams()['multiply_by'] == 2 + assert pipeline['MultiplyByN'].get_hyperparams()['multiply_by'] == 3 + assert pipeline['MultiplyByN1'].get_hyperparams()['multiply_by'] == 4 def test_apply_method_on_pipeline_should_call_method_on_each_steps(): pipeline = Pipeline([MultiplyByN(1), MultiplyByN(1)]) - pipeline.apply_method( - lambda step: step.set_hyperparams(HyperparameterSamples({'multiply_by': 2})) + pipeline.apply( + lambda step: step._set_hyperparams(HyperparameterSamples({'multiply_by': 2})) ) assert pipeline.get_hyperparams()['multiply_by'] == 2 @@ -45,59 +31,98 @@ def test_apply_method_on_pipeline_should_call_method_on_each_steps(): assert pipeline['MultiplyByN1'].get_hyperparams()['multiply_by'] == 2 -def test_apply_method_on_pipeline_with_positional_argument_should_call_method_on_each_steps(): - pipeline = Pipeline([MultiplyByN(1), MultiplyByN(1)]) - - pipeline.apply_method( - lambda step, hyperparams: step.set_hyperparams(hyperparams), - hyperparams=HyperparameterSamples({'multiply_by': 2}) - ) - - assert pipeline.get_hyperparams()['multiply_by'] == 2 - assert pipeline['MultiplyByN'].get_hyperparams()['multiply_by'] == 2 - assert pipeline['MultiplyByN1'].get_hyperparams()['multiply_by'] == 2 - - -def test_apply_on_pipeline_with_meta_step_should_call_method_on_each_steps(): +def test_apply_on_pipeline_with_meta_step_and_positional_argument(): pipeline = Pipeline([OutputTransformerWrapper(MultiplyByN(1)), MultiplyByN(1)]) - pipeline.apply('set_hyperparams', hyperparams=HyperparameterSamples({'multiply_by': 2})) + pipeline.apply('_set_hyperparams', hyperparams=HyperparameterSamples({ + 'multiply_by': 2, + 'OutputTransformerWrapper__multiply_by': 3, + 'OutputTransformerWrapper__MultiplyByN__multiply_by': 4, + 'MultiplyByN__multiply_by': 5 + })) assert pipeline.get_hyperparams()['multiply_by'] == 2 - assert pipeline['OutputTransformerWrapper'].wrapped.get_hyperparams()['multiply_by'] == 2 - assert pipeline['MultiplyByN'].get_hyperparams()['multiply_by'] == 2 - - -def test_apply_on_pipeline_with_meta_step_and_positional_argument_should_call_method_on_each_steps(): - pipeline = Pipeline([OutputTransformerWrapper(MultiplyByN(1)), MultiplyByN(1)]) - - pipeline.apply('set_hyperparams', hyperparams=HyperparameterSamples({'multiply_by': 2})) - - assert pipeline.get_hyperparams()['multiply_by'] == 2 - assert pipeline['OutputTransformerWrapper'].wrapped.get_hyperparams()['multiply_by'] == 2 - assert pipeline['MultiplyByN'].get_hyperparams()['multiply_by'] == 2 + assert pipeline['OutputTransformerWrapper'].get_hyperparams()['multiply_by'] == 3 + assert pipeline['OutputTransformerWrapper'].wrapped.get_hyperparams()['multiply_by'] == 4 + assert pipeline['MultiplyByN'].get_hyperparams()['multiply_by'] == 5 def test_apply_method_on_pipeline_with_meta_step_should_call_method_on_each_steps(): pipeline = Pipeline([OutputTransformerWrapper(MultiplyByN(1)), MultiplyByN(1)]) - pipeline.apply_method( - lambda step: step.set_hyperparams(HyperparameterSamples({'multiply_by': 2})) + pipeline.apply( + lambda step: step._set_hyperparams(HyperparameterSamples({'multiply_by': 2})) ) assert pipeline.get_hyperparams()['multiply_by'] == 2 + assert pipeline['OutputTransformerWrapper'].get_hyperparams()['multiply_by'] == 2 assert pipeline['OutputTransformerWrapper'].wrapped.get_hyperparams()['multiply_by'] == 2 assert pipeline['MultiplyByN'].get_hyperparams()['multiply_by'] == 2 -def test_apply_method_on_pipeline_with_meta_step_and_positional_argument_should_call_method_on_each_steps(): - pipeline = Pipeline([OutputTransformerWrapper(MultiplyByN(1)), MultiplyByN(1)]) - - pipeline.apply_method( - lambda step, hyperparams: step.set_hyperparams(hyperparams), - hyperparams=HyperparameterSamples({'multiply_by': 2}) - ) - - assert pipeline.get_hyperparams()['multiply_by'] == 2 - assert pipeline['OutputTransformerWrapper'].wrapped.get_hyperparams()['multiply_by'] == 2 - assert pipeline['MultiplyByN'].get_hyperparams()['multiply_by'] == 2 +def test_has_children_mixin_apply_should_apply_method_to_direct_childrends(): + p = Pipeline([ + ('a', Identity()), + ('b', Identity()), + Pipeline([ + ('c', Identity()), + ('d', Identity()) + ]), + ]) + + p.apply('_set_hyperparams', ra=None, hyperparams=HyperparameterSamples({ + 'a__hp': 0, + 'b__hp': 1, + 'Pipeline__hp': 2 + })) + + assert p['a'].hyperparams.to_flat_as_dict_primitive()['hp'] == 0 + assert p['b'].hyperparams.to_flat_as_dict_primitive()['hp'] == 1 + assert p['Pipeline'].hyperparams.to_flat_as_dict_primitive()['hp'] == 2 + + +def test_has_children_mixin_apply_should_apply_method_to_recursive_childrends(): + p = Pipeline([ + ('a', Identity()), + ('b', Identity()), + Pipeline([ + ('c', Identity()), + ('d', Identity()) + ]), + ]) + + p.apply('_set_hyperparams', ra=None, hyperparams=HyperparameterSamples({ + 'Pipeline__c__hp': 3, + 'Pipeline__d__hp': 4 + })) + + assert p['Pipeline']['c'].hyperparams.to_flat_as_dict_primitive()['hp'] == 3 + assert p['Pipeline']['d'].hyperparams.to_flat_as_dict_primitive()['hp'] == 4 + + +def test_has_children_mixin_apply_should_return_recursive_dict_to_direct_childrends(): + p = Pipeline([ + ('a', Identity().set_hyperparams(HyperparameterSamples({'hp': 0}))), + ('b', Identity().set_hyperparams(HyperparameterSamples({'hp': 1}))) + ]) + + results = p.apply('_get_hyperparams', ra=None) + + assert results.to_flat_as_dict_primitive()['a__hp'] == 0 + assert results.to_flat_as_dict_primitive()['b__hp'] == 1 + + +def test_has_children_mixin_apply_should_return_recursive_dict_to_recursive_childrends(): + p = Pipeline([ + Pipeline([ + ('c', Identity().set_hyperparams(HyperparameterSamples({'hp': 3}))), + ('d', Identity().set_hyperparams(HyperparameterSamples({'hp': 4}))) + ]).set_hyperparams(HyperparameterSamples({'hp': 2})), + ]) + + results = p.apply('_get_hyperparams', ra=None) + results = results.to_flat_as_dict_primitive() + + assert results['Pipeline__hp'] == 2 + assert results['Pipeline__c__hp'] == 3 + assert results['Pipeline__d__hp'] == 4 diff --git a/testing/test_deep_learning_pipeline.py b/testing/test_deep_learning_pipeline.py index 1a60bd17..0a86941b 100644 --- a/testing/test_deep_learning_pipeline.py +++ b/testing/test_deep_learning_pipeline.py @@ -37,13 +37,13 @@ def test_deep_learning_pipeline(): # When p, outputs = p.fit_transform(data_inputs, expected_outputs) - metrics = p.apply('get_metrics') + metrics = p.apply('get_metrics').to_flat_as_dict_primitive() # Then - batch_mse_train = metrics['DeepLearningPipeline__EpochRepeater__validation_split_wrapper__epoch_metrics']['train']['mse'] - epoch_mse_train = metrics['DeepLearningPipeline__EpochRepeater__validation_split_wrapper__epoch_metrics__TrainShuffled__MiniBatchSequentialPipeline__batch_metrics']['train']['mse'] + batch_mse_train = metrics['EpochRepeater__validation_split_wrapper__epoch_metrics__train__mse'] + epoch_mse_train = metrics['EpochRepeater__validation_split_wrapper__epoch_metrics__TrainShuffled__MiniBatchSequentialPipeline__batch_metrics__train__mse'] - epoch_mse_validation = metrics['DeepLearningPipeline__EpochRepeater__validation_split_wrapper__epoch_metrics']['validation']['mse'] + epoch_mse_validation = metrics['EpochRepeater__validation_split_wrapper__epoch_metrics__validation__mse'] assert len(epoch_mse_train) == N_EPOCHS assert len(epoch_mse_validation) == N_EPOCHS diff --git a/testing/test_optional.py b/testing/test_optional.py index 2b83787a..6bb4720f 100644 --- a/testing/test_optional.py +++ b/testing/test_optional.py @@ -7,8 +7,8 @@ def test_optional_should_disable_wrapped_step_when_disabled(): p = Optional(MultiplyByN(2), nullified_return_value=[]).set_hyperparams(HyperparameterSamples({ - 'enabled': False - })) + 'enabled': False + })) data_inputs = np.array(list(range(10))) outputs = p.transform(data_inputs) @@ -18,8 +18,8 @@ def test_optional_should_disable_wrapped_step_when_disabled(): def test_optional_should_enable_wrapped_step_when_enabled(): p = Optional(MultiplyByN(2), nullified_return_value=[]).set_hyperparams(HyperparameterSamples({ - 'enabled': True - })) + 'enabled': True + })) data_inputs = np.array(list(range(10))) outputs = p.transform(data_inputs) diff --git a/testing/test_pickle_checkpoint_step.py b/testing/test_pickle_checkpoint_step.py index c1504751..235e7748 100644 --- a/testing/test_pickle_checkpoint_step.py +++ b/testing/test_pickle_checkpoint_step.py @@ -91,7 +91,8 @@ def test_when_no_hyperparams_should_save_checkpoint_pickle(tmpdir: LocalPath): def test_when_hyperparams_should_save_checkpoint_pickle(tmpdir: LocalPath): tape = TapeCallbackFunction() pickle_checkpoint_step = DefaultCheckpoint() - pipeline = create_pipeline(tmpdir, pickle_checkpoint_step, tape, HyperparameterSamples({"a__learning_rate": 1})) + pipeline = create_pipeline(tmpdir, pickle_checkpoint_step, tape, + HyperparameterSamples({"a__learning_rate": 1})) pipeline, actual_data_inputs = pipeline.fit_transform(data_inputs, expected_outputs) diff --git a/testing/test_pipeline.py b/testing/test_pipeline.py index d6539904..b1f77c52 100644 --- a/testing/test_pipeline.py +++ b/testing/test_pipeline.py @@ -23,7 +23,7 @@ import pytest from neuraxle.hyperparams.distributions import RandInt, LogUniform -from neuraxle.hyperparams.space import nested_dict_to_flat, HyperparameterSpace +from neuraxle.hyperparams.space import HyperparameterSpace, RecursiveDict from neuraxle.pipeline import Pipeline from neuraxle.steps.misc import TransformCallbackStep, TapeCallbackFunction from neuraxle.steps.numpy import NumpyTranspose @@ -117,9 +117,9 @@ def test_pipeline_set_one_hyperparam_level_one_flat(): "a__learning_rate": 7 }) - assert p["a"].hyperparams["learning_rate"] == 7 - assert p["b"].hyperparams == dict() - assert p["c"].hyperparams == dict() + assert p["a"].hyperparams.to_flat_as_dict_primitive()["learning_rate"] == 7 + assert p["b"].hyperparams.to_flat_as_dict_primitive() == dict() + assert p["c"].hyperparams.to_flat_as_dict_primitive() == dict() def test_pipeline_set_one_hyperparam_level_one_dict(): @@ -157,9 +157,9 @@ def test_pipeline_set_one_hyperparam_level_two_flat(): print(p.get_hyperparams()) assert p["b"]["a"].hyperparams["learning_rate"] == 7 - assert p["b"]["c"].hyperparams == dict() - assert p["b"].hyperparams == dict() - assert p["c"].hyperparams == dict() + assert p["b"]["c"].hyperparams.to_flat_as_dict_primitive() == dict() + assert p["b"].hyperparams.to_flat_as_dict_primitive() == {'a__learning_rate': 7} + assert p["c"].hyperparams.to_flat_as_dict_primitive() == dict() def test_pipeline_set_one_hyperparam_level_two_dict(): @@ -183,10 +183,10 @@ def test_pipeline_set_one_hyperparam_level_two_dict(): }) print(p.get_hyperparams()) - assert p["b"]["a"].hyperparams["learning_rate"] == 7 - assert p["b"]["c"].hyperparams == dict() - assert p["b"].hyperparams["learning_rate"] == 9 - assert p["c"].hyperparams == dict() + assert p["b"]["a"].get_hyperparams()["learning_rate"] == 7 + assert p["b"]["c"].get_hyperparams() == dict() + assert p["b"].get_hyperparams()["learning_rate"] == 9 + assert p["c"].get_hyperparams() == dict() def test_pipeline_update_hyperparam_level_one_flat(): @@ -248,7 +248,10 @@ def test_pipeline_update_hyperparam_level_two_flat(): assert p["b"]["a"].hyperparams["learning_rate"] == 0.01 assert p["b"]["a"].hyperparams["other_hp"] == 8 assert p["b"]["c"].hyperparams == dict() - assert p["b"].hyperparams == dict() + assert p["b"].hyperparams.to_flat_as_dict_primitive() == { + 'a__learning_rate': 0.01, + 'a__other_hp': 8 + } assert p["c"].hyperparams == dict() @@ -297,7 +300,7 @@ def test_pipeline_tosklearn(): "b__learning_rate": 9 } }) - assert the_step.get_hyperparams()["learning_rate"] == 7 + assert p.get_hyperparams()['b__a__z__learning_rate'] == 7 p = p.tosklearn() p = sklearn.pipeline.Pipeline([('sk', p)]) @@ -311,12 +314,12 @@ def test_pipeline_tosklearn(): z_ = a_["z"] assert z_.get_params()["learning_rate"] == 11 - p.set_params(**nested_dict_to_flat({ + p.set_params(**RecursiveDict({ "sk__b": { "a__z__learning_rate": 12, "b__learning_rate": 9 } - })) + }).to_flat()) # p.set_params(**{"sk__b__a__z__learning_rate": 12}) assert p.named_steps["sk"].p["b"].wrapped_sklearn_predictor.named_steps["a"]["z"].get_params()[ "learning_rate"] == 12 diff --git a/testing/test_recursive_arguments.py b/testing/test_recursive_arguments.py new file mode 100644 index 00000000..c3768235 --- /dev/null +++ b/testing/test_recursive_arguments.py @@ -0,0 +1,54 @@ +from neuraxle.base import _RecursiveArguments +from neuraxle.hyperparams.space import HyperparameterSamples + + +def test_recursive_arguments_should_get_root_level(): + ra = _RecursiveArguments(hyperparams=HyperparameterSamples({ + 'hp0': 0, + 'hp1': 1, + 'pipeline__stepa__hp2': 2, + 'pipeline__stepb__hp3': 3 + })) + + root_ra = ra[None] + + root_ra.kargs == [] + root_ra.kwargs == {'hyperparams': HyperparameterSamples({ + 'hp0': 0, + 'hp1': 1 + })} + + +def test_recursive_arguments_should_get_recursive_levels(): + ra = _RecursiveArguments(hyperparams=HyperparameterSamples({ + 'hp0': 0, + 'hp1': 1, + 'stepa__hp2': 2, + 'stepb__hp3': 3, + 'stepb__stepd__hp4': 4 + })) + + ra = ra['stepb'] + + ra.kargs == [] + ra.kwargs == {'hyperparams': HyperparameterSamples({ + 'stepb__hp3': 2, + 'stepb__stepd__hp4': 4 + })} + + +def test_recursive_arguments_should_have_copy_constructor(): + ra = _RecursiveArguments( + ra=_RecursiveArguments(hyperparams=HyperparameterSamples({ + 'hp0': 0, + 'hp1': 1 + })) + ) + + ra.kargs == [] + ra.kwargs == {'hyperparams': HyperparameterSamples({ + 'hp0': 0, + 'hp1': 1, + })} + + diff --git a/testing/test_recursive_dict.py b/testing/test_recursive_dict.py new file mode 100644 index 00000000..0458e219 --- /dev/null +++ b/testing/test_recursive_dict.py @@ -0,0 +1,164 @@ +import pytest + +from neuraxle.base import Identity +from neuraxle.hyperparams.space import RecursiveDict, HyperparameterSamples + +POINT_SEPARATOR = '.' + + +@pytest.mark.parametrize("separator", ["__", ".", "___"]) +def test_recursive_dict_to_flat(separator): + dict_values = { + 'hp': 1, + 'stepa': { + 'hp': 2, + 'stepb': { + 'hp': 3 + } + } + } + r = RecursiveDict(separator=separator, **dict_values) + + r = r.to_flat_as_dict_primitive() + + expected_dict_values = { + 'hp': 1, + 'stepa{}hp'.format(separator): 2, + 'stepa{0}stepb{0}hp'.format(separator): 3 + } + assert r == expected_dict_values + + +def test_recursive_dict_to_flat_different_separator(): + dict_values = { + 'hp': 1, + 'stepa': { + 'hp': 2, + 'stepb': { + 'hp': 3 + } + } + } + r = RecursiveDict(separator='__', **dict_values) + r['stepa'] = RecursiveDict(r['stepa'], separator='.') + r['stepa']['stepb'] = RecursiveDict(r['stepa']['stepb'], separator='$$$') + + r = r.to_flat() + + expected_dict_values = { + 'hp': 1, + 'stepa.hp': 2, + 'stepa.stepb$$$hp': 3 + } + assert r.to_flat_as_dict_primitive() == expected_dict_values + + +def test_recursive_dict_to_nested_dict(): + dict_values = { + 'hp': 1, + 'stepa__hp': 2, + 'stepa__stepb__hp': 3 + } + r = HyperparameterSamples(**dict_values) + + r = r.to_nested_dict() + + expected_dict_values = { + 'hp': 1, + 'stepa': { + 'hp': 2, + 'stepb': { + 'hp': 3 + } + } + } + assert r == HyperparameterSamples(**expected_dict_values) + + +def test_recursive_dict_get_item(): + dict_values = { + 'hp': 1, + 'stepa__hp': 2, + 'stepa__stepb__hp': 3 + } + r = HyperparameterSamples(**dict_values) + + assert r[None].to_flat_as_dict_primitive() == {'hp': 1} + assert r['stepa'].to_flat_as_dict_primitive() == {'hp': 2, 'stepb__hp': 3} + + +def test_hyperparams_to_nested_dict(): + dict_values = { + 'hp': 1, + 'stepa__hp': 2, + 'stepa__stepb__hp': 3 + } + r = HyperparameterSamples(**dict_values) + + r = r.to_nested_dict() + + expected_dict_values = { + 'hp': 1, + 'stepa': { + 'hp': 2, + 'stepb': { + 'hp': 3 + } + } + } + assert r.to_nested_dict_as_dict_primitive() == expected_dict_values + + +def test_recursive_dict_copy_constructor(): + dict_values = { + 'hp': 1, + 'stepa__hp': 2, + 'stepa__stepb__hp': 3 + } + r = RecursiveDict(RecursiveDict(**dict_values), separator='__') + + assert r == RecursiveDict(**dict_values) + + +def test_recursive_dict_copy_constructor_should_set_separator(): + dict_values = { + 'hp': 1, + 'stepa__hp': 2, + 'stepa__stepb__hp': 3 + } + r = RecursiveDict(RecursiveDict(**dict_values, separator=POINT_SEPARATOR)) + + assert r.separator == POINT_SEPARATOR + + +def test_hyperparams_copy_constructor(): + dict_values = { + 'hp': 1, + 'stepa__hp': 2, + 'stepa__stepb__hp': 3 + } + r = HyperparameterSamples(HyperparameterSamples(**dict_values)) + + assert r == HyperparameterSamples(**dict_values) + + +def test_hyperparams_to_flat(): + dict_values = { + 'hp': 1, + 'stepa': { + 'hp': 2, + 'stepb': { + 'hp': 3 + } + } + } + r = HyperparameterSamples(**dict_values) + + r = r.to_flat() + + expected_dict_values = { + 'hp': 1, + 'stepa__hp': 2, + 'stepa__stepb__hp': 3 + } + assert r == HyperparameterSamples(**expected_dict_values) diff --git a/testing/test_union.py b/testing/test_union.py index 65583f1d..353fa8fb 100644 --- a/testing/test_union.py +++ b/testing/test_union.py @@ -78,7 +78,7 @@ def test_feature_union_should_apply_to_self_and_sub_steps(): ], joiner=NumpyTranspose()) ]) - p.apply('set_hyperparams', hyperparams=HyperparameterSamples({'applied': True})) + p.apply(lambda step: step._set_hyperparams(HyperparameterSamples({'applied': True}))) assert p.hyperparams['applied'] assert p['FeatureUnion'].hyperparams['applied']