Skip to content

Commit cebd493

Browse files
authored
Fix multiobj tuning (#1156)
* Fix tuner builder for multi objective tuning * Correct docs * Fix tests * Fix PEP8
1 parent 97f31db commit cebd493

File tree

3 files changed

+214
-65
lines changed

3 files changed

+214
-65
lines changed

docs/source/advanced/hyperparameters_tuning.rst

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
12
Tuning of Hyperparameters
23
=========================
34
To tune pipeline hyperparameters you can use GOLEM. There are two ways:
45

5-
1. Tuning of all models hyperparameters simultaneously. Implemented via ``SimultaneousTuner`` and ``IOptTuner`` classes.
6+
1. Tuning of all models hyperparameters simultaneously. Implemented via ``SimultaneousTuner``, ``OptunaTuner`` and ``IOptTuner`` classes.
67

78
2. Tuning of models hyperparameters sequentially node by node optimizing metric value for the whole pipeline or tuning
89
only one node hyperparametrs. Implemented via ``SequentialTuner`` class.
@@ -16,22 +17,25 @@ using ``SimultaneousTuner`` is applied for composed pipeline and ``metric`` valu
1617
FEDOT uses tuners implementation from GOLEM, see `GOLEM documentation`_ for more information.
1718

1819
.. list-table:: Tuners comparison
19-
:widths: 10 30 30 30
20+
:widths: 10 30 30 30 30
2021
:header-rows: 1
2122

2223
* -
2324
- ``SimultaneousTuner``
2425
- ``SequentialTuner``
2526
- ``IOptTuner``
27+
- ``OptunaTuner``
2628
* - Based on
2729
- Hyperopt
2830
- Hyperopt
2931
- iOpt
32+
- Optuna
3033
* - Type of tuning
3134
- Simultaneous
3235
- | Sequential or
3336
| for one node only
3437
- Simultaneous
38+
- Simultaneous
3539
* - | Optimized
3640
| parameters
3741
- | categorical
@@ -42,10 +46,14 @@ FEDOT uses tuners implementation from GOLEM, see `GOLEM documentation`_ for more
4246
| continuous
4347
- | discrete
4448
| continuous
49+
- | categorical
50+
| discrete
51+
| continuous
4552
* - Algorithm type
4653
- stochastic
4754
- stochastic
4855
- deterministic
56+
- stochastic
4957
* - | Supported
5058
| constraints
5159
- | timeout
@@ -58,11 +66,22 @@ FEDOT uses tuners implementation from GOLEM, see `GOLEM documentation`_ for more
5866
| eval_time_constraint
5967
- | iterations
6068
| eval_time_constraint
69+
- | timeout
70+
| iterations
71+
| early_stopping_rounds
72+
| eval_time_constraint
6173
* - | Supports initial
6274
| point
6375
- Yes
6476
- No
6577
- No
78+
- Yes
79+
* - | Supports multi
80+
| objective tuning
81+
- No
82+
- No
83+
- No
84+
- Yes
6685

6786
Hyperopt based tuners usually take less time for one iteration, but ``IOptTuner`` is able to obtain much more stable results.
6887

@@ -488,7 +507,91 @@ Tuned pipeline structure:
488507
{'depth': 2, 'length': 3, 'nodes': [knnreg, knnreg, rfr]}
489508
knnreg - {'n_neighbors': 51}
490509
knnreg - {'n_neighbors': 40}
491-
rfr - {'n_jobs': 1, 'max_features': 0.05324707031250003, 'min_samples_split': 12, 'min_samples_leaf': 11}
510+
rfr - {'n_jobs': 1, 'max_features': 0.05324, 'min_samples_split': 12, 'min_samples_leaf': 11}
511+
512+
Example for ``OptunaTuner``:
513+
514+
.. code-block:: python
515+
516+
from golem.core.tuning.optuna_tuner import OptunaTuner
517+
from fedot.core.data.data import InputData
518+
from fedot.core.pipelines.pipeline_builder import PipelineBuilder
519+
from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder
520+
from fedot.core.repository.quality_metrics_repository import RegressionMetricsEnum
521+
from fedot.core.repository.tasks import TaskTypesEnum, Task
522+
523+
task = Task(TaskTypesEnum.regression)
524+
525+
tuner = OptunaTuner
526+
527+
metric = RegressionMetricsEnum.MSE
528+
529+
iterations = 100
530+
531+
train_data = InputData.from_csv('train_data.csv', task='regression')
532+
533+
pipeline = PipelineBuilder().add_node('knnreg', branch_idx=0).add_branch('rfr', branch_idx=1) \
534+
.join_branches('knnreg').build()
535+
536+
pipeline_tuner = TunerBuilder(task) \
537+
.with_tuner(tuner) \
538+
.with_metric(metric) \
539+
.with_iterations(iterations) \
540+
.build(train_data)
541+
542+
tuned_pipeline = pipeline_tuner.tune(pipeline)
543+
544+
tuned_pipeline.print_structure()
545+
546+
Tuned pipeline structure:
547+
548+
.. code-block:: python
549+
550+
Pipeline structure:
551+
{'depth': 2, 'length': 3, 'nodes': [knnreg, knnreg, rfr]}
552+
knnreg - {'n_neighbors': 51}
553+
knnreg - {'n_neighbors': 40}
554+
rfr - {'n_jobs': 1, 'max_features': 0.05, 'min_samples_split': 12, 'min_samples_leaf': 11}
555+
556+
557+
Multi objective tuning
558+
^^^^^^^^^^^^^^^^^^^^^^
559+
560+
Multi objective tuning is available only for ``OptunaTuner``. Pass a list of metrics to ``.with_metric()``
561+
and obtain a list of tuned pipelines representing a pareto front after tuning.
562+
563+
.. code-block:: python
564+
565+
from typing import Iterable
566+
from golem.core.tuning.optuna_tuner import OptunaTuner
567+
from fedot.core.data.data import InputData
568+
from fedot.core.pipelines.pipeline import Pipeline
569+
from fedot.core.pipelines.pipeline_builder import PipelineBuilder
570+
from fedot.core.pipelines.tuning.tuner_builder import TunerBuilder
571+
from fedot.core.repository.quality_metrics_repository import RegressionMetricsEnum
572+
from fedot.core.repository.tasks import TaskTypesEnum, Task
573+
574+
task = Task(TaskTypesEnum.regression)
575+
576+
tuner = OptunaTuner
577+
578+
metric = [RegressionMetricsEnum.MSE, RegressionMetricsEnum.MAE]
579+
580+
iterations = 100
581+
582+
train_data = InputData.from_csv('train_data.csv', task='regression')
583+
584+
pipeline = PipelineBuilder().add_node('knnreg', branch_idx=0).add_branch('rfr', branch_idx=1) \
585+
.join_branches('knnreg').build()
586+
587+
pipeline_tuner = TunerBuilder(task) \
588+
.with_tuner(tuner) \
589+
.with_metric(metric) \
590+
.with_iterations(iterations) \
591+
.build(train_data)
592+
593+
pareto_front: Iterable[Pipeline] = pipeline_tuner.tune(pipeline)
594+
492595
493596
Sequential tuning
494597
-----------------

fedot/core/pipelines/tuning/tuner_builder.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from datetime import timedelta
2-
from typing import Type, Union
2+
from typing import Type, Union, Iterable, Sequence
33

4+
from golem.core.tuning.optuna_tuner import OptunaTuner
45
from golem.core.tuning.simultaneous import SimultaneousTuner
56
from golem.core.tuning.tuner_interface import BaseTuner
7+
from golem.core.utilities.data_structures import ensure_wrapped_in_sequence
68

79
from fedot.core.constants import DEFAULT_TUNING_ITERATIONS_NUMBER
810
from fedot.core.data.data import InputData
@@ -23,7 +25,7 @@ def __init__(self, task: Task):
2325
self.cv_folds = None
2426
self.validation_blocks = None
2527
self.n_jobs = -1
26-
self.metric: MetricsEnum = MetricByTask.get_default_quality_metrics(task.task_type)[0]
28+
self.metric: Sequence[MetricsEnum] = MetricByTask.get_default_quality_metrics(task.task_type)
2729
self.iterations = DEFAULT_TUNING_ITERATIONS_NUMBER
2830
self.early_stopping_rounds = None
2931
self.timeout = timedelta(minutes=5)
@@ -53,8 +55,8 @@ def with_n_jobs(self, n_jobs: int):
5355
self.n_jobs = n_jobs
5456
return self
5557

56-
def with_metric(self, metric: MetricType):
57-
self.metric = metric
58+
def with_metric(self, metrics: Union[MetricType, Iterable[MetricType]]):
59+
self.metric = ensure_wrapped_in_sequence(metrics)
5860
return self
5961

6062
def with_iterations(self, iterations: int):
@@ -88,11 +90,16 @@ def with_adapter(self, adapter):
8890
return self
8991

9092
def with_additional_params(self, **parameters):
91-
self.additional_params = parameters
93+
self.additional_params.update(parameters)
9294
return self
9395

9496
def build(self, data: InputData) -> BaseTuner:
95-
objective = MetricsObjective(self.metric)
97+
if len(self.metric) > 1:
98+
if self.tuner_class is OptunaTuner:
99+
self.additional_params.update({'objectives_number': len(self.metric)})
100+
else:
101+
raise ValueError('Multi objective tuning applicable only for OptunaTuner.')
102+
objective = MetricsObjective(self.metric, is_multi_objective=len(self.metric) > 1)
96103
data_splitter = DataSourceSplitter(self.cv_folds, validation_blocks=self.validation_blocks)
97104
data_producer = data_splitter.build(data)
98105
objective_evaluate = PipelineObjectiveEvaluate(objective, data_producer,

0 commit comments

Comments
 (0)