Automated Machine Learning (AutoML) Search¶
Background¶
Machine Learning¶
Machine learning (ML) is the process of constructing a mathematical model of a system based on a sample dataset collected from that system.
One of the main goals of training an ML model is to teach the model to separate the signal present in the data from the noise inherent in system and in the data collection process. If this is done effectively, the model can then be used to make accurate predictions about the system when presented with new, similar data. Additionally, introspecting on an ML model can reveal key information about the system being modeled, such as which inputs and transformations of the inputs are most useful to the ML model for learning the signal in the data, and are therefore the most predictive.
There are a variety of ML problem types. Supervised learning describes the case where the collected data contains an output value to be modeled and a set of inputs with which to train the model. EvalML focuses on training supervised learning models.
EvalML supports three common supervised ML problem types. The first is regression, where the target value to model is a continuous numeric value. Next are binary and multiclass classification, where the target value to model consists of two or more discrete values or categories. The choice of which supervised ML problem type is most appropriate depends on domain expertise and on how the model will be evaluated and used.
EvalML is currently building support for supervised time series problems: time series regression, time series binary classification, and time series multiclass classification. While we’ve added some features to tackle these kinds of problems, our functionality is still being actively developed so please be mindful of that before using it.
AutoML and Search¶
AutoML is the process of automating the construction, training and evaluation of ML models. Given a data and some configuration, AutoML searches for the most effective and accurate ML model or models to fit the dataset. During the search, AutoML will explore different combinations of model type, model parameters and model architecture.
An effective AutoML solution offers several advantages over constructing and tuning ML models by hand. AutoML can assist with many of the difficult aspects of ML, such as avoiding overfitting and underfitting, imbalanced data, detecting data leakage and other potential issues with the problem setup, and automatically applying best-practice data cleaning, feature engineering, feature selection and various modeling techniques. AutoML can also leverage search algorithms to optimally sweep the hyperparameter search space, resulting in model performance which would be difficult to achieve by manual training.
AutoML in EvalML¶
EvalML supports all of the above and more.
In its simplest usage, the AutoML search interface requires only the input data, the target data and a problem_type
specifying what kind of supervised ML problem to model.
** Graphing methods, like verbose AutoMLSearch, on Jupyter Notebook and Jupyter Lab require ipywidgets to be installed.
** If graphing on Jupyter Lab, jupyterlab-plotly required. To download this, make sure you have npm installed.
[1]:
import evalml
from evalml.utils import infer_feature_types
X, y = evalml.demos.load_fraud(n_rows=250)
Number of Features
Boolean 1
Categorical 6
Numeric 5
Number of training examples: 250
Targets
False 88.40%
True 11.60%
Name: fraud, dtype: object
To provide data to EvalML, it is recommended that you initialize a Woodwork accessor on your data. This allows you to easily control how EvalML will treat each of your features before training a model.
EvalML also accepts pandas
input, and will run type inference on top of the input pandas
data. If you’d like to change the types inferred by EvalML, you can use the infer_feature_types
utility method, which takes pandas or numpy input and converts it to a Woodwork data structure. The feature_types
parameter can be used to specify what types specific columns should be.
Feature types such as Natural Language
must be specified in this way, otherwise Woodwork will infer it as Unknown
type and drop it during the AutoMLSearch.
In the example below, we reformat a couple features to make them easily consumable by the model, and then specify that the provider, which would have otherwise been inferred as a column with natural language, is a categorical column.
[2]:
X.ww['expiration_date'] = X['expiration_date'].apply(lambda x: '20{}-01-{}'.format(x.split("/")[1], x.split("/")[0]))
X = infer_feature_types(X, feature_types= {'store_id': 'categorical',
'expiration_date': 'datetime',
'lat': 'categorical',
'lng': 'categorical',
'provider': 'categorical'})
In order to validate the results of the pipeline creation and optimization process, we will save some of our data as a holdout set.
[3]:
X_train, X_holdout, y_train, y_holdout = evalml.preprocessing.split_data(X, y, problem_type='binary', test_size=.2)
Data Checks¶
Before calling AutoMLSearch.search
, we should run some sanity checks on our data to ensure that the input data being passed will not run into some common issues before running a potentially time-consuming search. EvalML has various data checks that makes this easy. Each data check will return a collection of warnings and errors if it detects potential issues with the input data. This allows users to inspect their data to avoid confusing errors that may arise during the search process. You
can learn about each of the data checks available through our data checks guide
Here, we will run the DefaultDataChecks
class, which contains a series of data checks that are generally useful.
[4]:
from evalml.data_checks import DefaultDataChecks
data_checks = DefaultDataChecks("binary", "log loss binary")
data_checks.validate(X_train, y_train)
[4]:
[]
Since there were no warnings or errors returned, we can safely continue with the search process.
[5]:
automl = evalml.automl.AutoMLSearch(X_train=X_train, y_train=y_train, problem_type='binary', verbose=True)
automl.search()
Removing columns ['currency'] because they are of 'Unknown' type
Using default limit of max_batches=4.
*****************************
* Beginning pipeline search *
*****************************
Optimizing for Log Loss Binary.
Lower score is better.
Using SequentialEngine to train and score pipelines.
Searching up to 4 batches for a total of None pipelines.
Allowed model families:
Evaluating Baseline Pipeline: Mode Baseline Binary Classification Pipeline
Mode Baseline Binary Classification Pipeline:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 3.970
*****************************
* Evaluating Batch Number 1 *
*****************************
Logistic Regression Classifier w/ Label Encoder + Drop Columns Transformer + DateTime Featurizer + Imputer + One Hot Encoder + Oversampler + Standard Scaler:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.620
Random Forest Classifier w/ Label Encoder + Drop Columns Transformer + DateTime Featurizer + Imputer + One Hot Encoder + Oversampler:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.292
*****************************
* Evaluating Batch Number 2 *
*****************************
Logistic Regression Classifier w/ Label Encoder + Drop Columns Transformer + DateTime Featurizer + Imputer + One Hot Encoder + Oversampler + Standard Scaler + RF Classifier Select From Model:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.573
Random Forest Classifier w/ Label Encoder + Drop Columns Transformer + DateTime Featurizer + Imputer + One Hot Encoder + Oversampler + RF Classifier Select From Model:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.289
*****************************
* Evaluating Batch Number 3 *
*****************************
Decision Tree Classifier w/ Label Encoder + Select Columns By Type Transformer + Label Encoder + Drop Columns Transformer + DateTime Featurizer + Imputer + Oversampler + Select Columns Transformer + Select Columns Transformer + Label Encoder + Imputer + One Hot Encoder + Oversampler:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 3.656
High coefficient of variation (cv >= 0.5) within cross validation scores.
Decision Tree Classifier w/ Label Encoder + Select Columns By Type Transformer + Label Encoder + Drop Columns Transformer + DateTime Featurizer + Imputer + Oversampler + Select Columns Transformer + Select Columns Transformer + Label Encoder + Imputer + One Hot Encoder + Oversampler may not perform as estimated on unseen data.
LightGBM Classifier w/ Label Encoder + Select Columns By Type Transformer + Label Encoder + Drop Columns Transformer + DateTime Featurizer + Imputer + Oversampler + Select Columns Transformer + Select Columns Transformer + Label Encoder + Imputer + One Hot Encoder + Oversampler:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.316
Extra Trees Classifier w/ Label Encoder + Select Columns By Type Transformer + Label Encoder + Drop Columns Transformer + DateTime Featurizer + Imputer + Oversampler + Select Columns Transformer + Select Columns Transformer + Label Encoder + Imputer + One Hot Encoder + Oversampler:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.344
Elastic Net Classifier w/ Label Encoder + Select Columns By Type Transformer + Label Encoder + Drop Columns Transformer + DateTime Featurizer + Imputer + Oversampler + Standard Scaler + Select Columns Transformer + Select Columns Transformer + Label Encoder + Imputer + One Hot Encoder + Oversampler + Standard Scaler:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.617
CatBoost Classifier w/ Label Encoder + Select Columns By Type Transformer + Label Encoder + Drop Columns Transformer + DateTime Featurizer + Imputer + Oversampler + Select Columns Transformer + Select Columns Transformer + Label Encoder + Imputer + Oversampler:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.624
XGBoost Classifier w/ Label Encoder + Select Columns By Type Transformer + Label Encoder + Drop Columns Transformer + DateTime Featurizer + Imputer + Oversampler + Select Columns Transformer + Select Columns Transformer + Label Encoder + Imputer + One Hot Encoder + Oversampler:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.271
*****************************
* Evaluating Batch Number 4 *
*****************************
Stacked Ensemble Classification Pipeline:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.245
Search finished after 00:44
Best pipeline: Stacked Ensemble Classification Pipeline
Best pipeline Log Loss Binary: 0.245391
With the verbose
argument set to True, the AutoML search will log its progress, reporting each pipeline and parameter set evaluated during the search.
There are a number of mechanisms to control the AutoML search time. One way is to set the max_batches
parameter which controls the maximum number of rounds of AutoML to evaluate, where each round may train and score a variable number of pipelines. Another way is to set the max_iterations
parameter which controls the maximum number of candidate models to be evaluated during AutoML. By default, AutoML will search for a single batch. The first pipeline to be evaluated will always be a
baseline model representing a trivial solution.
The AutoML interface supports a variety of other parameters. For a comprehensive list, please refer to the API reference.
We also provide a standalone search method which does all of the above in a single line, and returns the AutoMLSearch
instance and data check results. If there were data check errors, AutoML will not be run and no AutoMLSearch
instance will be returned.
Detecting Problem Type¶
EvalML includes a simple method, detect_problem_type
, to help determine the problem type given the target data.
This function can return the predicted problem type as a ProblemType enum, choosing from ProblemType.BINARY, ProblemType.MULTICLASS, and ProblemType.REGRESSION. If the target data is invalid (for instance when there is only 1 unique label), the function will throw an error instead.
[6]:
import pandas as pd
from evalml.problem_types import detect_problem_type
y_binary = pd.Series([0, 1, 1, 0, 1, 1])
detect_problem_type(y_binary)
[6]:
<ProblemTypes.BINARY: 'binary'>
Objective parameter¶
AutoMLSearch takes in an objective
parameter to determine which objective
to optimize for. By default, this parameter is set to auto
, which allows AutoML to choose LogLossBinary
for binary classification problems, LogLossMulticlass
for multiclass classification problems, and R2
for regression problems.
It should be noted that the objective
parameter is only used in ranking and helping choose the pipelines to iterate over, but is not used to optimize each individual pipeline during fit-time.
To get the default objective for each problem type, you can use the get_default_primary_search_objective
function.
[7]:
from evalml.automl import get_default_primary_search_objective
binary_objective = get_default_primary_search_objective("binary")
multiclass_objective = get_default_primary_search_objective("multiclass")
regression_objective = get_default_primary_search_objective("regression")
print(binary_objective.name)
print(multiclass_objective.name)
print(regression_objective.name)
Log Loss Binary
Log Loss Multiclass
R2
Using custom pipelines¶
EvalML’s AutoML algorithm generates a set of pipelines to search with. To provide a custom set instead, set allowed_component_graphs to a dictionary of custom component graphs. AutoMLSearch
will use these to generate Pipeline
instances. Note: this will prevent AutoML from generating other pipelines to search over.
[8]:
from evalml.pipelines import MulticlassClassificationPipeline
automl_custom = evalml.automl.AutoMLSearch(
X_train=X_train,
y_train=y_train,
problem_type='multiclass',
verbose=True,
allowed_component_graphs={"My_pipeline": ['Simple Imputer', 'Random Forest Classifier'],
"My_other_pipeline": ['One Hot Encoder', 'Random Forest Classifier']})
Removing columns ['currency'] because they are of 'Unknown' type
Using default limit of max_batches=4.
Stopping the search early¶
To stop the search early, hit Ctrl-C
. This will bring up a prompt asking for confirmation. Responding with y
will immediately stop the search. Responding with n
will continue the search.
Callback functions¶
AutoMLSearch
supports several callback functions, which can be specified as parameters when initializing an AutoMLSearch
object. They are:
start_iteration_callback
add_result_callback
error_callback
Start Iteration Callback¶
Users can set start_iteration_callback
to set what function is called before each pipeline training iteration. This callback function must take three positional parameters: the pipeline class, the pipeline parameters, and the AutoMLSearch
object.
[9]:
## start_iteration_callback example function
def start_iteration_callback_example(pipeline_class, pipeline_params, automl_obj):
print ("Training pipeline with the following parameters:", pipeline_params)
Add Result Callback¶
Users can set add_result_callback
to set what function is called after each pipeline training iteration. This callback function must take three positional parameters: a dictionary containing the training results for the new pipeline, an untrained_pipeline containing the parameters used during training, and the AutoMLSearch
object.
[10]:
## add_result_callback example function
def add_result_callback_example(pipeline_results_dict, untrained_pipeline, automl_obj):
print ("Results for trained pipeline with the following parameters:", pipeline_results_dict)
Error Callback¶
Users can set the error_callback
to set what function called when search()
errors and raises an Exception
. This callback function takes three positional parameters: the Exception raised
, the traceback, and the AutoMLSearch object
. This callback function must also accept kwargs
, so AutoMLSearch
is able to pass along other parameters used by default.
Evalml defines several error callback functions, which can be found under evalml.automl.callbacks
. They are:
silent_error_callback
raise_error_callback
log_and_save_error_callback
raise_and_save_error_callback
log_error_callback
(default used whenerror_callback
is None)
[11]:
# error_callback example; this is implemented in the evalml library
def raise_error_callback(exception, traceback, automl, **kwargs):
"""Raises the exception thrown by the AutoMLSearch object. Also logs the exception as an error."""
logger.error(f'AutoMLSearch raised a fatal exception: {str(exception)}')
logger.error("\n".join(traceback))
raise exception
View Rankings¶
A summary of all the pipelines built can be returned as a pandas DataFrame which is sorted by score. The score column contains the average score across all cross-validation folds while the validation_score column is computed from the first cross-validation fold.
[12]:
automl.rankings
[12]:
id | pipeline_name | search_order | mean_cv_score | standard_deviation_cv_score | validation_score | percent_better_than_baseline | high_variance_cv | parameters | |
---|---|---|---|---|---|---|---|---|---|
0 | 11 | Stacked Ensemble Classification Pipeline | 11 | 0.245391 | 0.107989 | 0.245391 | 93.819519 | False | {'Label Encoder': {'positive_label': None}, 'L... |
1 | 10 | XGBoost Classifier w/ Label Encoder + Select C... | 10 | 0.270904 | 0.161665 | 0.270904 | 93.176944 | False | {'Label Encoder': {'positive_label': None}, 'N... |
2 | 4 | Random Forest Classifier w/ Label Encoder + Dr... | 4 | 0.288964 | 0.042651 | 0.288964 | 92.722092 | False | {'Label Encoder': {'positive_label': None}, 'D... |
3 | 2 | Random Forest Classifier w/ Label Encoder + Dr... | 2 | 0.292264 | 0.032252 | 0.292264 | 92.638975 | False | {'Label Encoder': {'positive_label': None}, 'D... |
4 | 6 | LightGBM Classifier w/ Label Encoder + Select ... | 6 | 0.316412 | 0.156072 | 0.316412 | 92.030785 | False | {'Label Encoder': {'positive_label': None}, 'N... |
5 | 7 | Extra Trees Classifier w/ Label Encoder + Sele... | 7 | 0.344051 | 0.004555 | 0.344051 | 91.334658 | False | {'Label Encoder': {'positive_label': None}, 'N... |
6 | 3 | Logistic Regression Classifier w/ Label Encode... | 3 | 0.573408 | 0.121277 | 0.573408 | 85.558016 | False | {'Label Encoder': {'positive_label': None}, 'D... |
7 | 8 | Elastic Net Classifier w/ Label Encoder + Sele... | 8 | 0.616802 | 0.195065 | 0.616802 | 84.465076 | False | {'Label Encoder': {'positive_label': None}, 'N... |
8 | 1 | Logistic Regression Classifier w/ Label Encode... | 1 | 0.620303 | 0.189890 | 0.620303 | 84.376900 | False | {'Label Encoder': {'positive_label': None}, 'D... |
9 | 9 | CatBoost Classifier w/ Label Encoder + Select ... | 9 | 0.623505 | 0.003046 | 0.623505 | 84.296249 | False | {'Label Encoder': {'positive_label': None}, 'N... |
10 | 5 | Decision Tree Classifier w/ Label Encoder + Se... | 5 | 3.655857 | 1.311022 | 3.655857 | 7.922744 | True | {'Label Encoder': {'positive_label': None}, 'N... |
11 | 0 | Mode Baseline Binary Classification Pipeline | 0 | 3.970423 | 0.266060 | 3.970423 | 0.000000 | False | {'Label Encoder': {'positive_label': None}, 'B... |
Describe Pipeline¶
Each pipeline is given an id
. We can get more information about any particular pipeline using that id
. Here, we will get more information about the pipeline with id = 1
.
[13]:
automl.describe_pipeline(1)
****************************************************************************************************************************************************************
* Logistic Regression Classifier w/ Label Encoder + Drop Columns Transformer + DateTime Featurizer + Imputer + One Hot Encoder + Oversampler + Standard Scaler *
****************************************************************************************************************************************************************
Problem Type: binary
Model Family: Linear
Pipeline Steps
==============
1. Label Encoder
* positive_label : None
2. Drop Columns Transformer
* columns : ['currency']
3. DateTime Featurizer
* features_to_extract : ['year', 'month', 'day_of_week', 'hour']
* encode_as_categories : False
* time_index : None
4. Imputer
* categorical_impute_strategy : most_frequent
* numeric_impute_strategy : mean
* categorical_fill_value : None
* numeric_fill_value : None
5. One Hot Encoder
* top_n : 10
* features_to_encode : None
* categories : None
* drop : if_binary
* handle_unknown : ignore
* handle_missing : error
6. Oversampler
* sampling_ratio : 0.25
* k_neighbors_default : 5
* n_jobs : -1
* sampling_ratio_dict : None
* categorical_features : [3, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43]
* k_neighbors : 5
7. Standard Scaler
8. Logistic Regression Classifier
* penalty : l2
* C : 1.0
* n_jobs : -1
* multi_class : auto
* solver : lbfgs
Training
========
Training for binary problems.
Total training time (including CV): 4.1 seconds
Cross Validation
----------------
Log Loss Binary MCC Binary Gini AUC Precision F1 Balanced Accuracy Binary Accuracy Binary # Training # Validation
0 0.440 0.101 0.284 0.642 0.250 0.167 0.537 0.851 133 67
1 0.602 0.246 0.144 0.572 0.400 0.308 0.600 0.866 133 67
2 0.819 0.070 0.031 0.516 0.136 0.207 0.553 0.652 134 66
mean 0.620 0.139 0.153 0.577 0.262 0.227 0.563 0.789 - -
std 0.190 0.094 0.126 0.063 0.132 0.073 0.032 0.120 - -
coef of var 0.306 0.676 0.826 0.110 0.504 0.320 0.058 0.151 - -
Get Pipeline¶
We can get the object of any pipeline via their id
as well:
[14]:
pipeline = automl.get_pipeline(1)
print(pipeline.name)
print(pipeline.parameters)
Logistic Regression Classifier w/ Label Encoder + Drop Columns Transformer + DateTime Featurizer + Imputer + One Hot Encoder + Oversampler + Standard Scaler
{'Label Encoder': {'positive_label': None}, 'Drop Columns Transformer': {'columns': ['currency']}, 'DateTime Featurizer': {'features_to_extract': ['year', 'month', 'day_of_week', 'hour'], 'encode_as_categories': False, 'time_index': None}, 'Imputer': {'categorical_impute_strategy': 'most_frequent', 'numeric_impute_strategy': 'mean', 'categorical_fill_value': None, 'numeric_fill_value': None}, 'One Hot Encoder': {'top_n': 10, 'features_to_encode': None, 'categories': None, 'drop': 'if_binary', 'handle_unknown': 'ignore', 'handle_missing': 'error'}, 'Oversampler': {'sampling_ratio': 0.25, 'k_neighbors_default': 5, 'n_jobs': -1, 'sampling_ratio_dict': None, 'categorical_features': [3, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43], 'k_neighbors': 5}, 'Logistic Regression Classifier': {'penalty': 'l2', 'C': 1.0, 'n_jobs': -1, 'multi_class': 'auto', 'solver': 'lbfgs'}}
Get best pipeline¶
If you specifically want to get the best pipeline, there is a convenient accessor for that. The pipeline returned is already fitted on the input X, y data that we passed to AutoMLSearch. To turn off this default behavior, set train_best_pipeline=False
when initializing AutoMLSearch.
[15]:
best_pipeline = automl.best_pipeline
print(best_pipeline.name)
print(best_pipeline.parameters)
best_pipeline.predict(X_train)
Stacked Ensemble Classification Pipeline
{'Label Encoder': {'positive_label': None}, 'Linear Pipeline - Label Encoder': {'positive_label': None}, 'Linear Pipeline - Drop Columns Transformer': {'columns': ['currency']}, 'Linear Pipeline - DateTime Featurizer': {'features_to_extract': ['year', 'month', 'day_of_week', 'hour'], 'encode_as_categories': False, 'time_index': None}, 'Linear Pipeline - Imputer': {'categorical_impute_strategy': 'most_frequent', 'numeric_impute_strategy': 'mean', 'categorical_fill_value': None, 'numeric_fill_value': None}, 'Linear Pipeline - One Hot Encoder': {'top_n': 10, 'features_to_encode': None, 'categories': None, 'drop': 'if_binary', 'handle_unknown': 'ignore', 'handle_missing': 'error'}, 'Linear Pipeline - Oversampler': {'sampling_ratio': 0.25, 'k_neighbors_default': 5, 'n_jobs': -1, 'sampling_ratio_dict': None, 'categorical_features': [3, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43], 'k_neighbors': 5}, 'Linear Pipeline - RF Classifier Select From Model': {'number_features': None, 'n_estimators': 10, 'max_depth': None, 'percent_features': 0.5, 'threshold': 'median', 'n_jobs': -1}, 'Linear Pipeline - Logistic Regression Classifier': {'penalty': 'l2', 'C': 1.0, 'n_jobs': -1, 'multi_class': 'auto', 'solver': 'lbfgs'}, 'Random Forest Pipeline - Label Encoder': {'positive_label': None}, 'Random Forest Pipeline - Drop Columns Transformer': {'columns': ['currency']}, 'Random Forest Pipeline - DateTime Featurizer': {'features_to_extract': ['year', 'month', 'day_of_week', 'hour'], 'encode_as_categories': False, 'time_index': None}, 'Random Forest Pipeline - Imputer': {'categorical_impute_strategy': 'most_frequent', 'numeric_impute_strategy': 'mean', 'categorical_fill_value': None, 'numeric_fill_value': None}, 'Random Forest Pipeline - One Hot Encoder': {'top_n': 10, 'features_to_encode': None, 'categories': None, 'drop': 'if_binary', 'handle_unknown': 'ignore', 'handle_missing': 'error'}, 'Random Forest Pipeline - Oversampler': {'sampling_ratio': 0.25, 'k_neighbors_default': 5, 'n_jobs': -1, 'sampling_ratio_dict': None, 'categorical_features': [3, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43], 'k_neighbors': 5}, 'Random Forest Pipeline - RF Classifier Select From Model': {'number_features': None, 'n_estimators': 10, 'max_depth': None, 'percent_features': 0.5, 'threshold': 'median', 'n_jobs': -1}, 'Random Forest Pipeline - Random Forest Classifier': {'n_estimators': 100, 'max_depth': 6, 'n_jobs': -1}, 'Decision Tree Pipeline - Label Encoder': {'positive_label': None}, 'Decision Tree Pipeline - Numeric Pipeline - Select Columns By Type Transformer': {'column_types': ['category'], 'exclude': True}, 'Decision Tree Pipeline - Numeric Pipeline - Label Encoder': {'positive_label': None}, 'Decision Tree Pipeline - Numeric Pipeline - Drop Columns Transformer': {'columns': ['currency']}, 'Decision Tree Pipeline - Numeric Pipeline - DateTime Featurizer': {'features_to_extract': ['year', 'month', 'day_of_week', 'hour'], 'encode_as_categories': False, 'time_index': None}, 'Decision Tree Pipeline - Numeric Pipeline - Imputer': {'categorical_impute_strategy': 'most_frequent', 'numeric_impute_strategy': 'mean', 'categorical_fill_value': None, 'numeric_fill_value': None}, 'Decision Tree Pipeline - Numeric Pipeline - Oversampler': {'sampling_ratio': 0.25, 'k_neighbors_default': 5, 'n_jobs': -1, 'sampling_ratio_dict': None, 'categorical_features': [3], 'k_neighbors': 5}, 'Decision Tree Pipeline - Numeric Pipeline - Select Columns Transformer': {'columns': ['card_id', 'store_id', 'amount', 'customer_present', 'lat', 'lng', 'datetime_month', 'datetime_day_of_week', 'datetime_hour', 'expiration_date_year', 'expiration_date_day_of_week']}, 'Decision Tree Pipeline - Categorical Pipeline - Select Columns Transformer': {'columns': ['provider', 'region', 'country']}, 'Decision Tree Pipeline - Categorical Pipeline - Label Encoder': {'positive_label': None}, 'Decision Tree Pipeline - Categorical Pipeline - Imputer': {'categorical_impute_strategy': 'most_frequent', 'numeric_impute_strategy': 'mean', 'categorical_fill_value': None, 'numeric_fill_value': None}, 'Decision Tree Pipeline - Categorical Pipeline - One Hot Encoder': {'top_n': 10, 'features_to_encode': None, 'categories': None, 'drop': 'if_binary', 'handle_unknown': 'ignore', 'handle_missing': 'error'}, 'Decision Tree Pipeline - Categorical Pipeline - Oversampler': {'sampling_ratio': 0.25, 'k_neighbors_default': 5, 'n_jobs': -1, 'sampling_ratio_dict': None, 'k_neighbors': 5}, 'Decision Tree Pipeline - Decision Tree Classifier': {'criterion': 'gini', 'max_features': 'auto', 'max_depth': 6, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0}, 'LightGBM Pipeline - Label Encoder': {'positive_label': None}, 'LightGBM Pipeline - Numeric Pipeline - Select Columns By Type Transformer': {'column_types': ['category'], 'exclude': True}, 'LightGBM Pipeline - Numeric Pipeline - Label Encoder': {'positive_label': None}, 'LightGBM Pipeline - Numeric Pipeline - Drop Columns Transformer': {'columns': ['currency']}, 'LightGBM Pipeline - Numeric Pipeline - DateTime Featurizer': {'features_to_extract': ['year', 'month', 'day_of_week', 'hour'], 'encode_as_categories': False, 'time_index': None}, 'LightGBM Pipeline - Numeric Pipeline - Imputer': {'categorical_impute_strategy': 'most_frequent', 'numeric_impute_strategy': 'mean', 'categorical_fill_value': None, 'numeric_fill_value': None}, 'LightGBM Pipeline - Numeric Pipeline - Oversampler': {'sampling_ratio': 0.25, 'k_neighbors_default': 5, 'n_jobs': -1, 'sampling_ratio_dict': None, 'categorical_features': [3], 'k_neighbors': 5}, 'LightGBM Pipeline - Numeric Pipeline - Select Columns Transformer': {'columns': ['card_id', 'store_id', 'amount', 'customer_present', 'lat', 'lng', 'datetime_month', 'datetime_day_of_week', 'datetime_hour', 'expiration_date_year', 'expiration_date_day_of_week']}, 'LightGBM Pipeline - Categorical Pipeline - Select Columns Transformer': {'columns': ['provider', 'region', 'country']}, 'LightGBM Pipeline - Categorical Pipeline - Label Encoder': {'positive_label': None}, 'LightGBM Pipeline - Categorical Pipeline - Imputer': {'categorical_impute_strategy': 'most_frequent', 'numeric_impute_strategy': 'mean', 'categorical_fill_value': None, 'numeric_fill_value': None}, 'LightGBM Pipeline - Categorical Pipeline - One Hot Encoder': {'top_n': 10, 'features_to_encode': None, 'categories': None, 'drop': 'if_binary', 'handle_unknown': 'ignore', 'handle_missing': 'error'}, 'LightGBM Pipeline - Categorical Pipeline - Oversampler': {'sampling_ratio': 0.25, 'k_neighbors_default': 5, 'n_jobs': -1, 'sampling_ratio_dict': None, 'k_neighbors': 5}, 'LightGBM Pipeline - LightGBM Classifier': {'boosting_type': 'gbdt', 'learning_rate': 0.1, 'n_estimators': 100, 'max_depth': 0, 'num_leaves': 31, 'min_child_samples': 20, 'n_jobs': -1, 'bagging_freq': 0, 'bagging_fraction': 0.9}, 'Extra Trees Pipeline - Label Encoder': {'positive_label': None}, 'Extra Trees Pipeline - Numeric Pipeline - Select Columns By Type Transformer': {'column_types': ['category'], 'exclude': True}, 'Extra Trees Pipeline - Numeric Pipeline - Label Encoder': {'positive_label': None}, 'Extra Trees Pipeline - Numeric Pipeline - Drop Columns Transformer': {'columns': ['currency']}, 'Extra Trees Pipeline - Numeric Pipeline - DateTime Featurizer': {'features_to_extract': ['year', 'month', 'day_of_week', 'hour'], 'encode_as_categories': False, 'time_index': None}, 'Extra Trees Pipeline - Numeric Pipeline - Imputer': {'categorical_impute_strategy': 'most_frequent', 'numeric_impute_strategy': 'mean', 'categorical_fill_value': None, 'numeric_fill_value': None}, 'Extra Trees Pipeline - Numeric Pipeline - Oversampler': {'sampling_ratio': 0.25, 'k_neighbors_default': 5, 'n_jobs': -1, 'sampling_ratio_dict': None, 'categorical_features': [3], 'k_neighbors': 5}, 'Extra Trees Pipeline - Numeric Pipeline - Select Columns Transformer': {'columns': ['card_id', 'store_id', 'amount', 'customer_present', 'lat', 'lng', 'datetime_month', 'datetime_day_of_week', 'datetime_hour', 'expiration_date_year', 'expiration_date_day_of_week']}, 'Extra Trees Pipeline - Categorical Pipeline - Select Columns Transformer': {'columns': ['provider', 'region', 'country']}, 'Extra Trees Pipeline - Categorical Pipeline - Label Encoder': {'positive_label': None}, 'Extra Trees Pipeline - Categorical Pipeline - Imputer': {'categorical_impute_strategy': 'most_frequent', 'numeric_impute_strategy': 'mean', 'categorical_fill_value': None, 'numeric_fill_value': None}, 'Extra Trees Pipeline - Categorical Pipeline - One Hot Encoder': {'top_n': 10, 'features_to_encode': None, 'categories': None, 'drop': 'if_binary', 'handle_unknown': 'ignore', 'handle_missing': 'error'}, 'Extra Trees Pipeline - Categorical Pipeline - Oversampler': {'sampling_ratio': 0.25, 'k_neighbors_default': 5, 'n_jobs': -1, 'sampling_ratio_dict': None, 'k_neighbors': 5}, 'Extra Trees Pipeline - Extra Trees Classifier': {'n_estimators': 100, 'max_features': 'auto', 'max_depth': 6, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'n_jobs': -1}, 'CatBoost Pipeline - Label Encoder': {'positive_label': None}, 'CatBoost Pipeline - Numeric Pipeline - Select Columns By Type Transformer': {'column_types': ['category'], 'exclude': True}, 'CatBoost Pipeline - Numeric Pipeline - Label Encoder': {'positive_label': None}, 'CatBoost Pipeline - Numeric Pipeline - Drop Columns Transformer': {'columns': ['currency']}, 'CatBoost Pipeline - Numeric Pipeline - DateTime Featurizer': {'features_to_extract': ['year', 'month', 'day_of_week', 'hour'], 'encode_as_categories': False, 'time_index': None}, 'CatBoost Pipeline - Numeric Pipeline - Imputer': {'categorical_impute_strategy': 'most_frequent', 'numeric_impute_strategy': 'mean', 'categorical_fill_value': None, 'numeric_fill_value': None}, 'CatBoost Pipeline - Numeric Pipeline - Oversampler': {'sampling_ratio': 0.25, 'k_neighbors_default': 5, 'n_jobs': -1, 'sampling_ratio_dict': None, 'categorical_features': [3], 'k_neighbors': 5}, 'CatBoost Pipeline - Numeric Pipeline - Select Columns Transformer': {'columns': ['card_id', 'store_id', 'amount', 'customer_present', 'lat', 'lng', 'datetime_month', 'datetime_day_of_week', 'datetime_hour', 'expiration_date_year', 'expiration_date_day_of_week']}, 'CatBoost Pipeline - Categorical Pipeline - Select Columns Transformer': {'columns': ['provider', 'region', 'country']}, 'CatBoost Pipeline - Categorical Pipeline - Label Encoder': {'positive_label': None}, 'CatBoost Pipeline - Categorical Pipeline - Imputer': {'categorical_impute_strategy': 'most_frequent', 'numeric_impute_strategy': 'mean', 'categorical_fill_value': None, 'numeric_fill_value': None}, 'CatBoost Pipeline - Categorical Pipeline - Oversampler': {'sampling_ratio': 0.25, 'k_neighbors_default': 5, 'n_jobs': -1, 'sampling_ratio_dict': None, 'k_neighbors': 5}, 'CatBoost Pipeline - CatBoost Classifier': {'n_estimators': 10, 'eta': 0.03, 'max_depth': 6, 'bootstrap_type': None, 'silent': True, 'allow_writing_files': False, 'n_jobs': -1}, 'XGBoost Pipeline - Label Encoder': {'positive_label': None}, 'XGBoost Pipeline - Numeric Pipeline - Select Columns By Type Transformer': {'column_types': ['category'], 'exclude': True}, 'XGBoost Pipeline - Numeric Pipeline - Label Encoder': {'positive_label': None}, 'XGBoost Pipeline - Numeric Pipeline - Drop Columns Transformer': {'columns': ['currency']}, 'XGBoost Pipeline - Numeric Pipeline - DateTime Featurizer': {'features_to_extract': ['year', 'month', 'day_of_week', 'hour'], 'encode_as_categories': False, 'time_index': None}, 'XGBoost Pipeline - Numeric Pipeline - Imputer': {'categorical_impute_strategy': 'most_frequent', 'numeric_impute_strategy': 'mean', 'categorical_fill_value': None, 'numeric_fill_value': None}, 'XGBoost Pipeline - Numeric Pipeline - Oversampler': {'sampling_ratio': 0.25, 'k_neighbors_default': 5, 'n_jobs': -1, 'sampling_ratio_dict': None, 'categorical_features': [3], 'k_neighbors': 5}, 'XGBoost Pipeline - Numeric Pipeline - Select Columns Transformer': {'columns': ['card_id', 'store_id', 'amount', 'customer_present', 'lat', 'lng', 'datetime_month', 'datetime_day_of_week', 'datetime_hour', 'expiration_date_year', 'expiration_date_day_of_week']}, 'XGBoost Pipeline - Categorical Pipeline - Select Columns Transformer': {'columns': ['provider', 'region', 'country']}, 'XGBoost Pipeline - Categorical Pipeline - Label Encoder': {'positive_label': None}, 'XGBoost Pipeline - Categorical Pipeline - Imputer': {'categorical_impute_strategy': 'most_frequent', 'numeric_impute_strategy': 'mean', 'categorical_fill_value': None, 'numeric_fill_value': None}, 'XGBoost Pipeline - Categorical Pipeline - One Hot Encoder': {'top_n': 10, 'features_to_encode': None, 'categories': None, 'drop': 'if_binary', 'handle_unknown': 'ignore', 'handle_missing': 'error'}, 'XGBoost Pipeline - Categorical Pipeline - Oversampler': {'sampling_ratio': 0.25, 'k_neighbors_default': 5, 'n_jobs': -1, 'sampling_ratio_dict': None, 'k_neighbors': 5}, 'XGBoost Pipeline - XGBoost Classifier': {'eta': 0.1, 'max_depth': 6, 'min_child_weight': 1, 'n_estimators': 100, 'n_jobs': -1, 'eval_metric': 'logloss'}, 'Stacked Ensemble Classifier': {'final_estimator': ElasticNetClassifier(penalty='elasticnet', C=1.0, l1_ratio=0.15, n_jobs=-1, multi_class='auto', solver='saga'), 'n_jobs': -1}}
[15]:
id
93 False
243 False
211 False
44 True
6 False
...
148 False
171 False
94 False
246 False
95 False
Name: fraud, Length: 200, dtype: bool
Training and Scoring Multiple Pipelines using AutoMLSearch¶
AutoMLSearch will automatically fit the best pipeline on the entire training data. It also provides an easy API for training and scoring other pipelines.
If you’d like to train one or more pipelines on the entire training data, you can use the train_pipelines
method
Similarly, if you’d like to score one or more pipelines on a particular dataset, you can use the train_pipelines
method
[16]:
trained_pipelines = automl.train_pipelines([automl.get_pipeline(i) for i in [0, 1, 2]])
trained_pipelines
[16]:
{'Mode Baseline Binary Classification Pipeline': pipeline = BinaryClassificationPipeline(component_graph={'Label Encoder': ['Label Encoder', 'X', 'y'], 'Baseline Classifier': ['Baseline Classifier', 'Label Encoder.x', 'Label Encoder.y']}, parameters={'Label Encoder':{'positive_label': None}, 'Baseline Classifier':{'strategy': 'mode'}}, custom_name='Mode Baseline Binary Classification Pipeline', random_seed=0),
'Logistic Regression Classifier w/ Label Encoder + Drop Columns Transformer + DateTime Featurizer + Imputer + One Hot Encoder + Oversampler + Standard Scaler': pipeline = BinaryClassificationPipeline(component_graph={'Label Encoder': ['Label Encoder', 'X', 'y'], 'Drop Columns Transformer': ['Drop Columns Transformer', 'X', 'Label Encoder.y'], 'DateTime Featurizer': ['DateTime Featurizer', 'Drop Columns Transformer.x', 'Label Encoder.y'], 'Imputer': ['Imputer', 'DateTime Featurizer.x', 'Label Encoder.y'], 'One Hot Encoder': ['One Hot Encoder', 'Imputer.x', 'Label Encoder.y'], 'Oversampler': ['Oversampler', 'One Hot Encoder.x', 'Label Encoder.y'], 'Standard Scaler': ['Standard Scaler', 'Oversampler.x', 'Oversampler.y'], 'Logistic Regression Classifier': ['Logistic Regression Classifier', 'Standard Scaler.x', 'Oversampler.y']}, parameters={'Label Encoder':{'positive_label': None}, 'Drop Columns Transformer':{'columns': ['currency']}, 'DateTime Featurizer':{'features_to_extract': ['year', 'month', 'day_of_week', 'hour'], 'encode_as_categories': False, 'time_index': None}, 'Imputer':{'categorical_impute_strategy': 'most_frequent', 'numeric_impute_strategy': 'mean', 'categorical_fill_value': None, 'numeric_fill_value': None}, 'One Hot Encoder':{'top_n': 10, 'features_to_encode': None, 'categories': None, 'drop': 'if_binary', 'handle_unknown': 'ignore', 'handle_missing': 'error'}, 'Oversampler':{'sampling_ratio': 0.25, 'k_neighbors_default': 5, 'n_jobs': -1, 'sampling_ratio_dict': None, 'categorical_features': [3, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43], 'k_neighbors': 5}, 'Logistic Regression Classifier':{'penalty': 'l2', 'C': 1.0, 'n_jobs': -1, 'multi_class': 'auto', 'solver': 'lbfgs'}}, random_seed=0),
'Random Forest Classifier w/ Label Encoder + Drop Columns Transformer + DateTime Featurizer + Imputer + One Hot Encoder + Oversampler': pipeline = BinaryClassificationPipeline(component_graph={'Label Encoder': ['Label Encoder', 'X', 'y'], 'Drop Columns Transformer': ['Drop Columns Transformer', 'X', 'Label Encoder.y'], 'DateTime Featurizer': ['DateTime Featurizer', 'Drop Columns Transformer.x', 'Label Encoder.y'], 'Imputer': ['Imputer', 'DateTime Featurizer.x', 'Label Encoder.y'], 'One Hot Encoder': ['One Hot Encoder', 'Imputer.x', 'Label Encoder.y'], 'Oversampler': ['Oversampler', 'One Hot Encoder.x', 'Label Encoder.y'], 'Random Forest Classifier': ['Random Forest Classifier', 'Oversampler.x', 'Oversampler.y']}, parameters={'Label Encoder':{'positive_label': None}, 'Drop Columns Transformer':{'columns': ['currency']}, 'DateTime Featurizer':{'features_to_extract': ['year', 'month', 'day_of_week', 'hour'], 'encode_as_categories': False, 'time_index': None}, 'Imputer':{'categorical_impute_strategy': 'most_frequent', 'numeric_impute_strategy': 'mean', 'categorical_fill_value': None, 'numeric_fill_value': None}, 'One Hot Encoder':{'top_n': 10, 'features_to_encode': None, 'categories': None, 'drop': 'if_binary', 'handle_unknown': 'ignore', 'handle_missing': 'error'}, 'Oversampler':{'sampling_ratio': 0.25, 'k_neighbors_default': 5, 'n_jobs': -1, 'sampling_ratio_dict': None, 'categorical_features': [3, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43], 'k_neighbors': 5}, 'Random Forest Classifier':{'n_estimators': 100, 'max_depth': 6, 'n_jobs': -1}}, random_seed=0)}
[17]:
pipeline_holdout_scores = automl.score_pipelines([trained_pipelines[name] for name in trained_pipelines.keys()],
X_holdout,
y_holdout,
['Accuracy Binary', 'F1', 'AUC'])
pipeline_holdout_scores
[17]:
{'Mode Baseline Binary Classification Pipeline': OrderedDict([('Accuracy Binary',
0.88),
('F1', 0.0),
('AUC', 0.5)]),
'Logistic Regression Classifier w/ Label Encoder + Drop Columns Transformer + DateTime Featurizer + Imputer + One Hot Encoder + Oversampler + Standard Scaler': OrderedDict([('Accuracy Binary',
0.36),
('F1', 0.2),
('AUC', 0.5113636363636364)]),
'Random Forest Classifier w/ Label Encoder + Drop Columns Transformer + DateTime Featurizer + Imputer + One Hot Encoder + Oversampler': OrderedDict([('Accuracy Binary',
0.94),
('F1', 0.6666666666666666),
('AUC', 0.8598484848484849)])}
Saving AutoMLSearch and pipelines from AutoMLSearch¶
There are two ways to save results from AutoMLSearch.
You can save the AutoMLSearch object itself, calling
.save(<filepath>)
to do so. This will allow you to save the AutoMLSearch state and reload all pipelines from this.If you want to save a pipeline from AutoMLSearch for future use, pipeline classes themselves have a
.save(<filepath>)
method.
[18]:
# saving the entire automl search
automl.save("automl.cloudpickle")
automl2 = evalml.automl.AutoMLSearch.load("automl.cloudpickle")
# saving the best pipeline using .save()
best_pipeline.save("pipeline.cloudpickle")
best_pipeline_copy = evalml.pipelines.PipelineBase.load("pipeline.cloudpickle")
Limiting the AutoML Search Space¶
The AutoML search algorithm first trains each component in the pipeline with their default values. After the first iteration, it then tweaks the parameters of these components using the pre-defined hyperparameter ranges that these components have. To limit the search over certain hyperparameter ranges, you can specify a custom_hyperparameters
argument with your AutoMLSearch
parameters. These parameters will limit the hyperparameter search space.
Hyperparameter ranges can be found through the API reference for each component. Parameter arguments must be specified as dictionaries, but the associated values can be single values or skopt.space
Real, Integer, Categorical values.
If however you’d like to specify certain values for the initial batch of the AutoML search algorithm, you can use the pipeline_parameters
argument. This will set the initial batch’s component parameters to the values passed by this argument.
[19]:
from evalml import AutoMLSearch
from evalml.demos import load_fraud
from skopt.space import Categorical
from evalml.model_family import ModelFamily
import woodwork as ww
X, y = load_fraud(n_rows=1000)
# example of setting parameter to just one value
custom_hyperparameters = {'Imputer': {
'numeric_impute_strategy': 'mean'
}}
# limit the numeric impute strategy to include only `median` and `most_frequent`
# `mean` is the default value for this argument, but it doesn't need to be included in the specified hyperparameter range for this to work
custom_hyperparameters = {'Imputer': {
'numeric_impute_strategy': Categorical(['median', 'most_frequent'])
}}
# set the initial batch numeric impute strategy strategy to 'median'
pipeline_parameters = {'Imputer': {
'numeric_impute_strategy': 'median'
}}
# using this custom hyperparameter means that our Imputer components in these pipelines will only search through
# 'median' and 'most_frequent' strategies for 'numeric_impute_strategy', and the initial batch parameter will be
# set to 'median'
automl_constrained = AutoMLSearch(X_train=X, y_train=y, problem_type='binary',
pipeline_parameters=pipeline_parameters,
custom_hyperparameters=custom_hyperparameters,
verbose=True)
Number of Features
Boolean 1
Categorical 6
Numeric 5
Number of training examples: 1000
Targets
False 85.90%
True 14.10%
Name: fraud, dtype: object
Using default limit of max_batches=4.
Imbalanced Data¶
The AutoML search algorithm now has functionality to handle imbalanced data during classification! AutoMLSearch now provides two additional parameters, sampler_method
and sampler_balanced_ratio
, that allow you to let AutoMLSearch know whether to sample imbalanced data, and how to do so. sampler_method
takes in either Undersampler
, Oversampler
, auto
, or None as the sampler to use, and sampler_balanced_ratio
specifies the minority/majority
ratio that you want to
sample to. Details on the Undersampler and Oversampler components can be found in the documentation.
This can be used for imbalanced datasets, like the fraud dataset, which has a ‘minority:majority’ ratio of < 0.2.
[20]:
automl_auto = AutoMLSearch(X_train=X, y_train=y, problem_type='binary', automl_algorithm='iterative')
automl_auto.allowed_pipelines[-1]
[20]:
pipeline = BinaryClassificationPipeline(component_graph={'Label Encoder': ['Label Encoder', 'X', 'y'], 'DateTime Featurizer': ['DateTime Featurizer', 'X', 'Label Encoder.y'], 'Imputer': ['Imputer', 'DateTime Featurizer.x', 'Label Encoder.y'], 'One Hot Encoder': ['One Hot Encoder', 'Imputer.x', 'Label Encoder.y'], 'Oversampler': ['Oversampler', 'One Hot Encoder.x', 'Label Encoder.y'], 'Extra Trees Classifier': ['Extra Trees Classifier', 'Oversampler.x', 'Oversampler.y']}, parameters={'Label Encoder':{'positive_label': None}, 'DateTime Featurizer':{'features_to_extract': ['year', 'month', 'day_of_week', 'hour'], 'encode_as_categories': False, 'time_index': None}, 'Imputer':{'categorical_impute_strategy': 'most_frequent', 'numeric_impute_strategy': 'mean', 'categorical_fill_value': None, 'numeric_fill_value': None}, 'One Hot Encoder':{'top_n': 10, 'features_to_encode': None, 'categories': None, 'drop': 'if_binary', 'handle_unknown': 'ignore', 'handle_missing': 'error'}, 'Oversampler':{'sampling_ratio': 0.25, 'k_neighbors_default': 5, 'n_jobs': -1, 'sampling_ratio_dict': None}, 'Extra Trees Classifier':{'n_estimators': 100, 'max_features': 'auto', 'max_depth': 6, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'n_jobs': -1}}, random_seed=0)
The Oversampler is chosen as the default sampling component here, since the sampler_balanced_ratio = 0.25
. If you specified a lower ratio, for instance sampler_balanced_ratio = 0.1
, then there would be no sampling component added here. This is because if a ratio of 0.1 would be considered balanced, then a ratio of 0.2 would also be balanced.
The Oversampler uses SMOTE under the hood, and automatically selects whether to use SMOTE, SMOTEN, or SMOTENC based on the data it receives.
[21]:
automl_auto_ratio = AutoMLSearch(X_train=X, y_train=y, problem_type='binary', sampler_balanced_ratio=0.1, automl_algorithm='iterative')
automl_auto_ratio.allowed_pipelines[-1]
[21]:
pipeline = BinaryClassificationPipeline(component_graph={'Label Encoder': ['Label Encoder', 'X', 'y'], 'DateTime Featurizer': ['DateTime Featurizer', 'X', 'Label Encoder.y'], 'Imputer': ['Imputer', 'DateTime Featurizer.x', 'Label Encoder.y'], 'One Hot Encoder': ['One Hot Encoder', 'Imputer.x', 'Label Encoder.y'], 'Extra Trees Classifier': ['Extra Trees Classifier', 'One Hot Encoder.x', 'Label Encoder.y']}, parameters={'Label Encoder':{'positive_label': None}, 'DateTime Featurizer':{'features_to_extract': ['year', 'month', 'day_of_week', 'hour'], 'encode_as_categories': False, 'time_index': None}, 'Imputer':{'categorical_impute_strategy': 'most_frequent', 'numeric_impute_strategy': 'mean', 'categorical_fill_value': None, 'numeric_fill_value': None}, 'One Hot Encoder':{'top_n': 10, 'features_to_encode': None, 'categories': None, 'drop': 'if_binary', 'handle_unknown': 'ignore', 'handle_missing': 'error'}, 'Extra Trees Classifier':{'n_estimators': 100, 'max_features': 'auto', 'max_depth': 6, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'n_jobs': -1}}, random_seed=0)
Additionally, you can add more fine-grained sampling ratios by passing in a sampling_ratio_dict
in pipeline parameters. For this dictionary, AutoMLSearch expects the keys to be int values from 0 to n-1
for the classes, and the values would be the sampler_balanced__ratio
associated with each target. This dictionary would override the AutoML argument sampler_balanced_ratio
. Below, you can see the scenario for Oversampler component on this dataset. Note that the logic for
Undersamplers is included in the commented section.
[22]:
# In this case, the majority class is the negative class
# for the oversampler, we don't want to oversample this class, so class 0 (majority) will have a ratio of 1 to itself
# for the minority class 1, we want to oversample it to have a minority/majority ratio of 0.5, which means we want minority to have 1/2 the samples as the minority
sampler_ratio_dict = {0: 1, 1: 0.5}
pipeline_parameters = {"Oversampler": {"sampler_balanced_ratio": sampler_ratio_dict}}
automl_auto_ratio_dict = AutoMLSearch(X_train=X, y_train=y, problem_type='binary', pipeline_parameters=pipeline_parameters, automl_algorithm='iterative')
automl_auto_ratio_dict.allowed_pipelines[-1]
# Undersampler case
# we don't want to undersample this class, so class 1 (minority) will have a ratio of 1 to itself
# for the majority class 0, we want to undersample it to have a minority/majority ratio of 0.5, which means we want majority to have 2x the samples as the minority
# sampler_ratio_dict = {0: 0.5, 1: 1}
# pipeline_parameters = {"Oversampler": {"sampler_balanced_ratio": sampler_ratio_dict}}
# automl_auto_ratio_dict = AutoMLSearch(X_train=X, y_train=y, problem_type='binary', pipeline_parameters=pipeline_parameters)
[22]:
pipeline = BinaryClassificationPipeline(component_graph={'Label Encoder': ['Label Encoder', 'X', 'y'], 'DateTime Featurizer': ['DateTime Featurizer', 'X', 'Label Encoder.y'], 'Imputer': ['Imputer', 'DateTime Featurizer.x', 'Label Encoder.y'], 'One Hot Encoder': ['One Hot Encoder', 'Imputer.x', 'Label Encoder.y'], 'Oversampler': ['Oversampler', 'One Hot Encoder.x', 'Label Encoder.y'], 'Extra Trees Classifier': ['Extra Trees Classifier', 'Oversampler.x', 'Oversampler.y']}, parameters={'Label Encoder':{'positive_label': None}, 'DateTime Featurizer':{'features_to_extract': ['year', 'month', 'day_of_week', 'hour'], 'encode_as_categories': False, 'time_index': None}, 'Imputer':{'categorical_impute_strategy': 'most_frequent', 'numeric_impute_strategy': 'mean', 'categorical_fill_value': None, 'numeric_fill_value': None}, 'One Hot Encoder':{'top_n': 10, 'features_to_encode': None, 'categories': None, 'drop': 'if_binary', 'handle_unknown': 'ignore', 'handle_missing': 'error'}, 'Oversampler':{'sampling_ratio': 0.25, 'k_neighbors_default': 5, 'n_jobs': -1, 'sampling_ratio_dict': None, 'sampler_balanced_ratio': {0: 1, 1: 0.5}}, 'Extra Trees Classifier':{'n_estimators': 100, 'max_features': 'auto', 'max_depth': 6, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'n_jobs': -1}}, random_seed=0)
Adding ensemble methods to AutoML¶
Stacking¶
Stacking is an ensemble machine learning algorithm that involves training a model to best combine the predictions of several base learning algorithms. First, each base learning algorithms is trained using the given data. Then, the combining algorithm or meta-learner is trained on the predictions made by those base learning algorithms to make a final prediction.
AutoML enables stacking using the ensembling
flag during initalization; this is set to False
by default. The stacking ensemble pipeline runs in its own batch after a whole cycle of training has occurred (each allowed pipeline trains for one batch). Note that this means a large number of iterations may need to run before the stacking ensemble runs. It is also important to note that only the first CV fold is calculated for stacking ensembles because the model internally uses CV
folds.
[23]:
X, y = evalml.demos.load_breast_cancer()
automl_with_ensembling = AutoMLSearch(X_train=X, y_train=y,
problem_type="binary",
allowed_model_families=[ModelFamily.LINEAR_MODEL],
max_batches=4,
ensembling=True,
automl_algorithm='iterative',
verbose=True)
automl_with_ensembling.search()
Number of Features
Numeric 30
Number of training examples: 569
Targets
benign 62.74%
malignant 37.26%
Name: target, dtype: object
Generating pipelines to search over...
Ensembling will run every 3 batches.
2 pipelines ready for search.
*****************************
* Beginning pipeline search *
*****************************
Optimizing for Log Loss Binary.
Lower score is better.
Using SequentialEngine to train and score pipelines.
Searching up to 4 batches for a total of 14 pipelines.
Allowed model families: linear_model, linear_model
Evaluating Baseline Pipeline: Mode Baseline Binary Classification Pipeline
Mode Baseline Binary Classification Pipeline:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 12.868
*****************************
* Evaluating Batch Number 1 *
*****************************
Elastic Net Classifier w/ Label Encoder + Imputer + Standard Scaler:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.077
Logistic Regression Classifier w/ Label Encoder + Imputer + Standard Scaler:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.077
*****************************
* Evaluating Batch Number 2 *
*****************************
Logistic Regression Classifier w/ Label Encoder + Imputer + Standard Scaler:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.097
Logistic Regression Classifier w/ Label Encoder + Imputer + Standard Scaler:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.080
Logistic Regression Classifier w/ Label Encoder + Imputer + Standard Scaler:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.085
Logistic Regression Classifier w/ Label Encoder + Imputer + Standard Scaler:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.091
Logistic Regression Classifier w/ Label Encoder + Imputer + Standard Scaler:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.097
*****************************
* Evaluating Batch Number 3 *
*****************************
Elastic Net Classifier w/ Label Encoder + Imputer + Standard Scaler:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.075
Elastic Net Classifier w/ Label Encoder + Imputer + Standard Scaler:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.075
Elastic Net Classifier w/ Label Encoder + Imputer + Standard Scaler:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.075
Elastic Net Classifier w/ Label Encoder + Imputer + Standard Scaler:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.076
Elastic Net Classifier w/ Label Encoder + Imputer + Standard Scaler:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.079
*****************************
* Evaluating Batch Number 4 *
*****************************
Stacked Ensemble Classification Pipeline:
Starting cross validation
Finished cross validation - mean Log Loss Binary: 0.103
Search finished after 00:21
Best pipeline: Elastic Net Classifier w/ Label Encoder + Imputer + Standard Scaler
Best pipeline Log Loss Binary: 0.075387
We can view more information about the stacking ensemble pipeline (which was the best performing pipeline) by calling .describe()
.
[24]:
automl_with_ensembling.best_pipeline.describe()
***********************************************************************
* Elastic Net Classifier w/ Label Encoder + Imputer + Standard Scaler *
***********************************************************************
Problem Type: binary
Model Family: Linear
Number of features: 30
Pipeline Steps
==============
1. Label Encoder
* positive_label : None
2. Imputer
* categorical_impute_strategy : most_frequent
* numeric_impute_strategy : median
* categorical_fill_value : None
* numeric_fill_value : None
3. Standard Scaler
4. Elastic Net Classifier
* penalty : elasticnet
* C : 8.123565600467177
* l1_ratio : 0.47997717237505744
* n_jobs : -1
* multi_class : auto
* solver : saga
Access raw results¶
The AutoMLSearch
class records detailed results information under the results
field, including information about the cross-validation scoring and parameters.
[25]:
import pprint
pp = pprint.PrettyPrinter(indent=0, width=100, depth=3, compact=True, sort_dicts=False)
pp.pprint(automl.results)
{'pipeline_results': {0: {'id': 0,
'pipeline_name': 'Mode Baseline Binary Classification Pipeline',
'pipeline_class': <class 'evalml.pipelines.binary_classification_pipeline.BinaryClassificationPipeline'>,
'pipeline_summary': 'Baseline Classifier w/ Label Encoder',
'parameters': {...},
'mean_cv_score': 3.970423187263591,
'standard_deviation_cv_score': 0.26606000431837074,
'high_variance_cv': False,
'training_time': 0.3067200183868408,
'cv_data': [...],
'percent_better_than_baseline_all_objectives': {...},
'percent_better_than_baseline': 0,
'validation_score': 3.970423187263591},
1: {'id': 1,
'pipeline_name': 'Logistic Regression Classifier w/ Label Encoder + Drop '
'Columns Transformer + DateTime Featurizer + Imputer + One '
'Hot Encoder + Oversampler + Standard Scaler',
'pipeline_class': <class 'evalml.pipelines.binary_classification_pipeline.BinaryClassificationPipeline'>,
'pipeline_summary': 'Logistic Regression Classifier w/ Label Encoder + Drop '
'Columns Transformer + DateTime Featurizer + Imputer + '
'One Hot Encoder + Oversampler + Standard Scaler',
'parameters': {...},
'mean_cv_score': 0.6203031849130022,
'standard_deviation_cv_score': 0.1898901497265282,
'high_variance_cv': False,
'training_time': 4.106977462768555,
'cv_data': [...],
'percent_better_than_baseline_all_objectives': {...},
'percent_better_than_baseline': 84.3769000014199,
'validation_score': 0.6203031849130022},
2: {'id': 2,
'pipeline_name': 'Random Forest Classifier w/ Label Encoder + Drop Columns '
'Transformer + DateTime Featurizer + Imputer + One Hot '
'Encoder + Oversampler',
'pipeline_class': <class 'evalml.pipelines.binary_classification_pipeline.BinaryClassificationPipeline'>,
'pipeline_summary': 'Random Forest Classifier w/ Label Encoder + Drop '
'Columns Transformer + DateTime Featurizer + Imputer + '
'One Hot Encoder + Oversampler',
'parameters': {...},
'mean_cv_score': 0.29226383693917385,
'standard_deviation_cv_score': 0.032252391685893105,
'high_variance_cv': False,
'training_time': 2.5096585750579834,
'cv_data': [...],
'percent_better_than_baseline_all_objectives': {...},
'percent_better_than_baseline': 92.63897516323439,
'validation_score': 0.29226383693917385},
3: {'id': 3,
'pipeline_name': 'Logistic Regression Classifier w/ Label Encoder + Drop '
'Columns Transformer + DateTime Featurizer + Imputer + One '
'Hot Encoder + Oversampler + Standard Scaler + RF '
'Classifier Select From Model',
'pipeline_class': <class 'evalml.pipelines.binary_classification_pipeline.BinaryClassificationPipeline'>,
'pipeline_summary': 'Logistic Regression Classifier w/ Label Encoder + Drop '
'Columns Transformer + DateTime Featurizer + Imputer + '
'One Hot Encoder + Oversampler + Standard Scaler + RF '
'Classifier Select From Model',
'parameters': {...},
'mean_cv_score': 0.5734078967814726,
'standard_deviation_cv_score': 0.12127741139077003,
'high_variance_cv': False,
'training_time': 2.9469680786132812,
'cv_data': [...],
'percent_better_than_baseline_all_objectives': {...},
'percent_better_than_baseline': 85.55801561352799,
'validation_score': 0.5734078967814726},
4: {'id': 4,
'pipeline_name': 'Random Forest Classifier w/ Label Encoder + Drop Columns '
'Transformer + DateTime Featurizer + Imputer + One Hot '
'Encoder + Oversampler + RF Classifier Select From Model',
'pipeline_class': <class 'evalml.pipelines.binary_classification_pipeline.BinaryClassificationPipeline'>,
'pipeline_summary': 'Random Forest Classifier w/ Label Encoder + Drop '
'Columns Transformer + DateTime Featurizer + Imputer + '
'One Hot Encoder + Oversampler + RF Classifier Select '
'From Model',
'parameters': {...},
'mean_cv_score': 0.2889637652674953,
'standard_deviation_cv_score': 0.042650791292308726,
'high_variance_cv': False,
'training_time': 2.873220920562744,
'cv_data': [...],
'percent_better_than_baseline_all_objectives': {...},
'percent_better_than_baseline': 92.72209153436239,
'validation_score': 0.2889637652674953},
5: {'id': 5,
'pipeline_name': 'Decision Tree Classifier w/ Label Encoder + Select '
'Columns By Type Transformer + Label Encoder + Drop '
'Columns Transformer + DateTime Featurizer + Imputer + '
'Oversampler + Select Columns Transformer + Select Columns '
'Transformer + Label Encoder + Imputer + One Hot Encoder + '
'Oversampler',
'pipeline_class': <class 'evalml.pipelines.binary_classification_pipeline.BinaryClassificationPipeline'>,
'pipeline_summary': 'Decision Tree Classifier w/ Label Encoder + Select '
'Columns By Type Transformer + Label Encoder + Drop '
'Columns Transformer + DateTime Featurizer + Imputer + '
'Oversampler + Select Columns Transformer + Select '
'Columns Transformer + Label Encoder + Imputer + One '
'Hot Encoder + Oversampler',
'parameters': {...},
'mean_cv_score': 3.6558567318906334,
'standard_deviation_cv_score': 1.311021926363768,
'high_variance_cv': True,
'training_time': 1.9366767406463623,
'cv_data': [...],
'percent_better_than_baseline_all_objectives': {...},
'percent_better_than_baseline': 7.922743761471843,
'validation_score': 3.6558567318906334},
6: {'id': 6,
'pipeline_name': 'LightGBM Classifier w/ Label Encoder + Select Columns By '
'Type Transformer + Label Encoder + Drop Columns '
'Transformer + DateTime Featurizer + Imputer + Oversampler '
'+ Select Columns Transformer + Select Columns Transformer '
'+ Label Encoder + Imputer + One Hot Encoder + Oversampler',
'pipeline_class': <class 'evalml.pipelines.binary_classification_pipeline.BinaryClassificationPipeline'>,
'pipeline_summary': 'LightGBM Classifier w/ Label Encoder + Select Columns '
'By Type Transformer + Label Encoder + Drop Columns '
'Transformer + DateTime Featurizer + Imputer + '
'Oversampler + Select Columns Transformer + Select '
'Columns Transformer + Label Encoder + Imputer + One '
'Hot Encoder + Oversampler',
'parameters': {...},
'mean_cv_score': 0.31641155204854204,
'standard_deviation_cv_score': 0.15607221058209464,
'high_variance_cv': False,
'training_time': 2.149409055709839,
'cv_data': [...],
'percent_better_than_baseline_all_objectives': {...},
'percent_better_than_baseline': 92.03078520537726,
'validation_score': 0.31641155204854204},
7: {'id': 7,
'pipeline_name': 'Extra Trees Classifier w/ Label Encoder + Select Columns '
'By Type Transformer + Label Encoder + Drop Columns '
'Transformer + DateTime Featurizer + Imputer + Oversampler '
'+ Select Columns Transformer + Select Columns Transformer '
'+ Label Encoder + Imputer + One Hot Encoder + Oversampler',
'pipeline_class': <class 'evalml.pipelines.binary_classification_pipeline.BinaryClassificationPipeline'>,
'pipeline_summary': 'Extra Trees Classifier w/ Label Encoder + Select '
'Columns By Type Transformer + Label Encoder + Drop '
'Columns Transformer + DateTime Featurizer + Imputer + '
'Oversampler + Select Columns Transformer + Select '
'Columns Transformer + Label Encoder + Imputer + One '
'Hot Encoder + Oversampler',
'parameters': {...},
'mean_cv_score': 0.3440507438306253,
'standard_deviation_cv_score': 0.004555135479468251,
'high_variance_cv': False,
'training_time': 2.7474517822265625,
'cv_data': [...],
'percent_better_than_baseline_all_objectives': {...},
'percent_better_than_baseline': 91.33465810560752,
'validation_score': 0.3440507438306253},
8: {'id': 8,
'pipeline_name': 'Elastic Net Classifier w/ Label Encoder + Select Columns '
'By Type Transformer + Label Encoder + Drop Columns '
'Transformer + DateTime Featurizer + Imputer + Oversampler '
'+ Standard Scaler + Select Columns Transformer + Select '
'Columns Transformer + Label Encoder + Imputer + One Hot '
'Encoder + Oversampler + Standard Scaler',
'pipeline_class': <class 'evalml.pipelines.binary_classification_pipeline.BinaryClassificationPipeline'>,
'pipeline_summary': 'Elastic Net Classifier w/ Label Encoder + Select '
'Columns By Type Transformer + Label Encoder + Drop '
'Columns Transformer + DateTime Featurizer + Imputer + '
'Oversampler + Standard Scaler + Select Columns '
'Transformer + Select Columns Transformer + Label '
'Encoder + Imputer + One Hot Encoder + Oversampler + '
'Standard Scaler',
'parameters': {...},
'mean_cv_score': 0.6168022266338659,
'standard_deviation_cv_score': 0.19506490263118442,
'high_variance_cv': False,
'training_time': 2.6298067569732666,
'cv_data': [...],
'percent_better_than_baseline_all_objectives': {...},
'percent_better_than_baseline': 84.46507594927269,
'validation_score': 0.6168022266338659},
9: {'id': 9,
'pipeline_name': 'CatBoost Classifier w/ Label Encoder + Select Columns By '
'Type Transformer + Label Encoder + Drop Columns '
'Transformer + DateTime Featurizer + Imputer + Oversampler '
'+ Select Columns Transformer + Select Columns Transformer '
'+ Label Encoder + Imputer + Oversampler',
'pipeline_class': <class 'evalml.pipelines.binary_classification_pipeline.BinaryClassificationPipeline'>,
'pipeline_summary': 'CatBoost Classifier w/ Label Encoder + Select Columns '
'By Type Transformer + Label Encoder + Drop Columns '
'Transformer + DateTime Featurizer + Imputer + '
'Oversampler + Select Columns Transformer + Select '
'Columns Transformer + Label Encoder + Imputer + '
'Oversampler',
'parameters': {...},
'mean_cv_score': 0.6235053667688665,
'standard_deviation_cv_score': 0.003045978532889411,
'high_variance_cv': False,
'training_time': 1.8771638870239258,
'cv_data': [...],
'percent_better_than_baseline_all_objectives': {...},
'percent_better_than_baseline': 84.29624910591494,
'validation_score': 0.6235053667688665},
10: {'id': 10,
'pipeline_name': 'XGBoost Classifier w/ Label Encoder + Select Columns By '
'Type Transformer + Label Encoder + Drop Columns '
'Transformer + DateTime Featurizer + Imputer + '
'Oversampler + Select Columns Transformer + Select '
'Columns Transformer + Label Encoder + Imputer + One Hot '
'Encoder + Oversampler',
'pipeline_class': <class 'evalml.pipelines.binary_classification_pipeline.BinaryClassificationPipeline'>,
'pipeline_summary': 'XGBoost Classifier w/ Label Encoder + Select Columns '
'By Type Transformer + Label Encoder + Drop Columns '
'Transformer + DateTime Featurizer + Imputer + '
'Oversampler + Select Columns Transformer + Select '
'Columns Transformer + Label Encoder + Imputer + One '
'Hot Encoder + Oversampler',
'parameters': {...},
'mean_cv_score': 0.27090419801325555,
'standard_deviation_cv_score': 0.16166501290168173,
'high_variance_cv': False,
'training_time': 2.82879376411438,
'cv_data': [...],
'percent_better_than_baseline_all_objectives': {...},
'percent_better_than_baseline': 93.17694398717326,
'validation_score': 0.27090419801325555},
11: {'id': 11,
'pipeline_name': 'Stacked Ensemble Classification Pipeline',
'pipeline_class': <class 'evalml.pipelines.binary_classification_pipeline.BinaryClassificationPipeline'>,
'pipeline_summary': 'Stacked Ensemble Classifier w/ Label Encoder + Label '
'Encoder + Drop Columns Transformer + DateTime '
'Featurizer + Imputer + One Hot Encoder + Oversampler '
'+ Standard Scaler + RF Classifier Select From Model + '
'Logistic Regression Classifier + Label Encoder + Drop '
'Columns Transformer + DateTime Featurizer + Imputer + '
'One Hot Encoder + Oversampler + RF Classifier Select '
'From Model + Random Forest Classifier + Label Encoder '
'+ Select Columns By Type Transformer + Label Encoder '
'+ Drop Columns Transformer + DateTime Featurizer + '
'Imputer + Oversampler + Select Columns Transformer + '
'Select Columns Transformer + Label Encoder + Imputer '
'+ One Hot Encoder + Oversampler + Decision Tree '
'Classifier + Label Encoder + Select Columns By Type '
'Transformer + Label Encoder + Drop Columns '
'Transformer + DateTime Featurizer + Imputer + '
'Oversampler + Select Columns Transformer + Select '
'Columns Transformer + Label Encoder + Imputer + One '
'Hot Encoder + Oversampler + LightGBM Classifier + '
'Label Encoder + Select Columns By Type Transformer + '
'Label Encoder + Drop Columns Transformer + DateTime '
'Featurizer + Imputer + Oversampler + Select Columns '
'Transformer + Select Columns Transformer + Label '
'Encoder + Imputer + One Hot Encoder + Oversampler + '
'Extra Trees Classifier + Label Encoder + Select '
'Columns By Type Transformer + Label Encoder + Drop '
'Columns Transformer + DateTime Featurizer + Imputer + '
'Oversampler + Select Columns Transformer + Select '
'Columns Transformer + Label Encoder + Imputer + '
'Oversampler + CatBoost Classifier + Label Encoder + '
'Select Columns By Type Transformer + Label Encoder + '
'Drop Columns Transformer + DateTime Featurizer + '
'Imputer + Oversampler + Select Columns Transformer + '
'Select Columns Transformer + Label Encoder + Imputer '
'+ One Hot Encoder + Oversampler + XGBoost Classifier',
'parameters': {...},
'mean_cv_score': 0.2453912669457092,
'standard_deviation_cv_score': 0.10798917363618954,
'high_variance_cv': False,
'training_time': 15.282623291015625,
'cv_data': [...],
'percent_better_than_baseline_all_objectives': {...},
'percent_better_than_baseline': 93.81951859104389,
'validation_score': 0.2453912669457092,
'input_pipeline_ids': [...]}},
'search_order': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]}
Parallel AutoML¶
By default, all pipelines in an AutoML batch are evaluated in series. Pipelines can be evaluated in parallel to improve performance during AutoML search. This is accomplished by a futures style submission and evaluation of pipelines in a batch. As of this writing, the pipelines use a threaded model for concurrent evaluation. This is similar to the currently implemented n_jobs
parameter in the estimators, which uses increased numbers of threads to train and evaluate estimators.
Quick Start¶
To quickly use some parallelism to enhance the pipeline searching, a string can be passed through to AutoMLSearch during initialization to setup the parallel engine and client within the AutoMLSearch object. The current options are “cf_threaded”, “cf_process”, “dask_threaded” and “dask_process” and indicate the futures backend to use and whether to use threaded- or process-level parallelism.
[26]:
automl_cf_threaded = AutoMLSearch(X_train=X, y_train=y,
problem_type="binary",
allowed_model_families=[ModelFamily.LINEAR_MODEL],
engine="cf_threaded")
automl_cf_threaded.search(show_iteration_plot = False)
automl_cf_threaded.close_engine()
High coefficient of variation (cv >= 0.5) within cross validation scores.
Decision Tree Classifier w/ Label Encoder + Imputer + Select Columns Transformer may not perform as estimated on unseen data.
Parallelism with Concurrent Futures¶
The EngineBase
class is robust and extensible enough to support futures-like implementations from a variety of libraries. The CFEngine
extends the EngineBase
to use the native Python concurrent.futures library. The CFEngine
supports both thread- and process-level parallelism. The type of parallelism can be chosen using either the ThreadPoolExecutor
or the ProcessPoolExecutor
. If either executor is passed a
max_workers
parameter, it will set the number of processes and threads spawned. If not, the default number of processes will be equal to the number of processors available and the number of threads set to five times the number of processors available.
Here, the CFEngine is invoked with default parameters, which is threaded parallelism using all available threads.
[27]:
from concurrent.futures import ThreadPoolExecutor
from evalml.automl.engine.cf_engine import CFEngine, CFClient
cf_engine = CFEngine(CFClient(ThreadPoolExecutor(max_workers=4)))
automl_cf_threaded = AutoMLSearch(X_train=X, y_train=y,
problem_type="binary",
allowed_model_families=[ModelFamily.LINEAR_MODEL],
engine=cf_engine)
automl_cf_threaded.search(show_iteration_plot = False)
automl_cf_threaded.close_engine()
High coefficient of variation (cv >= 0.5) within cross validation scores.
Decision Tree Classifier w/ Label Encoder + Imputer + Select Columns Transformer may not perform as estimated on unseen data.
Note: the cell demonstrating process-level parallelism is a markdown due to incompatibility with our ReadTheDocs build. It can be run successfully locally.
from concurrent.futures import ProcessPoolExecutor
# Repeat the process but using process-level parallelism\
cf_engine = CFEngine(CFClient(ProcessPoolExecutor(max_workers=2)))
automl_cf_process = AutoMLSearch(X_train=X, y_train=y,
problem_type="binary",
engine="cf_process")
automl_cf_process.search(show_iteration_plot = False)
automl_cf_process.close_engine()
Parallelism with Dask¶
Thread or process level parallelism can be explicitly invoked for the DaskEngine
(as well as the CFEngine
). The processes
can be set to True
and the number of processes set using n_workers
. If processes
is set to False
, then the resulting parallelism will be threaded and n_workers
will represent the threads used. Examples of both follow.
[28]:
from dask.distributed import LocalCluster
from evalml.automl.engine import DaskEngine
dask_engine_p2 = DaskEngine(cluster=LocalCluster(processes=True, n_workers = 2))
automl_dask_p2 = AutoMLSearch(X_train=X, y_train=y,
problem_type="binary",
allowed_model_families=[ModelFamily.LINEAR_MODEL],
engine=dask_engine_p2)
automl_dask_p2.search(show_iteration_plot = False)
# Explicitly shutdown the automl object's LocalCluster
automl_dask_p2.close_engine()
High coefficient of variation (cv >= 0.5) within cross validation scores.
Decision Tree Classifier w/ Label Encoder + Imputer + Select Columns Transformer may not perform as estimated on unseen data.
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
Loky-backed parallel loops cannot be called in a multiprocessing, setting n_jobs=1
[29]:
dask_engine_t4 = DaskEngine(cluster=LocalCluster(processes=False, n_workers = 4))
automl_dask_t4 = AutoMLSearch(X_train=X, y_train=y,
problem_type="binary",
allowed_model_families=[ModelFamily.LINEAR_MODEL],
engine=dask_engine_t4)
automl_dask_t4.search(show_iteration_plot = False)
automl_dask_t4.close_engine()
High coefficient of variation (cv >= 0.5) within cross validation scores.
Decision Tree Classifier w/ Label Encoder + Imputer + Select Columns Transformer may not perform as estimated on unseen data.
As we can see, a significant performance gain can result from simply using something other than the default SequentialEngine
, ranging from a 100% speed up with multiple processes to 500% speedup with multiple threads!
[30]:
print("Sequential search duration: %s" % str(automl.search_duration))
print("Concurrent futures (threaded) search duration: %s" % str(automl_cf_threaded.search_duration))
print("Dask (two processes) search duration: %s" % str(automl_dask_p2.search_duration))
print("Dask (four threads)search duration: %s" % str(automl_dask_t4.search_duration))
Sequential search duration: 44.92300033569336
Concurrent futures (threaded) search duration: 31.124618768692017
Dask (two processes) search duration: 36.90347981452942
Dask (four threads)search duration: 35.46495747566223