
Photo by cottonbro studio
In this post, I will explore how to leverage alternative hyperparameter tuning techniques to accelerate the tuning process and identify the hyperparameter sets that yield the best model performance.
The three methods covered are:
tune_race_anova
tune_win_loss
tune_sim_anneal
These functions are part of the finetune package.
Below is a summary based on the finetune package documentation:
Both the tune_race_anova and tune_win_loss functions begin by evaluating all hyperparameter combinations on an initial set of resamples.
The tune_race_anova function uses a repeated measures ANOVA model to eliminate hyperparameter combinations that are unlikely to yield the best results. In contrast, tune_win_loss employs pairwise win/loss statistics and fits a logistic regression model to predict the likelihood of each combination outperforming the others.
tune_sim_anneal uses a method called Simulated Annealing, which is a global optimization technique.
For model tuning, it iteratively explores the parameter space to find optimal hyperparameter combinations. At each iteration, a new combination is generated by slightly perturbing the current parameters to remain within a local neighborhood.
This new combination is then used to fit a model, and the model’s performance is evaluated using resampling (or a validation set).
If the new settings yield better performance than the current ones, they are accepted, and the process continues.
First, I will import all the necessary packages into the environment.
pacman::p_load(tidyverse, tidymodels, janitor, finetune, future, bonsai, aorsf, BradleyTerry2)
In this demonstration, I will use finetune packages to perform the different types of hyperparameter tuning.
I will be using this body performance dataset I found on Kaggle for the demonstration.
Next, I will split the dataset into training and testing datasets.
df_split <- initial_split(df, prop = 0.7, strata = class)
df_train <- training(df_split)
df_test <- testing(df_split)
df_vfold <- vfold_cv(df_train, strata = class)
Okay, let’s start building a model!
First, I will define the formula for the model.
orsf_recipe <-
recipe(class ~ .
,data = df_train)
I will also specify which model I want to build and which hyperparameters to tune.
orsf_spec <-
rand_forest(trees = tune(), min_n = tune(), mtry = tune()) %>%
set_engine("aorsf") %>%
set_mode("classification")
Next, I will define the model building workflow.
orsf_wf <-
workflow() %>%
add_recipe(orsf_recipe) %>%
add_model(orsf_spec)
I will also define the settings for hyperparameter tuning control.
race_ctrl <-
control_race(
save_pred = TRUE
,save_workflow = FALSE
,verbose_elim = TRUE
)
I will use grid_space_filling to generate the different combinations of hyperparameters.
One of the racing methods involves using a repeated measures ANOVA model to eliminate combinations of hyperparameters that are unlikely to produce good model results.
orsf_tune_anova <- tune_race_anova(
orsf_wf
,resamples = df_vfold
,grid = orsf_grid_param
,metrics = metric_set(roc_auc)
,control = race_ctrl
)
From the run log, we can see that at each fold, the different combinations of hyperparameters are evaluated.
If they’re unlikely to yield good results, those combinations are dropped from the study.
For instance, after the 10th fold, 19 hyperparameter combinations were eliminated.
By doing this, we save time by not evaluating hyperparameters that are unlikely to result in the best model.
We can also visualize how the different combinations of hyperparameters perform across different folds.
From the graph, we can see that the majority of the hyperparameter sets were dropped early in the tuning process.
plot_race(orsf_tune_anova)

Just like in traditional hyperparameter tuning, we can extract the best set of hyperparameters using the show_best function.
show_best(orsf_tune_anova)
# A tibble: 2 × 9
mtry trees min_n .metric .estimator mean n std_err .config
<int> <int> <int> <chr> <chr> <dbl> <int> <dbl> <chr>
1 8 50 3 roc_auc hand_till 0.901 10 0.00146 Preprocess…
2 8 44 1 roc_auc hand_till 0.900 10 0.00175 Preprocess…
AAnother racing method involves using win/loss statistics, which can be done with the tune_race_win_loss function from the finetune package.
orsf_tune_win_loss <- tune_race_win_loss(
orsf_wf
,resamples = df_vfold
,grid = orsf_grid_param
,metrics = metric_set(roc_auc)
,control = race_ctrl
)
As in the previous section, we can plot the tuning results using the plot_race function.
plot_race(orsf_tune_win_loss)

We can use show_best function to find the best combinations of hyperparameters.
show_best(orsf_tune_win_loss)
# A tibble: 5 × 9
mtry trees min_n .metric .estimator mean n std_err .config
<int> <int> <int> <chr> <chr> <dbl> <int> <dbl> <chr>
1 8 50 3 roc_auc hand_till 0.900 10 0.00165 Preprocess…
2 8 44 1 roc_auc hand_till 0.899 10 0.00144 Preprocess…
3 6 41 5 roc_auc hand_till 0.899 10 0.00151 Preprocess…
4 7 31 3 roc_auc hand_till 0.897 10 0.00143 Preprocess…
5 6 33 1 roc_auc hand_till 0.897 10 0.00152 Preprocess…
According to finetune package, tuning via simulated annealing optimization is an iterative search tool for finding good values.
First, I will define the model tuning control settings.
anneal_ctrl <-
control_sim_anneal(
save_workflow = FALSE
,verbose = TRUE
)
Then, I will use tune_sim_anneal function to perform model tuning.
orsf_tune_anneal <-
orsf_spec %>%
tune_sim_anneal(class ~ .
,resamples = df_vfold
,param_info = orsf_param_list
,iter = 30
,metrics = metric_set(roc_auc)
,control = anneal_ctrl
)
We can plot the results at each iteration using the autoplot function.
autoplot(orsf_tune_anneal, metric = "roc_auc", type = "performance") +
theme_bw()

Again, we can use the show_best function to find the hyperparameter combination that gives us the best model results.
show_best(orsf_tune_anneal)
# A tibble: 5 × 10
trees min_n mtry .metric .estimator mean n std_err .config
<int> <int> <int> <chr> <chr> <dbl> <int> <dbl> <chr>
1 48 4 7 roc_auc hand_till 0.900 10 0.00123 Iter27
2 48 2 6 roc_auc hand_till 0.900 10 0.00121 Iter25
3 50 1 7 roc_auc hand_till 0.900 10 0.00134 Iter5
4 46 2 8 roc_auc hand_till 0.900 10 0.00139 Iter6
5 47 1 7 roc_auc hand_till 0.899 10 0.00164 Iter22
# ℹ 1 more variable: .iter <int>
That’s all for the day!
Thanks for reading the post until the end.
Feel free to contact me through email or LinkedIn if you have any suggestions on future topics to share.
Refer to this link for the blog disclaimer.
Till next time, happy learning!

Photo by ANTONI SHKRABA production