diff --git a/examples/advanced/surrogate_optimization.py b/examples/advanced/surrogate_optimization.py new file mode 100644 index 0000000000..ed64726d5a --- /dev/null +++ b/examples/advanced/surrogate_optimization.py @@ -0,0 +1,48 @@ +from functools import partial +from typing import Any + +from golem.core.optimisers.meta.surrogate_model import SurrogateModel +from golem.core.optimisers.meta.surrogate_optimizer import SurrogateEachNgenOptimizer + +from examples.simple.time_series_forecasting.api_forecasting import get_ts_data +from fedot.api.main import Fedot +from fedot.core.repository.tasks import Task, TaskTypesEnum, TsForecastingParams + + +class GraphLenSurrogateModel(SurrogateModel): + def __call__(self, graph, **kwargs: Any): + # example how we can get input data from objective + input_data = kwargs.get('objective').__self__.input_data + print(input_data.features.shape) # for pep8 + return [len(graph.nodes)] + + +def run_ts_forecasting_example(dataset='australia', horizon: int = 30, validation_blocks=2, timeout: float = None, + visualization=False, with_tuning=True): + train_data, test_data = get_ts_data(dataset, horizon, validation_blocks) + # init model for the time series forecasting + model = Fedot(problem='ts_forecasting', + task_params=Task(TaskTypesEnum.ts_forecasting, + TsForecastingParams(forecast_length=horizon)).task_params, + timeout=timeout, + n_jobs=-1, + with_tuning=with_tuning, + cv_folds=2, validation_blocks=validation_blocks, preset='fast_train', + optimizer=partial(SurrogateEachNgenOptimizer, surrogate_model=GraphLenSurrogateModel())) + + # run AutoML model design in the same way + pipeline = model.fit(train_data) + + # use model to obtain two-step in-sample forecast + model.predict(test_data) + print('Metrics for two-step in-sample forecast: ', + model.get_metrics(metric_names=['rmse', 'mae', 'mape'])) + + # plot forecasting result + if visualization: + pipeline.show() + model.plot_prediction() + + +if __name__ == '__main__': + run_ts_forecasting_example(visualization=True, timeout=3) diff --git a/fedot/api/api_utils/api_composer.py b/fedot/api/api_utils/api_composer.py index 72f5469fe6..f570bacc0a 100644 --- a/fedot/api/api_utils/api_composer.py +++ b/fedot/api/api_utils/api_composer.py @@ -113,15 +113,14 @@ def compose_pipeline(self, train_data: InputData, initial_assumption: Sequence[P fitted_assumption: Pipeline) -> Tuple[Pipeline, List[Pipeline], GPComposer]: gp_composer: GPComposer = (ComposerBuilder(task=self.params.task) - .with_requirements(self.params.composer_requirements) - .with_initial_pipelines(initial_assumption) - .with_optimizer(self.params.get('optimizer')) - .with_optimizer_params(parameters=self.params.optimizer_params, - external_parameters=self.params.get('optimizer_external_params')) - .with_metrics(self.metrics.metric_functions) - .with_cache(self.pipelines_cache, self.preprocessing_cache) - .with_graph_generation_param(graph_generation_params=self.params.graph_generation_params) - .build()) + .with_requirements(self.params.composer_requirements) + .with_initial_pipelines(initial_assumption) + .with_optimizer(self.params.get('optimizer')) + .with_optimizer_params(parameters=self.params.optimizer_params) + .with_metrics(self.metrics.metric_functions) + .with_cache(self.pipelines_cache, self.preprocessing_cache) + .with_graph_generation_param(self.params.graph_generation_params) + .build()) if self.timer.have_time_for_composing(self.params.get('pop_size'), self.params.n_jobs): # Launch pipeline structure composition diff --git a/fedot/api/api_utils/api_params_repository.py b/fedot/api/api_utils/api_params_repository.py index b7afc7ba18..dfc79930f9 100644 --- a/fedot/api/api_utils/api_params_repository.py +++ b/fedot/api/api_utils/api_params_repository.py @@ -59,7 +59,6 @@ def default_params_for_task(task_type: TaskTypesEnum) -> dict: early_stopping_iterations=None, early_stopping_timeout=10, optimizer=None, - optimizer_external_params=None, collect_intermediate_metric=False, max_pipeline_fit_time=None, initial_assumption=None, diff --git a/fedot/api/main.py b/fedot/api/main.py index 09986ebac1..a48f8813d8 100644 --- a/fedot/api/main.py +++ b/fedot/api/main.py @@ -12,8 +12,8 @@ from fedot.api.api_utils.api_composer import ApiComposer from fedot.api.api_utils.api_data import ApiDataProcessor -from fedot.api.api_utils.input_analyser import InputAnalyser from fedot.api.api_utils.data_definition import FeaturesType, TargetType +from fedot.api.api_utils.input_analyser import InputAnalyser from fedot.api.api_utils.metrics import ApiMetrics from fedot.api.api_utils.params import ApiParams from fedot.api.api_utils.predefined_model import PredefinedModel @@ -161,8 +161,7 @@ class Fedot: :class:`~golem.core.optimisers.optimizer.GraphOptimizer` to specify a custom optimizer. Default optimizer is :class:`~golem.core.optimisers.genetic.gp_optimizer.EvoGraphOptimizer`. See the `example \ -`_. - optimizer_external_params (Dict[str, Any]): additional parameters for custom optimizer (if needed). +`_ """ def __init__(self, diff --git a/fedot/core/composer/composer_builder.py b/fedot/core/composer/composer_builder.py index 4a7850604d..9fce7c77d0 100644 --- a/fedot/core/composer/composer_builder.py +++ b/fedot/core/composer/composer_builder.py @@ -1,12 +1,11 @@ import platform from multiprocessing import set_start_method from pathlib import Path -from typing import Dict, List, Optional, Sequence, Type, Union +from typing import List, Optional, Sequence, Type, Union from golem.core.log import LoggerAdapter, default_log from golem.core.optimisers.genetic.gp_optimizer import EvoGraphOptimizer from golem.core.optimisers.genetic.gp_params import GPAlgorithmParameters -from fedot.core.pipelines.pipeline_composer_requirements import PipelineComposerRequirements from golem.core.optimisers.initial_graphs_generator import InitialPopulationGenerator, GenerationFunction from golem.core.optimisers.optimizer import GraphOptimizer, AlgorithmParameters, GraphGenerationParams from golem.core.utilities.data_structures import ensure_wrapped_in_sequence @@ -17,6 +16,7 @@ from fedot.core.composer.gp_composer.gp_composer import GPComposer from fedot.core.optimisers.objective.metrics_objective import MetricsObjective from fedot.core.pipelines.pipeline import Pipeline +from fedot.core.pipelines.pipeline_composer_requirements import PipelineComposerRequirements from fedot.core.pipelines.pipeline_graph_generation_params import get_pipeline_generation_params from fedot.core.pipelines.verification import rules_by_task from fedot.core.repository.operation_types_repository import get_operations_for_task @@ -46,7 +46,6 @@ def __init__(self, task: Task): self.optimizer_cls: Type[GraphOptimizer] = EvoGraphOptimizer # default optimizer class self.optimizer_parameters: Optional[AlgorithmParameters] = None - self.optimizer_external_parameters: dict = {} self.composer_cls: Type[Composer] = GPComposer # default composer class self.composer_requirements: Optional[PipelineComposerRequirements] = None @@ -72,12 +71,9 @@ def with_optimizer(self, optimizer_cls: Optional[Type[GraphOptimizer]]): return self def with_optimizer_params(self, parameters: Optional[AlgorithmParameters] = None, - external_parameters: Optional[Dict] = None, dispatcher=None): if parameters is not None: self.optimizer_parameters = parameters - if external_parameters is not None: - self.optimizer_external_parameters = external_parameters if dispatcher is not None: self.optimizer_parameters = dispatcher return self @@ -157,8 +153,7 @@ def build(self) -> Composer: initial_graphs=initial_population, requirements=self.composer_requirements, graph_generation_params=self.graph_generation_params, - graph_optimizer_params=self.optimizer_parameters, - **self.optimizer_external_parameters) + graph_optimizer_params=self.optimizer_parameters) composer = self.composer_cls(optimiser, self.composer_requirements, diff --git a/fedot/core/optimisers/objective/data_objective_eval.py b/fedot/core/optimisers/objective/data_objective_eval.py index d5ba48d1f3..93b066f128 100644 --- a/fedot/core/optimisers/objective/data_objective_eval.py +++ b/fedot/core/optimisers/objective/data_objective_eval.py @@ -146,3 +146,7 @@ def evaluate_intermediate_metrics(self, graph: Pipeline): validation_blocks=self._validation_blocks) # saving only the most important first metric node.metadata.metric = intermediate_fitness.values[0] + + @property + def input_data(self): + return self._data_producer.args[0] diff --git a/test/unit/api/test_api_params.py b/test/unit/api/test_api_params.py index 049639db2b..6bfae87d37 100644 --- a/test/unit/api/test_api_params.py +++ b/test/unit/api/test_api_params.py @@ -13,7 +13,6 @@ from fedot.core.repository.quality_metrics_repository import RegressionMetricsEnum from fedot.core.repository.tasks import TaskTypesEnum - fedot_params_full = dict(parallelization_mode='populational', show_progress=True, max_depth=4, @@ -29,7 +28,6 @@ early_stopping_iterations=2, early_stopping_timeout=None, optimizer=EvoGraphOptimizer, - optimizer_external_params=dict(), collect_intermediate_metric=False, max_pipeline_fit_time=7, initial_assumption=PipelineBuilder().add_node('lagged').add_node('ridge').build(), diff --git a/test/unit/optimizer/test_external.py b/test/unit/optimizer/test_external.py index 216f1cc14c..e53d661da0 100644 --- a/test/unit/optimizer/test_external.py +++ b/test/unit/optimizer/test_external.py @@ -1,4 +1,5 @@ import logging +from functools import partial from typing import Optional, Union, Sequence import pytest @@ -32,7 +33,7 @@ def __init__(self, super().__init__(objective, initial_graph, requirements, graph_generation_params, graph_optimizer_parameters) self.change_types = [] - self.node_name = kwargs.get('node_name') or 'logit' + self.node_name = kwargs.get('node_name') or 'rf' def optimise(self, objective: ObjectiveFunction): graph = OptGraph(OptNode(self.node_name)) @@ -47,9 +48,8 @@ def test_external_static_optimizer(data_fixture, request): automl = Fedot(problem='classification', timeout=0.2, logging_level=logging.DEBUG, preset='fast_train', with_tuning=False, - optimizer=StaticOptimizer, - pop_size=2, - optimizer_external_params={'node_name': 'logit'}) + optimizer=partial(StaticOptimizer, node_name='logit'), + pop_size=2) obtained_pipeline = automl.fit(train_data) automl.predict(test_data)