knitr::opts_chunk$set( collapse = TRUE, comment = "#>" )
One particularly important aspect of machine learning (ML) is hyperparameter tuning. A hyperparameter is a parameter that is set before the ML training begins. These parameters are tunable and they effect how well the model trains. We must do a grid search for many hyperparameter possibilities and exhaust our search to pick the ideal value for the model and dataset. In this package, we do this during the cross-validation step.
Let's start with an example ML run.
The input data to run_ml()
is a dataframe where each row is a sample or observation.
One column (assumed to be the first) is the outcome of interest,
and all of the other columns are the features.
We package otu_mini_bin
as a small example dataset with mikropml
.
# install.packages("devtools") # devtools::install_github("SchlossLab/mikropml") library(mikropml) head(otu_mini_bin)
Before we train and evaluate a ML model, we can preprocess the data.
You can learn more about this in the preprocessing vignette: vignette("preprocess")
.
preproc <- preprocess_data( dataset = otu_mini_bin, outcome_colname = "dx" ) dat <- preproc$dat_transformed
We'll use dat
for the following examples.
run_ml()
As mentioned above, the minimal input is your dataset (dataset
) and the machine learning model you want to use (method
).
When we run_ml()
, by default we do a 100 times repeated, 5-fold cross-validation,
where we evaluate the hyperparameters in these 500 total iterations.
Say we want to run L2 regularized logistic regression. We do this with:
results <- run_ml(dat, "glmnet", outcome_colname = "dx", cv_times = 100, seed = 2019 )
You'll probably get a warning when you run this because the dataset is very small. If you want to learn more about that, check out the introductory vignette about training and evaluating a ML model: vignette("introduction")
.
By default, run_ml()
selects hyperparameters depending on the dataset and method used.
results$trained_model
As you can see, the alpha
hyperparameter is set to 0, which specifies L2 regularization.
glmnet
gives us the option to run both L1 and L2 regularization.
If we change alpha
to 1, we would run L1-regularized logistic regression.
You can also tune alpha
by specifying a variety of values between 0 and 1.
When you use a value that is between 0 and 1, you are running elastic net.
The default hyperparameter lambda
which adjusts the L2 regularization penalty is a range of values between 10^-4 to 10.
When we look at the 100 repeated cross-validation performance metrics such as
AUC
, Accuracy
, prAUC
for each tested lambda
value,
we see that some are not appropriate for this dataset and some do better than others.
results$trained_model$results
In this example, we want to change the lambda
values to provide a better range to test in the cross-validation step.
We don't want to use the defaults but provide our own named list with new values.
For example:
new_hp <- list( alpha = 1, lambda = c(0.00001, 0.0001, 0.001, 0.01, 0.015, 0.02, 0.025, 0.03, 0.04, 0.05, 0.06, 0.1) ) new_hp
Now let's run L2 logistic regression with the new lambda
values:
results <- run_ml(dat, "glmnet", outcome_colname = "dx", cv_times = 100, hyperparameters = new_hp, seed = 2019 ) results$trained_model
This time, we cover a larger and different range of lambda
settings in cross-validation.
How do we know which lambda
value is the best one?
To answer that, we need to run the ML pipeline on multiple data splits
and look at the mean cross-validation performance of each lambda
across those modeling experiments.
We describe how to run the pipeline with multiple data splits in vignette("parallel")
.
Here we train the model with the new lambda
range we defined above.
We run it 3 times each with a different seed, which will result in different
splits of the data into training and testing sets.
We can then use plot_hp_performance
to see which lambda
gives us the largest mean AUC value across modeling experiments.
results <- lapply(seq(100, 102), function(seed) { run_ml(dat, "glmnet", seed = seed, hyperparameters = new_hp) }) models <- lapply(results, function(x) x$trained_model) hp_metrics <- combine_hp_performance(models) plot_hp_performance(hp_metrics$dat, lambda, AUC)
As you can see, we get a mean maxima at 0.03
which is the best lambda
value
for this dataset when we run 3 data splits.
The fact that we are seeing this maxima in the middle of our range and not at the edges,
shows that we are providing a large enough range to exhaust our lambda
search
as we build the model.
We recommend the user to use this plot to make sure the best hyperparameter
is not on the edges of the provided list.
For a better understanding of the global maxima,
it would be better to run more data splits by using more seeds.
We picked 3 seeds to keep the runtime down for this vignette,
but for real-world data we recommend using many more seeds.
You can see which default hyperparameters would be used for your dataset with get_hyperparams_list()
.
Here are a few examples with built-in datasets we provide:
get_hyperparams_list(otu_mini_bin, "glmnet") get_hyperparams_list(otu_mini_bin, "rf") get_hyperparams_list(otu_small, "rf")
Here are the hyperparameters that are tuned for each of the modeling methods. The output for all of them is very similar, so we won't go into those details.
As mentioned above, glmnet
uses the alpha
parameter and lambda
hyperparameter.
alpha
of 0
is for L2 regularization (ridge).
alpha
of 1
is for L1 regularization (lasso).
alpha
in between is elastic net. You can also tune alpha
like you would any other hyperparameter.
Please refer to original glmnet
documentation for more information:
https://web.stanford.edu/~hastie/glmnet/glmnet_alpha.html
The default hyperparameters chosen by run_ml()
are fixed for glmnet
.
mikropml:::set_hparams_glmnet()
When we run rf
or parRF
, we are using the the randomForest
package implementation.
We are tuning the mtry
hyperparameter.
This is the number of features that are randomly collected to be sampled at each tree node.
This number needs to be less than the number of features in the dataset.
Please refer to the original documentation for more information:
https://cran.r-project.org/web/packages/randomForest/randomForest.pdf
By default, we take the square root of number of features in the dataset
and we provide a range that is [sqrt_features / 2, sqrt_features, sqrt_features * 2]
.
For example if the number of features is 1000:
mikropml:::set_hparams_rf(1000)
Similar to the glmnet
method, we can provide our own mtry
range.
When we run rpart2
, we are running the rpart
package implementation of decision tree.
We are tuning the maxdepth
hyperparameter.
This is the maximum depth of any node of the final tree.
Please refer to the original documentation for more information on maxdepth:
https://cran.r-project.org/web/packages/rpart/rpart.pdf
By default, we provide a range that is less than the number of features in the dataset.
For example if we have 1000 features:
mikropml:::set_hparams_rpart2(1000)
or 10 features:
mikropml:::set_hparams_rpart2(10)
When we run the svmRadial
method, we are tuning the C
and sigma
hyperparameters.
sigma
defines how far the influence of a single training example reaches and C
behaves as a regularization parameter.
Please refer to this great sklearn
resource for more information on these hyperparameters:
https://scikit-learn.org/stable/auto_examples/svm/plot_rbf_parameters.html
By default, we provide 2 separate range of values for the two hyperparameters.
mikropml:::set_hparams_svmRadial()
When we run the xgbTree
method, we are tuning the
nrounds
, gamma
, eta
max_depth
, colsample_bytree
, min_child_weight
and subsample
hyperparameters.
You can read more about these hyperparameters here: https://xgboost.readthedocs.io/en/latest/parameter.html
By default, we set the nrounds
, gamma
, colsample_bytree
and min_child_weight
to fixed values and we provide a range of values for eta
, max_depth
and subsample
.
All of these can be changed and optimized by the user by supplying a custom
named list of hyperparameters to run_ml()
.
mikropml:::set_hparams_xgbTree(1000)
While the above ML methods are those that we have tested and set default
hyperparameters for, in theory you may be able use other methods supported by
caret with run_ml()
. Take a look at the available models in
caret (or see
here for a list by
tag). You will need to give run_ml()
your own custom hyperparameters just like
in the examples above:
run_ml(otu_mini_bin, "regLogistic", hyperparameters = list( cost = 10^seq(-4, 1, 1), epsilon = c(0.01), loss = c("L2_primal") ) )
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.