FLAML 2.4.0__tar.gz → 2.4.1__tar.gz

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 (120) hide show
  1. {flaml-2.4.0 → flaml-2.4.1}/FLAML.egg-info/PKG-INFO +4 -4
  2. {flaml-2.4.0 → flaml-2.4.1}/FLAML.egg-info/requires.txt +2 -2
  3. {flaml-2.4.0 → flaml-2.4.1}/PKG-INFO +4 -4
  4. {flaml-2.4.0 → flaml-2.4.1}/README.md +1 -1
  5. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/automl.py +371 -10
  6. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/model.py +3 -1
  7. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/nlp/huggingface/training_args.py +1 -1
  8. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/nlp/huggingface/utils.py +1 -1
  9. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/task/time_series_task.py +1 -1
  10. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/time_series/sklearn.py +10 -1
  11. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/time_series/ts_data.py +7 -2
  12. flaml-2.4.1/flaml/version.py +1 -0
  13. {flaml-2.4.0 → flaml-2.4.1}/setup.py +2 -2
  14. flaml-2.4.0/flaml/version.py +0 -1
  15. {flaml-2.4.0 → flaml-2.4.1}/FLAML.egg-info/SOURCES.txt +0 -0
  16. {flaml-2.4.0 → flaml-2.4.1}/FLAML.egg-info/dependency_links.txt +0 -0
  17. {flaml-2.4.0 → flaml-2.4.1}/FLAML.egg-info/top_level.txt +0 -0
  18. {flaml-2.4.0 → flaml-2.4.1}/LICENSE +0 -0
  19. {flaml-2.4.0 → flaml-2.4.1}/NOTICE.md +0 -0
  20. {flaml-2.4.0 → flaml-2.4.1}/flaml/__init__.py +0 -0
  21. {flaml-2.4.0 → flaml-2.4.1}/flaml/autogen/__init__.py +0 -0
  22. {flaml-2.4.0 → flaml-2.4.1}/flaml/autogen/agentchat/__init__.py +0 -0
  23. {flaml-2.4.0 → flaml-2.4.1}/flaml/autogen/agentchat/agent.py +0 -0
  24. {flaml-2.4.0 → flaml-2.4.1}/flaml/autogen/agentchat/assistant_agent.py +0 -0
  25. {flaml-2.4.0 → flaml-2.4.1}/flaml/autogen/agentchat/conversable_agent.py +0 -0
  26. {flaml-2.4.0 → flaml-2.4.1}/flaml/autogen/agentchat/groupchat.py +0 -0
  27. {flaml-2.4.0 → flaml-2.4.1}/flaml/autogen/agentchat/user_proxy_agent.py +0 -0
  28. {flaml-2.4.0 → flaml-2.4.1}/flaml/autogen/code_utils.py +0 -0
  29. {flaml-2.4.0 → flaml-2.4.1}/flaml/autogen/extensions/__init__.py +0 -0
  30. {flaml-2.4.0 → flaml-2.4.1}/flaml/autogen/math_utils.py +0 -0
  31. {flaml-2.4.0 → flaml-2.4.1}/flaml/autogen/oai/__init__.py +0 -0
  32. {flaml-2.4.0 → flaml-2.4.1}/flaml/autogen/oai/completion.py +0 -0
  33. {flaml-2.4.0 → flaml-2.4.1}/flaml/autogen/oai/openai_utils.py +0 -0
  34. {flaml-2.4.0 → flaml-2.4.1}/flaml/autogen/retrieve_utils.py +0 -0
  35. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/__init__.py +0 -0
  36. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/contrib/__init__.py +0 -0
  37. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/contrib/histgb.py +0 -0
  38. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/data.py +0 -0
  39. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/logger.py +0 -0
  40. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/ml.py +0 -0
  41. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/nlp/__init__.py +0 -0
  42. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/nlp/huggingface/__init__.py +0 -0
  43. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/nlp/huggingface/data_collator.py +0 -0
  44. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/nlp/huggingface/trainer.py +0 -0
  45. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/nlp/utils.py +0 -0
  46. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/spark/__init__.py +0 -0
  47. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/spark/metrics.py +0 -0
  48. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/spark/utils.py +0 -0
  49. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/state.py +0 -0
  50. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/task/__init__.py +0 -0
  51. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/task/factory.py +0 -0
  52. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/task/generic_task.py +0 -0
  53. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/task/task.py +0 -0
  54. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/time_series/__init__.py +0 -0
  55. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/time_series/feature.py +0 -0
  56. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/time_series/tcn.py +0 -0
  57. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/time_series/tft.py +0 -0
  58. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/time_series/ts_model.py +0 -0
  59. {flaml-2.4.0 → flaml-2.4.1}/flaml/automl/training_log.py +0 -0
  60. {flaml-2.4.0 → flaml-2.4.1}/flaml/config.py +0 -0
  61. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/__init__.py +0 -0
  62. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/all/binary.json +0 -0
  63. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/all/multiclass.json +0 -0
  64. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/all/regression.json +0 -0
  65. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/estimator.py +0 -0
  66. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/extra_tree/binary.json +0 -0
  67. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/extra_tree/multiclass.json +0 -0
  68. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/extra_tree/regression.json +0 -0
  69. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/greedy.py +0 -0
  70. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/lgbm/binary.json +0 -0
  71. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/lgbm/multiclass.json +0 -0
  72. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/lgbm/regression.json +0 -0
  73. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/portfolio.py +0 -0
  74. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/regret.py +0 -0
  75. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/rf/binary.json +0 -0
  76. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/rf/multiclass.json +0 -0
  77. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/rf/regression.json +0 -0
  78. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/suggest.py +0 -0
  79. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/xgb_limitdepth/binary.json +0 -0
  80. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/xgb_limitdepth/multiclass.json +0 -0
  81. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/xgb_limitdepth/regression.json +0 -0
  82. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/xgboost/binary.json +0 -0
  83. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/xgboost/multiclass.json +0 -0
  84. {flaml-2.4.0 → flaml-2.4.1}/flaml/default/xgboost/regression.json +0 -0
  85. {flaml-2.4.0 → flaml-2.4.1}/flaml/fabric/__init__.py +0 -0
  86. {flaml-2.4.0 → flaml-2.4.1}/flaml/fabric/mlflow.py +0 -0
  87. {flaml-2.4.0 → flaml-2.4.1}/flaml/ml.py +0 -0
  88. {flaml-2.4.0 → flaml-2.4.1}/flaml/onlineml/__init__.py +0 -0
  89. {flaml-2.4.0 → flaml-2.4.1}/flaml/onlineml/autovw.py +0 -0
  90. {flaml-2.4.0 → flaml-2.4.1}/flaml/onlineml/trial.py +0 -0
  91. {flaml-2.4.0 → flaml-2.4.1}/flaml/onlineml/trial_runner.py +0 -0
  92. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/__init__.py +0 -0
  93. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/analysis.py +0 -0
  94. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/logger.py +0 -0
  95. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/result.py +0 -0
  96. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/sample.py +0 -0
  97. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/scheduler/__init__.py +0 -0
  98. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/scheduler/online_scheduler.py +0 -0
  99. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/scheduler/trial_scheduler.py +0 -0
  100. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/searcher/__init__.py +0 -0
  101. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/searcher/blendsearch.py +0 -0
  102. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/searcher/cfo_cat.py +0 -0
  103. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/searcher/flow2.py +0 -0
  104. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/searcher/online_searcher.py +0 -0
  105. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/searcher/search_thread.py +0 -0
  106. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/searcher/suggestion.py +0 -0
  107. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/searcher/variant_generator.py +0 -0
  108. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/space.py +0 -0
  109. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/spark/__init__.py +0 -0
  110. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/spark/utils.py +0 -0
  111. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/trial.py +0 -0
  112. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/trial_runner.py +0 -0
  113. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/tune.py +0 -0
  114. {flaml-2.4.0 → flaml-2.4.1}/flaml/tune/utils.py +0 -0
  115. {flaml-2.4.0 → flaml-2.4.1}/pyproject.toml +0 -0
  116. {flaml-2.4.0 → flaml-2.4.1}/setup.cfg +0 -0
  117. {flaml-2.4.0 → flaml-2.4.1}/test/test_autovw.py +0 -0
  118. {flaml-2.4.0 → flaml-2.4.1}/test/test_conda_distribution.py +0 -0
  119. {flaml-2.4.0 → flaml-2.4.1}/test/test_gpu.py +0 -0
  120. {flaml-2.4.0 → flaml-2.4.1}/test/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: FLAML
