autogluon.tabular 1.4.1b20251214__py3-none-any.whl → 1.5.0b20251222__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 (43) hide show
  1. autogluon/tabular/configs/hyperparameter_configs.py +4 -0
  2. autogluon/tabular/configs/presets_configs.py +39 -2
  3. autogluon/tabular/configs/zeroshot/zeroshot_portfolio_2025.py +2 -44
  4. autogluon/tabular/configs/zeroshot/zeroshot_portfolio_cpu_2025_12_18.py +2 -0
  5. autogluon/tabular/configs/zeroshot/zeroshot_portfolio_gpu_2025_12_18.py +2 -0
  6. autogluon/tabular/learner/default_learner.py +1 -0
  7. autogluon/tabular/models/__init__.py +3 -1
  8. autogluon/tabular/models/abstract/__init__.py +0 -0
  9. autogluon/tabular/models/abstract/abstract_torch_model.py +148 -0
  10. autogluon/tabular/models/catboost/catboost_model.py +1 -1
  11. autogluon/tabular/models/fastainn/tabular_nn_fastai.py +5 -1
  12. autogluon/tabular/models/lgb/lgb_model.py +58 -8
  13. autogluon/tabular/models/lgb/lgb_utils.py +2 -2
  14. autogluon/tabular/models/mitra/_internal/core/trainer_finetune.py +14 -1
  15. autogluon/tabular/models/mitra/mitra_model.py +53 -22
  16. autogluon/tabular/models/realmlp/realmlp_model.py +8 -2
  17. autogluon/tabular/models/tabdpt/__init__.py +0 -0
  18. autogluon/tabular/models/tabdpt/tabdpt_model.py +253 -0
  19. autogluon/tabular/models/tabicl/tabicl_model.py +15 -2
  20. autogluon/tabular/models/tabm/tabm_model.py +23 -79
  21. autogluon/tabular/models/tabpfnv2/tabpfnv2_5_model.py +451 -0
  22. autogluon/tabular/models/tabpfnv2/tabpfnv2_model.py +86 -8
  23. autogluon/tabular/models/tabprep/__init__.py +0 -0
  24. autogluon/tabular/models/tabprep/prep_lgb_model.py +21 -0
  25. autogluon/tabular/models/tabprep/prep_mixin.py +220 -0
  26. autogluon/tabular/models/tabular_nn/torch/tabular_nn_torch.py +1 -1
  27. autogluon/tabular/models/tabular_nn/utils/data_preprocessor.py +12 -4
  28. autogluon/tabular/models/xgboost/xgboost_model.py +2 -0
  29. autogluon/tabular/predictor/predictor.py +47 -18
  30. autogluon/tabular/registry/_ag_model_registry.py +8 -2
  31. autogluon/tabular/testing/fit_helper.py +33 -0
  32. autogluon/tabular/trainer/abstract_trainer.py +45 -9
  33. autogluon/tabular/trainer/auto_trainer.py +5 -0
  34. autogluon/tabular/version.py +1 -1
  35. {autogluon_tabular-1.4.1b20251214.dist-info → autogluon_tabular-1.5.0b20251222.dist-info}/METADATA +36 -35
  36. {autogluon_tabular-1.4.1b20251214.dist-info → autogluon_tabular-1.5.0b20251222.dist-info}/RECORD +43 -33
  37. /autogluon.tabular-1.4.1b20251214-py3.11-nspkg.pth → /autogluon.tabular-1.5.0b20251222-py3.11-nspkg.pth +0 -0
  38. {autogluon_tabular-1.4.1b20251214.dist-info → autogluon_tabular-1.5.0b20251222.dist-info}/WHEEL +0 -0
  39. {autogluon_tabular-1.4.1b20251214.dist-info → autogluon_tabular-1.5.0b20251222.dist-info}/licenses/LICENSE +0 -0
  40. {autogluon_tabular-1.4.1b20251214.dist-info → autogluon_tabular-1.5.0b20251222.dist-info}/licenses/NOTICE +0 -0
  41. {autogluon_tabular-1.4.1b20251214.dist-info → autogluon_tabular-1.5.0b20251222.dist-info}/namespace_packages.txt +0 -0
  42. {autogluon_tabular-1.4.1b20251214.dist-info → autogluon_tabular-1.5.0b20251222.dist-info}/top_level.txt +0 -0
  43. {autogluon_tabular-1.4.1b20251214.dist-info → autogluon_tabular-1.5.0b20251222.dist-info}/zip-safe +0 -0
