Nothing
# Tests for mlr3 integration: LearnerClassifGGML / LearnerRegrGGML,
# ggml_default_mlp(), marshal roundtrip, resampling.
if (!requireNamespace("mlr3", quietly = TRUE) ||
!requireNamespace("paradox", quietly = TRUE) ||
!requireNamespace("R6", quietly = TRUE)) {
testthat::skip("mlr3 / paradox / R6 not available")
}
# In the R CMD check test process mlr3 may load before or after ggmlR,
# so the setHook callbacks in .onLoad may not have fired yet.
# Force registration explicitly — .register_mlr3() is idempotent.
library(mlr3)
ggmlR:::.register_mlr3()
skip_if_no_mlr3 <- function() {
skip_if_not_installed("mlr3")
skip_if_not_installed("paradox")
skip_if_not_installed("R6")
}
suppress_mlr3_output <- function(expr) {
if (requireNamespace("lgr", quietly = TRUE)) {
old <- lgr::get_logger("mlr3")$threshold
lgr::get_logger("mlr3")$set_threshold("warn")
on.exit(lgr::get_logger("mlr3")$set_threshold(old), add = TRUE)
}
suppressMessages(expr)
}
# ---------------------------------------------------------------------------
# ggml_default_mlp
# ---------------------------------------------------------------------------
test_that("ggml_default_mlp builds a classif MLP with softmax head", {
m <- ggml_default_mlp(
n_features = 4L,
n_out = 3L,
task_type = "classif",
hidden_layers = c(16L, 8L),
dropout = 0.1
)
expect_s3_class(m, "ggml_sequential_model")
expect_false(m$compiled)
# dense + dropout + dense + dropout + dense(softmax) = 5 layers
expect_equal(length(m$layers), 5L)
last <- m$layers[[length(m$layers)]]
expect_equal(last$type, "dense")
expect_equal(last$config$activation, "softmax")
expect_equal(last$config$units, 3L)
})
test_that("ggml_default_mlp builds a regr MLP with linear head", {
m <- ggml_default_mlp(
n_features = 10L,
n_out = 1L,
task_type = "regr",
hidden_layers = c(8L),
dropout = 0
)
expect_s3_class(m, "ggml_sequential_model")
# dense + dense(identity) = 2 layers (dropout=0 → no dropout layer)
expect_equal(length(m$layers), 2L)
last <- m$layers[[2L]]
expect_null(last$config$activation)
expect_equal(last$config$units, 1L)
})
test_that("ggml_default_mlp with empty hidden_layers yields a linear model", {
m <- ggml_default_mlp(
n_features = 5L,
n_out = 2L,
task_type = "classif",
hidden_layers = integer(0)
)
expect_equal(length(m$layers), 1L)
expect_equal(m$layers[[1L]]$config$activation, "softmax")
})
test_that("ggml_default_mlp validates its arguments", {
expect_error(ggml_default_mlp(n_features = 0L, n_out = 2L),
"n_features")
expect_error(ggml_default_mlp(n_features = 4L, n_out = 0L),
"n_out")
expect_error(ggml_default_mlp(n_features = 4L, n_out = 2L, dropout = 1),
"dropout")
})
# ---------------------------------------------------------------------------
# LearnerClassifGGML
# ---------------------------------------------------------------------------
test_that("LearnerClassifGGML constructs and has the expected contract", {
skip_if_no_mlr3()
learner <- mlr3::lrn("classif.ggml")
expect_s3_class(learner, "LearnerClassifGGML")
expect_s3_class(learner, "LearnerClassif")
expect_equal(learner$id, "classif.ggml")
expect_setequal(learner$predict_types, c("response", "prob"))
expect_equal(learner$feature_types, "numeric")
expect_true(all(c("multiclass", "twoclass", "marshal", "weights")
%in% learner$properties))
expect_true("ggmlR" %in% learner$packages)
expect_null(learner$model_fn)
})
test_that("LearnerClassifGGML trains and predicts on iris (response + prob)", {
skip_if_no_mlr3()
set.seed(42)
task <- mlr3::tsk("iris")
learner <- mlr3::lrn("classif.ggml")
learner$param_set$values$epochs <- 5L
learner$param_set$values$batch_size <- 10L
learner$param_set$values$backend <- "cpu"
learner$predict_type <- "prob"
learner$train(task)
expect_s3_class(learner$model, "classif_ggml_model")
expect_equal(learner$model$class_names, task$class_names)
expect_equal(learner$model$n_features, length(task$feature_names))
pred <- learner$predict(task)
expect_s3_class(pred, "PredictionClassif")
expect_equal(length(pred$response), task$nrow)
expect_true(is.factor(pred$response))
expect_equal(levels(pred$response), task$class_names)
# prob matrix sums to ~1 per row
expect_true(all(abs(rowSums(pred$prob) - 1) < 1e-4))
})
test_that("LearnerClassifGGML response-only predict_type works", {
skip_if_no_mlr3()
set.seed(42)
task <- mlr3::tsk("iris")
learner <- mlr3::lrn("classif.ggml")
learner$param_set$values$epochs <- 3L
learner$param_set$values$batch_size <- 10L
learner$param_set$values$backend <- "cpu"
# predict_type defaults to "response"
learner$train(task)
pred <- learner$predict(task)
expect_true(is.factor(pred$response))
})
test_that("LearnerClassifGGML accepts a user-supplied model_fn", {
skip_if_no_mlr3()
set.seed(42)
task <- mlr3::tsk("iris")
called <- FALSE
learner <- mlr3::lrn("classif.ggml")
learner$param_set$values$epochs <- 3L
learner$param_set$values$batch_size <- 10L
learner$param_set$values$backend <- "cpu"
learner$model_fn <- function(task, n_features, n_out, pars) {
called <<- TRUE
expect_equal(n_features, 4L)
expect_equal(n_out, 3L)
ggml_model_sequential() |>
ggml_layer_dense(8L, activation = "relu", input_shape = n_features) |>
ggml_layer_dense(n_out, activation = "softmax")
}
learner$train(task)
expect_true(called)
expect_s3_class(learner$model, "classif_ggml_model")
})
test_that("LearnerClassifGGML rejects an unsupported model_fn return", {
skip_if_no_mlr3()
task <- mlr3::tsk("iris")
learner <- mlr3::lrn("classif.ggml")
learner$param_set$values$epochs <- 1L
learner$param_set$values$batch_size <- 10L
learner$param_set$values$backend <- "cpu"
learner$model_fn <- function(task, n_features, n_out, pars) {
list(not = "a model")
}
expect_error(learner$train(task), "sequential, functional, or ag_sequential")
})
test_that("LearnerClassifGGML honours observation weights", {
skip_if_no_mlr3()
set.seed(42)
# Build a small numeric task with weights column
d <- data.frame(
x1 = rnorm(30),
x2 = rnorm(30),
y = factor(rep(c("a", "b"), each = 15)),
w = runif(30, 0.5, 2.0)
)
task <- mlr3::as_task_classif(d, target = "y")
task$set_col_roles("w", roles = "weights_learner")
learner <- mlr3::lrn("classif.ggml")
learner$param_set$values$epochs <- 2L
learner$param_set$values$batch_size <- 10L
learner$param_set$values$backend <- "cpu"
expect_no_error(learner$train(task))
expect_s3_class(learner$model, "classif_ggml_model")
})
# ---------------------------------------------------------------------------
# LearnerRegrGGML
# ---------------------------------------------------------------------------
test_that("LearnerRegrGGML constructs and has the expected contract", {
skip_if_no_mlr3()
learner <- mlr3::lrn("regr.ggml")
expect_s3_class(learner, "LearnerRegrGGML")
expect_s3_class(learner, "LearnerRegr")
expect_equal(learner$id, "regr.ggml")
expect_equal(learner$predict_types, "response")
expect_equal(learner$feature_types, "numeric")
expect_true("marshal" %in% learner$properties)
})
test_that("LearnerRegrGGML trains and predicts on mtcars", {
skip_if_no_mlr3()
set.seed(42)
task <- mlr3::tsk("mtcars")
learner <- mlr3::lrn("regr.ggml")
learner$param_set$values$epochs <- 5L
learner$param_set$values$batch_size <- 8L
learner$param_set$values$backend <- "cpu"
learner$train(task)
expect_s3_class(learner$model, "regr_ggml_model")
pred <- learner$predict(task)
expect_s3_class(pred, "PredictionRegr")
expect_equal(length(pred$response), task$nrow)
expect_true(all(is.finite(pred$response)))
})
# ---------------------------------------------------------------------------
# Dictionary registration
# ---------------------------------------------------------------------------
test_that("learners are registered in mlr_learners", {
skip_if_no_mlr3()
keys <- mlr3::mlr_learners$keys()
expect_true("classif.ggml" %in% keys)
expect_true("regr.ggml" %in% keys)
learner <- mlr3::lrn("classif.ggml")
expect_s3_class(learner, "LearnerClassifGGML")
})
# ---------------------------------------------------------------------------
# Marshal roundtrip
# ---------------------------------------------------------------------------
test_that("ggml_marshal_model / ggml_unmarshal_model roundtrip on sequential", {
set.seed(42)
m <- ggml_default_mlp(n_features = 4L, n_out = 3L, task_type = "classif",
hidden_layers = c(8L))
m <- ggml_compile(m, optimizer = "adam",
loss = "categorical_crossentropy", backend = "cpu")
x <- matrix(rnorm(40), 10, 4)
y <- matrix(0, 10, 3); y[cbind(1:10, sample(1:3, 10, replace = TRUE))] <- 1
m <- ggml_fit(m, x, y, epochs = 1L, batch_size = 10L, verbose = 0L)
blob <- ggml_marshal_model(m)
expect_s3_class(blob, "ggmlR_marshaled")
expect_equal(blob$format, "ggmlR.marshal")
expect_equal(blob$version, 1L)
expect_equal(blob$api, "sequential")
expect_true(is.raw(blob$payload))
expect_gt(length(blob$payload), 0L)
m2 <- ggml_unmarshal_model(blob, backend = "cpu")
expect_s3_class(m2, "ggml_sequential_model")
expect_true(m2$compiled)
# predictions should match bitwise-close (same weights, same backend)
p1 <- ggml_predict(m, x)
p2 <- ggml_predict(m2, x)
expect_equal(dim(p1), dim(p2))
expect_lt(max(abs(p1 - p2)), 1e-5)
})
test_that("ggml_unmarshal_model detects a corrupted payload via sha256", {
skip_if_not_installed("digest")
m <- ggml_default_mlp(n_features = 3L, n_out = 2L, task_type = "classif",
hidden_layers = c(4L))
m <- ggml_compile(m, optimizer = "adam",
loss = "categorical_crossentropy", backend = "cpu")
blob <- ggml_marshal_model(m)
# Flip one byte in the payload
blob$payload[1L] <- as.raw(bitwXor(as.integer(blob$payload[1L]), 0xFFL))
expect_error(ggml_unmarshal_model(blob, backend = "cpu"),
"checksum mismatch")
})
test_that("learner $marshal() / $unmarshal() roundtrip on classif", {
skip_if_no_mlr3()
set.seed(42)
task <- mlr3::tsk("iris")
learner <- mlr3::lrn("classif.ggml")
learner$param_set$values$epochs <- 3L
learner$param_set$values$batch_size <- 10L
learner$param_set$values$backend <- "cpu"
learner$predict_type <- "prob"
learner$train(task)
p_before <- learner$predict(task)$prob
expect_false(learner$marshaled)
learner$marshal()
expect_true(learner$marshaled)
expect_s3_class(learner$model, "classif_ggml_model_marshaled")
learner$unmarshal()
expect_false(learner$marshaled)
expect_s3_class(learner$model, "classif_ggml_model")
p_after <- learner$predict(task)$prob
expect_equal(dim(p_before), dim(p_after))
expect_lt(max(abs(p_before - p_after)), 1e-5)
})
test_that("learner $marshal() / $unmarshal() roundtrip on regr", {
skip_if_no_mlr3()
set.seed(42)
task <- mlr3::tsk("mtcars")
learner <- mlr3::lrn("regr.ggml")
learner$param_set$values$epochs <- 3L
learner$param_set$values$batch_size <- 8L
learner$param_set$values$backend <- "cpu"
learner$train(task)
r_before <- learner$predict(task)$response
learner$marshal()
expect_true(learner$marshaled)
learner$unmarshal()
r_after <- learner$predict(task)$response
expect_lt(max(abs(r_before - r_after)), 1e-5)
})
# ---------------------------------------------------------------------------
# Resampling
# ---------------------------------------------------------------------------
test_that("classif.ggml works with 3-fold CV resampling", {
skip_if_no_mlr3()
set.seed(42)
task <- mlr3::tsk("iris")
learner <- mlr3::lrn("classif.ggml")
learner$param_set$values$epochs <- 3L
learner$param_set$values$batch_size <- 10L
learner$param_set$values$backend <- "cpu"
rr <- suppress_mlr3_output(
mlr3::resample(task, learner, mlr3::rsmp("cv", folds = 3L))
)
acc <- rr$aggregate(mlr3::msr("classif.acc"))
expect_true(is.numeric(acc))
expect_true(acc >= 0 && acc <= 1)
})
test_that("regr.ggml works with 3-fold CV resampling", {
skip_if_no_mlr3()
set.seed(42)
task <- mlr3::tsk("mtcars")
learner <- mlr3::lrn("regr.ggml")
learner$param_set$values$epochs <- 3L
learner$param_set$values$batch_size <- 8L
learner$param_set$values$backend <- "cpu"
rr <- suppress_mlr3_output(
mlr3::resample(task, learner, mlr3::rsmp("cv", folds = 3L))
)
rmse <- rr$aggregate(mlr3::msr("regr.rmse"))
expect_true(is.numeric(rmse))
expect_true(is.finite(rmse))
})
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.