db <- read_diabetes()
hf <- read_heart_failure()
cv <- setup_KFold(n_resamples = 10L, seed = 2026L)33 Boosting and Bayesian Additive Regression Trees in rtemis
rtemis fits gradient boosting with setup_LightGBM(), using LightGBM (Ke et al. 2017); Bayesian additive regression trees with setup_BART(), using the stochtree package; and rule ensembles with setup_LightRuleFit(), which extracts rules from LightGBM trees and fits a lasso with glmnet. This chapter examines the learning curve of boosting, the selection of the number of rounds, tuning, BART, and the rules of a rule ensemble.
33.1 The learning curve of boosting
To show the training and validation loss at every round, a model is fitted with a fixed 1000 rounds (force_nrounds), learning rate 0.05, and a validation set of 20% of the diabetes cases:
split <- resample(db, setup_StratSub(n_resamples = 1L, train_p = 0.8, seed = 2026L))
mod_lc <- train(
db[split[[1]]],
dat_validation = db[-split[[1]]],
hyperparameters = setup_LightGBM(
force_nrounds = 1000L,
learning_rate = 0.05
),
execution_config = setup_SerialExecution(seed = 2026L),
verbosity = 0L
)
plot_learning(mod_lc)get_learning_curve() returns the losses as data and plot_learning() draws them. The training loss, the mean squared error, falls at every round. The validation loss falls to its minimum at round 43 and then rises as the model fits the noise of the training cases (Section 32.3).
33.2 Selecting the number of rounds
By default, setup_LightGBM() leaves the number of rounds to be selected: train() runs internal resampling of the training data, stops each fit early when its validation loss has not improved for early_stopping_rounds rounds, and refits on all training cases with the selected number of rounds. Within 10-fold cross-validation, the selection is repeated in each outer training set:
mod_lgb <- train(
db,
hyperparameters = setup_LightGBM(),
outer_resampling_config = cv,
execution_config = setup_SerialExecution(seed = 2026L),
question = diabetes_question
)<Resampled Regression Model>
LightGBM (LightGBM gradient boosting)
⚙ Tuned using exhaustive grid search.
⟳ Tested using 10 independent folds.
<Resampled Regression Training Metrics>
Showing mean (sd) across resamples.
MAE: 37.306 (1.128)
MSE: 2038.346 (102.690)
RMSE: 45.135 (1.139)
R²: 0.656 (0.018)
<Resampled Regression Test Metrics>
Showing mean (sd) across resamples.
MAE: 46.242 (3.170)
MSE: 3249.394 (379.175)
RMSE: 56.909 (3.450)
R²: 0.449 (0.059)
Fold_1 Fold_2 Fold_3 Fold_4 Fold_5 Fold_6 Fold_7 Fold_8 Fold_9 Fold_10
263 300 234 276 273 252 230 221 247 225
The default learning rate of setup_LightGBM() is 0.01, with up to 1000 rounds and trees of at most 8 leaves.
33.3 Tuning
The learning rate and the tree size are tuned together, with the number of rounds selected by early stopping for each combination:
mod_lgb_tuned <- train(
db,
hyperparameters = setup_LightGBM(
learning_rate = tune_over(0.01, 0.05, 0.1),
num_leaves = tune_over(4L, 8L, 16L)
),
tuner_config = setup_GridSearch(
resampler_config = setup_KFold(n_resamples = 5L, seed = 2027L)
),
outer_resampling_config = cv,
execution_config = setup_SerialExecution(seed = 2026L),
verbosity = 0L
)
rbindlist(lapply(mod_lgb_tuned@models, function(m) {
as.list(m@hyperparameters@hyperparameters[c("learning_rate", "num_leaves", "nrounds")])
}), idcol = "fold") fold learning_rate num_leaves nrounds
<char> <num> <int> <int>
1: Fold_1 0.05 4 82
2: Fold_2 0.10 4 52
3: Fold_3 0.05 4 83
4: Fold_4 0.05 16 52
5: Fold_5 0.10 4 53
6: Fold_6 0.05 4 80
7: Fold_7 0.05 4 69
8: Fold_8 0.05 4 70
9: Fold_9 0.10 4 49
10: Fold_10 0.05 8 61
33.4 Bayesian additive regression trees
setup_BART() uses the priors of Chipman et al. (2010) by default: 200 trees, \(\alpha = 0.95\), \(\beta = 2\). The sampler starts from 5 iterations of the grow-from-root algorithm and retains 100 MCMC samples:
mod_bart <- train(
db,
hyperparameters = setup_BART(seed = 2026L),
outer_resampling_config = cv,
execution_config = setup_SerialExecution(seed = 2026L),
question = diabetes_question,
verbosity = 0L
)33.5 Rule ensembles
mod_rulefit <- train(
db,
hyperparameters = setup_LightRuleFit(),
execution_config = setup_SerialExecution(seed = 2026L),
question = diabetes_question,
verbosity = 0L
)The boosted trees produced 288 distinct rules, of which the lasso retained 50 with nonzero coefficients. The rules with the largest coefficients:
Rule_ID Rule
<int> <char>
1: 12 s5 > 4.7833 AND s6 > 101.5
2: 21 bmi > 27.75 AND bp > 101.5 AND s3 <= 45.5
3: 6 bmi > 26.25 AND s5 > 4.51635 AND bmi > 32.95
4: 42 bp > 101.5 AND s1 <= 180.5
5: 10 s5 > 4.7833 AND s6 <= 103.5 AND sex IN {1} AND s1 <= 199.5
6: 18 bmi > 33.4
7: 30 s5 > 4.51635 AND s2 <= 147.9 AND bmi > 26.85 AND s3 > 40.5
8: 28 bmi <= 33.05 AND s5 <= 4.909 AND s5 <= 4.16665 AND s1 > 161.5
N_Cases Coefficient
<num> <num>
1: 52 23.16
2: 41 22.07
3: 29 17.41
4: 35 16.35
5: 28 16.13
6: 28 14.87
7: 61 14.66
8: 37 -13.14
N_Cases is the number of training cases that satisfy the rule, and Coefficient the change in predicted progression for those cases. A rule lists the conditions along a tree path as extracted, and can contain conditions on the same feature that a simpler rule would combine. Rules overlap, so each coefficient is the contribution of its rule given all the others.
mod_rulefit_cv <- train(
db,
hyperparameters = setup_LightRuleFit(),
outer_resampling_config = cv,
execution_config = setup_SerialExecution(seed = 2026L),
verbosity = 0L
)33.6 Comparison
mod_glm <- train(
db,
hyperparameters = setup_GLM(),
outer_resampling_config = cv,
execution_config = setup_SerialExecution(seed = 2026L),
verbosity = 0L
)
compare_db <- rbindlist(lapply(
list(
GLM = mod_glm,
`LightGBM, default` = mod_lgb,
`LightGBM, tuned` = mod_lgb_tuned,
BART = mod_bart,
LightRuleFit = mod_rulefit_cv
),
function(m) {
data.table(
rsq = m@metrics_test@mean_metrics[["rsq"]],
rsq_sd = m@metrics_test@sd_metrics[["rsq"]]
)
}
), idcol = "model")
compare_db model rsq rsq_sd
<char> <num> <num>
1: GLM 0.4935 0.08864
2: LightGBM, default 0.4487 0.05877
3: LightGBM, tuned 0.4542 0.06918
4: BART 0.4997 0.08151
5: LightRuleFit 0.4460 0.05594
For the heart failure data:
compare_hf <- rbindlist(lapply(
list(
LightGBM = setup_LightGBM(),
BART = setup_BART(seed = 2026L)
),
function(hp) {
mod <- train(
hf,
hyperparameters = hp,
outer_resampling_config = cv,
execution_config = setup_SerialExecution(seed = 2026L),
verbosity = 0L
)
data.table(mod@metrics_test@mean_metrics[c("auc", "brier_score")])
}
), idcol = "model")
compare_hf model auc brier_score
<char> <num> <num>
1: LightGBM 0.6977 0.1438
2: BART 0.7092 0.1390
On the diabetes data, BART reaches a mean test \(R^2\) of 0.500, against 0.493 for the linear model, without tuning. The boosted models perform comparably to the random forest of Section 31.1. On these small datasets, whose structure the additive models of Chapter 23 found to be largely additive and near-linear, the boosted trees do not predict better than the linear and additive models; the differences between all of them are smaller than the variation across folds.