@@ -0,0 +1,220 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from typing import Type
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+
9
+ from autogluon.features import ArithmeticFeatureGenerator
10
+ from autogluon.features import CategoricalInteractionFeatureGenerator
11
+ from autogluon.features import OOFTargetEncodingFeatureGenerator
12
+ from autogluon.features import BulkFeatureGenerator
13
+ from autogluon.features.generators.abstract import AbstractFeatureGenerator
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ # TODO: In future we can have a feature generator registry like what is done for models
18
+ _feature_generator_class_lst = [
19
+ ArithmeticFeatureGenerator,
20
+ CategoricalInteractionFeatureGenerator,
21
+ OOFTargetEncodingFeatureGenerator,
22
+ ]
23
+
24
+ _feature_generator_class_map = {
25
+ feature_generator_cls.__name__: feature_generator_cls for feature_generator_cls in _feature_generator_class_lst
26
+ }
27
+
28
+
29
+ def _recursive_expand_prep_param(prep_param: tuple | list[list | tuple]) -> list[tuple]:
30
+ if isinstance(prep_param, list):
31
+ if len(prep_param) == 0:
32
+ param_type = "list"
33
+ elif len(prep_param) == 2:
34
+ if isinstance(prep_param[0], (str, AbstractFeatureGenerator)):
35
+ param_type = "generator"
36
+ else:
37
+ param_type = "list"
38
+ else:
39
+ param_type = "list"
40
+ elif isinstance(prep_param, tuple):
41
+ param_type = "generator"
42
+ else:
43
+ raise ValueError(f"Invalid value for prep_param: {prep_param}")
44
+ if param_type == "list":
45
+ out = []
46
+ for p in prep_param:
47
+ out += _recursive_expand_prep_param(p)
48
+ return out
49
+ elif param_type == "generator":
50
+ return [prep_param]
51
+ else:
52
+ raise ValueError(f"Invalid value for prep_param: {prep_param}")
53
+
54
+
55
+ # FIXME: Why is preprocessing twice as slow per fold when bagging LightGBM??? Need to investigate. Try sequential fold fit
56
+ # TODO: Why is `prep_params` a dict instead of a list?
57
+ class ModelAgnosticPrepMixin:
58
+ def _estimate_dtypes_after_preprocessing(self, X: pd.DataFrame, **kwargs) -> int:
59
+ prep_params = self._get_ag_params().get("prep_params", None)
60
+ if prep_params is None:
61
+ prep_params = []
62
+
63
+ # FIXME: Temporarily simplify for memory calculation
64
+ prep_params = _recursive_expand_prep_param(prep_params)
65
+
66
+ X_nunique = X.nunique().values
67
+ n_categorical = X.select_dtypes(exclude=[np.number]).shape[1]
68
+ n_numeric = X.loc[:, X_nunique > 2].select_dtypes(include=[np.number]).shape[1]
69
+ n_binary = X.loc[:, X_nunique <= 2].select_dtypes(include=[np.number]).shape[
70
+ 1] # NOTE: It can happen that features have less than two unique values if cleaning is applied before the bagging, i.e. Bioresponse
71
+
72
+ assert n_numeric + n_categorical + n_binary == X.shape[1] # NOTE: FOr debugging, to be removed later
73
+ for preprocessor_cls_name, init_params in prep_params:
74
+ if preprocessor_cls_name == 'ArithmeticFeatureGenerator':
75
+ prep_cls = ArithmeticFeatureGenerator(target_type=self.problem_type, **init_params)
76
+ elif preprocessor_cls_name == 'CategoricalInteractionFeatureGenerator':
77
+ prep_cls = CategoricalInteractionFeatureGenerator(target_type=self.problem_type, **init_params)
78
+ elif preprocessor_cls_name == 'OOFTargetEncodingFeatureGenerator':
79
+ prep_cls = OOFTargetEncodingFeatureGenerator(target_type=self.problem_type, **init_params)
80
+ else:
81
+ raise ValueError(f"Unknown preprocessor class name: {preprocessor_cls_name}")
82
+ n_numeric, n_categorical, n_binary = prep_cls.estimate_new_dtypes(n_numeric, n_categorical, n_binary,
83
+ num_classes=self.num_classes)
84
+
85
+ return n_numeric, n_categorical, n_binary
86
+
87
+ def _estimate_memory_usage(self, X: pd.DataFrame, **kwargs) -> int:
88
+ hyperparameters = self._get_model_params()
89
+ n_numeric, n_categorical, n_binary = self._estimate_dtypes_after_preprocessing(X=X, **kwargs)
90
+
91
+ if hasattr(self, "_estimate_memory_usage_static_lite"):
92
+ return self._estimate_memory_usage_static_lite(
93
+ num_samples=X.shape[0],
94
+ num_features=n_numeric + n_categorical + n_binary,
95
+ num_bytes_per_cell=4,
96
+ hyperparameters=hyperparameters,
97
+ problem_type=self.problem_type,
98
+ num_classes=self.num_classes,
99
+ **kwargs,
100
+ )
101
+
102
+ # TODO: Replace with memory estimation logic based on no. of features instead of dataframe generation
103
+ shape = X.shape[0]
104
+ df_lst = []
105
+ if n_numeric > 0:
106
+ X_estimate = np.random.random(size=[shape, n_numeric]).astype(np.float32)
107
+ X_estimate_numeric = pd.DataFrame(X_estimate)
108
+ df_lst.append(X_estimate_numeric)
109
+ if n_categorical > 0:
110
+ cardinality = int(X.select_dtypes(exclude=[np.number]).nunique().mean())
111
+ X_estimate = np.random.randint(0, cardinality, [shape, n_categorical]).astype('str')
112
+ X_estimate_cat = pd.DataFrame(X_estimate)
113
+ df_lst.append(X_estimate_cat)
114
+ if n_binary > 0:
115
+ X_estimate = np.random.randint(0, 2, [shape, n_binary]).astype(np.int8)
116
+ X_estimate_binary = pd.DataFrame(X_estimate)
117
+ df_lst.append(X_estimate_binary)
118
+ X = pd.concat(df_lst, ignore_index=True, axis=1)
119
+
120
+ return self.estimate_memory_usage_static(
121
+ X=X,
122
+ problem_type=self.problem_type,
123
+ num_classes=self.num_classes,
124
+ hyperparameters=hyperparameters,
125
+ **kwargs,
126
+ )
127
+
128
+ def _init_preprocessor(
129
+ self,
130
+ preprocessor_cls: Type[AbstractFeatureGenerator] | str,
131
+ init_params: dict | None,
132
+ ) -> AbstractFeatureGenerator:
133
+ if isinstance(preprocessor_cls, str):
134
+ preprocessor_cls = _feature_generator_class_map[preprocessor_cls]
135
+ if init_params is None:
136
+ init_params = {}
137
+ _init_params = dict(
138
+ verbosity=0,
139
+ random_state=self.random_seed, # FIXME: Not a generic param
140
+ target_type=self.problem_type, # FIXME: Not a generic param
141
+ )
142
+ _init_params.update(**init_params)
143
+ return preprocessor_cls(
144
+ **_init_params,
145
+ )
146
+
147
+ def _recursive_init_preprocessors(self, prep_param: tuple | list[list | tuple]):
148
+ if isinstance(prep_param, list):
149
+ if len(prep_param) == 0:
150
+ param_type = "list"
151
+ elif len(prep_param) == 2:
152
+ if isinstance(prep_param[0], (str, AbstractFeatureGenerator)):
153
+ param_type = "generator"
154
+ else:
155
+ param_type = "list"
156
+ else:
157
+ param_type = "list"
158
+ elif isinstance(prep_param, tuple):
159
+ param_type = "generator"
160
+ else:
161
+ raise ValueError(f"Invalid value for prep_param: {prep_param}")
162
+
163
+ if param_type == "list":
164
+ out = []
165
+ for i, p in enumerate(prep_param):
166
+ out.append(self._recursive_init_preprocessors(p))
167
+ return out
168
+ elif param_type == "generator":
169
+ assert len(prep_param) == 2
170
+ preprocessor_cls = prep_param[0]
171
+ init_params = prep_param[1]
172
+ return self._init_preprocessor(preprocessor_cls=preprocessor_cls, init_params=init_params)
173
+ else:
174
+ raise ValueError(f"Invalid value for prep_param: {prep_param}")
175
+
176
+ def get_preprocessors(self) -> list[AbstractFeatureGenerator]:
177
+ ag_params = self._get_ag_params()
178
+ prep_params = ag_params.get("prep_params", None)
179
+ passthrough_types = ag_params.get("prep_params.passthrough_types", None)
180
+ if prep_params is None:
181
+ return []
182
+ if not prep_params:
183
+ return []
184
+
185
+ preprocessors = self._recursive_init_preprocessors(prep_param=prep_params)
186
+ if len(preprocessors) == 0:
187
+ return []
188
+ if len(preprocessors) == 1 and isinstance(preprocessors[0], AbstractFeatureGenerator):
189
+ return preprocessors
190
+ else:
191
+ preprocessors = [BulkFeatureGenerator(
192
+ generators=preprocessors,
193
+ # TODO: "false_recursive" technically can slow down inference, but need to optimize `True` first
194
+ # Refer to `Bioresponse` dataset where setting to `True` -> 200s fit time vs `false_recursive` -> 1s fit time
195
+ remove_unused_features="false_recursive",
196
+ post_drop_duplicates=True,
197
+ passthrough=True,
198
+ passthrough_types=passthrough_types,
199
+ verbosity=0,
200
+ )]
201
+ return preprocessors
202
+
203
+ def _preprocess(self, X: pd.DataFrame, y=None, is_train: bool = False, **kwargs):
204
+ if is_train:
205
+ self.preprocessors = self.get_preprocessors()
206
+ if self.preprocessors:
207
+ assert y is not None, f"y must be specified to fit preprocessors... Likely the inheriting class isn't passing `y` in its `preprocess` call."
208
+ # FIXME: add `post_drop_useless`, example: anneal has many useless features
209
+ feature_metadata_in = self._feature_metadata
210
+ for prep in self.preprocessors:
211
+ X = prep.fit_transform(X, y, feature_metadata_in=feature_metadata_in)
212
+ # FIXME: Nick: This is incorrect because it strips away special dtypes. Need to do this properly by fixing in the preprocessors
213
+ feature_metadata_in = prep.feature_metadata
214
+ self._feature_metadata = feature_metadata_in
215
+ self._features_internal = self._feature_metadata.get_features()
216
+ else:
217
+ for prep in self.preprocessors:
218
+ X = prep.transform(X)
219
+
220
+ return super()._preprocess(X, y=y, is_train=is_train, **kwargs)
@@ -495,7 +495,7 @@ class TabularNeuralNetTorchModel(AbstractNeuralNetworkModel):
495
495
 
