From f48ca2618fed2193ffa929af7a44d3031dfb16dd Mon Sep 17 00:00:00 2001
From: Chi Wang
Date: Fri, 8 Oct 2021 16:09:43 -0700
Subject: [PATCH] warning -> info for low cost partial config (#231)
* warning -> info for low cost partial config
#195, #110
* when n_estimators < 0, use trained_estimator's
* log debug info
* test random seed
* remove "objective"; avoid ZeroDivisionError
* hp config to estimator params
* check type of searcher
* default n_jobs
* try import
* Update searchalgo_auto.py
* CLASSIFICATION
* auto_augment flag
* min_sample_size
* make catboost optional
---
README.md | 4 +-
flaml/automl.py | 66 ++-
flaml/data.py | 11 +-
flaml/ml.py | 4 +-
flaml/model.py | 349 +++++--------
flaml/nlp/hpo/searchalgo_auto.py | 128 +++--
flaml/onlineml/trial_runner.py | 325 +++++++-----
flaml/searcher/blendsearch.py | 38 +-
flaml/searcher/flow2.py | 256 +++++----
flaml/searcher/online_searcher.py | 264 ++++++----
flaml/searcher/search_thread.py | 2 +-
flaml/tune/README.md | 62 ++-
flaml/tune/trial_runner.py | 38 +-
flaml/tune/tune.py | 43 +-
notebook/flaml_automl.ipynb | 825 +++++++++++++++---------------
notebook/flaml_lightgbm.ipynb | 606 ++++++++++------------
notebook/flaml_xgboost.ipynb | 582 ++++++++++-----------
setup.py | 4 +-
test/test_automl.py | 105 ++--
test/test_python_log.py | 54 +-
test/test_training_log.py | 18 +-
test/tune/test_searcher.py | 13 +-
22 files changed, 1938 insertions(+), 1859 deletions(-)
diff --git a/README.md b/README.md
index 5937a76bb3..45e3a71457 100644
--- a/README.md
+++ b/README.md
@@ -103,7 +103,7 @@ print(automl.model)
```python
from flaml import AutoML
-from sklearn.datasets import load_boston
+from sklearn.datasets import fetch_california_housing
# Initialize an AutoML instance
automl = AutoML()
# Specify automl goal and constraint
@@ -113,7 +113,7 @@ automl_settings = {
"task": 'regression',
"log_file_name": "test/boston.log",
}
-X_train, y_train = load_boston(return_X_y=True)
+X_train, y_train = fetch_california_housing(return_X_y=True)
# Train with labeled input data
automl.fit(X_train=X_train, y_train=y_train,
**automl_settings)
diff --git a/flaml/automl.py b/flaml/automl.py
index d5441f23d9..1b117a8cae 100644
--- a/flaml/automl.py
+++ b/flaml/automl.py
@@ -36,7 +36,7 @@ from .config import (
N_SPLITS,
SAMPLE_MULTIPLY_FACTOR,
)
-from .data import concat
+from .data import concat, CLASSIFICATION
from . import tune
from .training_log import training_log_reader, training_log_writer
@@ -619,7 +619,8 @@ class AutoML:
if issparse(X_train_all):
X_train_all = X_train_all.tocsr()
if (
- self._state.task in ("binary", "multi")
+ self._state.task in CLASSIFICATION
+ and self._auto_augment
and self._state.fit_kwargs.get("sample_weight") is None
and self._split_type not in ["time", "group"]
):
@@ -725,7 +726,7 @@ class AutoML:
y_train, y_val = y_train_all[train_idx], y_train_all[val_idx]
self._state.groups = self._state.groups_all[train_idx]
self._state.groups_val = self._state.groups_all[val_idx]
- elif self._state.task in ("binary", "multi"):
+ elif self._state.task in CLASSIFICATION:
# for classification, make sure the labels are complete in both
# training and validation data
label_set, first = np.unique(y_train_all, return_index=True)
@@ -904,10 +905,11 @@ class AutoML:
n_splits=N_SPLITS,
split_type=None,
groups=None,
- n_jobs=1,
+ n_jobs=-1,
train_best=True,
train_full=False,
record_id=-1,
+ auto_augment=True,
**fit_kwargs,
):
"""Retrain from log file
@@ -943,7 +945,8 @@ class AutoML:
groups: None or array-like | Group labels (with matching length to
y_train) or groups counts (with sum equal to length of y_train)
for training data.
- n_jobs: An integer of the number of threads for training.
+ n_jobs: An integer of the number of threads for training. Use all
+ available resources when n_jobs == -1.
train_best: A boolean of whether to train the best config in the
time budget; if false, train the last config in the budget.
train_full: A boolean of whether to train on the full data. If true,
@@ -952,6 +955,8 @@ class AutoML:
be retrained. By default `record_id = -1` which means this will be
ignored. `record_id = 0` corresponds to the first trial, and
when `record_id >= 0`, `time_budget` will be ignored.
+ auto_augment: boolean, default=True | Whether to automatically
+ augment rare classes.
**fit_kwargs: Other key word arguments to pass to fit() function of
the searched learners, such as sample_weight.
"""
@@ -1018,6 +1023,7 @@ class AutoML:
elif eval_method == "auto":
eval_method = self._decide_eval_method(time_budget)
self.modelcount = 0
+ self._auto_augment = auto_augment
self._prepare_data(eval_method, split_ratio, n_splits)
self._state.time_budget = None
self._state.n_jobs = n_jobs
@@ -1032,7 +1038,7 @@ class AutoML:
self._state.task = get_classification_objective(
len(np.unique(self._y_train_all))
)
- if self._state.task in ("binary", "multi"):
+ if self._state.task in CLASSIFICATION:
assert split_type in [None, "stratified", "uniform", "time", "group"]
self._split_type = (
split_type or self._state.groups is None and "stratified" or "group"
@@ -1191,7 +1197,7 @@ class AutoML:
Returns:
A float for the minimal sample size or None
"""
- return MIN_SAMPLE_TRAIN if self._sample else None
+ return self._min_sample_size if self._sample else None
@property
def max_resource(self) -> Optional[float]:
@@ -1282,7 +1288,7 @@ class AutoML:
sample_weight_val=None,
groups_val=None,
groups=None,
- verbose=1,
+ verbose=3,
retrain_full=True,
split_type=None,
learner_selector="sample",
@@ -1291,8 +1297,10 @@ class AutoML:
seed=None,
n_concurrent_trials=1,
keep_search_state=False,
- append_log=False,
early_stop=False,
+ append_log=False,
+ auto_augment=True,
+ min_sample_size=MIN_SAMPLE_TRAIN,
**fit_kwargs,
):
"""Find a model for a given task
@@ -1375,7 +1383,7 @@ class AutoML:
groups: None or array-like | Group labels (with matching length to
y_train) or groups counts (with sum equal to length of y_train)
for training data.
- verbose: int, default=1 | Controls the verbosity, higher means more
+ verbose: int, default=3 | Controls the verbosity, higher means more
messages.
retrain_full: bool or str, default=True | whether to retrain the
selected model on the full training data when using holdout.
@@ -1412,8 +1420,12 @@ class AutoML:
saving.
early_stop: boolean, default=False | Whether to stop early if the
search is considered to converge.
- append_log: boolean, default=False | whetehr to directly append the log
+ append_log: boolean, default=False | Whetehr to directly append the log
records to the input log file if it exists.
+ auto_augment: boolean, default=True | Whether to automatically
+ augment rare classes.
+ min_sample_size: int, default=MIN_SAMPLE_TRAIN | the minimal sample
+ size when sample=True.
**fit_kwargs: Other key word arguments to pass to fit() function of
the searched learners, such as sample_weight. Include period as
a key word argument for 'forecast' task.
@@ -1435,8 +1447,8 @@ class AutoML:
self._learner_selector = learner_selector
old_level = logger.getEffectiveLevel()
self.verbose = verbose
- if verbose == 0:
- logger.setLevel(logging.WARNING)
+ # if verbose == 0:
+ logger.setLevel(50 - verbose * 10)
if (not mlflow or not mlflow.active_run()) and not logger.handlers:
# Add the console handler.
_ch = logging.StreamHandler()
@@ -1457,12 +1469,14 @@ class AutoML:
and (eval_method == "holdout" and self._state.X_val is None)
or (eval_method == "cv")
)
+ self._auto_augment = auto_augment
+ self._min_sample_size = min_sample_size
self._prepare_data(eval_method, split_ratio, n_splits)
self._sample = (
sample
and task != "rank"
and eval_method != "cv"
- and (MIN_SAMPLE_TRAIN * SAMPLE_MULTIPLY_FACTOR < self._state.data_size)
+ and (self._min_sample_size * SAMPLE_MULTIPLY_FACTOR < self._state.data_size)
)
if "auto" == metric:
if "binary" in self._state.task:
@@ -1584,8 +1598,8 @@ class AutoML:
for state in self._search_states.values():
if state.trained_estimator:
del state.trained_estimator
- if verbose == 0:
- logger.setLevel(old_level)
+ # if verbose == 0:
+ logger.setLevel(old_level)
def _search_parallel(self):
try:
@@ -1631,6 +1645,8 @@ class AutoML:
points_to_evaluate=points_to_evaluate,
)
else:
+ self._state.time_from_start = time.time() - self._start_time_flag
+ time_left = self._state.time_budget - self._state.time_from_start
search_alg = SearchAlgo(
metric="val_loss",
space=space,
@@ -1645,13 +1661,9 @@ class AutoML:
],
metric_constraints=self.metric_constraints,
seed=self._seed,
+ time_budget_s=time_left,
)
search_alg = ConcurrencyLimiter(search_alg, self._n_concurrent_trials)
- self._state.time_from_start = time.time() - self._start_time_flag
- time_left = self._state.time_budget - self._state.time_from_start
- search_alg.set_search_properties(
- None, None, config={"time_budget_s": time_left}
- )
resources_per_trial = (
{"cpu": self._state.n_jobs} if self._state.n_jobs > 1 else None
)
@@ -1782,7 +1794,7 @@ class AutoML:
search_space = search_state.search_space
if self._sample:
prune_attr = "FLAML_sample_size"
- min_resource = MIN_SAMPLE_TRAIN
+ min_resource = self._min_sample_size
max_resource = self._state.data_size
else:
prune_attr = min_resource = max_resource = None
@@ -1840,10 +1852,10 @@ class AutoML:
else:
search_space = None
if self._hpo_method in ("bs", "cfo", "cfocat"):
- search_state.search_alg.set_search_properties(
+ search_state.search_alg.searcher.set_search_properties(
metric=None,
mode=None,
- config={
+ setting={
"metric_target": self._state.best_loss,
},
)
@@ -1852,7 +1864,7 @@ class AutoML:
search_state.training_function,
search_alg=search_state.search_alg,
time_budget_s=min(budget_left, self._state.train_time_limit),
- verbose=max(self.verbose - 1, 0),
+ verbose=max(self.verbose - 3, 0),
use_ray=False,
)
time_used = time.time() - start_run_time
@@ -2077,7 +2089,7 @@ class AutoML:
logger.info(estimators)
if len(estimators) <= 1:
return
- if self._state.task in ("binary", "multi"):
+ if self._state.task in CLASSIFICATION:
from sklearn.ensemble import StackingClassifier as Stacker
else:
from sklearn.ensemble import StackingRegressor as Stacker
@@ -2184,7 +2196,7 @@ class AutoML:
speed = delta_loss / delta_time
if speed:
estimated_cost = max(2 * gap / speed, estimated_cost)
- estimated_cost == estimated_cost or 1e-10
+ estimated_cost == estimated_cost or 1e-9
inv.append(1 / estimated_cost)
else:
estimated_cost = self._eci[i]
diff --git a/flaml/data.py b/flaml/data.py
index cf278c2d37..258f10b9b0 100644
--- a/flaml/data.py
+++ b/flaml/data.py
@@ -1,15 +1,18 @@
"""!
- * Copyright (c) 2020-2021 Microsoft Corporation. All rights reserved.
+ * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
"""
import numpy as np
from scipy.sparse import vstack, issparse
import pandas as pd
+
from .training_log import training_log_reader
from datetime import datetime
+CLASSIFICATION = ("binary", "multi", "classification")
+
def load_openml_dataset(
dataset_id, data_dir=None, random_state=0, dataset_format="dataframe"
@@ -300,11 +303,7 @@ class DataTransformer:
)
self._drop = drop
- if task in (
- "binary",
- "multi",
- "classification",
- ) or not pd.api.types.is_numeric_dtype(y):
+ if task in CLASSIFICATION or not pd.api.types.is_numeric_dtype(y):
from sklearn.preprocessing import LabelEncoder
self.label_transformer = LabelEncoder()
diff --git a/flaml/ml.py b/flaml/ml.py
index 19ff9c26c7..b2d149565d 100644
--- a/flaml/ml.py
+++ b/flaml/ml.py
@@ -33,7 +33,7 @@ from .model import (
ARIMA,
SARIMAX,
)
-from .data import group_counts
+from .data import CLASSIFICATION, group_counts
import logging
@@ -301,7 +301,7 @@ def evaluate_model_CV(
valid_fold_num = total_fold_num = 0
n = kf.get_n_splits()
X_train_split, y_train_split = X_train_all, y_train_all
- if task in ("binary", "multi"):
+ if task in CLASSIFICATION:
labels = np.unique(y_train_all)
else:
labels = None
diff --git a/flaml/model.py b/flaml/model.py
index cdce9812aa..6488c56d71 100644
--- a/flaml/model.py
+++ b/flaml/model.py
@@ -13,11 +13,11 @@ from lightgbm import LGBMClassifier, LGBMRegressor, LGBMRanker
from scipy.sparse import issparse
import pandas as pd
from . import tune
-from .data import group_counts
+from .data import group_counts, CLASSIFICATION
import logging
-logger = logging.getLogger(__name__)
+logger = logging.getLogger("flaml.automl")
class BaseEstimator:
@@ -30,24 +30,23 @@ class BaseEstimator:
for both regression and classification
"""
- def __init__(self, task="binary", **params):
+ def __init__(self, task="binary", **config):
"""Constructor
Args:
task: A string of the task type, one of
'binary', 'multi', 'regression', 'rank', 'forecast'
- n_jobs: An integer of the number of parallel threads
- params: A dictionary of the hyperparameter names and values
+ config: A dictionary containing the hyperparameter names
+ and 'n_jobs' as keys. n_jobs is the number of parallel threads.
"""
- self.params = params
+ self.params = self.config2params(config)
self.estimator_class = self._model = None
self._task = task
- if "_estimator_type" in params:
- self._estimator_type = params["_estimator_type"]
- del self.params["_estimator_type"]
+ if "_estimator_type" in config:
+ self._estimator_type = self.params.pop("_estimator_type")
else:
self._estimator_type = (
- "classifier" if task in ("binary", "multi") else "regressor"
+ "classifier" if task in CLASSIFICATION else "regressor"
)
def get_params(self, deep=False):
@@ -83,8 +82,9 @@ class BaseEstimator:
current_time = time.time()
if "groups" in kwargs:
kwargs = kwargs.copy()
+ groups = kwargs.pop("groups")
if self._task == "rank":
- kwargs["group"] = group_counts(kwargs["groups"])
+ kwargs["group"] = group_counts(groups)
# groups_val = kwargs.get('groups_val')
# if groups_val is not None:
# kwargs['eval_group'] = [group_counts(groups_val)]
@@ -92,10 +92,13 @@ class BaseEstimator:
# (kwargs['X_val'], kwargs['y_val'])]
# kwargs['verbose'] = False
# del kwargs['groups_val'], kwargs['X_val'], kwargs['y_val']
- del kwargs["groups"]
X_train = self._preprocess(X_train)
model = self.estimator_class(**self.params)
+ if logger.level == logging.DEBUG:
+ logger.debug(f"flaml.model - {model} fit started")
model.fit(X_train, y_train, **kwargs)
+ if logger.level == logging.DEBUG:
+ logger.debug(f"flaml.model - {model} fit finished")
train_time = time.time() - current_time
self._model = model
return train_time
@@ -143,9 +146,8 @@ class BaseEstimator:
Each element at (i,j) is the probability for instance i to be in
class j
"""
- assert self._task in (
- "binary",
- "multi",
+ assert (
+ self._task in CLASSIFICATION
), "predict_prob() only for classification task."
X_test = self._preprocess(X_test)
return self._model.predict_proba(X_test)
@@ -160,9 +162,10 @@ class BaseEstimator:
Returns:
A dictionary of the search space.
Each key is the name of a hyperparameter, and value is a dict with
- its domain and init_value (optional), cat_hp_cost (optional)
+ its domain (required) and low_cost_init_value, init_value,
+ cat_hp_cost (if applicable).
e.g.,
- {'domain': tune.randint(lower=1, upper=10), 'init_value': 1}
+ {'domain': tune.randint(lower=1, upper=10), 'init_value': 1}.
"""
return {}
@@ -171,11 +174,11 @@ class BaseEstimator:
"""[optional method] memory size of the estimator in bytes
Args:
- config - the dict of the hyperparameter config
+ config - A dict of the hyperparameter config.
Returns:
A float of the memory size required by the estimator to train the
- given config
+ given config.
"""
return 1.0
@@ -189,10 +192,21 @@ class BaseEstimator:
"""[optional method] initialize the class"""
pass
+ def config2params(self, config: dict) -> dict:
+ """[optional method] config dict to params dict
+
+ Args:
+ config - A dict of the hyperparameter config.
+
+ Returns:
+ A dict that will be passed to self.estimator_class's constructor.
+ """
+ return config.copy()
+
class SKLearnEstimator(BaseEstimator):
- def __init__(self, task="binary", **params):
- super().__init__(task, **params)
+ def __init__(self, task="binary", **config):
+ super().__init__(task, **config)
def _preprocess(self, X):
if isinstance(X, pd.DataFrame):
@@ -255,39 +269,22 @@ class LGBMEstimator(BaseEstimator):
},
}
+ def config2params(cls, config: dict) -> dict:
+ params = config.copy()
+ if "log_max_bin" in params:
+ params["max_bin"] = (1 << params.pop("log_max_bin")) - 1
+ return params
+
@classmethod
def size(cls, config):
num_leaves = int(round(config.get("num_leaves") or config["max_leaves"]))
n_estimators = int(round(config["n_estimators"]))
return (num_leaves * 3 + (num_leaves - 1) * 4 + 1.0) * n_estimators * 8
- def __init__(self, task="binary", log_max_bin=8, **params):
- super().__init__(task, **params)
- if "objective" not in self.params:
- # Default: ‘regression’ for LGBMRegressor,
- # ‘binary’ or ‘multiclass’ for LGBMClassifier
- objective = "regression"
- if "binary" in task:
- objective = "binary"
- elif "multi" in task:
- objective = "multiclass"
- elif "rank" == task:
- objective = "lambdarank"
- self.params["objective"] = objective
- if "n_estimators" in self.params:
- self.params["n_estimators"] = int(round(self.params["n_estimators"]))
- if "num_leaves" in self.params:
- self.params["num_leaves"] = int(round(self.params["num_leaves"]))
- if "min_child_samples" in self.params:
- self.params["min_child_samples"] = int(
- round(self.params["min_child_samples"])
- )
- if "max_bin" not in self.params:
- self.params["max_bin"] = 1 << int(round(log_max_bin)) - 1
+ def __init__(self, task="binary", **config):
+ super().__init__(task, **config)
if "verbose" not in self.params:
self.params["verbose"] = -1
- # if "subsample_freq" not in self.params:
- # self.params['subsample_freq'] = 1
if "regression" == task:
self.estimator_class = LGBMRegressor
elif "rank" == task:
@@ -355,7 +352,7 @@ class LGBMEstimator(BaseEstimator):
if self.params["n_estimators"] > 0:
self._fit(X_train, y_train, **kwargs)
else:
- self.params["n_estimators"] = n_iter
+ self.params["n_estimators"] = self._model.n_estimators
train_time = time.time() - start_time
return train_time
@@ -415,56 +412,30 @@ class XGBoostEstimator(SKLearnEstimator):
def cost_relative2lgbm(cls):
return 1.6
+ def config2params(cls, config: dict) -> dict:
+ params = config.copy()
+ params["max_depth"] = params.get("max_depth", 0)
+ params["grow_policy"] = params.get("grow_policy", "lossguide")
+ params["booster"] = params.get("booster", "gbtree")
+ params["use_label_encoder"] = params.get("use_label_encoder", False)
+ params["tree_method"] = params.get("tree_method", "hist")
+ if "n_jobs" in config:
+ params["nthread"] = params.pop("n_jobs")
+ return params
+
def __init__(
self,
task="regression",
- all_thread=False,
- n_jobs=1,
- n_estimators=4,
- max_leaves=4,
- subsample=1.0,
- min_child_weight=1,
- learning_rate=0.1,
- reg_lambda=1.0,
- reg_alpha=0.0,
- colsample_bylevel=1.0,
- colsample_bytree=1.0,
- tree_method="auto",
- **params,
+ **config,
):
- super().__init__(task, **params)
- self._n_estimators = int(round(n_estimators))
- self.params.update(
- {
- "max_leaves": int(round(max_leaves)),
- "max_depth": params.get("max_depth", 0),
- "grow_policy": params.get("grow_policy", "lossguide"),
- "tree_method": tree_method,
- "verbosity": params.get("verbosity", 0),
- "nthread": n_jobs,
- "learning_rate": float(learning_rate),
- "subsample": float(subsample),
- "reg_alpha": float(reg_alpha),
- "reg_lambda": float(reg_lambda),
- "min_child_weight": float(min_child_weight),
- "booster": params.get("booster", "gbtree"),
- "colsample_bylevel": float(colsample_bylevel),
- "colsample_bytree": float(colsample_bytree),
- "objective": params.get("objective"),
- }
- )
- if all_thread:
- del self.params["nthread"]
-
- def get_params(self, deep=False):
- params = super().get_params()
- params["n_jobs"] = params["nthread"]
- return params
+ super().__init__(task, **config)
+ self.params["verbosity"] = 0
def fit(self, X_train, y_train, budget=None, **kwargs):
start_time = time.time()
- if not issparse(X_train):
- self.params["tree_method"] = "hist"
+ if issparse(X_train):
+ self.params["tree_method"] = "auto"
+ else:
X_train = self._preprocess(X_train)
if "sample_weight" in kwargs:
dtrain = xgb.DMatrix(X_train, label=y_train, weight=kwargs["sample_weight"])
@@ -478,8 +449,10 @@ class XGBoostEstimator(SKLearnEstimator):
obj = objective
if "objective" in self.params:
del self.params["objective"]
- self._model = xgb.train(self.params, dtrain, self._n_estimators, obj=obj)
+ _n_estimators = self.params.pop("n_estimators")
+ self._model = xgb.train(self.params, dtrain, _n_estimators, obj=obj)
self.params["objective"] = objective
+ self.params["n_estimators"] = _n_estimators
del dtrain
train_time = time.time() - start_time
return train_time
@@ -502,54 +475,29 @@ class XGBoostSklearnEstimator(SKLearnEstimator, LGBMEstimator):
def cost_relative2lgbm(cls):
return XGBoostEstimator.cost_relative2lgbm()
+ def config2params(cls, config: dict) -> dict:
+ params = config.copy()
+ params["max_depth"] = 0
+ params["grow_policy"] = params.get("grow_policy", "lossguide")
+ params["booster"] = params.get("booster", "gbtree")
+ params["use_label_encoder"] = params.get("use_label_encoder", False)
+ params["tree_method"] = params.get("tree_method", "hist")
+ return params
+
def __init__(
self,
task="binary",
- n_jobs=1,
- n_estimators=4,
- max_leaves=4,
- subsample=1.0,
- min_child_weight=1,
- learning_rate=0.1,
- reg_lambda=1.0,
- reg_alpha=0.0,
- colsample_bylevel=1.0,
- colsample_bytree=1.0,
- tree_method="hist",
- **params,
+ **config,
):
- super().__init__(task, **params)
- del self.params["objective"]
- del self.params["max_bin"]
+ super().__init__(task, **config)
del self.params["verbose"]
- self.params.update(
- {
- "n_estimators": int(round(n_estimators)),
- "max_leaves": int(round(max_leaves)),
- "max_depth": 0,
- "grow_policy": params.get("grow_policy", "lossguide"),
- "tree_method": tree_method,
- "n_jobs": n_jobs,
- "verbosity": 0,
- "learning_rate": float(learning_rate),
- "subsample": float(subsample),
- "reg_alpha": float(reg_alpha),
- "reg_lambda": float(reg_lambda),
- "min_child_weight": float(min_child_weight),
- "booster": params.get("booster", "gbtree"),
- "colsample_bylevel": float(colsample_bylevel),
- "colsample_bytree": float(colsample_bytree),
- "use_label_encoder": params.get("use_label_encoder", False),
- }
- )
+ self.params["verbosity"] = 0
self.estimator_class = xgb.XGBRegressor
if "rank" == task:
self.estimator_class = xgb.XGBRanker
- elif task in ("binary", "multi"):
+ elif task in CLASSIFICATION:
self.estimator_class = xgb.XGBClassifier
- self._time_per_iter = None
- self._train_size = 0
def fit(self, X_train, y_train, budget=None, **kwargs):
if issparse(X_train):
@@ -578,7 +526,7 @@ class RandomForestEstimator(SKLearnEstimator, LGBMEstimator):
"low_cost_init_value": 4,
},
}
- if task in ("binary", "multi"):
+ if task in CLASSIFICATION:
space["criterion"] = {
"domain": tune.choice(["gini", "entropy"]),
# 'init_value': 'gini',
@@ -589,36 +537,24 @@ class RandomForestEstimator(SKLearnEstimator, LGBMEstimator):
def cost_relative2lgbm(cls):
return 2.0
+ def config2params(cls, config: dict) -> dict:
+ params = config.copy()
+ if "max_leaves" in params:
+ params["max_leaf_nodes"] = params.get(
+ "max_leaf_nodes", params.pop("max_leaves")
+ )
+ return params
+
def __init__(
self,
task="binary",
- n_jobs=1,
- n_estimators=4,
- max_features=1.0,
- criterion="gini",
- max_leaves=4,
**params,
):
super().__init__(task, **params)
- del self.params["objective"]
- del self.params["max_bin"]
- self.params.update(
- {
- "n_estimators": int(round(n_estimators)),
- "n_jobs": n_jobs,
- "verbose": 0,
- "max_features": float(max_features),
- "max_leaf_nodes": params.get("max_leaf_nodes", int(round(max_leaves))),
- }
- )
+ self.params["verbose"] = 0
self.estimator_class = RandomForestRegressor
- if task in ("binary", "multi"):
+ if task in CLASSIFICATION:
self.estimator_class = RandomForestClassifier
- self.params["criterion"] = criterion
-
- def get_params(self, deep=False):
- params = super().get_params()
- return params
class ExtraTreeEstimator(RandomForestEstimator):
@@ -648,21 +584,16 @@ class LRL1Classifier(SKLearnEstimator):
def cost_relative2lgbm(cls):
return 160
- def __init__(self, task="binary", n_jobs=1, tol=0.0001, C=1.0, **params):
- super().__init__(task, **params)
- self.params.update(
- {
- "penalty": params.get("penalty", "l1"),
- "tol": float(tol),
- "C": float(C),
- "solver": params.get("solver", "saga"),
- "n_jobs": n_jobs,
- }
- )
- assert task in (
- "binary",
- "multi",
- ), "LogisticRegression for classification task only"
+ def config2params(cls, config: dict) -> dict:
+ params = config.copy()
+ params["tol"] = params.get("tol", 0.0001)
+ params["solver"] = params.get("solver", "saga")
+ params["penalty"] = params.get("penalty", "l1")
+ return params
+
+ def __init__(self, task="binary", **config):
+ super().__init__(task, **config)
+ assert task in CLASSIFICATION, "LogisticRegression for classification task only"
self.estimator_class = LogisticRegression
@@ -675,21 +606,16 @@ class LRL2Classifier(SKLearnEstimator):
def cost_relative2lgbm(cls):
return 25
- def __init__(self, task="binary", n_jobs=1, tol=0.0001, C=1.0, **params):
- super().__init__(task, **params)
- self.params.update(
- {
- "penalty": params.get("penalty", "l2"),
- "tol": float(tol),
- "C": float(C),
- "solver": params.get("solver", "lbfgs"),
- "n_jobs": n_jobs,
- }
- )
- assert task in (
- "binary",
- "multi",
- ), "LogisticRegression for classification task only"
+ def config2params(cls, config: dict) -> dict:
+ params = config.copy()
+ params["tol"] = params.get("tol", 0.0001)
+ params["solver"] = params.get("solver", "lbfgs")
+ params["penalty"] = params.get("penalty", "l2")
+ return params
+
+ def __init__(self, task="binary", **config):
+ super().__init__(task, **config)
+ assert task in CLASSIFICATION, "LogisticRegression for classification task only"
self.estimator_class = LogisticRegression
@@ -749,39 +675,33 @@ class CatBoostEstimator(BaseEstimator):
X = X.to_numpy()
return X
+ def config2params(cls, config: dict) -> dict:
+ params = config.copy()
+ params["n_estimators"] = params.get("n_estimators", 8192)
+ if "n_jobs" in params:
+ params["thread_count"] = params.pop("n_jobs")
+ return params
+
def __init__(
self,
task="binary",
- n_jobs=1,
- n_estimators=8192,
- learning_rate=0.1,
- early_stopping_rounds=4,
- **params,
+ **config,
):
- super().__init__(task, **params)
+ super().__init__(task, **config)
self.params.update(
{
- "early_stopping_rounds": int(round(early_stopping_rounds)),
- "n_estimators": n_estimators,
- "learning_rate": learning_rate,
- "thread_count": n_jobs,
- "verbose": params.get("verbose", False),
- "random_seed": params.get("random_seed", 10242048),
+ "verbose": config.get("verbose", False),
+ "random_seed": config.get("random_seed", 10242048),
}
)
from catboost import CatBoostRegressor
self.estimator_class = CatBoostRegressor
- if task in ("binary", "multi"):
+ if task in CLASSIFICATION:
from catboost import CatBoostClassifier
self.estimator_class = CatBoostClassifier
- def get_params(self, deep=False):
- params = super().get_params()
- params["n_jobs"] = params["thread_count"]
- return params
-
def fit(self, X_train, y_train, budget=None, **kwargs):
import shutil
@@ -881,7 +801,7 @@ class CatBoostEstimator(BaseEstimator):
kwargs["sample_weight"] = weight
self._model = model
else:
- self.params["n_estimators"] = n_iter
+ self.params["n_estimators"] = self._model.tree_count_
# except CatBoostError:
# self._model = None
train_time = time.time() - start_time
@@ -904,22 +824,21 @@ class KNeighborsEstimator(BaseEstimator):
def cost_relative2lgbm(cls):
return 30
- def __init__(self, task="binary", n_jobs=1, n_neighbors=5, **params):
- super().__init__(task, **params)
- self.params.update(
- {
- "n_neighbors": int(round(n_neighbors)),
- "weights": params.get("weights", "distance"),
- "n_jobs": n_jobs,
- }
- )
- from sklearn.neighbors import KNeighborsRegressor
+ def config2params(cls, config: dict) -> dict:
+ params = config.copy()
+ params["weights"] = params.get("weights", "distance")
+ return params
- self.estimator_class = KNeighborsRegressor
- if task in ("binary", "multi"):
+ def __init__(self, task="binary", **config):
+ super().__init__(task, **config)
+ if task in CLASSIFICATION:
from sklearn.neighbors import KNeighborsClassifier
self.estimator_class = KNeighborsClassifier
+ else:
+ from sklearn.neighbors import KNeighborsRegressor
+
+ self.estimator_class = KNeighborsRegressor
def _preprocess(self, X):
if isinstance(X, pd.DataFrame):
@@ -963,9 +882,7 @@ class Prophet(BaseEstimator):
}
return space
- def __init__(self, task="forecast", **params):
- if "n_jobs" in params:
- params.pop("n_jobs")
+ def __init__(self, task="forecast", n_jobs=1, **params):
super().__init__(task, **params)
def _join(self, X_train, y_train):
diff --git a/flaml/nlp/hpo/searchalgo_auto.py b/flaml/nlp/hpo/searchalgo_auto.py
index 4a180cfac1..4411500269 100644
--- a/flaml/nlp/hpo/searchalgo_auto.py
+++ b/flaml/nlp/hpo/searchalgo_auto.py
@@ -13,7 +13,7 @@ SEARCH_ALGO_MAPPING = OrderedDict(
("bs", BlendSearch),
("grid", None),
("gridbert", None),
- ("rs", None)
+ ("rs", None),
]
)
@@ -35,14 +35,16 @@ class AutoSearchAlgorithm:
)
@classmethod
- def from_method_name(cls,
- search_algo_name,
- search_algo_args_mode,
- hpo_search_space,
- time_budget,
- metric_name,
- metric_mode_name,
- **custom_hpo_args):
+ def from_method_name(
+ cls,
+ search_algo_name,
+ search_algo_args_mode,
+ hpo_search_space,
+ time_budget,
+ metric_name,
+ metric_mode_name,
+ **custom_hpo_args
+ ):
"""
Instantiating one of the search algorithm classes based on the search algorithm name, search algorithm
argument mode, hpo search space and other keyword args
@@ -68,7 +70,9 @@ class AutoSearchAlgorithm:
{"points_to_evaluate": [{"learning_rate": 1e-5, "num_train_epochs": 10}])
"""
- assert hpo_search_space, "hpo_search_space needs to be specified for calling AutoSearchAlgorithm.from_method_name"
+ assert (
+ hpo_search_space
+ ), "hpo_search_space needs to be specified for calling AutoSearchAlgorithm.from_method_name"
if not search_algo_name:
# TODO coverage
search_algo_name = "grid"
@@ -83,9 +87,15 @@ class AutoSearchAlgorithm:
of the constructor function
"""
this_search_algo_kwargs = None
- allowed_arguments = SEARCH_ALGO_MAPPING[search_algo_name].__init__.__code__.co_varnames
- allowed_custom_args = {key: custom_hpo_args[key] for key in custom_hpo_args.keys() if
- key in allowed_arguments}
+ allowed_arguments = SEARCH_ALGO_MAPPING[
+ search_algo_name
+ ].__init__.__code__.co_varnames
+ custom_hpo_args["time_budget_s"] = time_budget
+ allowed_custom_args = {
+ key: custom_hpo_args[key]
+ for key in custom_hpo_args.keys()
+ if key in allowed_arguments
+ }
"""
If the search_algo_args_mode is "dft", set the args to the default args, e.g.,the default args for
@@ -94,26 +104,34 @@ class AutoSearchAlgorithm:
"""
if search_algo_args_mode == "dft":
# TODO coverage
- this_search_algo_kwargs = DEFAULT_SEARCH_ALGO_ARGS_MAPPING[search_algo_name](
+ this_search_algo_kwargs = DEFAULT_SEARCH_ALGO_ARGS_MAPPING[
+ search_algo_name
+ ](
"dft",
metric_name,
metric_mode_name,
hpo_search_space=hpo_search_space,
- **allowed_custom_args)
+ **allowed_custom_args
+ )
elif search_algo_args_mode == "cus":
- this_search_algo_kwargs = DEFAULT_SEARCH_ALGO_ARGS_MAPPING[search_algo_name](
+ this_search_algo_kwargs = DEFAULT_SEARCH_ALGO_ARGS_MAPPING[
+ search_algo_name
+ ](
"cus",
metric_name,
metric_mode_name,
hpo_search_space=hpo_search_space,
- **allowed_custom_args)
+ **allowed_custom_args
+ )
"""
returning the hpo algorithm with the arguments
"""
- search_algo = SEARCH_ALGO_MAPPING[search_algo_name](**this_search_algo_kwargs)
+ search_algo = SEARCH_ALGO_MAPPING[search_algo_name](
+ **this_search_algo_kwargs
+ )
if search_algo_name == "bs":
- search_algo.set_search_properties(config={"time_budget_s": time_budget})
+ search_algo.set_search_properties()
return search_algo
raise ValueError(
"Unrecognized method {} for this kind of AutoSearchAlgorithm: {}.\n"
@@ -125,29 +143,39 @@ class AutoSearchAlgorithm:
@staticmethod
def grid2list(grid_config):
# TODO coverage
- key_val_list = [[(key, each_val) for each_val in val_list['grid_search']]
- for (key, val_list) in grid_config.items()]
+ key_val_list = [
+ [(key, each_val) for each_val in val_list["grid_search"]]
+ for (key, val_list) in grid_config.items()
+ ]
config_list = [dict(x) for x in itertools.product(*key_val_list)]
return config_list
-def get_search_algo_args_optuna(search_args_mode,
- metric_name,
- metric_mode_name,
- hpo_search_space=None,
- **custom_hpo_args):
+def get_search_algo_args_optuna(
+ search_args_mode,
+ metric_name,
+ metric_mode_name,
+ hpo_search_space=None,
+ **custom_hpo_args
+):
# TODO coverage
return {}
-def default_search_algo_args_bs(search_args_mode,
- metric_name,
- metric_mode_name,
- hpo_search_space=None,
- **custom_hpo_args):
- assert hpo_search_space, "hpo_search_space needs to be specified for calling AutoSearchAlgorithm.from_method_name"
- if "num_train_epochs" in hpo_search_space and \
- isinstance(hpo_search_space["num_train_epochs"], ray.tune.sample.Categorical):
+def default_search_algo_args_bs(
+ search_args_mode,
+ metric_name,
+ metric_mode_name,
+ hpo_search_space=None,
+ time_budget_s=None,
+ **custom_hpo_args
+):
+ assert (
+ hpo_search_space
+ ), "hpo_search_space needs to be specified for calling AutoSearchAlgorithm.from_method_name"
+ if "num_train_epochs" in hpo_search_space and isinstance(
+ hpo_search_space["num_train_epochs"], ray.tune.sample.Categorical
+ ):
min_epoch = min(hpo_search_space["num_train_epochs"].categories)
else:
# TODO coverage
@@ -156,31 +184,38 @@ def default_search_algo_args_bs(search_args_mode,
default_search_algo_args = {
"low_cost_partial_config": {
"num_train_epochs": min_epoch,
- "per_device_train_batch_size": max(hpo_search_space["per_device_train_batch_size"].categories),
+ "per_device_train_batch_size": max(
+ hpo_search_space["per_device_train_batch_size"].categories
+ ),
},
"space": hpo_search_space,
"metric": metric_name,
- "mode": metric_mode_name
+ "mode": metric_mode_name,
+ "time_budget_s": time_budget_s,
}
if search_args_mode == "cus":
default_search_algo_args.update(custom_hpo_args)
return default_search_algo_args
-def default_search_algo_args_grid_search(search_args_mode,
- metric_name,
- metric_mode_name,
- hpo_search_space=None,
- **custom_hpo_args):
+def default_search_algo_args_grid_search(
+ search_args_mode,
+ metric_name,
+ metric_mode_name,
+ hpo_search_space=None,
+ **custom_hpo_args
+):
# TODO coverage
return {}
-def default_search_algo_args_random_search(search_args_mode,
- metric_name,
- metric_mode_name,
- hpo_search_space=None,
- **custom_hpo_args):
+def default_search_algo_args_random_search(
+ search_args_mode,
+ metric_name,
+ metric_mode_name,
+ hpo_search_space=None,
+ **custom_hpo_args
+):
# TODO coverage
return {}
@@ -191,6 +226,5 @@ DEFAULT_SEARCH_ALGO_ARGS_MAPPING = OrderedDict(
("cfo", default_search_algo_args_bs),
("bs", default_search_algo_args_bs),
("grid", default_search_algo_args_grid_search),
- ("gridbert", default_search_algo_args_random_search)
]
)
diff --git a/flaml/onlineml/trial_runner.py b/flaml/onlineml/trial_runner.py
index a5e584e811..d1cc6e5422 100644
--- a/flaml/onlineml/trial_runner.py
+++ b/flaml/onlineml/trial_runner.py
@@ -1,10 +1,10 @@
-import time
import numpy as np
import math
from flaml.tune import Trial
from flaml.scheduler import TrialScheduler
import logging
+
logger = logging.getLogger(__name__)
@@ -45,16 +45,18 @@ class OnlineTrialRunner:
Status change routine of a trial
Trial.PENDING -> (Trial.RUNNING -> Trial.PAUSED -> Trial.RUNNING -> ...) -> Trial.TERMINATED(optional)
"""
+
RANDOM_SEED = 123456
WARMSTART_NUM = 100
- def __init__(self,
- max_live_model_num: int,
- searcher=None,
- scheduler=None,
- champion_test_policy='loss_ucb',
- **kwargs
- ):
+ def __init__(
+ self,
+ max_live_model_num: int,
+ searcher=None,
+ scheduler=None,
+ champion_test_policy="loss_ucb",
+ **kwargs
+ ):
"""Constructor
Args:
@@ -64,7 +66,7 @@ class OnlineTrialRunner:
Required methods of the searcher:
- next_trial()
Generate the next trial to add.
- - set_search_properties(metric: Optional[str], mode: Optional[str], config: dict)
+ - set_search_properties(metric: Optional[str], mode: Optional[str], config: Optional[dict], setting: Optional[dict])
Generate new challengers based on the current champion and update the challenger list
- on_trial_result(trial_id: str, result: Dict)
Reprot results to the scheduler.
@@ -87,8 +89,8 @@ class OnlineTrialRunner:
self._scheduler = scheduler
self._champion_test_policy = champion_test_policy
self._max_live_model_num = max_live_model_num
- self._remove_worse = kwargs.get('remove_worse', True)
- self._bound_trial_num = kwargs.get('bound_trial_num', False)
+ self._remove_worse = kwargs.get("remove_worse", True)
+ self._bound_trial_num = kwargs.get("bound_trial_num", False)
self._no_model_persistence = True
# stores all the trials added to the OnlineTrialRunner
@@ -103,21 +105,19 @@ class OnlineTrialRunner:
# initially schedule up to max_live_model_num of live models and
# set the first trial as the champion (which is done inside self.step())
self._total_steps = 0
- logger.info('init step %s', self._max_live_model_num)
+ logger.info("init step %s", self._max_live_model_num)
# TODO: add more comments
self.step()
assert self._champion_trial is not None
@property
def champion_trial(self) -> Trial:
- """The champion trial
- """
+ """The champion trial"""
return self._champion_trial
@property
def running_trials(self):
- """The running/'live' trials
- """
+ """The running/'live' trials"""
return self._running_trials
def step(self, data_sample=None, prediction_trial_tuple=None):
@@ -147,7 +147,10 @@ class OnlineTrialRunner:
# ***********Update running trials with observation***************************
if data_sample is not None:
self._total_steps += 1
- prediction_made, prediction_trial = prediction_trial_tuple[0], prediction_trial_tuple[1]
+ prediction_made, prediction_trial = (
+ prediction_trial_tuple[0],
+ prediction_trial_tuple[1],
+ )
# assert prediction_trial.status == Trial.RUNNING
trials_to_pause = []
for trial in list(self._running_trials):
@@ -156,16 +159,27 @@ class OnlineTrialRunner:
else:
y_predicted = prediction_made
trial.train_eval_model_online(data_sample, y_predicted)
- logger.debug('running trial at iter %s %s %s %s %s %s', self._total_steps,
- trial.trial_id, trial.result.loss_avg, trial.result.loss_cb,
- trial.result.resource_used, trial.resource_lease)
+ logger.debug(
+ "running trial at iter %s %s %s %s %s %s",
+ self._total_steps,
+ trial.trial_id,
+ trial.result.loss_avg,
+ trial.result.loss_cb,
+ trial.result.resource_used,
+ trial.resource_lease,
+ )
# report result to the searcher
self._searcher.on_trial_result(trial.trial_id, trial.result)
# report result to the scheduler and the scheduler makes a decision about
# the running status of the trial
decision = self._scheduler.on_trial_result(self, trial, trial.result)
# set the status of the trial according to the decision made by the scheduler
- logger.debug('trial decision %s %s at step %s', decision, trial.trial_id, self._total_steps)
+ logger.debug(
+ "trial decision %s %s at step %s",
+ decision,
+ trial.trial_id,
+ self._total_steps,
+ )
if decision == TrialScheduler.STOP:
self.stop_trial(trial)
elif decision == TrialScheduler.PAUSE:
@@ -191,38 +205,45 @@ class OnlineTrialRunner:
else:
break
- def get_top_running_trials(self, top_ratio=None, top_metric='ucb') -> list:
- """Get a list of trial ids, whose performance is among the top running trials
- """
- running_valid_trials = [trial for trial in self._running_trials if
- trial.result is not None]
+ def get_top_running_trials(self, top_ratio=None, top_metric="ucb") -> list:
+ """Get a list of trial ids, whose performance is among the top running trials"""
+ running_valid_trials = [
+ trial for trial in self._running_trials if trial.result is not None
+ ]
if not running_valid_trials:
return
if top_ratio is None:
top_number = 0
elif isinstance(top_ratio, float):
top_number = math.ceil(len(running_valid_trials) * top_ratio)
- elif isinstance(top_ratio, str) and 'best' in top_ratio:
+ elif isinstance(top_ratio, str) and "best" in top_ratio:
top_number = 1
else:
raise NotImplementedError
- if 'ucb' in top_metric:
- test_attribute = 'loss_ucb'
- elif 'avg' in top_metric:
- test_attribute = 'loss_avg'
- elif 'lcb' in top_metric:
- test_attribute = 'loss_lcb'
+ if "ucb" in top_metric:
+ test_attribute = "loss_ucb"
+ elif "avg" in top_metric:
+ test_attribute = "loss_avg"
+ elif "lcb" in top_metric:
+ test_attribute = "loss_lcb"
else:
raise NotImplementedError
top_running_valid_trials = []
- logger.info('Running trial ids %s', [trial.trial_id for trial in running_valid_trials])
+ logger.info(
+ "Running trial ids %s", [trial.trial_id for trial in running_valid_trials]
+ )
self._random_state.shuffle(running_valid_trials)
- results = [trial.result.get_score(test_attribute) for trial in running_valid_trials]
- sorted_index = np.argsort(np.array(results)) # sorted result (small to large) index
+ results = [
+ trial.result.get_score(test_attribute) for trial in running_valid_trials
+ ]
+ # sorted result (small to large) index
+ sorted_index = np.argsort(np.array(results))
for i in range(min(top_number, len(running_valid_trials))):
top_running_valid_trials.append(running_valid_trials[sorted_index[i]])
- logger.info('Top running ids %s', [trial.trial_id for trial in top_running_valid_trials])
+ logger.info(
+ "Top running ids %s", [trial.trial_id for trial in top_running_valid_trials]
+ )
return top_running_valid_trials
def _add_trial_from_searcher(self):
@@ -234,12 +255,25 @@ class OnlineTrialRunner:
"""
# (optionally) upper bound the number of trials in the OnlineTrialRunner
if self._bound_trial_num and self._first_challenger_pool_size is not None:
- active_trial_size = len([t for t in self._trials if t.status != Trial.TERMINATED])
- trial_num_upper_bound = int(round((np.log10(self._total_steps) + 1) * self._first_challenger_pool_size)
- ) if self._first_challenger_pool_size else np.inf
+ active_trial_size = len(
+ [t for t in self._trials if t.status != Trial.TERMINATED]
+ )
+ trial_num_upper_bound = (
+ int(
+ round(
+ (np.log10(self._total_steps) + 1)
+ * self._first_challenger_pool_size
+ )
+ )
+ if self._first_challenger_pool_size
+ else np.inf
+ )
if active_trial_size > trial_num_upper_bound:
- logger.info('Not adding new trials: %s exceeds trial limit %s.',
- active_trial_size, trial_num_upper_bound)
+ logger.info(
+ "Not adding new trials: %s exceeds trial limit %s.",
+ active_trial_size,
+ trial_num_upper_bound,
+ )
return None
# output one trial from the trial pool (new challenger pool) maintained in the searcher
@@ -253,7 +287,7 @@ class OnlineTrialRunner:
# a valid trial is added.
# Assumption on self._searcher: the first trial generated is the champion trial
if self._champion_trial is None:
- logger.info('Initial set up of the champion trial %s', trial.config)
+ logger.info("Initial set up of the champion trial %s", trial.config)
self._set_champion(trial)
else:
self._all_new_challengers_added = True
@@ -261,14 +295,15 @@ class OnlineTrialRunner:
self._first_challenger_pool_size = len(self._trials)
def _champion_test(self):
- """Perform tests again the latest champion, including bette_than tests and worse_than tests
- """
+ """Perform tests again the latest champion, including bette_than tests and worse_than tests"""
# for BetterThan test, we only need to compare the best challenger with the champion
self._get_best_challenger()
if self._best_challenger_trial is not None:
assert self._best_challenger_trial.trial_id != self._champion_trial.trial_id
# test whether a new champion is found and set the trial properties accordingly
- is_new_champion_found = self._better_than_champion_test(self._best_challenger_trial)
+ is_new_champion_found = self._better_than_champion_test(
+ self._best_challenger_trial
+ )
if is_new_champion_found:
self._set_champion(new_champion_trial=self._best_challenger_trial)
@@ -278,39 +313,47 @@ class OnlineTrialRunner:
for trial_to_test in self._trials:
if trial_to_test.status != Trial.TERMINATED:
worse_than_champion = self._worse_than_champion_test(
- self._champion_trial, trial_to_test, self.WARMSTART_NUM)
+ self._champion_trial, trial_to_test, self.WARMSTART_NUM
+ )
if worse_than_champion:
to_stop.append(trial_to_test)
# we want to ensure there are at least #max_live_model_num of challengers remaining
- max_to_stop_num = len([t for t in self._trials if t.status != Trial.TERMINATED]
- ) - self._max_live_model_num
+ max_to_stop_num = (
+ len([t for t in self._trials if t.status != Trial.TERMINATED])
+ - self._max_live_model_num
+ )
for i in range(min(max_to_stop_num, len(to_stop))):
self.stop_trial(to_stop[i])
def _get_best_challenger(self):
- """Get the 'best' (in terms of the champion_test_policy) challenger under consideration.
- """
+ """Get the 'best' (in terms of the champion_test_policy) challenger under consideration."""
if self._champion_test_policy is None:
return
- if 'ucb' in self._champion_test_policy:
- test_attribute = 'loss_ucb'
- elif 'avg' in self._champion_test_policy:
- test_attribute = 'loss_avg'
+ if "ucb" in self._champion_test_policy:
+ test_attribute = "loss_ucb"
+ elif "avg" in self._champion_test_policy:
+ test_attribute = "loss_avg"
else:
raise NotImplementedError
- active_trials = [trial for trial in self._trials if
- (trial.status != Trial.TERMINATED
- and trial.trial_id != self._champion_trial.trial_id
- and trial.result is not None)]
+ active_trials = [
+ trial
+ for trial in self._trials
+ if (
+ trial.status != Trial.TERMINATED
+ and trial.trial_id != self._champion_trial.trial_id
+ and trial.result is not None
+ )
+ ]
if active_trials:
self._random_state.shuffle(active_trials)
- results = [trial.result.get_score(test_attribute) for trial in active_trials]
+ results = [
+ trial.result.get_score(test_attribute) for trial in active_trials
+ ]
best_index = np.argmin(results)
self._best_challenger_trial = active_trials[best_index]
def _set_champion(self, new_champion_trial):
- """Set the status of the existing trials once a new champion is found.
- """
+ """Set the status of the existing trials once a new champion is found."""
assert new_champion_trial is not None
is_init_update = False
if self._champion_trial is None:
@@ -324,21 +367,20 @@ class OnlineTrialRunner:
trial.set_checked_under_current_champion(False)
self._champion_trial = new_champion_trial
self._all_new_challengers_added = False
- logger.info('Set the champion as %s', self._champion_trial.trial_id)
+ logger.info("Set the champion as %s", self._champion_trial.trial_id)
if not is_init_update:
self._champion_update_times += 1
# calling set_search_properties of searcher will trigger
# new challenger generation. we do not do this for init champion
# as this step is already done when first constructing the searcher
- self._searcher.set_search_properties(None, None,
- {self._searcher.CHAMPION_TRIAL_NAME: self._champion_trial}
- )
+ self._searcher.set_search_properties(
+ setting={self._searcher.CHAMPION_TRIAL_NAME: self._champion_trial}
+ )
else:
self._champion_update_times = 0
def get_trials(self) -> list:
- """Return the list of trials managed by this TrialRunner.
- """
+ """Return the list of trials managed by this TrialRunner."""
return self._trials
def add_trial(self, new_trial):
@@ -357,8 +399,12 @@ class OnlineTrialRunner:
if trial.trial_id == new_trial.trial_id:
trial.set_checked_under_current_champion(True)
return
- logger.info('adding trial at iter %s, %s %s', self._total_steps, new_trial.trial_id,
- len(self._trials))
+ logger.info(
+ "adding trial at iter %s, %s %s",
+ self._total_steps,
+ new_trial.trial_id,
+ len(self._trials),
+ )
self._trials.append(new_trial)
self._scheduler.on_trial_add(self, new_trial)
@@ -369,8 +415,11 @@ class OnlineTrialRunner:
if trial.status in [Trial.ERROR, Trial.TERMINATED]:
return
else:
- logger.info('Terminating trial %s, with trial result %s',
- trial.trial_id, trial.result)
+ logger.info(
+ "Terminating trial %s, with trial result %s",
+ trial.trial_id,
+ trial.result,
+ )
trial.set_status(Trial.TERMINATED)
# clean up model and result
trial.clean_up_model()
@@ -385,10 +434,15 @@ class OnlineTrialRunner:
if trial.status in [Trial.ERROR, Trial.TERMINATED]:
return
else:
- logger.info('Pausing trial %s, with trial loss_avg: %s, loss_cb: %s, loss_ucb: %s,\
- resource_lease: %s', trial.trial_id, trial.result.loss_avg,
- trial.result.loss_cb, trial.result.loss_avg + trial.result.loss_cb,
- trial.resource_lease)
+ logger.info(
+ "Pausing trial %s, with trial loss_avg: %s, loss_cb: %s, loss_ucb: %s,\
+ resource_lease: %s",
+ trial.trial_id,
+ trial.result.loss_avg,
+ trial.result.loss_cb,
+ trial.result.loss_avg + trial.result.loss_cb,
+ trial.resource_lease,
+ )
trial.set_status(Trial.PAUSED)
# clean up model and result if no model persistence
if self._no_model_persistence:
@@ -413,11 +467,15 @@ class OnlineTrialRunner:
A bool indicating whether a new champion is found
"""
if trial_to_test.result is not None and self._champion_trial.result is not None:
- if 'ucb' in self._champion_test_policy:
- return self._test_lcb_ucb(self._champion_trial, trial_to_test, self.WARMSTART_NUM)
- elif 'avg' in self._champion_test_policy:
- return self._test_avg_loss(self._champion_trial, trial_to_test, self.WARMSTART_NUM)
- elif 'martingale' in self._champion_test_policy:
+ if "ucb" in self._champion_test_policy:
+ return self._test_lcb_ucb(
+ self._champion_trial, trial_to_test, self.WARMSTART_NUM
+ )
+ elif "avg" in self._champion_test_policy:
+ return self._test_avg_loss(
+ self._champion_trial, trial_to_test, self.WARMSTART_NUM
+ )
+ elif "martingale" in self._champion_test_policy:
return self._test_martingale(self._champion_trial, trial_to_test)
else:
raise NotImplementedError
@@ -426,22 +484,38 @@ class OnlineTrialRunner:
@staticmethod
def _worse_than_champion_test(champion_trial, trial, warmstart_num=1) -> bool:
- """Test whether the input trial is worse than the champion_trial
- """
+ """Test whether the input trial is worse than the champion_trial"""
if trial.result is not None and trial.result.resource_used >= warmstart_num:
if trial.result.loss_lcb > champion_trial.result.loss_ucb:
- logger.info('=========trial %s is worse than champion %s=====',
- trial.trial_id, champion_trial.trial_id)
- logger.info('trial %s %s %s', trial.config, trial.result, trial.resource_lease)
- logger.info('trial loss_avg:%s, trial loss_cb %s', trial.result.loss_avg,
- trial.result.loss_cb)
- logger.info('champion loss_avg:%s, champion loss_cb %s', champion_trial.result.loss_avg,
- champion_trial.result.loss_cb)
- logger.info('champion %s', champion_trial.config)
- logger.info('trial loss_avg_recent:%s, trial loss_cb %s', trial.result.loss_avg_recent,
- trial.result.loss_cb)
- logger.info('champion loss_avg_recent:%s, champion loss_cb %s',
- champion_trial.result.loss_avg_recent, champion_trial.result.loss_cb)
+ logger.info(
+ "=========trial %s is worse than champion %s=====",
+ trial.trial_id,
+ champion_trial.trial_id,
+ )
+ logger.info(
+ "trial %s %s %s", trial.config, trial.result, trial.resource_lease
+ )
+ logger.info(
+ "trial loss_avg:%s, trial loss_cb %s",
+ trial.result.loss_avg,
+ trial.result.loss_cb,
+ )
+ logger.info(
+ "champion loss_avg:%s, champion loss_cb %s",
+ champion_trial.result.loss_avg,
+ champion_trial.result.loss_cb,
+ )
+ logger.info("champion %s", champion_trial.config)
+ logger.info(
+ "trial loss_avg_recent:%s, trial loss_cb %s",
+ trial.result.loss_avg_recent,
+ trial.result.loss_cb,
+ )
+ logger.info(
+ "champion loss_avg_recent:%s, champion loss_cb %s",
+ champion_trial.result.loss_avg_recent,
+ champion_trial.result.loss_cb,
+ )
return True
return False
@@ -452,18 +526,35 @@ class OnlineTrialRunner:
"""
assert trial.trial_id != champion_trial.trial_id
if trial.result.resource_used >= warmstart_num:
- if trial.result.loss_ucb < champion_trial.result.loss_lcb - champion_trial.result.loss_cb:
- logger.info('======new champion condition satisfied: using lcb vs ucb=====')
- logger.info('new champion trial %s %s %s',
- trial.trial_id, trial.result.resource_used, trial.resource_lease)
- logger.info('new champion trial loss_avg:%s, trial loss_cb %s',
- trial.result.loss_avg, trial.result.loss_cb)
- logger.info('old champion trial %s %s %s',
- champion_trial.trial_id, champion_trial.result.resource_used,
- champion_trial.resource_lease,)
- logger.info('old champion loss avg %s, loss cb %s',
- champion_trial.result.loss_avg,
- champion_trial.result.loss_cb)
+ if (
+ trial.result.loss_ucb
+ < champion_trial.result.loss_lcb - champion_trial.result.loss_cb
+ ):
+ logger.info(
+ "======new champion condition satisfied: using lcb vs ucb====="
+ )
+ logger.info(
+ "new champion trial %s %s %s",
+ trial.trial_id,
+ trial.result.resource_used,
+ trial.resource_lease,
+ )
+ logger.info(
+ "new champion trial loss_avg:%s, trial loss_cb %s",
+ trial.result.loss_avg,
+ trial.result.loss_cb,
+ )
+ logger.info(
+ "old champion trial %s %s %s",
+ champion_trial.trial_id,
+ champion_trial.result.resource_used,
+ champion_trial.resource_lease,
+ )
+ logger.info(
+ "old champion loss avg %s, loss cb %s",
+ champion_trial.result.loss_avg,
+ champion_trial.result.loss_cb,
+ )
return True
return False
@@ -475,13 +566,19 @@ class OnlineTrialRunner:
assert trial.trial_id != champion_trial.trial_id
if trial.result.resource_used >= warmstart_num:
if trial.result.loss_avg < champion_trial.result.loss_avg:
- logger.info('=====new champion condition satisfied using avg loss=====')
- logger.info('trial %s', trial.config)
- logger.info('trial loss_avg:%s, trial loss_cb %s',
- trial.result.loss_avg, trial.result.loss_cb)
- logger.info('champion loss_avg:%s, champion loss_cb %s',
- champion_trial.result.loss_avg, champion_trial.result.loss_cb)
- logger.info('champion %s', champion_trial.config)
+ logger.info("=====new champion condition satisfied using avg loss=====")
+ logger.info("trial %s", trial.config)
+ logger.info(
+ "trial loss_avg:%s, trial loss_cb %s",
+ trial.result.loss_avg,
+ trial.result.loss_cb,
+ )
+ logger.info(
+ "champion loss_avg:%s, champion loss_cb %s",
+ champion_trial.result.loss_avg,
+ champion_trial.result.loss_cb,
+ )
+ logger.info("champion %s", champion_trial.config)
return True
return False
diff --git a/flaml/searcher/blendsearch.py b/flaml/searcher/blendsearch.py
index ee6dc5340e..e4e20130cc 100644
--- a/flaml/searcher/blendsearch.py
+++ b/flaml/searcher/blendsearch.py
@@ -129,11 +129,12 @@ class BlendSearch(Searcher):
self._metric, self._mode = metric, mode
init_config = low_cost_partial_config or {}
if not init_config:
- logger.warning(
+ logger.info(
"No low-cost partial config given to the search algorithm. "
"For cost-frugal search, "
"consider providing low-cost values for cost-related hps via "
- "'low_cost_partial_config'."
+ "'low_cost_partial_config'. More info can be found at "
+ "https://github.com/microsoft/FLAML/wiki/About-%60low_cost_partial_config%60"
)
if evaluated_rewards and mode:
self._points_to_evaluate = []
@@ -228,6 +229,7 @@ class BlendSearch(Searcher):
metric: Optional[str] = None,
mode: Optional[str] = None,
config: Optional[Dict] = None,
+ setting: Optional[Dict] = None,
) -> bool:
metric_changed = mode_changed = False
if metric and self._metric != metric:
@@ -264,22 +266,22 @@ class BlendSearch(Searcher):
)
self._gs.space = self._ls.space
self._init_search()
- if config:
- # CFO doesn't need these settings
- if "time_budget_s" in config:
- self._time_budget_s = config["time_budget_s"] # budget from now
- now = time.time()
- self._time_used += now - self._start_time
- self._start_time = now
- self._set_deadline()
- if "metric_target" in config:
- self._metric_target = config.get("metric_target")
- if "num_samples" in config:
- self._num_samples = (
- config["num_samples"]
- + len(self._result)
- + len(self._trial_proposed_by)
- )
+ if setting:
+ # CFO doesn't need these settings
+ if "time_budget_s" in setting:
+ self._time_budget_s = setting["time_budget_s"] # budget from now
+ now = time.time()
+ self._time_used += now - self._start_time
+ self._start_time = now
+ self._set_deadline()
+ if "metric_target" in setting:
+ self._metric_target = setting.get("metric_target")
+ if "num_samples" in setting:
+ self._num_samples = (
+ setting["num_samples"]
+ + len(self._result)
+ + len(self._trial_proposed_by)
+ )
return True
def _set_deadline(self):
diff --git a/flaml/searcher/flow2.py b/flaml/searcher/flow2.py
index 7e3b49cd98..9057c65842 100644
--- a/flaml/searcher/flow2.py
+++ b/flaml/searcher/flow2.py
@@ -1,14 +1,16 @@
-'''!
- * Copyright (c) 2020-2021 Microsoft Corporation. All rights reserved.
+"""!
+ * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE file in the
* project root for license information.
-'''
+"""
from flaml.tune.sample import Domain
from typing import Dict, Optional, Tuple
import numpy as np
+
try:
from ray import __version__ as ray_version
- assert ray_version >= '1.0.0'
+
+ assert ray_version >= "1.0.0"
from ray.tune.suggest import Searcher
from ray.tune.suggest.variant_generator import generate_variants
from ray.tune import sample
@@ -22,28 +24,30 @@ from ..tune.space import complete_config, denormalize, normalize
import logging
+
logger = logging.getLogger(__name__)
class FLOW2(Searcher):
- '''Local search algorithm FLOW2, with adaptive step size
- '''
+ """Local search algorithm FLOW2, with adaptive step size"""
STEPSIZE = 0.1
STEP_LOWER_BOUND = 0.0001
- def __init__(self,
- init_config: dict,
- metric: Optional[str] = None,
- mode: Optional[str] = None,
- space: Optional[dict] = None,
- prune_attr: Optional[str] = None,
- min_resource: Optional[float] = None,
- max_resource: Optional[float] = None,
- resource_multiple_factor: Optional[float] = 4,
- cost_attr: Optional[str] = 'time_total_s',
- seed: Optional[int] = 20):
- '''Constructor
+ def __init__(
+ self,
+ init_config: dict,
+ metric: Optional[str] = None,
+ mode: Optional[str] = None,
+ space: Optional[dict] = None,
+ prune_attr: Optional[str] = None,
+ min_resource: Optional[float] = None,
+ max_resource: Optional[float] = None,
+ resource_multiple_factor: Optional[float] = 4,
+ cost_attr: Optional[str] = "time_total_s",
+ seed: Optional[int] = 20,
+ ):
+ """Constructor
Args:
init_config: a dictionary of a partial or full initial config,
@@ -79,20 +83,18 @@ class FLOW2(Searcher):
used for increasing resource.
cost_attr: A string of the attribute used for cost.
seed: An integer of the random seed.
- '''
+ """
if mode:
assert mode in ["min", "max"], "`mode` must be 'min' or 'max'."
else:
mode = "min"
- super(FLOW2, self).__init__(
- metric=metric,
- mode=mode)
+ super(FLOW2, self).__init__(metric=metric, mode=mode)
# internally minimizes, so "max" => -1
if mode == "max":
- self.metric_op = -1.
+ self.metric_op = -1.0
elif mode == "min":
- self.metric_op = 1.
+ self.metric_op = 1.0
self.space = space or {}
self._space = flatten_dict(self.space, prevent_delimiter=True)
self._random = np.random.RandomState(seed)
@@ -106,7 +108,7 @@ class FLOW2(Searcher):
self.max_resource = max_resource
self._resource = None
self._step_lb = np.Inf
- if space:
+ if space is not None:
self._init_search()
def _init_search(self):
@@ -115,9 +117,10 @@ class FLOW2(Searcher):
self._unordered_cat_hp = {}
hier = False
for key, domain in self._space.items():
- assert not (isinstance(domain, dict) and 'grid_search' in domain), \
- f"{key}'s domain is grid search, not supported in FLOW^2."
- if callable(getattr(domain, 'get_sampler', None)):
+ assert not (
+ isinstance(domain, dict) and "grid_search" in domain
+ ), f"{key}'s domain is grid search, not supported in FLOW^2."
+ if callable(getattr(domain, "get_sampler", None)):
self._tunable_keys.append(key)
sampler = domain.get_sampler()
# the step size lower bound for uniform variables doesn't depend
@@ -125,12 +128,14 @@ class FLOW2(Searcher):
if isinstance(sampler, sample.Quantized):
q = sampler.q
sampler = sampler.get_sampler()
- if str(sampler) == 'Uniform':
+ if str(sampler) == "Uniform":
self._step_lb = min(
- self._step_lb, q / (domain.upper - domain.lower))
- elif isinstance(domain, sample.Integer) and str(sampler) == 'Uniform':
+ self._step_lb, q / (domain.upper - domain.lower)
+ )
+ elif isinstance(domain, sample.Integer) and str(sampler) == "Uniform":
self._step_lb = min(
- self._step_lb, 1.0 / (domain.upper - 1 - domain.lower))
+ self._step_lb, 1.0 / (domain.upper - 1 - domain.lower)
+ )
if isinstance(domain, sample.Categorical):
if not domain.ordered:
self._unordered_cat_hp[key] = len(domain.categories)
@@ -139,13 +144,12 @@ class FLOW2(Searcher):
if isinstance(cat, dict):
hier = True
break
- if str(sampler) != 'Normal':
+ if str(sampler) != "Normal":
self._bounded_keys.append(key)
if not hier:
self._space_keys = sorted(self._tunable_keys)
self.hierarchical = hier
- if (self.prune_attr and self.prune_attr not in self._space
- and self.max_resource):
+ if self.prune_attr and self.prune_attr not in self._space and self.max_resource:
self.min_resource = self.min_resource or self._min_resource()
self._resource = self._round(self.min_resource)
if not hier:
@@ -169,10 +173,11 @@ class FLOW2(Searcher):
if self.step > self.step_ub:
self.step = self.step_ub
# maximal # consecutive no improvements
- self.dir = 2**(min(9, self.dim))
+ self.dir = 2 ** (min(9, self.dim))
self._configs = {} # dict from trial_id to (config, stepsize)
self._K = 0
- self._iter_best_config = self.trial_count_proposed = self.trial_count_complete = 1
+ self._iter_best_config = 1
+ self.trial_count_proposed = self.trial_count_complete = 1
self._num_proposedby_incumbent = 0
self._reset_times = 0
# record intermediate trial cost
@@ -196,14 +201,18 @@ class FLOW2(Searcher):
if isinstance(sampler, sample.Quantized):
q = sampler.q
sampler_inner = sampler.get_sampler()
- if str(sampler_inner) == 'LogUniform':
+ if str(sampler_inner) == "LogUniform":
step_lb = min(
- step_lb, np.log(1.0 + q / self.best_config[key])
- / np.log(domain.upper / domain.lower))
- elif isinstance(domain, sample.Integer) and str(sampler) == 'LogUniform':
+ step_lb,
+ np.log(1.0 + q / self.best_config[key])
+ / np.log(domain.upper / domain.lower),
+ )
+ elif isinstance(domain, sample.Integer) and str(sampler) == "LogUniform":
step_lb = min(
- step_lb, np.log(1.0 + 1.0 / self.best_config[key])
- / np.log((domain.upper - 1) / domain.lower))
+ step_lb,
+ np.log(1.0 + 1.0 / self.best_config[key])
+ / np.log((domain.upper - 1) / domain.lower),
+ )
if np.isinf(step_lb):
step_lb = self.STEP_LOWER_BOUND
else:
@@ -215,13 +224,11 @@ class FLOW2(Searcher):
return self._resource
def _min_resource(self) -> float:
- ''' automatically decide minimal resource
- '''
+ """automatically decide minimal resource"""
return self.max_resource / np.pow(self.resource_multiple_factor, 5)
def _round(self, resource) -> float:
- ''' round the resource to self.max_resource if close to it
- '''
+ """round the resource to self.max_resource if close to it"""
if resource * self.resource_multiple_factor > self.max_resource:
return self.max_resource
return resource
@@ -231,70 +238,83 @@ class FLOW2(Searcher):
return vec
def complete_config(
- self, partial_config: Dict,
- lower: Optional[Dict] = None, upper: Optional[Dict] = None
+ self,
+ partial_config: Dict,
+ lower: Optional[Dict] = None,
+ upper: Optional[Dict] = None,
) -> Tuple[Dict, Dict]:
- ''' generate a complete config from the partial config input
+ """generate a complete config from the partial config input
add minimal resource to config if available
- '''
+ """
disturb = self._reset_times and partial_config == self.init_config
# if not the first time to complete init_config, use random gaussian
config, space = complete_config(
- partial_config, self.space, self, disturb, lower, upper)
+ partial_config, self.space, self, disturb, lower, upper
+ )
if partial_config == self.init_config:
self._reset_times += 1
if self._resource:
config[self.prune_attr] = self.min_resource
return config, space
- def create(self, init_config: Dict, obj: float, cost: float, space: Dict
- ) -> Searcher:
+ def create(
+ self, init_config: Dict, obj: float, cost: float, space: Dict
+ ) -> Searcher:
# space is the subspace where the init_config is located
flow2 = self.__class__(
- init_config, self.metric, self.mode,
- space, self.prune_attr,
- self.min_resource, self.max_resource,
- self.resource_multiple_factor, self.cost_attr, self.seed + 1)
+ init_config,
+ self.metric,
+ self.mode,
+ space,
+ self.prune_attr,
+ self.min_resource,
+ self.max_resource,
+ self.resource_multiple_factor,
+ self.cost_attr,
+ self.seed + 1,
+ )
flow2.best_obj = obj * self.metric_op # minimize internally
flow2.cost_incumbent = cost
self.seed += 1
return flow2
def normalize(self, config, recursive=False) -> Dict:
- ''' normalize each dimension in config to [0,1]
- '''
+ """normalize each dimension in config to [0,1]"""
return normalize(
- config, self._space, self.best_config, self.incumbent, recursive)
+ config, self._space, self.best_config, self.incumbent, recursive
+ )
def denormalize(self, config):
- ''' denormalize each dimension in config from [0,1]
- '''
+ """denormalize each dimension in config from [0,1]"""
return denormalize(
- config, self._space, self.best_config, self.incumbent, self._random)
+ config, self._space, self.best_config, self.incumbent, self._random
+ )
- def set_search_properties(self,
- metric: Optional[str] = None,
- mode: Optional[str] = None,
- config: Optional[Dict] = None) -> bool:
+ def set_search_properties(
+ self,
+ metric: Optional[str] = None,
+ mode: Optional[str] = None,
+ config: Optional[Dict] = None,
+ ) -> bool:
if metric:
self._metric = metric
if mode:
assert mode in ["min", "max"], "`mode` must be 'min' or 'max'."
self._mode = mode
if mode == "max":
- self.metric_op = -1.
+ self.metric_op = -1.0
elif mode == "min":
- self.metric_op = 1.
+ self.metric_op = 1.0
if config:
self.space = config
self._space = flatten_dict(self.space)
self._init_search()
return True
- def on_trial_complete(self, trial_id: str, result: Optional[Dict] = None,
- error: bool = False):
- ''' compare with incumbent
- '''
+ def on_trial_complete(
+ self, trial_id: str, result: Optional[Dict] = None, error: bool = False
+ ):
+ # compare with incumbent
# if better, move, reset num_complete and num_proposed
# if not better and num_complete >= 2*dim, num_allowed += 2
self.trial_count_complete += 1
@@ -329,15 +349,19 @@ class FLOW2(Searcher):
if proposed_by == self.incumbent:
# proposed by current incumbent and no better
self._num_complete4incumbent += 1
- cost = result.get(
- self.cost_attr) if result else self._trial_cost.get(trial_id)
+ cost = (
+ result.get(self.cost_attr) if result else self._trial_cost.get(trial_id)
+ )
if cost:
self._cost_complete4incumbent += cost
- if self._num_complete4incumbent >= 2 * self.dim and \
- self._num_allowed4incumbent == 0:
+ if (
+ self._num_complete4incumbent >= 2 * self.dim
+ and self._num_allowed4incumbent == 0
+ ):
self._num_allowed4incumbent = 2
if self._num_complete4incumbent == self.dir and (
- not self._resource or self._resource == self.max_resource):
+ not self._resource or self._resource == self.max_resource
+ ):
# check stuck condition if using max resource
self._num_complete4incumbent -= 2
if self._num_allowed4incumbent < 2:
@@ -345,8 +369,7 @@ class FLOW2(Searcher):
# elif proposed_by: del self._proposed_by[trial_id]
def on_trial_result(self, trial_id: str, result: Dict):
- ''' early update of incumbent
- '''
+ """early update of incumbent"""
if result:
obj = result.get(self._metric)
if obj:
@@ -373,27 +396,32 @@ class FLOW2(Searcher):
def rand_vector_unit_sphere(self, dim, trunc=0) -> np.ndarray:
vec = self._random.normal(0, 1, dim)
if 0 < trunc < dim:
- vec[np.abs(vec).argsort()[:dim - trunc]] = 0
+ vec[np.abs(vec).argsort()[: dim - trunc]] = 0
mag = np.linalg.norm(vec)
return vec / mag
def suggest(self, trial_id: str) -> Optional[Dict]:
- ''' suggest a new config, one of the following cases:
+ """suggest a new config, one of the following cases:
1. same incumbent, increase resource
2. same resource, move from the incumbent to a random direction
3. same resource, move from the incumbent to the opposite direction
#TODO: better decouple FLOW2 config suggestion and stepsize update
- '''
+ """
self.trial_count_proposed += 1
- if self._num_complete4incumbent > 0 and self.cost_incumbent and \
- self._resource and self._resource < self.max_resource and (
+ if (
+ self._num_complete4incumbent > 0
+ and self.cost_incumbent
+ and self._resource
+ and self._resource < self.max_resource
+ and (
self._cost_complete4incumbent
- >= self.cost_incumbent * self.resource_multiple_factor):
+ >= self.cost_incumbent * self.resource_multiple_factor
+ )
+ ):
# consider increasing resource using sum eval cost of complete
# configs
old_resource = self._resource
- self._resource = self._round(
- self._resource * self.resource_multiple_factor)
+ self._resource = self._round(self._resource * self.resource_multiple_factor)
self.cost_incumbent *= self._resource / old_resource
config = self.best_config.copy()
config[self.prune_attr] = self._resource
@@ -409,8 +437,9 @@ class FLOW2(Searcher):
self._direction_tried = None
else:
# propose a new direction
- self._direction_tried = self.rand_vector_unit_sphere(
- self.dim, self._trunc) * self.step
+ self._direction_tried = (
+ self.rand_vector_unit_sphere(self.dim, self._trunc) * self.step
+ )
for i, key in enumerate(self._tunable_keys):
move[key] += self._direction_tried[i]
self._project(move)
@@ -442,7 +471,8 @@ class FLOW2(Searcher):
break
self._same = same
if self._num_proposedby_incumbent == self.dir and (
- not self._resource or self._resource == self.max_resource):
+ not self._resource or self._resource == self.max_resource
+ ):
# check stuck condition if using max resource
self._num_proposedby_incumbent -= 2
self._init_phase = False
@@ -459,11 +489,11 @@ class FLOW2(Searcher):
# random
for i, key in enumerate(self._tunable_keys):
if self._direction_tried[i] != 0:
- for _, generated in generate_variants({'config': {
- key: self._space[key]
- }}):
- if generated['config'][key] != best_config[key]:
- config[key] = generated['config'][key]
+ for _, generated in generate_variants(
+ {"config": {key: self._space[key]}}
+ ):
+ if generated["config"][key] != best_config[key]:
+ config[key] = generated["config"][key]
return unflatten_dict(config)
break
else:
@@ -477,8 +507,7 @@ class FLOW2(Searcher):
return unflatten_dict(config)
def _project(self, config):
- ''' project normalized config in the feasible region and set prune_attr
- '''
+ """project normalized config in the feasible region and set prune_attr"""
for key in self._bounded_keys:
value = config[key]
config[key] = max(0, min(1, value))
@@ -487,14 +516,13 @@ class FLOW2(Searcher):
@property
def can_suggest(self) -> bool:
- ''' can't suggest if 2*dim configs have been proposed for the incumbent
- while fewer are completed
- '''
+ """can't suggest if 2*dim configs have been proposed for the incumbent
+ while fewer are completed
+ """
return self._num_allowed4incumbent > 0
def config_signature(self, config, space: Dict = None) -> tuple:
- ''' return the signature tuple of a config
- '''
+ """return the signature tuple of a config"""
config = flatten_dict(config)
if space:
space = flatten_dict(space)
@@ -514,8 +542,11 @@ class FLOW2(Searcher):
if self.hierarchical:
# can't remove constant for hierarchical search space,
# e.g., learner
- if not (domain is None or type(domain) in (str, int, float)
- or isinstance(domain, sample.Domain)):
+ if not (
+ domain is None
+ or type(domain) in (str, int, float)
+ or isinstance(domain, sample.Domain)
+ ):
# not domain or hashable
# get rid of list type for hierarchical search space.
continue
@@ -527,16 +558,14 @@ class FLOW2(Searcher):
@property
def converged(self) -> bool:
- ''' return whether the local search has converged
- '''
+ """return whether the local search has converged"""
if self._num_complete4incumbent < self.dir - 2:
return False
# check stepsize after enough configs are completed
return self.step < self.step_lower_bound
def reach(self, other: Searcher) -> bool:
- ''' whether the incumbent can reach the incumbent of other
- '''
+ """whether the incumbent can reach the incumbent of other"""
config1, config2 = self.best_config, other.best_config
incumbent1, incumbent2 = self.incumbent, other.incumbent
if self._resource and config1[self.prune_attr] > config2[self.prune_attr]:
@@ -547,6 +576,9 @@ class FLOW2(Searcher):
if config1[key] != config2.get(key):
return False
delta = np.array(
- [incumbent1[key] - incumbent2.get(key, np.inf)
- for key in self._tunable_keys])
+ [
+ incumbent1[key] - incumbent2.get(key, np.inf)
+ for key in self._tunable_keys
+ ]
+ )
return np.linalg.norm(delta) <= self.step
diff --git a/flaml/searcher/online_searcher.py b/flaml/searcher/online_searcher.py
index 836717ab02..e90f682815 100644
--- a/flaml/searcher/online_searcher.py
+++ b/flaml/searcher/online_searcher.py
@@ -20,14 +20,19 @@ class BaseSearcher:
on_trial_complete()
"""
- def __init__(self,
- metric: Optional[str] = None,
- mode: Optional[str] = None,
- ):
+ def __init__(
+ self,
+ metric: Optional[str] = None,
+ mode: Optional[str] = None,
+ ):
pass
- def set_search_properties(self, metric: Optional[str] = None, mode: Optional[str] = None,
- config: Optional[Dict] = None):
+ def set_search_properties(
+ self,
+ metric: Optional[str] = None,
+ mode: Optional[str] = None,
+ config: Optional[Dict] = None,
+ ):
if metric:
self._metric = metric
if mode:
@@ -66,6 +71,7 @@ class ChampionFrontierSearcher(BaseSearcher):
(although not the same searcher_trial_id).
searcher_trial_id will be used in suggest()
"""
+
# ****the following constants are used when generating new challengers in
# the _query_config_oracle function
# how many item to add when doing the expansion
@@ -84,25 +90,26 @@ class ChampionFrontierSearcher(BaseSearcher):
# 0.95 of the previous best config's loss.
# NOTE: this setting depends on the assumption that (and thus
# _query_config_oracle) is only triggered when a better champion is found.
- CFO_SEARCHER_METRIC_NAME = 'pseudo_loss'
+ CFO_SEARCHER_METRIC_NAME = "pseudo_loss"
CFO_SEARCHER_LARGE_LOSS = 1e6
# the random seed used in generating numerical hyperparamter configs (when CFO is not used)
NUM_RANDOM_SEED = 111
- CHAMPION_TRIAL_NAME = 'champion_trial'
+ CHAMPION_TRIAL_NAME = "champion_trial"
TRIAL_CLASS = VowpalWabbitTrial
- def __init__(self,
- init_config: Dict,
- space: Optional[Dict] = None,
- metric: Optional[str] = None,
- mode: Optional[str] = None,
- random_seed: Optional[int] = 2345,
- online_trial_args: Optional[Dict] = {},
- nonpoly_searcher_name: Optional[str] = 'CFO'
- ):
- '''Constructor
+ def __init__(
+ self,
+ init_config: Dict,
+ space: Optional[Dict] = None,
+ metric: Optional[str] = None,
+ mode: Optional[str] = None,
+ random_seed: Optional[int] = 2345,
+ online_trial_args: Optional[Dict] = {},
+ nonpoly_searcher_name: Optional[str] = "CFO",
+ ):
+ """Constructor
Args:
init_config: dict
@@ -113,7 +120,7 @@ class ChampionFrontierSearcher(BaseSearcher):
online_trial_args: dict
nonpoly_searcher_name: A string to specify the search algorithm
for nonpoly hyperparameters
- '''
+ """
self._init_config = init_config
self._space = space
self._seed = random_seed
@@ -122,44 +129,62 @@ class ChampionFrontierSearcher(BaseSearcher):
self._random_state = np.random.RandomState(self._seed)
self._searcher_for_nonpoly_hp = {}
- self._space_of_nonpoly_hp = {}
+
# dicts to remember the mapping between searcher_trial_id and trial_id
- self._searcher_trialid_to_trialid = {} # key: searcher_trial_id, value: trial_id
- self._trialid_to_searcher_trial_id = {} # value: trial_id, key: searcher_trial_id
+ self._space_of_nonpoly_hp = {}
+
+ # key: searcher_trial_id, value: trial_id
+ self._searcher_trialid_to_trialid = {}
+
+ # value: trial_id, key: searcher_trial_id
+ self._trialid_to_searcher_trial_id = {}
+
self._challenger_list = []
# initialize the search in set_search_properties
- self.set_search_properties(config={self.CHAMPION_TRIAL_NAME: None}, init_call=True)
- logger.debug('using random seed %s in config oracle', self._seed)
+ self.set_search_properties(
+ setting={self.CHAMPION_TRIAL_NAME: None}, init_call=True
+ )
+ logger.debug("using random seed %s in config oracle", self._seed)
- def set_search_properties(self, metric: Optional[str] = None,
- mode: Optional[str] = None,
- config: Optional[Dict] = {},
- init_call: Optional[bool] = False):
- """Construct search space with given config, and setup the search
- """
+ def set_search_properties(
+ self,
+ metric: Optional[str] = None,
+ mode: Optional[str] = None,
+ config: Optional[Dict] = {},
+ setting: Optional[Dict] = {},
+ init_call: Optional[bool] = False,
+ ):
+ """Construct search space with given config, and setup the search"""
super().set_search_properties(metric, mode, config)
# *********Use ConfigOralce (i.e, self._generate_new_space to generate list of new challengers)
- logger.info('champion trial %s', config)
- champion_trial = config.get(self.CHAMPION_TRIAL_NAME, None)
+ logger.info("setting %s", setting)
+ champion_trial = setting.get(self.CHAMPION_TRIAL_NAME, None)
if champion_trial is None:
champion_trial = self._create_trial_from_config(self._init_config)
# generate a new list of challenger trials
- new_challenger_list = self._query_config_oracle(champion_trial.config,
- champion_trial.trial_id,
- self._trialid_to_searcher_trial_id[champion_trial.trial_id])
+ new_challenger_list = self._query_config_oracle(
+ champion_trial.config,
+ champion_trial.trial_id,
+ self._trialid_to_searcher_trial_id[champion_trial.trial_id],
+ )
# add the newly generated challengers to existing challengers
# there can be duplicates and we check duplicates when calling next_trial()
self._challenger_list = self._challenger_list + new_challenger_list
# add the champion as part of the new_challenger_list when called initially
if init_call:
self._challenger_list.append(champion_trial)
- logger.critical('Created challengers from champion %s', champion_trial.trial_id)
- logger.critical('New challenger size %s, %s', len(self._challenger_list),
- [t.trial_id for t in self._challenger_list])
+ logger.info(
+ "**Important** Created challengers from champion %s",
+ champion_trial.trial_id,
+ )
+ logger.info(
+ "New challenger size %s, %s",
+ len(self._challenger_list),
+ [t.trial_id for t in self._challenger_list],
+ )
def next_trial(self):
- """Return a trial from the _challenger_list
- """
+ """Return a trial from the _challenger_list"""
next_trial = None
if self._challenger_list:
next_trial = self._challenger_list.pop()
@@ -175,8 +200,9 @@ class ChampionFrontierSearcher(BaseSearcher):
self._trialid_to_searcher_trial_id[trial.trial_id] = searcher_trial_id
return trial
- def _query_config_oracle(self, seed_config, seed_config_trial_id,
- seed_config_searcher_trial_id=None) -> List[Trial]:
+ def _query_config_oracle(
+ self, seed_config, seed_config_trial_id, seed_config_searcher_trial_id=None
+ ) -> List[Trial]:
"""Give the seed config, generate a list of new configs (which are supposed to include
at least one config that has better performance than the input seed_config)
"""
@@ -189,12 +215,16 @@ class ChampionFrontierSearcher(BaseSearcher):
config_domain = self._space[k]
if isinstance(config_domain, PolynomialExpansionSet):
# get candidate configs for hyperparameters of the PolynomialExpansionSet type
- partial_new_configs = self._generate_independent_hp_configs(k, v, config_domain)
+ partial_new_configs = self._generate_independent_hp_configs(
+ k, v, config_domain
+ )
if partial_new_configs:
hyperparameter_config_groups.append(partial_new_configs)
# does not have searcher_trial_ids
searcher_trial_ids_groups.append([])
- elif isinstance(config_domain, Float) or isinstance(config_domain, Categorical):
+ elif isinstance(config_domain, Float) or isinstance(
+ config_domain, Categorical
+ ):
# otherwise we need to deal with them in group
nonpoly_config[k] = v
if k not in self._space_of_nonpoly_hp:
@@ -204,38 +234,57 @@ class ChampionFrontierSearcher(BaseSearcher):
if nonpoly_config:
new_searcher_trial_ids = []
partial_new_nonpoly_configs = []
- if 'CFO' in self._nonpoly_searcher_name:
+ if "CFO" in self._nonpoly_searcher_name:
if seed_config_trial_id not in self._searcher_for_nonpoly_hp:
- self._searcher_for_nonpoly_hp[seed_config_trial_id] = CFO(space=self._space_of_nonpoly_hp,
- points_to_evaluate=[nonpoly_config],
- metric=self.CFO_SEARCHER_METRIC_NAME,
- )
+ self._searcher_for_nonpoly_hp[seed_config_trial_id] = CFO(
+ space=self._space_of_nonpoly_hp,
+ points_to_evaluate=[nonpoly_config],
+ metric=self.CFO_SEARCHER_METRIC_NAME,
+ )
# initialize the search in set_search_properties
- self._searcher_for_nonpoly_hp[seed_config_trial_id].set_search_properties(
- config={'metric_target': self.CFO_SEARCHER_LARGE_LOSS})
+ self._searcher_for_nonpoly_hp[
+ seed_config_trial_id
+ ].set_search_properties(
+ setting={"metric_target": self.CFO_SEARCHER_LARGE_LOSS}
+ )
# We need to call this for once, such that the seed config in points_to_evaluate will be called
# to be tried
- self._searcher_for_nonpoly_hp[seed_config_trial_id].suggest(seed_config_searcher_trial_id)
+ self._searcher_for_nonpoly_hp[seed_config_trial_id].suggest(
+ seed_config_searcher_trial_id
+ )
# assuming minimization
- if self._searcher_for_nonpoly_hp[seed_config_trial_id].metric_target is None:
+ if (
+ self._searcher_for_nonpoly_hp[seed_config_trial_id].metric_target
+ is None
+ ):
pseudo_loss = self.CFO_SEARCHER_LARGE_LOSS
else:
- pseudo_loss = self._searcher_for_nonpoly_hp[seed_config_trial_id].metric_target * 0.95
+ pseudo_loss = (
+ self._searcher_for_nonpoly_hp[
+ seed_config_trial_id
+ ].metric_target
+ * 0.95
+ )
pseudo_result_to_report = {}
for k, v in nonpoly_config.items():
- pseudo_result_to_report['config/' + str(k)] = v
+ pseudo_result_to_report["config/" + str(k)] = v
pseudo_result_to_report[self.CFO_SEARCHER_METRIC_NAME] = pseudo_loss
- pseudo_result_to_report['time_total_s'] = 1
- self._searcher_for_nonpoly_hp[seed_config_trial_id].on_trial_complete(seed_config_searcher_trial_id,
- result=pseudo_result_to_report)
+ pseudo_result_to_report["time_total_s"] = 1
+ self._searcher_for_nonpoly_hp[seed_config_trial_id].on_trial_complete(
+ seed_config_searcher_trial_id, result=pseudo_result_to_report
+ )
while len(partial_new_nonpoly_configs) < self.NUMERICAL_NUM:
# suggest multiple times
new_searcher_trial_id = Trial.generate_id()
new_searcher_trial_ids.append(new_searcher_trial_id)
- suggestion = self._searcher_for_nonpoly_hp[seed_config_trial_id].suggest(new_searcher_trial_id)
+ suggestion = self._searcher_for_nonpoly_hp[
+ seed_config_trial_id
+ ].suggest(new_searcher_trial_id)
if suggestion is not None:
partial_new_nonpoly_configs.append(suggestion)
- logger.info('partial_new_nonpoly_configs %s', partial_new_nonpoly_configs)
+ logger.info(
+ "partial_new_nonpoly_configs %s", partial_new_nonpoly_configs
+ )
else:
raise NotImplementedError
if partial_new_nonpoly_configs:
@@ -244,9 +293,11 @@ class ChampionFrontierSearcher(BaseSearcher):
# ----------- coordinate generation of new challengers in the case of multiple groups
new_trials = []
for i in range(len(hyperparameter_config_groups)):
- logger.info('hyperparameter_config_groups[i] %s %s',
- len(hyperparameter_config_groups[i]),
- hyperparameter_config_groups[i])
+ logger.info(
+ "hyperparameter_config_groups[i] %s %s",
+ len(hyperparameter_config_groups[i]),
+ hyperparameter_config_groups[i],
+ )
for j, new_partial_config in enumerate(hyperparameter_config_groups[i]):
new_seed_config = seed_config.copy()
new_seed_config.update(new_partial_config)
@@ -260,32 +311,55 @@ class ChampionFrontierSearcher(BaseSearcher):
new_searcher_trial_id = searcher_trial_ids_groups[i][j]
else:
new_searcher_trial_id = None
- new_trial = self._create_trial_from_config(new_seed_config, new_searcher_trial_id)
+ new_trial = self._create_trial_from_config(
+ new_seed_config, new_searcher_trial_id
+ )
new_trials.append(new_trial)
- logger.info('new_configs %s', [t.trial_id for t in new_trials])
+ logger.info("new_configs %s", [t.trial_id for t in new_trials])
return new_trials
- def _generate_independent_hp_configs(self, hp_name, current_config_value, config_domain) -> List:
+ def _generate_independent_hp_configs(
+ self, hp_name, current_config_value, config_domain
+ ) -> List:
if isinstance(config_domain, PolynomialExpansionSet):
- seed_interactions = list(current_config_value) + list(config_domain.init_monomials)
- logger.critical('Seed namespaces (singletons and interactions): %s', seed_interactions)
- logger.info('current_config_value %s %s', current_config_value, seed_interactions)
- configs = self._generate_poly_expansion_sets(seed_interactions,
- self.EXPANSION_ORDER,
- config_domain.allow_self_inter,
- config_domain.highest_poly_order,
- self.POLY_EXPANSION_ADDITION_NUM,
- )
+ seed_interactions = list(current_config_value) + list(
+ config_domain.init_monomials
+ )
+ logger.info(
+ "**Important** Seed namespaces (singletons and interactions): %s",
+ seed_interactions,
+ )
+ logger.info("current_config_value %s", current_config_value)
+ configs = self._generate_poly_expansion_sets(
+ seed_interactions,
+ self.EXPANSION_ORDER,
+ config_domain.allow_self_inter,
+ config_domain.highest_poly_order,
+ self.POLY_EXPANSION_ADDITION_NUM,
+ )
else:
raise NotImplementedError
configs_w_key = [{hp_name: hp_config} for hp_config in configs]
return configs_w_key
- def _generate_poly_expansion_sets(self, seed_interactions, order, allow_self_inter,
- highest_poly_order, interaction_num_to_add):
- champion_all_combinations = self._generate_all_comb(seed_interactions, order, allow_self_inter, highest_poly_order)
- space = sorted(list(itertools.combinations(
- champion_all_combinations, interaction_num_to_add)))
+ def _generate_poly_expansion_sets(
+ self,
+ seed_interactions,
+ order,
+ allow_self_inter,
+ highest_poly_order,
+ interaction_num_to_add,
+ ):
+ champion_all_combinations = self._generate_all_comb(
+ seed_interactions, order, allow_self_inter, highest_poly_order
+ )
+ space = sorted(
+ list(
+ itertools.combinations(
+ champion_all_combinations, interaction_num_to_add
+ )
+ )
+ )
self._random_state.shuffle(space)
candidate_configs = [set(seed_interactions) | set(item) for item in space]
final_candidate_configs = []
@@ -295,9 +369,12 @@ class ChampionFrontierSearcher(BaseSearcher):
return final_candidate_configs
@staticmethod
- def _generate_all_comb(seed_interactions: list, seed_interaction_order: int,
- allow_self_inter: Optional[bool] = False,
- highest_poly_order: Optional[int] = None):
+ def _generate_all_comb(
+ seed_interactions: list,
+ seed_interaction_order: int,
+ allow_self_inter: Optional[bool] = False,
+ highest_poly_order: Optional[int] = None,
+ ):
"""Generate new interactions by doing up to seed_interaction_order on the seed_interactions
Args:
@@ -312,8 +389,7 @@ class ChampionFrontierSearcher(BaseSearcher):
"""
def get_interactions(list1, list2):
- """Get combinatorial list of tuples
- """
+ """Get combinatorial list of tuples"""
new_list = []
for i in list1:
for j in list2:
@@ -321,19 +397,18 @@ class ChampionFrontierSearcher(BaseSearcher):
# 'abc' 'cba' 'bca' are all 'abc'
# this is done to ensure we can use the config as the signature
# of the trial, i.e., trial id.
- new_interaction = ''.join(sorted(i + j))
+ new_interaction = "".join(sorted(i + j))
if new_interaction not in new_list:
new_list.append(new_interaction)
return new_list
def strip_self_inter(s):
- """Remove duplicates in an interaction string
- """
+ """Remove duplicates in an interaction string"""
if len(s) == len(set(s)):
return s
else:
# return ''.join(sorted(set(s)))
- new_s = ''
+ new_s = ""
char_list = []
for i in s:
if i not in char_list:
@@ -351,10 +426,15 @@ class ChampionFrontierSearcher(BaseSearcher):
all_interactions_no_self_inter = []
for s in all_interactions:
s_no_inter = strip_self_inter(s)
- if len(s_no_inter) > 1 and s_no_inter not in all_interactions_no_self_inter:
+ if (
+ len(s_no_inter) > 1
+ and s_no_inter not in all_interactions_no_self_inter
+ ):
all_interactions_no_self_inter.append(s_no_inter)
all_interactions = all_interactions_no_self_inter
if highest_poly_order is not None:
- all_interactions = [c for c in all_interactions if len(c) <= highest_poly_order]
- logger.info('all_combinations %s', all_interactions)
+ all_interactions = [
+ c for c in all_interactions if len(c) <= highest_poly_order
+ ]
+ logger.info("all_combinations %s", all_interactions)
return all_interactions
diff --git a/flaml/searcher/search_thread.py b/flaml/searcher/search_thread.py
index f3118c753e..e04dccdf9d 100644
--- a/flaml/searcher/search_thread.py
+++ b/flaml/searcher/search_thread.py
@@ -54,7 +54,7 @@ class SearchThread:
@classmethod
def set_eps(cls, time_budget_s):
- cls._eps = max(min(time_budget_s / 1000.0, 1.0), 1e-10)
+ cls._eps = max(min(time_budget_s / 1000.0, 1.0), 1e-9)
def suggest(self, trial_id: str) -> Optional[Dict]:
''' use the suggest() of the underlying search algorithm
diff --git a/flaml/tune/README.md b/flaml/tune/README.md
index aada353da2..a0d5c6a83a 100644
--- a/flaml/tune/README.md
+++ b/flaml/tune/README.md
@@ -1,6 +1,6 @@
# Economical Hyperparameter Optimization
-`flaml.tune` is a module for economical hyperparameter tuning. It frees users from manually tuning many hyperparameters for a software, such as machine learning training procedures.
+`flaml.tune` is a module for economical hyperparameter tuning. It frees users from manually tuning many hyperparameters for a software, such as machine learning training procedures.
It can be used standalone, or together with ray tune or nni.
* Example for sequential tuning (recommended when compute resource is limited and each trial can consume all the resources):
@@ -18,8 +18,8 @@ def evaluate_config(config):
# and the cost could be related to certain hyperparameters
# in this example, we assume it's proportional to x
time.sleep(config['x']/100000)
- # use tune.report to report the metric to optimize
- tune.report(metric=metric)
+ # use tune.report to report the metric to optimize
+ tune.report(metric=metric)
analysis = tune.run(
evaluate_config, # the function to evaluate a config
@@ -33,7 +33,7 @@ analysis = tune.run(
num_samples=-1, # the maximal number of configs to try, -1 means infinite
time_budget_s=60, # the time budget in seconds
local_dir='logs/', # the local directory to store logs
- # verbose=0, # verbosity
+ # verbose=0, # verbosity
# use_ray=True, # uncomment when performing parallel tuning using ray
)
@@ -57,8 +57,8 @@ def evaluate_config(config):
# and the cost could be related to certain hyperparameters
# in this example, we assume it's proportional to x
time.sleep(config['x']/100000)
- # use tune.report to report the metric to optimize
- tune.report(metric=metric)
+ # use tune.report to report the metric to optimize
+ tune.report(metric=metric)
# provide a time budget (in seconds) for the tuning process
time_budget_s = 60
@@ -77,25 +77,25 @@ cfo = CFO(low_cost_partial_config=low_cost_partial_config)
blendsearch = BlendSearch(
metric="metric", mode="min",
space=config_search_space,
- low_cost_partial_config=low_cost_partial_config)
+ low_cost_partial_config=low_cost_partial_config,
+ time_budget_s=time_budget_s
+)
# NOTE: when using BlendSearch as a search_alg in ray tune, you need to
-# configure the 'time_budget_s' for BlendSearch accordingly as follows such that
+# configure the 'time_budget_s' for BlendSearch accordingly such that
# BlendSearch is aware of the time budget. This step is not needed when
-# BlendSearch is used as the search_alg in flaml.tune as it is already done
-# automatically in flaml. Also, this step needs to be done after the search
-# space is passed to BlendSearch and before raytune.run.
-blendsearch.set_search_properties(config={"time_budget_s": time_budget_s})
+# BlendSearch is used as the search_alg in flaml.tune as it is done
+# automatically in flaml.
analysis = raytune.run(
evaluate_config, # the function to evaluate a config
config=config_search_space,
metric='metric', # the name of the metric used for optimization
mode='min', # the optimization mode, 'min' or 'max'
- num_samples=-1, # the maximal number of configs to try, -1 means infinite
+ num_samples=-1, # the maximal number of configs to try, -1 means infinite
time_budget_s=time_budget_s, # the time budget in seconds
local_dir='logs/', # the local directory to store logs
search_alg=blendsearch # or cfo
- )
+)
print(analysis.best_trial.last_result) # the best trial's result
print(analysis.best_config) # the best config
@@ -107,11 +107,10 @@ print(analysis.best_config) # the best config
$nnictl create --config ./config.yml
```
-* For more examples, please check out
+* For more examples, please check out
[notebooks](https://github.com/microsoft/FLAML/tree/main/notebook/).
-
-`flaml` offers two HPO methods: CFO and BlendSearch.
+`flaml` offers two HPO methods: CFO and BlendSearch.
`flaml.tune` uses BlendSearch by default.
## CFO: Frugal Optimization for Cost-related Hyperparameters
@@ -121,27 +120,27 @@ $nnictl create --config ./config.yml
-CFO uses the randomized direct search method FLOW2 with adaptive stepsize and random restart.
+CFO uses the randomized direct search method FLOW2 with adaptive stepsize and random restart.
It requires a low-cost initial point as input if such point exists.
The search begins with the low-cost initial point and gradually move to
high cost region if needed. The local search method has a provable convergence
-rate and bounded cost.
+rate and bounded cost.
-About FLOW2: FLOW2 is a simple yet effective randomized direct search method.
+About FLOW2: FLOW2 is a simple yet effective randomized direct search method.
It is an iterative optimization method that can optimize for black-box functions.
FLOW2 only requires pairwise comparisons between function values to perform iterative update. Comparing to existing HPO methods, FLOW2 has the following appealing properties:
+
1. It is applicable to general black-box functions with a good convergence rate in terms of loss.
-3. It provides theoretical guarantees on the total evaluation cost incurred.
+1. It provides theoretical guarantees on the total evaluation cost incurred.
The GIFs attached below demonstrate an example search trajectory of FLOW2 shown in the loss and evaluation cost (i.e., the training time ) space respectively. From the demonstration, we can see that (1) FLOW2 can quickly move toward the low-loss region, showing good convergence property and (2) FLOW2 tends to avoid exploring the high-cost region until necessary.
-
+
Figure 1. FLOW2 in tuning the # of leaves and the # of trees for XGBoost. The two background heatmaps show the loss and cost distribution of all configurations. The black dots are the points evaluated in FLOW2. Black dots connected by lines are points that yield better loss performance when evaluated.
-
Example:
```python
@@ -152,9 +151,9 @@ tune.run(...
```
Recommended scenario: there exist cost-related hyperparameters and a low-cost
-initial point is known before optimization.
+initial point is known before optimization.
If the search space is complex and CFO gets trapped into local optima, consider
-using BlendSearch.
+using BlendSearch.
## BlendSearch: Economical Hyperparameter Optimization With Blended Search Strategy
@@ -167,7 +166,7 @@ BlendSearch combines local search with global search. It leverages the frugality
of CFO and the space exploration ability of global search methods such as
Bayesian optimization. Like CFO, BlendSearch requires a low-cost initial point
as input if such point exists, and starts the search from there. Different from
-CFO, BlendSearch will not wait for the local search to fully converge before
+CFO, BlendSearch will not wait for the local search to fully converge before
trying new start points. The new start points are suggested by the global search
method and filtered based on their distance to the existing points in the
cost-related dimensions. BlendSearch still gradually increases the trial cost.
@@ -184,19 +183,18 @@ tune.run(...
)
```
-- Recommended scenario: cost-related hyperparameters exist, a low-cost
+* Recommended scenario: cost-related hyperparameters exist, a low-cost
initial point is known, and the search space is complex such that local search
is prone to be stuck at local optima.
-
-- Suggestion about using larger search space in BlendSearch:
+* Suggestion about using larger search space in BlendSearch:
In hyperparameter optimization, a larger search space is desirable because it is more likely to include the optimal configuration (or one of the optimal configurations) in hindsight. However the performance (especially anytime performance) of most existing HPO methods is undesirable if the cost of the configurations in the search space has a large variation. Thus hand-crafted small search spaces (with relatively homogeneous cost) are often used in practice for these methods, which is subject to idiosyncrasy. BlendSearch combines the benefits of local search and global search, which enables a smart (economical) way of deciding where to explore in the search space even though it is larger than necessary. This allows users to specify a larger search space in BlendSearch, which is often easier and a better practice than narrowing down the search space by hand.
For more technical details, please check our papers.
* [Frugal Optimization for Cost-related Hyperparameters](https://arxiv.org/abs/2005.01571). Qingyun Wu, Chi Wang, Silu Huang. AAAI 2021.
-```
+```bibtex
@inproceedings{wu2021cfo,
title={Frugal Optimization for Cost-related Hyperparameters},
author={Qingyun Wu and Chi Wang and Silu Huang},
@@ -207,11 +205,11 @@ For more technical details, please check our papers.
* [Economical Hyperparameter Optimization With Blended Search Strategy](https://www.microsoft.com/en-us/research/publication/economical-hyperparameter-optimization-with-blended-search-strategy/). Chi Wang, Qingyun Wu, Silu Huang, Amin Saied. ICLR 2021.
-```
+```bibtex
@inproceedings{wang2021blendsearch,
title={Economical Hyperparameter Optimization With Blended Search Strategy},
author={Chi Wang and Qingyun Wu and Silu Huang and Amin Saied},
year={2021},
booktitle={ICLR'21},
}
-```
\ No newline at end of file
+```
diff --git a/flaml/tune/trial_runner.py b/flaml/tune/trial_runner.py
index 690e57df35..75c5181ea3 100644
--- a/flaml/tune/trial_runner.py
+++ b/flaml/tune/trial_runner.py
@@ -1,9 +1,10 @@
-'''!
- * Copyright (c) 2020-2021 Microsoft Corporation. All rights reserved.
+"""!
+ * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE file in the
* project root for license information.
-'''
+"""
from typing import Optional
+
# try:
# from ray import __version__ as ray_version
# assert ray_version >= '1.0.0'
@@ -11,20 +12,19 @@ from typing import Optional
# except (ImportError, AssertionError):
from .trial import Trial
import logging
+
logger = logging.getLogger(__name__)
-class Nologger():
- '''Logger without logging
- '''
+class Nologger:
+ """Logger without logging"""
def on_result(self, result):
pass
class SimpleTrial(Trial):
- '''A simple trial class
- '''
+ """A simple trial class"""
def __init__(self, config, trial_id=None):
self.trial_id = Trial.generate_id() if trial_id is None else trial_id
@@ -49,10 +49,13 @@ class BaseTrialRunner:
Note that the caller usually should not mutate trial state directly.
"""
- def __init__(self,
- search_alg=None, scheduler=None,
- metric: Optional[str] = None,
- mode: Optional[str] = 'min'):
+ def __init__(
+ self,
+ search_alg=None,
+ scheduler=None,
+ metric: Optional[str] = None,
+ mode: Optional[str] = "min",
+ ):
self._search_alg = search_alg
self._scheduler_alg = scheduler
self._trials = []
@@ -89,12 +92,12 @@ class BaseTrialRunner:
trial.set_status(Trial.PAUSED)
def stop_trial(self, trial):
- """Stops trial.
- """
+ """Stops trial."""
if trial.status not in [Trial.ERROR, Trial.TERMINATED]:
if self._scheduler_alg:
self._scheduler_alg.on_trial_complete(
- self, trial.trial_id, trial.last_result)
+ self, trial.trial_id, trial.last_result
+ )
self._search_alg.on_trial_complete(trial.trial_id, trial.last_result)
trial.set_status(Trial.TERMINATED)
elif self._scheduler_alg:
@@ -102,8 +105,7 @@ class BaseTrialRunner:
class SequentialTrialRunner(BaseTrialRunner):
- """Implementation of the sequential trial runner
- """
+ """Implementation of the sequential trial runner"""
def step(self) -> Trial:
"""Runs one step of the trial event loop.
@@ -114,7 +116,7 @@ class SequentialTrialRunner(BaseTrialRunner):
"""
trial_id = Trial.generate_id()
config = self._search_alg.suggest(trial_id)
- if config:
+ if config is not None:
trial = SimpleTrial(config, trial_id)
self.add_trial(trial)
trial.set_status(Trial.RUNNING)
diff --git a/flaml/tune/tune.py b/flaml/tune/tune.py
index 1d2db41ec2..22a7cbd114 100644
--- a/flaml/tune/tune.py
+++ b/flaml/tune/tune.py
@@ -13,7 +13,10 @@ try:
assert ray_version >= "1.0.0"
from ray.tune.analysis import ExperimentAnalysis as EA
+
+ ray_import = True
except (ImportError, AssertionError):
+ ray_import = False
from .analysis import ExperimentAnalysis as EA
from .result import DEFAULT_METRIC
import logging
@@ -278,9 +281,9 @@ def run(
else:
logger.setLevel(logging.CRITICAL)
- if search_alg is None:
- from ..searcher.blendsearch import BlendSearch
+ from ..searcher.blendsearch import BlendSearch
+ if search_alg is None:
search_alg = BlendSearch(
metric=metric or DEFAULT_METRIC,
mode=mode,
@@ -299,16 +302,27 @@ def run(
metric_constraints=metric_constraints,
)
else:
- search_alg.set_search_properties(metric, mode, config)
if metric is None or mode is None:
metric = metric or search_alg.metric
mode = mode or search_alg.mode
- if time_budget_s or num_samples > 0:
- search_alg.set_search_properties(
- None,
- None,
- config={"time_budget_s": time_budget_s, "num_samples": num_samples},
- )
+ if ray_import:
+ from ray.tune.suggest import ConcurrencyLimiter
+ else:
+ from flaml.searcher.suggestion import ConcurrencyLimiter
+ searcher = (
+ search_alg.searcher
+ if isinstance(search_alg, ConcurrencyLimiter)
+ else search_alg
+ )
+ if isinstance(searcher, BlendSearch):
+ setting = {}
+ if time_budget_s:
+ setting["time_budget_s"] = time_budget_s
+ if num_samples > 0:
+ setting["num_samples"] = num_samples
+ searcher.set_search_properties(metric, mode, config, setting)
+ else:
+ searcher.set_search_properties(metric, mode, config)
scheduler = None
if report_intermediate_result:
params = {}
@@ -321,15 +335,10 @@ def run(
params["grace_period"] = min_resource
if reduction_factor:
params["reduction_factor"] = reduction_factor
- try:
- from ray import __version__ as ray_version
-
- assert ray_version >= "1.0.0"
+ if ray_import:
from ray.tune.schedulers import ASHAScheduler
scheduler = ASHAScheduler(**params)
- except (ImportError, AssertionError):
- pass
if use_ray:
try:
from ray import tune
@@ -392,7 +401,9 @@ def run(
else:
fail += 1 # break with ub consecutive failures
if fail == ub:
- logger.warning("fail to sample a trial for 10 times in a row, stopping.")
+ logger.warning(
+ f"fail to sample a trial for {max_failure} times in a row, stopping."
+ )
if verbose > 0:
logger.handlers.clear()
return ExperimentAnalysis(_runner.get_trials(), metric=metric, mode=mode)
diff --git a/notebook/flaml_automl.ipynb b/notebook/flaml_automl.ipynb
index 99336f19c0..71c4b33d08 100644
--- a/notebook/flaml_automl.ipynb
+++ b/notebook/flaml_automl.ipynb
@@ -3,7 +3,7 @@
{
"cell_type": "markdown",
"source": [
- "Copyright (c) 2020-2021 Microsoft Corporation. All rights reserved. \n",
+ "Copyright (c) Microsoft Corporation. All rights reserved. \n",
"\n",
"Licensed under the MIT License.\n",
"\n",
@@ -13,8 +13,7 @@
"## 1. Introduction\n",
"\n",
"FLAML is a Python library (https://github.com/microsoft/FLAML) designed to automatically produce accurate machine learning models \n",
- "with low computational cost. It is fast and cheap. The simple and lightweight design makes it easy \n",
- "to use and extend, such as adding new learners. FLAML can \n",
+ "with low computational cost. It is fast and cheap. The simple and lightweight design makes it easy to use and extend, such as adding new learners. FLAML can \n",
"- serve as an economical AutoML engine,\n",
"- be used as a fast hyperparameter tuning tool, or \n",
"- be embedded in self-tuning software that requires low latency & resource in repetitive\n",
@@ -37,7 +36,10 @@
"cell_type": "code",
"execution_count": null,
"source": [
- "!pip install flaml[notebook];"
+ "!pip install flaml[notebook];\n",
+ "# from v0.6.6, catboost is made an optional dependency to build conda package.\n",
+ "# to install catboost, you can uncomment and run:\n",
+ "# !pip install flaml[catboost]"
],
"outputs": [],
"metadata": {}
@@ -117,7 +119,7 @@
" \"time_budget\": 240, # total running time in seconds\n",
" \"metric\": 'accuracy', # can be: 'r2', 'rmse', 'mae', 'mse', 'accuracy', 'roc_auc', 'roc_auc_ovr',\n",
" # 'roc_auc_ovo', 'log_loss', 'mape', 'f1', 'ap', 'ndcg', 'micro_f1', 'macro_f1'\n",
- " \"task\": 'classification', # task type \n",
+ " \"task\": 'classification', # task type\n",
" \"log_file_name\": 'airlines_experiment.log', # flaml log file\n",
" \"seed\": 7654321, # random seed\n",
"}"
@@ -141,270 +143,166 @@
"output_type": "stream",
"name": "stderr",
"text": [
- "[flaml.automl: 08-31 00:53:33] {1279} INFO - Evaluation method: holdout\n",
- "[flaml.automl: 08-31 00:53:34] {1312} INFO - Minimizing error metric: 1-accuracy\n",
- "[flaml.automl: 08-31 00:53:34] {1338} INFO - List of ML learners in AutoML Run: ['lgbm', 'rf', 'catboost', 'xgboost', 'extra_tree', 'lrl1']\n",
- "[flaml.automl: 08-31 00:53:34] {1532} INFO - iteration 0, current learner lgbm\n",
- "[flaml.automl: 08-31 00:53:34] {1689} INFO - at 1.4s,\tbest lgbm's error=0.3777,\tbest lgbm's error=0.3777\n",
- "[flaml.automl: 08-31 00:53:34] {1532} INFO - iteration 1, current learner lgbm\n",
- "[flaml.automl: 08-31 00:53:34] {1689} INFO - at 1.6s,\tbest lgbm's error=0.3777,\tbest lgbm's error=0.3777\n",
- "[flaml.automl: 08-31 00:53:34] {1532} INFO - iteration 2, current learner lgbm\n",
- "[flaml.automl: 08-31 00:53:34] {1689} INFO - at 1.7s,\tbest lgbm's error=0.3777,\tbest lgbm's error=0.3777\n",
- "[flaml.automl: 08-31 00:53:34] {1532} INFO - iteration 3, current learner lgbm\n",
- "[flaml.automl: 08-31 00:53:34] {1689} INFO - at 1.9s,\tbest lgbm's error=0.3661,\tbest lgbm's error=0.3661\n",
- "[flaml.automl: 08-31 00:53:34] {1532} INFO - iteration 4, current learner xgboost\n",
- "[flaml.automl: 08-31 00:53:35] {1689} INFO - at 2.1s,\tbest xgboost's error=0.3787,\tbest lgbm's error=0.3661\n",
- "[flaml.automl: 08-31 00:53:35] {1532} INFO - iteration 5, current learner xgboost\n",
- "[flaml.automl: 08-31 00:53:35] {1689} INFO - at 2.2s,\tbest xgboost's error=0.3769,\tbest lgbm's error=0.3661\n",
- "[flaml.automl: 08-31 00:53:35] {1532} INFO - iteration 6, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:53:35] {1689} INFO - at 2.4s,\tbest extra_tree's error=0.3788,\tbest lgbm's error=0.3661\n",
- "[flaml.automl: 08-31 00:53:35] {1532} INFO - iteration 7, current learner lgbm\n",
- "[flaml.automl: 08-31 00:53:35] {1689} INFO - at 2.5s,\tbest lgbm's error=0.3645,\tbest lgbm's error=0.3645\n",
- "[flaml.automl: 08-31 00:53:35] {1532} INFO - iteration 8, current learner lgbm\n",
- "[flaml.automl: 08-31 00:53:35] {1689} INFO - at 2.7s,\tbest lgbm's error=0.3645,\tbest lgbm's error=0.3645\n",
- "[flaml.automl: 08-31 00:53:35] {1532} INFO - iteration 9, current learner lgbm\n",
- "[flaml.automl: 08-31 00:53:35] {1689} INFO - at 2.8s,\tbest lgbm's error=0.3645,\tbest lgbm's error=0.3645\n",
- "[flaml.automl: 08-31 00:53:35] {1532} INFO - iteration 10, current learner lgbm\n",
- "[flaml.automl: 08-31 00:53:35] {1689} INFO - at 3.0s,\tbest lgbm's error=0.3610,\tbest lgbm's error=0.3610\n",
- "[flaml.automl: 08-31 00:53:35] {1532} INFO - iteration 11, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:53:36] {1689} INFO - at 3.2s,\tbest extra_tree's error=0.3763,\tbest lgbm's error=0.3610\n",
- "[flaml.automl: 08-31 00:53:36] {1532} INFO - iteration 12, current learner rf\n",
- "[flaml.automl: 08-31 00:53:36] {1689} INFO - at 3.4s,\tbest rf's error=0.3787,\tbest lgbm's error=0.3610\n",
- "[flaml.automl: 08-31 00:53:36] {1532} INFO - iteration 13, current learner rf\n",
- "[flaml.automl: 08-31 00:53:36] {1689} INFO - at 3.5s,\tbest rf's error=0.3689,\tbest lgbm's error=0.3610\n",
- "[flaml.automl: 08-31 00:53:36] {1532} INFO - iteration 14, current learner rf\n",
- "[flaml.automl: 08-31 00:53:36] {1689} INFO - at 3.7s,\tbest rf's error=0.3689,\tbest lgbm's error=0.3610\n",
- "[flaml.automl: 08-31 00:53:36] {1532} INFO - iteration 15, current learner rf\n",
- "[flaml.automl: 08-31 00:53:36] {1689} INFO - at 4.0s,\tbest rf's error=0.3689,\tbest lgbm's error=0.3610\n",
- "[flaml.automl: 08-31 00:53:36] {1532} INFO - iteration 16, current learner lgbm\n",
- "[flaml.automl: 08-31 00:53:37] {1689} INFO - at 4.2s,\tbest lgbm's error=0.3610,\tbest lgbm's error=0.3610\n",
- "[flaml.automl: 08-31 00:53:37] {1532} INFO - iteration 17, current learner xgboost\n",
- "[flaml.automl: 08-31 00:53:37] {1689} INFO - at 4.3s,\tbest xgboost's error=0.3765,\tbest lgbm's error=0.3610\n",
- "[flaml.automl: 08-31 00:53:37] {1532} INFO - iteration 18, current learner lgbm\n",
- "[flaml.automl: 08-31 00:53:37] {1689} INFO - at 4.9s,\tbest lgbm's error=0.3610,\tbest lgbm's error=0.3610\n",
- "[flaml.automl: 08-31 00:53:37] {1532} INFO - iteration 19, current learner rf\n",
- "[flaml.automl: 08-31 00:53:38] {1689} INFO - at 5.1s,\tbest rf's error=0.3689,\tbest lgbm's error=0.3610\n",
- "[flaml.automl: 08-31 00:53:38] {1532} INFO - iteration 20, current learner lgbm\n",
- "[flaml.automl: 08-31 00:53:38] {1689} INFO - at 5.3s,\tbest lgbm's error=0.3610,\tbest lgbm's error=0.3610\n",
- "[flaml.automl: 08-31 00:53:38] {1532} INFO - iteration 21, current learner rf\n",
- "[flaml.automl: 08-31 00:53:38] {1689} INFO - at 5.6s,\tbest rf's error=0.3689,\tbest lgbm's error=0.3610\n",
- "[flaml.automl: 08-31 00:53:38] {1532} INFO - iteration 22, current learner lgbm\n",
- "[flaml.automl: 08-31 00:53:38] {1689} INFO - at 6.0s,\tbest lgbm's error=0.3604,\tbest lgbm's error=0.3604\n",
- "[flaml.automl: 08-31 00:53:38] {1532} INFO - iteration 23, current learner lgbm\n",
- "[flaml.automl: 08-31 00:53:39] {1689} INFO - at 6.5s,\tbest lgbm's error=0.3545,\tbest lgbm's error=0.3545\n",
- "[flaml.automl: 08-31 00:53:39] {1532} INFO - iteration 24, current learner rf\n",
- "[flaml.automl: 08-31 00:53:39] {1689} INFO - at 6.8s,\tbest rf's error=0.3631,\tbest lgbm's error=0.3545\n",
- "[flaml.automl: 08-31 00:53:39] {1532} INFO - iteration 25, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:53:39] {1689} INFO - at 7.0s,\tbest extra_tree's error=0.3763,\tbest lgbm's error=0.3545\n",
- "[flaml.automl: 08-31 00:53:39] {1532} INFO - iteration 26, current learner lgbm\n",
- "[flaml.automl: 08-31 00:53:40] {1689} INFO - at 7.5s,\tbest lgbm's error=0.3523,\tbest lgbm's error=0.3523\n",
- "[flaml.automl: 08-31 00:53:40] {1532} INFO - iteration 27, current learner lgbm\n",
- "[flaml.automl: 08-31 00:53:40] {1689} INFO - at 8.0s,\tbest lgbm's error=0.3523,\tbest lgbm's error=0.3523\n",
- "[flaml.automl: 08-31 00:53:40] {1532} INFO - iteration 28, current learner rf\n",
- "[flaml.automl: 08-31 00:53:41] {1689} INFO - at 8.5s,\tbest rf's error=0.3621,\tbest lgbm's error=0.3523\n",
- "[flaml.automl: 08-31 00:53:41] {1532} INFO - iteration 29, current learner rf\n",
- "[flaml.automl: 08-31 00:53:41] {1689} INFO - at 8.7s,\tbest rf's error=0.3621,\tbest lgbm's error=0.3523\n",
- "[flaml.automl: 08-31 00:53:41] {1532} INFO - iteration 30, current learner lgbm\n",
- "[flaml.automl: 08-31 00:53:42] {1689} INFO - at 9.2s,\tbest lgbm's error=0.3523,\tbest lgbm's error=0.3523\n",
- "[flaml.automl: 08-31 00:53:42] {1532} INFO - iteration 31, current learner lgbm\n",
- "[flaml.automl: 08-31 00:53:42] {1689} INFO - at 9.7s,\tbest lgbm's error=0.3523,\tbest lgbm's error=0.3523\n",
- "[flaml.automl: 08-31 00:53:42] {1532} INFO - iteration 32, current learner lgbm\n",
- "[flaml.automl: 08-31 00:53:43] {1689} INFO - at 10.2s,\tbest lgbm's error=0.3523,\tbest lgbm's error=0.3523\n",
- "[flaml.automl: 08-31 00:53:43] {1532} INFO - iteration 33, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:53:43] {1689} INFO - at 10.4s,\tbest extra_tree's error=0.3763,\tbest lgbm's error=0.3523\n",
- "[flaml.automl: 08-31 00:53:43] {1532} INFO - iteration 34, current learner lgbm\n",
- "[flaml.automl: 08-31 00:53:46] {1689} INFO - at 13.3s,\tbest lgbm's error=0.3475,\tbest lgbm's error=0.3475\n",
- "[flaml.automl: 08-31 00:53:46] {1532} INFO - iteration 35, current learner catboost\n",
- "[flaml.automl: 08-31 00:53:46] {1689} INFO - at 13.9s,\tbest catboost's error=0.3602,\tbest lgbm's error=0.3475\n",
- "[flaml.automl: 08-31 00:53:46] {1532} INFO - iteration 36, current learner catboost\n",
- "[flaml.automl: 08-31 00:53:47] {1689} INFO - at 14.4s,\tbest catboost's error=0.3602,\tbest lgbm's error=0.3475\n",
- "[flaml.automl: 08-31 00:53:47] {1532} INFO - iteration 37, current learner xgboost\n",
- "[flaml.automl: 08-31 00:53:47] {1689} INFO - at 14.5s,\tbest xgboost's error=0.3746,\tbest lgbm's error=0.3475\n",
- "[flaml.automl: 08-31 00:53:47] {1532} INFO - iteration 38, current learner catboost\n",
- "[flaml.automl: 08-31 00:53:47] {1689} INFO - at 14.8s,\tbest catboost's error=0.3602,\tbest lgbm's error=0.3475\n",
- "[flaml.automl: 08-31 00:53:47] {1532} INFO - iteration 39, current learner catboost\n",
- "[flaml.automl: 08-31 00:53:48] {1689} INFO - at 15.4s,\tbest catboost's error=0.3602,\tbest lgbm's error=0.3475\n",
- "[flaml.automl: 08-31 00:53:48] {1532} INFO - iteration 40, current learner catboost\n",
- "[flaml.automl: 08-31 00:53:48] {1689} INFO - at 15.6s,\tbest catboost's error=0.3602,\tbest lgbm's error=0.3475\n",
- "[flaml.automl: 08-31 00:53:48] {1532} INFO - iteration 41, current learner catboost\n",
- "[flaml.automl: 08-31 00:53:50] {1689} INFO - at 17.4s,\tbest catboost's error=0.3493,\tbest lgbm's error=0.3475\n",
- "[flaml.automl: 08-31 00:53:50] {1532} INFO - iteration 42, current learner xgboost\n",
- "[flaml.automl: 08-31 00:53:50] {1689} INFO - at 17.6s,\tbest xgboost's error=0.3673,\tbest lgbm's error=0.3475\n",
- "[flaml.automl: 08-31 00:53:50] {1532} INFO - iteration 43, current learner lgbm\n",
- "[flaml.automl: 08-31 00:53:52] {1689} INFO - at 19.8s,\tbest lgbm's error=0.3475,\tbest lgbm's error=0.3475\n",
- "[flaml.automl: 08-31 00:53:52] {1532} INFO - iteration 44, current learner xgboost\n",
- "[flaml.automl: 08-31 00:53:52] {1689} INFO - at 19.9s,\tbest xgboost's error=0.3673,\tbest lgbm's error=0.3475\n",
- "[flaml.automl: 08-31 00:53:52] {1532} INFO - iteration 45, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:53:53] {1689} INFO - at 20.1s,\tbest extra_tree's error=0.3755,\tbest lgbm's error=0.3475\n",
- "[flaml.automl: 08-31 00:53:53] {1532} INFO - iteration 46, current learner xgboost\n",
- "[flaml.automl: 08-31 00:53:53] {1689} INFO - at 20.3s,\tbest xgboost's error=0.3617,\tbest lgbm's error=0.3475\n",
- "[flaml.automl: 08-31 00:53:53] {1532} INFO - iteration 47, current learner catboost\n",
- "[flaml.automl: 08-31 00:53:55] {1689} INFO - at 22.6s,\tbest catboost's error=0.3469,\tbest catboost's error=0.3469\n",
- "[flaml.automl: 08-31 00:53:55] {1532} INFO - iteration 48, current learner xgboost\n",
- "[flaml.automl: 08-31 00:53:55] {1689} INFO - at 22.8s,\tbest xgboost's error=0.3617,\tbest catboost's error=0.3469\n",
- "[flaml.automl: 08-31 00:53:55] {1532} INFO - iteration 49, current learner lgbm\n",
- "[flaml.automl: 08-31 00:54:00] {1689} INFO - at 27.4s,\tbest lgbm's error=0.3455,\tbest lgbm's error=0.3455\n",
- "[flaml.automl: 08-31 00:54:00] {1532} INFO - iteration 50, current learner catboost\n",
- "[flaml.automl: 08-31 00:54:01] {1689} INFO - at 29.0s,\tbest catboost's error=0.3469,\tbest lgbm's error=0.3455\n",
- "[flaml.automl: 08-31 00:54:01] {1532} INFO - iteration 51, current learner xgboost\n",
- "[flaml.automl: 08-31 00:54:02] {1689} INFO - at 29.2s,\tbest xgboost's error=0.3617,\tbest lgbm's error=0.3455\n",
- "[flaml.automl: 08-31 00:54:02] {1532} INFO - iteration 52, current learner catboost\n",
- "[flaml.automl: 08-31 00:54:03] {1689} INFO - at 30.3s,\tbest catboost's error=0.3469,\tbest lgbm's error=0.3455\n",
- "[flaml.automl: 08-31 00:54:03] {1532} INFO - iteration 53, current learner rf\n",
- "[flaml.automl: 08-31 00:54:03] {1689} INFO - at 30.7s,\tbest rf's error=0.3621,\tbest lgbm's error=0.3455\n",
- "[flaml.automl: 08-31 00:54:03] {1532} INFO - iteration 54, current learner lgbm\n",
- "[flaml.automl: 08-31 00:54:10] {1689} INFO - at 37.2s,\tbest lgbm's error=0.3444,\tbest lgbm's error=0.3444\n",
- "[flaml.automl: 08-31 00:54:10] {1532} INFO - iteration 55, current learner lgbm\n",
- "[flaml.automl: 08-31 00:54:14] {1689} INFO - at 41.7s,\tbest lgbm's error=0.3444,\tbest lgbm's error=0.3444\n",
- "[flaml.automl: 08-31 00:54:14] {1532} INFO - iteration 56, current learner xgboost\n",
- "[flaml.automl: 08-31 00:54:14] {1689} INFO - at 41.9s,\tbest xgboost's error=0.3591,\tbest lgbm's error=0.3444\n",
- "[flaml.automl: 08-31 00:54:14] {1532} INFO - iteration 57, current learner lgbm\n",
- "[flaml.automl: 08-31 00:54:22] {1689} INFO - at 49.3s,\tbest lgbm's error=0.3444,\tbest lgbm's error=0.3444\n",
- "[flaml.automl: 08-31 00:54:22] {1532} INFO - iteration 58, current learner rf\n",
- "[flaml.automl: 08-31 00:54:22] {1689} INFO - at 49.8s,\tbest rf's error=0.3567,\tbest lgbm's error=0.3444\n",
- "[flaml.automl: 08-31 00:54:22] {1532} INFO - iteration 59, current learner rf\n",
- "[flaml.automl: 08-31 00:54:23] {1689} INFO - at 50.2s,\tbest rf's error=0.3567,\tbest lgbm's error=0.3444\n",
- "[flaml.automl: 08-31 00:54:23] {1532} INFO - iteration 60, current learner lgbm\n",
- "[flaml.automl: 08-31 00:54:28] {1689} INFO - at 55.4s,\tbest lgbm's error=0.3356,\tbest lgbm's error=0.3356\n",
- "[flaml.automl: 08-31 00:54:28] {1532} INFO - iteration 61, current learner catboost\n",
- "[flaml.automl: 08-31 00:54:32] {1689} INFO - at 59.4s,\tbest catboost's error=0.3469,\tbest lgbm's error=0.3356\n",
- "[flaml.automl: 08-31 00:54:32] {1532} INFO - iteration 62, current learner lgbm\n",
- "[flaml.automl: 08-31 00:54:37] {1689} INFO - at 64.7s,\tbest lgbm's error=0.3300,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:54:37] {1532} INFO - iteration 63, current learner rf\n",
- "[flaml.automl: 08-31 00:54:38] {1689} INFO - at 65.4s,\tbest rf's error=0.3565,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:54:38] {1532} INFO - iteration 64, current learner xgboost\n",
- "[flaml.automl: 08-31 00:54:38] {1689} INFO - at 65.6s,\tbest xgboost's error=0.3591,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:54:38] {1532} INFO - iteration 65, current learner lgbm\n",
- "[flaml.automl: 08-31 00:54:43] {1689} INFO - at 70.4s,\tbest lgbm's error=0.3300,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:54:43] {1532} INFO - iteration 66, current learner lgbm\n",
- "[flaml.automl: 08-31 00:54:54] {1689} INFO - at 81.3s,\tbest lgbm's error=0.3300,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:54:54] {1532} INFO - iteration 67, current learner xgboost\n",
- "[flaml.automl: 08-31 00:54:54] {1689} INFO - at 81.5s,\tbest xgboost's error=0.3591,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:54:54] {1532} INFO - iteration 68, current learner lgbm\n",
- "[flaml.automl: 08-31 00:54:57] {1689} INFO - at 84.9s,\tbest lgbm's error=0.3300,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:54:57] {1532} INFO - iteration 69, current learner lgbm\n",
- "[flaml.automl: 08-31 00:55:01] {1689} INFO - at 88.3s,\tbest lgbm's error=0.3300,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:01] {1532} INFO - iteration 70, current learner xgboost\n",
- "[flaml.automl: 08-31 00:55:01] {1689} INFO - at 88.5s,\tbest xgboost's error=0.3591,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:01] {1532} INFO - iteration 71, current learner xgboost\n",
- "[flaml.automl: 08-31 00:55:01] {1689} INFO - at 88.6s,\tbest xgboost's error=0.3591,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:01] {1532} INFO - iteration 72, current learner catboost\n",
- "[flaml.automl: 08-31 00:55:04] {1689} INFO - at 91.8s,\tbest catboost's error=0.3469,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:04] {1532} INFO - iteration 73, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:55:05] {1689} INFO - at 92.1s,\tbest extra_tree's error=0.3755,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:05] {1532} INFO - iteration 74, current learner xgboost\n",
- "[flaml.automl: 08-31 00:55:05] {1689} INFO - at 92.3s,\tbest xgboost's error=0.3591,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:05] {1532} INFO - iteration 75, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:55:05] {1689} INFO - at 92.5s,\tbest extra_tree's error=0.3644,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:05] {1532} INFO - iteration 76, current learner xgboost\n",
- "[flaml.automl: 08-31 00:55:05] {1689} INFO - at 92.9s,\tbest xgboost's error=0.3574,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:05] {1532} INFO - iteration 77, current learner xgboost\n",
- "[flaml.automl: 08-31 00:55:06] {1689} INFO - at 93.3s,\tbest xgboost's error=0.3574,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:06] {1532} INFO - iteration 78, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:55:06] {1689} INFO - at 93.5s,\tbest extra_tree's error=0.3644,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:06] {1532} INFO - iteration 79, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:55:06] {1689} INFO - at 93.7s,\tbest extra_tree's error=0.3644,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:06] {1532} INFO - iteration 80, current learner rf\n",
- "[flaml.automl: 08-31 00:55:07] {1689} INFO - at 94.3s,\tbest rf's error=0.3565,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:07] {1532} INFO - iteration 81, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:55:07] {1689} INFO - at 94.5s,\tbest extra_tree's error=0.3644,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:07] {1532} INFO - iteration 82, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:55:07] {1689} INFO - at 94.7s,\tbest extra_tree's error=0.3644,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:07] {1532} INFO - iteration 83, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:55:07] {1689} INFO - at 94.9s,\tbest extra_tree's error=0.3644,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:07] {1532} INFO - iteration 84, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:55:08] {1689} INFO - at 95.2s,\tbest extra_tree's error=0.3644,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:08] {1532} INFO - iteration 85, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:55:08] {1689} INFO - at 95.5s,\tbest extra_tree's error=0.3640,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:08] {1532} INFO - iteration 86, current learner rf\n",
- "[flaml.automl: 08-31 00:55:09] {1689} INFO - at 96.5s,\tbest rf's error=0.3565,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:09] {1532} INFO - iteration 87, current learner xgboost\n",
- "[flaml.automl: 08-31 00:55:09] {1689} INFO - at 96.9s,\tbest xgboost's error=0.3534,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:09] {1532} INFO - iteration 88, current learner xgboost\n",
- "[flaml.automl: 08-31 00:55:10] {1689} INFO - at 97.2s,\tbest xgboost's error=0.3534,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:10] {1532} INFO - iteration 89, current learner lgbm\n",
- "[flaml.automl: 08-31 00:55:19] {1689} INFO - at 106.4s,\tbest lgbm's error=0.3300,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:19] {1532} INFO - iteration 90, current learner xgboost\n",
- "[flaml.automl: 08-31 00:55:20] {1689} INFO - at 107.3s,\tbest xgboost's error=0.3504,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:20] {1532} INFO - iteration 91, current learner xgboost\n",
- "[flaml.automl: 08-31 00:55:20] {1689} INFO - at 107.8s,\tbest xgboost's error=0.3504,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:20] {1532} INFO - iteration 92, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:55:20] {1689} INFO - at 108.0s,\tbest extra_tree's error=0.3624,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:20] {1532} INFO - iteration 93, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:55:21] {1689} INFO - at 108.3s,\tbest extra_tree's error=0.3624,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:21] {1532} INFO - iteration 94, current learner xgboost\n",
- "[flaml.automl: 08-31 00:55:24] {1689} INFO - at 111.2s,\tbest xgboost's error=0.3504,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:24] {1532} INFO - iteration 95, current learner lgbm\n",
- "[flaml.automl: 08-31 00:55:27] {1689} INFO - at 114.6s,\tbest lgbm's error=0.3300,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:27] {1532} INFO - iteration 96, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:55:27] {1689} INFO - at 114.8s,\tbest extra_tree's error=0.3598,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:27] {1532} INFO - iteration 97, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:55:28] {1689} INFO - at 115.2s,\tbest extra_tree's error=0.3598,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:28] {1532} INFO - iteration 98, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:55:28] {1689} INFO - at 115.4s,\tbest extra_tree's error=0.3597,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:28] {1532} INFO - iteration 99, current learner lrl1\n",
- "No low-cost partial config given to the search algorithm. For cost-frugal search, consider providing low-cost values for cost-related hps via 'low_cost_partial_config'.\n",
- "/home/dmx/miniconda2/envs/blend/lib/python3.8/site-packages/sklearn/linear_model/_sag.py:328: ConvergenceWarning: The max_iter was reached which means the coef_ did not converge\n",
+ "[flaml.automl: 10-08 15:12:49] {1458} INFO - Data split method: stratified\n",
+ "[flaml.automl: 10-08 15:12:49] {1462} INFO - Evaluation method: holdout\n",
+ "[flaml.automl: 10-08 15:12:49] {1510} INFO - Minimizing error metric: 1-accuracy\n",
+ "[flaml.automl: 10-08 15:12:49] {1547} INFO - List of ML learners in AutoML Run: ['lgbm', 'rf', 'xgboost', 'extra_tree', 'lrl1']\n",
+ "[flaml.automl: 10-08 15:12:49] {1777} INFO - iteration 0, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:12:50] {1894} INFO - Estimated sufficient time budget=318171s. Estimated necessary time budget=5298s.\n",
+ "[flaml.automl: 10-08 15:12:50] {1966} INFO - at 1.8s,\testimator lgbm's best error=0.3777,\tbest estimator lgbm's best error=0.3777\n",
+ "[flaml.automl: 10-08 15:12:50] {1777} INFO - iteration 1, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:12:51] {1966} INFO - at 2.4s,\testimator lgbm's best error=0.3759,\tbest estimator lgbm's best error=0.3759\n",
+ "[flaml.automl: 10-08 15:12:51] {1777} INFO - iteration 2, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:12:53] {1966} INFO - at 4.7s,\testimator lgbm's best error=0.3759,\tbest estimator lgbm's best error=0.3759\n",
+ "[flaml.automl: 10-08 15:12:53] {1777} INFO - iteration 3, current learner xgboost\n",
+ "[flaml.automl: 10-08 15:12:53] {1966} INFO - at 5.0s,\testimator xgboost's best error=0.3787,\tbest estimator lgbm's best error=0.3759\n",
+ "[flaml.automl: 10-08 15:12:53] {1777} INFO - iteration 4, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:12:54] {1966} INFO - at 5.1s,\testimator lgbm's best error=0.3644,\tbest estimator lgbm's best error=0.3644\n",
+ "[flaml.automl: 10-08 15:12:54] {1777} INFO - iteration 5, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:12:54] {1966} INFO - at 5.3s,\testimator lgbm's best error=0.3588,\tbest estimator lgbm's best error=0.3588\n",
+ "[flaml.automl: 10-08 15:12:54] {1777} INFO - iteration 6, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:12:54] {1966} INFO - at 5.4s,\testimator lgbm's best error=0.3588,\tbest estimator lgbm's best error=0.3588\n",
+ "[flaml.automl: 10-08 15:12:54] {1777} INFO - iteration 7, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:12:54] {1966} INFO - at 5.9s,\testimator lgbm's best error=0.3555,\tbest estimator lgbm's best error=0.3555\n",
+ "[flaml.automl: 10-08 15:12:54] {1777} INFO - iteration 8, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:12:55] {1966} INFO - at 6.2s,\testimator lgbm's best error=0.3555,\tbest estimator lgbm's best error=0.3555\n",
+ "[flaml.automl: 10-08 15:12:55] {1777} INFO - iteration 9, current learner xgboost\n",
+ "[flaml.automl: 10-08 15:12:55] {1966} INFO - at 6.3s,\testimator xgboost's best error=0.3649,\tbest estimator lgbm's best error=0.3555\n",
+ "[flaml.automl: 10-08 15:12:55] {1777} INFO - iteration 10, current learner xgboost\n",
+ "[flaml.automl: 10-08 15:12:55] {1966} INFO - at 6.4s,\testimator xgboost's best error=0.3649,\tbest estimator lgbm's best error=0.3555\n",
+ "[flaml.automl: 10-08 15:12:55] {1777} INFO - iteration 11, current learner xgboost\n",
+ "[flaml.automl: 10-08 15:12:55] {1966} INFO - at 6.6s,\testimator xgboost's best error=0.3649,\tbest estimator lgbm's best error=0.3555\n",
+ "[flaml.automl: 10-08 15:12:55] {1777} INFO - iteration 12, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:12:56] {1966} INFO - at 7.7s,\testimator lgbm's best error=0.3555,\tbest estimator lgbm's best error=0.3555\n",
+ "[flaml.automl: 10-08 15:12:56] {1777} INFO - iteration 13, current learner xgboost\n",
+ "[flaml.automl: 10-08 15:12:56] {1966} INFO - at 7.8s,\testimator xgboost's best error=0.3629,\tbest estimator lgbm's best error=0.3555\n",
+ "[flaml.automl: 10-08 15:12:56] {1777} INFO - iteration 14, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:12:58] {1966} INFO - at 9.2s,\testimator lgbm's best error=0.3555,\tbest estimator lgbm's best error=0.3555\n",
+ "[flaml.automl: 10-08 15:12:58] {1777} INFO - iteration 15, current learner extra_tree\n",
+ "[flaml.automl: 10-08 15:12:58] {1966} INFO - at 9.4s,\testimator extra_tree's best error=0.3773,\tbest estimator lgbm's best error=0.3555\n",
+ "[flaml.automl: 10-08 15:12:58] {1777} INFO - iteration 16, current learner extra_tree\n",
+ "[flaml.automl: 10-08 15:12:58] {1966} INFO - at 9.5s,\testimator extra_tree's best error=0.3757,\tbest estimator lgbm's best error=0.3555\n",
+ "[flaml.automl: 10-08 15:12:58] {1777} INFO - iteration 17, current learner rf\n",
+ "[flaml.automl: 10-08 15:12:58] {1966} INFO - at 9.7s,\testimator rf's best error=0.3765,\tbest estimator lgbm's best error=0.3555\n",
+ "[flaml.automl: 10-08 15:12:58] {1777} INFO - iteration 18, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:12:59] {1966} INFO - at 10.7s,\testimator lgbm's best error=0.3542,\tbest estimator lgbm's best error=0.3542\n",
+ "[flaml.automl: 10-08 15:12:59] {1777} INFO - iteration 19, current learner rf\n",
+ "[flaml.automl: 10-08 15:12:59] {1966} INFO - at 10.9s,\testimator rf's best error=0.3724,\tbest estimator lgbm's best error=0.3542\n",
+ "[flaml.automl: 10-08 15:12:59] {1777} INFO - iteration 20, current learner rf\n",
+ "[flaml.automl: 10-08 15:13:00] {1966} INFO - at 11.1s,\testimator rf's best error=0.3724,\tbest estimator lgbm's best error=0.3542\n",
+ "[flaml.automl: 10-08 15:13:00] {1777} INFO - iteration 21, current learner xgboost\n",
+ "[flaml.automl: 10-08 15:13:00] {1966} INFO - at 11.2s,\testimator xgboost's best error=0.3629,\tbest estimator lgbm's best error=0.3542\n",
+ "[flaml.automl: 10-08 15:13:00] {1777} INFO - iteration 22, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:13:02] {1966} INFO - at 13.2s,\testimator lgbm's best error=0.3507,\tbest estimator lgbm's best error=0.3507\n",
+ "[flaml.automl: 10-08 15:13:02] {1777} INFO - iteration 23, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:13:03] {1966} INFO - at 14.1s,\testimator lgbm's best error=0.3507,\tbest estimator lgbm's best error=0.3507\n",
+ "[flaml.automl: 10-08 15:13:03] {1777} INFO - iteration 24, current learner xgboost\n",
+ "[flaml.automl: 10-08 15:13:03] {1966} INFO - at 14.2s,\testimator xgboost's best error=0.3612,\tbest estimator lgbm's best error=0.3507\n",
+ "[flaml.automl: 10-08 15:13:03] {1777} INFO - iteration 25, current learner extra_tree\n",
+ "[flaml.automl: 10-08 15:13:03] {1966} INFO - at 14.4s,\testimator extra_tree's best error=0.3757,\tbest estimator lgbm's best error=0.3507\n",
+ "[flaml.automl: 10-08 15:13:03] {1777} INFO - iteration 26, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:13:07] {1966} INFO - at 19.0s,\testimator lgbm's best error=0.3507,\tbest estimator lgbm's best error=0.3507\n",
+ "[flaml.automl: 10-08 15:13:07] {1777} INFO - iteration 27, current learner xgboost\n",
+ "[flaml.automl: 10-08 15:13:08] {1966} INFO - at 19.1s,\testimator xgboost's best error=0.3612,\tbest estimator lgbm's best error=0.3507\n",
+ "[flaml.automl: 10-08 15:13:08] {1777} INFO - iteration 28, current learner extra_tree\n",
+ "[flaml.automl: 10-08 15:13:08] {1966} INFO - at 19.3s,\testimator extra_tree's best error=0.3757,\tbest estimator lgbm's best error=0.3507\n",
+ "[flaml.automl: 10-08 15:13:08] {1777} INFO - iteration 29, current learner xgboost\n",
+ "[flaml.automl: 10-08 15:13:08] {1966} INFO - at 19.5s,\testimator xgboost's best error=0.3612,\tbest estimator lgbm's best error=0.3507\n",
+ "[flaml.automl: 10-08 15:13:08] {1777} INFO - iteration 30, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:13:09] {1966} INFO - at 20.9s,\testimator lgbm's best error=0.3507,\tbest estimator lgbm's best error=0.3507\n",
+ "[flaml.automl: 10-08 15:13:09] {1777} INFO - iteration 31, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:13:11] {1966} INFO - at 22.5s,\testimator lgbm's best error=0.3507,\tbest estimator lgbm's best error=0.3507\n",
+ "[flaml.automl: 10-08 15:13:11] {1777} INFO - iteration 32, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:13:23] {1966} INFO - at 34.3s,\testimator lgbm's best error=0.3404,\tbest estimator lgbm's best error=0.3404\n",
+ "[flaml.automl: 10-08 15:13:23] {1777} INFO - iteration 33, current learner extra_tree\n",
+ "[flaml.automl: 10-08 15:13:23] {1966} INFO - at 34.5s,\testimator extra_tree's best error=0.3757,\tbest estimator lgbm's best error=0.3404\n",
+ "[flaml.automl: 10-08 15:13:23] {1777} INFO - iteration 34, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:13:44] {1966} INFO - at 55.1s,\testimator lgbm's best error=0.3343,\tbest estimator lgbm's best error=0.3343\n",
+ "[flaml.automl: 10-08 15:13:44] {1777} INFO - iteration 35, current learner rf\n",
+ "[flaml.automl: 10-08 15:13:44] {1966} INFO - at 55.2s,\testimator rf's best error=0.3724,\tbest estimator lgbm's best error=0.3343\n",
+ "[flaml.automl: 10-08 15:13:44] {1777} INFO - iteration 36, current learner extra_tree\n",
+ "[flaml.automl: 10-08 15:13:44] {1966} INFO - at 55.4s,\testimator extra_tree's best error=0.3757,\tbest estimator lgbm's best error=0.3343\n",
+ "[flaml.automl: 10-08 15:13:44] {1777} INFO - iteration 37, current learner rf\n",
+ "[flaml.automl: 10-08 15:13:44] {1966} INFO - at 55.5s,\testimator rf's best error=0.3724,\tbest estimator lgbm's best error=0.3343\n",
+ "[flaml.automl: 10-08 15:13:44] {1777} INFO - iteration 38, current learner xgboost\n",
+ "[flaml.automl: 10-08 15:13:44] {1966} INFO - at 55.7s,\testimator xgboost's best error=0.3612,\tbest estimator lgbm's best error=0.3343\n",
+ "[flaml.automl: 10-08 15:13:44] {1777} INFO - iteration 39, current learner rf\n",
+ "[flaml.automl: 10-08 15:13:44] {1966} INFO - at 56.0s,\testimator rf's best error=0.3719,\tbest estimator lgbm's best error=0.3343\n",
+ "[flaml.automl: 10-08 15:13:44] {1777} INFO - iteration 40, current learner xgboost\n",
+ "[flaml.automl: 10-08 15:13:45] {1966} INFO - at 56.3s,\testimator xgboost's best error=0.3600,\tbest estimator lgbm's best error=0.3343\n",
+ "[flaml.automl: 10-08 15:13:45] {1777} INFO - iteration 41, current learner extra_tree\n",
+ "[flaml.automl: 10-08 15:13:45] {1966} INFO - at 56.5s,\testimator extra_tree's best error=0.3757,\tbest estimator lgbm's best error=0.3343\n",
+ "[flaml.automl: 10-08 15:13:45] {1777} INFO - iteration 42, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:13:56] {1966} INFO - at 67.1s,\testimator lgbm's best error=0.3343,\tbest estimator lgbm's best error=0.3343\n",
+ "[flaml.automl: 10-08 15:13:56] {1777} INFO - iteration 43, current learner xgboost\n",
+ "[flaml.automl: 10-08 15:13:56] {1966} INFO - at 67.4s,\testimator xgboost's best error=0.3558,\tbest estimator lgbm's best error=0.3343\n",
+ "[flaml.automl: 10-08 15:13:56] {1777} INFO - iteration 44, current learner extra_tree\n",
+ "[flaml.automl: 10-08 15:13:56] {1966} INFO - at 67.5s,\testimator extra_tree's best error=0.3757,\tbest estimator lgbm's best error=0.3343\n",
+ "[flaml.automl: 10-08 15:13:56] {1777} INFO - iteration 45, current learner xgboost\n",
+ "[flaml.automl: 10-08 15:13:56] {1966} INFO - at 67.8s,\testimator xgboost's best error=0.3558,\tbest estimator lgbm's best error=0.3343\n",
+ "[flaml.automl: 10-08 15:13:56] {1777} INFO - iteration 46, current learner xgboost\n",
+ "[flaml.automl: 10-08 15:13:57] {1966} INFO - at 68.3s,\testimator xgboost's best error=0.3558,\tbest estimator lgbm's best error=0.3343\n",
+ "[flaml.automl: 10-08 15:13:57] {1777} INFO - iteration 47, current learner xgboost\n",
+ "[flaml.automl: 10-08 15:13:57] {1966} INFO - at 68.6s,\testimator xgboost's best error=0.3558,\tbest estimator lgbm's best error=0.3343\n",
+ "[flaml.automl: 10-08 15:13:57] {1777} INFO - iteration 48, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:14:08] {1966} INFO - at 79.8s,\testimator lgbm's best error=0.3296,\tbest estimator lgbm's best error=0.3296\n",
+ "[flaml.automl: 10-08 15:14:08] {1777} INFO - iteration 49, current learner xgboost\n",
+ "[flaml.automl: 10-08 15:14:11] {1966} INFO - at 82.2s,\testimator xgboost's best error=0.3544,\tbest estimator lgbm's best error=0.3296\n",
+ "[flaml.automl: 10-08 15:14:11] {1777} INFO - iteration 50, current learner extra_tree\n",
+ "[flaml.automl: 10-08 15:14:11] {1966} INFO - at 82.4s,\testimator extra_tree's best error=0.3753,\tbest estimator lgbm's best error=0.3296\n",
+ "[flaml.automl: 10-08 15:14:11] {1777} INFO - iteration 51, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:14:34] {1966} INFO - at 105.2s,\testimator lgbm's best error=0.3296,\tbest estimator lgbm's best error=0.3296\n",
+ "[flaml.automl: 10-08 15:14:34] {1777} INFO - iteration 52, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:14:43] {1966} INFO - at 114.1s,\testimator lgbm's best error=0.3296,\tbest estimator lgbm's best error=0.3296\n",
+ "[flaml.automl: 10-08 15:14:43] {1777} INFO - iteration 53, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:15:04] {1966} INFO - at 135.7s,\testimator lgbm's best error=0.3296,\tbest estimator lgbm's best error=0.3296\n",
+ "[flaml.automl: 10-08 15:15:04] {1777} INFO - iteration 54, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:15:09] {1966} INFO - at 140.0s,\testimator lgbm's best error=0.3296,\tbest estimator lgbm's best error=0.3296\n",
+ "[flaml.automl: 10-08 15:15:09] {1777} INFO - iteration 55, current learner xgboost\n",
+ "[flaml.automl: 10-08 15:15:12] {1966} INFO - at 143.5s,\testimator xgboost's best error=0.3494,\tbest estimator lgbm's best error=0.3296\n",
+ "[flaml.automl: 10-08 15:15:12] {1777} INFO - iteration 56, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:16:03] {1966} INFO - at 194.7s,\testimator lgbm's best error=0.3296,\tbest estimator lgbm's best error=0.3296\n",
+ "[flaml.automl: 10-08 15:16:03] {1777} INFO - iteration 57, current learner rf\n",
+ "[flaml.automl: 10-08 15:16:03] {1966} INFO - at 194.9s,\testimator rf's best error=0.3717,\tbest estimator lgbm's best error=0.3296\n",
+ "[flaml.automl: 10-08 15:16:03] {1777} INFO - iteration 58, current learner xgboost\n",
+ "[flaml.automl: 10-08 15:16:05] {1966} INFO - at 196.9s,\testimator xgboost's best error=0.3494,\tbest estimator lgbm's best error=0.3296\n",
+ "[flaml.automl: 10-08 15:16:05] {1777} INFO - iteration 59, current learner lgbm\n",
+ "[flaml.automl: 10-08 15:16:45] {1966} INFO - at 236.4s,\testimator lgbm's best error=0.3296,\tbest estimator lgbm's best error=0.3296\n",
+ "[flaml.automl: 10-08 15:16:45] {1777} INFO - iteration 60, current learner xgboost\n",
+ "[flaml.automl: 10-08 15:16:47] {1966} INFO - at 238.8s,\testimator xgboost's best error=0.3494,\tbest estimator lgbm's best error=0.3296\n",
+ "[flaml.automl: 10-08 15:16:47] {1777} INFO - iteration 61, current learner rf\n",
+ "[flaml.automl: 10-08 15:16:47] {1966} INFO - at 238.8s,\testimator rf's best error=0.3717,\tbest estimator lgbm's best error=0.3296\n",
+ "[flaml.automl: 10-08 15:16:47] {1777} INFO - iteration 62, current learner rf\n",
+ "[flaml.automl: 10-08 15:16:47] {1966} INFO - at 238.9s,\testimator rf's best error=0.3717,\tbest estimator lgbm's best error=0.3296\n",
+ "[flaml.automl: 10-08 15:16:47] {1777} INFO - iteration 63, current learner lrl1\n",
+ "/home/dmx/miniconda2/envs/test/lib/python3.8/site-packages/sklearn/linear_model/_sag.py:328: ConvergenceWarning: The max_iter was reached which means the coef_ did not converge\n",
" warnings.warn(\"The max_iter was reached which means \"\n",
- "[flaml.automl: 08-31 00:55:28] {1689} INFO - at 115.8s,\tbest lrl1's error=0.4338,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:28] {1532} INFO - iteration 100, current learner lrl1\n",
- "/home/dmx/miniconda2/envs/blend/lib/python3.8/site-packages/sklearn/linear_model/_sag.py:328: ConvergenceWarning: The max_iter was reached which means the coef_ did not converge\n",
+ "[flaml.automl: 10-08 15:16:48] {1966} INFO - at 239.2s,\testimator lrl1's best error=0.4339,\tbest estimator lgbm's best error=0.3296\n",
+ "[flaml.automl: 10-08 15:16:48] {1777} INFO - iteration 64, current learner lrl1\n",
+ "/home/dmx/miniconda2/envs/test/lib/python3.8/site-packages/sklearn/linear_model/_sag.py:328: ConvergenceWarning: The max_iter was reached which means the coef_ did not converge\n",
" warnings.warn(\"The max_iter was reached which means \"\n",
- "[flaml.automl: 08-31 00:55:29] {1689} INFO - at 116.1s,\tbest lrl1's error=0.4338,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:29] {1532} INFO - iteration 101, current learner lrl1\n",
- "/home/dmx/miniconda2/envs/blend/lib/python3.8/site-packages/sklearn/linear_model/_sag.py:328: ConvergenceWarning: The max_iter was reached which means the coef_ did not converge\n",
- " warnings.warn(\"The max_iter was reached which means \"\n",
- "[flaml.automl: 08-31 00:55:29] {1689} INFO - at 116.4s,\tbest lrl1's error=0.4338,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:29] {1532} INFO - iteration 102, current learner lrl1\n",
- "/home/dmx/miniconda2/envs/blend/lib/python3.8/site-packages/sklearn/linear_model/_sag.py:328: ConvergenceWarning: The max_iter was reached which means the coef_ did not converge\n",
- " warnings.warn(\"The max_iter was reached which means \"\n",
- "[flaml.automl: 08-31 00:55:30] {1689} INFO - at 117.3s,\tbest lrl1's error=0.4334,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:30] {1532} INFO - iteration 103, current learner catboost\n",
- "[flaml.automl: 08-31 00:55:42] {1689} INFO - at 130.0s,\tbest catboost's error=0.3385,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:42] {1532} INFO - iteration 104, current learner lgbm\n",
- "[flaml.automl: 08-31 00:55:52] {1689} INFO - at 139.2s,\tbest lgbm's error=0.3300,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:55:52] {1532} INFO - iteration 105, current learner catboost\n",
- "[flaml.automl: 08-31 00:56:44] {1689} INFO - at 191.8s,\tbest catboost's error=0.3385,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:56:44] {1532} INFO - iteration 106, current learner lgbm\n",
- "[flaml.automl: 08-31 00:56:49] {1689} INFO - at 196.9s,\tbest lgbm's error=0.3300,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:56:49] {1532} INFO - iteration 107, current learner catboost\n",
- "[flaml.automl: 08-31 00:56:56] {1689} INFO - at 203.1s,\tbest catboost's error=0.3378,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:56:56] {1532} INFO - iteration 108, current learner xgboost\n",
- "[flaml.automl: 08-31 00:56:56] {1689} INFO - at 203.8s,\tbest xgboost's error=0.3504,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:56:56] {1532} INFO - iteration 109, current learner lgbm\n",
- "[flaml.automl: 08-31 00:57:02] {1689} INFO - at 209.3s,\tbest lgbm's error=0.3300,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:57:02] {1532} INFO - iteration 110, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:57:02] {1689} INFO - at 209.8s,\tbest extra_tree's error=0.3580,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:57:02] {1532} INFO - iteration 111, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:57:03] {1689} INFO - at 210.2s,\tbest extra_tree's error=0.3555,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:57:03] {1532} INFO - iteration 112, current learner lgbm\n",
- "[flaml.automl: 08-31 00:57:21] {1689} INFO - at 228.3s,\tbest lgbm's error=0.3300,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:57:21] {1532} INFO - iteration 113, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:57:21] {1689} INFO - at 228.6s,\tbest extra_tree's error=0.3555,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:57:21] {1532} INFO - iteration 114, current learner extra_tree\n",
- "[flaml.automl: 08-31 00:57:21] {1689} INFO - at 229.0s,\tbest extra_tree's error=0.3555,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:57:21] {1532} INFO - iteration 115, current learner lrl1\n",
- "/home/dmx/miniconda2/envs/blend/lib/python3.8/site-packages/sklearn/linear_model/_sag.py:328: ConvergenceWarning: The max_iter was reached which means the coef_ did not converge\n",
- " warnings.warn(\"The max_iter was reached which means \"\n",
- "[flaml.automl: 08-31 00:57:22] {1689} INFO - at 229.9s,\tbest lrl1's error=0.4334,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:57:22] {1532} INFO - iteration 116, current learner lgbm\n",
- "[flaml.automl: 08-31 00:57:25] {1689} INFO - at 232.4s,\tbest lgbm's error=0.3300,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:57:25] {1532} INFO - iteration 117, current learner lgbm\n",
- "[flaml.automl: 08-31 00:57:34] {1689} INFO - at 241.7s,\tbest lgbm's error=0.3300,\tbest lgbm's error=0.3300\n",
- "[flaml.automl: 08-31 00:57:34] {1766} INFO - selected model: LGBMClassifier(colsample_bytree=0.3841266992710469,\n",
- " learning_rate=0.04886499949999022, max_bin=512,\n",
- " min_child_samples=4, n_estimators=220, num_leaves=270,\n",
- " objective='binary', reg_alpha=0.0009765625,\n",
- " reg_lambda=0.07539015928723636, verbose=-1)\n",
- "[flaml.automl: 08-31 00:57:43] {1814} INFO - retrain lgbm for 9.1s\n",
- "[flaml.automl: 08-31 00:57:43] {1817} INFO - retrained model: LGBMClassifier(colsample_bytree=0.3841266992710469,\n",
- " learning_rate=0.04886499949999022, max_bin=512,\n",
- " min_child_samples=4, n_estimators=220, num_leaves=270,\n",
- " objective='binary', reg_alpha=0.0009765625,\n",
- " reg_lambda=0.07539015928723636, verbose=-1)\n",
- "[flaml.automl: 08-31 00:57:43] {1364} INFO - fit succeeded\n",
- "[flaml.automl: 08-31 00:57:43] {1365} INFO - Time taken to find the best model: 241.71747207641602\n",
- "[flaml.automl: 08-31 00:57:43] {1370} WARNING - Time taken to find the best model is 101% of the provided time budget and not all estimators' hyperparameter search converged. Consider increasing the time budget.\n"
+ "[flaml.automl: 10-08 15:16:48] {1966} INFO - at 239.5s,\testimator lrl1's best error=0.4339,\tbest estimator lgbm's best error=0.3296\n",
+ "[flaml.automl: 10-08 15:16:48] {1777} INFO - iteration 65, current learner rf\n",
+ "[flaml.automl: 10-08 15:16:48] {1966} INFO - at 239.6s,\testimator rf's best error=0.3717,\tbest estimator lgbm's best error=0.3296\n",
+ "[flaml.automl: 10-08 15:16:48] {1777} INFO - iteration 66, current learner rf\n",
+ "[flaml.automl: 10-08 15:16:48] {1966} INFO - at 239.6s,\testimator rf's best error=0.3717,\tbest estimator lgbm's best error=0.3296\n",
+ "[flaml.automl: 10-08 15:16:48] {1777} INFO - iteration 67, current learner extra_tree\n",
+ "[flaml.automl: 10-08 15:16:48] {1966} INFO - at 239.7s,\testimator extra_tree's best error=0.3753,\tbest estimator lgbm's best error=0.3296\n",
+ "[flaml.automl: 10-08 15:16:48] {1777} INFO - iteration 68, current learner rf\n",
+ "[flaml.automl: 10-08 15:16:48] {1966} INFO - at 239.8s,\testimator rf's best error=0.3717,\tbest estimator lgbm's best error=0.3296\n",
+ "[flaml.automl: 10-08 15:16:48] {2073} INFO - selected model: LGBMClassifier(colsample_bytree=0.7263265270618353,\n",
+ " learning_rate=0.19240592731562936, max_bin=511,\n",
+ " min_child_samples=101, n_estimators=334, num_leaves=50,\n",
+ " reg_alpha=0.042474252908075376, reg_lambda=0.44574701224719,\n",
+ " verbose=-1)\n",
+ "[flaml.automl: 10-08 15:16:59] {2136} INFO - retrain lgbm for 10.5s\n",
+ "[flaml.automl: 10-08 15:16:59] {2142} INFO - retrained model: LGBMClassifier(colsample_bytree=0.7263265270618353,\n",
+ " learning_rate=0.19240592731562936, max_bin=511,\n",
+ " min_child_samples=101, n_estimators=334, num_leaves=50,\n",
+ " reg_alpha=0.042474252908075376, reg_lambda=0.44574701224719,\n",
+ " verbose=-1)\n",
+ "[flaml.automl: 10-08 15:16:59] {1571} INFO - fit succeeded\n",
+ "[flaml.automl: 10-08 15:16:59] {1572} INFO - Time taken to find the best model: 79.82886719703674\n"
]
}
],
@@ -444,9 +342,9 @@
"name": "stdout",
"text": [
"Best ML leaner: lgbm\n",
- "Best hyperparmeter config: {'n_estimators': 220, 'num_leaves': 270, 'min_child_samples': 4, 'learning_rate': 0.04886499949999022, 'log_max_bin': 10, 'colsample_bytree': 0.3841266992710469, 'reg_alpha': 0.0009765625, 'reg_lambda': 0.07539015928723636, 'FLAML_sample_size': 364083}\n",
- "Best accuracy on validation data: 0.67\n",
- "Training duration of best run: 9.323 s\n"
+ "Best hyperparmeter config: {'n_estimators': 334, 'num_leaves': 50, 'min_child_samples': 101, 'learning_rate': 0.19240592731562936, 'log_max_bin': 9, 'colsample_bytree': 0.7263265270618353, 'reg_alpha': 0.042474252908075376, 'reg_lambda': 0.44574701224719, 'FLAML_sample_size': 364083}\n",
+ "Best accuracy on validation data: 0.6704\n",
+ "Training duration of best run: 11.24 s\n"
]
}
],
@@ -468,11 +366,11 @@
"output_type": "execute_result",
"data": {
"text/plain": [
- "LGBMClassifier(colsample_bytree=0.3841266992710469,\n",
- " learning_rate=0.04886499949999022, max_bin=512,\n",
- " min_child_samples=4, n_estimators=220, num_leaves=270,\n",
- " objective='binary', reg_alpha=0.0009765625,\n",
- " reg_lambda=0.07539015928723636, verbose=-1)"
+ "LGBMClassifier(colsample_bytree=0.7263265270618353,\n",
+ " learning_rate=0.19240592731562936, max_bin=511,\n",
+ " min_child_samples=101, n_estimators=334, num_leaves=50,\n",
+ " reg_alpha=0.042474252908075376, reg_lambda=0.44574701224719,\n",
+ " verbose=-1)"
]
},
"metadata": {},
@@ -555,9 +453,9 @@
"output_type": "stream",
"name": "stdout",
"text": [
- "accuracy = 0.6729231864497278\n",
- "roc_auc = 0.7261961112785199\n",
- "log_loss = 0.6033707263741326\n"
+ "accuracy = 0.6713287750470908\n",
+ "roc_auc = 0.7249878990284184\n",
+ "log_loss = 0.6035815508574605\n"
]
}
],
@@ -597,18 +495,15 @@
"name": "stdout",
"text": [
"{'Current Learner': 'lgbm', 'Current Sample': 10000, 'Current Hyper-parameters': {'n_estimators': 4, 'num_leaves': 4, 'min_child_samples': 20, 'learning_rate': 0.09999999999999995, 'log_max_bin': 8, 'colsample_bytree': 1.0, 'reg_alpha': 0.0009765625, 'reg_lambda': 1.0, 'FLAML_sample_size': 10000}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 4, 'num_leaves': 4, 'min_child_samples': 20, 'learning_rate': 0.09999999999999995, 'log_max_bin': 8, 'colsample_bytree': 1.0, 'reg_alpha': 0.0009765625, 'reg_lambda': 1.0, 'FLAML_sample_size': 10000}}\n",
- "{'Current Learner': 'lgbm', 'Current Sample': 10000, 'Current Hyper-parameters': {'n_estimators': 12, 'num_leaves': 4, 'min_child_samples': 15, 'learning_rate': 0.2712162364070373, 'log_max_bin': 10, 'colsample_bytree': 0.9285002286474459, 'reg_alpha': 0.002668211515123386, 'reg_lambda': 0.5215467339232843, 'FLAML_sample_size': 10000}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 12, 'num_leaves': 4, 'min_child_samples': 15, 'learning_rate': 0.2712162364070373, 'log_max_bin': 10, 'colsample_bytree': 0.9285002286474459, 'reg_alpha': 0.002668211515123386, 'reg_lambda': 0.5215467339232843, 'FLAML_sample_size': 10000}}\n",
- "{'Current Learner': 'lgbm', 'Current Sample': 10000, 'Current Hyper-parameters': {'n_estimators': 14, 'num_leaves': 5, 'min_child_samples': 9, 'learning_rate': 0.2835381908967212, 'log_max_bin': 9, 'colsample_bytree': 0.8304072431299575, 'reg_alpha': 0.0014132988481787994, 'reg_lambda': 0.033183495034912504, 'FLAML_sample_size': 10000}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 14, 'num_leaves': 5, 'min_child_samples': 9, 'learning_rate': 0.2835381908967212, 'log_max_bin': 9, 'colsample_bytree': 0.8304072431299575, 'reg_alpha': 0.0014132988481787994, 'reg_lambda': 0.033183495034912504, 'FLAML_sample_size': 10000}}\n",
- "{'Current Learner': 'lgbm', 'Current Sample': 10000, 'Current Hyper-parameters': {'n_estimators': 53, 'num_leaves': 4, 'min_child_samples': 7, 'learning_rate': 0.15662398373030859, 'log_max_bin': 10, 'colsample_bytree': 0.7610534336273627, 'reg_alpha': 0.0009765625, 'reg_lambda': 0.0064258982194552745, 'FLAML_sample_size': 10000}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 53, 'num_leaves': 4, 'min_child_samples': 7, 'learning_rate': 0.15662398373030859, 'log_max_bin': 10, 'colsample_bytree': 0.7610534336273627, 'reg_alpha': 0.0009765625, 'reg_lambda': 0.0064258982194552745, 'FLAML_sample_size': 10000}}\n",
- "{'Current Learner': 'lgbm', 'Current Sample': 40000, 'Current Hyper-parameters': {'n_estimators': 53, 'num_leaves': 4, 'min_child_samples': 7, 'learning_rate': 0.15662398373030859, 'log_max_bin': 10, 'colsample_bytree': 0.7610534336273627, 'reg_alpha': 0.0009765625, 'reg_lambda': 0.0064258982194552745, 'FLAML_sample_size': 40000}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 53, 'num_leaves': 4, 'min_child_samples': 7, 'learning_rate': 0.15662398373030859, 'log_max_bin': 10, 'colsample_bytree': 0.7610534336273627, 'reg_alpha': 0.0009765625, 'reg_lambda': 0.0064258982194552745, 'FLAML_sample_size': 40000}}\n",
- "{'Current Learner': 'lgbm', 'Current Sample': 40000, 'Current Hyper-parameters': {'n_estimators': 89, 'num_leaves': 4, 'min_child_samples': 6, 'learning_rate': 0.2915468353191124, 'log_max_bin': 10, 'colsample_bytree': 0.8291836310024803, 'reg_alpha': 0.0009765625, 'reg_lambda': 0.022917008702549507, 'FLAML_sample_size': 40000}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 89, 'num_leaves': 4, 'min_child_samples': 6, 'learning_rate': 0.2915468353191124, 'log_max_bin': 10, 'colsample_bytree': 0.8291836310024803, 'reg_alpha': 0.0009765625, 'reg_lambda': 0.022917008702549507, 'FLAML_sample_size': 40000}}\n",
- "{'Current Learner': 'lgbm', 'Current Sample': 40000, 'Current Hyper-parameters': {'n_estimators': 53, 'num_leaves': 19, 'min_child_samples': 7, 'learning_rate': 0.15662398373030845, 'log_max_bin': 9, 'colsample_bytree': 0.7610534336273627, 'reg_alpha': 0.006958608037974516, 'reg_lambda': 0.0064258982194552745, 'FLAML_sample_size': 40000}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 53, 'num_leaves': 19, 'min_child_samples': 7, 'learning_rate': 0.15662398373030845, 'log_max_bin': 9, 'colsample_bytree': 0.7610534336273627, 'reg_alpha': 0.006958608037974516, 'reg_lambda': 0.0064258982194552745, 'FLAML_sample_size': 40000}}\n",
- "{'Current Learner': 'lgbm', 'Current Sample': 364083, 'Current Hyper-parameters': {'n_estimators': 53, 'num_leaves': 19, 'min_child_samples': 7, 'learning_rate': 0.15662398373030845, 'log_max_bin': 9, 'colsample_bytree': 0.7610534336273627, 'reg_alpha': 0.006958608037974516, 'reg_lambda': 0.0064258982194552745, 'FLAML_sample_size': 364083}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 53, 'num_leaves': 19, 'min_child_samples': 7, 'learning_rate': 0.15662398373030845, 'log_max_bin': 9, 'colsample_bytree': 0.7610534336273627, 'reg_alpha': 0.006958608037974516, 'reg_lambda': 0.0064258982194552745, 'FLAML_sample_size': 364083}}\n",
- "{'Current Learner': 'catboost', 'Current Sample': 40000, 'Current Hyper-parameters': {'early_stopping_rounds': 11, 'learning_rate': 0.0943289179113066, 'FLAML_sample_size': 40000}, 'Best Learner': 'catboost', 'Best Hyper-parameters': {'early_stopping_rounds': 11, 'learning_rate': 0.0943289179113066, 'FLAML_sample_size': 40000}}\n",
- "{'Current Learner': 'lgbm', 'Current Sample': 364083, 'Current Hyper-parameters': {'n_estimators': 112, 'num_leaves': 42, 'min_child_samples': 6, 'learning_rate': 0.05081630216512539, 'log_max_bin': 8, 'colsample_bytree': 0.6140029119098487, 'reg_alpha': 0.0043659867548350275, 'reg_lambda': 0.00988067243709054, 'FLAML_sample_size': 364083}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 112, 'num_leaves': 42, 'min_child_samples': 6, 'learning_rate': 0.05081630216512539, 'log_max_bin': 8, 'colsample_bytree': 0.6140029119098487, 'reg_alpha': 0.0043659867548350275, 'reg_lambda': 0.00988067243709054, 'FLAML_sample_size': 364083}}\n",
- "{'Current Learner': 'lgbm', 'Current Sample': 364083, 'Current Hyper-parameters': {'n_estimators': 224, 'num_leaves': 19, 'min_child_samples': 3, 'learning_rate': 0.06277721620788371, 'log_max_bin': 9, 'colsample_bytree': 0.6641909243388362, 'reg_alpha': 0.02046640007359354, 'reg_lambda': 0.021186644668220945, 'FLAML_sample_size': 364083}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 224, 'num_leaves': 19, 'min_child_samples': 3, 'learning_rate': 0.06277721620788371, 'log_max_bin': 9, 'colsample_bytree': 0.6641909243388362, 'reg_alpha': 0.02046640007359354, 'reg_lambda': 0.021186644668220945, 'FLAML_sample_size': 364083}}\n",
- "{'Current Learner': 'lgbm', 'Current Sample': 364083, 'Current Hyper-parameters': {'n_estimators': 159, 'num_leaves': 54, 'min_child_samples': 3, 'learning_rate': 0.15480526013767984, 'log_max_bin': 9, 'colsample_bytree': 0.4565737938156385, 'reg_alpha': 0.014844095616079196, 'reg_lambda': 0.01191495998733094, 'FLAML_sample_size': 364083}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 159, 'num_leaves': 54, 'min_child_samples': 3, 'learning_rate': 0.15480526013767984, 'log_max_bin': 9, 'colsample_bytree': 0.4565737938156385, 'reg_alpha': 0.014844095616079196, 'reg_lambda': 0.01191495998733094, 'FLAML_sample_size': 364083}}\n",
- "{'Current Learner': 'lgbm', 'Current Sample': 364083, 'Current Hyper-parameters': {'n_estimators': 100, 'num_leaves': 310, 'min_child_samples': 2, 'learning_rate': 0.0958942820044505, 'log_max_bin': 10, 'colsample_bytree': 0.40118043723920377, 'reg_alpha': 0.006532533034382694, 'reg_lambda': 0.02014962736208268, 'FLAML_sample_size': 364083}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 100, 'num_leaves': 310, 'min_child_samples': 2, 'learning_rate': 0.0958942820044505, 'log_max_bin': 10, 'colsample_bytree': 0.40118043723920377, 'reg_alpha': 0.006532533034382694, 'reg_lambda': 0.02014962736208268, 'FLAML_sample_size': 364083}}\n"
+ "{'Current Learner': 'lgbm', 'Current Sample': 10000, 'Current Hyper-parameters': {'n_estimators': 4, 'num_leaves': 14, 'min_child_samples': 15, 'learning_rate': 0.22841390623808822, 'log_max_bin': 9, 'colsample_bytree': 1.0, 'reg_alpha': 0.0014700173967242716, 'reg_lambda': 7.624911621832711, 'FLAML_sample_size': 10000}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 4, 'num_leaves': 14, 'min_child_samples': 15, 'learning_rate': 0.22841390623808822, 'log_max_bin': 9, 'colsample_bytree': 1.0, 'reg_alpha': 0.0014700173967242716, 'reg_lambda': 7.624911621832711, 'FLAML_sample_size': 10000}}\n",
+ "{'Current Learner': 'lgbm', 'Current Sample': 10000, 'Current Hyper-parameters': {'n_estimators': 4, 'num_leaves': 25, 'min_child_samples': 12, 'learning_rate': 0.5082200481556802, 'log_max_bin': 8, 'colsample_bytree': 0.9696263001275751, 'reg_alpha': 0.0028107036379524425, 'reg_lambda': 3.716898117989413, 'FLAML_sample_size': 10000}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 4, 'num_leaves': 25, 'min_child_samples': 12, 'learning_rate': 0.5082200481556802, 'log_max_bin': 8, 'colsample_bytree': 0.9696263001275751, 'reg_alpha': 0.0028107036379524425, 'reg_lambda': 3.716898117989413, 'FLAML_sample_size': 10000}}\n",
+ "{'Current Learner': 'lgbm', 'Current Sample': 10000, 'Current Hyper-parameters': {'n_estimators': 23, 'num_leaves': 14, 'min_child_samples': 15, 'learning_rate': 0.22841390623808822, 'log_max_bin': 9, 'colsample_bytree': 1.0, 'reg_alpha': 0.0014700173967242718, 'reg_lambda': 7.624911621832699, 'FLAML_sample_size': 10000}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 23, 'num_leaves': 14, 'min_child_samples': 15, 'learning_rate': 0.22841390623808822, 'log_max_bin': 9, 'colsample_bytree': 1.0, 'reg_alpha': 0.0014700173967242718, 'reg_lambda': 7.624911621832699, 'FLAML_sample_size': 10000}}\n",
+ "{'Current Learner': 'lgbm', 'Current Sample': 10000, 'Current Hyper-parameters': {'n_estimators': 101, 'num_leaves': 12, 'min_child_samples': 24, 'learning_rate': 0.07647794276357095, 'log_max_bin': 10, 'colsample_bytree': 1.0, 'reg_alpha': 0.001749539645587163, 'reg_lambda': 4.373760956394571, 'FLAML_sample_size': 10000}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 101, 'num_leaves': 12, 'min_child_samples': 24, 'learning_rate': 0.07647794276357095, 'log_max_bin': 10, 'colsample_bytree': 1.0, 'reg_alpha': 0.001749539645587163, 'reg_lambda': 4.373760956394571, 'FLAML_sample_size': 10000}}\n",
+ "{'Current Learner': 'lgbm', 'Current Sample': 40000, 'Current Hyper-parameters': {'n_estimators': 101, 'num_leaves': 12, 'min_child_samples': 24, 'learning_rate': 0.07647794276357095, 'log_max_bin': 10, 'colsample_bytree': 1.0, 'reg_alpha': 0.001749539645587163, 'reg_lambda': 4.373760956394571, 'FLAML_sample_size': 40000}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 101, 'num_leaves': 12, 'min_child_samples': 24, 'learning_rate': 0.07647794276357095, 'log_max_bin': 10, 'colsample_bytree': 1.0, 'reg_alpha': 0.001749539645587163, 'reg_lambda': 4.373760956394571, 'FLAML_sample_size': 40000}}\n",
+ "{'Current Learner': 'lgbm', 'Current Sample': 40000, 'Current Hyper-parameters': {'n_estimators': 361, 'num_leaves': 11, 'min_child_samples': 32, 'learning_rate': 0.13528717598813866, 'log_max_bin': 9, 'colsample_bytree': 0.9851977789068981, 'reg_alpha': 0.0038372002422749616, 'reg_lambda': 0.25113531892556773, 'FLAML_sample_size': 40000}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 361, 'num_leaves': 11, 'min_child_samples': 32, 'learning_rate': 0.13528717598813866, 'log_max_bin': 9, 'colsample_bytree': 0.9851977789068981, 'reg_alpha': 0.0038372002422749616, 'reg_lambda': 0.25113531892556773, 'FLAML_sample_size': 40000}}\n",
+ "{'Current Learner': 'lgbm', 'Current Sample': 364083, 'Current Hyper-parameters': {'n_estimators': 361, 'num_leaves': 11, 'min_child_samples': 32, 'learning_rate': 0.13528717598813866, 'log_max_bin': 9, 'colsample_bytree': 0.9851977789068981, 'reg_alpha': 0.0038372002422749616, 'reg_lambda': 0.25113531892556773, 'FLAML_sample_size': 364083}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 361, 'num_leaves': 11, 'min_child_samples': 32, 'learning_rate': 0.13528717598813866, 'log_max_bin': 9, 'colsample_bytree': 0.9851977789068981, 'reg_alpha': 0.0038372002422749616, 'reg_lambda': 0.25113531892556773, 'FLAML_sample_size': 364083}}\n",
+ "{'Current Learner': 'lgbm', 'Current Sample': 364083, 'Current Hyper-parameters': {'n_estimators': 654, 'num_leaves': 27, 'min_child_samples': 61, 'learning_rate': 0.0705835177602005, 'log_max_bin': 10, 'colsample_bytree': 0.8629551479851468, 'reg_alpha': 0.016562972790870267, 'reg_lambda': 0.25883390536609663, 'FLAML_sample_size': 364083}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 654, 'num_leaves': 27, 'min_child_samples': 61, 'learning_rate': 0.0705835177602005, 'log_max_bin': 10, 'colsample_bytree': 0.8629551479851468, 'reg_alpha': 0.016562972790870267, 'reg_lambda': 0.25883390536609663, 'FLAML_sample_size': 364083}}\n",
+ "{'Current Learner': 'lgbm', 'Current Sample': 364083, 'Current Hyper-parameters': {'n_estimators': 334, 'num_leaves': 50, 'min_child_samples': 101, 'learning_rate': 0.19240592731562936, 'log_max_bin': 9, 'colsample_bytree': 0.7263265270618353, 'reg_alpha': 0.042474252908075376, 'reg_lambda': 0.44574701224719, 'FLAML_sample_size': 364083}, 'Best Learner': 'lgbm', 'Best Hyper-parameters': {'n_estimators': 334, 'num_leaves': 50, 'min_child_samples': 101, 'learning_rate': 0.19240592731562936, 'log_max_bin': 9, 'colsample_bytree': 0.7263265270618353, 'reg_alpha': 0.042474252908075376, 'reg_lambda': 0.44574701224719, 'FLAML_sample_size': 364083}}\n"
]
}
],
@@ -637,11 +532,10 @@
{
"output_type": "display_data",
"data": {
+ "image/png": "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",
"text/plain": [
"