Source code for mindfoundry.optaas.client.sklearn_pipelines.mixin

from abc import ABC, abstractmethod
from typing import Any, List

from sklearn.base import BaseEstimator

from mindfoundry.optaas.client.parameter import GroupParameter, ChoiceParameter
from mindfoundry.optaas.client.sklearn_pipelines.parameter_maker import SklearnParameterMaker
from mindfoundry.optaas.client.sklearn_pipelines.utils import _make_id, _get_all_parameters_and_constraints_and_prior_means, \
    MissingArgumentError, Estimator, EstimatorTuple, ParametersConstraintsAndPriorMeans, Optimizable


[docs]class OptimizablePipeline(Optimizable): """A pipeline-like object to which will be used to generate parameters and constraints for optimization. Args: estimators (List[EstimatorTuple]): List of (name, estimator) tuples as you would provide when creating a sklearn :class:`.Pipeline`. An estimator can be: * A subclass of :class:`.OptimizableBaseEstimator` * A subclass of :class:`.BaseEstimator` (in which case a warning will be displayed informing you that it won't be optimized) * An :meth:`~mindfoundry.optaas.client.sklearn_pipelines.mixin.optional_step` * A :meth:`~mindfoundry.optaas.client.sklearn_pipelines.mixin.choice` or :meth:`~mindfoundry.optaas.client.sklearn_pipelines.mixin.optional_choice` * Another :class:`.OptimizablePipeline` optional (bool): Whether this will be an optional step (defaults to False). """ def __init__(self, estimators: List[EstimatorTuple], optional: bool = False) -> None: self.estimators = estimators self.optional = optional
[docs] def make_all_parameters_constraints_and_prior_means(self, estimator_name: str, id_prefix: str, **kwargs) -> ParametersConstraintsAndPriorMeans: pipeline_name = estimator_name prefix = id_prefix + pipeline_name + '__' parameters, constraints, prior_means\ = _get_all_parameters_and_constraints_and_prior_means(self.estimators, prefix, **kwargs) grouped_parameter = GroupParameter(pipeline_name, id=_make_id(id_prefix + pipeline_name), items=parameters, optional=self.optional) return [grouped_parameter], constraints, prior_means
[docs]class OptimizableBaseEstimator(BaseEstimator, Optimizable, ABC): """Mixin that allows an estimator to be optimized by OPTaaS. Subclasses must implement `make_parameters_and_constraints`."""
[docs] @abstractmethod def make_parameters_constraints_and_prior_means(self, sk: SklearnParameterMaker, **kwargs) \ -> ParametersConstraintsAndPriorMeans: """Abstract method that should generate the :class:`Parameters <.Parameter>` and :class:`Constraints <.Constraint>` required to optimize a sklearn estimator. When implementing this method, make sure to use the :class:`SklearnParameterMaker` `sk` to create parameters e.g. call `sk.IntParameter(...)` instead of `IntParameter(...)`. If the parameter you want to optimize can take values of different types, use a :meth:`ChoiceParameter <.SklearnParameterMaker.ChoiceParameter>`. For an example, see `n_components` in :class:`.PCA`. If the parameter value needs to be a list or array, use a :meth:`GroupParameter <.SklearnParameterMaker.GroupParameter>`. For an example, see `weights` in :class:`.VotingClassifier`. Args: sk (SklearnParameterMaker): Allows you to create parameters with the correct names and defaults. kwargs: Additional arguments required to optimize certain estimators, e.g. `feature_count` (number of features in your data set, required to optimize :class:`.PCA`) Returns: A tuple of 3 lists (:class:`Parameters <.Parameter>`, :class:`Constraints <.Constraint>`, :class:`PriorMeans <.PriorMeans>`) Raises: :class:`.MissingArgumentError` if a required argument is missing from `kwargs`. """
[docs] def get_required_kwarg(self, kwargs, arg_name: str) -> Any: """Returns value of a kwarg required to optimize this estimator. Raises error if argument not set. Args: kwargs: Arguments taken from :meth:`.make_parameters_and_constraints`. arg_name (str): Name of the required argument. Returns: Value of argument. Raises: :class:`.MissingArgumentError` if the argument is not present. """ if arg_name not in kwargs: raise MissingArgumentError(arg_name, self) return kwargs[arg_name]
[docs] def make_all_parameters_constraints_and_prior_means(self, estimator_name: str, id_prefix: str, **kwargs) -> ParametersConstraintsAndPriorMeans: estimator_id = _make_id(id_prefix + estimator_name) parameter_maker = SklearnParameterMaker(estimator_id, self) parameters, constraints, prior_means = self.make_parameters_constraints_and_prior_means(parameter_maker, **kwargs) grouped_parameter = GroupParameter(estimator_name, id=estimator_id, items=parameters, optional=isinstance(self, OptionalStepMixin)) return [grouped_parameter], constraints, prior_means
[docs]class OptionalStepMixin(OptimizableBaseEstimator): """Mixin that allows an estimator to be optional, i.e. it may be omitted from a :class:`.Configuration` generated by OPTaaS. Example: `class MyEstimator(OptionalStepMixin):` Your estimator can define `make_parameters_and_constraints` if you wish to optimize its parameters, or you can leave it undefined and use the default provided below. """
[docs] def make_parameters_constraints_and_prior_means(self, sk: SklearnParameterMaker, **kwargs) -> ParametersConstraintsAndPriorMeans: """A default implementation for when you need an optional estimator without optimizing any of its parameters.""" return [], [], []
[docs]class EstimatorChoice(Optimizable): """Allows OPTaaS to choose one of many estimators for a step in a pipeline. Args: *estimators (Estimator): Estimators from which to choose. optional (bool): Whether this will be an optional step (defaults to False). """ def __init__(self, *estimators: Estimator, optional: bool = False) -> None: self.estimators = estimators self.optional = optional
[docs] def make_all_parameters_constraints_and_prior_means(self, estimator_name: str, id_prefix: str, **kwargs) -> ParametersConstraintsAndPriorMeans: choices, constraints, prior_means = _get_all_parameters_and_constraints_and_prior_means([ (str(i), estimator) for i, estimator in enumerate(self.estimators) ], id_prefix + estimator_name + '__', **kwargs) choice_parameter = ChoiceParameter(estimator_name, id=_make_id(estimator_name), choices=choices, optional=self.optional) return [choice_parameter], constraints, prior_means
[docs]def optional_step(estimator: Estimator) -> OptionalStepMixin: """Wrapper method to easily make an estimator optional in an OPTaaS :class:`.SklearnTask`. The :class:`.OptionalStepMixin` class will be added to the estimator object's base classes (only this instance will be affected, not the entire class). Example: `create_sklearn_task(estimators=[ ('my_optional_step', optional_step(MyEstimator())) ])` """ estimator_type = type(estimator) estimator.__class__ = type(estimator_type.__name__, (estimator_type, OptionalStepMixin), {}) return estimator # type: ignore
[docs]def choice(*estimators: Estimator) -> EstimatorChoice: """Convenience method for creating a choice of estimators in a pipeline.""" return EstimatorChoice(*estimators)
[docs]def optional_choice(*estimators: Estimator) -> EstimatorChoice: """Convenience method for creating a choice of estimators as an optional step in a pipeline.""" return EstimatorChoice(*estimators, optional=True)