PyIAML 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iaml/__init__.py +56 -0
- iaml/actionable.py +11 -0
- iaml/actionables/__init__.py +21 -0
- iaml/actionables/boosting/__init__.py +4 -0
- iaml/actionables/boosting/act_adaboost.py +59 -0
- iaml/actionables/cleaning/__init__.py +26 -0
- iaml/actionables/cleaning/act_categorical_imputer.py +124 -0
- iaml/actionables/cleaning/act_count_vectorizer.py +204 -0
- iaml/actionables/cleaning/act_drop_categorical_column.py +51 -0
- iaml/actionables/cleaning/act_drop_date_column.py +48 -0
- iaml/actionables/cleaning/act_drop_high_cardinality_categorical.py +337 -0
- iaml/actionables/cleaning/act_drop_numerical_column.py +75 -0
- iaml/actionables/cleaning/act_drop_textual_column.py +51 -0
- iaml/actionables/cleaning/act_encode_target_column.py +56 -0
- iaml/actionables/cleaning/act_frequency_encoder.py +127 -0
- iaml/actionables/cleaning/act_hashing_vectorizer.py +186 -0
- iaml/actionables/cleaning/act_knn_imputer.py +152 -0
- iaml/actionables/cleaning/act_mean_column.py +79 -0
- iaml/actionables/cleaning/act_mice.py +464 -0
- iaml/actionables/cleaning/act_missing_count_feature.py +109 -0
- iaml/actionables/cleaning/act_missing_indicator.py +124 -0
- iaml/actionables/cleaning/act_onehot.py +65 -0
- iaml/actionables/cleaning/act_ordinal_encoder.py +177 -0
- iaml/actionables/cleaning/act_rare_category_grouper.py +173 -0
- iaml/actionables/cleaning/act_simple_imputer.py +109 -0
- iaml/actionables/cleaning/act_split_date.py +68 -0
- iaml/actionables/cleaning/act_target_encoder.py +274 -0
- iaml/actionables/cleaning/act_text_normalizer.py +241 -0
- iaml/actionables/cleaning/act_tf_idf.py +80 -0
- iaml/actionables/cleaning/act_word2vec.py +150 -0
- iaml/actionables/features_precleaning/__init__.py +12 -0
- iaml/actionables/features_precleaning/act_coerce_numeric_strings.py +194 -0
- iaml/actionables/features_precleaning/act_date_converter.py +99 -0
- iaml/actionables/features_precleaning/act_drop_bad_quality_rows.py +77 -0
- iaml/actionables/features_precleaning/act_drop_duplicate_rows.py +131 -0
- iaml/actionables/features_precleaning/act_drop_high_missing_columns.py +94 -0
- iaml/actionables/features_precleaning/act_drop_id_like_columns.py +294 -0
- iaml/actionables/features_precleaning/act_normalize_column_names.py +157 -0
- iaml/actionables/features_precleaning/act_sentinel_to_na_n.py +270 -0
- iaml/actionables/features_precleaning/act_trim_space.py +79 -0
- iaml/actionables/features_preprocessing/__init__.py +18 -0
- iaml/actionables/features_preprocessing/act_cyclical_date_encoding.py +212 -0
- iaml/actionables/features_preprocessing/act_fast_ica.py +161 -0
- iaml/actionables/features_preprocessing/act_feature_agglomeration.py +90 -0
- iaml/actionables/features_preprocessing/act_k_bins_discretizer.py +207 -0
- iaml/actionables/features_preprocessing/act_k_means_features.py +296 -0
- iaml/actionables/features_preprocessing/act_kernel_pca.py +143 -0
- iaml/actionables/features_preprocessing/act_log_transformer.py +122 -0
- iaml/actionables/features_preprocessing/act_nystroem.py +100 -0
- iaml/actionables/features_preprocessing/act_pca.py +77 -0
- iaml/actionables/features_preprocessing/act_polynomial_features.py +86 -0
- iaml/actionables/features_preprocessing/act_power_transformer.py +106 -0
- iaml/actionables/features_preprocessing/act_quantile_transformer.py +114 -0
- iaml/actionables/features_preprocessing/act_rbf_sampler.py +88 -0
- iaml/actionables/features_preprocessing/act_select_percentile.py +112 -0
- iaml/actionables/features_preprocessing/act_sparse_random_projection.py +157 -0
- iaml/actionables/features_preprocessing/act_truncated_svd.py +137 -0
- iaml/actionables/features_selection/__init__.py +8 -0
- iaml/actionables/features_selection/act_permutation_importance_selector.py +421 -0
- iaml/actionables/features_selection/act_remove_high_correlated_column.py +70 -0
- iaml/actionables/features_selection/act_remove_low_variance_column.py +74 -0
- iaml/actionables/features_selection/act_rfe.py +214 -0
- iaml/actionables/features_selection/act_select_from_model.py +325 -0
- iaml/actionables/features_selection/act_select_k_best.py +181 -0
- iaml/actionables/features_selection/act_vif_selector.py +130 -0
- iaml/actionables/imbalance/__init__.py +10 -0
- iaml/actionables/imbalance/act_adasyn.py +150 -0
- iaml/actionables/imbalance/act_borderline_smote.py +171 -0
- iaml/actionables/imbalance/act_near_miss.py +158 -0
- iaml/actionables/imbalance/act_random_over_sampling.py +60 -0
- iaml/actionables/imbalance/act_random_under_sampler.py +135 -0
- iaml/actionables/imbalance/act_smote.py +162 -0
- iaml/actionables/imbalance/act_smote_tomek.py +182 -0
- iaml/actionables/imbalance/act_smoteenn.py +193 -0
- iaml/actionables/imbalance/act_tomek_links.py +138 -0
- iaml/actionables/normalize/__init__.py +6 -0
- iaml/actionables/normalize/act_max_abs_scaler.py +78 -0
- iaml/actionables/normalize/act_minmax_scaler.py +56 -0
- iaml/actionables/normalize/act_normalizer.py +95 -0
- iaml/actionables/normalize/act_robust_scaler.py +111 -0
- iaml/actionables/normalize/act_standard_scaler.py +55 -0
- iaml/actionables/predictors/__init__.py +6 -0
- iaml/actionables/predictors/_xgboost.py +16 -0
- iaml/actionables/predictors/classifier/__init__.py +26 -0
- iaml/actionables/predictors/classifier/act_bagging_classifier.py +113 -0
- iaml/actionables/predictors/classifier/act_bernoulli_nb.py +89 -0
- iaml/actionables/predictors/classifier/act_catboost_classifier.py +135 -0
- iaml/actionables/predictors/classifier/act_complement_nb.py +106 -0
- iaml/actionables/predictors/classifier/act_decision_tree_classifier.py +117 -0
- iaml/actionables/predictors/classifier/act_extra_trees_classifier.py +115 -0
- iaml/actionables/predictors/classifier/act_gaussian_nb.py +53 -0
- iaml/actionables/predictors/classifier/act_hist_gradient_boosting_classifier.py +144 -0
- iaml/actionables/predictors/classifier/act_knn.py +86 -0
- iaml/actionables/predictors/classifier/act_light_gbm_classifier.py +211 -0
- iaml/actionables/predictors/classifier/act_linear_discriminant_analysis.py +63 -0
- iaml/actionables/predictors/classifier/act_linear_svc.py +134 -0
- iaml/actionables/predictors/classifier/act_logistic_regression.py +92 -0
- iaml/actionables/predictors/classifier/act_mlp_classifier.py +107 -0
- iaml/actionables/predictors/classifier/act_multinomial_nb.py +76 -0
- iaml/actionables/predictors/classifier/act_passive_aggressive_classifier.py +141 -0
- iaml/actionables/predictors/classifier/act_quadratic_discriminant_analysis.py +72 -0
- iaml/actionables/predictors/classifier/act_randomforest.py +113 -0
- iaml/actionables/predictors/classifier/act_ridge_classifier.py +116 -0
- iaml/actionables/predictors/classifier/act_sgd_classifier.py +149 -0
- iaml/actionables/predictors/classifier/act_svm_svc.py +88 -0
- iaml/actionables/predictors/classifier/act_xgboost.py +111 -0
- iaml/actionables/predictors/regressor/__init__.py +27 -0
- iaml/actionables/predictors/regressor/act_ada_boost_regressor.py +75 -0
- iaml/actionables/predictors/regressor/act_ard_regression.py +95 -0
- iaml/actionables/predictors/regressor/act_catboost_regressor.py +134 -0
- iaml/actionables/predictors/regressor/act_decision_tree_regressor.py +111 -0
- iaml/actionables/predictors/regressor/act_elastic_net_regressor.py +109 -0
- iaml/actionables/predictors/regressor/act_extra_trees_regressor.py +113 -0
- iaml/actionables/predictors/regressor/act_gaussian_process_regressor.py +55 -0
- iaml/actionables/predictors/regressor/act_gboost_regressor.py +95 -0
- iaml/actionables/predictors/regressor/act_hist_gradient_boosting_regressor.py +105 -0
- iaml/actionables/predictors/regressor/act_huber_regressor.py +101 -0
- iaml/actionables/predictors/regressor/act_knn_regressor.py +86 -0
- iaml/actionables/predictors/regressor/act_lasso_regressor.py +103 -0
- iaml/actionables/predictors/regressor/act_light_gbm_regressor.py +201 -0
- iaml/actionables/predictors/regressor/act_linear_regression.py +43 -0
- iaml/actionables/predictors/regressor/act_mlp_regressor.py +104 -0
- iaml/actionables/predictors/regressor/act_poisson_regressor.py +111 -0
- iaml/actionables/predictors/regressor/act_quantile_regressor.py +87 -0
- iaml/actionables/predictors/regressor/act_randomforest_regressor.py +116 -0
- iaml/actionables/predictors/regressor/act_ransac_regressor.py +106 -0
- iaml/actionables/predictors/regressor/act_ridge_regressor.py +107 -0
- iaml/actionables/predictors/regressor/act_sgd_regressor.py +106 -0
- iaml/actionables/predictors/regressor/act_svm_svr.py +81 -0
- iaml/actionables/predictors/regressor/act_xgboost_regressor.py +97 -0
- iaml/actionables/predictors/survival/__init__.py +12 -0
- iaml/actionables/predictors/survival/act_aalen_additive_model.py +83 -0
- iaml/actionables/predictors/survival/act_cox.py +110 -0
- iaml/actionables/predictors/survival/act_coxnet_survival_analysis.py +134 -0
- iaml/actionables/predictors/survival/act_extra_survival_trees.py +101 -0
- iaml/actionables/predictors/survival/act_fast_survival_svm.py +102 -0
- iaml/actionables/predictors/survival/act_gradient_boosting_survival_analysis.py +93 -0
- iaml/actionables/predictors/survival/act_random_survival_forest.py +91 -0
- iaml/actionables/predictors/survival/act_survival_component_wise_gboost.py +80 -0
- iaml/actionables/predictors/survival/act_survival_tree.py +120 -0
- iaml/actionables/predictors/survival/act_survival_xgboost.py +9 -0
- iaml/actionables/predictors/survival/act_weibull_aft.py +230 -0
- iaml/cache.py +61 -0
- iaml/cache_keys.py +57 -0
- iaml/candidate.py +736 -0
- iaml/core_dispatcher.py +125 -0
- iaml/data_type.py +11 -0
- iaml/dataset.py +506 -0
- iaml/decorators/__init__.py +3 -0
- iaml/decorators/all.py +4 -0
- iaml/decorators/is_step.py +45 -0
- iaml/decorators/runner.py +100 -0
- iaml/explanation.py +112 -0
- iaml/iaml.py +1072 -0
- iaml/iaml_pipeline.py +600 -0
- iaml/logger.py +138 -0
- iaml/meta_explorer_step.py +62 -0
- iaml/meta_ordered_step.py +28 -0
- iaml/meta_partial_explorer_step.py +34 -0
- iaml/meta_singleton.py +24 -0
- iaml/metastep.py +211 -0
- iaml/metric.py +111 -0
- iaml/metric_plot.py +82 -0
- iaml/metrics/__init__.py +21 -0
- iaml/metrics/_classification.py +28 -0
- iaml/metrics/_survival_times.py +22 -0
- iaml/metrics/accuracy_metric.py +59 -0
- iaml/metrics/balanced_accuracy_metric.py +67 -0
- iaml/metrics/brier_score.py +90 -0
- iaml/metrics/classification_error_metric.py +66 -0
- iaml/metrics/concordance_index_ipcw.py +84 -0
- iaml/metrics/concordance_index_metric.py +67 -0
- iaml/metrics/cumulative_dynamic_auc.py +119 -0
- iaml/metrics/f1_score_metric.py +71 -0
- iaml/metrics/integrated_brier_score.py +98 -0
- iaml/metrics/integrated_brier_score_loss.py +41 -0
- iaml/metrics/mean_absolute_error_metric.py +46 -0
- iaml/metrics/mean_squared_error_metric.py +46 -0
- iaml/metrics/mean_squared_log_error_metric.py +49 -0
- iaml/metrics/median_absolute_error_metric.py +48 -0
- iaml/metrics/precision_metric.py +63 -0
- iaml/metrics/r2_score_metric.py +45 -0
- iaml/metrics/recall_metric.py +65 -0
- iaml/metrics/roc_auc_metric.py +50 -0
- iaml/metrics/specificity_metric.py +44 -0
- iaml/metrics/specificity_multiclass_metric.py +55 -0
- iaml/metrics/specificity_multilabel_metric.py +60 -0
- iaml/optimizers/__init__.py +5 -0
- iaml/optimizers/bayesian_optimizer.py +193 -0
- iaml/optimizers/genetic_optimizer.py +284 -0
- iaml/optimizers/optimizer.py +31 -0
- iaml/optimizers/random_optimizer.py +101 -0
- iaml/plot.py +138 -0
- iaml/plots/__init__.py +32 -0
- iaml/plots/bar_plot.py +141 -0
- iaml/plots/box_plot.py +166 -0
- iaml/plots/class_prediction_error_plot.py +37 -0
- iaml/plots/classification_report_plot.py +35 -0
- iaml/plots/confusion_matrix_plot.py +34 -0
- iaml/plots/correlation_heatmap_plot.py +201 -0
- iaml/plots/cumulative_hazard_plot.py +72 -0
- iaml/plots/density_plot.py +210 -0
- iaml/plots/histogram_plot.py +179 -0
- iaml/plots/kaplan_meier_comparison_plot.py +89 -0
- iaml/plots/line_plot.py +70 -0
- iaml/plots/missingness_heatmap_plot.py +203 -0
- iaml/plots/outlier_plot.py +217 -0
- iaml/plots/pair_plot.py +228 -0
- iaml/plots/precision_recall_curve_plot.py +86 -0
- iaml/plots/prediction_error_plot.py +34 -0
- iaml/plots/qq_plot.py +220 -0
- iaml/plots/residual_plot.py +38 -0
- iaml/plots/roc_dynamique_curve_plot.py +79 -0
- iaml/plots/rocauc_plot.py +96 -0
- iaml/plots/shap_plot.py +187 -0
- iaml/plots/target_distribution_plot.py +241 -0
- iaml/plots/violin_plot.py +206 -0
- iaml/predictor.py +139 -0
- iaml/reference.py +65 -0
- iaml/shared_cache.py +90 -0
- iaml/sklearn_preprocessor.py +74 -0
- iaml/splitters/__init__.py +3 -0
- iaml/splitters/kfold_splitter.py +32 -0
- iaml/splitters/random_splitter.py +26 -0
- iaml/stack.py +39 -0
- iaml/statistic.py +66 -0
- iaml/statistics/__init__.py +77 -0
- iaml/statistics/anova_statistic.py +80 -0
- iaml/statistics/cardinality_ratio_statistic.py +63 -0
- iaml/statistics/category_cooccurrence_statistic.py +79 -0
- iaml/statistics/chi_square_statistic.py +81 -0
- iaml/statistics/coef_variation_statistic.py +72 -0
- iaml/statistics/correlation_with_target.py +105 -0
- iaml/statistics/count.py +72 -0
- iaml/statistics/data_type_summary_statistic.py +74 -0
- iaml/statistics/duplicate_row_statistic.py +56 -0
- iaml/statistics/effect_size_statistic.py +129 -0
- iaml/statistics/entropy_statistic.py +69 -0
- iaml/statistics/event_rate_statistic.py +52 -0
- iaml/statistics/grouped_mean_statistic.py +60 -0
- iaml/statistics/iqr_statistic.py +66 -0
- iaml/statistics/kurtosis.py +50 -0
- iaml/statistics/mad_statistic.py +66 -0
- iaml/statistics/mean.py +61 -0
- iaml/statistics/median_statistic.py +61 -0
- iaml/statistics/minmax.py +60 -0
- iaml/statistics/missing_rate_statistic.py +62 -0
- iaml/statistics/mode.py +47 -0
- iaml/statistics/most_frequent_ratio.py +81 -0
- iaml/statistics/outlier_count_iqr_statistic.py +76 -0
- iaml/statistics/quantile.py +59 -0
- iaml/statistics/range.py +53 -0
- iaml/statistics/rare_category_rate.py +92 -0
- iaml/statistics/skewness.py +53 -0
- iaml/statistics/stdev.py +50 -0
- iaml/statistics/summary_table_statistic.py +60 -0
- iaml/statistics/time_by_group_statistic.py +83 -0
- iaml/statistics/time_summary_statistic.py +56 -0
- iaml/statistics/top_k_value_counts.py +68 -0
- iaml/statistics/unique_count_statistic.py +57 -0
- iaml/statistics/value_counts.py +63 -0
- iaml/statistics/variance.py +51 -0
- iaml/statistics/violin.py +63 -0
- iaml/step.py +600 -0
- iaml/step_cache.py +87 -0
- iaml/step_wrapper.py +79 -0
- iaml/timed_pool_executor.py +492 -0
- iaml/type_of_target.py +68 -0
- iaml/void_step.py +101 -0
- iaml/worker_manager.py +169 -0
- iaml/wrapper/__init__.py +4 -0
- iaml/wrapper/wrap_basic_gridsearch.py +68 -0
- iaml/wrapper/wrap_genetic_gridsearch.py +293 -0
- iaml/wrapper/wrap_iterative_gridsearch.py +399 -0
- pyiaml-1.0.0.dist-info/METADATA +802 -0
- pyiaml-1.0.0.dist-info/RECORD +279 -0
- pyiaml-1.0.0.dist-info/WHEEL +5 -0
- pyiaml-1.0.0.dist-info/licenses/LICENSE +674 -0
- pyiaml-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""[WRAPPER] Genetic Grid Search implementation
|
|
2
|
+
This wrapper is inspired by genetic algorithms.
|
|
3
|
+
It'll randomly create and mutate generations Step configurations to find the best parameters
|
|
4
|
+
Each new generation will learn from the previous one
|
|
5
|
+
"""
|
|
6
|
+
from copy import deepcopy
|
|
7
|
+
from math import isfinite
|
|
8
|
+
import random
|
|
9
|
+
from typing import Callable
|
|
10
|
+
from ..step_wrapper import StepWrapper
|
|
11
|
+
from ..step import Step
|
|
12
|
+
from ..decorators.all import is_step, runner
|
|
13
|
+
from ..candidate import Candidate
|
|
14
|
+
from ..meta_explorer_step import MetaExplorerStep
|
|
15
|
+
from ..logger import Logger
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@is_step('wrapper')
|
|
19
|
+
class WrapGeneticGridSearch(StepWrapper):
|
|
20
|
+
"""[WRAPPER] Genetic Grid Search implementation
|
|
21
|
+
This wrapper is inspired by genetic algorithms.
|
|
22
|
+
It'll randomly create and mutate generations Step configurations
|
|
23
|
+
to find the best parameters
|
|
24
|
+
Each new generation will learn from the previous one
|
|
25
|
+
"""
|
|
26
|
+
name = "Wrap : Genetic GridSearch"
|
|
27
|
+
_usage: str = "Use when you need adaptive, multi-generation search over larger spaces and want broader exploration than WrapBasicGridSearch or WrapIterativeGridSearch. Applicable to numeric, categorical, and boolean hyperparameters. Avoid when budget is tight or a fixed grid suffices."
|
|
28
|
+
|
|
29
|
+
def __init__(self, step: Step, *, evaluator: Callable[[Candidate], dict] = None):
|
|
30
|
+
# Set of configuration key to ignore.
|
|
31
|
+
# For example, random_state is not a parameter to optimize
|
|
32
|
+
self.ignored_configs: set[str] = {'random_state'}
|
|
33
|
+
# An evaluator can capture a dataset or splitter that changes between runs.
|
|
34
|
+
# Recompute the search rather than cache results by input identity alone.
|
|
35
|
+
self.use_cache = False
|
|
36
|
+
self.evaluator = evaluator
|
|
37
|
+
"""Optional callback returning metric scores for each generated candidate.
|
|
38
|
+
|
|
39
|
+
Use Candidate.training_evaluate with the original dataset and the desired
|
|
40
|
+
splitter. Without a callback, multiple generations require scores already
|
|
41
|
+
supplied by the wrapped step.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
self.configuration = {
|
|
45
|
+
'initial_modificator': {
|
|
46
|
+
'description': 'Maximum multiplier of default value to generate \
|
|
47
|
+
the first generation of steps',
|
|
48
|
+
'default': 5
|
|
49
|
+
},
|
|
50
|
+
'nb_generations': {
|
|
51
|
+
'description': 'Number of generations to create, train and test',
|
|
52
|
+
'default': 5,
|
|
53
|
+
'range': [1, float('inf')]
|
|
54
|
+
},
|
|
55
|
+
'nb_estimators': {
|
|
56
|
+
'description': 'Number of Steps by generations',
|
|
57
|
+
'default': 15,
|
|
58
|
+
'range': [1, float('inf')]
|
|
59
|
+
},
|
|
60
|
+
'mutation_power': {
|
|
61
|
+
'description': 'Maximum multiplier of current value when mutating',
|
|
62
|
+
'default': 0.1,
|
|
63
|
+
'range': [0.001, 1]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
"""Dictionnary of genetic configuration"""
|
|
67
|
+
|
|
68
|
+
# pylint: disable=too-many-locals
|
|
69
|
+
@runner
|
|
70
|
+
def run(self, candidate: Candidate) -> list[Candidate]:
|
|
71
|
+
"""Will iterate over generation to find best parameters
|
|
72
|
+
|
|
73
|
+
Genetic GridSearch
|
|
74
|
+
First generation
|
|
75
|
+
Generate nb_estimator Step randomly
|
|
76
|
+
Run and get result
|
|
77
|
+
Next generations
|
|
78
|
+
Keep 1/4 BEST
|
|
79
|
+
Mutation 2/4 BEST to New steps
|
|
80
|
+
Generate totally new steps
|
|
81
|
+
|
|
82
|
+
:param Candidate candidate: Candidate data
|
|
83
|
+
:return: All generated Candidate
|
|
84
|
+
"""
|
|
85
|
+
# If no configuration, let's run the step once. Nothing to optimize here
|
|
86
|
+
if not self.__config_keys():
|
|
87
|
+
return self.step.run(candidate)
|
|
88
|
+
|
|
89
|
+
population_size = self.get_config('nb_estimators')
|
|
90
|
+
generations = self.get_config('nb_generations')
|
|
91
|
+
for name, value in [('nb_estimators', population_size), ('nb_generations', generations)]:
|
|
92
|
+
if type(value) is not int or value < 1:
|
|
93
|
+
raise ValueError(f'{name} must be a positive integer')
|
|
94
|
+
|
|
95
|
+
generation = [self.random_generation() for _ in range(population_size)]
|
|
96
|
+
candidates = []
|
|
97
|
+
for i_gen in range(generations):
|
|
98
|
+
Logger().info(f"created new generation: [b]{self.step.__class__.__name__}[/] \
|
|
99
|
+
(generation={i_gen})")
|
|
100
|
+
|
|
101
|
+
meta = MetaExplorerStep() # Use MetaExplorer to run all our generation easily
|
|
102
|
+
meta.add_steps(generation) # Give all steps to MetaExplorer
|
|
103
|
+
for step in generation:
|
|
104
|
+
step.candidate = []
|
|
105
|
+
meta.run(candidate)
|
|
106
|
+
|
|
107
|
+
# The runner keeps each step's outputs. No legacy Stack history is needed.
|
|
108
|
+
ranked = []
|
|
109
|
+
for step in generation:
|
|
110
|
+
for result in step.candidate or []:
|
|
111
|
+
result.main_metric = candidate.get_main_metric()
|
|
112
|
+
if self.evaluator is not None:
|
|
113
|
+
result.computed_metrics = self.evaluator(result) or {}
|
|
114
|
+
score = result.computed_metrics.get(result.main_metric)
|
|
115
|
+
if score is None or not isfinite(score):
|
|
116
|
+
continue
|
|
117
|
+
elif i_gen + 1 < generations and (
|
|
118
|
+
result.main_metric not in result.computed_metrics
|
|
119
|
+
or not isfinite(result.get_main_metric_value())
|
|
120
|
+
):
|
|
121
|
+
raise ValueError(
|
|
122
|
+
'WrapGeneticGridSearch needs an evaluator returning the main '
|
|
123
|
+
'metric to select subsequent generations. Pass evaluator=... '
|
|
124
|
+
'using Candidate.training_evaluate on the original dataset, '
|
|
125
|
+
'or use GeneticOptimizer in IAML.'
|
|
126
|
+
)
|
|
127
|
+
ranked.append((result, step))
|
|
128
|
+
|
|
129
|
+
candidates = [result for result, _ in ranked]
|
|
130
|
+
if not candidates or i_gen + 1 == generations:
|
|
131
|
+
break
|
|
132
|
+
|
|
133
|
+
ranked.sort(key=lambda item: item[0], reverse=True)
|
|
134
|
+
nb_to_keep = max(1, population_size // 4)
|
|
135
|
+
steps_to_keep = []
|
|
136
|
+
seen = set()
|
|
137
|
+
for _, step in ranked:
|
|
138
|
+
fingerprint = step.fingerprint()
|
|
139
|
+
if fingerprint in seen:
|
|
140
|
+
continue
|
|
141
|
+
seen.add(fingerprint)
|
|
142
|
+
kept = deepcopy(step)
|
|
143
|
+
kept.parents_steps.remove(id(meta))
|
|
144
|
+
steps_to_keep.append(kept)
|
|
145
|
+
if len(steps_to_keep) == nb_to_keep:
|
|
146
|
+
break
|
|
147
|
+
|
|
148
|
+
new_generation = list(steps_to_keep)
|
|
149
|
+
for _ in range(min(2 * nb_to_keep, population_size - len(new_generation))):
|
|
150
|
+
new_generation.append(self.random_mutation(random.choice(steps_to_keep)))
|
|
151
|
+
while len(new_generation) < population_size:
|
|
152
|
+
new_generation.append(self.random_generation())
|
|
153
|
+
|
|
154
|
+
# Compare the complete effective configuration, including every key.
|
|
155
|
+
generation = []
|
|
156
|
+
seen = set()
|
|
157
|
+
for step in new_generation:
|
|
158
|
+
fingerprint = step.fingerprint()
|
|
159
|
+
if fingerprint not in seen:
|
|
160
|
+
seen.add(fingerprint)
|
|
161
|
+
generation.append(step)
|
|
162
|
+
|
|
163
|
+
return candidates
|
|
164
|
+
|
|
165
|
+
# Return Step with random configuration
|
|
166
|
+
def random_generation(self) -> Step:
|
|
167
|
+
"""Randomly generate a new Step
|
|
168
|
+
|
|
169
|
+
:return: Generated step with random configuration
|
|
170
|
+
"""
|
|
171
|
+
# Deepcopy to avoid editing other Steps of the same generation
|
|
172
|
+
new_step: Step = deepcopy(self.step)
|
|
173
|
+
|
|
174
|
+
for key in self.__config_keys(): # For each configuration key, we'll choose a random value
|
|
175
|
+
config = new_step.configuration[key]
|
|
176
|
+
|
|
177
|
+
if 'categorical' in config:
|
|
178
|
+
new_value = random.choice(config['categorical'])
|
|
179
|
+
elif type(config['value']) in [int, float]: # Numeric value ? Let's apply multiplier
|
|
180
|
+
is_int = isinstance(config['value'], int)
|
|
181
|
+
|
|
182
|
+
new_value = None
|
|
183
|
+
# Randomly choose a positive or negative editing
|
|
184
|
+
if bool(random.getrandbits(1)):
|
|
185
|
+
# Negative -> Multiply value by something between 0.01 and 1
|
|
186
|
+
change_rate = random.uniform(0.01, 1)
|
|
187
|
+
new_value = config['value']*change_rate
|
|
188
|
+
else:
|
|
189
|
+
# Positive -> Multiply vaoue by something between 1
|
|
190
|
+
# and the max modificator in configuration
|
|
191
|
+
change_rate = random.uniform(1, self.get_config('initial_modificator'))
|
|
192
|
+
new_value = config['value']*change_rate
|
|
193
|
+
|
|
194
|
+
# Value was a int ? Round it to keep it int
|
|
195
|
+
if is_int:
|
|
196
|
+
new_value = round(new_value)
|
|
197
|
+
|
|
198
|
+
if not self.__valide_config(config, new_value):
|
|
199
|
+
# Cancel is the new value is not correct.
|
|
200
|
+
new_value = config['value']
|
|
201
|
+
|
|
202
|
+
elif isinstance(config['value'], bool):
|
|
203
|
+
# Bool value, choose randomly beetwen True and False
|
|
204
|
+
new_value = random.choice([True, False])
|
|
205
|
+
else: # Other value ? Just keep it
|
|
206
|
+
new_value = config['value']
|
|
207
|
+
|
|
208
|
+
new_step.configure(key, new_value) # Set new configuration in the step
|
|
209
|
+
|
|
210
|
+
return new_step
|
|
211
|
+
|
|
212
|
+
# Randomly mutate Step
|
|
213
|
+
def random_mutation(self, step: Step) -> Step:
|
|
214
|
+
"""Randomly mutate some parameters of the step
|
|
215
|
+
|
|
216
|
+
:param Step step: Step to mutate.
|
|
217
|
+
:return: Mutated Step
|
|
218
|
+
"""
|
|
219
|
+
new_step: Step = deepcopy(step) # Deepcopy to avoid editing another Step
|
|
220
|
+
|
|
221
|
+
keys = self.__config_keys()
|
|
222
|
+
if not keys:
|
|
223
|
+
return new_step
|
|
224
|
+
random_key = random.choice(keys) # Choose a random key to mutate
|
|
225
|
+
random_item = new_step.configuration[random_key] # Get value of the random key
|
|
226
|
+
new_value = None
|
|
227
|
+
|
|
228
|
+
if 'categorical' in random_item:
|
|
229
|
+
new_value = random.choice(random_item['categorical'])
|
|
230
|
+
elif type(random_item['value']) in [int, float]: # Numeric value ? Apply multiplier
|
|
231
|
+
is_int = isinstance(random_item['value'], int)
|
|
232
|
+
|
|
233
|
+
# Find a multiplier between - mutation_power & + mutation_power
|
|
234
|
+
change_rate = random.uniform(-self.get_config('mutation_power'), \
|
|
235
|
+
self.get_config('mutation_power'))
|
|
236
|
+
new_value = random_item['value']*(1+change_rate) # Apply random multiplier
|
|
237
|
+
|
|
238
|
+
# Value was a int ? Round it to keep it int
|
|
239
|
+
if is_int:
|
|
240
|
+
new_value = round(new_value)
|
|
241
|
+
|
|
242
|
+
if new_value == random_item['value']: # To be sure there is a mutation
|
|
243
|
+
new_value += random.choice([-1, 1])
|
|
244
|
+
|
|
245
|
+
if not self.__valide_config(random_item, new_value):
|
|
246
|
+
# Cancel is the new value is not correct.
|
|
247
|
+
new_value = random_item['value']
|
|
248
|
+
|
|
249
|
+
elif isinstance(random_item['value'], bool): # Bool -> Choose between True and False
|
|
250
|
+
new_value = random.choice([True, False])
|
|
251
|
+
else: # Other -> Keep it
|
|
252
|
+
new_value = random_item['value']
|
|
253
|
+
|
|
254
|
+
new_step.configure(random_key, new_value) # Apply configuration
|
|
255
|
+
return new_step
|
|
256
|
+
|
|
257
|
+
def conf_to_rich_str_list(self) -> list[str]:
|
|
258
|
+
"""List of String to Rich logger
|
|
259
|
+
|
|
260
|
+
:return: list of stirng
|
|
261
|
+
"""
|
|
262
|
+
l = [f'step={self.step.__class__.__name__}']
|
|
263
|
+
l.extend(super().conf_to_rich_str_list())
|
|
264
|
+
|
|
265
|
+
return l
|
|
266
|
+
|
|
267
|
+
def count_steps(self) -> int:
|
|
268
|
+
"""Estimation of remaining step count
|
|
269
|
+
|
|
270
|
+
:return: Number of steps
|
|
271
|
+
"""
|
|
272
|
+
estimators = self.get_config('nb_estimators')
|
|
273
|
+
generations = self.get_config('nb_generations')
|
|
274
|
+
|
|
275
|
+
return self.step.count_steps() * estimators * generations
|
|
276
|
+
|
|
277
|
+
def __valide_config(self, config: dict, value: float) -> bool:
|
|
278
|
+
"""Is the configuration valid or not.
|
|
279
|
+
|
|
280
|
+
:param dict config: The configuration to check.
|
|
281
|
+
:param float value: The value to check in range.
|
|
282
|
+
:return: Valid ?
|
|
283
|
+
"""
|
|
284
|
+
if 'range' not in config.keys():
|
|
285
|
+
return True
|
|
286
|
+
lower, upper = config['range']
|
|
287
|
+
return (lower is None or lower <= value) and (upper is None or value <= upper)
|
|
288
|
+
|
|
289
|
+
# Get configurable keys (without ignored keys)
|
|
290
|
+
def __config_keys(self):
|
|
291
|
+
"""Get configurable keys (without ignored keys)."""
|
|
292
|
+
return [key for key, config in self.step.configuration.items()
|
|
293
|
+
if key not in self.ignored_configs and not config.get('no_gridsearch', False)]
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
"""[WRAPPER] Wrap a step to apply an Iterative Grid Search implementation"""
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
from math import isfinite
|
|
4
|
+
from typing import Any, Callable
|
|
5
|
+
from ..step_wrapper import StepWrapper
|
|
6
|
+
from ..candidate import Candidate
|
|
7
|
+
from ..step import Step
|
|
8
|
+
from ..decorators.all import is_step, runner
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Wrapper : Implementation of an interative GridSearch
|
|
12
|
+
@is_step('wrapper')
|
|
13
|
+
class WrapIterativeGridSearch(StepWrapper):
|
|
14
|
+
"""[WRAPPER] Wrap a step to apply an Iterative Grid Search implementation
|
|
15
|
+
|
|
16
|
+
:param Step step: Step to apply grid search on.
|
|
17
|
+
:param evaluator: Callback returning metric scores, for example using
|
|
18
|
+
Candidate.training_evaluate with the original dataset and desired splitter.
|
|
19
|
+
Required unless the wrapped step supplies evaluation scores itself.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
name = "Wrap : Iterative GridSearch"
|
|
23
|
+
_usage = "Use when you need iterative narrowing of numeric grids, faster than WrapBasicGridSearch. Applicable to numeric-heavy configs with ordered ranges and small categorical/bool sets. Avoid when spaces are huge or unstructured; consider WrapGeneticGridSearch."
|
|
24
|
+
def __init__(self, step: Step, *, evaluator: Callable[[Candidate], dict] = None) -> None:
|
|
25
|
+
self.evaluator = evaluator
|
|
26
|
+
# Evaluation callbacks may capture data that changes between runs.
|
|
27
|
+
self.use_cache = False
|
|
28
|
+
self.configuration = {
|
|
29
|
+
'modificator': {
|
|
30
|
+
'description': 'Value modificator for each iteration',
|
|
31
|
+
'default': 0.5
|
|
32
|
+
},
|
|
33
|
+
'max_iterations': {
|
|
34
|
+
'description': 'Maximum number of iterations',
|
|
35
|
+
'default': 10
|
|
36
|
+
},
|
|
37
|
+
'patience': {
|
|
38
|
+
'description': 'Stop iterations after N tries without improvements',
|
|
39
|
+
'default': 3
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
"""Iterative grid search configuration"""
|
|
43
|
+
|
|
44
|
+
self.step: Step = step
|
|
45
|
+
"""Step to perform grid search on"""
|
|
46
|
+
|
|
47
|
+
to_avoid = ['random_state']
|
|
48
|
+
|
|
49
|
+
@runner
|
|
50
|
+
def run(self, candidate: Candidate) -> list[Candidate]:
|
|
51
|
+
"""Iterative GridSearch.
|
|
52
|
+
|
|
53
|
+
Search numeric parameters as follows::
|
|
54
|
+
|
|
55
|
+
Numeric values
|
|
56
|
+
-> First run -> 100% of the value
|
|
57
|
+
-> Next runs -> +10% and - 10% (100% * modificator value)
|
|
58
|
+
-> RUN
|
|
59
|
+
-> Result is better ? Keep best result and try again with modificator
|
|
60
|
+
-> Result is worst ? Keep previous result, update modificator
|
|
61
|
+
-> STOP Conditions ? -> Number of iterations OR no improvement since X interations
|
|
62
|
+
-> Remember the range and then do a dichotomous to find the best parameters
|
|
63
|
+
|
|
64
|
+
Use the basic grid search for other parameter types::
|
|
65
|
+
|
|
66
|
+
Categorical values -> 1 run each
|
|
67
|
+
Boolean values -> Run with True and False
|
|
68
|
+
Other -> keep current value
|
|
69
|
+
|
|
70
|
+
:param Candidate candidate: The candidate on which we run the grid search.
|
|
71
|
+
:return: All generated Candidates.
|
|
72
|
+
"""
|
|
73
|
+
step = deepcopy(self.step)
|
|
74
|
+
config = deepcopy({key: item for key, item in step.configuration.items()
|
|
75
|
+
if key not in self.to_avoid and not item.get('no_gridsearch', False)})
|
|
76
|
+
pending = [GridIteration(
|
|
77
|
+
step, self.get_config('modificator'), copy_config=config,
|
|
78
|
+
patience=self.get_config('patience'), max_iterations=self.get_config('max_iterations'),
|
|
79
|
+
evaluator=self.evaluator,
|
|
80
|
+
)]
|
|
81
|
+
results = []
|
|
82
|
+
while pending:
|
|
83
|
+
iteration = pending.pop(0)
|
|
84
|
+
current_results, siblings = iteration.run(candidate)
|
|
85
|
+
results.extend(current_results)
|
|
86
|
+
pending.extend(siblings)
|
|
87
|
+
return sorted(results, reverse=True)[:10]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class GridIteration: # pylint: disable=too-many-instance-attributes
|
|
91
|
+
"""One iteration of Iterative grid search
|
|
92
|
+
|
|
93
|
+
:param Step step: The step we are working on.
|
|
94
|
+
:param float modificator_rate: The modification ratio
|
|
95
|
+
:param list, optional value_range: Range used to compute modificator. Default to None.
|
|
96
|
+
:param int, optional patience: Patience. Default to 5.
|
|
97
|
+
:param dict, optional copy_config: Config to copy. Default to None.
|
|
98
|
+
:param str, optional key: Key to perform gridsearch on. Default to None.
|
|
99
|
+
:param Any, optional value: Value of the key. Default to None.
|
|
100
|
+
:param int, optional max_iterations: Maximum number of iterations. Default to 10.
|
|
101
|
+
:param int, optional number_of_results: Maximum number of results. Default to 10.
|
|
102
|
+
:param float, optional minimal_range_diff: Minimal range diff. Default to None.
|
|
103
|
+
:param float, optional best_result: Best ranking score. Default to negative infinity.
|
|
104
|
+
"""
|
|
105
|
+
# pylint: disable=too-many-arguments,too-many-locals,too-many-branches,R0915,R0917
|
|
106
|
+
def __init__(
|
|
107
|
+
self,
|
|
108
|
+
step: Step,
|
|
109
|
+
modificator_rate: float,
|
|
110
|
+
value_range: list = None,
|
|
111
|
+
patience: int = 5,
|
|
112
|
+
copy_config: dict = None,
|
|
113
|
+
key: str = None,
|
|
114
|
+
value: Any = None,
|
|
115
|
+
max_iterations: int = 10,
|
|
116
|
+
number_of_results: int = 10,
|
|
117
|
+
minimal_range_diff: float = None,
|
|
118
|
+
best_result: float = float('-inf'),
|
|
119
|
+
evaluator: Callable[[Candidate], dict] = None) -> None:
|
|
120
|
+
self.step: Step = step
|
|
121
|
+
"""The step we are working on"""
|
|
122
|
+
self.evaluator = evaluator
|
|
123
|
+
|
|
124
|
+
self.modificator_rate: float = modificator_rate
|
|
125
|
+
"""Modificator ratio"""
|
|
126
|
+
|
|
127
|
+
self.patience: int = patience
|
|
128
|
+
"""Patience"""
|
|
129
|
+
|
|
130
|
+
self.config: dict = deepcopy(step.configuration) if copy_config is None else copy_config
|
|
131
|
+
"""Config used"""
|
|
132
|
+
|
|
133
|
+
if key:
|
|
134
|
+
self.key = key
|
|
135
|
+
elif any(self.config.keys()):
|
|
136
|
+
self.key = list(self.config.keys())[0]
|
|
137
|
+
else:
|
|
138
|
+
self.key = None
|
|
139
|
+
|
|
140
|
+
self.max_iterations: int = max_iterations
|
|
141
|
+
"""Maximum number of iterations"""
|
|
142
|
+
|
|
143
|
+
self.count_iterations: int = 0
|
|
144
|
+
"""Iteration counter"""
|
|
145
|
+
|
|
146
|
+
self.iterations_without_improvement: int = 0
|
|
147
|
+
"""Iterations without improvement counter"""
|
|
148
|
+
|
|
149
|
+
self.children: list = []
|
|
150
|
+
"""Childrens"""
|
|
151
|
+
|
|
152
|
+
self.ways: list = []
|
|
153
|
+
"""List of ways to obtain possible values"""
|
|
154
|
+
|
|
155
|
+
self.values: list = []
|
|
156
|
+
"""List of possible values"""
|
|
157
|
+
|
|
158
|
+
if self.key:
|
|
159
|
+
self.value = self.config[self.key]['value'] if value is None else value
|
|
160
|
+
|
|
161
|
+
self.modificator = None
|
|
162
|
+
self.minimal_range_diff = None
|
|
163
|
+
if minimal_range_diff is not None:
|
|
164
|
+
self.minimal_range_diff = minimal_range_diff
|
|
165
|
+
|
|
166
|
+
# Type
|
|
167
|
+
self.can_generate_sibling = False
|
|
168
|
+
if 'categorical' in self.config[self.key]:
|
|
169
|
+
self.values = list(self.config[self.key]['categorical'])
|
|
170
|
+
elif type(self.value) in [int, float]:
|
|
171
|
+
self.can_generate_sibling = True
|
|
172
|
+
if value_range:
|
|
173
|
+
self.modificator = abs(value_range[1]-value_range[0])/2 * modificator_rate
|
|
174
|
+
else:
|
|
175
|
+
self.modificator = abs(self.value) * self.modificator_rate
|
|
176
|
+
|
|
177
|
+
for way_ind, way in enumerate([1, -1]):
|
|
178
|
+
way_values = [self.value+(self.modificator*ind*way) \
|
|
179
|
+
for ind in range(way_ind, self.max_iterations)]
|
|
180
|
+
if isinstance(self.value, int):
|
|
181
|
+
way_values = [round(v) for v in way_values]
|
|
182
|
+
|
|
183
|
+
if ('range' in self.config[self.key]) or value_range:
|
|
184
|
+
limits = value_range or self.config[self.key]['range']
|
|
185
|
+
way_values = [v for v in way_values
|
|
186
|
+
if (limits[0] is None or limits[0] <= v)
|
|
187
|
+
and (limits[1] is None or v <= limits[1])]
|
|
188
|
+
|
|
189
|
+
self.ways.append(list(dict.fromkeys(way_values)))
|
|
190
|
+
|
|
191
|
+
self.__next_way()
|
|
192
|
+
|
|
193
|
+
if self.minimal_range_diff is None:
|
|
194
|
+
self.minimal_range_diff = self.modificator * 0.1 # TODO improve this
|
|
195
|
+
|
|
196
|
+
elif isinstance(self.value, bool): # Bool
|
|
197
|
+
self.values = [True, False]
|
|
198
|
+
else: # Other
|
|
199
|
+
self.values = [self.value]
|
|
200
|
+
|
|
201
|
+
self.candidates: list[Candidate] = []
|
|
202
|
+
"""List of returned candidates"""
|
|
203
|
+
|
|
204
|
+
self.results: list = []
|
|
205
|
+
"""List of results"""
|
|
206
|
+
|
|
207
|
+
self.number_of_results: int = number_of_results
|
|
208
|
+
"""Number of results"""
|
|
209
|
+
|
|
210
|
+
self.best_result: float = best_result
|
|
211
|
+
"""Best result"""
|
|
212
|
+
|
|
213
|
+
def done(self) -> bool:
|
|
214
|
+
"""Iteration finished ?
|
|
215
|
+
|
|
216
|
+
:return: Finished ?
|
|
217
|
+
"""
|
|
218
|
+
if not self.can_generate_sibling:
|
|
219
|
+
return self.count_iterations >= len(self.values)
|
|
220
|
+
return (self.iterations_without_improvement >= self.patience) \
|
|
221
|
+
or (self.count_iterations >= self.max_iterations) \
|
|
222
|
+
or (len(self.values)-1 < self.count_iterations)
|
|
223
|
+
|
|
224
|
+
def go_deeper(self) -> bool:
|
|
225
|
+
"""Need to go deeper ? (Remains parameter to optimize)
|
|
226
|
+
|
|
227
|
+
:return: Deeper ?
|
|
228
|
+
"""
|
|
229
|
+
return len(self.config.keys()) > 1
|
|
230
|
+
|
|
231
|
+
def __generate_child(self):
|
|
232
|
+
"""Generate new childs"""
|
|
233
|
+
child_config = deepcopy(self.config)
|
|
234
|
+
del child_config[self.key]
|
|
235
|
+
|
|
236
|
+
self.children.append(self.__class__(
|
|
237
|
+
self.step,
|
|
238
|
+
self.modificator_rate,
|
|
239
|
+
patience=self.patience,
|
|
240
|
+
best_result = self.best_result,
|
|
241
|
+
copy_config=child_config,
|
|
242
|
+
max_iterations=self.max_iterations,
|
|
243
|
+
evaluator=self.evaluator))
|
|
244
|
+
|
|
245
|
+
def __get_best_range(self) -> tuple:
|
|
246
|
+
"""Get the best range using previous results.
|
|
247
|
+
The best range will be the higher results + his highest neighbour.
|
|
248
|
+
|
|
249
|
+
:return: best range.
|
|
250
|
+
"""
|
|
251
|
+
if len(self.results) < 2:
|
|
252
|
+
return None, None
|
|
253
|
+
|
|
254
|
+
ordered = sorted(self.results, key=lambda result: result['value'])
|
|
255
|
+
max_index = max(range(len(ordered)), key=lambda index: ordered[index]['result'])
|
|
256
|
+
around = ordered[max(0, max_index-1):(max_index+2)]
|
|
257
|
+
around = sorted(around, key=lambda x: x['result'])
|
|
258
|
+
return tuple(sorted((around[-2]['value'], around[-1]['value'])))
|
|
259
|
+
|
|
260
|
+
def __generate_siblings(self) -> list:
|
|
261
|
+
"""Generate siblings
|
|
262
|
+
Siblings will be next iterator at the same level (same key, same step).
|
|
263
|
+
They only explore the best range.
|
|
264
|
+
|
|
265
|
+
:return: list of siblings
|
|
266
|
+
"""
|
|
267
|
+
if not self.can_generate_sibling:
|
|
268
|
+
return []
|
|
269
|
+
|
|
270
|
+
mini, maxi = self.__get_best_range()
|
|
271
|
+
|
|
272
|
+
if maxi is None or mini is None:
|
|
273
|
+
return []
|
|
274
|
+
|
|
275
|
+
current_range = [mini, maxi]
|
|
276
|
+
middle = mini+(maxi-mini)/2
|
|
277
|
+
|
|
278
|
+
if self.minimal_range_diff >= (maxi-mini):
|
|
279
|
+
return []
|
|
280
|
+
|
|
281
|
+
if isinstance(maxi, int):
|
|
282
|
+
if (maxi-mini) <= 1:
|
|
283
|
+
return []
|
|
284
|
+
|
|
285
|
+
middle = round(middle)
|
|
286
|
+
|
|
287
|
+
next_iter = self.__class__(
|
|
288
|
+
self.step,
|
|
289
|
+
self.modificator_rate,
|
|
290
|
+
value=middle,
|
|
291
|
+
value_range=current_range,
|
|
292
|
+
patience=self.patience,
|
|
293
|
+
copy_config=self.config,
|
|
294
|
+
best_result = self.best_result,
|
|
295
|
+
key=self.key,
|
|
296
|
+
max_iterations=self.max_iterations,
|
|
297
|
+
minimal_range_diff=self.minimal_range_diff,
|
|
298
|
+
evaluator=self.evaluator)
|
|
299
|
+
|
|
300
|
+
return [next_iter]
|
|
301
|
+
|
|
302
|
+
def __next_way(self) -> None:
|
|
303
|
+
"""Go to the next direction.
|
|
304
|
+
Iterator values will go up and then go down.
|
|
305
|
+
"""
|
|
306
|
+
while self.ways:
|
|
307
|
+
self.values = self.ways.pop(0)
|
|
308
|
+
self.count_iterations = 0
|
|
309
|
+
self.iterations_without_improvement = 0
|
|
310
|
+
if self.values:
|
|
311
|
+
break
|
|
312
|
+
|
|
313
|
+
def next_iteration(self) -> None:
|
|
314
|
+
"""
|
|
315
|
+
Go to the next iteration or the next way if current one is finished
|
|
316
|
+
"""
|
|
317
|
+
self.count_iterations = self.count_iterations + 1
|
|
318
|
+
if self.done():
|
|
319
|
+
self.__next_way()
|
|
320
|
+
|
|
321
|
+
def current_value(self) -> Any:
|
|
322
|
+
"""Get current value
|
|
323
|
+
|
|
324
|
+
:return: Current value
|
|
325
|
+
"""
|
|
326
|
+
return self.values[self.count_iterations]
|
|
327
|
+
|
|
328
|
+
def __stack_results(self, results: list[Candidate]) -> None:
|
|
329
|
+
"""Stack, compute and save results"""
|
|
330
|
+
if not any(results):
|
|
331
|
+
return None
|
|
332
|
+
|
|
333
|
+
best = max(results, key=lambda result: result.get_main_metric_score())
|
|
334
|
+
best_val = best.get_main_metric_score()
|
|
335
|
+
|
|
336
|
+
self.results.append({'value': self.current_value(), 'result': best_val})
|
|
337
|
+
|
|
338
|
+
if best_val <= self.best_result:
|
|
339
|
+
self.iterations_without_improvement = self.iterations_without_improvement + 1
|
|
340
|
+
else:
|
|
341
|
+
self.best_result = best_val
|
|
342
|
+
self.iterations_without_improvement = 0
|
|
343
|
+
|
|
344
|
+
self.candidates.append(best)
|
|
345
|
+
|
|
346
|
+
# Keep only n best
|
|
347
|
+
self.candidates.sort(reverse=True)
|
|
348
|
+
self.candidates = self.candidates[:self.number_of_results]
|
|
349
|
+
|
|
350
|
+
return None
|
|
351
|
+
|
|
352
|
+
def run(
|
|
353
|
+
self,
|
|
354
|
+
candidate: Candidate) -> tuple[list[Candidate], list['GridIteration']]:
|
|
355
|
+
"""Run and Stack results
|
|
356
|
+
|
|
357
|
+
:param Candidate candidate: The candidate we run.
|
|
358
|
+
:return: tuple of lists of candidates and GridIteration
|
|
359
|
+
"""
|
|
360
|
+
# Run and Stack results
|
|
361
|
+
if not self.key:
|
|
362
|
+
return self.step.run(candidate.to_input()), []
|
|
363
|
+
|
|
364
|
+
while not self.done():
|
|
365
|
+
results = []
|
|
366
|
+
self.step.configure(self.key, self.current_value())
|
|
367
|
+
|
|
368
|
+
if self.go_deeper():
|
|
369
|
+
self.__generate_child()
|
|
370
|
+
results = []
|
|
371
|
+
while self.children:
|
|
372
|
+
child = self.children.pop(0)
|
|
373
|
+
current_results, siblings = child.run(candidate)
|
|
374
|
+
results = results + current_results
|
|
375
|
+
|
|
376
|
+
if siblings:
|
|
377
|
+
self.children = self.children + siblings
|
|
378
|
+
else:
|
|
379
|
+
for result in deepcopy(self.step).run(candidate.to_input()):
|
|
380
|
+
result.main_metric = candidate.get_main_metric()
|
|
381
|
+
if self.evaluator is not None:
|
|
382
|
+
result.computed_metrics = self.evaluator(result) or {}
|
|
383
|
+
score = result.computed_metrics.get(result.main_metric)
|
|
384
|
+
if score is None or not isfinite(score):
|
|
385
|
+
if self.evaluator is None:
|
|
386
|
+
raise ValueError(
|
|
387
|
+
'WrapIterativeGridSearch needs an evaluator returning the main '
|
|
388
|
+
'metric. Pass evaluator=... using Candidate.training_evaluate '
|
|
389
|
+
'on the original dataset.'
|
|
390
|
+
)
|
|
391
|
+
continue
|
|
392
|
+
results.append(result)
|
|
393
|
+
|
|
394
|
+
self.__stack_results(results)
|
|
395
|
+
self.next_iteration()
|
|
396
|
+
|
|
397
|
+
siblings = self.__generate_siblings()
|
|
398
|
+
|
|
399
|
+
return self.candidates, siblings
|