496
496
  if time_limit is not None:
497
497
  time_elapsed = time.time() - start_fit_time
498
- time_epoch_average = time_elapsed / (epoch + 1)
498
+ time_epoch_average = time_elapsed / max(epoch, 1) # avoid divide by 0
499
499
  time_left = time_limit - time_elapsed
500
500
  if time_left < time_epoch_average:
501
501
  logger.log(20, f"\tRan out of time, stopping training early. (Stopping on epoch {epoch})")
@@ -37,10 +37,18 @@ def create_preprocessor(
37
37
  steps=[("ordinal", OrdinalMergeRaresHandleUnknownEncoder(max_levels=max_category_levels))]
38
38
  ) # returns 0-n when max_category_levels = n-1. category n is reserved for unknown test-time categories.
39
39
  transformers.append(("ordinal", ordinal_transformer, embed_features))
40
- return ColumnTransformer(
41
- transformers=transformers, remainder="passthrough", force_int_remainder_cols=False,
42
- ) # numeric features are processed in the same order as in numeric_features vector, so feature-names remain the same.
43
-
40
+ try:
41
+ out = ColumnTransformer(
42
+ transformers=transformers, remainder="passthrough", force_int_remainder_cols=False,
43
+ ) # numeric features are processed in the same order as in numeric_features vector, so feature-names remain the same.
44
+ except:
45
+ # TODO: Avoid try/except once scikit-learn 1.5 is minimum
46
+ # Needed for scikit-learn 1.4 and 1.9+, force_int_remainder_cols is deprecated in 1.7 and introduced in 1.5
47
+ # ref: https://github.com/autogluon/autogluon/issues/5289
48
+ out = ColumnTransformer(
49
+ transformers=transformers, remainder="passthrough",
50
+ ) # numeric features are processed in the same order as in numeric_features vector, so feature-names remain the same.
51
+ return out
44
52
 
