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
iaml/type_of_target.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Try to figure out the type of target"""
|
|
2
|
+
from typing import Any
|
|
3
|
+
import numpy as np
|
|
4
|
+
from sklearn.utils.multiclass import type_of_target as sk_type_of_target
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def type_of_target(y: list) -> str:
|
|
8
|
+
"""Try to figure out the type of target
|
|
9
|
+
|
|
10
|
+
:param List y: Dataset's target
|
|
11
|
+
:return: type of target (binary, continuous, multi-label, etc.)
|
|
12
|
+
"""
|
|
13
|
+
y = np.array(y)
|
|
14
|
+
|
|
15
|
+
# Survival target -> list[tuple[bool, int]]
|
|
16
|
+
if is_survival(y):
|
|
17
|
+
return 'survival'
|
|
18
|
+
|
|
19
|
+
if np.issubdtype(y.dtype, np.number):
|
|
20
|
+
unique_count = len(np.unique(y))
|
|
21
|
+
if unique_count > len(y) * 0.2 or unique_count > 10:
|
|
22
|
+
return 'continuous'
|
|
23
|
+
|
|
24
|
+
return sk_type_of_target(y)
|
|
25
|
+
|
|
26
|
+
def is_survival(y: list[tuple[Any, Any]]) -> bool:
|
|
27
|
+
"""Checks if the input data represents survival data.
|
|
28
|
+
|
|
29
|
+
Survival data is expected to be a list of tuples where:
|
|
30
|
+
- The first element of each tuple is binary-like
|
|
31
|
+
- The second element of each tuple is numeric (int or float).
|
|
32
|
+
|
|
33
|
+
:param list[tuple[Any, Any]] y: each tuple contains two elements (binary-like, numeric).
|
|
34
|
+
:return: True if the data meets the survival data requirements, False otherwise.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# Ensure all elements are tuples of length 2
|
|
38
|
+
if not all(isinstance(row, (tuple, np.ndarray)) and len(row) == 2 for row in y):
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
# Check if the first element of all tuples is binary-like
|
|
42
|
+
first_elements = [row[0] for row in y]
|
|
43
|
+
if not is_bool_convertible(first_elements):
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
# Check if the second element of all tuples is numeric (int or float)
|
|
47
|
+
if not all(isinstance(row[1], (int, float, np.integer, np.floating)) for row in y):
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
return True
|
|
51
|
+
|
|
52
|
+
def is_bool_convertible(seq):
|
|
53
|
+
for element in seq:
|
|
54
|
+
if isinstance(element, (bool, np.bool_)):
|
|
55
|
+
continue
|
|
56
|
+
elif isinstance(element, (int, np.integer)):
|
|
57
|
+
if element in (0, 1):
|
|
58
|
+
continue
|
|
59
|
+
else:
|
|
60
|
+
return False
|
|
61
|
+
elif isinstance(element, (float, np.floating)):
|
|
62
|
+
if abs(element - 0.0) < 1e-9 or abs(element - 1.0) < 1e-9:
|
|
63
|
+
continue
|
|
64
|
+
else:
|
|
65
|
+
return False
|
|
66
|
+
else:
|
|
67
|
+
return False
|
|
68
|
+
return True
|
iaml/void_step.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""[STEP] Void step -> Just a step that do nothing and can be mutated to siblings"""
|
|
2
|
+
from hashlib import md5
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from .step import Step
|
|
7
|
+
from .decorators.all import is_step
|
|
8
|
+
from .dataset import Dataset
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def predict(X: pd.DataFrame) -> pd.DataFrame:
|
|
12
|
+
"""VoidStep : Do nothing
|
|
13
|
+
|
|
14
|
+
:param pd.DataFrame X: The dataframe we predict on
|
|
15
|
+
:return: The dataframe returned
|
|
16
|
+
"""
|
|
17
|
+
return X
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def transform(dataset: Dataset) -> Dataset:
|
|
21
|
+
"""VoidStep : Do nothing
|
|
22
|
+
|
|
23
|
+
:param Dataset dataset: The Dataset object we transform
|
|
24
|
+
:return: The transformed Dataset
|
|
25
|
+
"""
|
|
26
|
+
return dataset
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def resample(X: pd.DataFrame, y: list) -> tuple[pd.DataFrame, list]:
|
|
30
|
+
"""VoidStep : Do nothing
|
|
31
|
+
|
|
32
|
+
:param pd.DataFrame X: The dataframe to resample
|
|
33
|
+
:param List y: The dataframe target to resample
|
|
34
|
+
:return: The resampled X and y
|
|
35
|
+
"""
|
|
36
|
+
return X, y
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@is_step()
|
|
40
|
+
class VoidStep(Step):
|
|
41
|
+
"""[STEP] Void step -> Just a step that do nothing and can be mutated to siblings
|
|
42
|
+
|
|
43
|
+
:param Tuple, optional args: Additional parameters
|
|
44
|
+
:param Step, optional step_to_mimic: The step to mimic, if provided
|
|
45
|
+
:param Dict, optional kwargs: Additional parameters
|
|
46
|
+
"""
|
|
47
|
+
_usage: str = "Use when you need a no-op placeholder that mimics another step during search, unlike Actionable. Applicable to any dataset when inputs must pass through unchanged. Avoid when you need a real operation or orchestration; consider MetaStep or MetaOrderedStep."
|
|
48
|
+
name: str = "VoidStep"
|
|
49
|
+
def __init__(self, *args, step_to_mimic: Step = None, **kwargs) -> None: # pylint: disable=unused-argument
|
|
50
|
+
"""VoidStep : Do nothing"""
|
|
51
|
+
if step_to_mimic:
|
|
52
|
+
self.tags = step_to_mimic.tags
|
|
53
|
+
self.step_to_mimic = step_to_mimic
|
|
54
|
+
self.is_interchangeable = True
|
|
55
|
+
self.optimizable = True
|
|
56
|
+
|
|
57
|
+
if hasattr(step_to_mimic, 'predict') and callable(step_to_mimic.predict):
|
|
58
|
+
self.predict = predict
|
|
59
|
+
elif hasattr(step_to_mimic, 'transform') and callable(step_to_mimic.transform):
|
|
60
|
+
self.transform = transform
|
|
61
|
+
elif hasattr(step_to_mimic, 'resample') and callable(step_to_mimic.resample):
|
|
62
|
+
self.resample = resample
|
|
63
|
+
|
|
64
|
+
def fingerprint(self) -> str:
|
|
65
|
+
"""Keep cached placeholders in their own operation and mutation family."""
|
|
66
|
+
mimic = getattr(self, 'step_to_mimic', None)
|
|
67
|
+
signature = (
|
|
68
|
+
super().fingerprint(),
|
|
69
|
+
sorted(self.tags or []),
|
|
70
|
+
mimic.fingerprint() if mimic is not None else None,
|
|
71
|
+
)
|
|
72
|
+
return md5(json.dumps(signature).encode()).hexdigest()
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def from_pipeline(cls, pipeline: dict, *args, **kwargs) -> Step:
|
|
76
|
+
"""Load any VoidStep from json pipeline
|
|
77
|
+
|
|
78
|
+
:param dict pipeline: Pipeline in a JSON format.
|
|
79
|
+
:param tuple, optional \\*args: Additional parameters.
|
|
80
|
+
:param dict, optional \\**kwargs: Additional parameters.
|
|
81
|
+
:raise TypeError: Invalid pipeline: VoidStep must have a Step to mimic
|
|
82
|
+
:return: Step created from Json pipeline
|
|
83
|
+
"""
|
|
84
|
+
if not 'step_to_mimic' in pipeline:
|
|
85
|
+
raise TypeError('invalid pipeline: VoidStep must have a Step to mimic')
|
|
86
|
+
|
|
87
|
+
to_mimic = Step.from_pipeline(pipeline['step_to_mimic'])
|
|
88
|
+
|
|
89
|
+
step = super().from_pipeline(pipeline, step_to_mimic=to_mimic)
|
|
90
|
+
|
|
91
|
+
return step
|
|
92
|
+
|
|
93
|
+
def json_pipeline(self) -> dict:
|
|
94
|
+
"""Create JSON pipeline
|
|
95
|
+
|
|
96
|
+
:return: Pipeline in JSON format
|
|
97
|
+
"""
|
|
98
|
+
return {
|
|
99
|
+
**Step.json_pipeline(self),
|
|
100
|
+
'step_to_mimic': self.step_to_mimic.json_pipeline()
|
|
101
|
+
}
|
iaml/worker_manager.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""This implementation adds a level of abstraction to futures, allowing us to
|
|
2
|
+
manage the worker queue with more control, and keep multithreading code
|
|
3
|
+
within the steps as simple as possible.
|
|
4
|
+
"""
|
|
5
|
+
import math
|
|
6
|
+
import os
|
|
7
|
+
import traceback
|
|
8
|
+
|
|
9
|
+
from concurrent.futures import ThreadPoolExecutor, Future
|
|
10
|
+
from typing import Any
|
|
11
|
+
from .logger import Logger
|
|
12
|
+
from .meta_singleton import MetaSingleton
|
|
13
|
+
from .step import Step
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class WorkerFuture(Future):
|
|
17
|
+
"""Thread Step
|
|
18
|
+
|
|
19
|
+
:param Step step: The step that will be performed by the Thread
|
|
20
|
+
:param callable task: The function to run on the thread
|
|
21
|
+
:param tuple, optional args: callable parameters
|
|
22
|
+
:param dict, optional kwargs: callable parameters
|
|
23
|
+
"""
|
|
24
|
+
def __init__(self, step: Step, task: callable, *args, **kwargs) -> None:
|
|
25
|
+
super().__init__()
|
|
26
|
+
|
|
27
|
+
self.step: Step = step
|
|
28
|
+
"""The step that will be performed by the Thread"""
|
|
29
|
+
|
|
30
|
+
self.task: callable = task
|
|
31
|
+
"""The function to run on the thread"""
|
|
32
|
+
|
|
33
|
+
self.args: tuple = args
|
|
34
|
+
"""Optional parameters to be passed to the callable"""
|
|
35
|
+
|
|
36
|
+
self.kwargs: dict = kwargs
|
|
37
|
+
"""Optional parameters to be passed to the callable"""
|
|
38
|
+
|
|
39
|
+
def run(self) -> Any:
|
|
40
|
+
"""Run the thread and execute the affected task
|
|
41
|
+
In case of error, log them and return an empty list
|
|
42
|
+
|
|
43
|
+
:return: The task result or an empty list in case of errors.
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
return self.task(*self.args, **self.kwargs)
|
|
47
|
+
except: # pylint: disable=bare-except
|
|
48
|
+
Logger().error(f'[red]A worker for [b]{self.step.__class__.__name__}[/b] \
|
|
49
|
+
has crashed.[/red]')
|
|
50
|
+
Logger().error(traceback.format_exc())
|
|
51
|
+
|
|
52
|
+
return []
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class WorkerManager(metaclass=MetaSingleton):
|
|
56
|
+
"""This implementation adds a level of abstraction to futures, allowing us to
|
|
57
|
+
manage the worker queue with more control, and keep multithreading code
|
|
58
|
+
within the steps as simple as possible.
|
|
59
|
+
|
|
60
|
+
This worker manager acts as a smarter thread pool executor, which is able
|
|
61
|
+
to enqueue resource-intensive tasks in order to limit the amount of active
|
|
62
|
+
threads while still allowing jobs to complete when they depend on "child"
|
|
63
|
+
jobs, preventing deadlocks.
|
|
64
|
+
|
|
65
|
+
Reference: https://docs.python.org/3/library/concurrent.futures.html#threadpoolexecutor.
|
|
66
|
+
|
|
67
|
+
:param int, optional max_workers: Maximum number of parallel workers
|
|
68
|
+
"""
|
|
69
|
+
def __init__(self, max_workers: int = None) -> None:
|
|
70
|
+
self.active_workers_count: int = 0
|
|
71
|
+
"""Hold the current number of active workers"""
|
|
72
|
+
|
|
73
|
+
self.executor: ThreadPoolExecutor = ThreadPoolExecutor(
|
|
74
|
+
max_workers=math.inf,
|
|
75
|
+
thread_name_prefix='WorkerManager')
|
|
76
|
+
"""ThreadPoolExecutor object, handling workers"""
|
|
77
|
+
|
|
78
|
+
self.max_workers: int = max_workers or os.cpu_count()
|
|
79
|
+
"""The maximum number of paralllel workers"""
|
|
80
|
+
|
|
81
|
+
self.running_futures: list[WorkerFuture] = []
|
|
82
|
+
"""List of running workers"""
|
|
83
|
+
|
|
84
|
+
self.running_parents: list[list[int]] = []
|
|
85
|
+
"""List of Lists for parents ids"""
|
|
86
|
+
|
|
87
|
+
self.queue: list[WorkerFuture] = []
|
|
88
|
+
"""List of worker pending"""
|
|
89
|
+
|
|
90
|
+
def __done_callback(self, base_future: Future, worker_future: WorkerFuture) -> None:
|
|
91
|
+
"""Function called when a thread is done working
|
|
92
|
+
|
|
93
|
+
:param Future base_future: basic asynchronous task
|
|
94
|
+
:param WorkerFuture worker_future: Our asynchronous thread task
|
|
95
|
+
"""
|
|
96
|
+
worker_future.set_result(base_future.result())
|
|
97
|
+
|
|
98
|
+
self.active_workers_count -= 1
|
|
99
|
+
self.running_futures.remove(worker_future)
|
|
100
|
+
|
|
101
|
+
self.run_next(worker_future.step)
|
|
102
|
+
|
|
103
|
+
def is_sibling_running(self, step: Step) -> bool:
|
|
104
|
+
"""Checks whether a sibling of `step` is currently running.
|
|
105
|
+
|
|
106
|
+
:param Step step: The step we are working on
|
|
107
|
+
:return: Either the parent steps of our steps is running in an asynchronous thread
|
|
108
|
+
"""
|
|
109
|
+
return step.parents_steps in [ f.step.parents_steps for f in self.running_futures ]
|
|
110
|
+
|
|
111
|
+
def run(self, future: WorkerFuture) -> None:
|
|
112
|
+
"""Immediately starts `future`.
|
|
113
|
+
|
|
114
|
+
:param WorkerFuture future: The asynchronous task to run
|
|
115
|
+
"""
|
|
116
|
+
# parents are supposed to wait for their children to complete before
|
|
117
|
+
# doing anything else, so they are not an "active" worker as long as
|
|
118
|
+
# they have children: "release" a worker
|
|
119
|
+
if future.step.parents_steps not in self.running_parents:
|
|
120
|
+
self.running_parents.append(future.step.parents_steps)
|
|
121
|
+
|
|
122
|
+
self.active_workers_count += 1
|
|
123
|
+
self.running_futures.append(future)
|
|
124
|
+
|
|
125
|
+
f = self.executor.submit(future.run)
|
|
126
|
+
f.add_done_callback(lambda _f: self.__done_callback(_f, future))
|
|
127
|
+
|
|
128
|
+
def run_next(self, current_step: Step) -> None:
|
|
129
|
+
"""Finds a sibling of `current_step`, and starts it. This should be used
|
|
130
|
+
when a future is done running. If there is no sibling, start whatever
|
|
131
|
+
is next in the queue.
|
|
132
|
+
|
|
133
|
+
:param Step current_step: The current step we need to find the siblings
|
|
134
|
+
"""
|
|
135
|
+
next_sibling_in_queue = next(( f for f in self.queue \
|
|
136
|
+
if current_step.parents_steps == f.step.parents_steps ), None)
|
|
137
|
+
|
|
138
|
+
if next_sibling_in_queue is not None:
|
|
139
|
+
self.queue.remove(next_sibling_in_queue)
|
|
140
|
+
self.run(next_sibling_in_queue)
|
|
141
|
+
else:
|
|
142
|
+
if current_step.parents_steps in self.running_parents:
|
|
143
|
+
# all the children have completed: "unrelease" a worker
|
|
144
|
+
self.running_parents.remove(current_step.parents_steps)
|
|
145
|
+
|
|
146
|
+
if (self.active_workers_count - len(self.running_parents) + 1) < self.max_workers \
|
|
147
|
+
and len(self.queue) > 0:
|
|
148
|
+
self.run(self.queue.pop())
|
|
149
|
+
|
|
150
|
+
def submit(self, step: Step, task: callable, *args, **kwargs) -> WorkerFuture:
|
|
151
|
+
"""Submits a job to the worker manager, and returns a wrapped future which
|
|
152
|
+
can be used to wait for the job to end, even if it's not started yet
|
|
153
|
+
(as opposed to jobs submitted to a `ThreadPoolExecutor`).
|
|
154
|
+
|
|
155
|
+
:param Step step: The step we submit to the asynchronous task
|
|
156
|
+
:param callable task: The function to run on our asynchronous task
|
|
157
|
+
:param Tuple, optional args: callable parameters
|
|
158
|
+
:param Dict, optional kwargs: callable parameters
|
|
159
|
+
:return: The future we just created
|
|
160
|
+
"""
|
|
161
|
+
future = WorkerFuture(step, task, *args, **kwargs)
|
|
162
|
+
|
|
163
|
+
if (self.active_workers_count - len(self.running_parents) + 1) < self.max_workers \
|
|
164
|
+
or not self.is_sibling_running(step):
|
|
165
|
+
self.run(future)
|
|
166
|
+
else:
|
|
167
|
+
self.queue.append(future)
|
|
168
|
+
|
|
169
|
+
return future
|
iaml/wrapper/__init__.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""[WRAPPER] Wrap a step to apply Grid Search configuration parameters"""
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
from itertools import product
|
|
4
|
+
|
|
5
|
+
from ..step_wrapper import StepWrapper
|
|
6
|
+
from ..candidate import Candidate
|
|
7
|
+
from ..decorators.all import is_step, runner
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@is_step('wrapper')
|
|
11
|
+
class WrapBasicGridSearch(StepWrapper):
|
|
12
|
+
"""[WRAPPER] Wrap a step to apply Grid Search configuration parameters"""
|
|
13
|
+
|
|
14
|
+
_usage: str = "Use when you want a quick, simple grid around current values and prefer it over WrapGeneticGridSearch or WrapIterativeGridSearch. Applicable to numeric, categorical, and boolean hyperparameters. Avoid when search space is large or needs adaptive or iterative exploration."
|
|
15
|
+
to_avoid: list[str] = ['random_state'] # List of ignored key
|
|
16
|
+
|
|
17
|
+
@runner
|
|
18
|
+
def run(self, candidate: Candidate) -> list[Candidate]:
|
|
19
|
+
"""Super basic GridSearch.
|
|
20
|
+
|
|
21
|
+
- Numeric values -> 11 runs with 10% (50% to 150%)
|
|
22
|
+
- Categorial values -> 1 run each
|
|
23
|
+
- Boolean values -> Run with True and False
|
|
24
|
+
- Other -> keep current value
|
|
25
|
+
|
|
26
|
+
:param Candidate candidate: Candidate data
|
|
27
|
+
:return: All transformed candidates
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
to_explore = {}
|
|
31
|
+
|
|
32
|
+
for key, item in self.step.configuration.items():
|
|
33
|
+
if key in self.to_avoid or item.get('no_gridsearch'):
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
if 'categorical' in item.keys(): # Categorical
|
|
37
|
+
to_explore[key] = item['categorical']
|
|
38
|
+
elif type(item['value']) in [int, float]: # Numeric
|
|
39
|
+
current_value = item['value']
|
|
40
|
+
|
|
41
|
+
tmp = [factor * current_value for factor in
|
|
42
|
+
[.5, .6, .7, .8, .9, 1, 1.1, 1.2, 1.3, 1.4, 1.5]]
|
|
43
|
+
|
|
44
|
+
# Keep int
|
|
45
|
+
if isinstance(item['value'], int):
|
|
46
|
+
tmp = [round(x) for x in tmp]
|
|
47
|
+
|
|
48
|
+
# Check range
|
|
49
|
+
if 'range' in item.keys():
|
|
50
|
+
lower, upper = item['range']
|
|
51
|
+
tmp = [value for value in tmp
|
|
52
|
+
if (lower is None or lower <= value)
|
|
53
|
+
and (upper is None or value <= upper)]
|
|
54
|
+
|
|
55
|
+
to_explore[key] = list(dict.fromkeys(tmp))
|
|
56
|
+
|
|
57
|
+
elif isinstance(item['value'], bool): # Bool
|
|
58
|
+
to_explore[key] = [True, False]
|
|
59
|
+
else: # Other
|
|
60
|
+
to_explore[key] = [item['value']]
|
|
61
|
+
|
|
62
|
+
candidates = []
|
|
63
|
+
for values in product(*to_explore.values()):
|
|
64
|
+
step = deepcopy(self.step)
|
|
65
|
+
step.configure(dict(zip(to_explore, values)))
|
|
66
|
+
results = step.run(candidate.to_output())
|
|
67
|
+
candidates.extend([results] if isinstance(results, Candidate) else results)
|
|
68
|
+
return candidates
|