Skip to content

[Feat] Better traditional pipeline cutoff time #141

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
253 changes: 175 additions & 78 deletions autoPyTorch/api/base_task.py

Large diffs are not rendered by default.

24 changes: 16 additions & 8 deletions autoPyTorch/api/tabular_classification.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ def search(
budget_type: Optional[str] = None,
budget: Optional[float] = None,
total_walltime_limit: int = 100,
func_eval_time_limit: int = 60,
traditional_per_total_budget: float = 0.1,
func_eval_time_limit_secs: Optional[int] = None,
enable_traditional_pipeline: bool = True,
memory_limit: Optional[int] = 4096,
smac_scenario_args: Optional[Dict[str, Any]] = None,
get_smac_object_callback: Optional[Callable] = None,
Expand Down Expand Up @@ -156,16 +156,24 @@ def search(
in seconds for the search of appropriate models.
By increasing this value, autopytorch has a higher
chance of finding better models.
func_eval_time_limit (int), (default=60): Time limit
func_eval_time_limit_secs (int), (default=None): Time limit
for a single call to the machine learning model.
Model fitting will be terminated if the machine
learning algorithm runs over the time limit. Set
this value high enough so that typical machine
learning algorithms can be fit on the training
data.
traditional_per_total_budget (float), (default=0.1):
Percent of total walltime to be allocated for
running traditional classifiers.
When set to None, this time will automatically be set to
total_walltime_limit // 2 to allow enough time to fit
at least 2 individual machine learning algorithms.
Set to np.inf in case no time limit is desired.
enable_traditional_pipeline (bool), (default=True):
We fit traditional machine learning algorithms
(LightGBM, CatBoost, RandomForest, ExtraTrees, KNN, SVM)
before building PyTorch Neural Networks. You can disable this
feature by turning this flag to False. All machine learning
algorithms that are fitted during search() are considered for
ensemble building.
memory_limit (Optional[int]), (default=4096): Memory
limit in MB for the machine learning algorithm. autopytorch
will stop fitting the machine learning algorithm if it tries
Expand Down Expand Up @@ -228,8 +236,8 @@ def search(
budget_type=budget_type,
budget=budget,
total_walltime_limit=total_walltime_limit,
func_eval_time_limit=func_eval_time_limit,
traditional_per_total_budget=traditional_per_total_budget,
func_eval_time_limit_secs=func_eval_time_limit_secs,
enable_traditional_pipeline=enable_traditional_pipeline,
memory_limit=memory_limit,
smac_scenario_args=smac_scenario_args,
get_smac_object_callback=get_smac_object_callback,
Expand Down
57 changes: 31 additions & 26 deletions autoPyTorch/api/tabular_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,26 +103,27 @@ def _get_required_dataset_properties(self, dataset: BaseDataset) -> Dict[str, An
def build_pipeline(self, dataset_properties: Dict[str, Any]) -> TabularRegressionPipeline:
return TabularRegressionPipeline(dataset_properties=dataset_properties)

def search(self,
optimize_metric: str,
X_train: Optional[Union[List, pd.DataFrame, np.ndarray]] = None,
y_train: Optional[Union[List, pd.DataFrame, np.ndarray]] = None,
X_test: Optional[Union[List, pd.DataFrame, np.ndarray]] = None,
y_test: Optional[Union[List, pd.DataFrame, np.ndarray]] = None,
dataset_name: Optional[str] = None,
budget_type: Optional[str] = None,
budget: Optional[float] = None,
total_walltime_limit: int = 100,
func_eval_time_limit: int = 60,
traditional_per_total_budget: float = 0.1,
memory_limit: Optional[int] = 4096,
smac_scenario_args: Optional[Dict[str, Any]] = None,
get_smac_object_callback: Optional[Callable] = None,
all_supported_metrics: bool = True,
precision: int = 32,
disable_file_output: List = [],
load_models: bool = True,
) -> 'BaseTask':
def search(
self,
optimize_metric: str,
X_train: Optional[Union[List, pd.DataFrame, np.ndarray]] = None,
y_train: Optional[Union[List, pd.DataFrame, np.ndarray]] = None,
X_test: Optional[Union[List, pd.DataFrame, np.ndarray]] = None,
y_test: Optional[Union[List, pd.DataFrame, np.ndarray]] = None,
dataset_name: Optional[str] = None,
budget_type: Optional[str] = None,
budget: Optional[float] = None,
total_walltime_limit: int = 100,
func_eval_time_limit_secs: Optional[int] = None,
enable_traditional_pipeline: bool = False,
memory_limit: Optional[int] = 4096,
smac_scenario_args: Optional[Dict[str, Any]] = None,
get_smac_object_callback: Optional[Callable] = None,
all_supported_metrics: bool = True,
precision: int = 32,
disable_file_output: List = [],
load_models: bool = True,
) -> 'BaseTask':
"""
Search for the best pipeline configuration for the given dataset.

Expand All @@ -147,16 +148,20 @@ def search(self,
in seconds for the search of appropriate models.
By increasing this value, autopytorch has a higher
chance of finding better models.
func_eval_time_limit (int), (default=60): Time limit
func_eval_time_limit_secs (int), (default=None): Time limit
for a single call to the machine learning model.
Model fitting will be terminated if the machine
learning algorithm runs over the time limit. Set
this value high enough so that typical machine
learning algorithms can be fit on the training
data.
traditional_per_total_budget (float), (default=0.1):
Percent of total walltime to be allocated for
running traditional classifiers.
When set to None, this time will automatically be set to
total_walltime_limit // 2 to allow enough time to fit
at least 2 individual machine learning algorithms.
Set to np.inf in case no time limit is desired.
enable_traditional_pipeline (bool), (default=False):
Not enabled for regression. This flag is here to comply
with the API.
memory_limit (Optional[int]), (default=4096): Memory
limit in MB for the machine learning algorithm. autopytorch
will stop fitting the machine learning algorithm if it tries
Expand Down Expand Up @@ -219,8 +224,8 @@ def search(self,
budget_type=budget_type,
budget=budget,
total_walltime_limit=total_walltime_limit,
func_eval_time_limit=func_eval_time_limit,
traditional_per_total_budget=traditional_per_total_budget,
func_eval_time_limit_secs=func_eval_time_limit_secs,
enable_traditional_pipeline=enable_traditional_pipeline,
memory_limit=memory_limit,
smac_scenario_args=smac_scenario_args,
get_smac_object_callback=get_smac_object_callback,
Expand Down
5 changes: 3 additions & 2 deletions autoPyTorch/evaluation/abstract_evaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,9 @@ def _loss(self, y_true: np.ndarray, y_hat: np.ndarray) -> Dict[str, float]:

"""

if not isinstance(self.configuration, Configuration):
return {self.metric.name: self.metric._worst_possible_result}
if isinstance(self.configuration, int):
# We do not calculate performance of the dummy configurations
return {self.metric.name: self.metric._optimum - self.metric._sign * self.metric._worst_possible_result}

if self.additional_metrics is not None:
metrics = self.additional_metrics
Expand Down
4 changes: 2 additions & 2 deletions autoPyTorch/evaluation/tae.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ def run(

empty_queue(queue)
self.logger.debug(
'Finished function evaluation. Status: %s, Cost: %f, Runtime: %f, Additional %s',
status, cost, runtime, additional_run_info,
'Finished function evaluation %s. Status: %s, Cost: %f, Runtime: %f, Additional %s',
str(num_run), status, cost, runtime, additional_run_info,
)
return status, cost, runtime, additional_run_info
9 changes: 8 additions & 1 deletion autoPyTorch/evaluation/train_evaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ def fit_predict_and_loss(self) -> None:

status = StatusType.SUCCESS

self.logger.debug("In train evaluator fit_predict_and_loss, num_run: {} loss:{}".format(
self.num_run,
loss
))
self.finish_up(
loss=loss,
train_loss=train_loss,
Expand Down Expand Up @@ -236,7 +240,10 @@ def fit_predict_and_loss(self) -> None:
self.pipeline = self._get_pipeline()

status = StatusType.SUCCESS
self.logger.debug("In train evaluator fit_predict_and_loss, loss:{}".format(opt_loss))
self.logger.debug("In train evaluator fit_predict_and_loss, num_run: {} loss:{}".format(
self.num_run,
opt_loss
))
self.finish_up(
loss=opt_loss,
train_loss=train_loss,
Expand Down
8 changes: 4 additions & 4 deletions autoPyTorch/optimizer/smbo.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def __init__(self,
dataset_name: str,
backend: Backend,
total_walltime_limit: float,
func_eval_time_limit: float,
func_eval_time_limit_secs: float,
memory_limit: typing.Optional[int],
metric: autoPyTorchMetric,
watcher: StopWatch,
Expand Down Expand Up @@ -120,7 +120,7 @@ def __init__(self,
An interface with disk
total_walltime_limit (float):
The maximum allowed time for this job
func_eval_time_limit (float):
func_eval_time_limit_secs (float):
How much each individual task is allowed to last
memory_limit (typing.Optional[int]):
Maximum allowed CPU memory this task can use
Expand Down Expand Up @@ -180,7 +180,7 @@ def __init__(self,
# and a bunch of useful limits
self.worst_possible_result = get_cost_of_crash(self.metric)
self.total_walltime_limit = int(total_walltime_limit)
self.func_eval_time_limit = int(func_eval_time_limit)
self.func_eval_time_limit_secs = int(func_eval_time_limit_secs)
self.memory_limit = memory_limit
self.watcher = watcher
self.seed = seed
Expand Down Expand Up @@ -265,7 +265,7 @@ def run_smbo(self, func: typing.Optional[typing.Callable] = None
scenario_dict = {
'abort_on_first_run_crash': False,
'cs': self.config_space,
'cutoff_time': self.func_eval_time_limit,
'cutoff_time': self.func_eval_time_limit_secs,
'deterministic': 'true',
'instances': instances,
'memory_limit': self.memory_limit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,22 @@
SVMModel)

_classifiers = {
# Sort by more robust models
# Depending on the allocated time budget, only the
# top models from this dict are two be fitted.
# LGBM is the more robust model, with
# internal measures to prevent crashes, overfit
# Additionally, it is one of the state of the art
# methods for tabular prediction.
# Then follow with catboost for categorical heavy
# datasets. The other models are complementary and
# their ordering is not critical
'lgb': LGBModel,
'catboost': CatboostModel,
'random_forest': RFModel,
'extra_trees': ExtraTreesModel,
'svm_classifier': SVMModel,
'knn_classifier': KNNModel,
'lgb': LGBModel,
'random_forest': RFModel,
'svm_classifier': SVMModel
}
_addons = ThirdPartyComponents(BaseClassifier)

Expand Down
9 changes: 7 additions & 2 deletions autoPyTorch/pipeline/traditional_tabular_classification.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,13 @@ def get_pipeline_representation(self) -> Dict[str, str]:
"""
estimator_name = 'TraditionalTabularClassification'
if self.steps[0][1].choice is not None:
estimator_name = cast(str,
self.steps[0][1].choice.model.get_properties()['shortname'])
if self.steps[0][1].choice.model is None:
estimator_name = self.steps[0][1].choice.__class__.__name__
else:
estimator_name = cast(
str,
self.steps[0][1].choice.model.get_properties()['shortname']
)
return {
'Preprocessing': 'None',
'Estimator': estimator_name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
y_test=y_test.copy(),
optimize_metric='accuracy',
total_walltime_limit=300,
func_eval_time_limit=50
func_eval_time_limit_secs=50
)

############################################################################
Expand Down
4 changes: 2 additions & 2 deletions examples/tabular/20_basics/example_tabular_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@
y_test=y_test_scaled.copy(),
optimize_metric='r2',
total_walltime_limit=300,
func_eval_time_limit=50,
traditional_per_total_budget=0
func_eval_time_limit_secs=50,
enable_traditional_pipeline=False,
)

############################################################################
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def get_search_space_updates():
y_test=y_test.copy(),
optimize_metric='accuracy',
total_walltime_limit=300,
func_eval_time_limit=50
func_eval_time_limit_secs=50
)

############################################################################
Expand Down Expand Up @@ -119,7 +119,7 @@ def get_search_space_updates():
y_test=y_test.copy(),
optimize_metric='accuracy',
total_walltime_limit=300,
func_eval_time_limit=50
func_eval_time_limit_secs=50
)

############################################################################
Expand Down
6 changes: 3 additions & 3 deletions examples/tabular/40_advanced/example_resampling_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
y_test=y_test.copy(),
optimize_metric='accuracy',
total_walltime_limit=150,
func_eval_time_limit=30
func_eval_time_limit_secs=30
)

############################################################################
Expand Down Expand Up @@ -104,7 +104,7 @@
y_test=y_test.copy(),
optimize_metric='accuracy',
total_walltime_limit=150,
func_eval_time_limit=30
func_eval_time_limit_secs=30
)

############################################################################
Expand Down Expand Up @@ -145,7 +145,7 @@
y_test=y_test.copy(),
optimize_metric='accuracy',
total_walltime_limit=150,
func_eval_time_limit=30
func_eval_time_limit_secs=30
)

############################################################################
Expand Down
12 changes: 6 additions & 6 deletions test/test_api/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ def test_tabular_classification(openml_id, resampling_strategy, backend):
X_test=X_test, y_test=y_test,
optimize_metric='accuracy',
total_walltime_limit=150,
func_eval_time_limit=50,
traditional_per_total_budget=0
func_eval_time_limit_secs=50,
enable_traditional_pipeline=False,
)

# Internal dataset has expected settings
Expand Down Expand Up @@ -230,8 +230,8 @@ def test_tabular_regression(openml_name, resampling_strategy, backend):
X_test=X_test, y_test=y_test,
optimize_metric='r2',
total_walltime_limit=50,
func_eval_time_limit=10,
traditional_per_total_budget=0
func_eval_time_limit_secs=10,
enable_traditional_pipeline=False,
)

# Internal dataset has expected settings
Expand Down Expand Up @@ -390,7 +390,7 @@ def test_tabular_input_support(openml_id, backend):
X_test=X_test, y_test=y_test,
optimize_metric='accuracy',
total_walltime_limit=150,
func_eval_time_limit=50,
traditional_per_total_budget=0,
func_eval_time_limit_secs=50,
enable_traditional_pipeline=False,
load_models=False,
)
5 changes: 4 additions & 1 deletion test/test_pipeline/test_tabular_classification.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,10 @@ def test_pipeline_predict_proba(self, fit_dictionary_tabular):
config = cs.sample_configuration()
pipeline.set_hyperparameters(config)

pipeline.fit(fit_dictionary_tabular)
try:
pipeline.fit(fit_dictionary_tabular)
except Exception as e:
pytest.fail(f"Failed on config={config} with {e}")

# we expect the output to have the same batch size as the test input,
# and number of outputs per batch sample equal to the number of classes ("num_classes" in dataset_properties)
Expand Down