3
- Version: 2.4.0
3
+ Version: 2.4.1
4
4
  Summary: A fast library for automated machine learning and tuning
5
5
  Home-page: https://github.com/microsoft/FLAML
6
6
  Author: Microsoft Corporation
@@ -87,13 +87,13 @@ Provides-Extra: vw
87
87
  Requires-Dist: vowpalwabbit<9.0.0,>=8.10.0; extra == "vw"
88
88
  Requires-Dist: scikit-learn; extra == "vw"
89
89
  Provides-Extra: hf
90
- Requires-Dist: transformers[torch]==4.26; extra == "hf"
90
+ Requires-Dist: transformers[torch]>=4.26; extra == "hf"
91
91
  Requires-Dist: datasets; extra == "hf"
92
92
  Requires-Dist: nltk<=3.8.1; extra == "hf"
93
93
  Requires-Dist: rouge_score; extra == "hf"
94
94
  Requires-Dist: seqeval; extra == "hf"
95
95
  Provides-Extra: nlp
96
- Requires-Dist: transformers[torch]==4.26; extra == "nlp"
96
+ Requires-Dist: transformers[torch]>=4.26; extra == "nlp"
97
97
  Requires-Dist: datasets; extra == "nlp"
98
98
  Requires-Dist: nltk<=3.8.1; extra == "nlp"