45
53
  def convert_df_dtype_to_str(df):
46
54
  return df.astype(str)
@@ -122,6 +122,8 @@ class XGBoostModel(AbstractModel):
122
122
  if eval_metric is not None:
123
123
  params["eval_metric"] = eval_metric
124
124
  eval_metric_name = eval_metric.__name__ if not isinstance(eval_metric, str) else eval_metric
125
+ else:
126
+ eval_metric_name = params["eval_metric"].__name__ if not isinstance(params["eval_metric"], str) else params["eval_metric"]
125
127
 
126
128
  if X_val is None:
127
129
  early_stopping_rounds = None
@@ -19,6 +19,8 @@ from packaging import version
19
19
  from autogluon.common import FeatureMetadata, TabularDataset
20
20
  from autogluon.common.loaders import load_json
21
21
  from autogluon.common.savers import save_json
22
+ from autogluon.common.utils.cv_splitter import CVSplitter
23
+ from autogluon.common.utils.decorators import apply_presets
22
24
  from autogluon.common.utils.file_utils import get_directory_size, get_directory_size_per_file
23
25
  from autogluon.common.utils.resource_utils import ResourceManager, get_resource_manager
24
26
  from autogluon.common.utils.hyperparameter_utils import get_hyperparameter_str_deprecation_msg, is_advanced_hyperparameter_format
@@ -46,10 +48,9 @@ from autogluon.core.pseudolabeling.pseudolabeling import filter_ensemble_pseudo,
46
48
  from autogluon.core.scheduler.scheduler_factory import scheduler_factory
47
49
  from autogluon.core.stacked_overfitting.utils import check_stacked_overfitting_from_leaderboard
48
50
  from autogluon.core.utils import get_pred_from_proba_df, plot_performance_vs_trials, plot_summary_of_models, plot_tabular_models
49
- from autogluon.core.utils.decorators import apply_presets
50
51
  from autogluon.core.utils.loaders import load_pkl, load_str
51
52
  from autogluon.core.utils.savers import save_pkl, save_str
52
- from autogluon.core.utils.utils import CVSplitter, generate_train_test_split_combined
53
+ from autogluon.core.utils.utils import generate_train_test_split_combined
53
54
 
54
55
  from ..configs.feature_generator_presets import get_default_feature_generator
55
56
  from ..configs.hyperparameter_configs import get_hyperparameter_config
@@ -422,7 +423,7 @@ class TabularPredictor:
422
423
  num_gpus: int | str = "auto",
423
424
  fit_strategy: Literal["sequential", "parallel"] = "sequential",
424
425
  memory_limit: float | str = "auto",
425
- callbacks: list[AbstractCallback] = None,
426
+ callbacks: list[AbstractCallback | list | tuple] = None,
426
427
  **kwargs,
427
428
  ) -> "TabularPredictor":
