Skip to content

Commit

Permalink
add example for surrogate optimizer (#1085)
Browse files Browse the repository at this point in the history
Example with surrogate optimizer was added. External parameters field was removed from api (now we should use partial)

related pull request in GOLEM aimclub/GOLEM#82
  • Loading branch information
valer1435 committed Apr 27, 2023
1 parent 0be0311 commit 36b86af
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 27 deletions.
48 changes: 48 additions & 0 deletions examples/advanced/surrogate_optimization.py
Original file line number Diff line number Diff line change
@@ -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)
17 changes: 8 additions & 9 deletions fedot/api/api_utils/api_composer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion fedot/api/api_utils/api_params_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 2 additions & 3 deletions fedot/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 \
<https://github.com/aimclub/FEDOT/blob/master/examples/advanced/fedot_based_solutions/external_optimizer.py>`_.
optimizer_external_params (Dict[str, Any]): additional parameters for custom optimizer (if needed).
<https://github.com/aimclub/FEDOT/blob/master/examples/advanced/fedot_based_solutions/external_optimizer.py>`_
"""

def __init__(self,
Expand Down
11 changes: 3 additions & 8 deletions fedot/core/composer/composer_builder.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions fedot/core/optimisers/objective/data_objective_eval.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
2 changes: 0 additions & 2 deletions test/unit/api/test_api_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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(),
Expand Down
8 changes: 4 additions & 4 deletions test/unit/optimizer/test_external.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
from functools import partial
from typing import Optional, Union, Sequence

import pytest
Expand Down Expand Up @@ -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))
Expand All @@ -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)

Expand Down

0 comments on commit 36b86af

Please sign in to comment.