Nothing
# Avoid being tested on CRAN
if(Sys.getenv("GPBOOST_ALL_TESTS") == "GPBOOST_ALL_TESTS"){
context("gpboost()")
ON_WINDOWS <- .Platform$OS.type == "windows"
data(agaricus.train, package = "gpboost")
data(agaricus.test, package = "gpboost")
train <- agaricus.train
test <- agaricus.test
TOLERANCE <- 1e-6
set.seed(708L)
# [description] Every time this function is called, it adds 0.1
# to an accumulator then returns the current value.
# This is used to mock the situation where an evaluation
# metric increases every iteration
ACCUMULATOR_ENVIRONMENT <- new.env()
ACCUMULATOR_NAME <- "INCREASING_METRIC_ACUMULATOR"
assign(x = ACCUMULATOR_NAME, value = 0.0, envir = ACCUMULATOR_ENVIRONMENT)
.increasing_metric <- function(preds, dtrain) {
if (!exists(ACCUMULATOR_NAME, envir = ACCUMULATOR_ENVIRONMENT)) {
assign(ACCUMULATOR_NAME, 0.0, envir = ACCUMULATOR_ENVIRONMENT)
}
assign(
x = ACCUMULATOR_NAME
, value = get(ACCUMULATOR_NAME, envir = ACCUMULATOR_ENVIRONMENT) + 0.1
, envir = ACCUMULATOR_ENVIRONMENT
)
return(list(
name = "increasing_metric"
, value = get(ACCUMULATOR_NAME, envir = ACCUMULATOR_ENVIRONMENT)
, higher_better = TRUE
))
}
# [description] Evaluation function that always returns the
# same value
CONSTANT_METRIC_VALUE <- 0.2
.constant_metric <- function(preds, dtrain) {
return(list(
name = "constant_metric"
, value = CONSTANT_METRIC_VALUE
, higher_better = FALSE
))
}
# sample datasets to test early stopping
DTRAIN_RANDOM_REGRESSION <- gpb.Dataset(
data = as.matrix(rnorm(100L), ncol = 1L, drop = FALSE)
, label = rnorm(100L)
)
DVALID_RANDOM_REGRESSION <- gpb.Dataset(
data = as.matrix(rnorm(50L), ncol = 1L, drop = FALSE)
, label = rnorm(50L)
)
DTRAIN_RANDOM_CLASSIFICATION <- gpb.Dataset(
data = as.matrix(rnorm(120L), ncol = 1L, drop = FALSE)
, label = sample(c(0L, 1L), size = 120L, replace = TRUE)
)
DVALID_RANDOM_CLASSIFICATION <- gpb.Dataset(
data = as.matrix(rnorm(37L), ncol = 1L, drop = FALSE)
, label = sample(c(0L, 1L), size = 37L, replace = TRUE)
)
test_that("train and predict binary classification", {
nrounds <- 10L
capture.output( bst <- gpboost(
data = train$data
, label = train$label
, num_leaves = 5L
, nrounds = nrounds
, objective = "binary"
, metric = "binary_error"
) , file='NUL')
expect_false(is.null(bst$record_evals))
record_results <- gpb.get.eval.result(bst, "train", "binary_error")
expect_lt(min(record_results), 0.02)
pred <- predict(bst, test$data)
expect_equal(length(pred), 1611L)
pred1 <- predict(bst, train$data, num_iteration = 1L)
expect_equal(length(pred1), 6513L)
err_pred1 <- sum((pred1 > 0.5) != train$label) / length(train$label)
err_log <- record_results[1L]
expect_lt(abs(err_pred1 - err_log), TOLERANCE)
})
test_that("train and predict softmax", {
set.seed(708L)
lb <- as.numeric(iris$Species) - 1L
capture.output( bst <- gpboost(
data = as.matrix(iris[, -5L])
, label = lb
, num_leaves = 4L
, learning_rate = 0.05
, nrounds = 20L
, min_data = 20L
, min_hessian = 10.0
, objective = "multiclass"
, metric = "multi_error"
, num_class = 3L
) , file='NUL')
expect_false(is.null(bst$record_evals))
record_results <- gpb.get.eval.result(bst, "train", "multi_error")
expect_lt(min(record_results), 0.06)
pred <- predict(bst, as.matrix(iris[, -5L]))
expect_equal(length(pred), nrow(iris) * 3L)
})
test_that("use of multiple eval metrics works", {
metrics <- list("binary_error", "auc", "binary_logloss")
capture.output( bst <- gpboost(
data = train$data
, label = train$label
, num_leaves = 4L
, learning_rate = 1.0
, nrounds = 10L
, objective = "binary"
, metric = metrics
) , file='NUL')
expect_false(is.null(bst$record_evals))
expect_named(
bst$record_evals[["train"]]
, unlist(metrics)
, ignore.order = FALSE
, ignore.case = FALSE
)
})
test_that("gpb.Booster.upper_bound() and gpb.Booster.lower_bound() work as expected for binary classification", {
set.seed(708L)
nrounds <- 10L
bst <- gpboost(
data = train$data
, label = train$label
, num_leaves = 5L
, nrounds = nrounds
, objective = "binary"
, metric = "binary_error"
, verbose = 0
)
expect_true(abs(bst$lower_bound() - -1.590853) < TOLERANCE)
expect_true(abs(bst$upper_bound() - 1.871015) < TOLERANCE)
})
test_that("gpb.Booster.upper_bound() and gpb.Booster.lower_bound() work as expected for regression", {
set.seed(708L)
nrounds <- 10L
bst <- gpboost(
data = train$data
, label = train$label
, num_leaves = 5L
, nrounds = nrounds
, objective = "regression"
, metric = "l2"
, verbose = 0
)
expect_true(abs(bst$lower_bound() - 0.1513859) < TOLERANCE)
expect_true(abs(bst$upper_bound() - 0.9080349) < TOLERANCE)
})
test_that("gpboost() rejects negative or 0 value passed to nrounds", {
dtrain <- gpb.Dataset(train$data, label = train$label)
params <- list(objective = "regression", metric = "l2,l1")
for (nround_value in c(-10L, 0L)) {
expect_error({
bst <- gpboost(
data = dtrain
, params = params
, nrounds = nround_value
)
}, "nrounds should be greater than zero")
}
})
test_that("gpboost() performs evaluation on validation sets if they are provided", {
set.seed(708L)
dvalid1 <- gpb.Dataset(
data = train$data
, label = train$label
)
dvalid2 <- gpb.Dataset(
data = train$data
, label = train$label
)
nrounds <- 10L
capture.output( bst <- gpboost(
data = train$data
, label = train$label
, num_leaves = 5L
, nrounds = nrounds
, objective = "binary"
, metric = c(
"binary_error"
, "auc"
)
, valids = list(
"valid1" = dvalid1
, "valid2" = dvalid2
)
), file='NUL')
expect_named(
bst$record_evals
, c("train", "valid1", "valid2", "start_iter")
, ignore.order = TRUE
, ignore.case = FALSE
)
for (valid_name in c("train", "valid1", "valid2")) {
eval_results <- bst$record_evals[[valid_name]][["binary_error"]]
expect_length(eval_results[["eval"]], nrounds)
}
expect_true(abs(bst$record_evals[["train"]][["binary_error"]][["eval"]][[1L]] - 0.02226317) < TOLERANCE)
expect_true(abs(bst$record_evals[["valid1"]][["binary_error"]][["eval"]][[1L]] - 0.02226317) < TOLERANCE)
expect_true(abs(bst$record_evals[["valid2"]][["binary_error"]][["eval"]][[1L]] - 0.02226317) < TOLERANCE)
})
context("training continuation")
test_that("training continuation works", {
dtrain <- gpb.Dataset(
train$data
, label = train$label
, free_raw_data = FALSE
)
watchlist <- list(train = dtrain)
param <- list(
objective = "binary"
, metric = "binary_logloss"
, num_leaves = 5L
, learning_rate = 1.0
, verbose = 0
)
# train for 10 consecutive iterations
bst <- gpb.train(param, dtrain, nrounds = 10L, valids = watchlist, verbose = 0)
err_bst <- gpb.get.eval.result(bst, "train", "binary_logloss", 10L)
# train for 5 iterations, save, load, train for 5 more
bst1 <- gpb.train(param, dtrain, nrounds = 5L, valids = watchlist, verbose = 0)
model_file <- tempfile(fileext = ".model")
gpb.save(bst1, model_file)
bst2 <- gpb.train(param, dtrain, nrounds = 5L, valids = watchlist, init_model = bst1, verbose = 0)
err_bst2 <- gpb.get.eval.result(bst2, "train", "binary_logloss", 10L)
# evaluation metrics should be nearly identical for the model trained in 10 coonsecutive
# iterations and the one trained in 5-then-5.
expect_lt(abs(err_bst - err_bst2), 0.01)
})
context("gpb.cv()")
test_that("cv works", {
dtrain <- gpb.Dataset(train$data, label = train$label)
params <- list(objective = "regression", metric = "l2,l1")
bst <- gpb.cv(
params
, dtrain
, 10L
, nfold = 5L
, min_data = 1L
, learning_rate = 1.0
, early_stopping_rounds = 10L
, verbose = 0
)
expect_false(is.null(bst$record_evals))
})
test_that("gpb.cv() rejects negative or 0 value passed to nrounds", {
dtrain <- gpb.Dataset(train$data, label = train$label)
params <- list(objective = "regression", metric = "l2,l1")
for (nround_value in c(-10L, 0L)) {
expect_error({
bst <- gpb.cv(
params
, dtrain
, nround_value
, nfold = 5L
, min_data = 1L
, verbose = 0
)
}, "nrounds should be greater than zero")
}
})
test_that("gpb.cv() throws an informative error is 'data' is not an gpb.Dataset and labels are not given", {
bad_values <- list(
4L
, "hello"
, list(a = TRUE, b = seq_len(10L))
, data.frame(x = seq_len(5L), y = seq_len(5L))
, data.table::data.table(x = seq_len(5L), y = seq_len(5L))
, matrix(data = seq_len(10L), 2L, 5L)
)
for (val in bad_values) {
expect_error({
bst <- gpb.cv(
params = list(objective = "regression", metric = "l2,l1")
, data = val
, 10L
, nfold = 5L
, min_data = 1L
, verbose = 0
)
}, regexp = "'label' must be provided for gpb.cv if 'data' is not an 'gpb.Dataset'", fixed = TRUE)
}
})
test_that("gpboost.cv() gives the correct best_score and best_iter for a metric where higher values are better", {
set.seed(708L)
dtrain <- gpb.Dataset(
data = as.matrix(runif(n = 500L, min = 0.0, max = 15.0), drop = FALSE)
, label = rep(c(0L, 1L), 250L, verbose = 0)
)
nrounds <- 10L
cv_bst <- gpb.cv(
data = dtrain
, nfold = 5L
, nrounds = nrounds
, num_leaves = 5L
, params = list(
objective = "binary"
, metric = "auc,binary_error"
, learning_rate = 1.5
)
, verbose = 0
)
expect_is(cv_bst, "gpb.CVBooster")
expect_named(
cv_bst$record_evals
, c("start_iter", "valid")
, ignore.order = FALSE
, ignore.case = FALSE
)
auc_scores <- unlist(cv_bst$record_evals[["valid"]][["auc"]][["eval"]])
expect_length(auc_scores, nrounds)
expect_identical(cv_bst$best_iter, which.max(auc_scores))
expect_identical(cv_bst$best_score, auc_scores[which.max(auc_scores)])
})
test_that("gpb.cv() fit on linearly-relatead data improves when using linear learners", {
set.seed(708L)
.new_dataset <- function() {
X <- matrix(rnorm(1000L), ncol = 1L)
return(gpb.Dataset(
data = X
, label = 2L * X + runif(nrow(X), 0L, 0.1)
))
}
params <- list(
objective = "regression"
, verbose = -1L
, metric = "mse"
, seed = 0L
, num_leaves = 2L
)
dtrain <- .new_dataset()
cv_bst <- gpb.cv(
data = dtrain
, nrounds = 10L
, params = params
, nfold = 5L
, verbose = 0
)
expect_is(cv_bst, "gpb.CVBooster")
dtrain <- .new_dataset()
cv_bst_linear <- gpb.cv(
data = dtrain
, nrounds = 10L
, params = modifyList(params, list(linear_tree = TRUE))
, nfold = 5L
, verbose = 0
)
expect_is(cv_bst_linear, "gpb.CVBooster")
expect_true(cv_bst_linear$best_score < cv_bst$best_score)
})
test_that("gpb.cv() respects showsd argument", {
dtrain <- gpb.Dataset(train$data, label = train$label)
params <- list(objective = "regression", metric = "l2")
nrounds <- 5L
set.seed(708L)
capture.output( bst_showsd <- gpb.cv(
params = params
, data = dtrain
, nrounds = nrounds
, nfold = 3L
, min_data = 1L
, showsd = TRUE
) , file='NUL')
evals_showsd <- bst_showsd$record_evals[["valid"]][["l2"]]
set.seed(708L)
capture.output( bst_no_showsd <- gpb.cv(
params = params
, data = dtrain
, nrounds = nrounds
, nfold = 3L
, min_data = 1L
, showsd = FALSE
) , file='NUL')
evals_no_showsd <- bst_no_showsd$record_evals[["valid"]][["l2"]]
expect_equal(
evals_showsd[["eval"]]
, evals_no_showsd[["eval"]]
)
expect_is(evals_showsd[["eval_err"]], "list")
expect_equal(length(evals_showsd[["eval_err"]]), nrounds)
expect_identical(evals_no_showsd[["eval_err"]], list())
})
context("gpb.train()")
test_that("gpb.train() works as expected with multiple eval metrics", {
metrics <- c("binary_error", "auc", "binary_logloss")
capture.output( bst <- gpb.train(
data = gpb.Dataset(
train$data
, label = train$label
)
, learning_rate = 1.0
, nrounds = 10L
, params = list(
objective = "binary"
, metric = metrics
)
, valids = list(
"train" = gpb.Dataset(
train$data
, label = train$label
)
)
) , file='NUL')
expect_false(is.null(bst$record_evals))
expect_named(
bst$record_evals[["train"]]
, unlist(metrics)
, ignore.order = FALSE
, ignore.case = FALSE
)
})
test_that("gpb.train() rejects negative or 0 value passed to nrounds", {
dtrain <- gpb.Dataset(train$data, label = train$label)
params <- list(objective = "regression", metric = "l2,l1")
for (nround_value in c(-10L, 0L)) {
expect_error({
bst <- gpb.train(
params
, dtrain
, nround_value
)
}, "nrounds should be greater than zero")
}
})
test_that("gpb.train() throws an informative error if 'data' is not an gpb.Dataset", {
bad_values <- list(
4L
, "hello"
, list(a = TRUE, b = seq_len(10L))
, data.frame(x = seq_len(5L), y = seq_len(5L))
, data.table::data.table(x = seq_len(5L), y = seq_len(5L))
, matrix(data = seq_len(10L), 2L, 5L)
)
for (val in bad_values) {
expect_error({
bst <- gpb.train(
params = list(objective = "regression", metric = "l2,l1")
, data = val
, 10L
)
}, regexp = "data must be an gpb.Dataset instance", fixed = TRUE)
}
})
test_that("gpb.train() throws an informative error if 'valids' is not a list of gpb.Dataset objects", {
valids <- list(
"valid1" = data.frame(x = rnorm(5L), y = rnorm(5L))
, "valid2" = data.frame(x = rnorm(5L), y = rnorm(5L))
)
expect_error({
bst <- gpb.train(
params = list(objective = "regression", metric = "l2,l1")
, data = gpb.Dataset(train$data, label = train$label)
, 10L
, valids = valids
)
}, regexp = "valids must be a list of gpb.Dataset elements")
})
test_that("gpb.train() errors if 'valids' is a list of gpb.Dataset objects but some do not have names", {
valids <- list(
"valid1" = gpb.Dataset(matrix(rnorm(10L), 5L, 2L))
, gpb.Dataset(matrix(rnorm(10L), 2L, 5L))
)
expect_error({
bst <- gpb.train(
params = list(objective = "regression", metric = "l2,l1")
, data = gpb.Dataset(train$data, label = train$label)
, 10L
, valids = valids
)
}, regexp = "each element of valids must have a name")
})
test_that("gpb.train() throws an informative error if 'valids' contains gpb.Dataset objects but none have names", {
valids <- list(
gpb.Dataset(matrix(rnorm(10L), 5L, 2L))
, gpb.Dataset(matrix(rnorm(10L), 2L, 5L))
)
expect_error({
bst <- gpb.train(
params = list(objective = "regression", metric = "l2,l1")
, data = gpb.Dataset(train$data, label = train$label)
, 10L
, valids = valids
)
}, regexp = "each element of valids must have a name")
})
if(Sys.getenv("GPBOOST_ALL_TESTS") == "GPBOOST_ALL_TESTS"){
test_that("gpb.train() works with force_col_wise and force_row_wise", {
set.seed(1234L)
nrounds <- 10L
dtrain <- gpb.Dataset(
train$data
, label = train$label
)
params <- list(
objective = "binary"
, metric = "binary_error"
, force_col_wise = TRUE
)
bst_col_wise <- gpb.train(
params = params
, data = dtrain
, nrounds = nrounds
, verbose = 0
)
params <- list(
objective = "binary"
, metric = "binary_error"
, force_row_wise = TRUE
)
bst_row_wise <- gpb.train(
params = params
, data = dtrain
, nrounds = nrounds
, verbose = 0
)
expected_error <- 0.003070782
expect_equal(bst_col_wise$eval_train()[[1L]][["value"]], expected_error)
expect_equal(bst_row_wise$eval_train()[[1L]][["value"]], expected_error)
# check some basic details of the boosters just to be sure force_col_wise
# and force_row_wise are not causing any weird side effects
for (bst in list(bst_row_wise, bst_col_wise)) {
expect_equal(bst$current_iter(), nrounds)
parsed_model <- RJSONIO::fromJSON(bst$dump_model())
expect_equal(parsed_model$objective, "binary sigmoid:1")
expect_false(parsed_model$average_output)
}
})
}
test_that("gpb.train() works as expected with sparse features", {
set.seed(708L)
num_obs <- 70000L
trainDF <- data.frame(
y = sample(c(0L, 1L), size = num_obs, replace = TRUE)
, x = sample(c(1.0:10.0, rep(NA_real_, 50L)), size = num_obs, replace = TRUE)
)
dtrain <- gpb.Dataset(
data = as.matrix(trainDF[["x"]], drop = FALSE)
, label = trainDF[["y"]]
)
nrounds <- 1L
bst <- gpb.train(
params = list(
objective = "binary"
, min_data = 1L
, min_data_in_bin = 1L
)
, data = dtrain
, nrounds = nrounds
, verbose = 0
)
expect_true(gpboost:::gpb.is.Booster(bst))
expect_equal(bst$current_iter(), nrounds)
parsed_model <- RJSONIO::fromJSON(bst$dump_model())
expect_equal(parsed_model$objective, "binary sigmoid:1")
expect_false(parsed_model$average_output)
expected_error <- 0.6931268
expect_true(abs(bst$eval_train()[[1L]][["value"]] - expected_error) < TOLERANCE)
})
test_that("gpb.train() works with early stopping for classification", {
trainDF <- data.frame(
"feat1" = rep(c(5.0, 10.0), 500L)
, "target" = rep(c(0L, 1L), 500L)
)
validDF <- data.frame(
"feat1" = rep(c(5.0, 10.0), 50L)
, "target" = rep(c(0L, 1L), 50L)
)
dtrain <- gpb.Dataset(
data = as.matrix(trainDF[["feat1"]], drop = FALSE)
, label = trainDF[["target"]]
)
dvalid <- gpb.Dataset(
data = as.matrix(validDF[["feat1"]], drop = FALSE)
, label = validDF[["target"]]
)
nrounds <- 10L
################################
# train with no early stopping #
################################
bst <- gpb.train(
params = list(
objective = "binary"
, metric = "binary_error"
)
, data = dtrain
, nrounds = nrounds
, valids = list(
"valid1" = dvalid
)
, verbose = 0
)
# a perfect model should be trivial to obtain, but all 10 rounds
# should happen
expect_equal(bst$best_score, 0.0)
expect_equal(bst$best_iter, 1L)
expect_equal(length(bst$record_evals[["valid1"]][["binary_error"]][["eval"]]), nrounds)
#############################
# train with early stopping #
#############################
early_stopping_rounds <- 5L
bst <- gpb.train(
params = list(
objective = "binary"
, metric = "binary_error"
, early_stopping_rounds = early_stopping_rounds
)
, data = dtrain
, nrounds = nrounds
, valids = list(
"valid1" = dvalid
)
, verbose = 0
)
# a perfect model should be trivial to obtain, and only 6 rounds
# should have happen (1 with improvement, 5 consecutive with no improvement)
expect_equal(bst$best_score, 0.0)
expect_equal(bst$best_iter, 1L)
expect_equal(
length(bst$record_evals[["valid1"]][["binary_error"]][["eval"]])
, early_stopping_rounds + 1L
)
})
test_that("gpb.train() treats early_stopping_rounds<=0 as disabling early stopping", {
set.seed(708L)
trainDF <- data.frame(
"feat1" = rep(c(5.0, 10.0), 500L)
, "target" = rep(c(0L, 1L), 500L)
)
validDF <- data.frame(
"feat1" = rep(c(5.0, 10.0), 50L)
, "target" = rep(c(0L, 1L), 50L)
)
dtrain <- gpb.Dataset(
data = as.matrix(trainDF[["feat1"]], drop = FALSE)
, label = trainDF[["target"]]
)
dvalid <- gpb.Dataset(
data = as.matrix(validDF[["feat1"]], drop = FALSE)
, label = validDF[["target"]]
)
nrounds <- 5L
for (value in c(-5L, 0L)) {
#----------------------------#
# passed as keyword argument #
#----------------------------#
bst <- gpb.train(
params = list(
objective = "binary"
, metric = "binary_error"
)
, data = dtrain
, nrounds = nrounds
, valids = list(
"valid1" = dvalid
)
, early_stopping_rounds = value
, verbose = 0
)
# a perfect model should be trivial to obtain, but all 10 rounds
# should happen
expect_equal(bst$best_score, 0.0)
expect_equal(bst$best_iter, 1L)
expect_equal(length(bst$record_evals[["valid1"]][["binary_error"]][["eval"]]), nrounds)
#---------------------------#
# passed as parameter alias #
#---------------------------#
bst <- gpb.train(
params = list(
objective = "binary"
, metric = "binary_error"
, n_iter_no_change = value
)
, data = dtrain
, nrounds = nrounds
, valids = list(
"valid1" = dvalid
)
, verbose = 0
)
# a perfect model should be trivial to obtain, but all 10 rounds
# should happen
expect_equal(bst$best_score, 0.0)
expect_equal(bst$best_iter, 1L)
expect_equal(length(bst$record_evals[["valid1"]][["binary_error"]][["eval"]]), nrounds)
}
})
test_that("gpb.train() works with early stopping for classification with a metric that should be maximized", {
set.seed(708L)
dtrain <- gpb.Dataset(
data = train$data
, label = train$label
)
dvalid <- gpb.Dataset(
data = test$data
, label = test$label
)
nrounds <- 10L
#############################
# train with early stopping #
#############################
early_stopping_rounds <- 5L
# the harsh max_depth guarantees that AUC improves over at least the first few iterations
bst_auc <- gpb.train(
params = list(
objective = "binary"
, metric = "auc"
, max_depth = 3L
, early_stopping_rounds = early_stopping_rounds
)
, data = dtrain
, nrounds = nrounds
, valids = list(
"valid1" = dvalid
)
, verbose = 0
)
bst_binary_error <- gpb.train(
params = list(
objective = "binary"
, metric = "binary_error"
, max_depth = 3L
, early_stopping_rounds = early_stopping_rounds
)
, data = dtrain
, nrounds = nrounds
, valids = list(
"valid1" = dvalid
)
, verbose = 0
)
# early stopping should have been hit for binary_error (higher_better = FALSE)
eval_info <- bst_binary_error$.__enclos_env__$private$get_eval_info()
expect_identical(eval_info, "binary_error")
expect_identical(
unname(bst_binary_error$.__enclos_env__$private$higher_better_inner_eval)
, FALSE
)
expect_identical(bst_binary_error$best_iter, 1L)
expect_identical(bst_binary_error$current_iter(), early_stopping_rounds + 1L)
expect_true(abs(bst_binary_error$best_score - 0.01613904) < TOLERANCE)
# early stopping should not have been hit for AUC (higher_better = TRUE)
eval_info <- bst_auc$.__enclos_env__$private$get_eval_info()
expect_identical(eval_info, "auc")
expect_identical(
unname(bst_auc$.__enclos_env__$private$higher_better_inner_eval)
, TRUE
)
expect_identical(bst_auc$best_iter, 10L)
expect_identical(bst_auc$current_iter(), nrounds)
expect_true(abs(bst_auc$best_score - 1) < TOLERANCE)
})
test_that("gpb.train() works with early stopping for regression", {
set.seed(708L)
trainDF <- data.frame(
"feat1" = rep(c(10.0, 100.0), 500L)
, "target" = rep(c(-50.0, 50.0), 500L)
)
validDF <- data.frame(
"feat1" = rep(50.0, 4L)
, "target" = rep(50.0, 4L)
)
dtrain <- gpb.Dataset(
data = as.matrix(trainDF[["feat1"]], drop = FALSE)
, label = trainDF[["target"]]
)
dvalid <- gpb.Dataset(
data = as.matrix(validDF[["feat1"]], drop = FALSE)
, label = validDF[["target"]]
)
nrounds <- 10L
################################
# train with no early stopping #
################################
bst <- gpb.train(
params = list(
objective = "regression"
, metric = "rmse"
)
, data = dtrain
, nrounds = nrounds
, valids = list(
"valid1" = dvalid
)
, verbose = 0
)
# the best possible model should come from the first iteration, but
# all 10 training iterations should happen
expect_equal(bst$best_score, 55.0)
expect_equal(bst$best_iter, 1L)
expect_equal(length(bst$record_evals[["valid1"]][["rmse"]][["eval"]]), nrounds)
#############################
# train with early stopping #
#############################
early_stopping_rounds <- 5L
bst <- gpb.train(
params = list(
objective = "regression"
, metric = "rmse"
, early_stopping_rounds = early_stopping_rounds
)
, data = dtrain
, nrounds = nrounds
, valids = list(
"valid1" = dvalid
)
, verbose = 0
)
# the best model should be from the first iteration, and only 6 rounds
# should have happen (1 with improvement, 5 consecutive with no improvement)
expect_equal(bst$best_score, 55.0)
expect_equal(bst$best_iter, 1L)
expect_equal(
length(bst$record_evals[["valid1"]][["rmse"]][["eval"]])
, early_stopping_rounds + 1L
)
})
test_that("gpb.train() does not stop early if early_stopping_rounds is not given", {
set.seed(708L)
increasing_metric_starting_value <- get(
ACCUMULATOR_NAME
, envir = ACCUMULATOR_ENVIRONMENT
)
nrounds <- 10L
metrics <- list(
.constant_metric
, .increasing_metric
)
bst <- gpb.train(
params = list(
objective = "regression"
, metric = "None"
)
, data = DTRAIN_RANDOM_REGRESSION
, nrounds = nrounds
, valids = list("valid1" = DVALID_RANDOM_REGRESSION)
, eval = metrics
, verbose = 0
)
# Only the two functions provided to "eval" should have been evaluated
expect_equal(length(bst$record_evals[["valid1"]]), 2L)
# all 10 iterations should have happen, and the best_iter should be
# the first one (based on constant_metric)
best_iter <- 1L
expect_equal(bst$best_iter, best_iter)
# best_score should be taken from the first metric
expect_equal(
bst$best_score
, bst$record_evals[["valid1"]][["constant_metric"]][["eval"]][[best_iter]]
)
# early stopping should not have happened. Even though constant_metric
# had 9 consecutive iterations with no improvement, it is ignored because of
# first_metric_only = TRUE
expect_equal(
length(bst$record_evals[["valid1"]][["constant_metric"]][["eval"]])
, nrounds
)
expect_equal(
length(bst$record_evals[["valid1"]][["increasing_metric"]][["eval"]])
, nrounds
)
})
test_that("If first_metric_only is not given or is FALSE, gpb.train() decides to stop early based on all metrics", {
set.seed(708L)
early_stopping_rounds <- 3L
param_variations <- list(
list(
objective = "regression"
, metric = "None"
, early_stopping_rounds = early_stopping_rounds
)
, list(
objective = "regression"
, metric = "None"
, early_stopping_rounds = early_stopping_rounds
, first_metric_only = FALSE
)
)
for (params in param_variations) {
nrounds <- 10L
bst <- gpb.train(
params = params
, data = DTRAIN_RANDOM_REGRESSION
, nrounds = nrounds
, valids = list(
"valid1" = DVALID_RANDOM_REGRESSION
)
, eval = list(
.increasing_metric
, .constant_metric
)
, verbose = 0
)
# Only the two functions provided to "eval" should have been evaluated
expect_equal(length(bst$record_evals[["valid1"]]), 2L)
# early stopping should have happened, and should have stopped early_stopping_rounds + 1 rounds in
# because constant_metric never improves
#
# the best iteration should be the last one, because increasing_metric was first
# and gets better every iteration
best_iter <- early_stopping_rounds + 1L
expect_equal(bst$best_iter, best_iter)
# best_score should be taken from "increasing_metric" because it was first
expect_equal(
bst$best_score
, bst$record_evals[["valid1"]][["increasing_metric"]][["eval"]][[best_iter]]
)
# early stopping should not have happened. even though increasing_metric kept
# getting better, early stopping should have happened because "constant_metric"
# did not improve
expect_equal(
length(bst$record_evals[["valid1"]][["constant_metric"]][["eval"]])
, early_stopping_rounds + 1L
)
expect_equal(
length(bst$record_evals[["valid1"]][["increasing_metric"]][["eval"]])
, early_stopping_rounds + 1L
)
}
})
test_that("If first_metric_only is TRUE, gpb.train() decides to stop early based on only the first metric", {
set.seed(708L)
nrounds <- 10L
early_stopping_rounds <- 3L
increasing_metric_starting_value <- get(ACCUMULATOR_NAME, envir = ACCUMULATOR_ENVIRONMENT)
bst <- gpb.train(
params = list(
objective = "regression"
, metric = "None"
, early_stopping_rounds = early_stopping_rounds
, first_metric_only = TRUE
)
, data = DTRAIN_RANDOM_REGRESSION
, nrounds = nrounds
, valids = list(
"valid1" = DVALID_RANDOM_REGRESSION
)
, eval = list(
.increasing_metric
, .constant_metric
)
, verbose = 0
)
# Only the two functions provided to "eval" should have been evaluated
expect_equal(length(bst$record_evals[["valid1"]]), 2L)
# all 10 iterations should happen, and the best_iter should be the final one
expect_equal(bst$best_iter, nrounds)
# best_score should be taken from "increasing_metric"
expect_equal(
bst$best_score
, increasing_metric_starting_value + 0.1 * nrounds
)
# early stopping should not have happened. Even though constant_metric
# had 9 consecutive iterations with no improvement, it is ignored because of
# first_metric_only = TRUE
expect_equal(
length(bst$record_evals[["valid1"]][["constant_metric"]][["eval"]])
, nrounds
)
expect_equal(
length(bst$record_evals[["valid1"]][["increasing_metric"]][["eval"]])
, nrounds
)
})
test_that("gpb.train() works when a mixture of functions and strings are passed to eval", {
set.seed(708L)
nrounds <- 10L
increasing_metric_starting_value <- get(ACCUMULATOR_NAME, envir = ACCUMULATOR_ENVIRONMENT)
bst <- gpb.train(
params = list(
objective = "regression"
, metric = "None"
)
, data = DTRAIN_RANDOM_REGRESSION
, nrounds = nrounds
, valids = list(
"valid1" = DVALID_RANDOM_REGRESSION
)
, eval = list(
.increasing_metric
, "rmse"
, .constant_metric
, "l2"
)
, verbose = 0
)
# all 4 metrics should have been used
expect_named(
bst$record_evals[["valid1"]]
, expected = c("rmse", "l2", "increasing_metric", "constant_metric")
, ignore.order = TRUE
, ignore.case = FALSE
)
# the difference metrics shouldn't have been mixed up with each other
results <- bst$record_evals[["valid1"]]
expect_true(abs(results[["rmse"]][["eval"]][[1L]] - 1.105012) < TOLERANCE)
expect_true(abs(results[["l2"]][["eval"]][[1L]] - 1.221051) < TOLERANCE)
expected_increasing_metric <- increasing_metric_starting_value + 0.1
expect_true(
abs(
results[["increasing_metric"]][["eval"]][[1L]] - expected_increasing_metric
) < TOLERANCE
)
expect_true(abs(results[["constant_metric"]][["eval"]][[1L]] - CONSTANT_METRIC_VALUE) < TOLERANCE)
})
test_that("gpb.train() works when a list of strings or a character vector is passed to eval", {
# testing list and character vector, as well as length-1 and length-2
eval_variations <- list(
c("binary_error", "binary_logloss")
, "binary_logloss"
, list("binary_error", "binary_logloss")
, list("binary_logloss")
)
for (eval_variation in eval_variations) {
set.seed(708L)
nrounds <- 10L
increasing_metric_starting_value <- get(ACCUMULATOR_NAME, envir = ACCUMULATOR_ENVIRONMENT)
bst <- gpb.train(
params = list(
objective = "binary"
, metric = "None"
)
, data = DTRAIN_RANDOM_CLASSIFICATION
, nrounds = nrounds
, valids = list(
"valid1" = DVALID_RANDOM_CLASSIFICATION
)
, eval = eval_variation
, verbose = 0
)
# both metrics should have been used
expect_named(
bst$record_evals[["valid1"]]
, expected = unlist(eval_variation)
, ignore.order = TRUE
, ignore.case = FALSE
)
# the difference metrics shouldn't have been mixed up with each other
results <- bst$record_evals[["valid1"]]
if ("binary_error" %in% unlist(eval_variation)) {
expect_true(abs(results[["binary_error"]][["eval"]][[1L]] - 0.4864865) < TOLERANCE)
}
if ("binary_logloss" %in% unlist(eval_variation)) {
expect_true(abs(results[["binary_logloss"]][["eval"]][[1L]] - 0.6932548) < TOLERANCE)
}
}
})
test_that("gpb.train() works when you specify both 'metric' and 'eval' with strings", {
set.seed(708L)
nrounds <- 10L
increasing_metric_starting_value <- get(ACCUMULATOR_NAME, envir = ACCUMULATOR_ENVIRONMENT)
bst <- gpb.train(
params = list(
objective = "binary"
, metric = "binary_error"
)
, data = DTRAIN_RANDOM_CLASSIFICATION
, nrounds = nrounds
, valids = list(
"valid1" = DVALID_RANDOM_CLASSIFICATION
)
, eval = "binary_logloss"
, verbose = 0
)
# both metrics should have been used
expect_named(
bst$record_evals[["valid1"]]
, expected = c("binary_error", "binary_logloss")
, ignore.order = TRUE
, ignore.case = FALSE
)
# the difference metrics shouldn't have been mixed up with each other
results <- bst$record_evals[["valid1"]]
expect_true(abs(results[["binary_error"]][["eval"]][[1L]] - 0.4864865) < TOLERANCE)
expect_true(abs(results[["binary_logloss"]][["eval"]][[1L]] - 0.6932548) < TOLERANCE)
})
test_that("gpb.train() works when you give a function for eval", {
set.seed(708L)
nrounds <- 10L
increasing_metric_starting_value <- get(ACCUMULATOR_NAME, envir = ACCUMULATOR_ENVIRONMENT)
bst <- gpb.train(
params = list(
objective = "binary"
, metric = "None"
)
, data = DTRAIN_RANDOM_CLASSIFICATION
, nrounds = nrounds
, valids = list(
"valid1" = DVALID_RANDOM_CLASSIFICATION
)
, eval = .constant_metric
, verbose = 0
)
# the difference metrics shouldn't have been mixed up with each other
results <- bst$record_evals[["valid1"]]
expect_true(abs(results[["constant_metric"]][["eval"]][[1L]] - CONSTANT_METRIC_VALUE) < TOLERANCE)
})
test_that("gpb.train() works with early stopping for regression with a metric that should be minimized", {
set.seed(708L)
trainDF <- data.frame(
"feat1" = rep(c(10.0, 100.0), 500L)
, "target" = rep(c(-50.0, 50.0), 500L)
)
validDF <- data.frame(
"feat1" = rep(50.0, 4L)
, "target" = rep(50.0, 4L)
)
dtrain <- gpb.Dataset(
data = as.matrix(trainDF[["feat1"]], drop = FALSE)
, label = trainDF[["target"]]
)
dvalid <- gpb.Dataset(
data = as.matrix(validDF[["feat1"]], drop = FALSE)
, label = validDF[["target"]]
)
nrounds <- 10L
#############################
# train with early stopping #
#############################
early_stopping_rounds <- 5L
bst <- gpb.train(
params = list(
objective = "regression"
, metric = c(
"mape"
, "rmse"
, "mae"
)
, min_data_in_bin = 5L
, early_stopping_rounds = early_stopping_rounds
)
, data = dtrain
, nrounds = nrounds
, valids = list(
"valid1" = dvalid
)
, verbose = 0
)
# the best model should be from the first iteration, and only 6 rounds
# should have happened (1 with improvement, 5 consecutive with no improvement)
expect_equal(bst$best_score, 1.1)
expect_equal(bst$best_iter, 1L)
expect_equal(
length(bst$record_evals[["valid1"]][["mape"]][["eval"]])
, early_stopping_rounds + 1L
)
# Booster should understand thatt all three of these metrics should be minimized
eval_info <- bst$.__enclos_env__$private$get_eval_info()
expect_identical(eval_info, c("mape", "rmse", "l1"))
expect_identical(
unname(bst$.__enclos_env__$private$higher_better_inner_eval)
, rep(FALSE, 3L)
)
})
test_that("when early stopping is not activated, best_iter and best_score come from valids and not training data", {
set.seed(708L)
trainDF <- data.frame(
"feat1" = rep(c(10.0, 100.0), 500L)
, "target" = rep(c(-50.0, 50.0), 500L)
)
validDF <- data.frame(
"feat1" = rep(50.0, 4L)
, "target" = rep(50.0, 4L)
)
validDF2 <- data.frame(
"feat1" = rep(c(50.0,10), 4L)
, "target" = rep(c(50.0,-50.), 4L)
)
dtrain <- gpb.Dataset(
data = as.matrix(trainDF[["feat1"]], drop = FALSE)
, label = trainDF[["target"]]
)
dvalid1 <- gpb.Dataset(
data = as.matrix(validDF[["feat1"]], drop = FALSE)
, label = validDF[["target"]]
)
dvalid2 <- gpb.Dataset(
data = as.matrix(validDF2[["feat1"]], drop = FALSE)
, label = validDF2[["target"]]
)
nrounds <- 10L
train_params <- list(
objective = "regression"
, metric = "rmse"
, learning_rate = 1.5
)
# example 1: two valids, neither are the training data
bst <- gpb.train(
data = dtrain
, nrounds = nrounds
, num_leaves = 5L
, valids = list(
"valid1" = dvalid1
, "valid2" = dvalid2
)
, params = train_params
, verbose = 0
)
expect_named(
bst$record_evals
, c("start_iter", "valid1", "valid2")
, ignore.order = FALSE
, ignore.case = FALSE
)
rmse_scores <- unlist(bst$record_evals[["valid1"]][["rmse"]][["eval"]])
expect_length(rmse_scores, nrounds)
expect_identical(bst$best_iter, which.min(rmse_scores))
expect_identical(bst$best_score, rmse_scores[which.min(rmse_scores)])
# example 2: train first (called "train") and two valids
bst <- gpb.train(
data = dtrain
, nrounds = nrounds
, num_leaves = 5L
, valids = list(
"train" = dtrain
, "valid1" = dvalid1
, "valid2" = dvalid2
)
, params = train_params
, verbose = 0
)
expect_named(
bst$record_evals
, c("start_iter", "train", "valid1", "valid2")
, ignore.order = FALSE
, ignore.case = FALSE
)
rmse_scores <- unlist(bst$record_evals[["valid1"]][["rmse"]][["eval"]])
expect_length(rmse_scores, nrounds)
expect_identical(bst$best_iter, which.min(rmse_scores))
expect_identical(bst$best_score, rmse_scores[which.min(rmse_scores)])
# example 3: train second (called "train") and two valids
bst <- gpb.train(
data = dtrain
, nrounds = nrounds
, num_leaves = 5L
, valids = list(
"valid1" = dvalid1
, "train" = dtrain
, "valid2" = dvalid2
)
, params = train_params
, verbose = 0
)
# note that "train" still ends up as the first one
expect_named(
bst$record_evals
, c("start_iter", "train", "valid1", "valid2")
, ignore.order = FALSE
, ignore.case = FALSE
)
rmse_scores <- unlist(bst$record_evals[["valid1"]][["rmse"]][["eval"]])
expect_length(rmse_scores, nrounds)
expect_identical(bst$best_iter, which.min(rmse_scores))
expect_identical(bst$best_score, rmse_scores[which.min(rmse_scores)])
# example 4: train third (called "train") and two valids
bst <- gpb.train(
data = dtrain
, nrounds = nrounds
, num_leaves = 5L
, valids = list(
"valid1" = dvalid1
, "valid2" = dvalid2
, "train" = dtrain
)
, params = train_params
, verbose = 0
)
# note that "train" still ends up as the first one
expect_named(
bst$record_evals
, c("start_iter", "train", "valid1", "valid2")
, ignore.order = FALSE
, ignore.case = FALSE
)
rmse_scores <- unlist(bst$record_evals[["valid1"]][["rmse"]][["eval"]])
expect_length(rmse_scores, nrounds)
expect_identical(bst$best_iter, which.min(rmse_scores))
expect_identical(bst$best_score, rmse_scores[which.min(rmse_scores)])
# example 5: train second (called "something-random-we-would-not-hardcode") and two valids
bst <- gpb.train(
data = dtrain
, nrounds = nrounds
, num_leaves = 5L
, valids = list(
"valid1" = dvalid1
, "something-random-we-would-not-hardcode" = dtrain
, "valid2" = dvalid2
)
, params = train_params
, verbose = 0
)
# note that "something-random-we-would-not-hardcode" was recognized as the training
# data even though it isn't named "train"
expect_named(
bst$record_evals
, c("start_iter", "something-random-we-would-not-hardcode", "valid1", "valid2")
, ignore.order = FALSE
, ignore.case = FALSE
)
rmse_scores <- unlist(bst$record_evals[["valid1"]][["rmse"]][["eval"]])
expect_length(rmse_scores, nrounds)
expect_identical(bst$best_iter, which.min(rmse_scores))
expect_identical(bst$best_score, rmse_scores[which.min(rmse_scores)])
# example 6: the only valid supplied is the training data
bst <- gpb.train(
data = dtrain
, nrounds = nrounds
, num_leaves = 5L
, valids = list(
"train" = dtrain
)
, params = train_params
, verbose = 0
)
expect_identical(bst$best_iter, -1L)
expect_identical(bst$best_score, NA_real_)
})
test_that("gpboost.train() gives the correct best_score and best_iter for a metric where higher values are better", {
set.seed(708L)
trainDF <- data.frame(
"feat1" = runif(n = 500L, min = 0.0, max = 15.0)
, "target" = rep(c(0L, 1L), 500L)
)
validDF <- data.frame(
"feat1" = runif(n = 50L, min = 0.0, max = 15.0)
, "target" = rep(c(0L, 1L), 50L)
)
dtrain <- gpb.Dataset(
data = as.matrix(trainDF[["feat1"]], drop = FALSE)
, label = trainDF[["target"]]
)
dvalid1 <- gpb.Dataset(
data = as.matrix(validDF[1L:25L, "feat1"], drop = FALSE)
, label = validDF[1L:25L, "target"]
)
nrounds <- 10L
bst <- gpb.train(
data = dtrain
, nrounds = nrounds
, num_leaves = 5L
, valids = list(
"valid1" = dvalid1
, "something-random-we-would-not-hardcode" = dtrain
)
, params = list(
objective = "binary"
, metric = "auc"
, learning_rate = 1.5
)
, verbose = 0
)
# note that "something-random-we-would-not-hardcode" was recognized as the training
# data even though it isn't named "train"
expect_named(
bst$record_evals
, c("start_iter", "something-random-we-would-not-hardcode", "valid1")
, ignore.order = FALSE
, ignore.case = FALSE
)
auc_scores <- unlist(bst$record_evals[["valid1"]][["auc"]][["eval"]])
expect_length(auc_scores, nrounds)
expect_identical(bst$best_iter, which.max(auc_scores))
expect_identical(bst$best_score, auc_scores[which.max(auc_scores)])
})
test_that("using gpboost() without early stopping, best_iter and best_score come from valids and not training data", {
set.seed(708L)
# example: train second (called "something-random-we-would-not-hardcode"), two valids,
# and a metric where higher values are better ("auc")
trainDF <- data.frame(
"feat1" = runif(n = 500L, min = 0.0, max = 15.0)
, "target" = rep(c(0L, 1L), 500L)
)
validDF <- data.frame(
"feat1" = runif(n = 50L, min = 0.0, max = 15.0)
, "target" = rep(c(0L, 1L), 50L)
)
dtrain <- gpb.Dataset(
data = as.matrix(trainDF[["feat1"]], drop = FALSE)
, label = trainDF[["target"]]
)
dvalid1 <- gpb.Dataset(
data = as.matrix(validDF[1L:25L, "feat1"], drop = FALSE)
, label = validDF[1L:25L, "target"]
)
dvalid2 <- gpb.Dataset(
data = as.matrix(validDF[26L:50L, "feat1"], drop = FALSE)
, label = validDF[26L:50L, "target"]
)
nrounds <- 10L
bst <- gpboost(
data = dtrain
, nrounds = nrounds
, num_leaves = 5L
, valids = list(
"valid1" = dvalid1
, "something-random-we-would-not-hardcode" = dtrain
, "valid2" = dvalid2
)
, params = list(
objective = "binary"
, metric = "auc"
, learning_rate = 1.5
)
, verbose = -7L
)
# when verbose <= 0 is passed to gpboost(), 'valids' is passed through to gpb.train()
# untouched. If you set verbose to > 0, the training data will still be first but called "train"
expect_named(
bst$record_evals
, c("start_iter", "something-random-we-would-not-hardcode", "valid1", "valid2")
, ignore.order = FALSE
, ignore.case = FALSE
)
auc_scores <- unlist(bst$record_evals[["valid1"]][["auc"]][["eval"]])
expect_length(auc_scores, nrounds)
expect_identical(bst$best_iter, which.max(auc_scores))
expect_identical(bst$best_score, auc_scores[which.max(auc_scores)])
})
test_that("gpb.cv() works when you specify both 'metric' and 'eval' with strings", {
set.seed(708L)
nrounds <- 10L
nfolds <- 4L
increasing_metric_starting_value <- get(ACCUMULATOR_NAME, envir = ACCUMULATOR_ENVIRONMENT)
capture.output( bst <- gpb.cv(
params = list(
objective = "binary"
, metric = "binary_error"
)
, data = DTRAIN_RANDOM_CLASSIFICATION
, nrounds = nrounds
, nfold = nfolds
, eval = "binary_logloss"
), file='NUL')
# both metrics should have been used
expect_named(
bst$record_evals[["valid"]]
, expected = c("binary_error", "binary_logloss")
, ignore.order = TRUE
, ignore.case = FALSE
)
# the difference metrics shouldn't have been mixed up with each other
results <- bst$record_evals[["valid"]]
expect_true(abs(results[["binary_error"]][["eval"]][[1L]] - 0.5005654) < TOLERANCE)
expect_true(abs(results[["binary_logloss"]][["eval"]][[1L]] - 0.7016582) < TOLERANCE)
# all boosters should have been created
expect_length(bst$boosters, nfolds)
})
test_that("gpb.cv() works when you give a function for eval", {
set.seed(708L)
nrounds <- 10L
nfolds <- 3L
increasing_metric_starting_value <- get(ACCUMULATOR_NAME, envir = ACCUMULATOR_ENVIRONMENT)
capture.output( bst <- gpb.cv(
params = list(
objective = "binary"
, metric = "None"
)
, data = DTRAIN_RANDOM_CLASSIFICATION
, nfold = nfolds
, nrounds = nrounds
, eval = .constant_metric
), file='NUL')
# the difference metrics shouldn't have been mixed up with each other
results <- bst$record_evals[["valid"]]
expect_true(abs(results[["constant_metric"]][["eval"]][[1L]] - CONSTANT_METRIC_VALUE) < TOLERANCE)
expect_named(results, "constant_metric")
})
test_that("If first_metric_only is TRUE, gpb.cv() decides to stop early based on only the first metric", {
set.seed(708L)
nrounds <- 10L
nfolds <- 5L
early_stopping_rounds <- 3L
increasing_metric_starting_value <- get(ACCUMULATOR_NAME, envir = ACCUMULATOR_ENVIRONMENT)
capture.output(
bst <- gpb.cv(
params = list(
objective = "regression"
, metric = "None"
, early_stopping_rounds = early_stopping_rounds
, first_metric_only = TRUE
)
, data = DTRAIN_RANDOM_REGRESSION
, nfold = nfolds
, nrounds = nrounds
, valids = list(
"valid1" = DVALID_RANDOM_REGRESSION
)
, eval = list(
.increasing_metric
, .constant_metric
)
)
,file='NUL')
# Only the two functions provided to "eval" should have been evaluated
expect_named(bst$record_evals[["valid"]], c("increasing_metric", "constant_metric"))
# all 10 iterations should happen, and the best_iter should be the final one
expect_equal(bst$best_iter, nrounds)
# best_score should be taken from "increasing_metric"
#
# this expected value looks magical and confusing, but it's because
# evaluation metrics are averaged over all folds.
#
# consider 5-fold CV with a metric that adds 0.1 to a global accumulator
# each time it's called
#
# * iter 1: [0.1, 0.2, 0.3, 0.4, 0.5] (mean = 0.3)
# * iter 2: [0.6, 0.7, 0.8, 0.9, 1.0] (mean = 1.3)
# * iter 3: [1.1, 1.2, 1.3, 1.4, 1.5] (mean = 1.8)
#
cv_value <- increasing_metric_starting_value + mean(seq_len(nfolds) / 10.0) + (nrounds - 1L) * 0.1 * nfolds
expect_equal(bst$best_score, cv_value)
# early stopping should not have happened. Even though constant_metric
# had 9 consecutive iterations with no improvement, it is ignored because of
# first_metric_only = TRUE
expect_equal(
length(bst$record_evals[["valid"]][["constant_metric"]][["eval"]])
, nrounds
)
expect_equal(
length(bst$record_evals[["valid"]][["increasing_metric"]][["eval"]])
, nrounds
)
})
test_that("early stopping works with gpb.cv()", {
set.seed(708L)
nrounds <- 10L
nfolds <- 5L
early_stopping_rounds <- 3L
increasing_metric_starting_value <- get(ACCUMULATOR_NAME, envir = ACCUMULATOR_ENVIRONMENT)
capture.output(
bst <- gpb.cv(
params = list(
objective = "regression"
, metric = "None"
, early_stopping_rounds = early_stopping_rounds
, first_metric_only = TRUE
)
, data = DTRAIN_RANDOM_REGRESSION
, nfold = nfolds
, nrounds = nrounds
, valids = list(
"valid1" = DVALID_RANDOM_REGRESSION
)
, eval = list(
.constant_metric
, .increasing_metric
)
)
, file='NUL')
# only the two functions provided to "eval" should have been evaluated
expect_named(bst$record_evals[["valid"]], c("constant_metric", "increasing_metric"))
# best_iter should be based on the first metric. Since constant_metric
# never changes, its first iteration was the best oone
expect_equal(bst$best_iter, 1L)
# best_score should be taken from the first metri
expect_equal(bst$best_score, 0.2)
# early stopping should have happened, since constant_metric was the first
# one passed to eval and it will not improve over consecutive iterations
#
# note that this test is identical to the previous one, but with the
# order of the eval metrics switched
expect_equal(
length(bst$record_evals[["valid"]][["constant_metric"]][["eval"]])
, early_stopping_rounds + 1L
)
expect_equal(
length(bst$record_evals[["valid"]][["increasing_metric"]][["eval"]])
, early_stopping_rounds + 1L
)
})
context("linear learner")
test_that("gpb.train() fit on linearly-relatead data improves when using linear learners", {
set.seed(708L)
.new_dataset <- function() {
X <- matrix(rnorm(100L), ncol = 1L)
return(gpb.Dataset(
data = X
, label = 2L * X + runif(nrow(X), 0L, 0.1)
))
}
params <- list(
objective = "regression"
, verbose = -1L
, metric = "mse"
, seed = 0L
, num_leaves = 2L
)
dtrain <- .new_dataset()
bst <- gpb.train(
data = dtrain
, nrounds = 10L
, params = params
, valids = list("train" = dtrain)
, verbose = 0
)
expect_true(gpboost:::gpb.is.Booster(bst))
dtrain <- .new_dataset()
bst_linear <- gpb.train(
data = dtrain
, nrounds = 10L
, params = modifyList(params, list(linear_tree = TRUE))
, valids = list("train" = dtrain)
, verbose = 0
)
expect_true(gpboost:::gpb.is.Booster(bst_linear))
bst_last_mse <- bst$record_evals[["train"]][["l2"]][["eval"]][[10L]]
bst_lin_last_mse <- bst_linear$record_evals[["train"]][["l2"]][["eval"]][[10L]]
expect_true(bst_lin_last_mse < bst_last_mse)
})
# test_that("gpb.train() w/ linear learner fails already-constructed dataset with linear=false", {
# testthat::skip("Skipping this test because it causes issues for valgrind")
# set.seed(708L)
# params <- list(
# objective = "regression"
# , verbose = -1L
# , metric = "mse"
# , seed = 0L
# , num_leaves = 2L
# )
#
# dtrain <- gpb.Dataset(
# data = matrix(rnorm(100L), ncol = 1L)
# , label = rnorm(100L)
# )
# dtrain$construct()
# expect_error({
# bst_linear <- gpb.train(
# data = dtrain
# , nrounds = 10L
# , params = modifyList(params, list(linear_tree = TRUE))
# )
# }, regexp = "Cannot change linear_tree after constructed Dataset handle")
# })
test_that("gpb.train() works with linear learners when Dataset has categorical features", {
set.seed(708L)
.new_dataset <- function() {
X <- matrix(numeric(200L), nrow = 100L, ncol = 2L)
X[, 1L] <- rnorm(100L)
X[, 2L] <- sample(seq_len(4L), size = 100L, replace = TRUE)
return(gpb.Dataset(
data = X
, label = 2L * X[, 1L] + runif(nrow(X), 0L, 0.1)
))
}
params <- list(
objective = "regression"
, verbose = -1L
, metric = "mse"
, seed = 0L
, num_leaves = 2L
, categorical_featurs = 1L
)
dtrain <- .new_dataset()
capture.output(
bst <- gpb.train(
data = dtrain
, nrounds = 10L
, params = params
, valids = list("train" = dtrain)
, verbose = 0
)
, file='NUL')
expect_true(gpboost:::gpb.is.Booster(bst))
dtrain <- .new_dataset()
capture.output(
bst_linear <- gpb.train(
data = dtrain
, nrounds = 10L
, params = modifyList(params, list(linear_tree = TRUE))
, valids = list("train" = dtrain)
, verbose = 0
)
, file='NUL')
expect_true(gpboost:::gpb.is.Booster(bst_linear))
bst_last_mse <- bst$record_evals[["train"]][["l2"]][["eval"]][[10L]]
bst_lin_last_mse <- bst_linear$record_evals[["train"]][["l2"]][["eval"]][[10L]]
expect_true(bst_lin_last_mse < bst_last_mse)
})
context("interaction constraints")
test_that("gpb.train() throws an informative error if interaction_constraints is not a list", {
dtrain <- gpb.Dataset(train$data, label = train$label)
params <- list(objective = "regression", interaction_constraints = "[1,2],[3]")
expect_error({
bst <- gpboost(
data = dtrain
, params = params
, nrounds = 2L
)
}, "interaction_constraints must be a list")
})
test_that(paste0("gpb.train() throws an informative error if the members of interaction_constraints ",
"are not character or numeric vectors"), {
dtrain <- gpb.Dataset(train$data, label = train$label)
params <- list(objective = "regression", interaction_constraints = list(list(1L, 2L), list(3L)))
capture.output(
expect_error({
bst <- gpboost(
data = dtrain
, params = params
, nrounds = 2L
)
}, "every element in interaction_constraints must be a character vector or numeric vector")
, file='NUL')
})
test_that("gpb.train() throws an informative error if interaction_constraints contains a too large index", {
dtrain <- gpb.Dataset(train$data, label = train$label)
params <- list(objective = "regression",
interaction_constraints = list(c(1L, length(colnames(train$data)) + 1L), 3L))
capture.output(
expect_error({
bst <- gpboost(
data = dtrain
, params = params
, nrounds = 2L
)
}, "supplied a too large value in interaction_constraints")
, file='NUL')
})
test_that(paste0("gpb.train() gives same result when interaction_constraints is specified as a list of ",
"character vectors, numeric vectors, or a combination"), {
set.seed(1L)
dtrain <- gpb.Dataset(train$data, label = train$label)
params <- list(objective = "regression", interaction_constraints = list(c(1L, 2L), 3L))
capture.output(
bst <- gpboost(
data = dtrain
, params = params
, nrounds = 2L
)
, file='NUL')
pred1 <- bst$predict(test$data)
cnames <- colnames(train$data)
params <- list(objective = "regression", interaction_constraints = list(c(cnames[[1L]], cnames[[2L]]), cnames[[3L]]))
capture.output(
bst <- gpboost(
data = dtrain
, params = params
, nrounds = 2L
)
, file='NUL')
pred2 <- bst$predict(test$data)
params <- list(objective = "regression", interaction_constraints = list(c(cnames[[1L]], cnames[[2L]]), 3L))
capture.output(
bst <- gpboost(
data = dtrain
, params = params
, nrounds = 2L
)
, file='NUL')
pred3 <- bst$predict(test$data)
expect_equal(pred1, pred2)
expect_equal(pred2, pred3)
})
test_that(paste0("gpb.train() gives same results when using interaction_constraints and specifying colnames"), {
set.seed(1L)
dtrain <- gpb.Dataset(train$data, label = train$label)
params <- list(objective = "regression", interaction_constraints = list(c(1L, 2L), 3L))
capture.output(
bst <- gpboost(
data = dtrain
, params = params
, nrounds = 2L
)
, file='NUL')
pred1 <- bst$predict(test$data)
new_colnames <- paste0(colnames(train$data), "_x")
params <- list(objective = "regression"
, interaction_constraints = list(c(new_colnames[1L], new_colnames[2L]), new_colnames[3L]))
capture.output(
bst <- gpboost(
data = dtrain
, params = params
, nrounds = 2L
, colnames = new_colnames
)
, file='NUL')
pred2 <- bst$predict(test$data)
expect_equal(pred1, pred2)
})
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.