428
429
  """
@@ -463,16 +464,23 @@ class TabularPredictor:
463
464
  It is recommended to only use one `quality` based preset in a given call to `fit()` as they alter many of the same arguments and are not compatible with each-other.
464
465
 
465
466
  In-depth Preset Info:
466
- extreme_quality={"auto_stack": True, "dynamic_stacking": "auto", "_experimental_dynamic_hyperparameters": True, "hyperparameters": None}
467
- Significantly more accurate than `best_quality` on datasets <= 30000 samples. Requires a GPU for best results.
468
- For datasets <= 30000 samples, will use recent tabular foundation models TabPFNv2, TabICL, and Mitra to maximize performance.
469
- For datasets > 30000 samples, will behave identically to `best_quality`.
467
+ extreme_quality={...}
468
+ New in v1.5: The state-of-the-art for tabular machine learning.
469
+ Requires `pip install autogluon.tabular[tabarena]` to install TabPFN, TabICL, and TabDPT.
470
+ Significantly more accurate than `best_quality` on datasets <= 100000 samples. Requires a GPU.
471
+ Will use recent tabular foundation models TabPFNv2, TabICL, TabDPT, and Mitra to maximize performance.
470
472
  Recommended for applications that benefit from the best possible model accuracy.
471
473
 
474
+ best_quality_v150={...}
475
+ New in v1.5: Better quality than 'best_quality' and 5x+ faster to train. Give it a try!
476
+
472
477
  best_quality={'auto_stack': True, 'dynamic_stacking': 'auto', 'hyperparameters': 'zeroshot'}
473
478
  Best predictive accuracy with little consideration to inference time or disk usage. Achieve even better results by specifying a large time_limit value.
474
479
  Recommended for applications that benefit from the best possible model accuracy.
475
480
 
481
+ high_quality_v150={...}
482
+ New in v1.5: Better quality than 'high_quality' and 5x+ faster to train. Give it a try!
483
+
476
484
  high_quality={'auto_stack': True, 'dynamic_stacking': 'auto', 'hyperparameters': 'zeroshot', 'refit_full': True, 'set_best_to_refit_full': True, 'save_bag_folds': False}
477
485
  High predictive accuracy with fast inference. ~8x faster inference and ~8x lower disk usage than `best_quality`.
478
486
  Recommended for applications that require reasonable inference speed and/or model size.
@@ -1106,11 +1114,13 @@ class TabularPredictor:
1106
1114
  20,
1107
1115
  "No presets specified! To achieve strong results with AutoGluon, it is recommended to use the available presets. Defaulting to `'medium'`...\n"
1108
1116
  "\tRecommended Presets (For more details refer to https://auto.gluon.ai/stable/tutorials/tabular/tabular-essentials.html#presets):\n"
1109
- "\tpresets='extreme' : New in v1.4: Massively better than 'best' on datasets <30000 samples by using new models meta-learned on https://tabarena.ai: TabPFNv2, TabICL, Mitra, and TabM. Absolute best accuracy. Requires a GPU. Recommended 64 GB CPU memory and 32+ GB GPU memory.\n"
1110
- "\tpresets='best' : Maximize accuracy. Recommended for most users. Use in competitions and benchmarks.\n"
1111
- "\tpresets='high' : Strong accuracy with fast inference speed.\n"
1112
- "\tpresets='good' : Good accuracy with very fast inference speed.\n"
1113
- "\tpresets='medium' : Fast training time, ideal for initial prototyping.",
1117
+ "\tpresets='extreme' : New in v1.5: The state-of-the-art for tabular data. Massively better than 'best' on datasets <100000 samples by using new Tabular Foundation Models (TFMs) meta-learned on https://tabarena.ai: TabPFNv2, TabICL, Mitra, TabDPT, and TabM. Requires a GPU and `pip install autogluon.tabular[tabarena]` to install TabPFN, TabICL, and TabDPT.\n"
1118
+ "\tpresets='best' : Maximize accuracy. Recommended for most users. Use in competitions and benchmarks.\n"
1119
+ "\tpresets='best_v150': New in v1.5: Better quality than 'best' and 5x+ faster to train. Give it a try!\n"
1120
+ "\tpresets='high' : Strong accuracy with fast inference speed.\n"
1121
+ "\tpresets='high_v150': New in v1.5: Better quality than 'high' and 5x+ faster to train. Give it a try!\n"
1122
+ "\tpresets='good' : Good accuracy with very fast inference speed.\n"
1123
+ "\tpresets='medium' : Fast training time, ideal for initial prototyping.",
1114
1124
  )
1115
1125
 
1116
1126
  kwargs_orig = kwargs.copy()
@@ -1164,7 +1174,7 @@ class TabularPredictor:
1164
1174
  # TODO: Temporary for v1.4. Make this more extensible for v1.5 by letting users make their own dynamic hyperparameters.
1165
1175
  dynamic_hyperparameters = kwargs["_experimental_dynamic_hyperparameters"]
1166
1176
  if dynamic_hyperparameters:
1167
- logger.log(20, f"`extreme` preset uses a dynamic portfolio based on dataset size...")
1177
+ logger.log(20, f"`extreme_v140` preset uses a dynamic portfolio based on dataset size...")
1168
1178
  assert hyperparameters is None, f"hyperparameters must be unspecified when `_experimental_dynamic_hyperparameters=True`."
1169
1179
  n_samples = len(train_data)
1170
1180
  if n_samples > 30000:
@@ -1593,6 +1603,25 @@ class TabularPredictor:
1593
1603
  memory_safe_fits = ds_fit_kwargs.get("memory_safe_fits", True)
1594
1604
  enable_ray_logging = ds_fit_kwargs.get("enable_ray_logging", True)
1595
1605
  normal_fit = False
1606
+ total_resources = ag_fit_kwargs["core_kwargs"]["total_resources"]
1607
+
1608
+ if memory_safe_fits == "auto":
1609
+ num_gpus = total_resources.get("num_gpus", "auto")
1610
+ if num_gpus == "auto":
1611
+ num_gpus = ResourceManager.get_gpu_count_torch()
1612
+ if num_gpus > 0:
1613
+ logger.log(
1614
+ 30,
1615
+ f"DyStack: Disabling memory safe fit mode in DyStack "
1616
+ f"because GPUs were detected and num_gpus='auto' (GPUs cannot be used in memory safe fit mode). "
1617
+ f"If you want to use memory safe fit mode, manually set `num_gpus=0`."
1618
+ )
1619
+ if num_gpus > 0:
1620
+ memory_safe_fits = False
1621
+ else:
1622
+ memory_safe_fits = True
1623
+
1624
+
1596
1625
  if memory_safe_fits:
1597
1626
  try:
1598
1627
  _ds_ray = try_import_ray()
@@ -1633,8 +1662,6 @@ class TabularPredictor:
1633
1662
  # Handle resources
1634
1663
  # FIXME: what about distributed?
1635
1664
 
1636
- total_resources = ag_fit_kwargs["core_kwargs"]["total_resources"]
1637
-
1638
1665
  num_cpus = total_resources.get("num_cpus", "auto")
1639
1666
 
1640
1667
  if num_cpus == "auto":
@@ -5244,11 +5271,11 @@ class TabularPredictor:
5244
5271
  holdout_frac=1 / 9,
5245
5272
  n_folds=2,
5246
5273
  n_repeats=1,
5247
- memory_safe_fits=True,
5274
+ memory_safe_fits="auto",
5248
5275
  clean_up_fits=True,
5249
5276
  holdout_data=None,
5250
5277
  enable_ray_logging=True,
5251
- enable_callbacks=False,
5278
+ enable_callbacks=True,
5252
5279
  )
5253
5280
  allowed_kes = set(ds_args.keys())
5254
5281
 
@@ -5263,9 +5290,11 @@ class TabularPredictor:
5263
5290
  (not isinstance(ds_args["validation_procedure"], str)) or (ds_args["validation_procedure"] not in ["holdout", "cv"])
5264
5291
  ):
5265
5292
  raise ValueError("`validation_procedure` in `ds_args` must be str in {'holdout','cv'}. " + f"Got: {ds_args['validation_procedure']}")
5266
- for arg_name in ["memory_safe_fits", "clean_up_fits", "enable_ray_logging"]:
5293
+ for arg_name in ["clean_up_fits", "enable_ray_logging"]:
5267
5294
  if (arg_name in ds_args) and (not isinstance(ds_args[arg_name], bool)):
5268
5295
  raise ValueError(f"`{arg_name}` in `ds_args` must be bool. Got: {type(ds_args[arg_name])}")
5296
+ if "memory_safe_fits" in ds_args and not isinstance(ds_args["memory_safe_fits"], (bool, str)):
5297
+ raise ValueError(f"`memory_safe_fits` in `ds_args` must be bool or 'auto'. Got: {type(ds_args['memory_safe_fits'])}")
5269
5298
  for arg_name in ["detection_time_frac", "holdout_frac"]:
5270
5299
  if (arg_name in ds_args) and ((not isinstance(ds_args[arg_name], float)) or (ds_args[arg_name] >= 1) or (ds_args[arg_name] <= 0)):
5271
5300
  raise ValueError(f"`{arg_name}` in `ds_args` must be float in (0,1). Got: {type(ds_args[arg_name])}, {ds_args[arg_name]}")
@@ -20,14 +20,17 @@ from ..models import (
20
20
  LinearModel,
21
21
  MultiModalPredictorModel,
22
22
  NNFastAiTabularModel,
23
+ PrepLGBModel,
23
24
  RealMLPModel,
24
25
  RFModel,
25
26
  RuleFitModel,
27
+ TabDPTModel,
26
28
  TabICLModel,
27
29
  TabMModel,
28
30
  TabPFNMixModel,
29
31
  MitraModel,
30
- TabPFNV2Model,
32
+ RealTabPFNv2Model,
33
+ RealTabPFNv25Model,
31
34
  TabularNeuralNetTorchModel,
32
35
  TextPredictorModel,
33
36
  XGBoostModel,
@@ -47,14 +50,17 @@ REGISTERED_MODEL_CLS_LST = [
47
50
  TabularNeuralNetTorchModel,
48
51
  LinearModel,
49
52
  NNFastAiTabularModel,
53
+ PrepLGBModel,
50
54
  TextPredictorModel,
51
55
  ImagePredictorModel,
52
56
  MultiModalPredictorModel,
53
57
  FTTransformerModel,
58
+ TabDPTModel,
54
59
  TabICLModel,
55
60
  TabMModel,
56
61
  TabPFNMixModel,
57
- TabPFNV2Model,
62
+ RealTabPFNv2Model,
63
+ RealTabPFNv25Model,
58
64
  MitraModel,
59
65
  FastTextModel,
60
66
  GreedyWeightedEnsembleModel,
@@ -4,6 +4,9 @@ import copy
4
4
  import os
5
5
  import pandas as pd
6
6
  import shutil
7
+ import sys
8
+ import subprocess
9
+ import textwrap
7
10
  import uuid
8
11
  from typing import Any, Type
9
12
 
@@ -12,6 +15,7 @@ from autogluon.core.constants import BINARY, MULTICLASS, REGRESSION
12
15
  from autogluon.core.metrics import METRICS
13
16
  from autogluon.core.models import AbstractModel, BaggedEnsembleModel
14
17
  from autogluon.core.stacked_overfitting.utils import check_stacked_overfitting_from_leaderboard
18
+ from autogluon.core.testing.global_context_snapshot import GlobalContextSnapshot
15
19
  from autogluon.core.utils import download, generate_train_test_split_combined, infer_problem_type, unzip
16
20
 
17
21
  from autogluon.tabular import TabularDataset, TabularPredictor
@@ -176,6 +180,7 @@ class FitHelper:
176
180
  raise_on_model_failure: bool | None = None,
177
181
  deepcopy_fit_args: bool = True,
178
182
  verify_model_seed: bool = False,
183
+ verify_load_wo_cuda: bool = False,
179
184
  ) -> TabularPredictor:
180
185
  if compiler_configs is None:
181
186
  compiler_configs = {}
@@ -219,6 +224,8 @@ class FitHelper:
219
224
  expected_model_count -= 1
220
225
  fit_args["fit_weighted_ensemble"] = fit_weighted_ensemble
221
226
 
227
+ ctx_before = GlobalContextSnapshot.capture()
228
+
222
229
  predictor: TabularPredictor = FitHelper.fit_dataset(
223
230
  train_data=train_data,
224
231
  init_args=init_args,
@@ -227,6 +234,10 @@ class FitHelper:
227
234
  scikit_api=scikit_api,
228
235
  min_cls_count_train=min_cls_count_train,
229
236
  )
237
+
238
+ ctx_after = GlobalContextSnapshot.capture()
239
+ ctx_before.assert_unchanged(ctx_after)
240
+
230
241
  if compile:
231
242
  predictor.compile(models="all", compiler_configs=compiler_configs)
232
243
  predictor.persist(models="all")
@@ -287,6 +298,28 @@ class FitHelper:
287
298
  predictor_load = predictor.load(path=predictor.path)
288
299
  predictor_load.predict(test_data)
289
300
 
301
+ # TODO: This is expensive, only do this sparingly.
302
+ if verify_load_wo_cuda:
303
+ import torch
304
+ if torch.cuda.is_available():
305
+ # Checks if the model is able to predict w/o CUDA.
306
+ # This verifies that a model artifact works on a CPU machine.
307
+ predictor_path = predictor.path
308
+
309
+ code = textwrap.dedent(f"""
310
+ import os
311
+ os.environ["CUDA_VISIBLE_DEVICES"] = ""
312
+ from autogluon.tabular import TabularPredictor
313
+
314
+ import torch
315
+ assert torch.cuda.is_available() is False
316
+ predictor = TabularPredictor.load(r"{predictor_path}")
317
+ X, y = predictor.load_data_internal()
318
+ predictor.persist("all")
319
+ predictor.predict_multi(X, transform_features=False)
320
+ """)
321
+ subprocess.run([sys.executable, "-c", code], check=True)
322
+
290
323
  assert os.path.realpath(save_path) == os.path.realpath(predictor.path)
291
324
  if delete_directory:
292
325
  shutil.rmtree(save_path, ignore_errors=True) # Delete AutoGluon output directory to ensure runs' information has been removed.
@@ -27,7 +27,7 @@ from autogluon.core.calibrate.conformity_score import compute_conformity_score
27
27
  from autogluon.core.calibrate.temperature_scaling import apply_temperature_scaling, tune_temperature_scaling
28
28
  from autogluon.core.callbacks import AbstractCallback
29
29
  from autogluon.core.constants import BINARY, MULTICLASS, QUANTILE, REFIT_FULL_NAME, REGRESSION, SOFTCLASS
30
- from autogluon.core.data.label_cleaner import LabelCleanerMulticlassToBinary
30
+ from autogluon.core.data.label_cleaner import LabelCleanerMulticlassToBinary, LabelCleaner
31
31
  from autogluon.core.metrics import Scorer, compute_metric, get_metric
32
32
  from autogluon.core.models import (
33
33
  AbstractModel,
@@ -530,7 +530,7 @@ class AbstractTabularTrainer(AbstractTrainer[AbstractModel]):
530
530
  self.save()
531
531
  return model_names_fit
532
532
 
533
- def _fit_setup(self, time_limit: float | None = None, callbacks: list[AbstractCallback] | None = None):
533
+ def _fit_setup(self, time_limit: float | None = None, callbacks: list[AbstractCallback | list | tuple] | None = None):
534
534
  """