99
99
  Requires-Dist: rouge_score; extra == "nlp"
@@ -172,7 +172,7 @@ FLAML has a .NET implementation in [ML.NET](http://dot.net/ml), an open-source,
172
172
 
173
173
  ## Installation
174
174
 
175
- FLAML requires **Python version >= 3.9**. It can be installed from pip:
175
+ The latest version of FLAML requires **Python >= 3.10 and \< 3.13**. While other Python versions may work for core components, full model support is not guaranteed. FLAML can be installed via `pip`:
176
176
 
177
177
  ```bash
178
178
  pip install flaml
@@ -40,14 +40,14 @@ tensorboardX>=2.6
40
40
  pytorch-forecasting>=0.10.4
41
41
 
42
42
  [hf]
43
- transformers[torch]==4.26
43
+ transformers[torch]>=4.26
44
44
  datasets
45
45
  nltk<=3.8.1
46
46
  rouge_score
47
47
  seqeval
48
48
 
49
49
  [nlp]
50
- transformers[torch]==4.26
50
+ transformers[torch]>=4.26
51
51
  datasets
52
52
  nltk<=3.8.1
53
53
  rouge_score
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: FLAML
3
- Version: 2.4.0
3
+ Version: 2.4.1
4
4
  Summary: A fast library for automated machine learning and tuning
5
5
  Home-page: https://github.com/microsoft/FLAML
6
6
  Author: Microsoft Corporation
@@ -87,13 +87,13 @@ Provides-Extra: vw
87
87
  Requires-Dist: vowpalwabbit<9.0.0,>=8.10.0; extra == "vw"
88
88
  Requires-Dist: scikit-learn; extra == "vw"
89
89
  Provides-Extra: hf
90
- Requires-Dist: transformers[torch]==4.26; extra == "hf"
90
+ Requires-Dist: transformers[torch]>=4.26; extra == "hf"
91
91
  Requires-Dist: datasets; extra == "hf"
92
92
  Requires-Dist: nltk<=3.8.1; extra == "hf"
93
93
  Requires-Dist: rouge_score; extra == "hf"
94
94
  Requires-Dist: seqeval; extra == "hf"
95
95
  Provides-Extra: nlp
96
- Requires-Dist: transformers[torch]==4.26; extra == "nlp"
96
+ Requires-Dist: transformers[torch]>=4.26; extra == "nlp"
97
97
  Requires-Dist: datasets; extra == "nlp"
98
98
  Requires-Dist: nltk<=3.8.1; extra == "nlp"
99
99
  Requires-Dist: rouge_score; extra == "nlp"
@@ -172,7 +172,7 @@ FLAML has a .NET implementation in [ML.NET](http://dot.net/ml), an open-source,
172
172
 
173
173
  ## Installation
174
174
 
175
- FLAML requires **Python version >= 3.9**. It can be installed from pip:
175
+ The latest version of FLAML requires **Python >= 3.10 and \< 3.13**. While other Python versions may work for core components, full model support is not guaranteed. FLAML can be installed via `pip`:
176
176
 
177
177
  ```bash
178
178
  pip install flaml
@@ -34,7 +34,7 @@ FLAML has a .NET implementation in [ML.NET](http://dot.net/ml), an open-source,
34
34
 
35
35
  ## Installation
36
36
 
37
- FLAML requires **Python version >= 3.9**. It can be installed from pip:
37
+ The latest version of FLAML requires **Python >= 3.10 and \< 3.13**. While other Python versions may work for core components, full model support is not guaranteed. FLAML can be installed via `pip`:
38
38
 
39
39
  ```bash
40
40
  pip install flaml
@@ -413,13 +413,47 @@ class AutoML(BaseEstimator):
413
413
  """
414
414
 
415
415
  state = self.__dict__.copy()
416
- state.pop("mlflow_integration", None)
416
+ # Keep mlflow_integration for post-load visualization (e.g., infos), but
417
+ # strip non-picklable runtime-only members (thread futures, clients).
418
+ mlflow_integration = state.get("mlflow_integration", None)
419
+ if mlflow_integration is not None:
420
+ import copy
421
+
422
+ mi = copy.copy(mlflow_integration)
423
+ # These are runtime-only and often contain locks / threads.
424
+ if hasattr(mi, "futures"):
425
+ mi.futures = {}
426
+ if hasattr(mi, "futures_log_model"):
427
+ mi.futures_log_model = {}
428
+ if hasattr(mi, "train_func"):
429
+ mi.train_func = None
430
+ if hasattr(mi, "mlflow_client"):
431
+ mi.mlflow_client = None
432
+ state["mlflow_integration"] = mi
433
+ # MLflow signature objects may hold references to Spark/pandas-on-Spark
434
+ # inputs and can indirectly capture SparkContext, which is not picklable.
435
+ state.pop("estimator_signature", None)
436
+ state.pop("pipeline_signature", None)
417
437
  return state
418
438
 
419
439
  def __setstate__(self, state):
420
440
  self.__dict__.update(state)
421
- # Ensure attribute exists post-unpickle.
422
- self.mlflow_integration = None
441
+ # Ensure mlflow_integration runtime members exist post-unpickle.
442
+ mi = getattr(self, "mlflow_integration", None)
443
+ if mi is not None:
444
+ if not hasattr(mi, "futures") or mi.futures is None:
445
+ mi.futures = {}
446
+ if not hasattr(mi, "futures_log_model") or mi.futures_log_model is None:
447
+ mi.futures_log_model = {}
448
+ if not hasattr(mi, "train_func"):
449
+ mi.train_func = None
450
+ if not hasattr(mi, "mlflow_client") or mi.mlflow_client is None:
451
+ try:
452
+ import mlflow as _mlflow
453
+
454
+ mi.mlflow_client = _mlflow.tracking.MlflowClient()
455
+ except Exception:
456
+ mi.mlflow_client = None
423
457
 
424
458
  def get_params(self, deep: bool = False) -> dict:
425
459
  return self._settings.copy()
@@ -1114,17 +1148,344 @@ class AutoML(BaseEstimator):
1114
1148
  return self._state.data_size[0] if self._sample else None
1115
1149
 
1116
1150
  def pickle(self, output_file_name):
1151
+ """Serialize the AutoML instance to a pickle file.
1152
+
1153
+ Notes:
1154
+ When the trained estimator(s) are Spark-based, they may hold references
1155
+ to SparkContext/SparkSession via Spark ML objects. Such objects are not
1156
+ safely picklable and can cause pickling/broadcast errors.
1157
+
1158
+ This method externalizes Spark ML models into an adjacent artifact
1159
+ directory and stores only lightweight metadata in the pickle.
1160
+ """
1161
+
1162
+ import os
1117
1163
  import pickle
1164
+ import re
1165
+
1166
+ def _safe_name(name: str) -> str:
1167
+ return re.sub(r"[^A-Za-z0-9_.-]+", "_", name)
1168
+
1169
+ def _iter_trained_estimators():
1170
+ trained = getattr(self, "_trained_estimator", None)
1171
+ if trained is not None:
1172
+ yield "_trained_estimator", trained
1173
+ for est_name in getattr(self, "estimator_list", []) or []:
1174
+ ss = getattr(self, "_search_states", {}).get(est_name)
1175
+ te = ss and getattr(ss, "trained_estimator", None)
1176
+ if te is not None:
1177
+ yield f"_search_states.{est_name}.trained_estimator", te
1178
+
1179
+ def _scrub_pyspark_refs(root_obj):
1180
+ """Best-effort removal of pyspark objects prior to pickling.
1181
+
1182
+ SparkContext/SparkSession and Spark DataFrame objects are not picklable.
1183
+ This function finds such objects within common containers and instance
1184
+ attributes and replaces them with None, returning a restore mapping.
1185
+ """
1186
+
1187
+ try:
1188
+ import pyspark
1189
+ from pyspark.broadcast import Broadcast
1190
+ from pyspark.sql import DataFrame as SparkDataFrame
1191
+ from pyspark.sql import SparkSession
1118
1192
 
1193
+ try:
1194
+ import pyspark.pandas as ps
1195
+
1196
+ psDataFrameType = getattr(ps, "DataFrame", None)
1197
+ psSeriesType = getattr(ps, "Series", None)
1198
+ except Exception:
1199
+ psDataFrameType = None
1200
+ psSeriesType = None
1201
+
1202
+ bad_types = [
1203
+ pyspark.SparkContext,
1204
+ SparkSession,
1205
+ SparkDataFrame,
1206
+ Broadcast,
1207
+ ]
1208
+ if psDataFrameType is not None:
1209
+ bad_types.append(psDataFrameType)
1210
+ if psSeriesType is not None:
1211
+ bad_types.append(psSeriesType)
1212
+ bad_types = tuple(t for t in bad_types if t is not None)
1213
+ except Exception:
1214
+ return {}
1215
+
1216
+ restore = {}
1217
+ visited = set()
1218
+
1219
+ def _mark(parent, key, value, path):
1220
+ restore[(id(parent), key)] = (parent, key, value)
1221
+ try:
1222
+ if isinstance(parent, dict):
1223
+ parent[key] = None
1224
+ elif isinstance(parent, list):
1225
+ parent[key] = None
1226
+ elif isinstance(parent, tuple):
1227
+ # tuples are immutable; we can't modify in-place
1228
+ pass
1229
+ else:
1230
+ setattr(parent, key, None)
1231
+ except Exception:
1232
+ # Best-effort.
1233
+ pass
1234
+
1235
+ def _walk(obj, depth, parent=None, key=None, path="self"):
1236
+ if obj is None:
1237
+ return
1238
+ oid = id(obj)
1239
+ if oid in visited:
1240
+ return
1241
+ visited.add(oid)
1242
+
1243
+ if isinstance(obj, bad_types):
1244
+ if parent is not None:
1245
+ _mark(parent, key, obj, path)
1246
+ return
1247
+ if depth <= 0:
1248
+ return
1249
+
1250
+ if isinstance(obj, dict):
1251
+ for k, v in list(obj.items()):
1252
+ _walk(v, depth - 1, parent=obj, key=k, path=f"{path}[{k!r}]")
1253
+ return
1254
+ if isinstance(obj, list):
1255
+ for i, v in enumerate(list(obj)):
1256
+ _walk(v, depth - 1, parent=obj, key=i, path=f"{path}[{i}]")
1257
+ return
1258
+ if isinstance(obj, tuple):
1259
+ # Can't scrub inside tuples safely; but still inspect for diagnostics.
1260
+ for i, v in enumerate(obj):
1261
+ _walk(v, depth - 1, parent=None, key=None, path=f"{path}[{i}]")
1262
+ return
1263
+ if isinstance(obj, set):
1264
+ for v in list(obj):
1265
+ _walk(v, depth - 1, parent=None, key=None, path=f"{path}{{...}}")
1266
+ return
1267
+
1268
+ d = getattr(obj, "__dict__", None)
1269
+ if isinstance(d, dict):
1270
+ for attr, v in list(d.items()):
1271
+ _walk(v, depth - 1, parent=obj, key=attr, path=f"{path}.{attr}")
1272
+
1273
+ _walk(root_obj, depth=6)
1274
+ return restore
1275
+
1276
+ # Temporarily remove non-picklable pieces (e.g., SparkContext-backed objects)
1277
+ # and externalize spark models.
1119
1278
  estimator_to_training_function = {}
1120
- for estimator in self.estimator_list:
1121
- search_state = self._search_states[estimator]
1122
- if hasattr(search_state, "training_function"):
1123
- estimator_to_training_function[estimator] = search_state.training_function
1124
- del search_state.training_function
1279
+ spark_restore = []
1280
+ artifact_dir = None
1281
+ state_restore = {}
1282
+ automl_restore = {}
1283
+ scrub_restore = {}
1284
+
1285
+ try:
1286
+ # Signatures are only used for MLflow logging; they are not required
1287
+ # for inference and can capture SparkContext via pyspark objects.
1288
+ for attr in ("estimator_signature", "pipeline_signature"):
1289
+ if hasattr(self, attr):
1290
+ automl_restore[attr] = getattr(self, attr)
1291
+ setattr(self, attr, None)
1292
+
1293
+ for estimator in self.estimator_list:
1294
+ search_state = self._search_states[estimator]
1295
+ if hasattr(search_state, "training_function"):
1296
+ estimator_to_training_function[estimator] = search_state.training_function
1297
+ del search_state.training_function
1298
+
1299
+ # AutoMLState may keep Spark / pandas-on-Spark dataframes which are not picklable.
1300
+ # They are not required for inference, so strip them for serialization.
1301
+ state = getattr(self, "_state", None)
1302
+ if state is not None:
1303
+ for attr in (
1304
+ "X_train",
1305
+ "y_train",
1306
+ "X_train_all",
1307
+ "y_train_all",
1308
+ "X_val",
1309
+ "y_val",
1310
+ "weight_val",
1311
+ "groups_val",
1312
+ "sample_weight_all",
1313
+ "groups",
1314
+ "groups_all",
1315
+ "kf",
1316
+ ):
1317
+ if hasattr(state, attr):
1318
+ state_restore[attr] = getattr(state, attr)
1319
+ setattr(state, attr, None)
1320
+
1321
+ for key, est in _iter_trained_estimators():
1322
+ if getattr(est, "estimator_baseclass", None) != "spark":
1323
+ continue
1324
+
1325
+ # Drop training data reference (Spark DataFrame / pandas-on-Spark).
1326
+ old_df_train = getattr(est, "df_train", None)
1327
+ old_model = getattr(est, "_model", None)
1328
+
1329
+ model_meta = None
1330
+ if old_model is not None:
1331
+ if artifact_dir is None:
1332
+ artifact_dir = output_file_name + ".flaml_artifacts"
1333
+ os.makedirs(artifact_dir, exist_ok=True)
1334
+ # store relative dirname so the pickle+folder can be moved together
1335
+ self._flaml_pickle_artifacts_dirname = os.path.basename(artifact_dir)
1336
+
1337
+ model_dir = os.path.join(artifact_dir, _safe_name(key))
1338
+ # Spark ML models are saved as directories.
1339
+ try:
1340
+ writer = old_model.write()
1341
+ writer.overwrite().save(model_dir)
1342
+ except Exception as e:
1343
+ raise RuntimeError(
1344
+ "Failed to externalize Spark model for pickling. "
1345
+ "Please ensure the Spark ML model supports write().overwrite().save(path)."
1346
+ ) from e
1347
+
1348
+ model_meta = {
1349
+ "path": os.path.relpath(model_dir, os.path.dirname(output_file_name) or "."),
1350
+ "class": old_model.__class__.__module__ + "." + old_model.__class__.__name__,
1351
+ }
1352
+ # Replace in-memory Spark model with metadata only.
1353
+ est._model = None
1354
+ est._flaml_spark_model_meta = model_meta
1355
+
1356
+ est.df_train = None
1357
+ spark_restore.append((est, old_model, old_df_train, model_meta))
1358
+
1359
+ with open(output_file_name, "wb") as f:
1360
+ try:
1361
+ pickle.dump(self, f, pickle.HIGHEST_PROTOCOL)
1362
+ except Exception:
1363
+ # Some pyspark objects can still be captured indirectly.
1364
+ scrub_restore = _scrub_pyspark_refs(self)
1365
+ if scrub_restore:
1366
+ f.seek(0)
1367
+ f.truncate()
1368
+ pickle.dump(self, f, pickle.HIGHEST_PROTOCOL)
1369
+ else:
1370
+ raise
1371
+ finally:
1372
+ # Restore training_function and Spark models so current object remains usable.
1373
+ for estimator, tf in estimator_to_training_function.items():
1374
+ self._search_states[estimator].training_function = tf
1375
+
1376
+ for attr, val in automl_restore.items():
1377
+ setattr(self, attr, val)
1378
+
1379
+ state = getattr(self, "_state", None)
1380
+ if state is not None and state_restore:
1381
+ for attr, val in state_restore.items():
1382
+ setattr(state, attr, val)
1383
+
1384
+ for est, old_model, old_df_train, model_meta in spark_restore:
1385
+ est._model = old_model
1386
+ est.df_train = old_df_train
1387
+ if model_meta is not None and hasattr(est, "_flaml_spark_model_meta"):
1388
+ delattr(est, "_flaml_spark_model_meta")
1389
+
1390
+ if scrub_restore:
1391
+ for _, (parent, key, value) in scrub_restore.items():
1392
+ try:
1393
+ if isinstance(parent, dict):
1394
+ parent[key] = value
1395
+ elif isinstance(parent, list):
1396
+ parent[key] = value
1397
+ else:
1398
+ setattr(parent, key, value)
1399
+ except Exception:
1400
+ pass
1401
+
1402
+ @classmethod
1403
+ def load_pickle(cls, input_file_name: str, load_spark_models: bool = True):
1404
+ """Load an AutoML instance saved by :meth:`pickle`.
1405
+
1406
+ Args:
1407
+ input_file_name: Path to the pickle file created by :meth:`pickle`.
1408
+ load_spark_models: Whether to load externalized Spark ML models back
1409
+ into the estimator objects. If False, Spark estimators will remain
1410
+ without their underlying Spark model and cannot be used for predict.
1411
+
1412
+ Returns:
1413
+ The deserialized AutoML instance.
1414
+ """
1415
+ import importlib
1416
+ import os
1417
+ import pickle
1418
+
1419
+ with open(input_file_name, "rb") as f:
1420
+ automl = pickle.load(f)
1421
+
1422
+ # Recreate per-estimator training_function if it was removed for pickling.
1423
+ try:
1424
+ for est_name, ss in getattr(automl, "_search_states", {}).items():
1425
+ if not hasattr(ss, "training_function"):
1426
+ ss.training_function = partial(
1427
+ AutoMLState._compute_with_config_base,
1428
+ state=automl._state,
1429
+ estimator=est_name,
1430
+ )
1431
+ except Exception:
1432
+ # Best-effort; training_function is only needed for re-searching.
1433
+ pass
1434
+
1435
+ if not load_spark_models:
1436
+ return automl
1437
+
1438
+ base_dir = os.path.dirname(input_file_name) or "."
1439
+
1440
+ def _iter_trained_estimators_loaded():
1441
+ trained = getattr(automl, "_trained_estimator", None)
1442
+ if trained is not None:
1443
+ yield trained
1444
+ for ss in getattr(automl, "_search_states", {}).values():
1445
+ te = ss and getattr(ss, "trained_estimator", None)
1446
+ if te is not None:
1447
+ yield te
1448
+
1449
+ for est in _iter_trained_estimators_loaded():
1450
+ meta = getattr(est, "_flaml_spark_model_meta", None)
1451
+ if not meta:
1452
+ continue
1453
+ model_path = meta.get("path")
1454
+ model_class = meta.get("class")
1455
+ if not model_path or not model_class:
1456
+ continue
1457
+
1458
+ abs_model_path = os.path.join(base_dir, model_path)
1459
+
1460
+ module_name, _, class_name = model_class.rpartition(".")
1461
+ try:
1462
+ module = importlib.import_module(module_name)
1463
+ model_cls = getattr(module, class_name)
1464
+ except Exception as e:
1465
+ raise RuntimeError(f"Failed to import Spark model class '{model_class}'") from e
1466
+
1467
+ # Most Spark ML models support either Class.load(path) or Class.read().load(path).
1468
+ if hasattr(model_cls, "load"):
1469
+ est._model = model_cls.load(abs_model_path)
1470
+ elif hasattr(model_cls, "read"):
1471
+ est._model = model_cls.read().load(abs_model_path)
1472
+ else:
1473
+ try:
1474
+ from pyspark.ml.pipeline import PipelineModel
1475
+
1476
+ loaded_model = PipelineModel.load(abs_model_path)
1477
+ if not isinstance(loaded_model, model_cls):
1478
+ raise RuntimeError(
1479
+ f"Loaded model type '{type(loaded_model).__name__}' does not match expected type '{model_class}'."
1480
+ )
1481
+ est._model = loaded_model
1482
+ except Exception as e:
1483
+ raise RuntimeError(
1484
+ f"Spark model class '{model_class}' does not support load/read(). "
1485
+ "Unable to restore Spark model from artifacts."
1486
+ ) from e
1125
1487
 
1126
- with open(output_file_name, "wb") as f:
1127
- pickle.dump(self, f, pickle.HIGHEST_PROTOCOL)
1488
+ return automl
1128
1489
 
1129
1490
  @property
1130
1491
  def trainable(self) -> Callable[[dict], float | None]:
@@ -135,6 +135,7 @@ class BaseEstimator(sklearn.base.ClassifierMixin, sklearn.base.BaseEstimator):
135
135
  self._task = task if isinstance(task, Task) else task_factory(task, None, None)
136
136
  self.params = self.config2params(config)
137
137
  self.estimator_class = self._model = None
138
+ self.estimator_baseclass = "sklearn"
138
139
  if "_estimator_type" in self.params:
139
140
  self._estimator_type = self.params.pop("_estimator_type")
140
141
  else:
@@ -439,6 +440,7 @@ class SparkEstimator(BaseEstimator):
439
440
  raise SPARK_ERROR
440
441
  super().__init__(task, **config)
441
442
  self.df_train = None
443
+ self.estimator_baseclass = "spark"
442
444
 
443
445
  def _preprocess(
444
446
  self,
@@ -974,7 +976,7 @@ class TransformersEstimator(BaseEstimator):
974
976
  from .nlp.huggingface.utils import tokenize_text
975
977
  from .nlp.utils import is_a_list_of_str
976
978
 
977
- is_str = str(X.dtypes[0]) in ("string", "str")
979
+ is_str = str(X.dtypes.iloc[0]) in ("string", "str")
978
980
  is_list_of_str = is_a_list_of_str(X[list(X.keys())[0]].to_list()[0])
979
981
 
980
982
  if is_str or is_list_of_str:
@@ -5,7 +5,7 @@ from typing import List, Optional
5
5
  from flaml.automl.task.task import NLG_TASKS
6
6
 
7
7
  try:
8
- from transformers import TrainingArguments
8
+ from transformers import Seq2SeqTrainingArguments as TrainingArguments
9
9
  except ImportError:
10
10
  TrainingArguments = object
11
11
 
@@ -396,7 +396,7 @@ def load_model(checkpoint_path, task, num_labels=None):
396
396
 
397
397
  if task in (SEQCLASSIFICATION, SEQREGRESSION):
398
398
  return AutoModelForSequenceClassification.from_pretrained(
399
- checkpoint_path, config=model_config, ignore_mismatched_sizes=True
399
+ checkpoint_path, config=model_config, ignore_mismatched_sizes=True, trust_remote_code=True
400
400
  )
401
401
  elif task == TOKENCLASSIFICATION:
402
402
  return AutoModelForTokenClassification.from_pretrained(checkpoint_path, config=model_config)
@@ -151,7 +151,7 @@ class TimeSeriesTask(Task):
151
151
  raise ValueError("Must supply either X_train_all and y_train_all, or dataframe and label")
152
152
 
153
153
  try:
154
- dataframe[self.time_col] = pd.to_datetime(dataframe[self.time_col])
154
+ dataframe.loc[:, self.time_col] = pd.to_datetime(dataframe[self.time_col])
155
155
  except Exception:
156
156
  raise ValueError(
157
157
  f"For '{TS_FORECAST}' task, time column {self.time_col} must contain timestamp values."
@@ -76,6 +76,8 @@ class SklearnWrapper:
76
76
  self.pca = None
77
77
 
78
78
  def fit(self, X: pd.DataFrame, y: pd.Series, **kwargs):
79
+ if "is_retrain" in kwargs:
80
+ kwargs.pop("is_retrain")
79
81
  self._X = X
80
82
  self._y = y
81
83
 
@@ -92,7 +94,14 @@ class SklearnWrapper:
92
94
 
93
95
  for i, model in enumerate(self.models):
94
96
  offset = i + self.lags
95
- model.fit(X_trans[: len(X) - offset], y[offset:], **fit_params)
97
+ if len(X) - offset > 2:
98
+ # series with length 2 will meet All features are either constant or ignored.
99
+ # TODO: see why the non-constant features are ignored. Selector?
100
+ model.fit(X_trans[: len(X) - offset], y[offset:], **fit_params)
101
+ elif len(X) > offset and "catboost" not in str(model).lower():
102
+ model.fit(X_trans[: len(X) - offset], y[offset:], **fit_params)
103
+ else:
104
+ print("[INFO]: Length of data should longer than period + lags.")
96
105
  return self
97
106
 
98
107
  def predict(self, X, X_train=None, y_train=None):
@@ -121,7 +121,12 @@ class TimeSeriesDataset:
121
121
 
122
122
  @property
123
123
  def X_all(self) -> pd.DataFrame:
124
- return pd.concat([self.X_train, self.X_val], axis=0)
124
+ # Remove empty or all-NA columns before concatenation
125
+ X_train_filtered = self.X_train.dropna(axis=1, how="all")
126
+ X_val_filtered = self.X_val.dropna(axis=1, how="all")
127
+
128
+ # Concatenate the filtered DataFrames
129
+ return pd.concat([X_train_filtered, X_val_filtered], axis=0)
125
130
 
126
131
  @property
127
132
  def y_train(self) -> pd.DataFrame:
@@ -472,7 +477,7 @@ class DataTransformerTS:
472
477
  if "__NAN__" not in X[col].cat.categories:
473
478
  X[col] = X[col].cat.add_categories("__NAN__").fillna("__NAN__")
474
479
  else:
475
- X[col] = X[col].fillna("__NAN__")
480
+ X[col] = X[col].fillna("__NAN__").infer_objects(copy=False)
476
481
  X[col] = X[col].astype("category")
477
482
 
478
483
  for column in self.num_columns:
@@ -0,0 +1 @@
1
+ __version__ = "2.4.1"
@@ -116,14 +116,14 @@ setuptools.setup(
116
116
  "scikit-learn",
117
117
  ],
118
118
  "hf": [
119
- "transformers[torch]==4.26",
119
+ "transformers[torch]>=4.26",
120
120
  "datasets",
121
121
  "nltk<=3.8.1",
122
122
  "rouge_score",
123
123
  "seqeval",
124
124
  ],
125
125
  "nlp": [ # for backward compatibility; hf is the new option name
126
- "transformers[torch]==4.26",
126
+ "transformers[torch]>=4.26",
127
127
  "datasets",
128
128
  "nltk<=3.8.1",
129
129
  "rouge_score",
@@ -1 +0,0 @@
1
- __version__ = "2.4.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes