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.
Files changed (279) hide show
  1. iaml/__init__.py +56 -0
  2. iaml/actionable.py +11 -0
  3. iaml/actionables/__init__.py +21 -0
  4. iaml/actionables/boosting/__init__.py +4 -0
  5. iaml/actionables/boosting/act_adaboost.py +59 -0
  6. iaml/actionables/cleaning/__init__.py +26 -0
  7. iaml/actionables/cleaning/act_categorical_imputer.py +124 -0
  8. iaml/actionables/cleaning/act_count_vectorizer.py +204 -0
  9. iaml/actionables/cleaning/act_drop_categorical_column.py +51 -0
  10. iaml/actionables/cleaning/act_drop_date_column.py +48 -0
  11. iaml/actionables/cleaning/act_drop_high_cardinality_categorical.py +337 -0
  12. iaml/actionables/cleaning/act_drop_numerical_column.py +75 -0
  13. iaml/actionables/cleaning/act_drop_textual_column.py +51 -0
  14. iaml/actionables/cleaning/act_encode_target_column.py +56 -0
  15. iaml/actionables/cleaning/act_frequency_encoder.py +127 -0
  16. iaml/actionables/cleaning/act_hashing_vectorizer.py +186 -0
  17. iaml/actionables/cleaning/act_knn_imputer.py +152 -0
  18. iaml/actionables/cleaning/act_mean_column.py +79 -0
  19. iaml/actionables/cleaning/act_mice.py +464 -0
  20. iaml/actionables/cleaning/act_missing_count_feature.py +109 -0
  21. iaml/actionables/cleaning/act_missing_indicator.py +124 -0
  22. iaml/actionables/cleaning/act_onehot.py +65 -0
  23. iaml/actionables/cleaning/act_ordinal_encoder.py +177 -0
  24. iaml/actionables/cleaning/act_rare_category_grouper.py +173 -0
  25. iaml/actionables/cleaning/act_simple_imputer.py +109 -0
  26. iaml/actionables/cleaning/act_split_date.py +68 -0
  27. iaml/actionables/cleaning/act_target_encoder.py +274 -0
  28. iaml/actionables/cleaning/act_text_normalizer.py +241 -0
  29. iaml/actionables/cleaning/act_tf_idf.py +80 -0
  30. iaml/actionables/cleaning/act_word2vec.py +150 -0
  31. iaml/actionables/features_precleaning/__init__.py +12 -0
  32. iaml/actionables/features_precleaning/act_coerce_numeric_strings.py +194 -0
  33. iaml/actionables/features_precleaning/act_date_converter.py +99 -0
  34. iaml/actionables/features_precleaning/act_drop_bad_quality_rows.py +77 -0
  35. iaml/actionables/features_precleaning/act_drop_duplicate_rows.py +131 -0
  36. iaml/actionables/features_precleaning/act_drop_high_missing_columns.py +94 -0
  37. iaml/actionables/features_precleaning/act_drop_id_like_columns.py +294 -0
  38. iaml/actionables/features_precleaning/act_normalize_column_names.py +157 -0
  39. iaml/actionables/features_precleaning/act_sentinel_to_na_n.py +270 -0
  40. iaml/actionables/features_precleaning/act_trim_space.py +79 -0
  41. iaml/actionables/features_preprocessing/__init__.py +18 -0
  42. iaml/actionables/features_preprocessing/act_cyclical_date_encoding.py +212 -0
  43. iaml/actionables/features_preprocessing/act_fast_ica.py +161 -0
  44. iaml/actionables/features_preprocessing/act_feature_agglomeration.py +90 -0
  45. iaml/actionables/features_preprocessing/act_k_bins_discretizer.py +207 -0
  46. iaml/actionables/features_preprocessing/act_k_means_features.py +296 -0
  47. iaml/actionables/features_preprocessing/act_kernel_pca.py +143 -0
  48. iaml/actionables/features_preprocessing/act_log_transformer.py +122 -0
  49. iaml/actionables/features_preprocessing/act_nystroem.py +100 -0
  50. iaml/actionables/features_preprocessing/act_pca.py +77 -0
  51. iaml/actionables/features_preprocessing/act_polynomial_features.py +86 -0
  52. iaml/actionables/features_preprocessing/act_power_transformer.py +106 -0
  53. iaml/actionables/features_preprocessing/act_quantile_transformer.py +114 -0
  54. iaml/actionables/features_preprocessing/act_rbf_sampler.py +88 -0
  55. iaml/actionables/features_preprocessing/act_select_percentile.py +112 -0
  56. iaml/actionables/features_preprocessing/act_sparse_random_projection.py +157 -0
  57. iaml/actionables/features_preprocessing/act_truncated_svd.py +137 -0
  58. iaml/actionables/features_selection/__init__.py +8 -0
  59. iaml/actionables/features_selection/act_permutation_importance_selector.py +421 -0
  60. iaml/actionables/features_selection/act_remove_high_correlated_column.py +70 -0
  61. iaml/actionables/features_selection/act_remove_low_variance_column.py +74 -0
  62. iaml/actionables/features_selection/act_rfe.py +214 -0
  63. iaml/actionables/features_selection/act_select_from_model.py +325 -0
  64. iaml/actionables/features_selection/act_select_k_best.py +181 -0
  65. iaml/actionables/features_selection/act_vif_selector.py +130 -0
  66. iaml/actionables/imbalance/__init__.py +10 -0
  67. iaml/actionables/imbalance/act_adasyn.py +150 -0
  68. iaml/actionables/imbalance/act_borderline_smote.py +171 -0
  69. iaml/actionables/imbalance/act_near_miss.py +158 -0
  70. iaml/actionables/imbalance/act_random_over_sampling.py +60 -0
  71. iaml/actionables/imbalance/act_random_under_sampler.py +135 -0
  72. iaml/actionables/imbalance/act_smote.py +162 -0
  73. iaml/actionables/imbalance/act_smote_tomek.py +182 -0
  74. iaml/actionables/imbalance/act_smoteenn.py +193 -0
  75. iaml/actionables/imbalance/act_tomek_links.py +138 -0
  76. iaml/actionables/normalize/__init__.py +6 -0
  77. iaml/actionables/normalize/act_max_abs_scaler.py +78 -0
  78. iaml/actionables/normalize/act_minmax_scaler.py +56 -0
  79. iaml/actionables/normalize/act_normalizer.py +95 -0
  80. iaml/actionables/normalize/act_robust_scaler.py +111 -0
  81. iaml/actionables/normalize/act_standard_scaler.py +55 -0
  82. iaml/actionables/predictors/__init__.py +6 -0
  83. iaml/actionables/predictors/_xgboost.py +16 -0
  84. iaml/actionables/predictors/classifier/__init__.py +26 -0
  85. iaml/actionables/predictors/classifier/act_bagging_classifier.py +113 -0
  86. iaml/actionables/predictors/classifier/act_bernoulli_nb.py +89 -0
  87. iaml/actionables/predictors/classifier/act_catboost_classifier.py +135 -0
  88. iaml/actionables/predictors/classifier/act_complement_nb.py +106 -0
  89. iaml/actionables/predictors/classifier/act_decision_tree_classifier.py +117 -0
  90. iaml/actionables/predictors/classifier/act_extra_trees_classifier.py +115 -0
  91. iaml/actionables/predictors/classifier/act_gaussian_nb.py +53 -0
  92. iaml/actionables/predictors/classifier/act_hist_gradient_boosting_classifier.py +144 -0
  93. iaml/actionables/predictors/classifier/act_knn.py +86 -0
  94. iaml/actionables/predictors/classifier/act_light_gbm_classifier.py +211 -0
  95. iaml/actionables/predictors/classifier/act_linear_discriminant_analysis.py +63 -0
  96. iaml/actionables/predictors/classifier/act_linear_svc.py +134 -0
  97. iaml/actionables/predictors/classifier/act_logistic_regression.py +92 -0
  98. iaml/actionables/predictors/classifier/act_mlp_classifier.py +107 -0
  99. iaml/actionables/predictors/classifier/act_multinomial_nb.py +76 -0
  100. iaml/actionables/predictors/classifier/act_passive_aggressive_classifier.py +141 -0
  101. iaml/actionables/predictors/classifier/act_quadratic_discriminant_analysis.py +72 -0
  102. iaml/actionables/predictors/classifier/act_randomforest.py +113 -0
  103. iaml/actionables/predictors/classifier/act_ridge_classifier.py +116 -0
  104. iaml/actionables/predictors/classifier/act_sgd_classifier.py +149 -0
  105. iaml/actionables/predictors/classifier/act_svm_svc.py +88 -0
  106. iaml/actionables/predictors/classifier/act_xgboost.py +111 -0
  107. iaml/actionables/predictors/regressor/__init__.py +27 -0
  108. iaml/actionables/predictors/regressor/act_ada_boost_regressor.py +75 -0
  109. iaml/actionables/predictors/regressor/act_ard_regression.py +95 -0
  110. iaml/actionables/predictors/regressor/act_catboost_regressor.py +134 -0
  111. iaml/actionables/predictors/regressor/act_decision_tree_regressor.py +111 -0
  112. iaml/actionables/predictors/regressor/act_elastic_net_regressor.py +109 -0
  113. iaml/actionables/predictors/regressor/act_extra_trees_regressor.py +113 -0
  114. iaml/actionables/predictors/regressor/act_gaussian_process_regressor.py +55 -0
  115. iaml/actionables/predictors/regressor/act_gboost_regressor.py +95 -0
  116. iaml/actionables/predictors/regressor/act_hist_gradient_boosting_regressor.py +105 -0
  117. iaml/actionables/predictors/regressor/act_huber_regressor.py +101 -0
  118. iaml/actionables/predictors/regressor/act_knn_regressor.py +86 -0
  119. iaml/actionables/predictors/regressor/act_lasso_regressor.py +103 -0
  120. iaml/actionables/predictors/regressor/act_light_gbm_regressor.py +201 -0
  121. iaml/actionables/predictors/regressor/act_linear_regression.py +43 -0
  122. iaml/actionables/predictors/regressor/act_mlp_regressor.py +104 -0
  123. iaml/actionables/predictors/regressor/act_poisson_regressor.py +111 -0
  124. iaml/actionables/predictors/regressor/act_quantile_regressor.py +87 -0
  125. iaml/actionables/predictors/regressor/act_randomforest_regressor.py +116 -0
  126. iaml/actionables/predictors/regressor/act_ransac_regressor.py +106 -0
  127. iaml/actionables/predictors/regressor/act_ridge_regressor.py +107 -0
  128. iaml/actionables/predictors/regressor/act_sgd_regressor.py +106 -0
  129. iaml/actionables/predictors/regressor/act_svm_svr.py +81 -0
  130. iaml/actionables/predictors/regressor/act_xgboost_regressor.py +97 -0
  131. iaml/actionables/predictors/survival/__init__.py +12 -0
  132. iaml/actionables/predictors/survival/act_aalen_additive_model.py +83 -0
  133. iaml/actionables/predictors/survival/act_cox.py +110 -0
  134. iaml/actionables/predictors/survival/act_coxnet_survival_analysis.py +134 -0
  135. iaml/actionables/predictors/survival/act_extra_survival_trees.py +101 -0
  136. iaml/actionables/predictors/survival/act_fast_survival_svm.py +102 -0
  137. iaml/actionables/predictors/survival/act_gradient_boosting_survival_analysis.py +93 -0
  138. iaml/actionables/predictors/survival/act_random_survival_forest.py +91 -0
  139. iaml/actionables/predictors/survival/act_survival_component_wise_gboost.py +80 -0
  140. iaml/actionables/predictors/survival/act_survival_tree.py +120 -0
  141. iaml/actionables/predictors/survival/act_survival_xgboost.py +9 -0
  142. iaml/actionables/predictors/survival/act_weibull_aft.py +230 -0
  143. iaml/cache.py +61 -0
  144. iaml/cache_keys.py +57 -0
  145. iaml/candidate.py +736 -0
  146. iaml/core_dispatcher.py +125 -0
  147. iaml/data_type.py +11 -0
  148. iaml/dataset.py +506 -0
  149. iaml/decorators/__init__.py +3 -0
  150. iaml/decorators/all.py +4 -0
  151. iaml/decorators/is_step.py +45 -0
  152. iaml/decorators/runner.py +100 -0
  153. iaml/explanation.py +112 -0
  154. iaml/iaml.py +1072 -0
  155. iaml/iaml_pipeline.py +600 -0
  156. iaml/logger.py +138 -0
  157. iaml/meta_explorer_step.py +62 -0
  158. iaml/meta_ordered_step.py +28 -0
  159. iaml/meta_partial_explorer_step.py +34 -0
  160. iaml/meta_singleton.py +24 -0
  161. iaml/metastep.py +211 -0
  162. iaml/metric.py +111 -0
  163. iaml/metric_plot.py +82 -0
  164. iaml/metrics/__init__.py +21 -0
  165. iaml/metrics/_classification.py +28 -0
  166. iaml/metrics/_survival_times.py +22 -0
  167. iaml/metrics/accuracy_metric.py +59 -0
  168. iaml/metrics/balanced_accuracy_metric.py +67 -0
  169. iaml/metrics/brier_score.py +90 -0
  170. iaml/metrics/classification_error_metric.py +66 -0
  171. iaml/metrics/concordance_index_ipcw.py +84 -0
  172. iaml/metrics/concordance_index_metric.py +67 -0
  173. iaml/metrics/cumulative_dynamic_auc.py +119 -0
  174. iaml/metrics/f1_score_metric.py +71 -0
  175. iaml/metrics/integrated_brier_score.py +98 -0
  176. iaml/metrics/integrated_brier_score_loss.py +41 -0
  177. iaml/metrics/mean_absolute_error_metric.py +46 -0
  178. iaml/metrics/mean_squared_error_metric.py +46 -0
  179. iaml/metrics/mean_squared_log_error_metric.py +49 -0
  180. iaml/metrics/median_absolute_error_metric.py +48 -0
  181. iaml/metrics/precision_metric.py +63 -0
  182. iaml/metrics/r2_score_metric.py +45 -0
  183. iaml/metrics/recall_metric.py +65 -0
  184. iaml/metrics/roc_auc_metric.py +50 -0
  185. iaml/metrics/specificity_metric.py +44 -0
  186. iaml/metrics/specificity_multiclass_metric.py +55 -0
  187. iaml/metrics/specificity_multilabel_metric.py +60 -0
  188. iaml/optimizers/__init__.py +5 -0
  189. iaml/optimizers/bayesian_optimizer.py +193 -0
  190. iaml/optimizers/genetic_optimizer.py +284 -0
  191. iaml/optimizers/optimizer.py +31 -0
  192. iaml/optimizers/random_optimizer.py +101 -0
  193. iaml/plot.py +138 -0
  194. iaml/plots/__init__.py +32 -0
  195. iaml/plots/bar_plot.py +141 -0
  196. iaml/plots/box_plot.py +166 -0
  197. iaml/plots/class_prediction_error_plot.py +37 -0
  198. iaml/plots/classification_report_plot.py +35 -0
  199. iaml/plots/confusion_matrix_plot.py +34 -0
  200. iaml/plots/correlation_heatmap_plot.py +201 -0
  201. iaml/plots/cumulative_hazard_plot.py +72 -0
  202. iaml/plots/density_plot.py +210 -0
  203. iaml/plots/histogram_plot.py +179 -0
  204. iaml/plots/kaplan_meier_comparison_plot.py +89 -0
  205. iaml/plots/line_plot.py +70 -0
  206. iaml/plots/missingness_heatmap_plot.py +203 -0
  207. iaml/plots/outlier_plot.py +217 -0
  208. iaml/plots/pair_plot.py +228 -0
  209. iaml/plots/precision_recall_curve_plot.py +86 -0
  210. iaml/plots/prediction_error_plot.py +34 -0
  211. iaml/plots/qq_plot.py +220 -0
  212. iaml/plots/residual_plot.py +38 -0
  213. iaml/plots/roc_dynamique_curve_plot.py +79 -0
  214. iaml/plots/rocauc_plot.py +96 -0
  215. iaml/plots/shap_plot.py +187 -0
  216. iaml/plots/target_distribution_plot.py +241 -0
  217. iaml/plots/violin_plot.py +206 -0
  218. iaml/predictor.py +139 -0
  219. iaml/reference.py +65 -0
  220. iaml/shared_cache.py +90 -0
  221. iaml/sklearn_preprocessor.py +74 -0
  222. iaml/splitters/__init__.py +3 -0
  223. iaml/splitters/kfold_splitter.py +32 -0
  224. iaml/splitters/random_splitter.py +26 -0
  225. iaml/stack.py +39 -0
  226. iaml/statistic.py +66 -0
  227. iaml/statistics/__init__.py +77 -0
  228. iaml/statistics/anova_statistic.py +80 -0
  229. iaml/statistics/cardinality_ratio_statistic.py +63 -0
  230. iaml/statistics/category_cooccurrence_statistic.py +79 -0
  231. iaml/statistics/chi_square_statistic.py +81 -0
  232. iaml/statistics/coef_variation_statistic.py +72 -0
  233. iaml/statistics/correlation_with_target.py +105 -0
  234. iaml/statistics/count.py +72 -0
  235. iaml/statistics/data_type_summary_statistic.py +74 -0
  236. iaml/statistics/duplicate_row_statistic.py +56 -0
  237. iaml/statistics/effect_size_statistic.py +129 -0
  238. iaml/statistics/entropy_statistic.py +69 -0
  239. iaml/statistics/event_rate_statistic.py +52 -0
  240. iaml/statistics/grouped_mean_statistic.py +60 -0
  241. iaml/statistics/iqr_statistic.py +66 -0
  242. iaml/statistics/kurtosis.py +50 -0
  243. iaml/statistics/mad_statistic.py +66 -0
  244. iaml/statistics/mean.py +61 -0
  245. iaml/statistics/median_statistic.py +61 -0
  246. iaml/statistics/minmax.py +60 -0
  247. iaml/statistics/missing_rate_statistic.py +62 -0
  248. iaml/statistics/mode.py +47 -0
  249. iaml/statistics/most_frequent_ratio.py +81 -0
  250. iaml/statistics/outlier_count_iqr_statistic.py +76 -0
  251. iaml/statistics/quantile.py +59 -0
  252. iaml/statistics/range.py +53 -0
  253. iaml/statistics/rare_category_rate.py +92 -0
  254. iaml/statistics/skewness.py +53 -0
  255. iaml/statistics/stdev.py +50 -0
  256. iaml/statistics/summary_table_statistic.py +60 -0
  257. iaml/statistics/time_by_group_statistic.py +83 -0
  258. iaml/statistics/time_summary_statistic.py +56 -0
  259. iaml/statistics/top_k_value_counts.py +68 -0
  260. iaml/statistics/unique_count_statistic.py +57 -0
  261. iaml/statistics/value_counts.py +63 -0
  262. iaml/statistics/variance.py +51 -0
  263. iaml/statistics/violin.py +63 -0
  264. iaml/step.py +600 -0
  265. iaml/step_cache.py +87 -0
  266. iaml/step_wrapper.py +79 -0
  267. iaml/timed_pool_executor.py +492 -0
  268. iaml/type_of_target.py +68 -0
  269. iaml/void_step.py +101 -0
  270. iaml/worker_manager.py +169 -0
  271. iaml/wrapper/__init__.py +4 -0
  272. iaml/wrapper/wrap_basic_gridsearch.py +68 -0
  273. iaml/wrapper/wrap_genetic_gridsearch.py +293 -0
  274. iaml/wrapper/wrap_iterative_gridsearch.py +399 -0
  275. pyiaml-1.0.0.dist-info/METADATA +802 -0
  276. pyiaml-1.0.0.dist-info/RECORD +279 -0
  277. pyiaml-1.0.0.dist-info/WHEEL +5 -0
  278. pyiaml-1.0.0.dist-info/licenses/LICENSE +674 -0
  279. 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
@@ -0,0 +1,4 @@
1
+ """All Wrapper Steps"""
2
+ from .wrap_basic_gridsearch import WrapBasicGridSearch
3
+ from .wrap_iterative_gridsearch import GridIteration, WrapIterativeGridSearch
4
+ from .wrap_genetic_gridsearch import WrapGeneticGridSearch
@@ -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