535
535
  Prepare the trainer state at the start of / prior to a fit call.
536
536
  Should be paired with a `self._fit_cleanup()` at the conclusion of the fit call.
@@ -539,15 +539,45 @@ class AbstractTabularTrainer(AbstractTrainer[AbstractModel]):
539
539
  self._time_train_start_last = self._time_train_start
540
540
  self._time_limit = time_limit
541
541
  self.reset_callbacks()
542
+ callbacks_new = []
542
543
  if callbacks is not None:
543
544
  assert isinstance(callbacks, list), f"`callbacks` must be a list. Found invalid type: `{type(callbacks)}`."
544
545
  for callback in callbacks:
545
- assert isinstance(
546
- callback, AbstractCallback
547
- ), f"Elements in `callbacks` must be of type AbstractCallback. Found invalid type: `{type(callback)}`."
546
+ if isinstance(callback, (list, tuple)):
547
+ assert len(callback) == 2, f"Callback must either be an initialized object or a tuple/list of length 2, found: {callback}"
548
+ callback_cls = callback[0]
549
+ if isinstance(callback_cls, str):
550
+ from autogluon.core.callbacks._early_stopping_count_callback import EarlyStoppingCountCallback
551
+ from autogluon.core.callbacks._early_stopping_callback import EarlyStoppingCallback
552
+ from autogluon.core.callbacks._early_stopping_ensemble_callback import EarlyStoppingEnsembleCallback
553
+
554
+ _callback_cls_lst = [
555
+ EarlyStoppingCallback,
556
+ EarlyStoppingCountCallback,
557
+ EarlyStoppingEnsembleCallback,
558
+ ]
559
+
560
+ _callback_cls_name_map = {
561
+ c.__name__: c for c in _callback_cls_lst
562
+ }
563
+
564
+ assert callback_cls in _callback_cls_name_map.keys(), (
565
+ f"Unknown callback class: {callback_cls}. "
566
+ f"Valid classes: {list(_callback_cls_name_map.keys())}"
567
+ )
568
+ callback_cls = _callback_cls_name_map[callback_cls]
569
+
570
+ callback_kwargs = callback[1]
571
+ assert isinstance(callback_kwargs, dict), f"Callback kwargs must be a dictionary, found: {callback_kwargs}"
572
+ callback = callback_cls(**callback_kwargs)
573
+ else:
574
+ assert isinstance(
575
+ callback, AbstractCallback
576
+ ), f"Elements in `callbacks` must be of type AbstractCallback. Found invalid type: `{type(callback)}`."
577
+ callbacks_new.append(callback)
548
578
  else:
549
- callbacks = []
550
- self.callbacks = callbacks
579
+ callbacks_new = []
580
+ self.callbacks = callbacks_new
551
581
 
552
582
  def _fit_cleanup(self):
553
583
  """
@@ -2493,6 +2523,7 @@ class AbstractTabularTrainer(AbstractTrainer[AbstractModel]):
2493
2523
  errors_ignore: list | None = None,
2494
2524
  errors_raise: list | None = None,
2495
2525
  is_ray_worker: bool = False,
2526
+ label_cleaner: None | LabelCleaner = None,
2496
2527
  **kwargs,
2497
2528
  ) -> list[str]:
2498
2529
  """
@@ -2527,7 +2558,8 @@ class AbstractTabularTrainer(AbstractTrainer[AbstractModel]):
2527
2558
  return []
2528
2559
 
2529
2560
  model_fit_kwargs = self._get_model_fit_kwargs(
2530
- X=X, X_val=X_val, time_limit=time_limit, k_fold=k_fold, fit_kwargs=fit_kwargs, ens_sample_weight=kwargs.get("ens_sample_weight", None)
2561
+ X=X, X_val=X_val, time_limit=time_limit, k_fold=k_fold, fit_kwargs=fit_kwargs,
2562
+ ens_sample_weight=kwargs.get("ens_sample_weight", None), label_cleaner=label_cleaner,
2531
2563
  )
2532
2564
  exception = None
2533
2565
  if hyperparameter_tune_kwargs:
@@ -4294,7 +4326,8 @@ class AbstractTabularTrainer(AbstractTrainer[AbstractModel]):
4294
4326
  return distilled_model_names
4295
4327
 
4296
4328
  def _get_model_fit_kwargs(
4297
- self, X: pd.DataFrame, X_val: pd.DataFrame, time_limit: float, k_fold: int, fit_kwargs: dict, ens_sample_weight: list | None = None
4329
+ self, X: pd.DataFrame, X_val: pd.DataFrame, time_limit: float, k_fold: int,
4330
+ fit_kwargs: dict, ens_sample_weight: list | None = None, label_cleaner: None | LabelCleaner = None
4298
4331
  ) -> dict:
4299
4332
  # Returns kwargs to be passed to AbstractModel's fit function
4300
4333
  if fit_kwargs is None:
@@ -4316,6 +4349,9 @@ class AbstractTabularTrainer(AbstractTrainer[AbstractModel]):
4316
4349
  if k_fold == self.k_fold: # don't do this on refit full
4317
4350
  model_fit_kwargs["groups"] = self._groups
4318
4351
 
4352
+ if label_cleaner is not None:
4353
+ model_fit_kwargs["label_cleaner"] = label_cleaner
4354
+
4319
4355
  # FIXME: Sample weight `extract_column` is a hack, have to compute feature_metadata here because sample weight column could be in X upstream, extract sample weight column upstream instead.
4320
4356
  if "feature_metadata" not in model_fit_kwargs:
4321
4357
  raise AssertionError(f"Missing expected parameter 'feature_metadata'.")
@@ -59,6 +59,7 @@ class AutoTrainer(AbstractTabularTrainer):
59
59
  use_bag_holdout=False,
60
60
  groups=None,
61
61
  callbacks: list[callable] = None,
62
+ label_cleaner=None,
62
63
  **kwargs,
63
64
  ):
64
65
  for key in kwargs:
@@ -112,6 +113,7 @@ class AutoTrainer(AbstractTabularTrainer):
112
113
  extra_log_str = ""
113
114
  display_all = (n_configs < 20) or (self.verbosity >= 3)
114
115
  if not display_all:
116
+ # FIXME: This isn't correct
115
117
  extra_log_str = (
116
118
  f"Large model count detected ({n_configs} configs) ... " f"Only displaying the first 3 models of each family. To see all, set `verbosity=3`.\n"
117
119
  )
@@ -132,6 +134,9 @@ class AutoTrainer(AbstractTabularTrainer):
132
134
  log_str += "}"
133
135
  logger.log(20, log_str)
134
136
 
137
+ if label_cleaner is not None:
138
+ core_kwargs["label_cleaner"] = label_cleaner
139
+
135
140
  self._train_multi_and_ensemble(
136
141
  X=X,
137
142
  y=y,