chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:27:18 +08:00
commit d72d1a58f0
553 changed files with 214565 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
\.appveyor\.yml
AUTOCONF_UBUNTU_VERSION
^autom4te.cache/.*$
^.*\.bin
^build_r.R$
\.clang-format
^.*\.clusterfuzzlite$
^cran-comments\.md$
^docs$
^.*\.dll
\.drone\.yml
^.*\.dylib
\.git
\.gitkeep$
^.*\.history
^Makefile$
^.*\.o
^.*\.out
^pkgdown$
^recreate-configure\.sh$
^.*\.so
^src/build/.*$
^src/CMakeLists.txt$
^src/external_libs/compute/.appveyor.yml$
^src/external_libs/compute/.coveralls.yml$
^src/external_libs/compute/.travis.yml$
^src/external_libs/compute/test/.*$
^src/external_libs/compute/index.html$
^src/external_libs/compute/.git$
^src/external_libs/compute/.gitignore$
^src/external_libs/compute/CONTRIBUTING.md$
^src/external_libs/compute/README.md$
src/external_libs/fast_double_parser/benchmarks
src/external_libs/fast_double_parser/Makefile
src/external_libs/fast_double_parser/.*\.md
src/external_libs/fast_double_parser/tests
src/external_libs/fast_double_parser/.*\.yaml
src/external_libs/fast_double_parser/.*\.yml
src/external_libs/fmt/.*\.md
src/external_libs/fmt/.travis.yml
src/external_libs/fmt/doc
src/external_libs/fmt/support/Android\.mk
src/external_libs/fmt/support/bazel/.bazel.*
src/external_libs/fmt/support/.*\.gradle
src/external_libs/fmt/support/.*\.pro
src/external_libs/fmt/support/.*\.py
src/external_libs/fmt/support/rtd
src/external_libs/fmt/support/.*sublime-syntax
src/external_libs/fmt/support/Vagrantfile
src/external_libs/fmt/support/.*\.xml
src/external_libs/fmt/support/.*\.yml
src/external_libs/fmt/test
+1
View File
@@ -0,0 +1 @@
2.71-2
+66
View File
@@ -0,0 +1,66 @@
Package: lightgbm
Type: Package
Title: Light Gradient Boosting Machine
Version: ~~VERSION~~
Date: ~~DATE~~
Authors@R: c(
person("Yu", "Shi", email = "yushi2@microsoft.com", role = c("aut")),
person("Guolin", "Ke", email = "guolin.ke@outlook.com", role = c("aut")),
person("Damien", "Soukhavong", email = "damien.soukhavong@skema.edu", role = c("aut")),
person("James", "Lamb", email="jaylamb20@gmail.com", role = c("aut", "cre")),
person("Qi", "Meng", role = c("aut")),
person("Thomas", "Finley", role = c("aut")),
person("Taifeng", "Wang", role = c("aut")),
person("Wei", "Chen", role = c("aut")),
person("Weidong", "Ma", role = c("aut")),
person("Qiwei", "Ye", role = c("aut")),
person("Tie-Yan", "Liu", role = c("aut")),
person("Nikita", "Titov", role = c("aut")),
person("Yachen", "Yan", role = c("ctb")),
person("Microsoft Corporation", role = c("cph")),
person("Dropbox, Inc.", role = c("cph")),
person("Alberto", "Ferreira", role = c("ctb")),
person("Daniel", "Lemire", role = c("ctb")),
person("Victor", "Zverovich", role = c("cph")),
person("IBM Corporation", role = c("ctb")),
person("David", "Cortes", role = c("aut")),
person("Michael", "Mayer", role = c("ctb"))
)
Description: Tree based algorithms can be improved by introducing boosting frameworks.
'LightGBM' is one such framework, based on Ke, Guolin et al. (2017) <https://proceedings.neurips.cc/paper/2017/hash/6449f44a102fde848669bdd9eb6b76fa-Abstract.html>.
This package offers an R interface to work with it.
It is designed to be distributed and efficient with the following advantages:
1. Faster training speed and higher efficiency.
2. Lower memory usage.
3. Better accuracy.
4. Parallel learning supported.
5. Capable of handling large-scale data.
In recognition of these advantages, 'LightGBM' has been widely-used in many winning solutions of machine learning competitions.
Comparison experiments on public datasets suggest that 'LightGBM' can outperform existing boosting frameworks on both efficiency and accuracy, with significantly lower memory consumption. In addition, parallel experiments suggest that in certain circumstances, 'LightGBM' can achieve a linear speed-up in training time by using multiple machines.
Encoding: UTF-8
License: MIT + file LICENSE
URL: https://github.com/lightgbm-org/LightGBM
BugReports: https://github.com/lightgbm-org/LightGBM/issues
NeedsCompilation: yes
Biarch: true
VignetteBuilder: knitr
Suggests:
knitr,
markdown,
processx,
RhpcBLASctl,
testthat
Depends:
R (>= 4.0)
Imports:
R6 (>= 2.4.0),
data.table (>= 1.9.6),
graphics,
jsonlite (>= 1.0),
Matrix (>= 1.1-0),
methods,
parallel,
utils
SystemRequirements:
C++17
Config/roxygen2/version: 8.0.0
+2
View File
@@ -0,0 +1,2 @@
YEAR: 2016
COPYRIGHT HOLDER: Microsoft Corporation
+66
View File
@@ -0,0 +1,66 @@
# Generated by roxygen2: do not edit by hand
S3method("dimnames<-",lgb.Dataset)
S3method(dim,lgb.Dataset)
S3method(dimnames,lgb.Dataset)
S3method(get_field,lgb.Dataset)
S3method(predict,lgb.Booster)
S3method(print,lgb.Booster)
S3method(set_field,lgb.Dataset)
S3method(summary,lgb.Booster)
export(getLGBMthreads)
export(get_field)
export(lgb.Dataset)
export(lgb.Dataset.construct)
export(lgb.Dataset.create.valid)
export(lgb.Dataset.save)
export(lgb.Dataset.set.categorical)
export(lgb.Dataset.set.reference)
export(lgb.configure_fast_predict)
export(lgb.convert_with_rules)
export(lgb.cv)
export(lgb.drop_serialized)
export(lgb.dump)
export(lgb.get.eval.result)
export(lgb.importance)
export(lgb.interpret)
export(lgb.interprete)
export(lgb.load)
export(lgb.make_serializable)
export(lgb.model.dt.tree)
export(lgb.plot.importance)
export(lgb.plot.interpretation)
export(lgb.restore_handle)
export(lgb.save)
export(lgb.slice.Dataset)
export(lgb.train)
export(lightgbm)
export(setLGBMthreads)
export(set_field)
import(methods)
importClassesFrom(Matrix,CsparseMatrix)
importClassesFrom(Matrix,RsparseMatrix)
importClassesFrom(Matrix,dgCMatrix)
importClassesFrom(Matrix,dgRMatrix)
importClassesFrom(Matrix,dsparseMatrix)
importClassesFrom(Matrix,dsparseVector)
importFrom(Matrix,Matrix)
importFrom(R6,R6Class)
importFrom(data.table,":=")
importFrom(data.table,as.data.table)
importFrom(data.table,data.table)
importFrom(data.table,rbindlist)
importFrom(data.table,set)
importFrom(data.table,setnames)
importFrom(data.table,setorder)
importFrom(data.table,setorderv)
importFrom(graphics,barplot)
importFrom(graphics,par)
importFrom(jsonlite,fromJSON)
importFrom(methods,is)
importFrom(methods,new)
importFrom(parallel,detectCores)
importFrom(stats,quantile)
importFrom(utils,modifyList)
importFrom(utils,read.delim)
useDynLib(lightgbm , .registration = TRUE)
+104
View File
@@ -0,0 +1,104 @@
# Central location for parameter aliases.
# See https://lightgbm.readthedocs.io/en/latest/Parameters.html#core-parameters
# [description] List of respected parameter aliases specific to lgb.Dataset. Wrapped in a function to
# take advantage of lazy evaluation (so it doesn't matter what order
# R sources files during installation).
# [return] A named list, where each key is a parameter relevant to lgb.Dataset and each value is a character
# vector of corresponding aliases.
.DATASET_PARAMETERS <- function() {
all_aliases <- .PARAMETER_ALIASES()
return(all_aliases[c(
"bin_construct_sample_cnt"
, "categorical_feature"
, "data_random_seed"
, "enable_bundle"
, "feature_pre_filter"
, "forcedbins_filename"
, "group_column"
, "header"
, "ignore_column"
, "is_enable_sparse"
, "label_column"
, "linear_tree"
, "max_bin"
, "max_bin_by_feature"
, "min_data_in_bin"
, "pre_partition"
, "precise_float_parser"
, "two_round"
, "use_missing"
, "weight_column"
, "zero_as_missing"
)])
}
# [description] Non-exported environment, used for caching details that only need to be
# computed once per R session.
.lgb_session_cache_env <- new.env()
# [description] List of respected parameter aliases. Wrapped in a function to take advantage of
# lazy evaluation (so it doesn't matter what order R sources files during installation).
# [return] A named list, where each key is a main LightGBM parameter and each value is a character
# vector of corresponding aliases.
.PARAMETER_ALIASES <- function() {
if (exists("PARAMETER_ALIASES", where = .lgb_session_cache_env)) {
return(get("PARAMETER_ALIASES", envir = .lgb_session_cache_env))
}
params_to_aliases <- jsonlite::fromJSON(
.Call(
LGBM_DumpParamAliases_R
)
)
for (main_name in names(params_to_aliases)) {
aliases_with_main_name <- c(main_name, unlist(params_to_aliases[[main_name]]))
params_to_aliases[[main_name]] <- aliases_with_main_name
}
# store in cache so the next call to `.PARAMETER_ALIASES()` doesn't need to recompute this
assign(
x = "PARAMETER_ALIASES"
, value = params_to_aliases
, envir = .lgb_session_cache_env
)
return(params_to_aliases)
}
# [description]
# Per https://github.com/lightgbm-org/LightGBM/blob/main/docs/Parameters.rst#metric,
# a few different strings can be used to indicate "no metrics".
# [returns]
# A character vector
.NO_METRIC_STRINGS <- function() {
return(
c(
"na"
, "None"
, "null"
, "custom"
)
)
}
.MULTICLASS_OBJECTIVES <- function() {
return(
c(
"multi_logloss"
, "multiclass"
, "softmax"
, "multiclassova"
, "multiclass_ova"
, "ova"
, "ovr"
)
)
}
.BINARY_OBJECTIVES <- function() {
return(
c(
"binary_logloss"
, "binary"
, "binary_error"
)
)
}
+368
View File
@@ -0,0 +1,368 @@
# constants that control naming in lists
.EVAL_KEY <- function() {
return("eval")
}
.EVAL_ERR_KEY <- function() {
return("eval_err")
}
#' @importFrom R6 R6Class
CB_ENV <- R6::R6Class(
"lgb.cb_env",
cloneable = FALSE,
public = list(
model = NULL,
iteration = NULL,
begin_iteration = NULL,
end_iteration = NULL,
eval_list = list(),
eval_err_list = list(),
best_iter = -1L,
best_score = NA,
met_early_stop = FALSE
)
)
# Format the evaluation metric string
.format_eval_string <- function(eval_res, eval_err) {
# Check for empty evaluation string
if (is.null(eval_res) || length(eval_res) == 0L) {
stop("no evaluation results")
}
# Check for empty evaluation error
if (!is.null(eval_err)) {
return(sprintf("%s\'s %s:%g+%g", eval_res$data_name, eval_res$name, eval_res$value, eval_err))
} else {
return(sprintf("%s\'s %s:%g", eval_res$data_name, eval_res$name, eval_res$value))
}
}
.merge_eval_string <- function(env) {
# Check length of evaluation list
if (length(env$eval_list) <= 0L) {
return("")
}
# Get evaluation
msg <- list(sprintf("[%d]:", env$iteration))
# Set if evaluation error
is_eval_err <- length(env$eval_err_list) > 0L
# Loop through evaluation list
for (j in seq_along(env$eval_list)) {
# Store evaluation error
eval_err <- NULL
if (isTRUE(is_eval_err)) {
eval_err <- env$eval_err_list[[j]]
}
# Set error message
msg <- c(msg, .format_eval_string(eval_res = env$eval_list[[j]], eval_err = eval_err))
}
return(paste(msg, collapse = " "))
}
cb_print_evaluation <- function(period) {
# Create callback
callback <- function(env) {
# Check if period is at least 1 or more
if (period > 0L) {
# Store iteration
i <- env$iteration
# Check if iteration matches moduo
if ((i - 1L) %% period == 0L || is.element(i, c(env$begin_iteration, env$end_iteration))) {
# Merge evaluation string
msg <- .merge_eval_string(env = env)
# Check if message is existing
if (nchar(msg) > 0L) {
cat(.merge_eval_string(env = env), "\n")
}
}
}
return(invisible(NULL))
}
# Store attributes
attr(callback, "call") <- match.call()
attr(callback, "name") <- "cb_print_evaluation"
return(callback)
}
cb_record_evaluation <- function() {
# Create callback
callback <- function(env) {
if (length(env$eval_list) <= 0L) {
return()
}
# Set if evaluation error
is_eval_err <- length(env$eval_err_list) > 0L
# Check length of recorded evaluation
if (length(env$model$record_evals) == 0L) {
# Loop through each evaluation list element
for (j in seq_along(env$eval_list)) {
# Store names
data_name <- env$eval_list[[j]]$data_name
name <- env$eval_list[[j]]$name
env$model$record_evals$start_iter <- env$begin_iteration
# Check if evaluation record exists
if (is.null(env$model$record_evals[[data_name]])) {
env$model$record_evals[[data_name]] <- list()
}
# Create dummy lists
env$model$record_evals[[data_name]][[name]] <- list()
env$model$record_evals[[data_name]][[name]][[.EVAL_KEY()]] <- list()
env$model$record_evals[[data_name]][[name]][[.EVAL_ERR_KEY()]] <- list()
}
}
# Loop through each evaluation list element
for (j in seq_along(env$eval_list)) {
# Get evaluation data
eval_res <- env$eval_list[[j]]
eval_err <- NULL
if (isTRUE(is_eval_err)) {
eval_err <- env$eval_err_list[[j]]
}
# Store names
data_name <- eval_res$data_name
name <- eval_res$name
# Store evaluation data
env$model$record_evals[[data_name]][[name]][[.EVAL_KEY()]] <- c(
env$model$record_evals[[data_name]][[name]][[.EVAL_KEY()]]
, eval_res$value
)
env$model$record_evals[[data_name]][[name]][[.EVAL_ERR_KEY()]] <- c(
env$model$record_evals[[data_name]][[name]][[.EVAL_ERR_KEY()]]
, eval_err
)
}
return(invisible(NULL))
}
# Store attributes
attr(callback, "call") <- match.call()
attr(callback, "name") <- "cb_record_evaluation"
return(callback)
}
cb_early_stop <- function(stopping_rounds, first_metric_only, verbose) {
factor_to_bigger_better <- NULL
best_iter <- NULL
best_score <- NULL
best_msg <- NULL
eval_len <- NULL
# Initialization function
init <- function(env) {
# Early stopping cannot work without metrics
if (length(env$eval_list) == 0L) {
stop("For early stopping, valids must have at least one element")
}
# Store evaluation length
eval_len <<- length(env$eval_list)
# Check if verbose or not
if (isTRUE(verbose)) {
msg <- paste0(
"Will train until there is no improvement in "
, stopping_rounds
, " rounds.\n"
)
cat(msg)
}
# Internally treat everything as a maximization task
factor_to_bigger_better <<- rep.int(1.0, eval_len)
best_iter <<- rep.int(-1L, eval_len)
best_score <<- rep.int(-Inf, eval_len)
best_msg <<- list()
# Loop through evaluation elements
for (i in seq_len(eval_len)) {
# Prepend message
best_msg <<- c(best_msg, "")
# Internally treat everything as a maximization task
if (!isTRUE(env$eval_list[[i]]$higher_better)) {
factor_to_bigger_better[i] <<- -1.0
}
}
return(invisible(NULL))
}
# Create callback
callback <- function(env) {
# Check for empty evaluation
if (is.null(eval_len)) {
init(env = env)
}
# Store iteration
cur_iter <- env$iteration
# By default, any metric can trigger early stopping. This can be disabled
# with 'first_metric_only = TRUE'
if (isTRUE(first_metric_only)) {
evals_to_check <- 1L
} else {
evals_to_check <- seq_len(eval_len)
}
# Loop through evaluation
for (i in evals_to_check) {
# Store score
score <- env$eval_list[[i]]$value * factor_to_bigger_better[i]
# Check if score is better
if (score > best_score[i]) {
# Store new scores
best_score[i] <<- score
best_iter[i] <<- cur_iter
# Prepare to print if verbose
if (verbose) {
best_msg[[i]] <<- as.character(.merge_eval_string(env = env))
}
} else {
# Check if early stopping is required
if (cur_iter - best_iter[i] >= stopping_rounds) {
if (!is.null(env$model)) {
env$model$best_score <- best_score[i]
env$model$best_iter <- best_iter[i]
}
if (isTRUE(verbose)) {
cat(paste0("Early stopping, best iteration is: ", best_msg[[i]], "\n"))
}
# Store best iteration and stop
env$best_iter <- best_iter[i]
env$met_early_stop <- TRUE
}
}
if (!isTRUE(env$met_early_stop) && cur_iter == env$end_iteration) {
if (!is.null(env$model)) {
env$model$best_score <- best_score[i]
env$model$best_iter <- best_iter[i]
}
if (isTRUE(verbose)) {
cat(paste0("Did not meet early stopping, best iteration is: ", best_msg[[i]], "\n"))
}
# Store best iteration and stop
env$best_iter <- best_iter[i]
env$met_early_stop <- TRUE
}
}
return(invisible(NULL))
}
attr(callback, "call") <- match.call()
attr(callback, "name") <- "cb_early_stop"
return(callback)
}
# Extract callback names from the list of callbacks
.callback_names <- function(cb_list) {
return(unlist(lapply(cb_list, attr, "name")))
}
.add_cb <- function(cb_list, cb) {
# Combine two elements
cb_list <- c(cb_list, cb)
# Set names of elements
names(cb_list) <- .callback_names(cb_list = cb_list)
if ("cb_early_stop" %in% names(cb_list)) {
# Concatenate existing elements
cb_list <- c(cb_list, cb_list["cb_early_stop"])
# Remove only the first one
cb_list["cb_early_stop"] <- NULL
}
return(cb_list)
}
.categorize_callbacks <- function(cb_list) {
# Check for pre-iteration or post-iteration
return(
list(
pre_iter = Filter(function(x) {
pre <- attr(x, "is_pre_iteration")
!is.null(pre) && pre
}, cb_list),
post_iter = Filter(function(x) {
pre <- attr(x, "is_pre_iteration")
is.null(pre) || !pre
}, cb_list)
)
)
}
File diff suppressed because it is too large Load Diff
+94
View File
@@ -0,0 +1,94 @@
DataProcessor <- R6::R6Class(
classname = "lgb.DataProcessor",
public = list(
factor_levels = NULL,
process_label = function(label, objective, params) {
if (is.character(label)) {
label <- factor(label)
}
if (is.factor(label)) {
self$factor_levels <- levels(label)
if (length(self$factor_levels) <= 1L) {
stop("Labels to predict is a factor with <2 possible values.")
}
label <- as.numeric(label) - 1.0
out <- list(label = label)
if (length(self$factor_levels) == 2L) {
if (objective == "auto") {
objective <- "binary"
}
if (!(objective %in% .BINARY_OBJECTIVES())) {
stop("Two-level factors as labels only allowed for objective='binary' or objective='auto'.")
}
} else {
if (objective == "auto") {
objective <- "multiclass"
}
if (!(objective %in% .MULTICLASS_OBJECTIVES())) {
stop(
sprintf(
"Factors with >2 levels as labels only allowed for multi-class objectives. Got: %s (allowed: %s)"
, objective
, toString(.MULTICLASS_OBJECTIVES())
)
)
}
data_num_class <- length(self$factor_levels)
params <- .check_wrapper_param(
main_param_name = "num_class"
, params = params
, alternative_kwarg_value = data_num_class
)
if (params[["num_class"]] != data_num_class) {
warning(
sprintf(
"Found num_class=%d in params, but 'label' is a factor with %d levels. 'num_class' will be ignored."
, params[["num_class"]]
, data_num_class
)
)
params$num_class <- data_num_class
}
}
out$objective <- objective
out$params <- params
return(out)
} else {
label <- as.numeric(label)
if (objective == "auto") {
objective <- "regression"
}
out <- list(
label = label
, objective = objective
, params = params
)
return(out)
}
},
process_predictions = function(pred, type) {
if (NROW(self$factor_levels)) {
if (type == "class") {
pred <- as.integer(pred) + 1L
attributes(pred)$levels <- self$factor_levels
attributes(pred)$class <- "factor"
} else if (type %in% c("response", "raw")) {
if (is.matrix(pred) && ncol(pred) == length(self$factor_levels)) {
colnames(pred) <- self$factor_levels
}
}
}
return(pred)
}
)
)
File diff suppressed because it is too large Load Diff
+529
View File
@@ -0,0 +1,529 @@
#' @importFrom methods is new
#' @importFrom R6 R6Class
#' @importFrom utils read.delim
#' @importClassesFrom Matrix dsparseMatrix dsparseVector dgCMatrix dgRMatrix CsparseMatrix RsparseMatrix
Predictor <- R6::R6Class(
classname = "lgb.Predictor",
cloneable = FALSE,
public = list(
# Initialize will create a starter model
initialize = function(modelfile, params = list(), fast_predict_config = list()) {
private$params <- .params2str(params = params)
handle <- NULL
if (is.character(modelfile)) {
# Create handle on it
handle <- .Call(
LGBM_BoosterCreateFromModelfile_R
, path.expand(modelfile)
)
private$need_free_handle <- TRUE
} else if (methods::is(modelfile, "lgb.Booster.handle") || inherits(modelfile, "externalptr")) {
# Check if model file is a booster handle already
handle <- modelfile
private$need_free_handle <- FALSE
} else if (.is_Booster(modelfile)) {
handle <- modelfile$get_handle()
private$need_free_handle <- FALSE
} else {
stop("lgb.Predictor: modelfile must be either a character filename or an lgb.Booster.handle")
}
private$fast_predict_config <- fast_predict_config
# Override class and store it
class(handle) <- "lgb.Booster.handle"
private$handle <- handle
return(invisible(NULL))
},
# Get current iteration
current_iter = function() {
cur_iter <- 0L
.Call(
LGBM_BoosterGetCurrentIteration_R
, private$handle
, cur_iter
)
return(cur_iter)
},
# Predict from data
predict = function(data,
start_iteration = NULL,
num_iteration = NULL,
rawscore = FALSE,
predleaf = FALSE,
predcontrib = FALSE,
header = FALSE) {
# Check if number of iterations is existing - if not, then set it to -1 (use all)
if (is.null(num_iteration)) {
num_iteration <- -1L
}
# Check if start iterations is existing - if not, then set it to 0 (start from the first iteration)
if (is.null(start_iteration)) {
start_iteration <- 0L
}
# Check if data is a file name and not a matrix
if (identical(class(data), "character") && length(data) == 1L) {
data <- path.expand(data)
# Data is a filename, create a temporary file with a "lightgbm_" pattern in it
tmp_filename <- tempfile(pattern = "lightgbm_")
on.exit(unlink(tmp_filename), add = TRUE)
# Predict from temporary file
.Call(
LGBM_BoosterPredictForFile_R
, private$handle
, data
, as.integer(header)
, as.integer(rawscore)
, as.integer(predleaf)
, as.integer(predcontrib)
, as.integer(start_iteration)
, as.integer(num_iteration)
, private$params
, tmp_filename
)
# Get predictions from file
preds <- utils::read.delim(tmp_filename, header = FALSE, sep = "\t")
num_row <- nrow(preds)
preds <- as.vector(t(preds))
} else if (predcontrib && inherits(data, c("dsparseMatrix", "dsparseVector"))) {
ncols <- .Call(LGBM_BoosterGetNumFeature_R, private$handle)
ncols_out <- integer(1L)
.Call(LGBM_BoosterGetNumClasses_R, private$handle, ncols_out)
ncols_out <- (ncols + 1L) * max(ncols_out, 1L)
if (is.na(ncols_out)) {
ncols_out <- as.numeric(ncols + 1L) * as.numeric(max(ncols_out, 1L))
}
if (!inherits(data, "dsparseVector") && ncols_out > .Machine$integer.max) {
stop("Resulting matrix of feature contributions is too large for R to handle.")
}
if (inherits(data, "dsparseVector")) {
if (length(data) > ncols) {
stop(sprintf("Model was fitted to data with %d columns, input data has %.0f columns."
, ncols
, length(data)))
}
res <- .Call(
LGBM_BoosterPredictSparseOutput_R
, private$handle
, c(0L, as.integer(length(data@x)))
, data@i - 1L
, data@x
, TRUE
, 1L
, ncols
, start_iteration
, num_iteration
, private$params
)
out <- methods::new("dsparseVector")
out@i <- res$indices + 1L
out@x <- res$data
out@length <- ncols_out
return(out)
} else if (inherits(data, "dgRMatrix")) {
if (ncol(data) > ncols) {
stop(sprintf("Model was fitted to data with %d columns, input data has %.0f columns."
, ncols
, ncol(data)))
}
res <- .Call(
LGBM_BoosterPredictSparseOutput_R
, private$handle
, data@p
, data@j
, data@x
, TRUE
, nrow(data)
, ncols
, start_iteration
, num_iteration
, private$params
)
out <- methods::new("dgRMatrix")
out@p <- res$indptr
out@j <- res$indices
out@x <- res$data
out@Dim <- as.integer(c(nrow(data), ncols_out))
} else if (inherits(data, "dgCMatrix")) {
if (ncol(data) != ncols) {
stop(sprintf("Model was fitted to data with %d columns, input data has %.0f columns."
, ncols
, ncol(data)))
}
res <- .Call(
LGBM_BoosterPredictSparseOutput_R
, private$handle
, data@p
, data@i
, data@x
, FALSE
, nrow(data)
, ncols
, start_iteration
, num_iteration
, private$params
)
out <- methods::new("dgCMatrix")
out@p <- res$indptr
out@i <- res$indices
out@x <- res$data
out@Dim <- as.integer(c(nrow(data), length(res$indptr) - 1L))
} else {
stop(sprintf("Predictions on sparse inputs are only allowed for '%s', '%s', '%s' - got: %s"
, "dsparseVector"
, "dgRMatrix"
, "dgCMatrix"
, toString(class(data))))
}
if (NROW(row.names(data))) {
out@Dimnames[[1L]] <- row.names(data)
}
return(out)
} else {
# Not a file, we need to predict from R object
num_row <- nrow(data)
if (is.null(num_row)) {
num_row <- 1L
}
npred <- 0L
# Check number of predictions to do
.Call(
LGBM_BoosterCalcNumPredict_R
, private$handle
, as.integer(num_row)
, as.integer(rawscore)
, as.integer(predleaf)
, as.integer(predcontrib)
, as.integer(start_iteration)
, as.integer(num_iteration)
, npred
)
# Pre-allocate empty vector
preds <- numeric(npred)
# Check if data is a matrix
if (is.matrix(data)) {
# this if() prevents the memory and computational costs
# of converting something that is already "double" to "double"
if (storage.mode(data) != "double") {
storage.mode(data) <- "double"
}
if (nrow(data) == 1L) {
use_fast_config <- private$check_can_use_fast_predict_config(
csr = FALSE
, rawscore = rawscore
, predleaf = predleaf
, predcontrib = predcontrib
, start_iteration = start_iteration
, num_iteration = num_iteration
)
if (use_fast_config) {
.Call(
LGBM_BoosterPredictForMatSingleRowFast_R
, private$fast_predict_config$handle
, data
, preds
)
} else {
.Call(
LGBM_BoosterPredictForMatSingleRow_R
, private$handle
, data
, rawscore
, predleaf
, predcontrib
, start_iteration
, num_iteration
, private$params
, preds
)
}
} else {
.Call(
LGBM_BoosterPredictForMat_R
, private$handle
, data
, as.integer(nrow(data))
, as.integer(ncol(data))
, as.integer(rawscore)
, as.integer(predleaf)
, as.integer(predcontrib)
, as.integer(start_iteration)
, as.integer(num_iteration)
, private$params
, preds
)
}
} else if (inherits(data, "dsparseVector")) {
if (length(self$fast_predict_config)) {
ncols <- self$fast_predict_config$ncols
use_fast_config <- private$check_can_use_fast_predict_config(
csr = TRUE
, rawscore = rawscore
, predleaf = predleaf
, predcontrib = predcontrib
, start_iteration = start_iteration
, num_iteration = num_iteration
)
} else {
ncols <- .Call(LGBM_BoosterGetNumFeature_R, private$handle)
use_fast_config <- FALSE
}
if (length(data) > ncols) {
stop(sprintf("Model was fitted to data with %d columns, input data has %.0f columns."
, ncols
, length(data)))
}
if (use_fast_config) {
.Call(
LGBM_BoosterPredictForCSRSingleRowFast_R
, self$fast_predict_config$handle
, data@i - 1L
, data@x
, preds
)
} else {
.Call(
LGBM_BoosterPredictForCSRSingleRow_R
, private$handle
, data@i - 1L
, data@x
, ncols
, as.integer(rawscore)
, as.integer(predleaf)
, as.integer(predcontrib)
, start_iteration
, num_iteration
, private$params
, preds
)
}
} else if (inherits(data, "dgRMatrix")) {
ncols <- .Call(LGBM_BoosterGetNumFeature_R, private$handle)
if (ncol(data) > ncols) {
stop(sprintf("Model was fitted to data with %d columns, input data has %.0f columns."
, ncols
, ncol(data)))
}
if (nrow(data) == 1L) {
if (length(self$fast_predict_config)) {
ncols <- self$fast_predict_config$ncols
use_fast_config <- private$check_can_use_fast_predict_config(
csr = TRUE
, rawscore = rawscore
, predleaf = predleaf
, predcontrib = predcontrib
, start_iteration = start_iteration
, num_iteration = num_iteration
)
} else {
ncols <- .Call(LGBM_BoosterGetNumFeature_R, private$handle)
use_fast_config <- FALSE
}
if (use_fast_config) {
.Call(
LGBM_BoosterPredictForCSRSingleRowFast_R
, self$fast_predict_config$handle
, data@j
, data@x
, preds
)
} else {
.Call(
LGBM_BoosterPredictForCSRSingleRow_R
, private$handle
, data@j
, data@x
, ncols
, as.integer(rawscore)
, as.integer(predleaf)
, as.integer(predcontrib)
, start_iteration
, num_iteration
, private$params
, preds
)
}
} else {
.Call(
LGBM_BoosterPredictForCSR_R
, private$handle
, data@p
, data@j
, data@x
, ncols
, as.integer(rawscore)
, as.integer(predleaf)
, as.integer(predcontrib)
, start_iteration
, num_iteration
, private$params
, preds
)
}
} else if (methods::is(data, "dgCMatrix")) {
if (length(data@p) > 2147483647L) {
stop("Cannot support large CSC matrix")
}
# Check if data is a dgCMatrix (sparse matrix, column compressed format)
.Call(
LGBM_BoosterPredictForCSC_R
, private$handle
, data@p
, data@i
, data@x
, length(data@p)
, length(data@x)
, nrow(data)
, as.integer(rawscore)
, as.integer(predleaf)
, as.integer(predcontrib)
, as.integer(start_iteration)
, as.integer(num_iteration)
, private$params
, preds
)
} else {
stop("predict: cannot predict on data of class ", sQuote(class(data), q = FALSE))
}
}
# Check if number of rows is strange (not a multiple of the dataset rows)
if (length(preds) %% num_row != 0L) {
stop(
"predict: prediction length "
, sQuote(length(preds), q = FALSE)
, " is not a multiple of nrows(data): "
, sQuote(num_row, q = FALSE)
)
}
# Get number of cases per row
npred_per_case <- length(preds) / num_row
# Data reshaping
if (npred_per_case > 1L || predleaf || predcontrib) {
preds <- matrix(preds, ncol = npred_per_case, byrow = TRUE)
}
# Keep row names if possible
if (NROW(row.names(data)) && NROW(data) == NROW(preds)) {
if (is.null(dim(preds))) {
names(preds) <- row.names(data)
} else {
row.names(preds) <- row.names(data)
}
}
return(preds)
}
),
private = list(
handle = NULL
, need_free_handle = FALSE
, params = ""
, fast_predict_config = list()
, check_can_use_fast_predict_config = function(csr,
rawscore,
predleaf,
predcontrib,
start_iteration,
num_iteration) {
if (!NROW(private$fast_predict_config)) {
return(FALSE)
}
if (.is_null_handle(private$fast_predict_config$handle)) {
warning(paste0("Model had fast CSR predict configuration, but it is inactive."
, " Try re-generating it through 'lgb.configure_fast_predict'."))
return(FALSE)
}
if (isTRUE(csr) != private$fast_predict_config$csr) {
return(FALSE)
}
return(
private$params == "" &&
private$fast_predict_config$rawscore == rawscore &&
private$fast_predict_config$predleaf == predleaf &&
private$fast_predict_config$predcontrib == predcontrib &&
.equal_or_both_null(private$fast_predict_config$start_iteration, start_iteration) &&
.equal_or_both_null(private$fast_predict_config$num_iteration, num_iteration)
)
}
# finalize() will free up the handles
, finalize = function() {
if (private$need_free_handle) {
.Call(
LGBM_BoosterFree_R
, private$handle
)
private$handle <- NULL
}
return(invisible(NULL))
}
)
)
+207
View File
@@ -0,0 +1,207 @@
# [description] get all column classes of a data.table or data.frame.
# This function collapses the result of class() into a single string
.get_column_classes <- function(df) {
return(
vapply(
X = df
, FUN = function(x) {
paste(class(x), collapse = ",")
}
, FUN.VALUE = character(1L)
)
)
}
# [description] check a data frame or data table for columns that are any
# type other than numeric and integer. This is used by lgb.convert_with_rules()
# to warn if more action is needed by users
# before a dataset can be converted to a lgb.Dataset.
.warn_for_unconverted_columns <- function(df, function_name) {
column_classes <- .get_column_classes(df = df)
unconverted_columns <- column_classes[!(column_classes %in% c("numeric", "integer"))]
if (length(unconverted_columns) > 0L) {
col_detail_string <- toString(
paste0(
names(unconverted_columns)
, " ("
, unconverted_columns
, ")"
)
)
msg <- paste0(
function_name
, ": "
, length(unconverted_columns)
, " columns are not numeric or integer. These need to be dropped or converted to "
, "be used in an lgb.Dataset object. "
, col_detail_string
)
warning(msg)
}
return(invisible(NULL))
}
.LGB_CONVERT_DEFAULT_FOR_LOGICAL_NA <- function() {
return(-1L)
}
.LGB_CONVERT_DEFAULT_FOR_NON_LOGICAL_NA <- function() {
return(0L)
}
#' @name lgb.convert_with_rules
#' @title Data preparator for LightGBM datasets with rules (integer)
#' @description Attempts to prepare a clean dataset to prepare to put in a \code{lgb.Dataset}.
#' Factor, character, and logical columns are converted to integer. Missing values
#' in factors and characters will be filled with 0L. Missing values in logicals
#' will be filled with -1L.
#'
#' This function returns and optionally takes in "rules" the describe exactly
#' how to convert values in columns.
#'
#' Columns that contain only NA values will be converted by this function but will
#' not show up in the returned \code{rules}.
#'
#' NOTE: In previous releases of LightGBM, this function was called \code{lgb.prepare_rules2}.
#' @param data A data.frame or data.table to prepare.
#' @param rules A set of rules from the data preparator, if already used. This should be an R list,
#' where names are column names in \code{data} and values are named character
#' vectors whose names are column values and whose values are new values to
#' replace them with.
#' @return A list with the cleaned dataset (\code{data}) and the rules (\code{rules}).
#' Note that the data must be converted to a matrix format (\code{as.matrix}) for input in
#' \code{lgb.Dataset}.
#'
#' @examples
#' \donttest{
#' data(iris)
#'
#' str(iris)
#'
#' new_iris <- lgb.convert_with_rules(data = iris)
#' str(new_iris$data)
#'
#' data(iris) # Erase iris dataset
#' iris$Species[1L] <- "NEW FACTOR" # Introduce junk factor (NA)
#'
#' # Use conversion using known rules
#' # Unknown factors become 0, excellent for sparse datasets
#' newer_iris <- lgb.convert_with_rules(data = iris, rules = new_iris$rules)
#'
#' # Unknown factor is now zero, perfect for sparse datasets
#' newer_iris$data[1L, ] # Species became 0 as it is an unknown factor
#'
#' newer_iris$data[1L, 5L] <- 1.0 # Put back real initial value
#'
#' # Is the newly created dataset equal? YES!
#' all.equal(new_iris$data, newer_iris$data)
#'
#' # Can we test our own rules?
#' data(iris) # Erase iris dataset
#'
#' # We remapped values differently
#' personal_rules <- list(
#' Species = c(
#' "setosa" = 3L
#' , "versicolor" = 2L
#' , "virginica" = 1L
#' )
#' )
#' newest_iris <- lgb.convert_with_rules(data = iris, rules = personal_rules)
#' str(newest_iris$data) # SUCCESS!
#' }
#' @importFrom data.table set
#' @export
lgb.convert_with_rules <- function(data, rules = NULL) {
column_classes <- .get_column_classes(df = data)
is_data_table <- data.table::is.data.table(x = data)
is_data_frame <- is.data.frame(data)
if (!(is_data_table || is_data_frame)) {
stop(
"lgb.convert_with_rules: you provided "
, paste(class(data), collapse = " & ")
, " but data should have class data.frame or data.table"
)
}
# if user didn't provide rules, create them
if (is.null(rules)) {
rules <- list()
columns_to_fix <- which(column_classes %in% c("character", "factor", "logical"))
for (i in columns_to_fix) {
col_values <- data[[i]]
# Get unique values
if (is.factor(col_values)) {
unique_vals <- levels(col_values)
unique_vals <- unique_vals[!is.na(unique_vals)]
mini_numeric <- seq_along(unique_vals) # respect ordinal
} else if (is.character(col_values)) {
unique_vals <- as.factor(unique(col_values))
unique_vals <- unique_vals[!is.na(unique_vals)]
mini_numeric <- as.integer(unique_vals) # no respect for ordinal
} else if (is.logical(col_values)) {
unique_vals <- c(FALSE, TRUE)
mini_numeric <- c(0L, 1L)
}
# don't add rules for all-NA columns
if (length(unique_vals) > 0L) {
col_name <- names(data)[i]
rules[[col_name]] <- mini_numeric
names(rules[[col_name]]) <- unique_vals
}
}
}
for (col_name in names(rules)) {
if (column_classes[[col_name]] == "logical") {
default_value_for_na <- .LGB_CONVERT_DEFAULT_FOR_LOGICAL_NA()
} else {
default_value_for_na <- .LGB_CONVERT_DEFAULT_FOR_NON_LOGICAL_NA()
}
if (is_data_table) {
data.table::set(
x = data
, j = col_name
, value = unname(rules[[col_name]][data[[col_name]]])
)
data[is.na(get(col_name)), (col_name) := default_value_for_na]
} else {
data[[col_name]] <- unname(rules[[col_name]][data[[col_name]]])
data[is.na(data[col_name]), col_name] <- default_value_for_na
}
}
# if any all-NA columns exist, they won't be in rules. Convert them
all_na_cols <- which(
sapply(
X = data
, FUN = function(x) {
(is.factor(x) || is.character(x) || is.logical(x)) && all(is.na(unique(x)))
}
)
)
for (col_name in all_na_cols) {
if (column_classes[[col_name]] == "logical") {
default_value_for_na <- .LGB_CONVERT_DEFAULT_FOR_LOGICAL_NA()
} else {
default_value_for_na <- .LGB_CONVERT_DEFAULT_FOR_NON_LOGICAL_NA()
}
if (is_data_table) {
data[, (col_name) := rep(default_value_for_na, .N)]
} else {
data[[col_name]] <- default_value_for_na
}
}
.warn_for_unconverted_columns(df = data, function_name = "lgb.convert_with_rules")
return(list(data = data, rules = rules))
}
+617
View File
@@ -0,0 +1,617 @@
#' @importFrom R6 R6Class
CVBooster <- R6::R6Class(
classname = "lgb.CVBooster",
cloneable = FALSE,
public = list(
best_iter = -1L,
best_score = NA,
record_evals = list(),
boosters = list(),
initialize = function(x) {
self$boosters <- x
return(invisible(NULL))
},
reset_parameter = function(new_params) {
for (x in self$boosters) {
x[["booster"]]$reset_parameter(params = new_params)
}
return(invisible(self))
}
)
)
#' @name lgb.cv
#' @title Main CV logic for LightGBM
#' @description Cross validation logic used by LightGBM
#' @inheritParams lgb_shared_params
#' @param nfold the original dataset is randomly partitioned into \code{nfold} equal size subsamples.
#' @param record Boolean, TRUE will record iteration message to \code{booster$record_evals}
#' @param showsd \code{boolean}, whether to show standard deviation of cross validation.
#' This parameter defaults to \code{TRUE}. Setting it to \code{FALSE} can lead to a
#' slight speedup by avoiding unnecessary computation.
#' @param stratified a \code{boolean} indicating whether sampling of folds should be stratified
#' by the values of outcome labels.
#' @param folds \code{list} provides a possibility to use a list of pre-defined CV folds
#' (each element must be a vector of test fold's indices). When folds are supplied,
#' the \code{nfold} and \code{stratified} parameters are ignored.
#' @param callbacks List of callback functions that are applied at each iteration.
#' @param reset_data Boolean, setting it to TRUE (not the default value) will transform the booster model
#' into a predictor model which frees up memory and the original datasets
#' @param eval_train_metric \code{boolean}, whether to add the cross validation results on the
#' training data. This parameter defaults to \code{FALSE}. Setting it to \code{TRUE}
#' will increase run time.
#' @inheritSection lgb_shared_params Early Stopping
#' @return a trained model \code{lgb.CVBooster}.
#'
#' @examples
#' \donttest{
#' \dontshow{setLGBMthreads(2L)}
#' \dontshow{data.table::setDTthreads(1L)}
#' data(agaricus.train, package = "lightgbm")
#' train <- agaricus.train
#' dtrain <- lgb.Dataset(train$data, label = train$label)
#' params <- list(
#' objective = "regression"
#' , metric = "l2"
#' , min_data = 1L
#' , learning_rate = 1.0
#' , num_threads = 2L
#' )
#' model <- lgb.cv(
#' params = params
#' , data = dtrain
#' , nrounds = 5L
#' , nfold = 3L
#' )
#' }
#'
#' @importFrom data.table data.table setorderv
#' @export
lgb.cv <- function(params = list()
, data
, nrounds = 100L
, nfold = 3L
, obj = NULL
, eval = NULL
, verbose = 1L
, record = TRUE
, eval_freq = 1L
, showsd = TRUE
, stratified = TRUE
, folds = NULL
, init_model = NULL
, early_stopping_rounds = NULL
, callbacks = list()
, reset_data = FALSE
, serializable = TRUE
, eval_train_metric = FALSE
) {
if (nrounds <= 0L) {
stop("nrounds should be greater than zero")
}
if (!.is_Dataset(x = data)) {
stop("lgb.cv: data must be an lgb.Dataset instance")
}
# set some parameters, resolving the way they were passed in with other parameters
# in `params`.
# this ensures that the model stored with Booster$save() correctly represents
# what was passed in
params <- .check_wrapper_param(
main_param_name = "verbosity"
, params = params
, alternative_kwarg_value = verbose
)
params <- .check_wrapper_param(
main_param_name = "num_iterations"
, params = params
, alternative_kwarg_value = nrounds
)
params <- .check_wrapper_param(
main_param_name = "metric"
, params = params
, alternative_kwarg_value = NULL
)
params <- .check_wrapper_param(
main_param_name = "objective"
, params = params
, alternative_kwarg_value = obj
)
params <- .check_wrapper_param(
main_param_name = "early_stopping_round"
, params = params
, alternative_kwarg_value = early_stopping_rounds
)
early_stopping_rounds <- params[["early_stopping_round"]]
# extract any function objects passed for objective or metric
fobj <- NULL
if (is.function(params$objective)) {
fobj <- params$objective
params$objective <- "none"
}
# If eval is a single function, store it as a 1-element list
# (for backwards compatibility). If it is a list of functions, store
# all of them. This makes it possible to pass any mix of strings like "auc"
# and custom functions to eval
params <- .check_eval(params = params, eval = eval)
eval_functions <- list(NULL)
if (is.function(eval)) {
eval_functions <- list(eval)
}
if (methods::is(eval, "list")) {
eval_functions <- Filter(
f = is.function
, x = eval
)
}
# Init predictor to empty
predictor <- NULL
# Check for boosting from a trained model
if (is.character(init_model)) {
predictor <- Predictor$new(modelfile = init_model)
} else if (.is_Booster(x = init_model)) {
predictor <- init_model$to_predictor()
}
# Set the iteration to start from / end to (and check for boosting from a trained model, again)
begin_iteration <- 1L
if (!is.null(predictor)) {
begin_iteration <- predictor$current_iter() + 1L
}
end_iteration <- begin_iteration + params[["num_iterations"]] - 1L
# pop interaction_constraints off of params. It needs some preprocessing on the
# R side before being passed into the Dataset object
interaction_constraints <- params[["interaction_constraints"]]
params["interaction_constraints"] <- NULL
# Construct datasets, if needed
data$update_params(params = params)
data$construct()
# Check interaction constraints
params[["interaction_constraints"]] <- .check_interaction_constraints(
interaction_constraints = interaction_constraints
, column_names = data$get_colnames()
)
# Update parameters with parsed parameters
data$update_params(params = params)
# Create the predictor set
data$.__enclos_env__$private$set_predictor(predictor = predictor)
if (!is.null(folds)) {
# Check for list of folds or for single value
if (!identical(class(folds), "list") || length(folds) < 2L) {
stop(sQuote("folds"), " must be a list with 2 or more elements that are vectors of indices for each CV-fold")
}
} else {
if (nfold <= 1L) {
stop(sQuote("nfold"), " must be > 1")
}
# Create folds
folds <- .generate_cv_folds(
nfold = nfold
, nrows = nrow(data)
, stratified = stratified
, label = get_field(dataset = data, field_name = "label")
, group = get_field(dataset = data, field_name = "group")
, params = params
)
}
# Add printing log callback
if (params[["verbosity"]] > 0L && eval_freq > 0L) {
callbacks <- .add_cb(cb_list = callbacks, cb = cb_print_evaluation(period = eval_freq))
}
# Add evaluation log callback
if (record) {
callbacks <- .add_cb(cb_list = callbacks, cb = cb_record_evaluation())
}
# Did user pass parameters that indicate they want to use early stopping?
using_early_stopping <- !is.null(early_stopping_rounds) && early_stopping_rounds > 0L
boosting_param_names <- .PARAMETER_ALIASES()[["boosting"]]
using_dart <- any(
sapply(
X = boosting_param_names
, FUN = function(param) {
identical(params[[param]], "dart")
}
)
)
# Cannot use early stopping with 'dart' boosting
if (using_dart) {
if (using_early_stopping) {
warning("Early stopping is not available in 'dart' mode.")
}
using_early_stopping <- FALSE
# Remove the cb_early_stop() function if it was passed in to callbacks
callbacks <- Filter(
f = function(cb_func) {
!identical(attr(cb_func, "name"), "cb_early_stop")
}
, x = callbacks
)
}
# If user supplied early_stopping_rounds, add the early stopping callback
if (using_early_stopping) {
callbacks <- .add_cb(
cb_list = callbacks
, cb = cb_early_stop(
stopping_rounds = early_stopping_rounds
, first_metric_only = isTRUE(params[["first_metric_only"]])
, verbose = params[["verbosity"]] > 0L
)
)
}
cb <- .categorize_callbacks(cb_list = callbacks)
# Construct booster for each fold. The data.table() code below is used to
# guarantee that indices are sorted while keeping init_score and weight together
# with the correct indices. Note that it takes advantage of the fact that
# someDT$some_column returns NULL is 'some_column' does not exist in the data.table
bst_folds <- lapply(
X = seq_along(folds)
, FUN = function(k) {
# For learning-to-rank, each fold is a named list with two elements:
# * `fold` = an integer vector of row indices
# * `group` = an integer vector describing which groups are in the fold
# For classification or regression tasks, it will just be an integer
# vector of row indices
folds_have_group <- "group" %in% names(folds[[k]])
if (folds_have_group) {
test_indices <- folds[[k]]$fold
test_group_indices <- folds[[k]]$group
test_groups <- get_field(dataset = data, field_name = "group")[test_group_indices]
train_groups <- get_field(dataset = data, field_name = "group")[-test_group_indices]
} else {
test_indices <- folds[[k]]
}
train_indices <- seq_len(nrow(data))[-test_indices]
# set up test set
indexDT <- data.table::data.table(
indices = test_indices
, weight = get_field(dataset = data, field_name = "weight")[test_indices]
, init_score = get_field(dataset = data, field_name = "init_score")[test_indices]
)
data.table::setorderv(x = indexDT, cols = "indices", order = 1L)
dtest <- lgb.slice.Dataset(data, indexDT$indices)
set_field(dataset = dtest, field_name = "weight", data = indexDT$weight)
set_field(dataset = dtest, field_name = "init_score", data = indexDT$init_score)
# set up training set
indexDT <- data.table::data.table(
indices = train_indices
, weight = get_field(dataset = data, field_name = "weight")[train_indices]
, init_score = get_field(dataset = data, field_name = "init_score")[train_indices]
)
data.table::setorderv(x = indexDT, cols = "indices", order = 1L)
dtrain <- lgb.slice.Dataset(data, indexDT$indices)
set_field(dataset = dtrain, field_name = "weight", data = indexDT$weight)
set_field(dataset = dtrain, field_name = "init_score", data = indexDT$init_score)
if (folds_have_group) {
set_field(dataset = dtest, field_name = "group", data = test_groups)
set_field(dataset = dtrain, field_name = "group", data = train_groups)
}
booster <- Booster$new(params = params, train_set = dtrain)
if (isTRUE(eval_train_metric)) {
booster$add_valid(data = dtrain, name = "train")
}
booster$add_valid(data = dtest, name = "valid")
return(
list(booster = booster)
)
}
)
# Create new booster
cv_booster <- CVBooster$new(x = bst_folds)
# Callback env
env <- CB_ENV$new()
env$model <- cv_booster
env$begin_iteration <- begin_iteration
env$end_iteration <- end_iteration
# Start training model using number of iterations to start and end with
for (i in seq.int(from = begin_iteration, to = end_iteration)) {
# Overwrite iteration in environment
env$iteration <- i
env$eval_list <- list()
for (f in cb$pre_iter) {
f(env)
}
# Update one boosting iteration
msg <- lapply(cv_booster$boosters, function(fd) {
fd$booster$update(fobj = fobj)
out <- list()
for (eval_function in eval_functions) {
out <- append(out, fd$booster$eval_valid(feval = eval_function))
}
return(out)
})
# Prepare collection of evaluation results
merged_msg <- .merge_cv_result(
msg = msg
, showsd = showsd
)
# Write evaluation result in environment
env$eval_list <- merged_msg$eval_list
# Check for standard deviation requirement
if (showsd) {
env$eval_err_list <- merged_msg$eval_err_list
}
# Loop through env
for (f in cb$post_iter) {
f(env)
}
# Check for early stopping and break if needed
if (env$met_early_stop) break
}
# When early stopping is not activated, we compute the best iteration / score ourselves
# based on the first first metric
if (record && is.na(env$best_score)) {
# when using a custom eval function, the metric name is returned from the
# function, so figure it out from record_evals
if (!is.null(eval_functions[1L])) {
first_metric <- names(cv_booster$record_evals[["valid"]])[1L]
} else {
first_metric <- cv_booster$.__enclos_env__$private$eval_names[1L]
}
.find_best <- which.min
if (isTRUE(env$eval_list[[1L]]$higher_better[1L])) {
.find_best <- which.max
}
cv_booster$best_iter <- unname(
.find_best(
unlist(
cv_booster$record_evals[["valid"]][[first_metric]][[.EVAL_KEY()]]
)
)
)
cv_booster$best_score <- cv_booster$record_evals[["valid"]][[first_metric]][[.EVAL_KEY()]][[cv_booster$best_iter]]
}
# Propagate the best_iter attribute from the cv_booster to the individual boosters
for (bst in cv_booster$boosters) {
bst$booster$best_iter <- cv_booster$best_iter
}
if (reset_data) {
lapply(cv_booster$boosters, function(fd) {
# Store temporarily model data elsewhere
booster_old <- list(
best_iter = fd$booster$best_iter
, best_score = fd$booster$best_score
, record_evals = fd$booster$record_evals
)
# Reload model
fd$booster <- lgb.load(model_str = fd$booster$save_model_to_string())
fd$booster$best_iter <- booster_old$best_iter
fd$booster$best_score <- booster_old$best_score
fd$booster$record_evals <- booster_old$record_evals
})
}
if (serializable) {
lapply(cv_booster$boosters, function(model) model$booster$save_raw())
}
return(cv_booster)
}
# Generates random (stratified if needed) CV folds
.generate_cv_folds <- function(nfold, nrows, stratified, label, group, params) {
# Check for group existence
if (is.null(group)) {
# Shuffle
rnd_idx <- sample.int(nrows)
# Request stratified folds
if (isTRUE(stratified) && params$objective %in% c("binary", "multiclass") && length(label) == length(rnd_idx)) {
y <- label[rnd_idx]
y <- as.factor(y)
folds <- .stratified_folds(y = y, k = nfold)
} else {
# Make simple non-stratified folds
folds <- list()
# Loop through each fold
for (i in seq_len(nfold)) {
kstep <- length(rnd_idx) %/% (nfold - i + 1L)
folds[[i]] <- rnd_idx[seq_len(kstep)]
rnd_idx <- rnd_idx[-seq_len(kstep)]
}
}
} else {
# When doing group, stratified is not possible (only random selection)
if (nfold > length(group)) {
stop("\nYou requested too many folds for the number of available groups.\n")
}
# Degroup the groups
ungrouped <- inverse.rle(list(lengths = group, values = seq_along(group)))
# Can't stratify, shuffle
rnd_idx <- sample.int(length(group))
# Make simple non-stratified folds
folds <- list()
# Loop through each fold
for (i in seq_len(nfold)) {
kstep <- length(rnd_idx) %/% (nfold - i + 1L)
folds[[i]] <- list(
fold = which(ungrouped %in% rnd_idx[seq_len(kstep)])
, group = rnd_idx[seq_len(kstep)]
)
rnd_idx <- rnd_idx[-seq_len(kstep)]
}
}
return(folds)
}
# Creates CV folds stratified by the values of y.
# It was borrowed from caret::createFolds and simplified
# by always returning an unnamed list of fold indices.
#' @importFrom stats quantile
.stratified_folds <- function(y, k) {
# Group the numeric data based on their magnitudes
# and sample within those groups.
# When the number of samples is low, we may have
# issues further slicing the numeric data into
# groups. The number of groups will depend on the
# ratio of the number of folds to the sample size.
# At most, we will use quantiles. If the sample
# is too small, we just do regular unstratified CV
if (is.numeric(y)) {
cuts <- length(y) %/% k
if (cuts < 2L) {
cuts <- 2L
}
if (cuts > 5L) {
cuts <- 5L
}
y <- cut(
y
, unique(stats::quantile(y, probs = seq.int(0.0, 1.0, length.out = cuts)))
, include.lowest = TRUE
)
}
if (k < length(y)) {
# Reset levels so that the possible levels and
# the levels in the vector are the same
y <- as.factor(as.character(y))
numInClass <- table(y)
foldVector <- vector(mode = "integer", length(y))
# For each class, balance the fold allocation as far
# as possible, then resample the remainder.
# The final assignment of folds is also randomized.
for (i in seq_along(numInClass)) {
# Create a vector of integers from 1:k as many times as possible without
# going over the number of samples in the class. Note that if the number
# of samples in a class is less than k, nothing is produced here.
seqVector <- rep(seq_len(k), numInClass[i] %/% k)
# Add enough random integers to get length(seqVector) == numInClass[i]
if (numInClass[i] %% k > 0L) {
seqVector <- c(seqVector, sample.int(k, numInClass[i] %% k))
}
# Shuffle the integers for fold assignment and assign to this classes's data
foldVector[y == dimnames(numInClass)$y[i]] <- sample(seqVector)
}
} else {
foldVector <- seq(along = y)
}
out <- split(seq(along = y), foldVector)
names(out) <- NULL
return(out)
}
.merge_cv_result <- function(msg, showsd) {
if (length(msg) == 0L) {
stop("lgb.cv: size of cv result error")
}
eval_len <- length(msg[[1L]])
if (eval_len == 0L) {
stop("lgb.cv: should provide at least one metric for CV")
}
# Get evaluation results using a list apply
eval_result <- lapply(seq_len(eval_len), function(j) {
as.numeric(lapply(seq_along(msg), function(i) {
msg[[i]][[j]]$value }))
})
# Get evaluation. Just taking the first element here to
# get structure (name, higher_better, data_name)
ret_eval <- msg[[1L]]
for (j in seq_len(eval_len)) {
ret_eval[[j]]$value <- mean(eval_result[[j]])
}
ret_eval_err <- NULL
# Check for standard deviation
if (showsd) {
# Parse standard deviation
for (j in seq_len(eval_len)) {
ret_eval_err <- c(
ret_eval_err
, sqrt(mean(eval_result[[j]] ^ 2L) - mean(eval_result[[j]]) ^ 2L)
)
}
ret_eval_err <- as.list(ret_eval_err)
}
return(
list(
eval_list = ret_eval
, eval_err_list = ret_eval_err
)
)
}
+21
View File
@@ -0,0 +1,21 @@
#' @name lgb.drop_serialized
#' @title Drop serialized raw bytes in a LightGBM model object
#' @description If a LightGBM model object was produced with argument `serializable=TRUE`, the R object will keep
#' a copy of the underlying C++ object as raw bytes, which can be used to reconstruct such object after getting
#' serialized and de-serialized, but at the cost of extra memory usage. If these raw bytes are not needed anymore,
#' they can be dropped through this function in order to save memory. Note that the object will be modified in-place.
#'
#' \emph{New in version 4.0.0}
#'
#' @param model \code{lgb.Booster} object which was produced with `serializable=TRUE`.
#'
#' @return \code{lgb.Booster} (the same `model` object that was passed as input, as invisible).
#' @seealso \link{lgb.restore_handle}, \link{lgb.make_serializable}.
#' @export
lgb.drop_serialized <- function(model) {
if (!.is_Booster(x = model)) {
stop("lgb.drop_serialized: model should be an ", sQuote("lgb.Booster", q = FALSE))
}
model$drop_raw()
return(invisible(model))
}
+83
View File
@@ -0,0 +1,83 @@
#' @name lgb.importance
#' @title Compute feature importance in a model
#' @description Creates a \code{data.table} of feature importances in a model.
#' @param model object of class \code{lgb.Booster}.
#' @param percentage whether to show importance in relative percentage.
#'
#' @return For a tree model, a \code{data.table} with the following columns:
#' \itemize{
#' \item{\code{Feature}: Feature names in the model.}
#' \item{\code{Gain}: The total gain of this feature's splits.}
#' \item{\code{Cover}: The number of observation related to this feature.}
#' \item{\code{Frequency}: The number of times a feature split in trees.}
#' }
#'
#' @examples
#' \donttest{
#' \dontshow{setLGBMthreads(2L)}
#' \dontshow{data.table::setDTthreads(1L)}
#' data(agaricus.train, package = "lightgbm")
#' train <- agaricus.train
#' dtrain <- lgb.Dataset(train$data, label = train$label)
#'
#' params <- list(
#' objective = "binary"
#' , learning_rate = 0.1
#' , max_depth = -1L
#' , min_data_in_leaf = 1L
#' , min_sum_hessian_in_leaf = 1.0
#' , num_threads = 2L
#' )
#' model <- lgb.train(
#' params = params
#' , data = dtrain
#' , nrounds = 5L
#' )
#'
#' tree_imp1 <- lgb.importance(model, percentage = TRUE)
#' tree_imp2 <- lgb.importance(model, percentage = FALSE)
#' }
#' @importFrom data.table := setnames setorderv
#' @export
lgb.importance <- function(model, percentage = TRUE) {
if (!.is_Booster(x = model)) {
stop("'model' has to be an object of class lgb.Booster")
}
# Setup importance
tree_dt <- lgb.model.dt.tree(model = model)
# Extract elements
tree_imp_dt <- tree_dt[
!is.na(split_index)
, .(Gain = sum(split_gain), Cover = sum(internal_count), Frequency = .N)
, by = "split_feature"
]
data.table::setnames(
x = tree_imp_dt
, old = "split_feature"
, new = "Feature"
)
# Sort features by Gain
data.table::setorderv(
x = tree_imp_dt
, cols = "Gain"
, order = -1L
)
# Check if relative values are requested
if (percentage) {
tree_imp_dt[, `:=`(
Gain = Gain / sum(Gain)
, Cover = Cover / sum(Cover)
, Frequency = Frequency / sum(Frequency)
)]
}
# adding an empty [] to ensure the table is printed the first time print.data.table() is called
return(tree_imp_dt[])
}
+256
View File
@@ -0,0 +1,256 @@
#' @name lgb.interpret
#' @title Compute feature contribution of prediction
#' @description Computes feature contribution components of rawscore prediction.
#' @param model object of class \code{lgb.Booster}.
#' @param data a matrix object or a dgCMatrix object.
#' @param idxset an integer vector of indices of rows needed.
#' @param num_iteration number of iteration want to predict with, NULL or <= 0 means use best iteration.
#'
#' @return For regression, binary classification and lambdarank model, a \code{list} of \code{data.table}
#' with the following columns:
#' \itemize{
#' \item{\code{Feature}: Feature names in the model.}
#' \item{\code{Contribution}: The total contribution of this feature's splits.}
#' }
#' For multiclass classification, a \code{list} of \code{data.table} with the Feature column and
#' Contribution columns to each class.
#'
#' @examples
#' \donttest{
#' \dontshow{setLGBMthreads(2L)}
#' \dontshow{data.table::setDTthreads(1L)}
#' Logit <- function(x) log(x / (1.0 - x))
#' data(agaricus.train, package = "lightgbm")
#' train <- agaricus.train
#' dtrain <- lgb.Dataset(train$data, label = train$label)
#' set_field(
#' dataset = dtrain
#' , field_name = "init_score"
#' , data = rep(Logit(mean(train$label)), length(train$label))
#' )
#' data(agaricus.test, package = "lightgbm")
#' test <- agaricus.test
#'
#' params <- list(
#' objective = "binary"
#' , learning_rate = 0.1
#' , max_depth = -1L
#' , min_data_in_leaf = 1L
#' , min_sum_hessian_in_leaf = 1.0
#' , num_threads = 2L
#' )
#' model <- lgb.train(
#' params = params
#' , data = dtrain
#' , nrounds = 3L
#' )
#'
#' tree_interpretation <- lgb.interpret(model, test$data, 1L:5L)
#' }
#' @importFrom data.table as.data.table
#' @export
lgb.interpret <- function(model,
data,
idxset,
num_iteration = NULL) {
# Get tree model
tree_dt <- lgb.model.dt.tree(model = model, num_iteration = num_iteration)
# Check number of classes
num_class <- model$.__enclos_env__$private$num_class
# Get vector list
tree_interpretation_dt_list <- vector(mode = "list", length = length(idxset))
# Get parsed predictions of data
pred_mat <- t(
model$predict(
data = data[idxset, , drop = FALSE]
, num_iteration = num_iteration
, predleaf = TRUE
)
)
leaf_index_dt <- data.table::as.data.table(x = pred_mat)
leaf_index_mat_list <- lapply(
X = leaf_index_dt
, FUN = matrix
, ncol = num_class
, byrow = TRUE
)
# Get list of trees
tree_index_mat_list <- lapply(
X = leaf_index_mat_list
, FUN = function(x) {
matrix(seq_along(x) - 1L, ncol = num_class, byrow = TRUE)
}
)
for (i in seq_along(idxset)) {
tree_interpretation_dt_list[[i]] <- .single_row_interpret(
tree_dt = tree_dt
, num_class = num_class
, tree_index_mat = tree_index_mat_list[[i]]
, leaf_index_mat = leaf_index_mat_list[[i]]
)
}
return(tree_interpretation_dt_list)
}
#' @name lgb.interprete
#' @title DEPRECATED - use lgb.interpret() instead
#' @description Alias for \code{lgb.interpret}.
#' @param ... Arguments passed through to \code{lgb.interpret}
#' @export
lgb.interprete <- function(...) {
warning("lgb.interprete() is deprecated and will be removed in a future release. Use lgb.interpret() instead.")
return(lgb.interpret(...))
}
#' @importFrom data.table data.table
single.tree.interpret <- function(tree_dt,
tree_id,
leaf_id) {
# Match tree id
single_tree_dt <- tree_dt[tree_index == tree_id, ]
# Get leaves
leaf_dt <- single_tree_dt[leaf_index == leaf_id, .(leaf_index, leaf_parent, leaf_value)]
# Get nodes
node_dt <- single_tree_dt[!is.na(split_index), .(split_index, split_feature, node_parent, internal_value)]
# Prepare sequences
feature_seq <- character(0L)
value_seq <- numeric(0L)
# Get to root from leaf
leaf_to_root <- function(parent_id, current_value) {
value_seq <<- c(current_value, value_seq)
if (!is.na(parent_id)) {
# Not null means existing node
this_node <- node_dt[split_index == parent_id, ]
feature_seq <<- c(this_node[["split_feature"]], feature_seq)
leaf_to_root(
parent_id = this_node[["node_parent"]]
, current_value = this_node[["internal_value"]]
)
}
}
# Perform leaf to root conversion
leaf_to_root(
parent_id = leaf_dt[["leaf_parent"]]
, current_value = leaf_dt[["leaf_value"]]
)
return(
data.table::data.table(
Feature = feature_seq
, Contribution = diff.default(value_seq)
)
)
}
#' @importFrom data.table := rbindlist setorder
.multiple_tree_interpret <- function(tree_dt,
tree_index,
leaf_index) {
interp_dt <- data.table::rbindlist(
l = mapply(
FUN = single.tree.interpret
, tree_id = tree_index
, leaf_id = leaf_index
, MoreArgs = list(
tree_dt = tree_dt
)
, SIMPLIFY = FALSE
, USE.NAMES = TRUE
)
, use.names = TRUE
)
interp_dt <- interp_dt[, .(Contribution = sum(Contribution)), by = "Feature"]
# Sort features in descending order by contribution
interp_dt[, abs_contribution := abs(Contribution)]
data.table::setorder(
x = interp_dt
, -abs_contribution
)
# Drop absolute value of contribution (only needed for sorting)
interp_dt[, abs_contribution := NULL]
return(interp_dt)
}
#' @importFrom data.table set setnames
.single_row_interpret <- function(tree_dt, num_class, tree_index_mat, leaf_index_mat) {
# Prepare vector list
tree_interpretation <- vector(mode = "list", length = num_class)
# Loop throughout each class
for (i in seq_len(num_class)) {
next_interp_dt <- .multiple_tree_interpret(
tree_dt = tree_dt
, tree_index = tree_index_mat[, i]
, leaf_index = leaf_index_mat[, i]
)
if (num_class > 1L) {
data.table::setnames(
x = next_interp_dt
, old = "Contribution"
, new = paste("Class", i - 1L)
)
}
tree_interpretation[[i]] <- next_interp_dt
}
if (num_class == 1L) {
tree_interpretation_dt <- tree_interpretation[[1L]]
} else {
# Full interpretation elements
tree_interpretation_dt <- Reduce(
f = function(x, y) {
merge(x, y, by = "Feature", all = TRUE)
}
, x = tree_interpretation
)
# Loop throughout each tree
for (j in 2L:ncol(tree_interpretation_dt)) {
data.table::set(
x = tree_interpretation_dt
, i = which(is.na(tree_interpretation_dt[[j]]))
, j = j
, value = 0.0
)
}
}
return(tree_interpretation_dt)
}
+21
View File
@@ -0,0 +1,21 @@
#' @name lgb.make_serializable
#' @title Make a LightGBM object serializable by keeping raw bytes
#' @description If a LightGBM model object was produced with argument `serializable=FALSE`, the R object will not
#' be serializable (e.g. cannot save and load with \code{saveRDS} and \code{readRDS}) as it will lack the raw bytes
#' needed to reconstruct its underlying C++ object. This function can be used to forcibly produce those serialized
#' raw bytes and make the object serializable. Note that the object will be modified in-place.
#'
#' \emph{New in version 4.0.0}
#'
#' @param model \code{lgb.Booster} object which was produced with `serializable=FALSE`.
#'
#' @return \code{lgb.Booster} (the same `model` object that was passed as input, as invisible).
#' @seealso \link{lgb.restore_handle}, \link{lgb.drop_serialized}.
#' @export
lgb.make_serializable <- function(model) {
if (!.is_Booster(x = model)) {
stop("lgb.make_serializable: model should be an ", sQuote("lgb.Booster", q = FALSE))
}
model$save_raw()
return(invisible(model))
}
+192
View File
@@ -0,0 +1,192 @@
#' @name lgb.model.dt.tree
#' @title Parse a LightGBM model json dump
#' @description Parse a LightGBM model json dump into a \code{data.table} structure.
#' @param model object of class \code{lgb.Booster}.
#' @param num_iteration Number of iterations to include. NULL or <= 0 means use best iteration.
#' @param start_iteration Index (1-based) of the first boosting round to include in the output.
#' For example, passing \code{start_iteration=5, num_iteration=3} for a regression model
#' means "return information about the fifth, sixth, and seventh trees".
#'
#' \emph{New in version 4.4.0}
#'
#' @return
#' A \code{data.table} with detailed information about model trees' nodes and leaves.
#'
#' The columns of the \code{data.table} are:
#'
#' \itemize{
#' \item{\code{tree_index}: ID of a tree in a model (integer)}
#' \item{\code{split_index}: ID of a node in a tree (integer)}
#' \item{\code{split_feature}: for a node, it's a feature name (character);
#' for a leaf, it simply labels it as \code{"NA"}}
#' \item{\code{node_parent}: ID of the parent node for current node (integer)}
#' \item{\code{leaf_index}: ID of a leaf in a tree (integer)}
#' \item{\code{leaf_parent}: ID of the parent node for current leaf (integer)}
#' \item{\code{split_gain}: Split gain of a node}
#' \item{\code{threshold}: Splitting threshold value of a node}
#' \item{\code{decision_type}: Decision type of a node}
#' \item{\code{default_left}: Determine how to handle NA value, TRUE -> Left, FALSE -> Right}
#' \item{\code{internal_value}: Node value}
#' \item{\code{internal_count}: The number of observation collected by a node}
#' \item{\code{leaf_value}: Leaf value}
#' \item{\code{leaf_count}: The number of observation collected by a leaf}
#' }
#'
#' @examples
#' \donttest{
#' \dontshow{setLGBMthreads(2L)}
#' \dontshow{data.table::setDTthreads(1L)}
#' data(agaricus.train, package = "lightgbm")
#' train <- agaricus.train
#' dtrain <- lgb.Dataset(train$data, label = train$label)
#'
#' params <- list(
#' objective = "binary"
#' , learning_rate = 0.01
#' , num_leaves = 63L
#' , max_depth = -1L
#' , min_data_in_leaf = 1L
#' , min_sum_hessian_in_leaf = 1.0
#' , num_threads = 2L
#' )
#' model <- lgb.train(params, dtrain, 10L)
#'
#' tree_dt <- lgb.model.dt.tree(model)
#' }
#' @importFrom data.table := rbindlist
#' @importFrom jsonlite fromJSON
#' @export
lgb.model.dt.tree <- function(
model, num_iteration = NULL, start_iteration = 1L
) {
json_model <- lgb.dump(
booster = model
, num_iteration = num_iteration
, start_iteration = start_iteration
)
parsed_json_model <- jsonlite::fromJSON(
txt = json_model
, simplifyVector = TRUE
, simplifyDataFrame = FALSE
, simplifyMatrix = FALSE
, flatten = FALSE
)
# Parse tree model
tree_list <- lapply(
X = parsed_json_model$tree_info
, FUN = .single_tree_parse
)
# Combine into single data.table
tree_dt <- data.table::rbindlist(l = tree_list, use.names = TRUE)
# Substitute feature index with the actual feature name
# Since the index comes from C++ (which is 0-indexed), be sure
# to add 1 (e.g. index 28 means the 29th feature in feature_names)
split_feature_indx <- tree_dt[, split_feature] + 1L
# Get corresponding feature names. Positions in split_feature_indx
# which are NA will result in an NA feature name
feature_names <- parsed_json_model$feature_names[split_feature_indx]
tree_dt[, split_feature := feature_names]
return(tree_dt)
}
#' @importFrom data.table := data.table rbindlist
.single_tree_parse <- function(lgb_tree) {
tree_info_cols <- c(
"split_index"
, "split_feature"
, "split_gain"
, "threshold"
, "decision_type"
, "default_left"
, "internal_value"
, "internal_count"
)
# Traverse tree function
pre_order_traversal <- function(env = NULL, tree_node_leaf, current_depth = 0L, parent_index = NA_integer_) {
if (is.null(env)) {
# Setup initial default data.table with default types
env <- new.env(parent = emptyenv())
env$single_tree_dt <- list()
env$single_tree_dt[[1L]] <- data.table::data.table(
tree_index = integer(0L)
, depth = integer(0L)
, split_index = integer(0L)
, split_feature = integer(0L)
, node_parent = integer(0L)
, leaf_index = integer(0L)
, leaf_parent = integer(0L)
, split_gain = numeric(0L)
, threshold = numeric(0L)
, decision_type = character(0L)
, default_left = character(0L)
, internal_value = integer(0L)
, internal_count = integer(0L)
, leaf_value = integer(0L)
, leaf_count = integer(0L)
)
# start tree traversal
pre_order_traversal(
env = env
, tree_node_leaf = tree_node_leaf
, current_depth = current_depth
, parent_index = parent_index
)
} else {
# Check if split index is not null in leaf
if (!is.null(tree_node_leaf$split_index)) {
# update data.table
env$single_tree_dt[[length(env$single_tree_dt) + 1L]] <- c(
tree_node_leaf[tree_info_cols]
, list("depth" = current_depth, "node_parent" = parent_index)
)
# Traverse tree again both left and right
pre_order_traversal(
env = env
, tree_node_leaf = tree_node_leaf$left_child
, current_depth = current_depth + 1L
, parent_index = tree_node_leaf$split_index
)
pre_order_traversal(
env = env
, tree_node_leaf = tree_node_leaf$right_child
, current_depth = current_depth + 1L
, parent_index = tree_node_leaf$split_index
)
} else if (!is.null(tree_node_leaf$leaf_index)) {
# update list
env$single_tree_dt[[length(env$single_tree_dt) + 1L]] <- c(
tree_node_leaf[c("leaf_index", "leaf_value", "leaf_count")]
, list("depth" = current_depth, "leaf_parent" = parent_index)
)
}
}
return(env$single_tree_dt)
}
# Traverse structure and rowbind everything
single_tree_dt <- data.table::rbindlist(
pre_order_traversal(tree_node_leaf = lgb_tree$tree_structure)
, use.names = TRUE
, fill = TRUE
)
# Store index
single_tree_dt[, tree_index := lgb_tree$tree_index]
return(single_tree_dt)
}
+99
View File
@@ -0,0 +1,99 @@
#' @name lgb.plot.importance
#' @title Plot feature importance as a bar graph
#' @description Plot previously calculated feature importance: Gain, Cover and Frequency, as a bar graph.
#' @param tree_imp a \code{data.table} returned by \code{\link{lgb.importance}}.
#' @param top_n maximal number of top features to include into the plot.
#' @param measure the name of importance measure to plot, can be "Gain", "Cover" or "Frequency".
#' @param left_margin (base R barplot) allows to adjust the left margin size to fit feature names.
#' @param cex (base R barplot) passed as \code{cex.names} parameter to \code{\link[graphics]{barplot}}.
#' Set a number smaller than 1.0 to make the bar labels smaller than R's default and values
#' greater than 1.0 to make them larger.
#'
#' @details
#' The graph represents each feature as a horizontal bar of length proportional to the defined importance of a feature.
#' Features are shown ranked in a decreasing importance order.
#'
#' @return
#' The \code{lgb.plot.importance} function creates a \code{barplot}
#' and silently returns a processed data.table with \code{top_n} features sorted by defined importance.
#'
#' @examples
#' \donttest{
#' \dontshow{setLGBMthreads(2L)}
#' \dontshow{data.table::setDTthreads(1L)}
#' data(agaricus.train, package = "lightgbm")
#' train <- agaricus.train
#' dtrain <- lgb.Dataset(train$data, label = train$label)
#'
#' params <- list(
#' objective = "binary"
#' , learning_rate = 0.1
#' , min_data_in_leaf = 1L
#' , min_sum_hessian_in_leaf = 1.0
#' , num_threads = 2L
#' )
#'
#' model <- lgb.train(
#' params = params
#' , data = dtrain
#' , nrounds = 5L
#' )
#'
#' tree_imp <- lgb.importance(model, percentage = TRUE)
#' lgb.plot.importance(tree_imp, top_n = 5L, measure = "Gain")
#' }
#' @importFrom graphics barplot par
#' @export
lgb.plot.importance <- function(tree_imp,
top_n = 10L,
measure = "Gain",
left_margin = 10L,
cex = NULL
) {
# Check for measurement (column names) correctness
measure <- match.arg(
measure
, choices = c("Gain", "Cover", "Frequency")
, several.ok = FALSE
)
# Get top N importance (defaults to 10)
top_n <- min(top_n, nrow(tree_imp))
# Parse importance
tree_imp <- tree_imp[order(abs(get(measure)), decreasing = TRUE), ][seq_len(top_n), ]
# Attempt to setup a correct cex
if (is.null(cex)) {
cex <- 2.5 / log2(1.0 + top_n)
}
# Refresh plot
op <- graphics::par(no.readonly = TRUE)
on.exit(graphics::par(op))
graphics::par(
mar = c(
op$mar[1L]
, left_margin
, op$mar[3L]
, op$mar[4L]
)
)
tree_imp[rev(seq_len(.N)),
graphics::barplot(
height = get(measure)
, names.arg = Feature
, horiz = TRUE
, border = NA
, main = "Feature Importance"
, xlab = measure
, cex.names = cex
, las = 1L
)]
return(invisible(tree_imp))
}
+166
View File
@@ -0,0 +1,166 @@
#' @name lgb.plot.interpretation
#' @title Plot feature contribution as a bar graph
#' @description Plot previously calculated feature contribution as a bar graph.
#' @param tree_interpretation_dt a \code{data.table} returned by \code{\link{lgb.interpret}}.
#' @param top_n maximal number of top features to include into the plot.
#' @param cols the column numbers of layout, will be used only for multiclass classification feature contribution.
#' @param left_margin (base R barplot) allows to adjust the left margin size to fit feature names.
#' @param cex (base R barplot) passed as \code{cex.names} parameter to \code{barplot}.
#'
#' @details
#' The graph represents each feature as a horizontal bar of length proportional to the defined
#' contribution of a feature. Features are shown ranked in a decreasing contribution order.
#'
#' @return
#' The \code{lgb.plot.interpretation} function creates a \code{barplot}.
#'
#' @examples
#' \donttest{
#' \dontshow{setLGBMthreads(2L)}
#' \dontshow{data.table::setDTthreads(1L)}
#' Logit <- function(x) {
#' log(x / (1.0 - x))
#' }
#' data(agaricus.train, package = "lightgbm")
#' labels <- agaricus.train$label
#' dtrain <- lgb.Dataset(
#' agaricus.train$data
#' , label = labels
#' )
#' set_field(
#' dataset = dtrain
#' , field_name = "init_score"
#' , data = rep(Logit(mean(labels)), length(labels))
#' )
#'
#' data(agaricus.test, package = "lightgbm")
#'
#' params <- list(
#' objective = "binary"
#' , learning_rate = 0.1
#' , max_depth = -1L
#' , min_data_in_leaf = 1L
#' , min_sum_hessian_in_leaf = 1.0
#' , num_threads = 2L
#' )
#' model <- lgb.train(
#' params = params
#' , data = dtrain
#' , nrounds = 5L
#' )
#'
#' tree_interpretation <- lgb.interpret(
#' model = model
#' , data = agaricus.test$data
#' , idxset = 1L:5L
#' )
#' lgb.plot.interpretation(
#' tree_interpretation_dt = tree_interpretation[[1L]]
#' , top_n = 3L
#' )
#' }
#' @importFrom data.table setnames
#' @importFrom graphics barplot par
#' @export
lgb.plot.interpretation <- function(tree_interpretation_dt,
top_n = 10L,
cols = 1L,
left_margin = 10L,
cex = NULL) {
num_class <- ncol(tree_interpretation_dt) - 1L
# Refresh plot
op <- graphics::par(no.readonly = TRUE)
on.exit(graphics::par(op))
# Do some magic plotting
bottom_margin <- 3.0
top_margin <- 2.0
right_margin <- op$mar[4L]
graphics::par(
mar = c(
bottom_margin
, left_margin
, top_margin
, right_margin
)
)
if (num_class == 1L) {
# Only one class, plot straight away
.multiple_tree_plot_interpretation(
tree_interpretation = tree_interpretation_dt
, top_n = top_n
, title = NULL
, cex = cex
)
} else {
# More than one class, shape data first
layout_mat <- matrix(
seq.int(to = cols * ceiling(num_class / cols))
, ncol = cols
, nrow = ceiling(num_class / cols)
)
# Shape output
graphics::par(mfcol = c(nrow(layout_mat), ncol(layout_mat)))
# Loop throughout all classes
for (i in seq_len(num_class)) {
# Prepare interpretation, perform T, get the names, and plot straight away
plot_dt <- tree_interpretation_dt[, c(1L, i + 1L), with = FALSE]
data.table::setnames(
x = plot_dt
, old = names(plot_dt)
, new = c("Feature", "Contribution")
)
.multiple_tree_plot_interpretation(
tree_interpretation = plot_dt
, top_n = top_n
, title = paste("Class", i - 1L)
, cex = cex
)
}
}
return(invisible(NULL))
}
#' @importFrom graphics barplot
.multiple_tree_plot_interpretation <- function(tree_interpretation,
top_n,
title,
cex) {
# Parse tree
tree_interpretation <- tree_interpretation[order(abs(Contribution), decreasing = TRUE), ][seq_len(min(top_n, .N)), ]
# Attempt to setup a correct cex
if (is.null(cex)) {
cex <- 2.5 / log2(1.0 + top_n)
}
# create plot
tree_interpretation[abs(Contribution) > 0.0, bar_color := "firebrick"]
tree_interpretation[Contribution == 0.0, bar_color := "steelblue"]
tree_interpretation[rev(seq_len(.N)),
graphics::barplot(
height = Contribution
, names.arg = Feature
, horiz = TRUE
, col = bar_color
, border = NA
, main = title
, cex.names = cex
, las = 1L
)]
return(invisible(NULL))
}
+47
View File
@@ -0,0 +1,47 @@
#' @name lgb.restore_handle
#' @title Restore the C++ component of a de-serialized LightGBM model
#' @description After a LightGBM model object is de-serialized through functions such as \code{save} or
#' \code{saveRDS}, its underlying C++ object will be blank and needs to be restored to able to use it. Such
#' object is restored automatically when calling functions such as \code{predict}, but this function can be
#' used to forcibly restore it beforehand. Note that the object will be modified in-place.
#'
#' \emph{New in version 4.0.0}
#'
#' @details Be aware that fast single-row prediction configurations are not restored through this
#' function. If you wish to make fast single-row predictions using a \code{lgb.Booster} loaded this way,
#' call \link{lgb.configure_fast_predict} on the loaded \code{lgb.Booster} object.
#' @param model \code{lgb.Booster} object which was de-serialized and whose underlying C++ object and R handle
#' need to be restored.
#'
#' @return \code{lgb.Booster} (the same `model` object that was passed as input, invisibly).
#' @seealso \link{lgb.make_serializable}, \link{lgb.drop_serialized}.
#' @examples
#' \donttest{
#' library(lightgbm)
#' \dontshow{setLGBMthreads(2L)}
#' \dontshow{data.table::setDTthreads(1L)}
#' data("agaricus.train")
#' model <- lightgbm(
#' agaricus.train$data
#' , agaricus.train$label
#' , params = list(objective = "binary")
#' , nrounds = 5L
#' , verbose = 0
#' , num_threads = 2L
#' )
#' fname <- tempfile(fileext="rds")
#' saveRDS(model, fname)
#'
#' model_new <- readRDS(fname)
#' model_new$check_null_handle()
#' lgb.restore_handle(model_new)
#' model_new$check_null_handle()
#' }
#' @export
lgb.restore_handle <- function(model) {
if (!.is_Booster(x = model)) {
stop("lgb.restore_handle: model should be an ", sQuote("lgb.Booster", q = FALSE))
}
model$restore_handle()
return(invisible(model))
}
+383
View File
@@ -0,0 +1,383 @@
#' @name lgb.train
#' @title Main training logic for LightGBM
#' @description Low-level R interface to train a LightGBM model. Unlike \code{\link{lightgbm}},
#' this function is focused on performance (e.g. speed, memory efficiency). It is also
#' less likely to have breaking API changes in new releases than \code{\link{lightgbm}}.
#' @inheritParams lgb_shared_params
#' @param valids a list of \code{lgb.Dataset} objects, used for validation
#' @param record Boolean, TRUE will record iteration message to \code{booster$record_evals}
#' @param callbacks List of callback functions that are applied at each iteration.
#' @param reset_data Boolean, setting it to TRUE (not the default value) will transform the
#' booster model into a predictor model which frees up memory and the
#' original datasets
#' @inheritSection lgb_shared_params Early Stopping
#' @return a trained booster model \code{lgb.Booster}.
#'
#' @examples
#' \donttest{
#' \dontshow{setLGBMthreads(2L)}
#' \dontshow{data.table::setDTthreads(1L)}
#' data(agaricus.train, package = "lightgbm")
#' train <- agaricus.train
#' dtrain <- lgb.Dataset(train$data, label = train$label)
#' data(agaricus.test, package = "lightgbm")
#' test <- agaricus.test
#' dtest <- lgb.Dataset.create.valid(dtrain, test$data, label = test$label)
#' params <- list(
#' objective = "regression"
#' , metric = "l2"
#' , min_data = 1L
#' , learning_rate = 1.0
#' , num_threads = 2L
#' )
#' valids <- list(test = dtest)
#' model <- lgb.train(
#' params = params
#' , data = dtrain
#' , nrounds = 5L
#' , valids = valids
#' , early_stopping_rounds = 3L
#' )
#' }
#'
#' @export
lgb.train <- function(params = list(),
data,
nrounds = 100L,
valids = list(),
obj = NULL,
eval = NULL,
verbose = 1L,
record = TRUE,
eval_freq = 1L,
init_model = NULL,
early_stopping_rounds = NULL,
callbacks = list(),
reset_data = FALSE,
serializable = TRUE) {
# validate inputs early to avoid unnecessary computation
if (nrounds <= 0L) {
stop("nrounds should be greater than zero")
}
if (!.is_Dataset(x = data)) {
stop("lgb.train: data must be an lgb.Dataset instance")
}
if (length(valids) > 0L) {
if (!identical(class(valids), "list") || !all(vapply(valids, .is_Dataset, logical(1L)))) {
stop("lgb.train: valids must be a list of lgb.Dataset elements")
}
evnames <- names(valids)
if (is.null(evnames) || !all(nzchar(evnames))) {
stop("lgb.train: each element of valids must have a name")
}
}
# set some parameters, resolving the way they were passed in with other parameters
# in `params`.
# this ensures that the model stored with Booster$save() correctly represents
# what was passed in
params <- .check_wrapper_param(
main_param_name = "verbosity"
, params = params
, alternative_kwarg_value = verbose
)
params <- .check_wrapper_param(
main_param_name = "num_iterations"
, params = params
, alternative_kwarg_value = nrounds
)
params <- .check_wrapper_param(
main_param_name = "metric"
, params = params
, alternative_kwarg_value = NULL
)
params <- .check_wrapper_param(
main_param_name = "objective"
, params = params
, alternative_kwarg_value = obj
)
params <- .check_wrapper_param(
main_param_name = "early_stopping_round"
, params = params
, alternative_kwarg_value = early_stopping_rounds
)
early_stopping_rounds <- params[["early_stopping_round"]]
# extract any function objects passed for objective or metric
fobj <- NULL
if (is.function(params$objective)) {
fobj <- params$objective
params$objective <- "none"
}
# If eval is a single function, store it as a 1-element list
# (for backwards compatibility). If it is a list of functions, store
# all of them. This makes it possible to pass any mix of strings like "auc"
# and custom functions to eval
params <- .check_eval(params = params, eval = eval)
eval_functions <- list(NULL)
if (is.function(eval)) {
eval_functions <- list(eval)
}
if (methods::is(eval, "list")) {
eval_functions <- Filter(
f = is.function
, x = eval
)
}
# Init predictor to empty
predictor <- NULL
# Check for boosting from a trained model
if (is.character(init_model)) {
predictor <- Predictor$new(modelfile = init_model)
} else if (.is_Booster(x = init_model)) {
predictor <- init_model$to_predictor()
}
# Set the iteration to start from / end to (and check for boosting from a trained model, again)
begin_iteration <- 1L
if (!is.null(predictor)) {
begin_iteration <- predictor$current_iter() + 1L
}
end_iteration <- begin_iteration + params[["num_iterations"]] - 1L
# pop interaction_constraints off of params. It needs some preprocessing on the
# R side before being passed into the Dataset object
interaction_constraints <- params[["interaction_constraints"]]
params["interaction_constraints"] <- NULL
# Construct datasets, if needed
data$update_params(params = params)
data$construct()
# Check interaction constraints
params[["interaction_constraints"]] <- .check_interaction_constraints(
interaction_constraints = interaction_constraints
, column_names = data$get_colnames()
)
# Update parameters with parsed parameters
data$update_params(params)
# Create the predictor set
data$.__enclos_env__$private$set_predictor(predictor)
valid_contain_train <- FALSE
train_data_name <- "train"
reduced_valid_sets <- list()
# Parse validation datasets
if (length(valids) > 0L) {
for (key in names(valids)) {
# Use names to get validation datasets
valid_data <- valids[[key]]
# Check for duplicate train/validation dataset
if (identical(data, valid_data)) {
valid_contain_train <- TRUE
train_data_name <- key
next
}
# Update parameters, data
valid_data$update_params(params)
valid_data$set_reference(data)
reduced_valid_sets[[key]] <- valid_data
}
}
# Add printing log callback
if (params[["verbosity"]] > 0L && eval_freq > 0L) {
callbacks <- .add_cb(
cb_list = callbacks
, cb = cb_print_evaluation(period = eval_freq)
)
}
# Add evaluation log callback
if (record && length(valids) > 0L) {
callbacks <- .add_cb(
cb_list = callbacks
, cb = cb_record_evaluation()
)
}
# Did user pass parameters that indicate they want to use early stopping?
using_early_stopping <- !is.null(early_stopping_rounds) && early_stopping_rounds > 0L
boosting_param_names <- .PARAMETER_ALIASES()[["boosting"]]
using_dart <- any(
sapply(
X = boosting_param_names
, FUN = function(param) {
identical(params[[param]], "dart")
}
)
)
# Cannot use early stopping with 'dart' boosting
if (using_dart) {
if (using_early_stopping) {
warning("Early stopping is not available in 'dart' mode.")
}
using_early_stopping <- FALSE
# Remove the cb_early_stop() function if it was passed in to callbacks
callbacks <- Filter(
f = function(cb_func) {
!identical(attr(cb_func, "name"), "cb_early_stop")
}
, x = callbacks
)
}
# If user supplied early_stopping_rounds, add the early stopping callback
if (using_early_stopping) {
callbacks <- .add_cb(
cb_list = callbacks
, cb = cb_early_stop(
stopping_rounds = early_stopping_rounds
, first_metric_only = isTRUE(params[["first_metric_only"]])
, verbose = params[["verbosity"]] > 0L
)
)
}
cb <- .categorize_callbacks(cb_list = callbacks)
# Construct booster with datasets
booster <- Booster$new(params = params, train_set = data)
if (valid_contain_train) {
booster$set_train_data_name(name = train_data_name)
}
for (key in names(reduced_valid_sets)) {
booster$add_valid(data = reduced_valid_sets[[key]], name = key)
}
# Callback env
env <- CB_ENV$new()
env$model <- booster
env$begin_iteration <- begin_iteration
env$end_iteration <- end_iteration
# Start training model using number of iterations to start and end with
for (i in seq.int(from = begin_iteration, to = end_iteration)) {
# Overwrite iteration in environment
env$iteration <- i
env$eval_list <- list()
# Loop through "pre_iter" element
for (f in cb$pre_iter) {
f(env)
}
# Update one boosting iteration
booster$update(fobj = fobj)
# Prepare collection of evaluation results
eval_list <- list()
# Collection: Has validation dataset?
if (length(valids) > 0L) {
# Get evaluation results with passed-in functions
for (eval_function in eval_functions) {
# Validation has training dataset?
if (valid_contain_train) {
eval_list <- append(eval_list, booster$eval_train(feval = eval_function))
}
eval_list <- append(eval_list, booster$eval_valid(feval = eval_function))
}
# Calling booster$eval_valid() will get
# evaluation results with the metrics in params$metric by calling LGBM_BoosterGetEval_R",
# so need to be sure that gets called, which it wouldn't be above if no functions
# were passed in
if (length(eval_functions) == 0L) {
if (valid_contain_train) {
eval_list <- append(eval_list, booster$eval_train(feval = eval_function))
}
eval_list <- append(eval_list, booster$eval_valid(feval = eval_function))
}
}
# Write evaluation result in environment
env$eval_list <- eval_list
# Loop through env
for (f in cb$post_iter) {
f(env)
}
# Check for early stopping and break if needed
if (env$met_early_stop) break
}
# check if any valids were given other than the training data
non_train_valid_names <- names(valids)[!(names(valids) == train_data_name)]
first_valid_name <- non_train_valid_names[1L]
# When early stopping is not activated, we compute the best iteration / score ourselves by
# selecting the first metric and the first dataset
if (record && length(non_train_valid_names) > 0L && is.na(env$best_score)) {
# when using a custom eval function, the metric name is returned from the
# function, so figure it out from record_evals
if (!is.null(eval_functions[1L])) {
first_metric <- names(booster$record_evals[[first_valid_name]])[1L]
} else {
first_metric <- booster$.__enclos_env__$private$eval_names[1L]
}
.find_best <- which.min
if (isTRUE(env$eval_list[[1L]]$higher_better[1L])) {
.find_best <- which.max
}
booster$best_iter <- unname(
.find_best(
unlist(
booster$record_evals[[first_valid_name]][[first_metric]][[.EVAL_KEY()]]
)
)
)
booster$best_score <- booster$record_evals[[first_valid_name]][[first_metric]][[.EVAL_KEY()]][[booster$best_iter]]
}
# Check for booster model conversion to predictor model
if (reset_data) {
# Store temporarily model data elsewhere
booster_old <- list(
best_iter = booster$best_iter
, best_score = booster$best_score
, record_evals = booster$record_evals
)
# Reload model
booster <- lgb.load(model_str = booster$save_model_to_string())
booster$best_iter <- booster_old$best_iter
booster$best_score <- booster_old$best_score
booster$record_evals <- booster_old$record_evals
}
if (serializable) {
booster$save_raw()
}
return(booster)
}
+368
View File
@@ -0,0 +1,368 @@
#' @name lgb_shared_params
#' @title Shared parameter docs
#' @description Parameter docs shared by \code{lgb.train}, \code{lgb.cv}, and \code{lightgbm}
#' @param callbacks List of callback functions that are applied at each iteration.
#' @param data a \code{lgb.Dataset} object, used for training. Some functions, such as \code{\link{lgb.cv}},
#' may allow you to pass other types of data like \code{matrix} and then separately supply
#' \code{label} as a keyword argument.
#' @param early_stopping_rounds int. Activates early stopping. When this parameter is non-null,
#' training will stop if the evaluation of any metric on any validation set
#' fails to improve for \code{early_stopping_rounds} consecutive boosting rounds.
#' If training stops early, the returned model will have attribute \code{best_iter}
#' set to the iteration number of the best iteration.
#' @param eval evaluation function(s). This can be a character vector, function, or list with a mixture of
#' strings and functions.
#'
#' \itemize{
#' \item{\bold{a. character vector}:
#' If you provide a character vector to this argument, it should contain strings with valid
#' evaluation metrics.
#' See \href{https://lightgbm.readthedocs.io/en/latest/Parameters.html#metric}{
#' The "metric" section of the documentation}
#' for a list of valid metrics.
#' }
#' \item{\bold{b. function}:
#' You can provide a custom evaluation function. This
#' should accept the keyword arguments \code{preds} and \code{dtrain} and should return a named
#' list with three elements:
#' \itemize{
#' \item{\code{name}: A string with the name of the metric, used for printing
#' and storing results.
#' }
#' \item{\code{value}: A single number indicating the value of the metric for the
#' given predictions and true values
#' }
#' \item{
#' \code{higher_better}: A boolean indicating whether higher values indicate a better fit.
#' For example, this would be \code{FALSE} for metrics like MAE or RMSE.
#' }
#' }
#' }
#' \item{\bold{c. list}:
#' If a list is given, it should only contain character vectors and functions.
#' These should follow the requirements from the descriptions above.
#' }
#' }
#' @param eval_freq evaluation output frequency, only effective when verbose > 0 and \code{valids} has been provided
#' @param init_model path of model file or \code{lgb.Booster} object, will continue training from this model
#' @param nrounds number of training rounds
#' @param obj objective function, can be character or custom objective function. Examples include
#' \code{regression}, \code{regression_l1}, \code{huber},
#' \code{binary}, \code{lambdarank}, \code{multiclass}, \code{multiclass}
#' @param params a list of parameters. See \href{https://lightgbm.readthedocs.io/en/latest/Parameters.html}{
#' the "Parameters" section of the documentation} for a list of parameters and valid values.
#' @param verbose verbosity for output, if <= 0 and \code{valids} has been provided, also will disable the
#' printing of evaluation during training
#' @param serializable whether to make the resulting objects serializable through functions such as
#' \code{save} or \code{saveRDS} (see section "Model serialization").
#' @section Early Stopping:
#'
#' "early stopping" refers to stopping the training process if the model's performance on a given
#' validation set does not improve for several consecutive iterations.
#'
#' If multiple arguments are given to \code{eval}, their order will be preserved. If you enable
#' early stopping by setting \code{early_stopping_rounds} in \code{params}, by default all
#' metrics will be considered for early stopping.
#'
#' If you want to only consider the first metric for early stopping, pass
#' \code{first_metric_only = TRUE} in \code{params}. Note that if you also specify \code{metric}
#' in \code{params}, that metric will be considered the "first" one. If you omit \code{metric},
#' a default metric will be used based on your choice for the parameter \code{obj} (keyword argument)
#' or \code{objective} (passed into \code{params}).
#'
#' \bold{NOTE:} if using \code{boosting_type="dart"}, any early stopping configuration will be ignored
#' and early stopping will not be performed.
#' @section Model serialization:
#'
#' LightGBM model objects can be serialized and de-serialized through functions such as \code{save}
#' or \code{saveRDS}, but similarly to libraries such as 'xgboost', serialization works a bit differently
#' from typical R objects. In order to make models serializable in R, a copy of the underlying C++ object
#' as serialized raw bytes is produced and stored in the R model object, and when this R object is
#' de-serialized, the underlying C++ model object gets reconstructed from these raw bytes, but will only
#' do so once some function that uses it is called, such as \code{predict}. In order to forcibly
#' reconstruct the C++ object after deserialization (e.g. after calling \code{readRDS} or similar), one
#' can use the function \link{lgb.restore_handle} (for example, if one makes predictions in parallel or in
#' forked processes, it will be faster to restore the handle beforehand).
#'
#' Producing and keeping these raw bytes however uses extra memory, and if they are not required,
#' it is possible to avoid producing them by passing `serializable=FALSE`. In such cases, these raw
#' bytes can be added to the model on demand through function \link{lgb.make_serializable}.
#'
#' \emph{New in version 4.0.0}
#'
#' @keywords internal
NULL
#' @name lightgbm
#' @title Train a LightGBM model
#' @description High-level R interface to train a LightGBM model. Unlike \code{\link{lgb.train}}, this function
#' is focused on compatibility with other statistics and machine learning interfaces in R.
#' This focus on compatibility means that this interface may experience more frequent breaking API changes
#' than \code{\link{lgb.train}}.
#' For efficiency-sensitive applications, or for applications where breaking API changes across releases
#' is very expensive, use \code{\link{lgb.train}}.
#' @inheritParams lgb_shared_params
#' @param label Vector of labels, used if \code{data} is not an \code{\link{lgb.Dataset}}
#' @param weights Sample / observation weights for rows in the input data. If \code{NULL}, will assume that all
#' observations / rows have the same importance / weight.
#'
#' \emph{Changed from 'weight', in version 4.0.0}
#'
#' @param objective Optimization objective (e.g. `"regression"`, `"binary"`, etc.).
#' For a list of accepted objectives, see
#' \href{https://lightgbm.readthedocs.io/en/latest/Parameters.html#objective}{
#' the "objective" item of the "Parameters" section of the documentation}.
#'
#' If passing \code{"auto"} and \code{data} is not of type \code{lgb.Dataset}, the objective will
#' be determined according to what is passed for \code{label}:\itemize{
#' \item If passing a factor with two variables, will use objective \code{"binary"}.
#' \item If passing a factor with more than two variables, will use objective \code{"multiclass"}
#' (note that parameter \code{num_class} in this case will also be determined automatically from
#' \code{label}).
#' \item Otherwise (or if passing \code{lgb.Dataset} as input), will use objective \code{"regression"}.
#' }
#'
#' \emph{New in version 4.0.0}
#'
#' @param init_score initial score is the base prediction lightgbm will boost from
#'
#' \emph{New in version 4.0.0}
#'
#' @param num_threads Number of parallel threads to use. For best speed, this should be set to the number of
#' physical cores in the CPU - in a typical x86-64 machine, this corresponds to half the
#' number of maximum threads.
#'
#' Be aware that using too many threads can result in speed degradation in smaller datasets
#' (see the parameters documentation for more details).
#'
#' If passing zero, will use the default number of threads configured for OpenMP
#' (typically controlled through an environment variable \code{OMP_NUM_THREADS}).
#'
#' If passing \code{NULL} (the default), will try to use the number of physical cores in the
#' system, but be aware that getting the number of cores detected correctly requires package
#' \code{RhpcBLASctl} to be installed.
#'
#' This parameter gets overridden by \code{num_threads} and its aliases under \code{params}
#' if passed there.
#'
#' \emph{New in version 4.0.0}
#'
#' @param colnames Character vector of features. Only used if \code{data} is not an \code{\link{lgb.Dataset}}.
#' @param categorical_feature categorical features. This can either be a character vector of feature
#' names or an integer vector with the indices of the features (e.g.
#' \code{c(1L, 10L)} to say "the first and tenth columns").
#' Only used if \code{data} is not an \code{\link{lgb.Dataset}}.
#'
#' @param ... Additional arguments passed to \code{\link{lgb.train}}. For example
#' \itemize{
#' \item{\code{valids}: a list of \code{lgb.Dataset} objects, used for validation}
#' \item{\code{obj}: objective function, can be character or custom objective function. Examples include
#' \code{regression}, \code{regression_l1}, \code{huber},
#' \code{binary}, \code{lambdarank}, \code{multiclass}, \code{multiclass}}
#' \item{\code{eval}: evaluation function, can be (a list of) character or custom eval function}
#' \item{\code{record}: Boolean, TRUE will record iteration message to \code{booster$record_evals}}
#' \item{\code{reset_data}: Boolean, setting it to TRUE (not the default value) will transform the booster model
#' into a predictor model which frees up memory and the original datasets}
#' }
#' @inheritSection lgb_shared_params Early Stopping
#' @return a trained \code{lgb.Booster}
#' @export
lightgbm <- function(data,
label = NULL,
weights = NULL,
params = list(),
nrounds = 100L,
verbose = 1L,
eval_freq = 1L,
early_stopping_rounds = NULL,
init_model = NULL,
callbacks = list(),
serializable = TRUE,
objective = "auto",
init_score = NULL,
num_threads = NULL,
colnames = NULL,
categorical_feature = NULL,
...) {
# validate inputs early to avoid unnecessary computation
if (nrounds <= 0L) {
stop("nrounds should be greater than zero")
}
if (is.null(num_threads)) {
num_threads <- .get_default_num_threads()
}
params <- .check_wrapper_param(
main_param_name = "num_threads"
, params = params
, alternative_kwarg_value = num_threads
)
params <- .check_wrapper_param(
main_param_name = "verbosity"
, params = params
, alternative_kwarg_value = verbose
)
# Process factors as labels and auto-determine objective
if (!.is_Dataset(data)) {
data_processor <- DataProcessor$new()
temp <- data_processor$process_label(
label = label
, objective = objective
, params = params
)
label <- temp$label
objective <- temp$objective
params <- temp$params
rm(temp)
} else {
data_processor <- NULL
if (objective == "auto") {
objective <- "regression"
}
}
# Set data to a temporary variable
dtrain <- data
# Check whether data is lgb.Dataset, if not then create lgb.Dataset manually
if (!.is_Dataset(x = dtrain)) {
dtrain <- lgb.Dataset(
data = data
, label = label
, weight = weights
, init_score = init_score
, categorical_feature = categorical_feature
, colnames = colnames
)
}
train_args <- list(
"params" = params
, "data" = dtrain
, "nrounds" = nrounds
, "obj" = objective
, "verbose" = params[["verbosity"]]
, "eval_freq" = eval_freq
, "early_stopping_rounds" = early_stopping_rounds
, "init_model" = init_model
, "callbacks" = callbacks
, "serializable" = serializable
)
train_args <- append(train_args, list(...))
if (! "valids" %in% names(train_args)) {
train_args[["valids"]] <- list()
}
# Train a model using the regular way
bst <- do.call(
what = lgb.train
, args = train_args
)
bst$data_processor <- data_processor
return(bst)
}
#' @name agaricus.train
#' @title Training part from Mushroom Data Set
#' @description This data set is originally from the Mushroom data set,
#' UCI Machine Learning Repository.
#' This data set includes the following fields:
#'
#' \itemize{
#' \item{\code{label}: the label for each record}
#' \item{\code{data}: a sparse Matrix of \code{dgCMatrix} class, with 126 columns.}
#' }
#'
#' @references
#' https://archive.ics.uci.edu/ml/datasets/Mushroom
#'
#' Bache, K. & Lichman, M. (2013). UCI Machine Learning Repository
#' [https://archive.ics.uci.edu/ml]. Irvine, CA: University of California,
#' School of Information and Computer Science.
#'
#' @docType data
#' @keywords datasets
#' @usage data(agaricus.train)
#' @format A list containing a label vector, and a dgCMatrix object with 6513
#' rows and 127 variables
NULL
#' @name agaricus.test
#' @title Test part from Mushroom Data Set
#' @description This data set is originally from the Mushroom data set,
#' UCI Machine Learning Repository.
#' This data set includes the following fields:
#'
#' \itemize{
#' \item{\code{label}: the label for each record}
#' \item{\code{data}: a sparse Matrix of \code{dgCMatrix} class, with 126 columns.}
#' }
#' @references
#' https://archive.ics.uci.edu/ml/datasets/Mushroom
#'
#' Bache, K. & Lichman, M. (2013). UCI Machine Learning Repository
#' [https://archive.ics.uci.edu/ml]. Irvine, CA: University of California,
#' School of Information and Computer Science.
#'
#' @docType data
#' @keywords datasets
#' @usage data(agaricus.test)
#' @format A list containing a label vector, and a dgCMatrix object with 1611
#' rows and 126 variables
NULL
#' @name bank
#' @title Bank Marketing Data Set
#' @description This data set is originally from the Bank Marketing data set,
#' UCI Machine Learning Repository.
#'
#' It contains only the following: bank.csv with 10% of the examples and 17 inputs,
#' randomly selected from 3 (older version of this dataset with less inputs).
#'
#' @references
#' https://archive.ics.uci.edu/ml/datasets/Bank+Marketing
#'
#' S. Moro, P. Cortez and P. Rita. (2014)
#' A Data-Driven Approach to Predict the Success of Bank Telemarketing. Decision Support Systems
#'
#' @docType data
#' @keywords datasets
#' @usage data(bank)
#' @format A data.table with 4521 rows and 17 variables
NULL
# Various imports
#' @import methods
#' @importFrom Matrix Matrix
#' @importFrom R6 R6Class
#' @useDynLib lightgbm , .registration = TRUE
NULL
# Suppress false positive warnings from R CMD CHECK about
# "unrecognized global variable"
globalVariables(c(
"."
, ".N"
, ".SD"
, "abs_contribution"
, "bar_color"
, "Contribution"
, "Cover"
, "Feature"
, "Frequency"
, "Gain"
, "internal_count"
, "internal_value"
, "leaf_index"
, "leaf_parent"
, "leaf_value"
, "node_parent"
, "split_feature"
, "split_gain"
, "split_index"
, "tree_index"
))
+38
View File
@@ -0,0 +1,38 @@
# [description] List of metrics known to LightGBM. The most up to date list can be found
# at https://lightgbm.readthedocs.io/en/latest/Parameters.html#metric-parameters
#
# [return] A named logical vector, where each key is a metric name and each value is a boolean.
# TRUE if higher values of the metric are desirable, FALSE if lower values are desirable.
# Note that only the 'main' metrics are stored here, not aliases, since only the 'main' metrics
# are returned from the C++ side. For example, if you use `metric = "mse"` in your code,
# the metric name `"l2"` will be returned.
.METRICS_HIGHER_BETTER <- function() {
return(
c(
"l1" = FALSE
, "l2" = FALSE
, "mape" = FALSE
, "rmse" = FALSE
, "quantile" = FALSE
, "huber" = FALSE
, "fair" = FALSE
, "poisson" = FALSE
, "gamma" = FALSE
, "gamma_deviance" = FALSE
, "tweedie" = FALSE
, "ndcg" = TRUE
, "map" = TRUE
, "auc" = TRUE
, "average_precision" = TRUE
, "r2" = TRUE
, "binary_logloss" = FALSE
, "binary_error" = FALSE
, "auc_mu" = TRUE
, "multi_logloss" = FALSE
, "multi_error" = FALSE
, "cross_entropy" = FALSE
, "cross_entropy_lambda" = FALSE
, "kullback_leibler" = FALSE
)
)
}
+51
View File
@@ -0,0 +1,51 @@
#' @name setLGBMThreads
#' @title Set maximum number of threads used by LightGBM
#' @description LightGBM attempts to speed up many operations by using multi-threading.
#' The number of threads used in those operations can be controlled via the
#' \code{num_threads} parameter passed through \code{params} to functions like
#' \link{lgb.train} and \link{lgb.Dataset}. However, some operations (like materializing
#' a model from a text file) are done via code paths that don't explicitly accept thread-control
#' configuration.
#'
#' Use this function to set the maximum number of threads LightGBM will use for such operations.
#'
#' This function affects all LightGBM operations in the same process.
#'
#' So, for example, if you call \code{setLGBMthreads(4)}, no other multi-threaded LightGBM
#' operation in the same process will use more than 4 threads.
#'
#' Call \code{setLGBMthreads(-1)} to remove this limitation.
#' @param num_threads maximum number of threads to be used by LightGBM in multi-threaded operations
#' @return NULL
#' @seealso \link{getLGBMthreads}
#' @export
setLGBMthreads <- function(num_threads) {
.Call(
LGBM_SetMaxThreads_R,
num_threads
)
return(invisible(NULL))
}
#' @name getLGBMThreads
#' @title Get default number of threads used by LightGBM
#' @description LightGBM attempts to speed up many operations by using multi-threading.
#' The number of threads used in those operations can be controlled via the
#' \code{num_threads} parameter passed through \code{params} to functions like
#' \link{lgb.train} and \link{lgb.Dataset}. However, some operations (like materializing
#' a model from a text file) are done via code paths that don't explicitly accept thread-control
#' configuration.
#'
#' Use this function to see the default number of threads LightGBM will use for such operations.
#' @return number of threads as an integer. \code{-1} means that in situations where parameter \code{num_threads} is
#' not explicitly supplied, LightGBM will choose a number of threads to use automatically.
#' @seealso \link{setLGBMthreads}
#' @export
getLGBMthreads <- function() {
out <- 0L
.Call(
LGBM_GetMaxThreads_R,
out
)
return(out)
}
+260
View File
@@ -0,0 +1,260 @@
.is_Booster <- function(x) {
return(all(c("R6", "lgb.Booster") %in% class(x))) # nolint: class_equals.
}
.is_Dataset <- function(x) {
return(all(c("R6", "lgb.Dataset") %in% class(x))) # nolint: class_equals.
}
.is_Predictor <- function(x) {
return(all(c("R6", "lgb.Predictor") %in% class(x))) # nolint: class_equals.
}
.is_null_handle <- function(x) {
if (is.null(x)) {
return(TRUE)
}
return(
isTRUE(.Call(LGBM_HandleIsNull_R, x))
)
}
.params2str <- function(params) {
if (!identical(class(params), "list")) {
stop("params must be a list")
}
names(params) <- gsub(".", "_", names(params), fixed = TRUE)
param_names <- names(params)
ret <- list()
# Perform key value join
for (i in seq_along(params)) {
# If a parameter has multiple values, join those values together with commas.
# trimws() is necessary because format() will pad to make strings the same width
val <- paste(
trimws(
format(
x = unname(params[[i]])
, scientific = FALSE
)
)
, collapse = ","
)
if (nchar(val) <= 0L) next # Skip join
# Join key value
pair <- paste(c(param_names[[i]], val), collapse = "=")
ret <- c(ret, pair)
}
if (length(ret) == 0L) {
return("")
}
return(paste(ret, collapse = " "))
}
# [description]
#
# Besides applying checks, this function
#
# 1. turns feature *names* into 1-based integer positions, then
# 2. adds an extra list element with skipped features, then
# 3. turns 1-based integer positions into 0-based positions, and finally
# 4. collapses the values of each list element into a string like "[0, 1]".
#
.check_interaction_constraints <- function(interaction_constraints, column_names) {
if (is.null(interaction_constraints)) {
return(list())
}
if (!identical(class(interaction_constraints), "list")) {
stop("interaction_constraints must be a list")
}
column_indices <- seq_along(column_names)
# Convert feature names to 1-based integer positions and apply checks
for (j in seq_along(interaction_constraints)) {
constraint <- interaction_constraints[[j]]
if (is.character(constraint)) {
constraint_indices <- match(constraint, column_names)
} else if (is.numeric(constraint)) {
constraint_indices <- as.integer(constraint)
} else {
stop("every element in interaction_constraints must be a character vector or numeric vector")
}
# Features outside range?
bad <- !(constraint_indices %in% column_indices)
if (any(bad)) {
stop(
"unknown feature(s) in interaction_constraints: "
, toString(sQuote(constraint[bad], q = FALSE))
)
}
interaction_constraints[[j]] <- constraint_indices
}
# Add missing features as new interaction set
remaining_indices <- setdiff(
column_indices, sort(unique(unlist(interaction_constraints)))
)
if (length(remaining_indices) > 0L) {
interaction_constraints <- c(
interaction_constraints, list(remaining_indices)
)
}
# Turn indices 0-based and convert to string
for (j in seq_along(interaction_constraints)) {
interaction_constraints[[j]] <- paste0(
"[", paste(interaction_constraints[[j]] - 1L, collapse = ","), "]"
)
}
return(interaction_constraints)
}
# [description]
# Take any character values from eval and store them in params$metric.
# This has to account for the fact that `eval` could be a character vector,
# a function, a list of functions, or a list with a mix of strings and
# functions
.check_eval <- function(params, eval) {
if (is.null(params$metric)) {
params$metric <- list()
} else if (is.character(params$metric)) {
params$metric <- as.list(params$metric)
}
# if 'eval' is a character vector or list, find the character
# elements and add them to 'metric'
if (!is.function(eval)) {
for (i in seq_along(eval)) {
element <- eval[[i]]
if (is.character(element)) {
params$metric <- append(params$metric, element)
}
}
}
# If more than one character metric was given, then "None" should
# not be included
if (length(params$metric) > 1L) {
params$metric <- Filter(
f = function(metric) {
!(metric %in% .NO_METRIC_STRINGS())
}
, x = params$metric
)
}
# duplicate metrics should be filtered out
params$metric <- as.list(unique(unlist(params$metric)))
return(params)
}
# [description]
#
# Resolve differences between passed-in keyword arguments, parameters,
# and parameter aliases. This function exists because some functions in the
# package take in parameters through their own keyword arguments other than
# the `params` list.
#
# If the same underlying parameter is provided multiple
# ways, the first item in this list is used:
#
# 1. the main (non-alias) parameter found in `params`
# 2. the alias with the highest priority found in `params`
# 3. the keyword argument passed in
#
# For example, "num_iterations" can also be provided to lgb.train()
# via keyword "nrounds". lgb.train() will choose one value for this parameter
# based on the first match in this list:
#
# 1. params[["num_iterations]]
# 2. the highest priority alias of "num_iterations" found in params
# 3. the nrounds keyword argument
#
# If multiple aliases are found in `params` for the same parameter, they are
# all removed before returning `params`.
#
# [return]
# params with num_iterations set to the chosen value, and other aliases
# of num_iterations removed
.check_wrapper_param <- function(main_param_name, params, alternative_kwarg_value) {
aliases <- .PARAMETER_ALIASES()[[main_param_name]]
aliases_provided <- aliases[aliases %in% names(params)]
aliases_provided <- aliases_provided[aliases_provided != main_param_name]
# prefer the main parameter
if (!is.null(params[[main_param_name]])) {
for (param in aliases_provided) {
params[[param]] <- NULL
}
return(params)
}
# if the main parameter wasn't provided, prefer the first alias
if (length(aliases_provided) > 0L) {
first_param <- aliases_provided[1L]
params[[main_param_name]] <- params[[first_param]]
for (param in aliases_provided) {
params[[param]] <- NULL
}
return(params)
}
# if not provided in params at all, use the alternative value provided
# through a keyword argument from lgb.train(), lgb.cv(), etc.
params[[main_param_name]] <- alternative_kwarg_value
return(params)
}
#' @importFrom parallel detectCores
.get_default_num_threads <- function() {
if (requireNamespace("RhpcBLASctl", quietly = TRUE)) { # nolint: undesirable_function.
return(RhpcBLASctl::get_num_cores())
} else {
msg <- "Optional package 'RhpcBLASctl' not found."
cores <- 0L
if (Sys.info()["sysname"] != "Linux") {
cores <- parallel::detectCores(logical = FALSE)
if (is.na(cores) || cores < 0L) {
cores <- 0L
}
}
if (cores == 0L) {
msg <- paste(msg, "Will use default number of OpenMP threads.", sep = " ")
} else {
msg <- paste(msg, "Detection of CPU cores might not be accurate.", sep = " ")
}
warning(msg)
return(cores)
}
}
.equal_or_both_null <- function(a, b) {
if (is.null(a)) {
if (!is.null(b)) {
return(FALSE)
}
return(TRUE)
} else {
if (is.null(b)) {
return(FALSE)
}
return(a == b)
}
}
+508
View File
@@ -0,0 +1,508 @@
# LightGBM R-package
[![CRAN Version](https://www.r-pkg.org/badges/version/lightgbm)](https://cran.r-project.org/package=lightgbm)
[![Downloads](https://cranlogs.r-pkg.org/badges/grand-total/lightgbm)](https://cran.r-project.org/package=lightgbm)
[![API Docs](https://readthedocs.org/projects/lightgbm/badge/?version=latest)](https://lightgbm.readthedocs.io/en/latest/R/reference/)
<img src="man/figures/logo.svg" align="right" alt="" width="175" />
### Contents
* [Installation](#installation)
- [Installing the CRAN Package](#installing-the-cran-package)
- [Installing from Source with CMake](#install)
- [Installing a GPU-enabled Build](#installing-a-gpu-enabled-build)
- [Installing Precompiled Binaries](#installing-precompiled-binaries)
- [Installing from a Pre-compiled lib_lightgbm](#lib_lightgbm)
* [Examples](#examples)
* [Testing](#testing)
- [Running the Tests](#running-the-tests)
- [Code Coverage](#code-coverage)
* [Updating Documentation](#updating-documentation)
* [Preparing a CRAN Package](#preparing-a-cran-package)
* [Known Issues](#known-issues)
Installation
------------
For the easiest installation, go to ["Installing the CRAN package"](#installing-the-cran-package).
If you experience any issues with that, try ["Installing from Source with CMake"](#install). This can produce a more efficient version of the library on Windows systems with Visual Studio.
To build a GPU-enabled version of the package, follow the steps in ["Installing a GPU-enabled Build"](#installing-a-gpu-enabled-build).
If any of the above options do not work for you or do not meet your needs, please let the maintainers know by [opening an issue](https://github.com/lightgbm-org/LightGBM/issues).
When your package installation is done, you can check quickly if your LightGBM R-package is working by running the following:
```r
library(lightgbm)
data(agaricus.train, package='lightgbm')
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
model <- lgb.cv(
params = list(
objective = "regression"
, metric = "l2"
)
, data = dtrain
)
```
### Installing the CRAN package
`{lightgbm}` is [available on CRAN](https://cran.r-project.org/package=lightgbm), and can be installed with the following R code.
```r
install.packages("lightgbm", repos = "https://cran.r-project.org")
```
This is the easiest way to install `{lightgbm}`. It does not require `CMake` or `Visual Studio`, and should work well on many different operating systems and compilers.
Each CRAN package is also available on [LightGBM releases](https://github.com/lightgbm-org/LightGBM/releases), with a name like `lightgbm-{VERSION}-r-cran.tar.gz`.
#### Custom Installation (Linux, Mac)
The steps above should work on most systems, but users with highly-customized environments might want to change how R builds packages from source.
To change the compiler used when installing the CRAN package, you can create a file `~/.R/Makevars` which overrides `CC` (`C` compiler) and `CXX` (`C++` compiler).
For example, to use `gcc-14` instead of `clang` on macOS, you could use something like the following:
```make
# ~/.R/Makevars
CC=gcc-14
CC17=gcc-14
CXX=g++-14
CXX17=g++-14
```
To check the values R is using, run the following:
```shell
R CMD config --all
```
### Installing from Source with CMake <a id="install"></a>
You need to install git and [CMake](https://cmake.org/) first.
Note: this method is only supported on 64-bit systems. If you need to run LightGBM on 32-bit Windows (i386), follow the instructions in ["Installing the CRAN Package"](#installing-the-cran-package).
#### Windows Preparation
NOTE: Windows users may need to run with administrator rights (either R or the command prompt, depending on the way you are installing this package).
Installing a 64-bit version of [Rtools](https://cran.r-project.org/bin/windows/Rtools/) is mandatory.
After installing `Rtools` and `CMake`, be sure the following paths are added to the environment variable `PATH`. These may have been automatically added when installing other software.
* `Rtools`
- If you have `Rtools` 4.0, example:
- `C:\rtools40\mingw64\bin`
- `C:\rtools40\usr\bin`
- If you have `Rtools` 4.2+, example:
- `C:\rtools42\x86_64-w64-mingw32.static.posix\bin`
- `C:\rtools42\usr\bin`
- **NOTE**: this is e.g. `rtools43\` for R 4.3
* `CMake`
- example: `C:\Program Files\CMake\bin`
* `R`
- example: `C:\Program Files\R\R-4.5.1\bin`
NOTE: Two `Rtools` paths are required from `Rtools` 4.0 onwards because paths and the list of included software was changed in `Rtools` 4.0.
NOTE: `Rtools42` and later take a very different approach to the compiler toolchain than previous releases, and how you install it changes what is required to build packages. See ["Howto: Building R 4.2 and packages on Windows"](https://cran.r-project.org/bin/windows/base/howto-R-4.2.html).
#### Windows Toolchain Options
A "toolchain" refers to the collection of software used to build the library. The R-package can be built with three different toolchains.
**Warning for Windows users**: it is recommended to use *Visual Studio* for its better multi-threading efficiency in Windows for many core systems. For very simple systems (dual core computers or worse), MinGW64 is recommended for maximum performance. If you do not know what to choose, it is recommended to use [Visual Studio](https://visualstudio.microsoft.com/downloads/), the default compiler. **Do not try using MinGW in Windows on many core systems. It may result in 10x slower results than Visual Studio.**
**Visual Studio (default)**
By default, the package will be built with [Visual Studio Build Tools](https://visualstudio.microsoft.com/downloads/).
**MSYS2 (R 4.x)**
If you are using R 4.x and installation fails with Visual Studio, `LightGBM` will fall back to using [MSYS2](https://www.msys2.org/). This should work with the tools already bundled in `Rtools` 4.0.
If you want to force `LightGBM` to use MSYS2 (for any R version), pass `--use-msys2` to the installation script.
```shell
Rscript build_r.R --use-msys2
```
**MinGW**
If you want to force `LightGBM` to use [MinGW](https://www.mingw-w64.org/) (for any R version), pass `--use-mingw` to the installation script.
```shell
Rscript build_r.R --use-mingw
```
#### Mac OS Preparation
You can perform installation either with **Apple Clang** or **gcc**. In case you prefer **Apple Clang**, you should install **OpenMP** (details for installation can be found in [Installation Guide](https://github.com/lightgbm-org/LightGBM/blob/main/docs/Installation-Guide.rst#apple-clang)) first. In case you prefer **gcc**, you need to install it (details for installation can be found in [Installation Guide](https://github.com/lightgbm-org/LightGBM/blob/main/docs/Installation-Guide.rst#gcc)) and set some environment variables to tell R to use `gcc` and `g++`. If you install these from Homebrew, your versions of `g++` and `gcc` are most likely in `/usr/local/bin`, as shown below.
```
# replace 8 with version of gcc installed on your machine
export CXX=/usr/local/bin/g++-8 CC=/usr/local/bin/gcc-8
```
#### Install with CMake
After following the "preparation" steps above for your operating system, build and install the R-package with the following commands:
```sh
git clone --recursive https://github.com/lightgbm-org/LightGBM
cd LightGBM
Rscript build_r.R
```
The `build_r.R` script builds the package in a temporary directory called `lightgbm_r`. It will destroy and recreate that directory each time you run the script. That script supports the following command-line options:
- `--no-build-vignettes`: Skip building vignettes.
- `-j[jobs]`: Number of threads to use when compiling LightGBM. E.g., `-j4` will try to compile 4 objects at a time.
- by default, this script uses single-thread compilation
- for best results, set `-j` to the number of physical CPUs
- `--skip-install`: Build the package tarball, but do not install it.
- `--use-gpu`: Build a GPU-enabled version of the library.
- `--use-mingw`: Force the use of MinGW toolchain, regardless of R version.
- `--use-msys2`: Force the use of MSYS2 toolchain, regardless of R version.
Note: for the build with Visual Studio/VS Build Tools in Windows, you should use the Windows CMD or PowerShell.
### Installing a GPU-enabled Build
You will need to install Boost and OpenCL first: details for installation can be found in [Installation-Guide](https://github.com/lightgbm-org/LightGBM/blob/main/docs/Installation-Guide.rst#build-gpu-version).
After installing these other libraries, follow the steps in ["Installing from Source with CMake"](#install). When you reach the step that mentions `build_r.R`, pass the flag `--use-gpu`.
```shell
Rscript build_r.R --use-gpu
```
You may also need or want to provide additional configuration, depending on your setup. For example, you may need to provide locations for Boost and OpenCL.
```shell
Rscript build_r.R \
--use-gpu \
--opencl-library=/usr/lib/x86_64-linux-gnu/libOpenCL.so \
--boost-librarydir=/usr/lib/x86_64-linux-gnu
```
The following options correspond to the [CMake FindBoost options](https://cmake.org/cmake/help/latest/module/FindBoost.html) by the same names.
* `--boost-root`
* `--boost-dir`
* `--boost-include-dir`
* `--boost-librarydir`
The following options correspond to the [CMake FindOpenCL options](https://cmake.org/cmake/help/latest/module/FindOpenCL.html) by the same names.
* `--opencl-include-dir`
* `--opencl-library`
### Installing Precompiled Binaries
Precompiled binaries for Mac and Windows are prepared by CRAN a few days after each release to CRAN. They can be installed with the following R code.
```r
install.packages(
"lightgbm"
, type = "both"
, repos = "https://cran.r-project.org"
)
```
These packages do not require compilation, so they will be faster and easier to install than packages that are built from source.
CRAN does not prepare precompiled binaries for Linux, and as of this writing neither does this project.
### Installing from a Pre-compiled lib_lightgbm <a id="lib_lightgbm"></a>
Previous versions of LightGBM offered the ability to first compile the C++ library (`lib_lightgbm.{dll,dylib,so}`) and then build an R-package that wraps it.
As of version 3.0.0, this is no longer supported. If building from source is difficult for you, please [open an issue](https://github.com/lightgbm-org/LightGBM/issues).
Examples
--------
Please visit [demo](https://github.com/lightgbm-org/LightGBM/tree/main/R-package/demo):
* [Basic walkthrough of wrappers](https://github.com/lightgbm-org/LightGBM/blob/main/R-package/demo/basic_walkthrough.R)
* [Boosting from existing prediction](https://github.com/lightgbm-org/LightGBM/blob/main/R-package/demo/boost_from_prediction.R)
* [Early Stopping](https://github.com/lightgbm-org/LightGBM/blob/main/R-package/demo/early_stopping.R)
* [Cross Validation](https://github.com/lightgbm-org/LightGBM/blob/main/R-package/demo/cross_validation.R)
* [Multiclass Training/Prediction](https://github.com/lightgbm-org/LightGBM/blob/main/R-package/demo/multiclass.R)
* [Leaf (in)Stability](https://github.com/lightgbm-org/LightGBM/blob/main/R-package/demo/leaf_stability.R)
* [Weight-Parameter Adjustment Relationship](https://github.com/lightgbm-org/LightGBM/blob/main/R-package/demo/weight_param.R)
Testing
-------
The R-package's unit tests are run automatically on every commit, via integrations like [GitHub Actions](https://github.com/lightgbm-org/LightGBM/actions). Adding new tests in `R-package/tests/testthat` is a valuable way to improve the reliability of the R-package.
### Running the Tests
While developing the R-package, run the code below to run the unit tests.
```shell
sh build-cran-package.sh \
--no-build-vignettes
R CMD INSTALL --with-keep.source lightgbm*.tar.gz
cd R-package/tests
Rscript testthat.R
```
To run the tests with more verbose logs, set environment variable `LIGHTGBM_TEST_VERBOSITY` to a valid value for parameter [`verbosity`](https://lightgbm.readthedocs.io/en/latest/Parameters.html#verbosity).
```shell
export LIGHTGBM_TEST_VERBOSITY=1
cd R-package/tests
Rscript testthat.R
```
### Code Coverage
When adding tests, you may want to use test coverage to identify untested areas and to check if the tests you've added are covering all branches of the intended code.
The example below shows how to generate code coverage for the R-package on a macOS or Linux setup. To adjust for your environment, refer to [the customization step described above](#custom-installation-linux-mac).
```shell
# Install
sh build-cran-package.sh \
--no-build-vignettes
# Get coverage
Rscript -e " \
library(covr);
coverage <- covr::package_coverage('./lightgbm_r', type = 'tests', quiet = FALSE);
print(coverage);
covr::report(coverage, file = file.path(getwd(), 'coverage.html'), browse = TRUE);
"
```
Updating Documentation
----------------------
The R-package uses [`{roxygen2}`](https://CRAN.R-project.org/package=roxygen2) to generate its documentation.
The generated `DESCRIPTION`, `NAMESPACE`, and `man/` files are checked into source control.
To regenerate those files, run the following.
```shell
Rscript \
--vanilla \
-e "install.packages('roxygen2', repos = 'https://cran.rstudio.com')"
sh build-cran-package.sh --no-build-vignettes
R CMD INSTALL \
--with-keep.source \
./lightgbm_*.tar.gz
cd R-package
Rscript \
--vanilla \
-e "roxygen2::roxygenize(load = 'installed')"
```
Preparing a CRAN Package
------------------------
This section is primarily for maintainers, but may help users and contributors to understand the structure of the R-package.
Most of `LightGBM` uses `CMake` to handle tasks like setting compiler and linker flags, including header file locations, and linking to other libraries. Because CRAN packages typically do not assume the presence of `CMake`, the R-package uses an alternative method that is in the CRAN-supported toolchain for building R packages with C++ code: `Autoconf`.
For more information on this approach, see ["Writing R Extensions"](https://cran.r-project.org/doc/manuals/r-release/R-exts.html#Configure-and-cleanup).
### Build a CRAN Package
From the root of the repository, run the following.
```shell
git submodule update --init --recursive
sh build-cran-package.sh
```
This will create a file `lightgbm_${VERSION}.tar.gz`, where `VERSION` is the version of `LightGBM`.
That script supports the following command-line options:
- `--no-build-vignettes`: Skip building vignettes.
- `--r-executable=[path-to-executable]`: Use an alternative build of R.
### Standard Installation from CRAN Package
After building the package, install it with a command like the following:
```shell
R CMD install lightgbm_*.tar.gz
```
### Changing the CRAN Package
A lot of details are handled automatically by `R CMD build` and `R CMD install`, so it can be difficult to understand how the files in the R-package are related to each other. An extensive treatment of those details is available in ["Writing R Extensions"](https://cran.r-project.org/doc/manuals/r-release/R-exts.html).
This section briefly explains the key files for building a CRAN package. To update the package, edit the files relevant to your change and re-run the steps in [Build a CRAN Package](#build-a-cran-package).
**Linux or Mac**
At build time, `configure` will be run and used to create a file `Makevars`, using `Makevars.in` as a template.
1. Edit `configure.ac`.
2. Create `configure` with `autoconf`. Do not edit it by hand. This file must be generated on Ubuntu 22.04.
If you have an Ubuntu 22.04 environment available, run the provided script from the root of the `LightGBM` repository.
```shell
./R-package/recreate-configure.sh
```
If you do not have easy access to an Ubuntu 22.04 environment, the `configure` script can be generated using Docker by running the code below from the root of this repo.
```shell
docker run \
--rm \
-v $(pwd):/opt/LightGBM \
-w /opt/LightGBM \
ubuntu:22.04 \
./R-package/recreate-configure.sh
```
The version of `autoconf` used by this project is stored in `R-package/AUTOCONF_UBUNTU_VERSION`. To update that version, update that file and run the commands above. To see available versions, see https://packages.ubuntu.com/search?keywords=autoconf.
3. Edit `src/Makevars.in`.
Alternatively, GitHub Actions can re-generate this file for you.
1. navigate to https://github.com/lightgbm-org/LightGBM/actions/workflows/r_configure.yml
2. click "Run workflow" (drop-down)
3. enter the branch from the pull request for the `pr-branch` input
4. click "Run workflow" (button)
**Configuring for Windows**
At build time, `configure.win` will be run and used to create a file `Makevars.win`, using `Makevars.win.in` as a template.
1. Edit `configure.win` directly.
2. Edit `src/Makevars.win.in`.
### Testing the CRAN Package
`{lightgbm}` is tested automatically on every commit, across many combinations of operating system, R version, and compiler. This section describes how to test the package locally while you are developing.
#### Windows, Mac, and Linux
```shell
sh build-cran-package.sh
R CMD check --as-cran lightgbm_*.tar.gz
```
#### <a id="UBSAN"></a>ASAN and UBSAN
All packages uploaded to CRAN must pass builds using `gcc` and `clang`, instrumented with two sanitizers: the Address Sanitizer (ASAN) and the Undefined Behavior Sanitizer (UBSAN).
For more background, see
* [this blog post](https://dirk.eddelbuettel.com/code/sanitizers.html)
* [top-level CRAN documentation on these checks](https://cran.r-project.org/web/checks/check_issue_kinds.html)
* [CRAN's configuration of these checks](https://www.stats.ox.ac.uk/pub/bdr/memtests/README.txt)
You can replicate these checks locally using Docker.
For more information on the image used for testing, see https://github.com/wch/r-debug.
In the code below, environment variable `R_CUSTOMIZATION` should be set to one of two values.
* `"san"` = replicates CRAN's `gcc-ASAN` and `gcc-UBSAN` checks
* `"csan"` = replicates CRAN's `clang-ASAN` and `clang-UBSAN` checks
```shell
docker run \
--rm \
-it \
-v $(pwd):/opt/LightGBM \
-w /opt/LightGBM \
--env R_CUSTOMIZATION=san \
wch1/r-debug:latest \
/bin/bash
# install dependencies
RDscript${R_CUSTOMIZATION} \
-e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'markdown', 'Matrix', 'RhpcBLASctl', 'testthat'), repos = 'https://cran.r-project.org', Ncpus = parallel::detectCores())"
# install lightgbm
sh build-cran-package.sh --r-executable=RD${R_CUSTOMIZATION}
RD${R_CUSTOMIZATION} \
CMD INSTALL lightgbm_*.tar.gz
# run tests
cd R-package/tests
rm -f ./tests.log
RDscript${R_CUSTOMIZATION} testthat.R >> tests.log 2>&1
# check that tests passed
echo "test exit code: $?"
tail -300 ./tests.log
```
#### Valgrind
All packages uploaded to CRAN must be built and tested without raising any issues from `valgrind`. `valgrind` is a profiler that can catch serious issues like memory leaks and illegal writes. For more information, see [this blog post](https://reside-ic.github.io/blog/debugging-and-fixing-crans-additional-checks-errors/).
You can replicate these checks locally using Docker. Note that instrumented versions of R built to use `valgrind` run much slower, and these tests may take as long as 20 minutes to run.
```shell
docker run \
--rm \
-v $(pwd):/opt/LightGBM \
-w /opt/LightGBM \
-it \
wch1/r-debug
RDscriptvalgrind -e "install.packages(c('R6', 'data.table', 'jsonlite', 'knitr', 'markdown', 'Matrix', 'RhpcBLASctl', 'testthat'), repos = 'https://cran.rstudio.com', Ncpus = parallel::detectCores())"
sh build-cran-package.sh \
--r-executable=RDvalgrind
RDvalgrind CMD INSTALL \
--preclean \
--install-tests \
lightgbm_*.tar.gz
cd R-package/tests
RDvalgrind \
--no-readline \
--vanilla \
-d "valgrind --tool=memcheck --leak-check=full --track-origins=yes" \
-f testthat.R \
2>&1 \
| tee out.log \
| cat
```
These tests can also be triggered on a pull request branch, using GitHub Actions.
1. navigate to https://github.com/lightgbm-org/LightGBM/actions/workflows/r_valgrind.yml
2. click "Run workflow" (drop-down)
3. enter the branch from the pull request for the `pr-branch` input
4. enter the pull request ID for the `pr-number` input
5. click "Run workflow" (button)
Or by using the GitHub CLI, using a command similar to this:
```shell
gh workflow run \
--repo lightgbm-org/LightGBM \
r_valgrind.yml \
-f pr-branch=ci/fix-rerun-workflow \
-f pr-number=7072
```
Known Issues
------------
For information about known issues with the R-package, see the [R-package section of LightGBM's main FAQ page](https://lightgbm.readthedocs.io/en/latest/FAQ.html#r-package).
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
rm -f aclocal.m4
rm -rf ./autom4te.cache
rm -f config.log
rm -f config.status
rm -f src/Makevars
Vendored Executable
+3072
View File
File diff suppressed because it is too large Load Diff
+172
View File
@@ -0,0 +1,172 @@
### configure.ac -*- Autoconf -*-
# Template used by Autoconf to generate 'configure' script. For more see:
# * https://unconj.ca/blog/an-autoconf-primer-for-r-package-authors.html
# * https://cran.r-project.org/doc/manuals/r-release/R-exts.html#Configure-and-cleanup
AC_PREREQ(2.69)
AC_INIT([lightgbm], [~~VERSION~~], [], [lightgbm], [])
###########################
# find compiler and flags #
###########################
AC_MSG_CHECKING([location of R])
AC_MSG_RESULT([${R_HOME}])
# set up CPP flags
# find the compiler and compiler flags used by R.
: ${R_HOME=`R HOME`}
if test -z "${R_HOME}"; then
echo "could not determine R_HOME"
exit 1
fi
CXX17=`"${R_HOME}/bin/R" CMD config CXX17`
CXX17STD=`"${R_HOME}/bin/R" CMD config CXX17STD`
CXX="${CXX17} ${CXX17STD}"
CPPFLAGS=`"${R_HOME}/bin/R" CMD config CPPFLAGS`
CXXFLAGS=`"${R_HOME}/bin/R" CMD config CXX17FLAGS`
LDFLAGS=`"${R_HOME}/bin/R" CMD config LDFLAGS`
AC_LANG(C++)
# LightGBM-specific flags
LGB_CPPFLAGS=""
#########
# Eigen #
#########
LGB_CPPFLAGS="${LGB_CPPFLAGS} -DEIGEN_MPL2_ONLY -DEIGEN_DONT_PARALLELIZE"
###############
# MM_PREFETCH #
###############
AC_MSG_CHECKING([whether MM_PREFETCH works])
ac_mmprefetch=no
AC_LANG_CONFTEST(
[
AC_LANG_PROGRAM(
[[
#include <xmmintrin.h>
]],
[[
int a = 0;
_mm_prefetch(&a, _MM_HINT_NTA);
return 0;
]]
)
]
)
${CXX} ${CPPFLAGS} ${CXXFLAGS} -o conftest conftest.cpp 2>/dev/null && ./conftest && ac_mmprefetch=yes
AC_MSG_RESULT([${ac_mmprefetch}])
if test "${ac_mmprefetch}" = yes; then
LGB_CPPFLAGS="${LGB_CPPFLAGS} -DMM_PREFETCH=1"
fi
############
# MM_ALLOC #
############
AC_MSG_CHECKING([whether MM_MALLOC works])
ac_mm_malloc=no
AC_LANG_CONFTEST(
[
AC_LANG_PROGRAM(
[[
#include <mm_malloc.h>
]],
[[
char *a = (char*)_mm_malloc(8, 16);
_mm_free(a);
return 0;
]]
)
]
)
${CXX} ${CPPFLAGS} ${CXXFLAGS} -o conftest conftest.cpp 2>/dev/null && ./conftest && ac_mm_malloc=yes
AC_MSG_RESULT([${ac_mm_malloc}])
if test "${ac_mm_malloc}" = yes; then
LGB_CPPFLAGS="${LGB_CPPFLAGS} -DMM_MALLOC=1"
fi
##########
# OpenMP #
##########
OPENMP_CXXFLAGS=""
if test `uname -s` = "Linux"
then
OPENMP_CXXFLAGS="\$(SHLIB_OPENMP_CXXFLAGS)"
fi
if test `uname -s` = "Darwin"
then
OPENMP_CXXFLAGS='-Xclang -fopenmp'
OPENMP_LIB='-lomp'
# libomp 15.0+ from brew is keg-only (i.e. not symlinked into the standard paths search by the linker),
# so need to search in other locations.
# See https://github.com/Homebrew/homebrew-core/issues/112107#issuecomment-1278042927.
#
# If Homebrew is found and libomp was installed with it, this code adds the necessary
# flags for the compiler to find libomp headers and for the linker to find libomp.dylib.
HOMEBREW_LIBOMP_PREFIX=""
if command -v brew >/dev/null 2>&1; then
ac_brew_openmp=no
AC_MSG_CHECKING([whether OpenMP was installed via Homebrew])
brew --prefix libomp >/dev/null 2>&1 && ac_brew_openmp=yes
AC_MSG_RESULT([${ac_brew_openmp}])
if test "${ac_brew_openmp}" = yes; then
HOMEBREW_LIBOMP_PREFIX=`brew --prefix libomp`
OPENMP_CXXFLAGS="${OPENMP_CXXFLAGS} -I${HOMEBREW_LIBOMP_PREFIX}/include"
OPENMP_LIB="${OPENMP_LIB} -L${HOMEBREW_LIBOMP_PREFIX}/lib"
fi
fi
ac_pkg_openmp=no
AC_MSG_CHECKING([whether OpenMP will work in a package])
AC_LANG_CONFTEST(
[
AC_LANG_PROGRAM(
[[
#include <omp.h>
]],
[[
return (omp_get_max_threads() <= 1);
]]
)
]
)
${CXX} ${CPPFLAGS} ${CXXFLAGS} ${LDFLAGS} ${OPENMP_CXXFLAGS} ${OPENMP_LIB} -o conftest conftest.cpp 2>/dev/null && ./conftest && ac_pkg_openmp=yes
# -Xclang is not portable (it is clang-specific)
# if compilation above failed, try without that flag
if test "${ac_pkg_openmp}" = no; then
if test -f "./conftest"; then
rm ./conftest
fi
OPENMP_CXXFLAGS="-fopenmp"
${CXX} ${CPPFLAGS} ${CXXFLAGS} ${LDFLAGS} ${OPENMP_CXXFLAGS} ${OPENMP_LIB} -o conftest conftest.cpp 2>/dev/null && ./conftest && ac_pkg_openmp=yes
fi
AC_MSG_RESULT([${ac_pkg_openmp}])
if test "${ac_pkg_openmp}" = no; then
OPENMP_CXXFLAGS=''
OPENMP_LIB=''
echo '***********************************************************************************************'
echo ' OpenMP is unavailable on this macOS system. LightGBM code will run single-threaded as a result.'
echo ' To use all CPU cores for training jobs, you should install OpenMP by running'
echo ''
echo ' brew install libomp'
echo '***********************************************************************************************'
fi
fi
# substitute variables from this script into Makevars.in
AC_SUBST(OPENMP_CXXFLAGS)
AC_SUBST(OPENMP_LIB)
AC_SUBST(LGB_CPPFLAGS)
AC_CONFIG_FILES([src/Makevars])
# write out Autoconf output
AC_OUTPUT
+102
View File
@@ -0,0 +1,102 @@
# Script used to generate `Makevars.win` from `Makevars.win.in`
# on Windows
###########################
# find compiler and flags #
###########################
R_EXE="${R_HOME}/bin${R_ARCH_BIN}/R"
CXX17=`"${R_EXE}" CMD config CXX17`
CXX17STD=`"${R_EXE}" CMD config CXX17STD`
CXX="${CXX17} ${CXX17STD}"
CXXFLAGS=`"${R_EXE}" CMD config CXX17FLAGS`
CPPFLAGS=`"${R_EXE}" CMD config CPPFLAGS`
# LightGBM-specific flags
LGB_CPPFLAGS=""
#########
# Eigen #
#########
LGB_CPPFLAGS="${LGB_CPPFLAGS} -DEIGEN_MPL2_ONLY -DEIGEN_DONT_PARALLELIZE"
###############
# MM_PREFETCH #
###############
ac_mm_prefetch="no"
cat > conftest.cpp <<EOL
#include <xmmintrin.h>
int main() {
int a = 0;
_mm_prefetch(&a, _MM_HINT_NTA);
return 0;
}
EOL
${CXX} ${CXXFLAGS} ${CPPFLAGS} -o conftest conftest.cpp 2>/dev/null && ./conftest && ac_mm_prefetch="yes"
rm -f ./conftest
rm -f ./conftest.cpp
echo "checking whether MM_PREFETCH works...${ac_mm_prefetch}"
if test "${ac_mm_prefetch}" = "yes";
then
LGB_CPPFLAGS="${LGB_CPPFLAGS} -DMM_PREFETCH=1"
fi
############
# MM_ALLOC #
############
ac_mm_malloc="no"
cat > conftest.cpp <<EOL
#include <mm_malloc.h>
int main() {
char *a = (char*)_mm_malloc(8, 16);
_mm_free(a);
return 0;
}
EOL
${CXX} ${CXXFLAGS} ${CPPFLAGS} -o conftest conftest.cpp 2>/dev/null && ./conftest && ac_mm_malloc="yes"
rm -f ./conftest
rm -f ./conftest.cpp
echo "checking whether MM_MALLOC works...${ac_mm_malloc}"
if test "${ac_mm_malloc}" = "yes";
then
LGB_CPPFLAGS="${LGB_CPPFLAGS} -DMM_MALLOC=1"
fi
#############
# INET_PTON #
#############
ac_inet_pton="no"
cat > conftest.cpp <<EOL
#include <ws2tcpip.h>
int main() {
int (*fptr)(int, const char*, void*);
fptr = &inet_pton;
return 0;
}
EOL
${CXX} ${CXXFLAGS} ${CPPFLAGS} -o conftest conftest.cpp 2>/dev/null && ./conftest && ac_inet_pton="yes"
rm -f ./conftest
rm -f ./conftest.cpp
echo "checking whether INET_PTON works...${ac_inet_pton}"
if test "${ac_inet_pton}" = "yes";
then
LGB_CPPFLAGS="${LGB_CPPFLAGS} -DWIN_HAS_INET_PTON=1"
fi
# Generate Makevars.win from Makevars.win.in
sed -e \
"s/@LGB_CPPFLAGS@/$LGB_CPPFLAGS/" \
< src/Makevars.win.in > src/Makevars.win
+962
View File
@@ -0,0 +1,962 @@
# CRAN Submission History
## v4.6.0 - Submission 1 - (February 13, 2025)
### CRAN response
Accepted to CRAN
### Maintainer Notes
This release fixed several issues reported by CRAN.
Bashisms in `configure`
```text
possible bashism in configure.ac line 63 (should be VAR="${VAR}foo"):
LGB_CPPFLAGS+=" -DMM_PREFETCH=1"
possible bashism in configure.ac line 89 (should be VAR="${VAR}foo"):
LGB_CPPFLAGS+=" -DMM_MALLOC=1"
```
Compilation errors on GCC 15.
```text
io/json11.cpp:97:28: error: 'uint8_t' does not name a type
97 | } else if (static_cast<uint8_t>(ch) == 0xe2 &&
| ^~~~~~~
io/json11.cpp:97:28: note: 'uint8_t' is defined in header '<cstdint>'; this is probably fixable by adding '#include <cstdint>'
io/json11.cpp:98:28: error: 'uint8_t' does not name a type
98 | static_cast<uint8_t>(value[i + 1]) == 0x80 &&
```
This release contains fixes for those issues.
## v4.5.0 - Submission 1 - (July 25, 2024)
### CRAN response
Accepted to CRAN
### Maintainer Notes
This release was a response to a request from CRAN.
On July 4, 2024, CRAN notified us that the following compiler warnings raised by `gcc` 14 needed to be fixed by August 3, 2024.
```text
Result: WARN
Found the following significant warnings:
io/dense_bin.hpp:617:27: warning: template-id not allowed for constructor in C++20 [-Wtemplate-id-cdtor]
io/multi_val_dense_bin.hpp:346:26: warning: template-id not allowed for constructor in C++20 [-Wtemplate-id-cdtor]
io/multi_val_sparse_bin.hpp:433:36: warning: template-id not allowed for constructor in C++20 [-Wtemplate-id-cdtor]
io/sparse_bin.hpp:785:19: warning: template-id not allowed for constructor in C++20 [-Wtemplate-id-cdtor]
See /data/gannet/ripley/R/packages/tests-devel/lightgbm.Rcheck/00install.out for details.
```
This release contains fixes for those issues.
## v4.4.0 - Submission 1 - (June 14, 2024)
### CRAN response
Accepted to CRAN
### Maintainer Notes
This was a standard release of `{lightgbm}`, not intended to fix any particular R-specific issues.
## v4.3.0 - Submission 1 - (January 18, 2024)
### CRAN response
Accepted to CRAN
### Maintainer Notes
This submission was put up in response to CRAN saying the package would be archived if the following
warning was not fixed within 14 days.
```text
/usr/local/clang-trunk/bin/../include/c++/v1/__fwd/string_view.h:22:41:
warning: 'char_traits<fmt::detail::char8_type>' is deprecated:
char_traits<T> for T not equal to char, wchar_t, char8_t, char16_t or char32_t is non-standard and is provided for a temporary period.
It will be removed in LLVM 19, so please migrate off of it. [-Wdeprecated-declarations]
```
See https://github.com/lightgbm-org/LightGBM/issues/6264.
## v4.2.0 - Submission 1 - (December 7, 2023)
### CRAN response
Accepted to CRAN
### Maintainer Notes
This submission included many changes from the last 2 years, as well as fixes for a warning
CRAN said could cause the package to be archived: https://github.com/lightgbm-org/LightGBM/issues/6221.
## v4.1.0 - not submitted
v4.1.0 was not submitted to CRAN, because https://github.com/lightgbm-org/LightGBM/issues/5987 had not been resolved.
## v4.0.0 - Submission 2 - (July 19, 2023)
### CRAN response
> Dear maintainer,
> package lightgbm_4.0.0.tar.gz does not pass the incoming checks automatically.
The logs linked from those messages showed one issue remaining on Debian (0 on Windows).
```text
* checking examples ... [7s/4s] NOTE
Examples with CPU time > 2.5 times elapsed time
user system elapsed ratio
lgb.restore_handle 1.206 0.085 0.128 10.08
```
### Maintainer Notes
Chose to document the issue and need for a fix in https://github.com/lightgbm-org/LightGBM/issues/5987, but not resubmit,
to avoid annoying CRAN maintainers.
## v4.0.0 - Submission 1 - (July 16, 2023)
### CRAN response
> Dear maintainer,
> package lightgbm_4.0.0.tar.gz does not pass the incoming checks automatically.
The logs linked from those messages showed the following issues from `R CMD check`.
```text
* checking S3 generic/method consistency ... NOTE
Mismatches for apparent methods not registered:
merge:
function(x, y, ...)
merge.eval.string:
function(env)
format:
function(x, ...)
format.eval.string:
function(eval_res, eval_err)
See section 'Registering S3 methods' in the 'Writing R Extensions'
manual.
```
```text
* checking examples ... [8s/4s] NOTE
Examples with CPU time > 2.5 times elapsed time
user system elapsed ratio
lgb.restore_handle 1.819 0.128 0.165 11.8
```
### Maintainer Notes
Attempted to fix these with https://github.com/lightgbm-org/LightGBM/pull/5988 and resubmitted.
## v3.3.5 - Submission 2 - (January 16, 2023)
### CRAN response
> Reason was
>
> Flavor: r-devel-windows-x86_64
> Check: OOverall checktime, Result: NOTE
> Overall checktime 14 min > 10 min
>
> but the maintainer cannot do much to reduce this, so I triggered revdep checks now.
> Please reply to the archival message in case the issue is not fixable easily.
>
> Best,
> Uwe Ligges
### Maintainer Notes
This was technically not a "resubmission".
We asked CRAN why the first v3.3.5 submission had been archived, and they responded with the response above... and then v3.3.5 passed all checks with no further work from LightGBM maintainers.
## v3.3.5 - Submission 1 - (January 11, 2023)
### CRAN response
Archived without a response.
### Maintainer Notes
Submitted with the following comment.
> This submission contains {lightgbm} 3.3.5
> Per CRAN's policies, I am submitting it on behalf of the project's maintainer (Yu Shi), with his permission.
> This submission includes patches to address the following warnings observed on the fedora and debian CRAN checks.
> Found the following significant warnings:
> io/json11.cpp:207:47: warning: unqualified call to 'std::move' [-Wunqualified-std-cast-call]
> io/json11.cpp:216:51: warning: unqualified call to 'std::move' [-Wunqualified-std-cast-call]
> io/json11.cpp:225:53: warning: unqualified call to 'std::move' [-Wunqualified-std-cast-call]
> io/json11.cpp:268:60: warning: unqualified call to 'std::move' [-Wunqualified-std-cast-call]
> io/json11.cpp:272:36: warning: unqualified call to 'std::move' [-Wunqualified-std-cast-call]
> io/json11.cpp:276:37: warning: unqualified call to 'std::move' [-Wunqualified-std-cast-call]
> io/json11.cpp:381:41: warning: unqualified call to 'std::move' [-Wunqualified-std-cast-call]
> io/json11.cpp:150:39: warning: unqualified call to 'std::move' [-Wunqualified-std-cast-call]
Thank you very much for your time and consideration.
## v3.3.4 - Submission 1 - (December 15, 2022)
### CRAN response
Accepted to CRAN
### Maintainer Notes
Submitted with the following comment:
> This submission contains {lightgbm} 3.3.4
> Per CRAN's policies, I am submitting it on behalf of the project's maintainer (Yu Shi), with his permission.
> This submission includes patches to address the following warnings observed on the fedora and debian CRAN checks.
>
> Compiled code should not call entry points which might terminate R nor write to stdout/stderr instead of to the console, nor use Fortran I/O nor system RNGs nor [v]sprintf.
> Thank you very much for your time and consideration.
## v3.3.3 - Submission 1 - (October 10, 2022)
### CRAN response
Accepted to CRAN
### Maintainer Notes
Submitted with the following comment:
> This submission contains {lightgbm} 3.3.3.
> Per CRAN's policies, I am submitting on it on behalf of the project's maintainer (Yu Shi), with his permission (https://github.com/lightgbm-org/LightGBM/pull/5525).
> This submission includes two patches:
> * a change to testing to avoid a failed test related to non-ASCII strings on the `r-devel-linux-x86_64-debian-clang` check flavor (https://github.com/lightgbm-org/LightGBM/pull/5526)
> * modifications to allow compatibility with the RTools42 build toolchain (https://github.com/lightgbm-org/LightGBM/pull/5503)
> Thank you very much for your time and consideration.
## v3.3.2 - Submission 1 - (January 7, 2022)
### CRAN response
Accepted to CRAN on January 14, 2022.
### Maintainer Notes
In this submission, we uploaded a patch that CRAN stuff provided us via e-mail. The full text of the e-mail from CRAN:
```text
Dear maintainers,
This concerns the CRAN packages
Cairo cepreader gpboost httpuv ipaddress lightgbm proj4 prophet
RcppCWB RcppParallel RDieHarder re2 redux rgeolocate RGtk2 tth
udunits2 unrtf
maintained by one of you:
Andreas Blaette andreas.blaette@uni-due.de: RcppCWB
David Hall david.hall.physics@gmail.com: ipaddress
Dirk Eddelbuettel edd@debian.org: RDieHarder
Fabio Sigrist fabiosigrist@gmail.com: gpboost
Friedrich Leisch Friedrich.Leisch@R-project.org: tth
Girish Palya girishji@gmail.com: re2
James Hiebert hiebert@uvic.ca: udunits2
Jari Oksanen jhoksane@gmail.com: cepreader
Kevin Ushey kevin@rstudio.com: RcppParallel
ORPHANED: RGtk2
Os Keyes ironholds@gmail.com: rgeolocate
Rich FitzJohn rich.fitzjohn@gmail.com: redux
Sean Taylor sjtz@pm.me: prophet
Simon Urbanek simon.urbanek@r-project.org: proj4
Simon Urbanek Simon.Urbanek@r-project.org: Cairo
Winston Chang winston@rstudio.com: httpuv
Yu Shi yushi2@microsoft.com: lightgbm
your packages need to be updated for R-devel/R 4.2 to work on Windows,
following the recent switch to UCRT and Rtools42.
Sorry for the group message, please feel free to respond individually
regarding your package or ask specifically about what needs to be fixed.
I've created patches for you, so please review them and fix your packages:
https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fsvn.r-project.org%2FR-dev-web%2Ftrunk%2FWindowsBuilds%2Fwinutf8%2Fucrt3%2Fr_packages%2Fpatches%2FCRAN%2F&amp;data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=rFGf7Y4Dvo6g1kzV%2BeAJDLGm1TUtzQsLsavElTw6H1U%3D&amp;reserved=0
You can apply them as follows
tar xfz package_1.0.0.tar.gz
wget
https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fsvn.r-project.org%2FR-dev-web%2Ftrunk%2FWindowsBuilds%2Fwinutf8%2Fucrt3%2Fr_packages%2Fpatches%2FCRAN%2Fpackage.diff&amp;data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=iyTjhoqvzj3IbQ8HGCZeh1IQl34FAGpIdVyZWkzNvO0%3D&amp;reserved=0
patch --binary < package.diff
These patches are currently automatically applied by R-devel on Windows
at installation time, which makes most of your packages pass their
checks (as OK or NOTE), but please check your results carefully and
carefully review the patches. Usually these changes were because of
newer GCC or newer MinGW in the toolchain, but some for other reasons,
and some of them will definitely have to be improved so that the package
keeps building also for older versions of R using Rtools40. We have only
been testing the patches with UCRT (and Rtools42) on Windows.
For more information, please see
https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fdeveloper.r-project.org%2FBlog%2Fpublic%2F2021%2F12%2F07%2Fupcoming-changes-in-r-4.2-on-windows%2F&amp;data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=SY77zgtbDbHvTxTgPLOoe%2Fw5OZDhXvJoxpVOoEaKoYo%3D&amp;reserved=0
https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fdeveloper.r-project.org%2FWindowsBuilds%2Fwinutf8%2Fucrt3%2Fhowto.html&amp;data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=dlVJ4nhQlmDPd56bHoVsWZuRfrUUorvOWxoUTmVDM%2Bg%3D&amp;reserved=0
Once you add your patches/fix the issues, your package will probably
show a warning during R CMD check (as patching would be attempted to be
applied again). That's ok, at that point please let me know and I will
remove my patch from the repository of automatically applied patches.
If you end up just applying the patch as is, there is probably no need
testing on your end, but you can do so using Winbuilder, r-hub, github
actions (e.g. https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fkalibera%2Fucrt3&amp;data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=msqoPzqDStlAUn%2Bb6gGevwFPD%2FaNL5dTxiNud2Sqzy8%3D&amp;reserved=0).
If you wanted to test locally on your Windows machine and do not have a
UCRT version of R-devel yet, please uninstall your old version of
R-devel, delete the old library used with that, install a new UCRT
version of R-devel , and install Rtools42. You can keep Rtools40
installed if you need it with R 4.1 or earlier.
Currently, the new R-devel can be downloaded from
https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.r-project.org%2Fnosvn%2Fwinutf8%2Fucrt3%2Fweb%2Frdevel.html&amp;data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=0hCwONzLmcW0GIXNqiOZQEIuhNA%2BjHhQvXsofs8J98o%3D&amp;reserved=0
And Rtools42 from
https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fwww.r-project.org%2Fnosvn%2Fwinutf8%2Fucrt3%2Fweb%2Frtools.html&amp;data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=WLWLbOyQKbaYz8gkfKz2sqoGknjIOtl1aGAhUF%2Bpylg%3D&amp;reserved=0
If you end up testing locally, you can use R_INSTALL_TIME_PATCHES
environment variable to disable the automated patching, see the "howto"
document above. That way you could also see what the original issue was
causing.
If you wanted to find libraries to link for yourself, e.g. in a newer
version of your package, please look for "Using findLinkingOrder with
Rtools42 (tiff package example)" in the "howto" document above. I
created the patches for you manually before we finished this script, so
you may be able to create a shorter version using it, but - it's
probably not worth the effort.
If you wanted to try in a virtual machine, but did not have a license,
you can use also an automated setup of a free trial VM from
https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fdeveloper.r-project.org%2FBlog%2Fpublic%2F2021%2F03%2F18%2Fvirtual-windows-machine-for-checking-r-packages&amp;data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&amp;sdata=aFFQYuC9CoBwBiLgZHi8N3yUnSiHu5Xtdqb2YBiMIHQ%3D&amp;reserved=0
(but that needs a very good and un-metered network connection to install)
Please let us know if you have any questions.
Thanks,
Tomas & Uwe
```
## v3.3.1 - Submission 1 - (October 27, 2021)
### CRAN response
Accepted to CRAN on October 30, 2021.
CRAN completed its checks and preparation of binaries on November 6, 2021.
### Maintainer Notes
Submitted v3.3.1 to CRAN, with the following fixes for the issues that caused CRAN to reject v3.3.0 and archive the package:
* https://github.com/lightgbm-org/LightGBM/pull/4673
* https://github.com/lightgbm-org/LightGBM/pull/4714
Submitted with the following comment:
> This submission contains {lightgbm} 3.3.1.
> Per CRAN's policies, I am submitting on it on behalf of the project's maintainer (Yu Shi), with his permission (https://github.com/lightgbm-org/LightGBM/pull/4715#issuecomment-952537783).
> {lightgbm} was removed from CRAN on October 25, 2021 due to issues detected in the gcc-ASAN and clang-ASAN checks. To the best of our knowledge, we believe this release fixes those issues. We have introduced automated testing that we believe faithfully reproduces CRAN's tests with sanitizers (https://github.com/lightgbm-org/LightGBM/pull/4678).
> Thank you very much for your time and consideration.
Progress on the submission was tracked in https://github.com/lightgbm-org/LightGBM/issues/4713.
## v3.3.0 - Submission 1 - (October 8, 2021)
### CRAN response
`{lightgbm}` was removed from CRAN entirely on October 25, 2021.
On October 12, 2021, maintainers received the following message from CRAN (ripley@stats.ox.ac.uk):
> Dear maintainer,
> Please see the problems shown on https://cran.r-project.org/web/checks/check_results_lightgbm.html
> Please correct before 2021-10-25 to safely retain your package on CRAN.
> Do remember to look at the 'Additional issues'.
> The CRAN Team
We failed to produce a new submission prior to that date, so the package was removed entirely.
See https://github.com/lightgbm-org/LightGBM/issues/4713 for additional background and links explaining the specific failed CRAN checks.
### Maintainer Notes
In this submission, we attempted to switch the maintainer of the package (in the CRAN official sense) from Guolin Ke to Yu Shi.
Did this by adding a note in the CRAN submission web form explaining Guolin's departure from Microsoft.
## v3.2.1 - Submission 1 - (April 12, 2021)
### CRAN response
Accepted to CRAN.
### Maintainer Notes
## v3.2.0 - Submission 1 - (March 22, 2021)
### CRAN response
Package is failing checks in the `r-devel-linux-x86_64-debian-clang` environment (described [here](https://cran.r-project.org/web/checks/check_flavors.html#r-devel-linux-x86_64-debian-clang)). Specifically, one unit test on the use of non-ASCII feature names in `Booster$dump_model()` fails.
> Apparently your package fails its checks in a strict Latin-1* locale,
e.g. under Linux using LANG=en_US.iso88591 (see the debian-clang
results).
> Please correct before 2021-04-21 to safely retain your package on CRAN.
### Maintainer Notes
Submitted a version 3.2.1 to correct the errors noted.
## v3.1.1 - Submission 1 - (December 7, 2020)
### CRAN response
Accepted to CRAN, December 8.
### Maintainer Notes
Submitted a fix to 3.1.0 that skips some learning-to-rank tests on 32-bit Windows.
## v3.1.0 - Submission 1 - (November 15, 2020)
### CRAN response
Accepted to CRAN, November 18.
On November 21, found out that the CRAN's `r-oldrel-windows-ix86+x86_64` check was failing, with an issue similar to the one faced on Solaris and fixed in https://github.com/lightgbm-org/LightGBM/pull/3534.
CRAN did not ask for a re-submission, but this was fixed in 3.1.1.
### Maintainer Notes
This package was submitted with the following information in the "optional comments" box.
```text
Hello,
I'm submitting {lightgbm} 3.1.0 on behalf of the maintainer, Guolin Ke. I am a co-author on the package, and he has asked me to handle this submission. We saw in https://cran.r-project.org/web/packages/policies.html#Submission that this is permitted.
{lightgbm} was removed from CRAN in October for issues found by valgrind checks. We have invested significant effort in addressing those issues and creating an automatic test that tries to replicate CRAN's valgrind checks: https://github.com/lightgbm-org/LightGBM/blob/742d72f8bb051105484fd5cca11620493ffb0b2b/.github/workflows/r_valgrind.yml.
We see two warnings from valgrind that we believe are not problematic.
==2063== Conditional jump or move depends on uninitialised value(s)
==2063== at 0x49CF138: gregexpr_Regexc (grep.c:2439)
==2063== by 0x49D1F13: do_regexpr (grep.c:3100)
==2063== by 0x49A0058: bcEval (eval.c:7121)
==2063== by 0x498B67F: Rf_eval (eval.c:727)
==2063== by 0x498E414: R_execClosure (eval.c:1895)
==2063== by 0x498E0C7: Rf_applyClosure (eval.c:1821)
==2063== by 0x499FC8C: bcEval (eval.c:7089)
==2063== by 0x498B67F: Rf_eval (eval.c:727)
==2063== by 0x498B1CB: forcePromise (eval.c:555)
==2063== by 0x49963AB: FORCE_PROMISE (eval.c:5142)
==2063== by 0x4996566: getvar (eval.c:5183)
==2063== by 0x499D1A5: bcEval (eval.c:6873)
==2063== Uninitialised value was created by a stack allocation
==2063== at 0x49CEC37: gregexpr_Regexc (grep.c:2369)
This seems to be related to R itself and not any code in {lightgbm}.
==2063== 336 bytes in 1 blocks are possibly lost in loss record 153 of 2,709
==2063== at 0x483DD99: calloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==2063== by 0x40149CA: allocate_dtv (dl-tls.c:286)
==2063== by 0x40149CA: _dl_allocate_tls (dl-tls.c:532)
==2063== by 0x5702322: allocate_stack (allocatestack.c:622)
==2063== by 0x5702322: pthread_create@@GLIBC_2.2.5 (pthread_create.c:660)
==2063== by 0x56D0DDA: ??? (in /usr/lib/x86_64-linux-gnu/libgomp.so.1.0.0)
==2063== by 0x56C88E0: GOMP_parallel (in /usr/lib/x86_64-linux-gnu/libgomp.so.1.0.0)
==2063== by 0x1544D29C: LGBM_DatasetCreateFromCSC (c_api.cpp:1286)
==2063== by 0x1546F980: LGBM_DatasetCreateFromCSC_R (lightgbm_R.cpp:91)
==2063== by 0x4941E2F: R_doDotCall (dotcode.c:634)
==2063== by 0x494CCC6: do_dotcall (dotcode.c:1281)
==2063== by 0x499FB01: bcEval (eval.c:7078)
==2063== by 0x498B67F: Rf_eval (eval.c:727)
==2063== by 0x498E414: R_execClosure (eval.c:1895)
We believe this is a false positive, and related to a misunderstanding between valgrind and openmp (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=36298).
We have also added automated tests with ASAN/UBSAN to our testing setup, and have checked the package on Solaris 10 and found no issues.
Thanks for your time and consideration.
```
## v3.0.0.2 - Submission 1 - (September 29, 2020)
### CRAN response
First response was a message talking about failing checks on 3.0.0.
```text
package lightgbm_3.0.0.2.tar.gz has been auto-processed.
The auto-check found additional issues for the last version released on CRAN:
gcc-UBSAN <link>
valgrind <link>
CRAN incoming checks do not test for these additional issues and you will need an appropriately instrumented build of R to reproduce these.
Hence please reply-all and explain: Have these been fixed?
Please correct before 2020-10-05 to safely retain your package on CRAN.
There is still a valgrind error. This did not happen when tested on
submission, but the tests did run until timeout at 4 hours. When you
write illegally, corruption is common.
Illegal writes are serious errors.
```
Then in later responses to email correspondence with CRAN, CRAN expressed frustration with the number of failed submission and banned this package from new submissions for a month.
The content of that frustrated message was regrettable and it does not need to be preserved forever in this file.
### Maintainer Notes
The 3.0.0.x series is officially not making it to CRAN. We will wait until November, and try again.
Detailed plan about what will be tried before November 2020 to increase the likelihood of success for that package: https://github.com/lightgbm-org/LightGBM/pull/3338#issuecomment-702756840.
## v3.0.0.1 - Submission 1 - (September 24, 2020)
### CRAN response
```text
Thanks, we see:
Still lots of alignment errors, such as
lightgbm.Rcheck/tests/testthat.Rout:io/dataset_loader.cpp:340:59:
runtime error: reference binding to misaligned address 0x7f51fefad81e for type 'const value_type', which requires 4 byte alignment
lightgbm.Rcheck/tests/testthat.Rout:/usr/include/c++/10/bits/stl_vector.h:1198:21:
runtime error: reference binding to misaligned address 0x7f51fefad81e for type 'const int', which requires 4 byte alignment lightgbm.Rcheck/tests/testthat.Rout:/usr/include/c++/10/bits/vector.tcc:449:28:runtime
error: reference binding to misaligned address 0x7f51fefad81e for type 'const type', which requires 4 byte alignment
lightgbm.Rcheck/tests/testthat.Rout:/usr/include/c++/10/bits/move.h:77:36:
runtime error: reference binding to misaligned address 0x7f51fefad81e for type 'const int', which requires 4 byte alignment
lightgbm.Rcheck/tests/testthat.Rout:/usr/include/c++/10/bits/alloc_traits.h:512:17:
runtime error: reference binding to misaligned address 0x7f51fefad81e for type 'const type', which requires 4 byte alignment
Please fix and resubmit.
```
### Maintainer Notes
Ok, these are the notes from the UBSAN tests. Was able to reproduce them with https://github.com/lightgbm-org/LightGBM/pull/3338#issuecomment-700399862, and they were fixed in https://github.com/lightgbm-org/LightGBM/pull/3415.
Struggling to replicate the valgrind result (running `R CMD check --use-valgrind` returns no issues), so trying submission again. Hoping that the fixes for mis-alignment fix the other errors too.
## v3.0.0 - Submission 6 - (September 24, 2020)
### CRAN response
Failing pre-checks.
### `R CMD check` results
```text
* checking CRAN incoming feasibility ... WARNING
Maintainer: Guolin Ke <guolin.ke@microsoft.com>
Insufficient package version (submitted: 3.0.0, existing: 3.0.0)
Days since last update: 4
```
### Maintainer Notes
Did not think the version needed to be incremented if submitting a package in response to CRAN saying "you are failing checks and will be kicked off if you don't fix it", but I guess you do!
This can be fixed by just re-submitting but with the version changed from `3.0.0` to `3.0.0.1`.
## v3.0.0 - Submission 5 - (September 11, 2020)
### CRAN Response
Accepted to CRAN!
Please correct the problems below before 2020-10-05 to safely retain your package on CRAN:
```text
checking installed package size ... NOTE
installed size is 49.7Mb
sub-directories of 1Mb or more:
libs 49.1Mb
"network/socket_wrapper.hpp", line 30: Error: Could not open include file<ifaddrs.h>.
"network/socket_wrapper.hpp", line 216: Error: The type "ifaddrs" is incomplete.
"network/socket_wrapper.hpp", line 217: Error: The type "ifaddrs" is incomplete.
"network/socket_wrapper.hpp", line 220: Error: The type "ifaddrs" is incomplete.
"network/socket_wrapper.hpp", line 222: Error: The type "ifaddrs" is incomplete.
"network/socket_wrapper.hpp", line 214: Error: The function "getifaddrs" must have a prototype.
"network/socket_wrapper.hpp", line 228: Error: The function "freeifaddrs" must have a prototype.
"network/linkers_socket.cpp", line 76: Warning: A non-POD object of type "std::chrono::duration<double, std::ratio<1, 1000>>" passed as a variable argument to function "static LightGBM::Log::Info(const char*, ...)".
7 Error(s) and 1 Warning(s) detected.
*** Error code 2
make: Fatal error: Command failed for target `network/linkers_socket.o'
Current working directory /tmp/RtmpNfaavG/R.INSTALL40a84f70130a/lightgbm/src
ERROR: compilation failed for package lightgbm
* removing /home/ripley/R/Lib32/lightgbm
```
### Maintainer Notes
Added a patch that `psutil` has used to fix missing `ifaddrs.h` on Solaris 10: https://github.com/lightgbm-org/LightGBM/issues/629#issuecomment-665091451.
## v3.0.0 - Submission 4 - (September 4, 2020)
### CRAN Response
> Thanks, if the running time is the only reason to wrap the examples in
\donttest, please replace \donttest by \donttest (\donttest examples are
not executed in the CRAN checks).
> Please replace cat() by message() or warning() in your functions (except
for print() and summary() functions). Messages and warnings can be
suppressed if needed.
> Missing Rd-tags:
lightgbm/man/dimnames.lgb.Dataset.Rd: \value
lightgbm/man/lgb.Dataset.construct.Rd: \value
lightgbm/man/lgb.prepare.Rd: \value
...
> Please add the tag and explain in detail the returned objects.
### Maintainer Notes
Responded to CRAN with the following:
All examples have been wrapped with `\donttest` as requested. We have replied to Swetlana Herbrandt asking for clarification on the donttest news item in the R 4.0.2 changelog (https://cran.r-project.org/doc/manuals/r-devel/NEWS.html).
All uses of `cat()` have been replaced with `print()`. We chose `print()` over `message()` because it's important that they be written to stdout alongside all the other logs coming from the library's C++ code. `message()` and `warning()` write to stderr.
All exported objects now have `\value{}` statements in their documentation files in `man/`.
**We also replied directly to CRAN's feedback email**
> Swetlana,
> Thank you for your comments. I've just created a new submission that I believe addresses them.
> Can you help us understand something? In your message you said "\donttest examples are
not executed in the CRAN checks)", but in https://cran.r-project.org/doc/manuals/r-devel/NEWS.html we see the following:
> > "`R CMD check --as-cran` now runs \donttest examples (which are run by example()) instead of instructing the tester to do so. This can be temporarily circumvented during development by setting environment variable `_R_CHECK_DONTTEST_EXAMPLES_` to a false value."
> Could you help us understand how both of those statements can be true?
## v3.0.0 - Submission 3 - (August 29, 2020)
### CRAN response
* Please write references in the description of the DESCRIPTION file in
the form
- authors (year) doi:...
- authors (year) arXiv:...
- authors (year, ISBN:...)
* if those are not available: authors (year) https:... with no space after 'doi:', 'arXiv:', 'https:' and angle brackets for auto-linking.
* (If you want to add a title as well please put it in quotes: "Title")
* \donttest{} should only be used if the example really cannot be executed (e.g. because of missing additional software, missing API keys, ...) by the user. That's why wrapping examples in \donttest{} adds the comment ("# Not run:") as a warning for the user. Does not seem necessary. Please unwrap the examples if they are executable in < 5 sec, or replace
\donttest{} with \donttest{}.
* Please do not modify the global environment (e.g. by using <<-) in your
functions. This is not allowed by the CRAN policies.
* Please always add all authors, contributors and copyright holders in the Authors@R field with the appropriate roles. From CRAN policies you agreed to: "The ownership of copyright and intellectual property rights of all components of the package must be clear and unambiguous (including from the authors specification in the DESCRIPTION file). Where code is copied (or derived) from the work of others (including from R itself), care must be taken that any copyright/license statements are preserved and authorship is not misrepresented." e.g.: Microsoft Corporation, Dropbox Inc. Please explain in the submission comments what you did about this issue.
Please fix and resubmit
### Maintainer Notes
Responded to CRAN with the following:
The paper citation has been adjusted as requested. We were using 'glmnet' as a guide on how to include the URL but maybe they are no longer in compliance with CRAN policies: https://github.com/cran/glmnet/blob/b1a4b50de01e0cd24343959d7cf86452bac17b26/DESCRIPTION
All authors from the original LightGBM paper have been added to Authors@R as `"aut"`. We have also added Microsoft and DropBox, Inc. as `"cph"` (copyright holders). These roles were chosen based on the guidance in https://journal.r-project.org/archive/2012/RJ-2012-009/index.html.
lightgbm's code does use `<<-`, but it does not modify the global environment. The uses of `<<-` in R/lgb.interprete.R and R/callback.R are in functions which are called in an environment created by the lightgbm functions that call them, and this operator is used to reach one level up into the calling function's environment.
We chose to wrap our examples in `\donttest{}` because we found, through testing on https://r-hub.github.io/rhub/ and in our own continuous integration environments, that their run time varies a lot between platforms, and we cannot guarantee that all examples will run in under 5 seconds. We intentionally chose `\donttest{}` over `\donttest{}` because this item in the R 4.0.0 changelog (https://cran.r-project.org/doc/manuals/r-devel/NEWS.html) seems to indicate that \donttest will be ignored by CRAN's automated checks:
> "`R CMD check --as-cran` now runs \donttest examples (which are run by example()) instead of instructing the tester to do so. This can be temporarily circumvented during development by setting environment variable `_R_CHECK_DONTTEST_EXAMPLES_` to a false value."
We run all examples with `R CMD check --as-cran --run-dontrun` in our continuous integration tests on every commit to the package, so we have high confidence that they are working correctly.
## v3.0.0 - Submission 2 - (August 28, 2020)
### CRAN response
Failing pre-checks.
### `R CMD check` results
* Debian: 2 NOTEs
```text
* checking CRAN incoming feasibility ... NOTE
Maintainer: 'Guolin Ke <guolin.ke@microsoft.com>'
New submission
Possibly mis-spelled words in DESCRIPTION:
Guolin (13:52)
Ke (13:48)
LightGBM (14:20)
al (13:62)
et (13:59)
* checking top-level files ... NOTE
Non-standard files/directories found at top level:
'docs' 'lightgbm-hex-logo.png' 'lightgbm-hex-logo.svg'
```
* Windows: 2 NOTEs
```text
* checking CRAN incoming feasibility ... NOTE
Maintainer: 'Guolin Ke <guolin.ke@microsoft.com>'
New submission
Possibly mis-spelled words in DESCRIPTION:
Guolin (13:52)
Ke (13:48)
LightGBM (14:20)
al (13:62)
et (13:59)
* checking top-level files ... NOTE
Non-standard files/directories found at top level:
'docs' 'lightgbm-hex-logo.png' 'lightgbm-hex-logo.svg'
```
### Maintainer Notes
We should tell them the misspellings note is a false positive.
For the note about included files, that is my fault. I had extra files laying around when I generated the package. I'm surprised to see `docs/` in that list, since it is ignored in `.Rbuildignore`. I even tested that with [the exact code Rbuildignore uses](https://github.com/wch/r-source/blob/9d13622f41cfa0f36db2595bd6a5bf93e2010e21/src/library/tools/R/build.R#L85). For now, I added `rm -r docs/` to `build-cran-package.sh`. We can figure out what is happening with `.Rbuildignore` in the future, but it shouldn't block a release.
## v3.0.0 - Submission 1 - (August 24, 2020)
NOTE: 3.0.0-1 was never released to CRAN. CRAN was on vacation August 14-24, 2020, and in that time version 3.0.0-1 (a release candidate) became 3.0.0.
### CRAN response
> Please only ship the CRAN template for the MIT license.
> Is there some reference about the method you can add in the Description field in the form Authors (year) doi:.....?
> Please fix and resubmit.
### `R CMD check` results
* Debian: 1 NOTE
```text
* checking CRAN incoming feasibility ... NOTE
Maintainer: Guolin Ke <guolin.ke@microsoft.com>
New submission
License components with restrictions and base license permitting such:
MIT + file LICENSE
```
* Windows: 1 NOTE
```text
* checking CRAN incoming feasibility ... NOTE
Maintainer: 'Guolin Ke <guolin.ke@microsoft.com>'
New submission
License components with restrictions and base license permitting such:
MIT + file LICENSE
```
### Maintainer Notes
Tried updating `LICENSE` file to this template:
```yaml
YEAR: 2016
COPYRIGHT HOLDER: Microsoft Corporation
```
Added a citation and link for [the main paper](https://proceedings.neurips.cc/paper/2017/hash/6449f44a102fde848669bdd9eb6b76fa-Abstract.html) in `DESCRIPTION`.
## v3.0.0-1 - Submission 3 - (August 12, 2020)
### CRAN response
Failing pre-checks.
### `R CMD check` results
* Debian: 1 NOTE
```text
* checking CRAN incoming feasibility ... NOTE
Maintainer: Guolin Ke <guolin.ke@microsoft.com>
New submission
License components with restrictions and base license permitting such:
MIT + file LICENSE
```
* Windows: 1 ERROR, 1 NOTE
```text
* checking CRAN incoming feasibility ... NOTE
Maintainer: Guolin Ke <guolin.ke@microsoft.com>
New submission
License components with restrictions and base license permitting such:
MIT + file LICENSE
** running tests for arch 'i386' ... [9s] ERROR
Running 'testthat.R' [8s]
Running the tests in 'tests/testthat.R' failed.
Complete output:
> library(testthat)
> library(lightgbm)
Loading required package: R6
>
> test_check(
+ package = "lightgbm"
+ , stop_on_failure = TRUE
+ , stop_on_warning = FALSE
+ )
-- 1. Error: predictions do not fail for integer input (@test_Predictor.R#7) --
lgb.Dataset.construct: cannot create Dataset handle
Backtrace:
1. lightgbm::lgb.train(...)
2. data$construct()
```
### Maintainer Notes
The "checking CRAN incoming feasibility" NOTE can be safely ignored. It only shows up the first time you submit a package to CRAN.
So the only thing I see broken right now is the test error on 32-bit Windows. This is documented in https://github.com/lightgbm-org/LightGBM/issues/3187.
## v3.0.0-1 - Submission 2 - (August 10, 2020)
### CRAN response
Failing pre-checks.
### `R CMD check` results
* Debian: 2 NOTEs
```text
* checking CRAN incoming feasibility ... NOTE
Maintainer: Guolin Ke <guolin.ke@microsoft.com>
New submission
License components with restrictions and base license permitting such:
MIT + file LICENSE
Non-standard files/directories found at top level:
cran-comments.md docs
```
* Windows: 1 ERROR, 2 NOTEs
```text
* checking CRAN incoming feasibility ... NOTE
Maintainer: 'Guolin Ke <guolin.ke@microsoft.com>'
New submission
License components with restrictions and base license permitting such:
MIT + file LICENSE
* checking top-level files ... NOTE
Non-standard files/directories found at top level:
'cran-comments.md' 'docs'
** checking whether the package can be loaded ... ERROR
Loading this package had a fatal error status code 1
Loading log:
Error: package 'lightgbm' is not installed for 'arch = i386'
Execution halted
```
### Maintainer Notes
Seems removing `Biarch` field didn't work. Noticed this in the install logs:
> Warning: this package has a non-empty 'configure.win' file, so building only the main architecture
Tried adding `Biarch: true` to `DESCRIPTION` to overcome this.
NOTE about non-standard files was the result of a mistake in `.Rbuildignore` syntax, and something strange with how `cran-comments.md` line in `.Rbuildignore` was treated. Updated `.Rbuildignore` and added an `rm cran-comments.md` to `build-cran-package.sh`.
## v3.0.0-1 - Submission 1 - (August 9, 2020)
### CRAN response
Failing pre-checks.
### `R CMD check` results
* Debian: 1 NOTE
```text
Possibly mis-spelled words in DESCRIPTION:
LightGBM (12:88, 19:41, 20:60, 20:264)
```
* Windows: 1 ERROR, 1 NOTE
```text
Possibly mis-spelled words in DESCRIPTION:
LightGBM (12:88, 19:41, 20:60, 20:264)
** checking whether the package can be loaded ... ERROR
Loading this package had a fatal error status code 1
Loading log:
Error: package 'lightgbm' is not installed for 'arch = i386'
Execution halted
```
### Maintainer Notes
Thought the issue on Windows was caused by `Biarch: false` in `DESCRIPTION`. Removed `Biarch` field.
Thought the "misspellings" issue could be resolved by adding single quotes around LightGBM, like `'LightGBM'`.
+10
View File
@@ -0,0 +1,10 @@
basic_walkthrough Basic feature walkthrough
boost_from_prediction Boosting from existing prediction
categorical_features_rules Categorical Feature Preparation with Rules
cross_validation Cross Validation
early_stopping Early Stop in training
efficient_many_training Efficiency for Many Model Trainings
multiclass Multiclass training/prediction
multiclass_custom_objective Multiclass with Custom Objective Function
leaf_stability Leaf (in)Stability example
weight_param Weight-Parameter adjustment relationship
+153
View File
@@ -0,0 +1,153 @@
library(lightgbm)
# We load in the agaricus dataset
# In this example, we are aiming to predict whether a mushroom is edible
data(agaricus.train, package = "lightgbm")
data(agaricus.test, package = "lightgbm")
train <- agaricus.train
test <- agaricus.test
# The loaded data is stored in sparseMatrix, and label is a numeric vector in {0,1}
class(train$label)
class(train$data)
# Set parameters for model training
train_params <- list(
num_leaves = 4L
, learning_rate = 1.0
, objective = "binary"
, nthread = 2L
)
#--------------------Basic Training using lightgbm----------------
# This is the basic usage of lightgbm you can put matrix in data field
# Note: we are putting in sparse matrix here, lightgbm naturally handles sparse input
# Use sparse matrix when your feature is sparse (e.g. when you are using one-hot encoding vector)
print("Training lightgbm with sparseMatrix")
bst <- lightgbm(
data = train$data
, params = train_params
, label = train$label
, nrounds = 2L
)
# Alternatively, you can put in dense matrix, i.e. basic R-matrix
print("Training lightgbm with Matrix")
bst <- lightgbm(
data = as.matrix(train$data)
, params = train_params
, label = train$label
, nrounds = 2L
)
# You can also put in lgb.Dataset object, which stores label, data and other meta datas needed for advanced features
print("Training lightgbm with lgb.Dataset")
dtrain <- lgb.Dataset(
data = train$data
, label = train$label
)
bst <- lightgbm(
data = dtrain
, params = train_params
, nrounds = 2L
)
# Verbose = 0,1,2
print("Train lightgbm with verbose 0, no message")
bst <- lightgbm(
data = dtrain
, params = train_params
, nrounds = 2L
, verbose = 0L
)
print("Train lightgbm with verbose 1, print evaluation metric")
bst <- lightgbm(
data = dtrain
, params = train_params
, nrounds = 2L
, verbose = 1L
)
print("Train lightgbm with verbose 2, also print information about tree")
bst <- lightgbm(
data = dtrain
, params = train_params
, nrounds = 2L
, verbose = 2L
)
# You can also specify data as file path to a LibSVM/TCV/CSV format input
# Since we do not have this file with us, the following line is just for illustration
# bst <- lightgbm(
# data = "agaricus.train.svm"
# , num_leaves = 4L
# , learning_rate = 1.0
# , nrounds = 2L
# , objective = "binary"
# )
#--------------------Basic prediction using lightgbm--------------
# You can do prediction using the following line
# You can put in Matrix, sparseMatrix, or lgb.Dataset
pred <- predict(bst, test$data)
err <- mean(as.numeric(pred > 0.5) != test$label)
print(paste("test-error=", err))
#--------------------Save and load models-------------------------
# Save model to binary local file
lgb.save(bst, "lightgbm.model")
# Load binary model to R
bst2 <- lgb.load("lightgbm.model")
pred2 <- predict(bst2, test$data)
# pred2 should be identical to pred
print(paste("sum(abs(pred2-pred))=", sum(abs(pred2 - pred))))
#--------------------Advanced features ---------------------------
# To use advanced features, we need to put data in lgb.Dataset
dtrain <- lgb.Dataset(data = train$data, label = train$label, free_raw_data = FALSE)
dtest <- lgb.Dataset.create.valid(dtrain, data = test$data, label = test$label)
#--------------------Using validation set-------------------------
# valids is a list of lgb.Dataset, each of them is tagged with name
valids <- list(train = dtrain, test = dtest)
# To train with valids, use lgb.train, which contains more advanced features
# valids allows us to monitor the evaluation result on all data in the list
print("Train lightgbm using lgb.train with valids")
bst <- lgb.train(
data = dtrain
, params = train_params
, nrounds = 2L
, valids = valids
)
# We can change evaluation metrics, or use multiple evaluation metrics
print("Train lightgbm using lgb.train with valids, watch logloss and error")
bst <- lgb.train(
data = dtrain
, params = train_params
, nrounds = 2L
, valids = valids
, eval = c("binary_error", "binary_logloss")
)
# lgb.Dataset can also be saved using lgb.Dataset.save
lgb.Dataset.save(dtrain, "dtrain.buffer")
# To load it in, simply call lgb.Dataset
dtrain2 <- lgb.Dataset("dtrain.buffer")
bst <- lgb.train(
data = dtrain2
, params = train_params
, nrounds = 2L
, valids = valids
)
# information can be extracted from lgb.Dataset using get_field()
label <- get_field(dtest, "label")
pred <- predict(bst, test$data)
err <- as.numeric(sum(as.integer(pred > 0.5) != label)) / length(label)
print(paste("test-error=", err))
+38
View File
@@ -0,0 +1,38 @@
library(lightgbm)
# Load in the agaricus dataset
data(agaricus.train, package = "lightgbm")
data(agaricus.test, package = "lightgbm")
dtrain <- lgb.Dataset(agaricus.train$data, label = agaricus.train$label)
dtest <- lgb.Dataset.create.valid(dtrain, data = agaricus.test$data, label = agaricus.test$label)
valids <- list(eval = dtest, train = dtrain)
#--------------------Advanced features ---------------------------
# advanced: start from an initial base prediction
print("Start running example to start from an initial prediction")
# Train lightgbm for 1 round
param <- list(
num_leaves = 4L
, learning_rate = 1.0
, nthread = 2L
, objective = "binary"
)
bst <- lgb.train(param, dtrain, 1L, valids = valids)
# Note: we need the margin value instead of transformed prediction in set_init_score
ptrain <- predict(bst, agaricus.train$data, type = "raw")
ptest <- predict(bst, agaricus.test$data, type = "raw")
# set the init_score property of dtrain and dtest
# base margin is the base prediction we will boost from
set_field(dtrain, "init_score", ptrain)
set_field(dtest, "init_score", ptest)
print("This is result of boost from initial prediction")
bst <- lgb.train(
params = param
, data = dtrain
, nrounds = 5L
, valids = valids
)
+100
View File
@@ -0,0 +1,100 @@
# Here we are going to try training a model with categorical features
# Load libraries
library(data.table)
library(lightgbm)
# Load data and look at the structure
#
# Classes 'data.table' and 'data.frame': 4521 obs. of 17 variables:
# $ age : int 30 33 35 30 59 35 36 39 41 43 ...
# $ job : chr "unemployed" "services" "management" "management" ...
# $ marital : chr "married" "married" "single" "married" ...
# $ education: chr "primary" "secondary" "tertiary" "tertiary" ...
# $ default : chr "no" "no" "no" "no" ...
# $ balance : int 1787 4789 1350 1476 0 747 307 147 221 -88 ...
# $ housing : chr "no" "yes" "yes" "yes" ...
# $ loan : chr "no" "yes" "no" "yes" ...
# $ contact : chr "cellular" "cellular" "cellular" "unknown" ...
# $ day : int 19 11 16 3 5 23 14 6 14 17 ...
# $ month : chr "oct" "may" "apr" "jun" ...
# $ duration : int 79 220 185 199 226 141 341 151 57 313 ...
# $ campaign : int 1 1 1 4 1 2 1 2 2 1 ...
# $ pdays : int -1 339 330 -1 -1 176 330 -1 -1 147 ...
# $ previous : int 0 4 1 0 0 3 2 0 0 2 ...
# $ poutcome : chr "unknown" "failure" "failure" "unknown" ...
# $ y : chr "no" "no" "no" "no" ...
data(bank, package = "lightgbm")
str(bank)
# We are dividing the dataset into two: one train, one validation
bank_train <- bank[1L:4000L, ]
bank_test <- bank[4001L:4521L, ]
# We must now transform the data to fit in LightGBM
# For this task, we use lgb.convert_with_rules
# The function transforms the data into a fittable data
#
# Classes 'data.table' and 'data.frame': 521 obs. of 17 variables:
# $ age : int 53 36 58 26 34 55 55 34 41 38 ...
# $ job : num 1 10 10 9 10 2 2 3 3 4 ...
# $ marital : num 1 2 1 3 3 2 2 2 1 1 ...
# $ education: num 2 2 2 2 2 1 2 3 2 2 ...
# $ default : num 1 1 1 1 1 1 1 1 1 1 ...
# $ balance : int 26 191 -123 -147 179 1086 471 105 1588 70 ...
# $ housing : num 2 1 1 1 1 2 2 2 2 1 ...
# $ loan : num 1 1 1 1 1 1 1 1 2 1 ...
# $ contact : num 1 1 1 3 1 1 3 3 3 1 ...
# $ day : int 7 31 5 4 19 6 30 28 20 27 ...
# $ month : num 9 2 2 7 2 9 9 9 7 11 ...
# $ duration : int 56 69 131 95 294 146 58 249 10 255 ...
# $ campaign : int 1 1 2 2 3 1 2 2 8 3 ...
# $ pdays : int 359 -1 -1 -1 -1 272 -1 -1 -1 148 ...
# $ previous : int 1 0 0 0 0 2 0 0 0 1 ...
# $ poutcome : num 1 4 4 4 4 1 4 4 4 3 ...
# $ y : num 1 1 1 1 1 1 1 1 1 2 ...
bank_rules <- lgb.convert_with_rules(data = bank_train)
bank_train <- bank_rules$data
bank_test <- lgb.convert_with_rules(data = bank_test, rules = bank_rules$rules)$data
str(bank_test)
# Remove 1 to label because it must be between 0 and 1
bank_train$y <- bank_train$y - 1L
bank_test$y <- bank_test$y - 1L
# Data input to LightGBM must be a matrix, without the label
my_data_train <- as.matrix(bank_train[, 1L:16L, with = FALSE])
my_data_test <- as.matrix(bank_test[, 1L:16L, with = FALSE])
# Creating the LightGBM dataset with categorical features
# The categorical features can be passed to lgb.train to not copy and paste a lot
dtrain <- lgb.Dataset(
data = my_data_train
, label = bank_train$y
, categorical_feature = c(2L, 3L, 4L, 5L, 7L, 8L, 9L, 11L, 16L)
)
dtest <- lgb.Dataset.create.valid(
dtrain
, data = my_data_test
, label = bank_test$y
)
# We can now train a model
params <- list(
objective = "binary"
, metric = "l2"
, min_data = 1L
, learning_rate = 0.1
, min_hessian = 1.0
, max_depth = 2L
)
model <- lgb.train(
params = params
, data = dtrain
, nrounds = 100L
, valids = list(train = dtrain, valid = dtest)
)
# Try to find split_feature: 11
# If you find it, it means it used a categorical feature in the first tree
lgb.dump(model, num_iteration = 1L)
+72
View File
@@ -0,0 +1,72 @@
library(lightgbm)
# load in the agaricus dataset
data(agaricus.train, package = "lightgbm")
data(agaricus.test, package = "lightgbm")
dtrain <- lgb.Dataset(agaricus.train$data, label = agaricus.train$label)
dtest <- lgb.Dataset.create.valid(dtrain, data = agaricus.test$data, label = agaricus.test$label)
nrounds <- 2L
param <- list(
num_leaves = 4L
, learning_rate = 1.0
, objective = "binary"
)
print("Running cross validation")
# Do cross validation, this will print result out as
# [iteration] metric_name:mean_value+std_value
# std_value is standard deviation of the metric
lgb.cv(
param
, dtrain
, nrounds
, nfold = 5L
, eval = "binary_error"
)
print("Running cross validation, disable standard deviation display")
# do cross validation, this will print result out as
# [iteration] metric_name:mean_value+std_value
# std_value is standard deviation of the metric
lgb.cv(
param
, dtrain
, nrounds
, nfold = 5L
, eval = "binary_error"
, showsd = FALSE
)
# You can also do cross validation with customized loss function
print("Running cross validation, with cutomsized loss function")
logregobj <- function(preds, dtrain) {
labels <- get_field(dtrain, "label")
preds <- 1.0 / (1.0 + exp(-preds))
grad <- preds - labels
hess <- preds * (1.0 - preds)
return(list(grad = grad, hess = hess))
}
# User-defined evaluation function returns a pair (metric_name, result, higher_better)
# NOTE: when you do customized loss function, the default prediction value is margin
# This may make built-in evaluation metric calculate wrong results
# For example, we are doing logistic loss, the prediction is score before logistic transformation
# Keep this in mind when you use the customization, and maybe you need write customized evaluation function
evalerror <- function(preds, dtrain) {
labels <- get_field(dtrain, "label")
preds <- 1.0 / (1.0 + exp(-preds))
err <- as.numeric(sum(labels != (preds > 0.5))) / length(labels)
return(list(name = "error", value = err, higher_better = FALSE))
}
# train with customized objective
lgb.cv(
params = param
, data = dtrain
, nrounds = nrounds
, obj = logregobj
, eval = evalerror
, nfold = 5L
)
+51
View File
@@ -0,0 +1,51 @@
library(lightgbm)
# Load in the agaricus dataset
data(agaricus.train, package = "lightgbm")
data(agaricus.test, package = "lightgbm")
dtrain <- lgb.Dataset(agaricus.train$data, label = agaricus.train$label)
dtest <- lgb.Dataset.create.valid(dtrain, data = agaricus.test$data, label = agaricus.test$label)
# Note: for customized objective function, we leave objective as default
# Note: what we are getting is margin value in prediction
# You must know what you are doing
param <- list(
num_leaves = 4L
, learning_rate = 1.0
)
valids <- list(eval = dtest)
num_round <- 20L
# User define objective function, given prediction, return gradient and second order gradient
# This is loglikelihood loss
logregobj <- function(preds, dtrain) {
labels <- get_field(dtrain, "label")
preds <- 1.0 / (1.0 + exp(-preds))
grad <- preds - labels
hess <- preds * (1.0 - preds)
return(list(grad = grad, hess = hess))
}
# User-defined evaluation function returns a pair (metric_name, result, higher_better)
# NOTE: when you do customized loss function, the default prediction value is margin
# This may make built-in evaluation metric calculate wrong results
# For example, we are doing logistic loss, the prediction is score before logistic transformation
# The built-in evaluation error assumes input is after logistic transformation
# Keep this in mind when you use the customization, and maybe you need write customized evaluation function
evalerror <- function(preds, dtrain) {
labels <- get_field(dtrain, "label")
err <- as.numeric(sum(labels != (preds > 0.5))) / length(labels)
return(list(name = "error", value = err, higher_better = FALSE))
}
print("Start training with early Stopping setting")
bst <- lgb.train(
param
, dtrain
, num_round
, valids
, obj = logregobj
, eval = evalerror
, early_stopping_round = 3L
)
+36
View File
@@ -0,0 +1,36 @@
# Efficient training means training without giving up too much RAM
# In the case of many trainings (like 100+ models), RAM will be eaten very quickly
# Therefore, it is essential to know a strategy to deal with such issue
# More results can be found here: https://github.com/lightgbm-org/LightGBM/issues/879#issuecomment-326656580
# Quote: "@Laurae2 Thanks for nice easily reproducible example (unlike mine).
# With reset=FALSE you get after 500 iterations (not 1000): OS reports 27GB usage, while R gc() reports 1.5GB.
# Just doing reset=TRUE will already improve things: OS reports 4.6GB.
# Doing reset=TRUE and calling gc() in the loop will have OS 1.3GB. Thanks for the latest tip."
# Load library
library(lightgbm)
# Generate fictive data of size 1M x 100
set.seed(11111L)
x_data <- matrix(rnorm(n = 100000000L, mean = 0.0, sd = 100.0), nrow = 1000000L, ncol = 100L)
y_data <- rnorm(n = 1000000L, mean = 0.0, sd = 5.0)
# Create lgb.Dataset for training
data <- lgb.Dataset(x_data, label = y_data)
data$construct()
# Loop through a training of 1000 models, please check your RAM on your task manager
# It MUST remain constant (if not increasing very slightly)
gbm <- list()
for (i in 1L:1000L) {
print(i)
gbm[[i]] <- lgb.train(
params = list(objective = "regression")
, data = data
, 1L
, reset_data = TRUE
)
gc(verbose = FALSE)
}
+256
View File
@@ -0,0 +1,256 @@
# We are going to look at how iterating too much might generate observation instability.
# Obviously, we are in a controlled environment, without issues (real rules).
# Do not do this in a real scenario.
library(lightgbm)
# define helper functions for creating plots
# output of `RColorBrewer::brewer.pal(10, "RdYlGn")`, hardcooded here to avoid a dependency
.diverging_palette <- c(
"#A50026", "#D73027", "#F46D43", "#FDAE61", "#FEE08B"
, "#D9EF8B", "#A6D96A", "#66BD63", "#1A9850", "#006837"
)
.prediction_depth_plot <- function(df) {
plot(
x = df$X
, y = df$Y
, type = "p"
, main = "Prediction Depth"
, xlab = "Leaf Bin"
, ylab = "Prediction Probability"
, pch = 19L
, col = .diverging_palette[df$binned + 1L]
)
legend(
"topright"
, title = "bin"
, legend = sort(unique(df$binned))
, pch = 19L
, col = .diverging_palette[sort(unique(df$binned + 1L))]
, cex = 0.7
)
}
.prediction_depth_spread_plot <- function(df) {
plot(
x = df$binned
, xlim = c(0L, 9L)
, y = df$Z
, type = "p"
, main = "Prediction Depth Spread"
, xlab = "Leaf Bin"
, ylab = "Logloss"
, pch = 19L
, col = .diverging_palette[df$binned + 1L]
)
legend(
"topright"
, title = "bin"
, legend = sort(unique(df$binned))
, pch = 19L
, col = .diverging_palette[sort(unique(df$binned + 1L))]
, cex = 0.7
)
}
.depth_density_plot <- function(df) {
plot(
x = density(df$Y)
, xlim = c(min(df$Y), max(df$Y))
, type = "p"
, main = "Depth Density"
, xlab = "Prediction Probability"
, ylab = "Bin Density"
, pch = 19L
, col = .diverging_palette[df$binned + 1L]
)
legend(
"topright"
, title = "bin"
, legend = sort(unique(df$binned))
, pch = 19L
, col = .diverging_palette[sort(unique(df$binned + 1L))]
, cex = 0.7
)
}
# load some data
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
data(agaricus.test, package = "lightgbm")
test <- agaricus.test
dtest <- lgb.Dataset.create.valid(dtrain, test$data, label = test$label)
# setup parameters and we train a model
params <- list(
objective = "regression"
, metric = "l2"
, min_data = 1L
, learning_rate = 0.1
, bagging_fraction = 0.1
, bagging_freq = 1L
, bagging_seed = 1L
)
valids <- list(test = dtest)
model <- lgb.train(
params
, dtrain
, 50L
, valids
)
# We create a data.frame with the following structure:
# X = average leaf of the observation throughout all trees
# Y = prediction probability (clamped to [1e-15, 1-1e-15])
# Z = logloss
# binned = binned quantile of average leaf
new_data <- data.frame(
X = rowMeans(predict(
model
, agaricus.test$data
, type = "leaf"
))
, Y = pmin(
pmax(
predict(model, agaricus.test$data)
, 1e-15
)
, 1.0 - 1e-15
)
)
new_data$Z <- -1.0 * (agaricus.test$label * log(new_data$Y) + (1L - agaricus.test$label) * log(1L - new_data$Y))
new_data$binned <- .bincode(
x = new_data$X
, breaks = quantile(
x = new_data$X
, probs = seq_len(9L) / 10.0
)
, right = TRUE
, include.lowest = TRUE
)
new_data$binned[is.na(new_data$binned)] <- 0L
# We can check the binned content
table(new_data$binned)
# We can plot the binned content
# On the second plot, we clearly notice the lower the bin (the lower the leaf value), the higher the loss
# On the third plot, it is smooth!
.prediction_depth_plot(df = new_data)
.prediction_depth_spread_plot(df = new_data)
.depth_density_plot(df = new_data)
# Now, let's show with other parameters
params <- list(
objective = "regression"
, metric = "l2"
, min_data = 1L
, learning_rate = 1.0
)
model2 <- lgb.train(
params
, dtrain
, 100L
, valids
)
# We create the data structure, but for model2
new_data2 <- data.frame(
X = rowMeans(predict(
model2
, agaricus.test$data
, type = "leaf"
))
, Y = pmin(
pmax(
predict(
model2
, agaricus.test$data
)
, 1e-15
)
, 1.0 - 1e-15
)
)
new_data2$Z <- -1.0 * (agaricus.test$label * log(new_data2$Y) + (1L - agaricus.test$label) * log(1L - new_data2$Y))
new_data2$binned <- .bincode(
x = new_data2$X
, breaks = quantile(
x = new_data2$X
, probs = seq_len(9L) / 10.0
)
, right = TRUE
, include.lowest = TRUE
)
new_data2$binned[is.na(new_data2$binned)] <- 0L
# We can check the binned content
table(new_data2$binned)
# We can plot the binned content
# On the second plot, we clearly notice the lower the bin (the lower the leaf value), the higher the loss
# On the third plot, it is clearly not smooth! We are severely overfitting the data, but the rules are
# real thus it is not an issue
# However, if the rules were not true, the loss would explode.
.prediction_depth_plot(df = new_data2)
.prediction_depth_spread_plot(df = new_data2)
.depth_density_plot(df = new_data2)
# Now, try with very severe overfitting
params <- list(
objective = "regression"
, metric = "l2"
, min_data = 1L
, learning_rate = 1.0
)
model3 <- lgb.train(
params
, dtrain
, 1000L
, valids
)
# We create the data structure, but for model3
new_data3 <- data.frame(
X = rowMeans(predict(
model3
, agaricus.test$data
, type = "leaf"
))
, Y = pmin(
pmax(
predict(
model3
, agaricus.test$data
)
, 1e-15
)
, 1.0 - 1e-15
)
)
new_data3$Z <- -1.0 * (agaricus.test$label * log(new_data3$Y) + (1L - agaricus.test$label) * log(1L - new_data3$Y))
new_data3$binned <- .bincode(
x = new_data3$X
, breaks = quantile(
x = new_data3$X
, probs = seq_len(9L) / 10.0
)
, right = TRUE
, include.lowest = TRUE
)
new_data3$binned[is.na(new_data3$binned)] <- 0L
# We can check the binned content
table(new_data3$binned)
# We can plot the binned content
# On the third plot, it is clearly not smooth! We are severely overfitting the data, but the rules
# are real thus it is not an issue.
# However, if the rules were not true, the loss would explode. See the sudden spikes?
.depth_density_plot(df = new_data3)
# Compare with our second model, the difference is severe. This is smooth.
.depth_density_plot(df = new_data2)
+70
View File
@@ -0,0 +1,70 @@
library(lightgbm)
# We load the default iris dataset shipped with R
data(iris)
# We must convert factors to numeric
# They must be starting from number 0 to use multiclass
# For instance: 0, 1, 2, 3, 4, 5...
iris$Species <- as.numeric(as.factor(iris$Species)) - 1L
# We cut the data set into 80% train and 20% validation
# The 10 last samples of each class are for validation
train <- as.matrix(iris[c(1L:40L, 51L:90L, 101L:140L), ])
test <- as.matrix(iris[c(41L:50L, 91L:100L, 141L:150L), ])
dtrain <- lgb.Dataset(data = train[, 1L:4L], label = train[, 5L])
dtest <- lgb.Dataset.create.valid(dtrain, data = test[, 1L:4L], label = test[, 5L])
valids <- list(test = dtest)
# Method 1 of training
params <- list(
objective = "multiclass"
, metric = "multi_error"
, num_class = 3L
, min_data = 1L
, learning_rate = 1.0
)
model <- lgb.train(
params
, dtrain
, 100L
, valids
, early_stopping_rounds = 10L
)
# We can predict on test data, outputs a 90-length vector
# Order: obs1 class1, obs1 class2, obs1 class3, obs2 class1, obs2 class2, obs2 class3...
my_preds <- predict(model, test[, 1L:4L])
# Method 2 of training, identical
params <- list(
min_data = 1L
, learning_rate = 1.0
, objective = "multiclass"
, metric = "multi_error"
, num_class = 3L
)
model <- lgb.train(
params
, dtrain
, 100L
, valids
, early_stopping_rounds = 10L
)
# We can predict on test data, identical
my_preds <- predict(model, test[, 1L:4L])
# A (30x3) matrix with the predictions
# class1 class2 class3
# obs1 obs1 obs1
# obs2 obs2 obs2
# .... .... ....
my_preds <- predict(model, test[, 1L:4L])
# We can also get the predicted scores before the Sigmoid/Softmax application
my_preds <- predict(model, test[, 1L:4L], type = "raw")
# We can also get the leaf index
my_preds <- predict(model, test[, 1L:4L], type = "leaf")
@@ -0,0 +1,117 @@
library(lightgbm)
# We load the default iris dataset shipped with R
data(iris)
# We must convert factors to numeric
# They must be starting from number 0 to use multiclass
# For instance: 0, 1, 2, 3, 4, 5...
iris$Species <- as.numeric(as.factor(iris$Species)) - 1L
# Create imbalanced training data (20, 30, 40 examples for classes 0, 1, 2)
train <- as.matrix(iris[c(1L:20L, 51L:80L, 101L:140L), ])
# The 10 last samples of each class are for validation
test <- as.matrix(iris[c(41L:50L, 91L:100L, 141L:150L), ])
dtrain <- lgb.Dataset(data = train[, 1L:4L], label = train[, 5L])
dtest <- lgb.Dataset.create.valid(dtrain, data = test[, 1L:4L], label = test[, 5L])
valids <- list(train = dtrain, test = dtest)
# Method 1 of training with built-in multiclass objective
# Note: need to turn off boost from average to match custom objective
# (https://github.com/lightgbm-org/LightGBM/issues/1846)
params <- list(
min_data = 1L
, learning_rate = 1.0
, num_class = 3L
, boost_from_average = FALSE
, metric = "multi_logloss"
)
model_builtin <- lgb.train(
params
, dtrain
, 100L
, valids
, early_stopping_rounds = 10L
, obj = "multiclass"
)
preds_builtin <- predict(model_builtin, test[, 1L:4L], type = "raw")
probs_builtin <- exp(preds_builtin) / rowSums(exp(preds_builtin))
# Method 2 of training with custom objective function
# User defined objective function, given prediction, return gradient and second order gradient
custom_multiclass_obj <- function(preds, dtrain) {
labels <- get_field(dtrain, "label")
# preds is a matrix with rows corresponding to samples and columns corresponding to choices
preds <- matrix(preds, nrow = length(labels))
# to prevent overflow, normalize preds by row
preds <- preds - apply(preds, MARGIN = 1L, max)
prob <- exp(preds) / rowSums(exp(preds))
# compute gradient
grad <- prob
subset_index <- as.matrix(
data.frame(
seq_along(labels)
, labels + 1L
, fix.empty.names = FALSE
)
, nrow = length(labels)
, dimnames = NULL
)
grad[subset_index] <- grad[subset_index] - 1L
# compute hessian (approximation)
hess <- 2.0 * prob * (1.0 - prob)
return(list(grad = grad, hess = hess))
}
# define custom metric
custom_multiclass_metric <- function(preds, dtrain) {
labels <- get_field(dtrain, "label")
preds <- matrix(preds, nrow = length(labels))
preds <- preds - apply(preds, 1L, max)
prob <- exp(preds) / rowSums(exp(preds))
subset_index <- as.matrix(
data.frame(
seq_along(labels)
, labels + 1L
, fix.empty.names = FALSE
)
, nrow = length(labels)
, dimnames = NULL
)
return(list(
name = "error"
, value = -mean(log(prob[subset_index]))
, higher_better = FALSE
))
}
params <- list(
min_data = 1L
, learning_rate = 1.0
, num_class = 3L
)
model_custom <- lgb.train(
params
, dtrain
, 100L
, valids
, early_stopping_rounds = 10L
, obj = custom_multiclass_obj
, eval = custom_multiclass_metric
)
preds_custom <- predict(model_custom, test[, 1L:4L], type = "raw")
probs_custom <- exp(preds_custom) / rowSums(exp(preds_custom))
# compare predictions
stopifnot(identical(probs_builtin, probs_custom))
stopifnot(identical(preds_builtin, preds_custom))
+104
View File
@@ -0,0 +1,104 @@
# This demo R code is to provide a demonstration of hyperparameter adjustment
# when scaling weights for appropriate learning
# As with any optimizers, bad parameters can impair performance
# Load library
library(lightgbm)
# We will train a model with the following scenarii:
# - Run 1: sum of weights equal to 6513 (x 1e-5) without adjusted regularization (not learning)
# - Run 2: sum of weights equal to 6513 (x 1e-5) adjusted regularization (learning)
# - Run 3: sum of weights equal to 6513 with adjusted regularization (learning)
# Setup small weights
weights1 <- rep(1e-5, 6513L)
weights2 <- rep(1e-5, 1611L)
# Load data and create datasets
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label, weight = weights1)
data(agaricus.test, package = "lightgbm")
test <- agaricus.test
dtest <- lgb.Dataset.create.valid(dtrain, test$data, label = test$label, weight = weights2)
valids <- list(test = dtest)
# Run 1: sum of weights equal to 6513 (x 1e-5) without adjusted regularization (not learning)
# It cannot learn because regularization is too large!
# min_sum_hessian alone is bigger than the sum of weights, thus you will never learn anything
params <- list(
objective = "regression"
, metric = "l2"
, device = "cpu"
, min_sum_hessian = 10.0
, num_leaves = 7L
, max_depth = 3L
, nthread = 1L
, min_data = 1L
, learning_rate = 1.0
)
model <- lgb.train(
params
, dtrain
, 50L
, valids
, early_stopping_rounds = 10L
)
weight_loss <- as.numeric(model$record_evals$test$l2$eval)
plot(weight_loss) # Shows how poor the learning was: a straight line!
# Run 2: sum of weights equal to 6513 (x 1e-5) with adjusted regularization (learning)
# Adjusted regularization just consisting in multiplicating results by 1e4 (x10000)
# Notice how it learns, there is no issue as we adjusted regularization ourselves
params <- list(
objective = "regression"
, metric = "l2"
, device = "cpu"
, min_sum_hessian = 1e-4
, num_leaves = 7L
, max_depth = 3L
, nthread = 1L
, min_data = 1L
, learning_rate = 1.0
)
model <- lgb.train(
params
, dtrain
, 50L
, valids
, early_stopping_rounds = 10L
)
small_weight_loss <- as.numeric(model$record_evals$test$l2$eval)
plot(small_weight_loss) # It learns!
# Run 3: sum of weights equal to 6513 with adjusted regularization (learning)
dtrain <- lgb.Dataset(train$data, label = train$label)
dtest <- lgb.Dataset.create.valid(dtrain, test$data, label = test$label)
valids <- list(test = dtest)
# Setup parameters and run model...
params <- list(
objective = "regression"
, metric = "l2"
, device = "cpu"
, min_sum_hessian = 10.0
, num_leaves = 7L
, max_depth = 3L
, nthread = 1L
, min_data = 1L
, learning_rate = 1.0
)
model <- lgb.train(
params
, dtrain
, 50L
, valids
, early_stopping_rounds = 10L
)
large_weight_loss <- as.numeric(model$record_evals$test$l2$eval)
plot(large_weight_loss) # It learns!
# Do you want to compare the learning? They both converge.
plot(small_weight_loss, large_weight_loss)
curve(1.0 * x, from = 0L, to = 0.02, add = TRUE)
+1
View File
@@ -0,0 +1 @@
+98
View File
@@ -0,0 +1,98 @@
# [description]
# Create a definition file (.def) from a .dll file, using objdump.
#
# [usage]
#
# Rscript make-r-def.R something.dll something.def
#
# [references]
# * https://www.cs.colorado.edu/~main/cs1300/doc/mingwfaq.html
args <- commandArgs(trailingOnly = TRUE)
IN_DLL_FILE <- args[[1L]]
OUT_DEF_FILE <- args[[2L]]
DLL_BASE_NAME <- basename(IN_DLL_FILE)
message(sprintf("Creating '%s' from '%s'", OUT_DEF_FILE, IN_DLL_FILE))
# system() will not raise an R exception if the process called
# fails. Wrapping it here to get that behavior.
#
# system() introduces a lot of overhead, at least on Windows,
# so trying processx if it is available
.pipe_shell_command_to_stdout <- function(command, args, out_file) {
has_processx <- suppressMessages({
suppressWarnings({
require("processx") # nolint: undesirable_function.
})
})
if (has_processx) {
p <- processx::process$new(
command = command
, args = args
, stdout = out_file
, windows_verbatim_args = FALSE
)
invisible(p$wait())
} else {
message(paste0(
"Using system2() to run shell commands. Installing "
, "'processx' with install.packages('processx') might "
, "make this faster."
))
# shQuote() is necessary here since one of the arguments
# is a file-path to R.dll, which may have spaces. processx
# does such quoting but system2() does not
exit_code <- system2(
command = command
, args = shoQuote(args)
, stdout = out_file
)
if (exit_code != 0L) {
stop(paste0("Command failed with exit code: ", exit_code))
}
}
return(invisible(NULL))
}
# use objdump to dump all the symbols
OBJDUMP_FILE <- "objdump-out.txt"
.pipe_shell_command_to_stdout(
command = "objdump"
, args = c("-p", IN_DLL_FILE)
, out_file = OBJDUMP_FILE
)
objdump_results <- readLines(OBJDUMP_FILE)
invisible(file.remove(OBJDUMP_FILE))
# Only one table in the objdump results matters for our purposes,
# see https://www.cs.colorado.edu/~main/cs1300/doc/mingwfaq.html
start_index <- which(
grepl(
pattern = "[Ordinal/Name Pointer] Table" # nolint: non_portable_path.
, x = objdump_results
, fixed = TRUE
)
)
empty_lines <- which(objdump_results == "")
end_of_table <- empty_lines[empty_lines > start_index][1L]
# Read the contents of the table
exported_symbols <- objdump_results[(start_index + 1L):end_of_table]
exported_symbols <- gsub("\t", "", exported_symbols, fixed = TRUE)
exported_symbols <- gsub(".*\\] ", "", exported_symbols)
exported_symbols <- gsub(" ", "", exported_symbols, fixed = TRUE)
# Write R.def file
writeLines(
text = c(
paste0("LIBRARY \"", DLL_BASE_NAME, "\"")
, "EXPORTS"
, exported_symbols
)
, con = OUT_DEF_FILE
, sep = "\n"
)
message(sprintf("Successfully created '%s'", OUT_DEF_FILE))
+31
View File
@@ -0,0 +1,31 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lightgbm.R
\docType{data}
\name{agaricus.test}
\alias{agaricus.test}
\title{Test part from Mushroom Data Set}
\format{
A list containing a label vector, and a dgCMatrix object with 1611
rows and 126 variables
}
\usage{
data(agaricus.test)
}
\description{
This data set is originally from the Mushroom data set,
UCI Machine Learning Repository.
This data set includes the following fields:
\itemize{
\item{\code{label}: the label for each record}
\item{\code{data}: a sparse Matrix of \code{dgCMatrix} class, with 126 columns.}
}
}
\references{
https://archive.ics.uci.edu/ml/datasets/Mushroom
Bache, K. & Lichman, M. (2013). UCI Machine Learning Repository
[https://archive.ics.uci.edu/ml]. Irvine, CA: University of California,
School of Information and Computer Science.
}
\keyword{datasets}
+31
View File
@@ -0,0 +1,31 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lightgbm.R
\docType{data}
\name{agaricus.train}
\alias{agaricus.train}
\title{Training part from Mushroom Data Set}
\format{
A list containing a label vector, and a dgCMatrix object with 6513
rows and 127 variables
}
\usage{
data(agaricus.train)
}
\description{
This data set is originally from the Mushroom data set,
UCI Machine Learning Repository.
This data set includes the following fields:
\itemize{
\item{\code{label}: the label for each record}
\item{\code{data}: a sparse Matrix of \code{dgCMatrix} class, with 126 columns.}
}
}
\references{
https://archive.ics.uci.edu/ml/datasets/Mushroom
Bache, K. & Lichman, M. (2013). UCI Machine Learning Repository
[https://archive.ics.uci.edu/ml]. Irvine, CA: University of California,
School of Information and Computer Science.
}
\keyword{datasets}
+26
View File
@@ -0,0 +1,26 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lightgbm.R
\docType{data}
\name{bank}
\alias{bank}
\title{Bank Marketing Data Set}
\format{
A data.table with 4521 rows and 17 variables
}
\usage{
data(bank)
}
\description{
This data set is originally from the Bank Marketing data set,
UCI Machine Learning Repository.
It contains only the following: bank.csv with 10% of the examples and 17 inputs,
randomly selected from 3 (older version of this dataset with less inputs).
}
\references{
https://archive.ics.uci.edu/ml/datasets/Bank+Marketing
S. Moro, P. Cortez and P. Rita. (2014)
A Data-Driven Approach to Predict the Success of Bank Telemarketing. Decision Support Systems
}
\keyword{datasets}
+34
View File
@@ -0,0 +1,34 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.Dataset.R
\name{dim.lgb.Dataset}
\alias{dim.lgb.Dataset}
\title{Dimensions of an \code{lgb.Dataset}}
\usage{
\method{dim}{lgb.Dataset}(x)
}
\arguments{
\item{x}{Object of class \code{lgb.Dataset}}
}
\value{
a vector of numbers of rows and of columns
}
\description{
Returns a vector of numbers of rows and of columns in an \code{lgb.Dataset}.
}
\details{
Note: since \code{nrow} and \code{ncol} internally use \code{dim}, they can also
be directly used with an \code{lgb.Dataset} object.
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
stopifnot(nrow(dtrain) == nrow(train$data))
stopifnot(ncol(dtrain) == ncol(train$data))
stopifnot(all(dim(dtrain) == dim(train$data)))
}
}
+42
View File
@@ -0,0 +1,42 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.Dataset.R
\name{dimnames.lgb.Dataset}
\alias{dimnames.lgb.Dataset}
\alias{dimnames<-.lgb.Dataset}
\title{Handling of column names of \code{lgb.Dataset}}
\usage{
\method{dimnames}{lgb.Dataset}(x)
\method{dimnames}{lgb.Dataset}(x) <- value
}
\arguments{
\item{x}{object of class \code{lgb.Dataset}}
\item{value}{a list of two elements: the first one is ignored
and the second one is column names}
}
\value{
A list with the dimension names of the dataset
}
\description{
Only column names are supported for \code{lgb.Dataset}, thus setting of
row names would have no effect and returned row names would be NULL.
}
\details{
Generic \code{dimnames} methods are used by \code{colnames}.
Since row names are irrelevant, it is recommended to use \code{colnames} directly.
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
lgb.Dataset.construct(dtrain)
dimnames(dtrain)
colnames(dtrain)
colnames(dtrain) <- make.names(seq_len(ncol(train$data)))
print(dtrain, verbose = TRUE)
}
}
+41
View File
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Creator: CorelDRAW 2020 (64-Bit Evaluation Version) -->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="8442px" height="9748px" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
viewBox="0 0 8438.53 9743.98"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xodm="http://www.corel.com/coreldraw/odm/2003">
<defs>
<style type="text/css">
<![CDATA[
.str0 {stroke:#373435;stroke-width:2.36;stroke-miterlimit:22.9256}
.fil1 {fill:#373435}
.fil5 {fill:#EF4A37}
.fil3 {fill:#1B9AD7}
.fil2 {fill:#76B644}
.fil0 {fill:#FCB518}
.fil4 {fill:#E6E7E8;fill-rule:nonzero}
]]>
</style>
</defs>
<g id="图层_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer"/>
<polygon class="fil0" points="4219.26,0 6328.9,1218 8438.53,2435.99 8438.53,4871.99 8438.53,7307.98 6328.9,8525.98 4219.26,9743.98 2109.63,8525.98 0,7307.98 0,4871.99 0,2435.99 2109.63,1218 "/>
<polygon class="fil1 str0" points="4219.26,287 6204.62,1433.25 8189.98,2579.5 8189.98,4871.99 8189.98,7164.48 6204.62,8310.72 4219.26,9456.97 2233.91,8310.72 248.55,7164.48 248.55,4871.99 248.55,2579.5 2233.91,1433.25 "/>
<polygon class="fil2" points="3906.02,3183.13 5436.73,2190.94 5436.73,3483.55 "/>
<polygon class="fil0" points="2815.23,5749.77 5436.74,5749.77 5436.74,4071.86 "/>
<polygon class="fil3" points="4345.88,3965.45 2815.16,4957.64 2815.16,3665.03 "/>
<g id="_1713672554880">
<path class="fil4" d="M7626.3 7313.8l-194.4 0 0 -467.44c0,-89.77 -12.58,-154.96 -38.02,-194.98 -25.45,-40.31 -68.33,-60.32 -128.66,-60.32 -50.89,0 -94.05,25.44 -129.51,76.05 -35.45,50.88 -53.17,111.78 -53.17,182.68l0 464.01 -194.98 0 0 -483.17c0,-159.81 -56.32,-239.57 -169.25,-239.57 -52.61,0 -95.78,24.01 -129.52,72.04 -33.73,48.03 -50.6,110.07 -50.6,186.69l0 464.01 -194.4 0 0 -857.68 194.4 0 0 135.79 3.44 0c62.03,-104.06 152.09,-155.81 270.45,-155.81 59.18,0 110.93,16.3 155.53,48.89 44.31,32.59 74.61,75.48 90.63,128.65 63.75,-118.36 158.67,-177.54 285.03,-177.54 188.69,0 283.03,116.36 283.03,349.08l0 528.62z"/>
<path class="fil4" d="M5535 7189.72l-3.43 0 0 124.08 -194.41 0 0 -1269.66 194.41 0 0 562.64 3.43 0c66.33,-113.78 163.53,-170.68 291.33,-170.68 108.35,0 193.26,38.31 254.15,115.22 61.19,76.62 91.78,179.54 91.78,308.47 0,143.53 -34.31,258.45 -102.92,344.8 -68.62,86.05 -162.39,129.22 -281.32,129.22 -111.79,0 -196.12,-48.03 -253.02,-144.09zm-5.14 -340.79l0 106.35c0,62.62 20.01,115.51 60.03,159.25 39.74,43.45 90.63,65.19 151.81,65.19 72.05,0 128.65,-28.03 169.82,-84.06 40.89,-56.04 61.47,-134.37 61.47,-235 0,-84.34 -19.15,-150.39 -57.46,-198.13 -38.03,-47.74 -90.06,-71.47 -155.24,-71.47 -69.19,0 -124.94,24.58 -167.25,73.47 -42.03,49.17 -63.18,110.64 -63.18,184.4z"/>
<path class="fil4" d="M4150.5 7304.65c-37.74,18.87 -87.77,28.3 -149.8,28.3 -166.4,0 -249.59,-79.76 -249.59,-239.58l0 -484.88 -143.24 0 0 -152.37 143.24 0 0 -198.42 194.41 -55.46 0 253.88 204.98 0 0 152.37 -204.98 0 0 428.85c0,50.89 9.14,87.2 27.44,108.92 18.58,21.73 49.17,32.59 92.34,32.59 32.88,0 61.19,-9.43 85.2,-28.3l0 154.1z"/>
<path class="fil4" d="M3435.32 7313.8l-194.12 0 0 -469.16c0,-169.53 -56.89,-254.44 -170.11,-254.44 -56.89,0 -104.92,24.59 -144.09,73.76 -39.16,48.89 -58.6,111.78 -58.6,188.41l0 461.43 -195.27 0 0 -1269.66 195.27 0 0 554.35 3.43 0c64.61,-108.35 157.52,-162.39 277.88,-162.39 190.41,0 285.61,116.36 285.61,349.08l0 528.62z"/>
<polygon class="fil4" points="1482.64,7313.8 1288.23,7313.8 1288.23,6456.12 1482.64,6456.12 "/>
<path class="fil4" d="M1386.29 6276c-31.73,0 -58.89,-10.29 -81.48,-30.88 -22.87,-20.87 -34.02,-46.88 -34.02,-78.9 0,-31.74 11.15,-58.32 34.02,-79.48 22.59,-21.16 49.75,-31.73 81.48,-31.73 32.88,0 60.9,10.57 83.77,31.73 22.87,21.16 34.31,47.74 34.31,79.48 0,30.3 -11.44,56.04 -34.31,77.47 -22.87,21.45 -50.89,32.31 -83.77,32.31z"/>
<polygon class="fil4" points="1112.75,7313.8 917.48,7313.8 917.48,6044.14 1112.75,6044.14 "/>
<path class="fil4" d="M2496.58 7245.18c0,314.77 -158.39,472.3 -474.87,472.3 -111.79,0 -209.28,-18.58 -292.48,-56.04l0 -177.54c93.78,53.47 182.98,80.34 267.32,80.34 203.84,0 305.62,-100.35 305.62,-300.76l0 -93.77 -3.43 0c-64.05,109.5 -160.68,164.1 -289.61,164.1 -104.36,0 -188.7,-38.02 -252.73,-114.36 -63.76,-76.05 -95.78,-178.4 -95.78,-306.76 0,-145.81 34.31,-261.6 102.92,-347.65 68.9,-86.05 163.25,-128.94 283.32,-128.94 113.22,0 197.27,46.32 251.88,138.94l3.43 0 0 -118.92 194.41 0 0 789.06zm-192.7 -324.2l0 -111.5c0,-60.32 -20.01,-111.78 -59.75,-154.38 -40.02,-42.89 -89.77,-64.04 -149.52,-64.04 -73.76,0 -131.51,27.16 -172.97,81.48 -41.74,54.6 -62.61,130.65 -62.61,228.43 0,84.33 20.02,151.52 60.04,202.12 39.74,50.61 92.91,75.76 158.67,75.76 66.9,0 121.51,-24.01 163.25,-72.32 42.02,-48.32 62.89,-110.08 62.89,-185.55z"/>
<path class="fil4" d="M5165.09 7245.18c0,314.77 -158.38,472.3 -474.87,472.3 -111.78,0 -209.27,-18.58 -292.47,-56.04l0 -177.54c93.77,53.47 182.97,80.34 267.31,80.34 203.84,0 305.62,-100.35 305.62,-300.76l0 -93.77 -3.43 0c-64.04,109.5 -160.67,164.1 -289.61,164.1 -104.35,0 -188.69,-38.02 -252.73,-114.36 -63.76,-76.05 -95.78,-178.4 -95.78,-306.76 0,-145.81 34.31,-261.6 102.93,-347.65 68.9,-86.05 163.24,-128.94 283.32,-128.94 113.21,0 197.27,46.32 251.87,138.94l3.43 0 0 -118.92 194.41 0 0 789.06zm-192.69 -324.2l0 -111.5c0,-60.32 -20.02,-111.78 -59.75,-154.38 -40.03,-42.89 -89.77,-64.04 -149.53,-64.04 -73.76,0 -131.51,27.16 -172.96,81.48 -41.74,54.6 -62.61,130.65 -62.61,228.43 0,84.33 20.01,151.52 60.03,202.12 39.74,50.61 92.92,75.76 158.67,75.76 66.9,0 121.51,-24.01 163.25,-72.32 42.03,-48.32 62.9,-110.08 62.9,-185.55z"/>
</g>
<polygon class="fil5" points="2814.64,3068.81 2814.64,1388.62 5436.7,1388.62 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

+26
View File
@@ -0,0 +1,26 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/multithreading.R
\name{getLGBMThreads}
\alias{getLGBMThreads}
\alias{getLGBMthreads}
\title{Get default number of threads used by LightGBM}
\usage{
getLGBMthreads()
}
\value{
number of threads as an integer. \code{-1} means that in situations where parameter \code{num_threads} is
not explicitly supplied, LightGBM will choose a number of threads to use automatically.
}
\description{
LightGBM attempts to speed up many operations by using multi-threading.
The number of threads used in those operations can be controlled via the
\code{num_threads} parameter passed through \code{params} to functions like
\link{lgb.train} and \link{lgb.Dataset}. However, some operations (like materializing
a model from a text file) are done via code paths that don't explicitly accept thread-control
configuration.
Use this function to see the default number of threads LightGBM will use for such operations.
}
\seealso{
\link{setLGBMthreads}
}
+48
View File
@@ -0,0 +1,48 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.Dataset.R
\name{get_field}
\alias{get_field}
\alias{get_field.lgb.Dataset}
\title{Get one attribute of a \code{lgb.Dataset}}
\usage{
get_field(dataset, field_name)
\method{get_field}{lgb.Dataset}(dataset, field_name)
}
\arguments{
\item{dataset}{Object of class \code{lgb.Dataset}}
\item{field_name}{String with the name of the attribute to get. One of the following.
\itemize{
\item \code{label}: label lightgbm learns from ;
\item \code{weight}: to do a weight rescale ;
\item{\code{group}: used for learning-to-rank tasks. An integer vector describing how to
group rows together as ordered results from the same set of candidate results to be ranked.
For example, if you have a 100-document dataset with \code{group = c(10, 20, 40, 10, 10, 10)},
that means that you have 6 groups, where the first 10 records are in the first group,
records 11-30 are in the second group, etc.}
\item \code{init_score}: initial score is the base prediction lightgbm will boost from.
}}
}
\value{
requested attribute
}
\description{
Get one attribute of a \code{lgb.Dataset}
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
lgb.Dataset.construct(dtrain)
labels <- lightgbm::get_field(dtrain, "label")
lightgbm::set_field(dtrain, "label", 1 - labels)
labels2 <- lightgbm::get_field(dtrain, "label")
stopifnot(all(labels2 == 1 - labels))
}
}
+82
View File
@@ -0,0 +1,82 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.Dataset.R
\name{lgb.Dataset}
\alias{lgb.Dataset}
\title{Construct \code{lgb.Dataset} object}
\usage{
lgb.Dataset(
data,
params = list(),
reference = NULL,
colnames = NULL,
categorical_feature = NULL,
free_raw_data = TRUE,
label = NULL,
weight = NULL,
group = NULL,
init_score = NULL
)
}
\arguments{
\item{data}{a \code{matrix} object, a \code{dgCMatrix} object,
a character representing a path to a text file (CSV, TSV, or LibSVM),
or a character representing a path to a binary \code{lgb.Dataset} file}
\item{params}{a list of parameters. See
\href{https://lightgbm.readthedocs.io/en/latest/Parameters.html#dataset-parameters}{
The "Dataset Parameters" section of the documentation} for a list of parameters
and valid values.}
\item{reference}{reference dataset. When LightGBM creates a Dataset, it does some preprocessing like binning
continuous features into histograms. If you want to apply the same bin boundaries from an existing
dataset to new \code{data}, pass that existing Dataset to this argument.}
\item{colnames}{names of columns}
\item{categorical_feature}{categorical features. This can either be a character vector of feature
names or an integer vector with the indices of the features (e.g.
\code{c(1L, 10L)} to say "the first and tenth columns").}
\item{free_raw_data}{LightGBM constructs its data format, called a "Dataset", from tabular data.
By default, that Dataset object on the R side does not keep a copy of the raw data.
This reduces LightGBM's memory consumption, but it means that the Dataset object
cannot be changed after it has been constructed. If you'd prefer to be able to
change the Dataset object after construction, set \code{free_raw_data = FALSE}.}
\item{label}{vector of labels to use as the target variable}
\item{weight}{numeric vector of sample weights}
\item{group}{used for learning-to-rank tasks. An integer vector describing how to
group rows together as ordered results from the same set of candidate results
to be ranked. For example, if you have a 100-document dataset with
\code{group = c(10, 20, 40, 10, 10, 10)}, that means that you have 6 groups,
where the first 10 records are in the first group, records 11-30 are in the
second group, etc.}
\item{init_score}{initial score is the base prediction lightgbm will boost from}
}
\value{
constructed dataset
}
\description{
LightGBM does not train on raw data.
It discretizes continuous features into histogram bins, tries to
combine categorical features, and automatically handles missing and
The \code{Dataset} class handles that preprocessing, and holds that
alternative representation of the input data.
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
data_file <- tempfile(fileext = ".data")
lgb.Dataset.save(dtrain, data_file)
dtrain <- lgb.Dataset(data_file)
lgb.Dataset.construct(dtrain)
}
}
+27
View File
@@ -0,0 +1,27 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.Dataset.R
\name{lgb.Dataset.construct}
\alias{lgb.Dataset.construct}
\title{Construct Dataset explicitly}
\usage{
lgb.Dataset.construct(dataset)
}
\arguments{
\item{dataset}{Object of class \code{lgb.Dataset}}
}
\value{
constructed dataset
}
\description{
Construct Dataset explicitly
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
lgb.Dataset.construct(dtrain)
}
}
+95
View File
@@ -0,0 +1,95 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.Dataset.R
\name{lgb.Dataset.create.valid}
\alias{lgb.Dataset.create.valid}
\title{Construct validation data}
\usage{
lgb.Dataset.create.valid(
dataset,
data,
label = NULL,
weight = NULL,
group = NULL,
init_score = NULL,
params = list()
)
}
\arguments{
\item{dataset}{\code{lgb.Dataset} object, training data}
\item{data}{a \code{matrix} object, a \code{dgCMatrix} object,
a character representing a path to a text file (CSV, TSV, or LibSVM),
or a character representing a path to a binary \code{Dataset} file}
\item{label}{vector of labels to use as the target variable}
\item{weight}{numeric vector of sample weights}
\item{group}{used for learning-to-rank tasks. An integer vector describing how to
group rows together as ordered results from the same set of candidate results
to be ranked. For example, if you have a 100-document dataset with
\code{group = c(10, 20, 40, 10, 10, 10)}, that means that you have 6 groups,
where the first 10 records are in the first group, records 11-30 are in the
second group, etc.}
\item{init_score}{initial score is the base prediction lightgbm will boost from}
\item{params}{a list of parameters. See
\href{https://lightgbm.readthedocs.io/en/latest/Parameters.html#dataset-parameters}{
The "Dataset Parameters" section of the documentation} for a list of parameters
and valid values. If this is an empty list (the default), the validation Dataset
will have the same parameters as the Dataset passed to argument \code{dataset}.}
}
\value{
constructed dataset
}
\description{
Construct validation data according to training data
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
data(agaricus.test, package = "lightgbm")
test <- agaricus.test
dtest <- lgb.Dataset.create.valid(dtrain, test$data, label = test$label)
# parameters can be changed between the training data and validation set,
# for example to account for training data in a text file with a header row
# and validation data in a text file without it
train_file <- tempfile(pattern = "train_", fileext = ".csv")
write.table(
data.frame(y = rnorm(100L), x1 = rnorm(100L), x2 = rnorm(100L))
, file = train_file
, sep = ","
, col.names = TRUE
, row.names = FALSE
, quote = FALSE
)
valid_file <- tempfile(pattern = "valid_", fileext = ".csv")
write.table(
data.frame(y = rnorm(100L), x1 = rnorm(100L), x2 = rnorm(100L))
, file = valid_file
, sep = ","
, col.names = FALSE
, row.names = FALSE
, quote = FALSE
)
dtrain <- lgb.Dataset(
data = train_file
, params = list(has_header = TRUE)
)
dtrain$construct()
dvalid <- lgb.Dataset(
data = valid_file
, params = list(has_header = FALSE)
)
dvalid$construct()
}
}
+30
View File
@@ -0,0 +1,30 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.Dataset.R
\name{lgb.Dataset.save}
\alias{lgb.Dataset.save}
\title{Save \code{lgb.Dataset} to a binary file}
\usage{
lgb.Dataset.save(dataset, fname)
}
\arguments{
\item{dataset}{object of class \code{lgb.Dataset}}
\item{fname}{object filename of output file}
}
\value{
the dataset you passed in
}
\description{
Please note that \code{init_score} is not saved in binary file.
If you need it, please set it again after loading Dataset.
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
lgb.Dataset.save(dtrain, tempfile(fileext = ".bin"))
}
}
@@ -0,0 +1,35 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.Dataset.R
\name{lgb.Dataset.set.categorical}
\alias{lgb.Dataset.set.categorical}
\title{Set categorical feature of \code{lgb.Dataset}}
\usage{
lgb.Dataset.set.categorical(dataset, categorical_feature)
}
\arguments{
\item{dataset}{object of class \code{lgb.Dataset}}
\item{categorical_feature}{categorical features. This can either be a character vector of feature
names or an integer vector with the indices of the features (e.g.
\code{c(1L, 10L)} to say "the first and tenth columns").}
}
\value{
the dataset you passed in
}
\description{
Set the categorical features of an \code{lgb.Dataset} object. Use this function
to tell LightGBM which features should be treated as categorical.
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
data_file <- tempfile(fileext = ".data")
lgb.Dataset.save(dtrain, data_file)
dtrain <- lgb.Dataset(data_file)
lgb.Dataset.set.categorical(dtrain, 1L:2L)
}
}
@@ -0,0 +1,35 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.Dataset.R
\name{lgb.Dataset.set.reference}
\alias{lgb.Dataset.set.reference}
\title{Set reference of \code{lgb.Dataset}}
\usage{
lgb.Dataset.set.reference(dataset, reference)
}
\arguments{
\item{dataset}{object of class \code{lgb.Dataset}}
\item{reference}{object of class \code{lgb.Dataset}}
}
\value{
the dataset you passed in
}
\description{
If you want to use validation data, you should set reference to training data
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
# create training Dataset
data(agaricus.train, package ="lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
# create a validation Dataset, using dtrain as a reference
data(agaricus.test, package = "lightgbm")
test <- agaricus.test
dtest <- lgb.Dataset(test$data, label = test$label)
lgb.Dataset.set.reference(dtest, dtrain)
}
}
+144
View File
@@ -0,0 +1,144 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.Booster.R
\name{lgb.configure_fast_predict}
\alias{lgb.configure_fast_predict}
\title{Configure Fast Single-Row Predictions}
\usage{
lgb.configure_fast_predict(
model,
csr = FALSE,
start_iteration = NULL,
num_iteration = NULL,
type = "response",
params = list()
)
}
\arguments{
\item{model}{LightGBM model object (class \code{lgb.Booster}).
\bold{The object will be modified in-place}.}
\item{csr}{Whether the prediction function is going to be called on sparse CSR inputs.
If \code{FALSE}, will be assumed that predictions are going to be called on single-row
regular R matrices.}
\item{start_iteration}{int or None, optional (default=None)
Start index of the iteration to predict.
If None or <= 0, starts from the first iteration.}
\item{num_iteration}{int or None, optional (default=None)
Limit number of iterations in the prediction.
If None, if the best iteration exists and start_iteration is None or <= 0, the
best iteration is used; otherwise, all iterations from start_iteration are used.
If <= 0, all iterations from start_iteration are used (no limits).}
\item{type}{Type of prediction to output. Allowed types are:\itemize{
\item \code{"response"}: will output the predicted score according to the objective function being
optimized (depending on the link function that the objective uses), after applying any necessary
transformations - for example, for \code{objective="binary"}, it will output class probabilities.
\item \code{"class"}: for classification objectives, will output the class with the highest predicted
probability. For other objectives, will output the same as "response". Note that \code{"class"} is
not a supported type for \link{lgb.configure_fast_predict} (see the documentation of that function
for more details).
\item \code{"raw"}: will output the non-transformed numbers (sum of predictions from boosting iterations'
results) from which the "response" number is produced for a given objective function - for example,
for \code{objective="binary"}, this corresponds to log-odds. For many objectives such as
"regression", since no transformation is applied, the output will be the same as for "response".
\item \code{"leaf"}: will output the index of the terminal node / leaf at which each observations falls
in each tree in the model, outputted as integers, with one column per tree.
\item \code{"contrib"}: will return the per-feature contributions for each prediction, including an
intercept (each feature will produce one column).
}
Note that, if using custom objectives, types "class" and "response" will not be available and will
default towards using "raw" instead.
If the model was fit through function \link{lightgbm} and it was passed a factor as labels,
passing the prediction type through \code{params} instead of through this argument might
result in factor levels for classification objectives not being applied correctly to the
resulting output.
\emph{New in version 4.0.0}}
\item{params}{a list of additional named parameters. See
\href{https://lightgbm.readthedocs.io/en/latest/Parameters.html#predict-parameters}{
the "Predict Parameters" section of the documentation} for a list of parameters and
valid values. Where these conflict with the values of keyword arguments to this function,
the values in \code{params} take precedence.}
}
\value{
The same \code{model} that was passed as input, invisibly, with the desired
configuration stored inside it and available to be used in future calls to
\link{predict.lgb.Booster}.
}
\description{
Pre-configures a LightGBM model object to produce fast single-row predictions
for a given input data type, prediction type, and parameters.
}
\details{
Calling this function multiple times with different parameters might not override
the previous configuration and might trigger undefined behavior.
Any saved configuration for fast predictions might be lost after making a single-row
prediction of a different type than what was configured (except for types "response" and
"class", which can be switched between each other at any time without losing the configuration).
In some situations, setting a fast prediction configuration for one type of prediction
might cause the prediction function to keep using that configuration for single-row
predictions even if the requested type of prediction is different from what was configured.
Note that this function will not accept argument \code{type="class"} - for such cases, one
can pass \code{type="response"} to this function and then \code{type="class"} to the
\code{predict} function - the fast configuration will not be lost or altered if the switch
is between "response" and "class".
The configuration does not survive de-serializations, so it has to be generated
anew in every R process that is going to use it (e.g. if loading a model object
through \code{readRDS}, whatever configuration was there previously will be lost).
Requesting a different prediction type or passing parameters to \link{predict.lgb.Booster}
will cause it to ignore the fast-predict configuration and take the slow route instead
(but be aware that an existing configuration might not always be overridden by supplying
different parameters or prediction type, so make sure to check that the output is what
was expected when a prediction is to be made on a single row for something different than
what is configured).
Note that, if configuring a non-default prediction type (such as leaf indices),
then that type must also be passed in the call to \link{predict.lgb.Booster} in
order for it to use the configuration. This also applies for \code{start_iteration}
and \code{num_iteration}, but \bold{the \code{params} list must be empty} in the call to \code{predict}.
Predictions about feature contributions do not allow a fast route for CSR inputs,
and as such, this function will produce an error if passing \code{csr=TRUE} and
\code{type = "contrib"} together.
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
library(lightgbm)
data(mtcars)
X <- as.matrix(mtcars[, -1L])
y <- mtcars[, 1L]
dtrain <- lgb.Dataset(X, label = y, params = list(max_bin = 5L))
params <- list(
min_data_in_leaf = 2L
, num_threads = 2L
)
model <- lgb.train(
params = params
, data = dtrain
, obj = "regression"
, nrounds = 5L
, verbose = -1L
)
lgb.configure_fast_predict(model)
x_single <- X[11L, , drop = FALSE]
predict(model, x_single)
# Will not use it if the prediction to be made
# is different from what was configured
predict(model, x_single, type = "leaf")
}
}
+74
View File
@@ -0,0 +1,74 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.convert_with_rules.R
\name{lgb.convert_with_rules}
\alias{lgb.convert_with_rules}
\title{Data preparator for LightGBM datasets with rules (integer)}
\usage{
lgb.convert_with_rules(data, rules = NULL)
}
\arguments{
\item{data}{A data.frame or data.table to prepare.}
\item{rules}{A set of rules from the data preparator, if already used. This should be an R list,
where names are column names in \code{data} and values are named character
vectors whose names are column values and whose values are new values to
replace them with.}
}
\value{
A list with the cleaned dataset (\code{data}) and the rules (\code{rules}).
Note that the data must be converted to a matrix format (\code{as.matrix}) for input in
\code{lgb.Dataset}.
}
\description{
Attempts to prepare a clean dataset to prepare to put in a \code{lgb.Dataset}.
Factor, character, and logical columns are converted to integer. Missing values
in factors and characters will be filled with 0L. Missing values in logicals
will be filled with -1L.
This function returns and optionally takes in "rules" the describe exactly
how to convert values in columns.
Columns that contain only NA values will be converted by this function but will
not show up in the returned \code{rules}.
NOTE: In previous releases of LightGBM, this function was called \code{lgb.prepare_rules2}.
}
\examples{
\donttest{
data(iris)
str(iris)
new_iris <- lgb.convert_with_rules(data = iris)
str(new_iris$data)
data(iris) # Erase iris dataset
iris$Species[1L] <- "NEW FACTOR" # Introduce junk factor (NA)
# Use conversion using known rules
# Unknown factors become 0, excellent for sparse datasets
newer_iris <- lgb.convert_with_rules(data = iris, rules = new_iris$rules)
# Unknown factor is now zero, perfect for sparse datasets
newer_iris$data[1L, ] # Species became 0 as it is an unknown factor
newer_iris$data[1L, 5L] <- 1.0 # Put back real initial value
# Is the newly created dataset equal? YES!
all.equal(new_iris$data, newer_iris$data)
# Can we test our own rules?
data(iris) # Erase iris dataset
# We remapped values differently
personal_rules <- list(
Species = c(
"setosa" = 3L
, "versicolor" = 2L
, "virginica" = 1L
)
)
newest_iris <- lgb.convert_with_rules(data = iris, rules = personal_rules)
str(newest_iris$data) # SUCCESS!
}
}
+164
View File
@@ -0,0 +1,164 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.cv.R
\name{lgb.cv}
\alias{lgb.cv}
\title{Main CV logic for LightGBM}
\usage{
lgb.cv(
params = list(),
data,
nrounds = 100L,
nfold = 3L,
obj = NULL,
eval = NULL,
verbose = 1L,
record = TRUE,
eval_freq = 1L,
showsd = TRUE,
stratified = TRUE,
folds = NULL,
init_model = NULL,
early_stopping_rounds = NULL,
callbacks = list(),
reset_data = FALSE,
serializable = TRUE,
eval_train_metric = FALSE
)
}
\arguments{
\item{params}{a list of parameters. See \href{https://lightgbm.readthedocs.io/en/latest/Parameters.html}{
the "Parameters" section of the documentation} for a list of parameters and valid values.}
\item{data}{a \code{lgb.Dataset} object, used for training. Some functions, such as \code{\link{lgb.cv}},
may allow you to pass other types of data like \code{matrix} and then separately supply
\code{label} as a keyword argument.}
\item{nrounds}{number of training rounds}
\item{nfold}{the original dataset is randomly partitioned into \code{nfold} equal size subsamples.}
\item{obj}{objective function, can be character or custom objective function. Examples include
\code{regression}, \code{regression_l1}, \code{huber},
\code{binary}, \code{lambdarank}, \code{multiclass}, \code{multiclass}}
\item{eval}{evaluation function(s). This can be a character vector, function, or list with a mixture of
strings and functions.
\itemize{
\item{\bold{a. character vector}:
If you provide a character vector to this argument, it should contain strings with valid
evaluation metrics.
See \href{https://lightgbm.readthedocs.io/en/latest/Parameters.html#metric}{
The "metric" section of the documentation}
for a list of valid metrics.
}
\item{\bold{b. function}:
You can provide a custom evaluation function. This
should accept the keyword arguments \code{preds} and \code{dtrain} and should return a named
list with three elements:
\itemize{
\item{\code{name}: A string with the name of the metric, used for printing
and storing results.
}
\item{\code{value}: A single number indicating the value of the metric for the
given predictions and true values
}
\item{
\code{higher_better}: A boolean indicating whether higher values indicate a better fit.
For example, this would be \code{FALSE} for metrics like MAE or RMSE.
}
}
}
\item{\bold{c. list}:
If a list is given, it should only contain character vectors and functions.
These should follow the requirements from the descriptions above.
}
}}
\item{verbose}{verbosity for output, if <= 0 and \code{valids} has been provided, also will disable the
printing of evaluation during training}
\item{record}{Boolean, TRUE will record iteration message to \code{booster$record_evals}}
\item{eval_freq}{evaluation output frequency, only effective when verbose > 0 and \code{valids} has been provided}
\item{showsd}{\code{boolean}, whether to show standard deviation of cross validation.
This parameter defaults to \code{TRUE}. Setting it to \code{FALSE} can lead to a
slight speedup by avoiding unnecessary computation.}
\item{stratified}{a \code{boolean} indicating whether sampling of folds should be stratified
by the values of outcome labels.}
\item{folds}{\code{list} provides a possibility to use a list of pre-defined CV folds
(each element must be a vector of test fold's indices). When folds are supplied,
the \code{nfold} and \code{stratified} parameters are ignored.}
\item{init_model}{path of model file or \code{lgb.Booster} object, will continue training from this model}
\item{early_stopping_rounds}{int. Activates early stopping. When this parameter is non-null,
training will stop if the evaluation of any metric on any validation set
fails to improve for \code{early_stopping_rounds} consecutive boosting rounds.
If training stops early, the returned model will have attribute \code{best_iter}
set to the iteration number of the best iteration.}
\item{callbacks}{List of callback functions that are applied at each iteration.}
\item{reset_data}{Boolean, setting it to TRUE (not the default value) will transform the booster model
into a predictor model which frees up memory and the original datasets}
\item{serializable}{whether to make the resulting objects serializable through functions such as
\code{save} or \code{saveRDS} (see section "Model serialization").}
\item{eval_train_metric}{\code{boolean}, whether to add the cross validation results on the
training data. This parameter defaults to \code{FALSE}. Setting it to \code{TRUE}
will increase run time.}
}
\value{
a trained model \code{lgb.CVBooster}.
}
\description{
Cross validation logic used by LightGBM
}
\section{Early Stopping}{
"early stopping" refers to stopping the training process if the model's performance on a given
validation set does not improve for several consecutive iterations.
If multiple arguments are given to \code{eval}, their order will be preserved. If you enable
early stopping by setting \code{early_stopping_rounds} in \code{params}, by default all
metrics will be considered for early stopping.
If you want to only consider the first metric for early stopping, pass
\code{first_metric_only = TRUE} in \code{params}. Note that if you also specify \code{metric}
in \code{params}, that metric will be considered the "first" one. If you omit \code{metric},
a default metric will be used based on your choice for the parameter \code{obj} (keyword argument)
or \code{objective} (passed into \code{params}).
\bold{NOTE:} if using \code{boosting_type="dart"}, any early stopping configuration will be ignored
and early stopping will not be performed.
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
params <- list(
objective = "regression"
, metric = "l2"
, min_data = 1L
, learning_rate = 1.0
, num_threads = 2L
)
model <- lgb.cv(
params = params
, data = dtrain
, nrounds = 5L
, nfold = 3L
)
}
}
+25
View File
@@ -0,0 +1,25 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.drop_serialized.R
\name{lgb.drop_serialized}
\alias{lgb.drop_serialized}
\title{Drop serialized raw bytes in a LightGBM model object}
\usage{
lgb.drop_serialized(model)
}
\arguments{
\item{model}{\code{lgb.Booster} object which was produced with `serializable=TRUE`.}
}
\value{
\code{lgb.Booster} (the same `model` object that was passed as input, as invisible).
}
\description{
If a LightGBM model object was produced with argument `serializable=TRUE`, the R object will keep
a copy of the underlying C++ object as raw bytes, which can be used to reconstruct such object after getting
serialized and de-serialized, but at the cost of extra memory usage. If these raw bytes are not needed anymore,
they can be dropped through this function in order to save memory. Note that the object will be modified in-place.
\emph{New in version 4.0.0}
}
\seealso{
\link{lgb.restore_handle}, \link{lgb.make_serializable}.
}
+54
View File
@@ -0,0 +1,54 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.Booster.R
\name{lgb.dump}
\alias{lgb.dump}
\title{Dump LightGBM model to json}
\usage{
lgb.dump(booster, num_iteration = NULL, start_iteration = 1L)
}
\arguments{
\item{booster}{Object of class \code{lgb.Booster}}
\item{num_iteration}{Number of iterations to be dumped. NULL or <= 0 means use best iteration}
\item{start_iteration}{Index (1-based) of the first boosting round to dump.
For example, passing \code{start_iteration=5, num_iteration=3} for a regression model
means "dump the fifth, sixth, and seventh tree"
\emph{New in version 4.4.0}}
}
\value{
json format of model
}
\description{
Dump LightGBM model to json
}
\examples{
\donttest{
library(lightgbm)
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
data(agaricus.test, package = "lightgbm")
test <- agaricus.test
dtest <- lgb.Dataset.create.valid(dtrain, test$data, label = test$label)
params <- list(
objective = "regression"
, metric = "l2"
, min_data = 1L
, learning_rate = 1.0
, num_threads = 2L
)
valids <- list(test = dtest)
model <- lgb.train(
params = params
, data = dtrain
, nrounds = 10L
, valids = valids
, early_stopping_rounds = 5L
)
json_model <- lgb.dump(model)
}
}
+69
View File
@@ -0,0 +1,69 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.Booster.R
\name{lgb.get.eval.result}
\alias{lgb.get.eval.result}
\title{Get record evaluation result from booster}
\usage{
lgb.get.eval.result(
booster,
data_name,
eval_name,
iters = NULL,
is_err = FALSE
)
}
\arguments{
\item{booster}{Object of class \code{lgb.Booster}}
\item{data_name}{Name of the dataset to return evaluation results for.}
\item{eval_name}{Name of the evaluation metric to return results for.}
\item{iters}{An integer vector of iterations you want to get evaluation results for. If NULL
(the default), evaluation results for all iterations will be returned.}
\item{is_err}{TRUE will return evaluation error instead}
}
\value{
numeric vector of evaluation result
}
\description{
Given a \code{lgb.Booster}, return evaluation results for a
particular metric on a particular dataset.
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
# train a regression model
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
data(agaricus.test, package = "lightgbm")
test <- agaricus.test
dtest <- lgb.Dataset.create.valid(dtrain, test$data, label = test$label)
params <- list(
objective = "regression"
, metric = "l2"
, min_data = 1L
, learning_rate = 1.0
, num_threads = 2L
)
valids <- list(test = dtest)
model <- lgb.train(
params = params
, data = dtrain
, nrounds = 5L
, valids = valids
)
# Examine valid data_name values
print(setdiff(names(model$record_evals), "start_iter"))
# Examine valid eval_name values for dataset "test"
print(names(model$record_evals[["test"]]))
# Get L2 values for "test" dataset
lgb.get.eval.result(model, "test", "l2")
}
}
+51
View File
@@ -0,0 +1,51 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.importance.R
\name{lgb.importance}
\alias{lgb.importance}
\title{Compute feature importance in a model}
\usage{
lgb.importance(model, percentage = TRUE)
}
\arguments{
\item{model}{object of class \code{lgb.Booster}.}
\item{percentage}{whether to show importance in relative percentage.}
}
\value{
For a tree model, a \code{data.table} with the following columns:
\itemize{
\item{\code{Feature}: Feature names in the model.}
\item{\code{Gain}: The total gain of this feature's splits.}
\item{\code{Cover}: The number of observation related to this feature.}
\item{\code{Frequency}: The number of times a feature split in trees.}
}
}
\description{
Creates a \code{data.table} of feature importances in a model.
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
params <- list(
objective = "binary"
, learning_rate = 0.1
, max_depth = -1L
, min_data_in_leaf = 1L
, min_sum_hessian_in_leaf = 1.0
, num_threads = 2L
)
model <- lgb.train(
params = params
, data = dtrain
, nrounds = 5L
)
tree_imp1 <- lgb.importance(model, percentage = TRUE)
tree_imp2 <- lgb.importance(model, percentage = FALSE)
}
}
+63
View File
@@ -0,0 +1,63 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.interpret.R
\name{lgb.interpret}
\alias{lgb.interpret}
\title{Compute feature contribution of prediction}
\usage{
lgb.interpret(model, data, idxset, num_iteration = NULL)
}
\arguments{
\item{model}{object of class \code{lgb.Booster}.}
\item{data}{a matrix object or a dgCMatrix object.}
\item{idxset}{an integer vector of indices of rows needed.}
\item{num_iteration}{number of iteration want to predict with, NULL or <= 0 means use best iteration.}
}
\value{
For regression, binary classification and lambdarank model, a \code{list} of \code{data.table}
with the following columns:
\itemize{
\item{\code{Feature}: Feature names in the model.}
\item{\code{Contribution}: The total contribution of this feature's splits.}
}
For multiclass classification, a \code{list} of \code{data.table} with the Feature column and
Contribution columns to each class.
}
\description{
Computes feature contribution components of rawscore prediction.
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
Logit <- function(x) log(x / (1.0 - x))
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
set_field(
dataset = dtrain
, field_name = "init_score"
, data = rep(Logit(mean(train$label)), length(train$label))
)
data(agaricus.test, package = "lightgbm")
test <- agaricus.test
params <- list(
objective = "binary"
, learning_rate = 0.1
, max_depth = -1L
, min_data_in_leaf = 1L
, min_sum_hessian_in_leaf = 1.0
, num_threads = 2L
)
model <- lgb.train(
params = params
, data = dtrain
, nrounds = 3L
)
tree_interpretation <- lgb.interpret(model, test$data, 1L:5L)
}
}
+14
View File
@@ -0,0 +1,14 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.interpret.R
\name{lgb.interprete}
\alias{lgb.interprete}
\title{DEPRECATED - use lgb.interpret() instead}
\usage{
lgb.interprete(...)
}
\arguments{
\item{...}{Arguments passed through to \code{lgb.interpret}}
}
\description{
Alias for \code{lgb.interpret}.
}
+52
View File
@@ -0,0 +1,52 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.Booster.R
\name{lgb.load}
\alias{lgb.load}
\title{Load LightGBM model}
\usage{
lgb.load(filename = NULL, model_str = NULL)
}
\arguments{
\item{filename}{path of model file}
\item{model_str}{a str containing the model (as a \code{character} or \code{raw} vector)}
}
\value{
lgb.Booster
}
\description{
Load LightGBM takes in either a file path or model string.
If both are provided, Load will default to loading from file
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
data(agaricus.test, package = "lightgbm")
test <- agaricus.test
dtest <- lgb.Dataset.create.valid(dtrain, test$data, label = test$label)
params <- list(
objective = "regression"
, metric = "l2"
, min_data = 1L
, learning_rate = 1.0
, num_threads = 2L
)
valids <- list(test = dtest)
model <- lgb.train(
params = params
, data = dtrain
, nrounds = 5L
, valids = valids
, early_stopping_rounds = 3L
)
model_file <- tempfile(fileext = ".txt")
lgb.save(model, model_file)
load_booster <- lgb.load(filename = model_file)
model_string <- model$save_model_to_string(NULL) # saves best iteration
load_booster_from_str <- lgb.load(model_str = model_string)
}
}
+25
View File
@@ -0,0 +1,25 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.make_serializable.R
\name{lgb.make_serializable}
\alias{lgb.make_serializable}
\title{Make a LightGBM object serializable by keeping raw bytes}
\usage{
lgb.make_serializable(model)
}
\arguments{
\item{model}{\code{lgb.Booster} object which was produced with `serializable=FALSE`.}
}
\value{
\code{lgb.Booster} (the same `model` object that was passed as input, as invisible).
}
\description{
If a LightGBM model object was produced with argument `serializable=FALSE`, the R object will not
be serializable (e.g. cannot save and load with \code{saveRDS} and \code{readRDS}) as it will lack the raw bytes
needed to reconstruct its underlying C++ object. This function can be used to forcibly produce those serialized
raw bytes and make the object serializable. Note that the object will be modified in-place.
\emph{New in version 4.0.0}
}
\seealso{
\link{lgb.restore_handle}, \link{lgb.drop_serialized}.
}
+67
View File
@@ -0,0 +1,67 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.model.dt.tree.R
\name{lgb.model.dt.tree}
\alias{lgb.model.dt.tree}
\title{Parse a LightGBM model json dump}
\usage{
lgb.model.dt.tree(model, num_iteration = NULL, start_iteration = 1L)
}
\arguments{
\item{model}{object of class \code{lgb.Booster}.}
\item{num_iteration}{Number of iterations to include. NULL or <= 0 means use best iteration.}
\item{start_iteration}{Index (1-based) of the first boosting round to include in the output.
For example, passing \code{start_iteration=5, num_iteration=3} for a regression model
means "return information about the fifth, sixth, and seventh trees".
\emph{New in version 4.4.0}}
}
\value{
A \code{data.table} with detailed information about model trees' nodes and leaves.
The columns of the \code{data.table} are:
\itemize{
\item{\code{tree_index}: ID of a tree in a model (integer)}
\item{\code{split_index}: ID of a node in a tree (integer)}
\item{\code{split_feature}: for a node, it's a feature name (character);
for a leaf, it simply labels it as \code{"NA"}}
\item{\code{node_parent}: ID of the parent node for current node (integer)}
\item{\code{leaf_index}: ID of a leaf in a tree (integer)}
\item{\code{leaf_parent}: ID of the parent node for current leaf (integer)}
\item{\code{split_gain}: Split gain of a node}
\item{\code{threshold}: Splitting threshold value of a node}
\item{\code{decision_type}: Decision type of a node}
\item{\code{default_left}: Determine how to handle NA value, TRUE -> Left, FALSE -> Right}
\item{\code{internal_value}: Node value}
\item{\code{internal_count}: The number of observation collected by a node}
\item{\code{leaf_value}: Leaf value}
\item{\code{leaf_count}: The number of observation collected by a leaf}
}
}
\description{
Parse a LightGBM model json dump into a \code{data.table} structure.
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
params <- list(
objective = "binary"
, learning_rate = 0.01
, num_leaves = 63L
, max_depth = -1L
, min_data_in_leaf = 1L
, min_sum_hessian_in_leaf = 1.0
, num_threads = 2L
)
model <- lgb.train(params, dtrain, 10L)
tree_dt <- lgb.model.dt.tree(model)
}
}
+64
View File
@@ -0,0 +1,64 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.plot.importance.R
\name{lgb.plot.importance}
\alias{lgb.plot.importance}
\title{Plot feature importance as a bar graph}
\usage{
lgb.plot.importance(
tree_imp,
top_n = 10L,
measure = "Gain",
left_margin = 10L,
cex = NULL
)
}
\arguments{
\item{tree_imp}{a \code{data.table} returned by \code{\link{lgb.importance}}.}
\item{top_n}{maximal number of top features to include into the plot.}
\item{measure}{the name of importance measure to plot, can be "Gain", "Cover" or "Frequency".}
\item{left_margin}{(base R barplot) allows to adjust the left margin size to fit feature names.}
\item{cex}{(base R barplot) passed as \code{cex.names} parameter to \code{\link[graphics]{barplot}}.
Set a number smaller than 1.0 to make the bar labels smaller than R's default and values
greater than 1.0 to make them larger.}
}
\value{
The \code{lgb.plot.importance} function creates a \code{barplot}
and silently returns a processed data.table with \code{top_n} features sorted by defined importance.
}
\description{
Plot previously calculated feature importance: Gain, Cover and Frequency, as a bar graph.
}
\details{
The graph represents each feature as a horizontal bar of length proportional to the defined importance of a feature.
Features are shown ranked in a decreasing importance order.
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
params <- list(
objective = "binary"
, learning_rate = 0.1
, min_data_in_leaf = 1L
, min_sum_hessian_in_leaf = 1.0
, num_threads = 2L
)
model <- lgb.train(
params = params
, data = dtrain
, nrounds = 5L
)
tree_imp <- lgb.importance(model, percentage = TRUE)
lgb.plot.importance(tree_imp, top_n = 5L, measure = "Gain")
}
}
+81
View File
@@ -0,0 +1,81 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.plot.interpretation.R
\name{lgb.plot.interpretation}
\alias{lgb.plot.interpretation}
\title{Plot feature contribution as a bar graph}
\usage{
lgb.plot.interpretation(
tree_interpretation_dt,
top_n = 10L,
cols = 1L,
left_margin = 10L,
cex = NULL
)
}
\arguments{
\item{tree_interpretation_dt}{a \code{data.table} returned by \code{\link{lgb.interpret}}.}
\item{top_n}{maximal number of top features to include into the plot.}
\item{cols}{the column numbers of layout, will be used only for multiclass classification feature contribution.}
\item{left_margin}{(base R barplot) allows to adjust the left margin size to fit feature names.}
\item{cex}{(base R barplot) passed as \code{cex.names} parameter to \code{barplot}.}
}
\value{
The \code{lgb.plot.interpretation} function creates a \code{barplot}.
}
\description{
Plot previously calculated feature contribution as a bar graph.
}
\details{
The graph represents each feature as a horizontal bar of length proportional to the defined
contribution of a feature. Features are shown ranked in a decreasing contribution order.
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
Logit <- function(x) {
log(x / (1.0 - x))
}
data(agaricus.train, package = "lightgbm")
labels <- agaricus.train$label
dtrain <- lgb.Dataset(
agaricus.train$data
, label = labels
)
set_field(
dataset = dtrain
, field_name = "init_score"
, data = rep(Logit(mean(labels)), length(labels))
)
data(agaricus.test, package = "lightgbm")
params <- list(
objective = "binary"
, learning_rate = 0.1
, max_depth = -1L
, min_data_in_leaf = 1L
, min_sum_hessian_in_leaf = 1.0
, num_threads = 2L
)
model <- lgb.train(
params = params
, data = dtrain
, nrounds = 5L
)
tree_interpretation <- lgb.interpret(
model = model
, data = agaricus.test$data
, idxset = 1L:5L
)
lgb.plot.interpretation(
tree_interpretation_dt = tree_interpretation[[1L]]
, top_n = 3L
)
}
}
+54
View File
@@ -0,0 +1,54 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.restore_handle.R
\name{lgb.restore_handle}
\alias{lgb.restore_handle}
\title{Restore the C++ component of a de-serialized LightGBM model}
\usage{
lgb.restore_handle(model)
}
\arguments{
\item{model}{\code{lgb.Booster} object which was de-serialized and whose underlying C++ object and R handle
need to be restored.}
}
\value{
\code{lgb.Booster} (the same `model` object that was passed as input, invisibly).
}
\description{
After a LightGBM model object is de-serialized through functions such as \code{save} or
\code{saveRDS}, its underlying C++ object will be blank and needs to be restored to able to use it. Such
object is restored automatically when calling functions such as \code{predict}, but this function can be
used to forcibly restore it beforehand. Note that the object will be modified in-place.
\emph{New in version 4.0.0}
}
\details{
Be aware that fast single-row prediction configurations are not restored through this
function. If you wish to make fast single-row predictions using a \code{lgb.Booster} loaded this way,
call \link{lgb.configure_fast_predict} on the loaded \code{lgb.Booster} object.
}
\examples{
\donttest{
library(lightgbm)
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
data("agaricus.train")
model <- lightgbm(
agaricus.train$data
, agaricus.train$label
, params = list(objective = "binary")
, nrounds = 5L
, verbose = 0
, num_threads = 2L
)
fname <- tempfile(fileext="rds")
saveRDS(model, fname)
model_new <- readRDS(fname)
model_new$check_null_handle()
lgb.restore_handle(model_new)
model_new$check_null_handle()
}
}
\seealso{
\link{lgb.make_serializable}, \link{lgb.drop_serialized}.
}
+56
View File
@@ -0,0 +1,56 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.Booster.R
\name{lgb.save}
\alias{lgb.save}
\title{Save LightGBM model}
\usage{
lgb.save(booster, filename, num_iteration = NULL, start_iteration = 1L)
}
\arguments{
\item{booster}{Object of class \code{lgb.Booster}}
\item{filename}{Saved filename}
\item{num_iteration}{Number of iterations to save, NULL or <= 0 means use best iteration}
\item{start_iteration}{Index (1-based) of the first boosting round to save.
For example, passing \code{start_iteration=5, num_iteration=3} for a regression model
means "save the fifth, sixth, and seventh tree"
\emph{New in version 4.4.0}}
}
\value{
lgb.Booster
}
\description{
Save LightGBM model
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
library(lightgbm)
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
data(agaricus.test, package = "lightgbm")
test <- agaricus.test
dtest <- lgb.Dataset.create.valid(dtrain, test$data, label = test$label)
params <- list(
objective = "regression"
, metric = "l2"
, min_data = 1L
, learning_rate = 1.0
, num_threads = 2L
)
valids <- list(test = dtest)
model <- lgb.train(
params = params
, data = dtrain
, nrounds = 10L
, valids = valids
, early_stopping_rounds = 5L
)
lgb.save(model, tempfile(fileext = ".txt"))
}
}
+35
View File
@@ -0,0 +1,35 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.Dataset.R
\name{lgb.slice.Dataset}
\alias{lgb.slice.Dataset}
\title{Slice a dataset}
\usage{
lgb.slice.Dataset(dataset, idxset)
}
\arguments{
\item{dataset}{Object of class \code{lgb.Dataset}}
\item{idxset}{an integer vector of indices of rows needed}
}
\value{
constructed sub dataset
}
\description{
Get a new \code{lgb.Dataset} containing the specified rows of
original \code{lgb.Dataset} object
\emph{Renamed from} \code{slice()} \emph{in 4.4.0}
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
dsub <- lgb.slice.Dataset(dtrain, seq_len(42L))
lgb.Dataset.construct(dsub)
labels <- lightgbm::get_field(dsub, "label")
}
}
+153
View File
@@ -0,0 +1,153 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.train.R
\name{lgb.train}
\alias{lgb.train}
\title{Main training logic for LightGBM}
\usage{
lgb.train(
params = list(),
data,
nrounds = 100L,
valids = list(),
obj = NULL,
eval = NULL,
verbose = 1L,
record = TRUE,
eval_freq = 1L,
init_model = NULL,
early_stopping_rounds = NULL,
callbacks = list(),
reset_data = FALSE,
serializable = TRUE
)
}
\arguments{
\item{params}{a list of parameters. See \href{https://lightgbm.readthedocs.io/en/latest/Parameters.html}{
the "Parameters" section of the documentation} for a list of parameters and valid values.}
\item{data}{a \code{lgb.Dataset} object, used for training. Some functions, such as \code{\link{lgb.cv}},
may allow you to pass other types of data like \code{matrix} and then separately supply
\code{label} as a keyword argument.}
\item{nrounds}{number of training rounds}
\item{valids}{a list of \code{lgb.Dataset} objects, used for validation}
\item{obj}{objective function, can be character or custom objective function. Examples include
\code{regression}, \code{regression_l1}, \code{huber},
\code{binary}, \code{lambdarank}, \code{multiclass}, \code{multiclass}}
\item{eval}{evaluation function(s). This can be a character vector, function, or list with a mixture of
strings and functions.
\itemize{
\item{\bold{a. character vector}:
If you provide a character vector to this argument, it should contain strings with valid
evaluation metrics.
See \href{https://lightgbm.readthedocs.io/en/latest/Parameters.html#metric}{
The "metric" section of the documentation}
for a list of valid metrics.
}
\item{\bold{b. function}:
You can provide a custom evaluation function. This
should accept the keyword arguments \code{preds} and \code{dtrain} and should return a named
list with three elements:
\itemize{
\item{\code{name}: A string with the name of the metric, used for printing
and storing results.
}
\item{\code{value}: A single number indicating the value of the metric for the
given predictions and true values
}
\item{
\code{higher_better}: A boolean indicating whether higher values indicate a better fit.
For example, this would be \code{FALSE} for metrics like MAE or RMSE.
}
}
}
\item{\bold{c. list}:
If a list is given, it should only contain character vectors and functions.
These should follow the requirements from the descriptions above.
}
}}
\item{verbose}{verbosity for output, if <= 0 and \code{valids} has been provided, also will disable the
printing of evaluation during training}
\item{record}{Boolean, TRUE will record iteration message to \code{booster$record_evals}}
\item{eval_freq}{evaluation output frequency, only effective when verbose > 0 and \code{valids} has been provided}
\item{init_model}{path of model file or \code{lgb.Booster} object, will continue training from this model}
\item{early_stopping_rounds}{int. Activates early stopping. When this parameter is non-null,
training will stop if the evaluation of any metric on any validation set
fails to improve for \code{early_stopping_rounds} consecutive boosting rounds.
If training stops early, the returned model will have attribute \code{best_iter}
set to the iteration number of the best iteration.}
\item{callbacks}{List of callback functions that are applied at each iteration.}
\item{reset_data}{Boolean, setting it to TRUE (not the default value) will transform the
booster model into a predictor model which frees up memory and the
original datasets}
\item{serializable}{whether to make the resulting objects serializable through functions such as
\code{save} or \code{saveRDS} (see section "Model serialization").}
}
\value{
a trained booster model \code{lgb.Booster}.
}
\description{
Low-level R interface to train a LightGBM model. Unlike \code{\link{lightgbm}},
this function is focused on performance (e.g. speed, memory efficiency). It is also
less likely to have breaking API changes in new releases than \code{\link{lightgbm}}.
}
\section{Early Stopping}{
"early stopping" refers to stopping the training process if the model's performance on a given
validation set does not improve for several consecutive iterations.
If multiple arguments are given to \code{eval}, their order will be preserved. If you enable
early stopping by setting \code{early_stopping_rounds} in \code{params}, by default all
metrics will be considered for early stopping.
If you want to only consider the first metric for early stopping, pass
\code{first_metric_only = TRUE} in \code{params}. Note that if you also specify \code{metric}
in \code{params}, that metric will be considered the "first" one. If you omit \code{metric},
a default metric will be used based on your choice for the parameter \code{obj} (keyword argument)
or \code{objective} (passed into \code{params}).
\bold{NOTE:} if using \code{boosting_type="dart"}, any early stopping configuration will be ignored
and early stopping will not be performed.
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
data(agaricus.test, package = "lightgbm")
test <- agaricus.test
dtest <- lgb.Dataset.create.valid(dtrain, test$data, label = test$label)
params <- list(
objective = "regression"
, metric = "l2"
, min_data = 1L
, learning_rate = 1.0
, num_threads = 2L
)
valids <- list(test = dtest)
model <- lgb.train(
params = params
, data = dtrain
, nrounds = 5L
, valids = valids
, early_stopping_rounds = 3L
)
}
}
@@ -0,0 +1,57 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.Booster.R
\name{lgb_predict_shared_params}
\alias{lgb_predict_shared_params}
\title{Shared prediction parameter docs}
\arguments{
\item{type}{Type of prediction to output. Allowed types are:\itemize{
\item \code{"response"}: will output the predicted score according to the objective function being
optimized (depending on the link function that the objective uses), after applying any necessary
transformations - for example, for \code{objective="binary"}, it will output class probabilities.
\item \code{"class"}: for classification objectives, will output the class with the highest predicted
probability. For other objectives, will output the same as "response". Note that \code{"class"} is
not a supported type for \link{lgb.configure_fast_predict} (see the documentation of that function
for more details).
\item \code{"raw"}: will output the non-transformed numbers (sum of predictions from boosting iterations'
results) from which the "response" number is produced for a given objective function - for example,
for \code{objective="binary"}, this corresponds to log-odds. For many objectives such as
"regression", since no transformation is applied, the output will be the same as for "response".
\item \code{"leaf"}: will output the index of the terminal node / leaf at which each observations falls
in each tree in the model, outputted as integers, with one column per tree.
\item \code{"contrib"}: will return the per-feature contributions for each prediction, including an
intercept (each feature will produce one column).
}
Note that, if using custom objectives, types "class" and "response" will not be available and will
default towards using "raw" instead.
If the model was fit through function \link{lightgbm} and it was passed a factor as labels,
passing the prediction type through \code{params} instead of through this argument might
result in factor levels for classification objectives not being applied correctly to the
resulting output.
\emph{New in version 4.0.0}}
\item{start_iteration}{int or None, optional (default=None)
Start index of the iteration to predict.
If None or <= 0, starts from the first iteration.}
\item{num_iteration}{int or None, optional (default=None)
Limit number of iterations in the prediction.
If None, if the best iteration exists and start_iteration is None or <= 0, the
best iteration is used; otherwise, all iterations from start_iteration are used.
If <= 0, all iterations from start_iteration are used (no limits).}
\item{params}{a list of additional named parameters. See
\href{https://lightgbm.readthedocs.io/en/latest/Parameters.html#predict-parameters}{
the "Predict Parameters" section of the documentation} for a list of parameters and
valid values. Where these conflict with the values of keyword arguments to this function,
the values in \code{params} take precedence.}
}
\description{
Shared prediction parameter docs
}
\details{
This page contains shared documentation for prediction-related parameters used throughout the package.
}
\keyword{internal}
@@ -0,0 +1,26 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.Dataset.R
\name{lgb_shared_dataset_params}
\alias{lgb_shared_dataset_params}
\title{Shared Dataset parameter docs}
\arguments{
\item{label}{vector of labels to use as the target variable}
\item{weight}{numeric vector of sample weights}
\item{init_score}{initial score is the base prediction lightgbm will boost from}
\item{group}{used for learning-to-rank tasks. An integer vector describing how to
group rows together as ordered results from the same set of candidate results
to be ranked. For example, if you have a 100-document dataset with
\code{group = c(10, 20, 40, 10, 10, 10)}, that means that you have 6 groups,
where the first 10 records are in the first group, records 11-30 are in the
second group, etc.}
}
\description{
Parameter docs for fields used in \code{lgb.Dataset} construction
}
\details{
This page contains shared documentation for dataset-related parameters used throughout the package.
}
\keyword{internal}
+115
View File
@@ -0,0 +1,115 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lightgbm.R
\name{lgb_shared_params}
\alias{lgb_shared_params}
\title{Shared parameter docs}
\arguments{
\item{callbacks}{List of callback functions that are applied at each iteration.}
\item{data}{a \code{lgb.Dataset} object, used for training. Some functions, such as \code{\link{lgb.cv}},
may allow you to pass other types of data like \code{matrix} and then separately supply
\code{label} as a keyword argument.}
\item{early_stopping_rounds}{int. Activates early stopping. When this parameter is non-null,
training will stop if the evaluation of any metric on any validation set
fails to improve for \code{early_stopping_rounds} consecutive boosting rounds.
If training stops early, the returned model will have attribute \code{best_iter}
set to the iteration number of the best iteration.}
\item{eval}{evaluation function(s). This can be a character vector, function, or list with a mixture of
strings and functions.
\itemize{
\item{\bold{a. character vector}:
If you provide a character vector to this argument, it should contain strings with valid
evaluation metrics.
See \href{https://lightgbm.readthedocs.io/en/latest/Parameters.html#metric}{
The "metric" section of the documentation}
for a list of valid metrics.
}
\item{\bold{b. function}:
You can provide a custom evaluation function. This
should accept the keyword arguments \code{preds} and \code{dtrain} and should return a named
list with three elements:
\itemize{
\item{\code{name}: A string with the name of the metric, used for printing
and storing results.
}
\item{\code{value}: A single number indicating the value of the metric for the
given predictions and true values
}
\item{
\code{higher_better}: A boolean indicating whether higher values indicate a better fit.
For example, this would be \code{FALSE} for metrics like MAE or RMSE.
}
}
}
\item{\bold{c. list}:
If a list is given, it should only contain character vectors and functions.
These should follow the requirements from the descriptions above.
}
}}
\item{eval_freq}{evaluation output frequency, only effective when verbose > 0 and \code{valids} has been provided}
\item{init_model}{path of model file or \code{lgb.Booster} object, will continue training from this model}
\item{nrounds}{number of training rounds}
\item{obj}{objective function, can be character or custom objective function. Examples include
\code{regression}, \code{regression_l1}, \code{huber},
\code{binary}, \code{lambdarank}, \code{multiclass}, \code{multiclass}}
\item{params}{a list of parameters. See \href{https://lightgbm.readthedocs.io/en/latest/Parameters.html}{
the "Parameters" section of the documentation} for a list of parameters and valid values.}
\item{verbose}{verbosity for output, if <= 0 and \code{valids} has been provided, also will disable the
printing of evaluation during training}
\item{serializable}{whether to make the resulting objects serializable through functions such as
\code{save} or \code{saveRDS} (see section "Model serialization").}
}
\description{
Parameter docs shared by \code{lgb.train}, \code{lgb.cv}, and \code{lightgbm}
}
\section{Early Stopping}{
"early stopping" refers to stopping the training process if the model's performance on a given
validation set does not improve for several consecutive iterations.
If multiple arguments are given to \code{eval}, their order will be preserved. If you enable
early stopping by setting \code{early_stopping_rounds} in \code{params}, by default all
metrics will be considered for early stopping.
If you want to only consider the first metric for early stopping, pass
\code{first_metric_only = TRUE} in \code{params}. Note that if you also specify \code{metric}
in \code{params}, that metric will be considered the "first" one. If you omit \code{metric},
a default metric will be used based on your choice for the parameter \code{obj} (keyword argument)
or \code{objective} (passed into \code{params}).
\bold{NOTE:} if using \code{boosting_type="dart"}, any early stopping configuration will be ignored
and early stopping will not be performed.
}
\section{Model serialization}{
LightGBM model objects can be serialized and de-serialized through functions such as \code{save}
or \code{saveRDS}, but similarly to libraries such as 'xgboost', serialization works a bit differently
from typical R objects. In order to make models serializable in R, a copy of the underlying C++ object
as serialized raw bytes is produced and stored in the R model object, and when this R object is
de-serialized, the underlying C++ model object gets reconstructed from these raw bytes, but will only
do so once some function that uses it is called, such as \code{predict}. In order to forcibly
reconstruct the C++ object after deserialization (e.g. after calling \code{readRDS} or similar), one
can use the function \link{lgb.restore_handle} (for example, if one makes predictions in parallel or in
forked processes, it will be faster to restore the handle beforehand).
Producing and keeping these raw bytes however uses extra memory, and if they are not required,
it is possible to avoid producing them by passing `serializable=FALSE`. In such cases, these raw
bytes can be added to the model on demand through function \link{lgb.make_serializable}.
\emph{New in version 4.0.0}
}
\keyword{internal}
+150
View File
@@ -0,0 +1,150 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lightgbm.R
\name{lightgbm}
\alias{lightgbm}
\title{Train a LightGBM model}
\usage{
lightgbm(
data,
label = NULL,
weights = NULL,
params = list(),
nrounds = 100L,
verbose = 1L,
eval_freq = 1L,
early_stopping_rounds = NULL,
init_model = NULL,
callbacks = list(),
serializable = TRUE,
objective = "auto",
init_score = NULL,
num_threads = NULL,
colnames = NULL,
categorical_feature = NULL,
...
)
}
\arguments{
\item{data}{a \code{lgb.Dataset} object, used for training. Some functions, such as \code{\link{lgb.cv}},
may allow you to pass other types of data like \code{matrix} and then separately supply
\code{label} as a keyword argument.}
\item{label}{Vector of labels, used if \code{data} is not an \code{\link{lgb.Dataset}}}
\item{weights}{Sample / observation weights for rows in the input data. If \code{NULL}, will assume that all
observations / rows have the same importance / weight.
\emph{Changed from 'weight', in version 4.0.0}}
\item{params}{a list of parameters. See \href{https://lightgbm.readthedocs.io/en/latest/Parameters.html}{
the "Parameters" section of the documentation} for a list of parameters and valid values.}
\item{nrounds}{number of training rounds}
\item{verbose}{verbosity for output, if <= 0 and \code{valids} has been provided, also will disable the
printing of evaluation during training}
\item{eval_freq}{evaluation output frequency, only effective when verbose > 0 and \code{valids} has been provided}
\item{early_stopping_rounds}{int. Activates early stopping. When this parameter is non-null,
training will stop if the evaluation of any metric on any validation set
fails to improve for \code{early_stopping_rounds} consecutive boosting rounds.
If training stops early, the returned model will have attribute \code{best_iter}
set to the iteration number of the best iteration.}
\item{init_model}{path of model file or \code{lgb.Booster} object, will continue training from this model}
\item{callbacks}{List of callback functions that are applied at each iteration.}
\item{serializable}{whether to make the resulting objects serializable through functions such as
\code{save} or \code{saveRDS} (see section "Model serialization").}
\item{objective}{Optimization objective (e.g. `"regression"`, `"binary"`, etc.).
For a list of accepted objectives, see
\href{https://lightgbm.readthedocs.io/en/latest/Parameters.html#objective}{
the "objective" item of the "Parameters" section of the documentation}.
If passing \code{"auto"} and \code{data} is not of type \code{lgb.Dataset}, the objective will
be determined according to what is passed for \code{label}:\itemize{
\item If passing a factor with two variables, will use objective \code{"binary"}.
\item If passing a factor with more than two variables, will use objective \code{"multiclass"}
(note that parameter \code{num_class} in this case will also be determined automatically from
\code{label}).
\item Otherwise (or if passing \code{lgb.Dataset} as input), will use objective \code{"regression"}.
}
\emph{New in version 4.0.0}}
\item{init_score}{initial score is the base prediction lightgbm will boost from
\emph{New in version 4.0.0}}
\item{num_threads}{Number of parallel threads to use. For best speed, this should be set to the number of
physical cores in the CPU - in a typical x86-64 machine, this corresponds to half the
number of maximum threads.
Be aware that using too many threads can result in speed degradation in smaller datasets
(see the parameters documentation for more details).
If passing zero, will use the default number of threads configured for OpenMP
(typically controlled through an environment variable \code{OMP_NUM_THREADS}).
If passing \code{NULL} (the default), will try to use the number of physical cores in the
system, but be aware that getting the number of cores detected correctly requires package
\code{RhpcBLASctl} to be installed.
This parameter gets overridden by \code{num_threads} and its aliases under \code{params}
if passed there.
\emph{New in version 4.0.0}}
\item{colnames}{Character vector of features. Only used if \code{data} is not an \code{\link{lgb.Dataset}}.}
\item{categorical_feature}{categorical features. This can either be a character vector of feature
names or an integer vector with the indices of the features (e.g.
\code{c(1L, 10L)} to say "the first and tenth columns").
Only used if \code{data} is not an \code{\link{lgb.Dataset}}.}
\item{...}{Additional arguments passed to \code{\link{lgb.train}}. For example
\itemize{
\item{\code{valids}: a list of \code{lgb.Dataset} objects, used for validation}
\item{\code{obj}: objective function, can be character or custom objective function. Examples include
\code{regression}, \code{regression_l1}, \code{huber},
\code{binary}, \code{lambdarank}, \code{multiclass}, \code{multiclass}}
\item{\code{eval}: evaluation function, can be (a list of) character or custom eval function}
\item{\code{record}: Boolean, TRUE will record iteration message to \code{booster$record_evals}}
\item{\code{reset_data}: Boolean, setting it to TRUE (not the default value) will transform the booster model
into a predictor model which frees up memory and the original datasets}
}}
}
\value{
a trained \code{lgb.Booster}
}
\description{
High-level R interface to train a LightGBM model. Unlike \code{\link{lgb.train}}, this function
is focused on compatibility with other statistics and machine learning interfaces in R.
This focus on compatibility means that this interface may experience more frequent breaking API changes
than \code{\link{lgb.train}}.
For efficiency-sensitive applications, or for applications where breaking API changes across releases
is very expensive, use \code{\link{lgb.train}}.
}
\section{Early Stopping}{
"early stopping" refers to stopping the training process if the model's performance on a given
validation set does not improve for several consecutive iterations.
If multiple arguments are given to \code{eval}, their order will be preserved. If you enable
early stopping by setting \code{early_stopping_rounds} in \code{params}, by default all
metrics will be considered for early stopping.
If you want to only consider the first metric for early stopping, pass
\code{first_metric_only = TRUE} in \code{params}. Note that if you also specify \code{metric}
in \code{params}, that metric will be considered the "first" one. If you omit \code{metric},
a default metric will be used based on your choice for the parameter \code{obj} (keyword argument)
or \code{objective} (passed into \code{params}).
\bold{NOTE:} if using \code{boosting_type="dart"}, any early stopping configuration will be ignored
and early stopping will not be performed.
}
+157
View File
@@ -0,0 +1,157 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.Booster.R
\name{predict.lgb.Booster}
\alias{predict.lgb.Booster}
\title{Predict method for LightGBM model}
\usage{
\method{predict}{lgb.Booster}(
object,
newdata,
type = "response",
start_iteration = NULL,
num_iteration = NULL,
header = FALSE,
params = list(),
...
)
}
\arguments{
\item{object}{Object of class \code{lgb.Booster}}
\item{newdata}{a \code{matrix} object, a \code{dgCMatrix}, a \code{dgRMatrix} object, a \code{dsparseVector} object,
or a character representing a path to a text file (CSV, TSV, or LibSVM).
For sparse inputs, if predictions are only going to be made for a single row, it will be faster to
use CSR format, in which case the data may be passed as either a single-row CSR matrix (class
\code{dgRMatrix} from package \code{Matrix}) or as a sparse numeric vector (class
\code{dsparseVector} from package \code{Matrix}).
If single-row predictions are going to be performed frequently, it is recommended to
pre-configure the model object for fast single-row sparse predictions through function
\link{lgb.configure_fast_predict}.
\emph{Changed from 'data', in version 4.0.0}}
\item{type}{Type of prediction to output. Allowed types are:\itemize{
\item \code{"response"}: will output the predicted score according to the objective function being
optimized (depending on the link function that the objective uses), after applying any necessary
transformations - for example, for \code{objective="binary"}, it will output class probabilities.
\item \code{"class"}: for classification objectives, will output the class with the highest predicted
probability. For other objectives, will output the same as "response". Note that \code{"class"} is
not a supported type for \link{lgb.configure_fast_predict} (see the documentation of that function
for more details).
\item \code{"raw"}: will output the non-transformed numbers (sum of predictions from boosting iterations'
results) from which the "response" number is produced for a given objective function - for example,
for \code{objective="binary"}, this corresponds to log-odds. For many objectives such as
"regression", since no transformation is applied, the output will be the same as for "response".
\item \code{"leaf"}: will output the index of the terminal node / leaf at which each observations falls
in each tree in the model, outputted as integers, with one column per tree.
\item \code{"contrib"}: will return the per-feature contributions for each prediction, including an
intercept (each feature will produce one column).
}
Note that, if using custom objectives, types "class" and "response" will not be available and will
default towards using "raw" instead.
If the model was fit through function \link{lightgbm} and it was passed a factor as labels,
passing the prediction type through \code{params} instead of through this argument might
result in factor levels for classification objectives not being applied correctly to the
resulting output.
\emph{New in version 4.0.0}}
\item{start_iteration}{int or None, optional (default=None)
Start index of the iteration to predict.
If None or <= 0, starts from the first iteration.}
\item{num_iteration}{int or None, optional (default=None)
Limit number of iterations in the prediction.
If None, if the best iteration exists and start_iteration is None or <= 0, the
best iteration is used; otherwise, all iterations from start_iteration are used.
If <= 0, all iterations from start_iteration are used (no limits).}
\item{header}{only used for prediction for text file. True if text file has header}
\item{params}{a list of additional named parameters. See
\href{https://lightgbm.readthedocs.io/en/latest/Parameters.html#predict-parameters}{
the "Predict Parameters" section of the documentation} for a list of parameters and
valid values. Where these conflict with the values of keyword arguments to this function,
the values in \code{params} take precedence.}
\item{...}{ignored}
}
\value{
For prediction types that are meant to always return one output per observation (e.g. when predicting
\code{type="response"} or \code{type="raw"} on a binary classification or regression objective), will
return a vector with one element per row in \code{newdata}.
For prediction types that are meant to return more than one output per observation (e.g. when predicting
\code{type="response"} or \code{type="raw"} on a multi-class objective, or when predicting
\code{type="leaf"}, regardless of objective), will return a matrix with one row per observation in
\code{newdata} and one column per output.
For \code{type="leaf"} predictions, will return a matrix with one row per observation in \code{newdata}
and one column per tree. Note that for multiclass objectives, LightGBM trains one tree per class at each
boosting iteration. That means that, for example, for a multiclass model with 3 classes, the leaf
predictions for the first class can be found in columns 1, 4, 7, 10, etc.
For \code{type="contrib"}, will return a matrix of SHAP values with one row per observation in
\code{newdata} and columns corresponding to features. For regression, ranking, cross-entropy, and binary
classification objectives, this matrix contains one column per feature plus a final column containing the
Shapley base value. For multiclass objectives, this matrix will represent \code{num_classes} such matrices,
in the order "feature contributions for first class, feature contributions for second class, feature
contributions for third class, etc.".
If the model was fit through function \link{lightgbm} and it was passed a factor as labels, predictions
returned from this function will retain the factor levels (either as values for \code{type="class"}, or
as column names for \code{type="response"} and \code{type="raw"} for multi-class objectives). Note that
passing the requested prediction type under \code{params} instead of through \code{type} might result in
the factor levels not being present in the output.
}
\description{
Predicted values based on class \code{lgb.Booster}
\emph{New in version 4.0.0}
}
\details{
If the model object has been configured for fast single-row predictions through
\link{lgb.configure_fast_predict}, this function will use the prediction parameters
that were configured for it - as such, extra prediction parameters should not be passed
here, otherwise the configuration will be ignored and the slow route will be taken.
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
data(agaricus.test, package = "lightgbm")
test <- agaricus.test
dtest <- lgb.Dataset.create.valid(dtrain, test$data, label = test$label)
params <- list(
objective = "regression"
, metric = "l2"
, min_data = 1L
, learning_rate = 1.0
, num_threads = 2L
)
valids <- list(test = dtest)
model <- lgb.train(
params = params
, data = dtrain
, nrounds = 5L
, valids = valids
)
preds <- predict(model, test$data)
# pass other prediction parameters
preds <- predict(
model,
test$data,
params = list(
predict_disable_shape_check = TRUE
)
)
}
}
+21
View File
@@ -0,0 +1,21 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.Booster.R
\name{print.lgb.Booster}
\alias{print.lgb.Booster}
\title{Print method for LightGBM model}
\usage{
\method{print}{lgb.Booster}(x, ...)
}
\arguments{
\item{x}{Object of class \code{lgb.Booster}}
\item{...}{Not used}
}
\value{
The same input \code{x}, returned as invisible.
}
\description{
Show summary information about a LightGBM model object (same as \code{summary}).
\emph{New in version 4.0.0}
}
+32
View File
@@ -0,0 +1,32 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/multithreading.R
\name{setLGBMThreads}
\alias{setLGBMThreads}
\alias{setLGBMthreads}
\title{Set maximum number of threads used by LightGBM}
\usage{
setLGBMthreads(num_threads)
}
\arguments{
\item{num_threads}{maximum number of threads to be used by LightGBM in multi-threaded operations}
}
\description{
LightGBM attempts to speed up many operations by using multi-threading.
The number of threads used in those operations can be controlled via the
\code{num_threads} parameter passed through \code{params} to functions like
\link{lgb.train} and \link{lgb.Dataset}. However, some operations (like materializing
a model from a text file) are done via code paths that don't explicitly accept thread-control
configuration.
Use this function to set the maximum number of threads LightGBM will use for such operations.
This function affects all LightGBM operations in the same process.
So, for example, if you call \code{setLGBMthreads(4)}, no other multi-threaded LightGBM
operation in the same process will use more than 4 threads.
Call \code{setLGBMthreads(-1)} to remove this limitation.
}
\seealso{
\link{getLGBMthreads}
}
+50
View File
@@ -0,0 +1,50 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.Dataset.R
\name{set_field}
\alias{set_field}
\alias{set_field.lgb.Dataset}
\title{Set one attribute of a \code{lgb.Dataset} object}
\usage{
set_field(dataset, field_name, data)
\method{set_field}{lgb.Dataset}(dataset, field_name, data)
}
\arguments{
\item{dataset}{Object of class \code{lgb.Dataset}}
\item{field_name}{String with the name of the attribute to set. One of the following.
\itemize{
\item \code{label}: label lightgbm learns from ;
\item \code{weight}: to do a weight rescale ;
\item{\code{group}: used for learning-to-rank tasks. An integer vector describing how to
group rows together as ordered results from the same set of candidate results to be ranked.
For example, if you have a 100-document dataset with \code{group = c(10, 20, 40, 10, 10, 10)},
that means that you have 6 groups, where the first 10 records are in the first group,
records 11-30 are in the second group, etc.}
\item \code{init_score}: initial score is the base prediction lightgbm will boost from.
}}
\item{data}{The data for the field. See examples.}
}
\value{
The \code{lgb.Dataset} you passed in.
}
\description{
Set one attribute of a \code{lgb.Dataset}
}
\examples{
\donttest{
\dontshow{setLGBMthreads(2L)}
\dontshow{data.table::setDTthreads(1L)}
data(agaricus.train, package = "lightgbm")
train <- agaricus.train
dtrain <- lgb.Dataset(train$data, label = train$label)
lgb.Dataset.construct(dtrain)
labels <- lightgbm::get_field(dtrain, "label")
lightgbm::set_field(dtrain, "label", 1 - labels)
labels2 <- lightgbm::get_field(dtrain, "label")
stopifnot(all.equal(labels2, 1 - labels))
}
}
+21
View File
@@ -0,0 +1,21 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/lgb.Booster.R
\name{summary.lgb.Booster}
\alias{summary.lgb.Booster}
\title{Summary method for LightGBM model}
\usage{
\method{summary}{lgb.Booster}(object, ...)
}
\arguments{
\item{object}{Object of class \code{lgb.Booster}}
\item{...}{Not used}
}
\value{
The same input \code{object}, returned as invisible.
}
\description{
Show summary information about a LightGBM model object (same as \code{print}).
\emph{New in version 4.0.0}
}
+111
View File
@@ -0,0 +1,111 @@
template:
bootstrap: 5
params:
bootswatch: cerulean
site:
root: ''
title: LightGBM, Light Gradient Boosting Machine
repo:
url:
home: https://github.com/lightgbm-org/LightGBM/
source: https://github.com/lightgbm-org/LightGBM/tree/main/R-package/
issue: https://github.com/lightgbm-org/LightGBM/issues/
user: https://github.com/
development:
mode: unreleased
authors:
Yu Shi:
href: https://github.com/shiyu1994
html: <img src="https://avatars.githubusercontent.com/u/14541765?s=400&v=4" height="48" /> Yu Shi
Guolin Ke:
href: https://github.com/guolinke
html: <img src="https://avatars.githubusercontent.com/u/16040950?s=400&v=4" height="48" /> Guolin Ke
Damien Soukhavong:
href: https://github.com/Laurae2
html: <img src="https://avatars.githubusercontent.com/u/9083669?s=400&v=4" height="48" /> Damien Soukhavong
Yachen Yan:
href: https://github.com/yanyachen
html: <img src="https://avatars.githubusercontent.com/u/6893682?s=400&v=4" height="48" /> Yachen Yan
James Lamb:
href: https://github.com/jameslamb
html: <img src="https://avatars.githubusercontent.com/u/7608904?s=400&v=4" height="48" /> James Lamb
navbar:
title: LightGBM
type: default
left:
- icon: fa-reply fa-lg
href: ../
- icon: fa-home fa-lg
href: index.html
- text: Articles
href: articles/index.html
- text: Reference
href: reference/index.html
right:
- icon: fa-github fa-lg
href: https://github.com/lightgbm-org/LightGBM/tree/main/R-package
reference:
- title: Datasets
desc: Datasets included with the R-package
contents:
- '`agaricus.train`'
- '`agaricus.test`'
- '`bank`'
- title: Data Input / Output
desc: Data I/O required for LightGBM
contents:
- '`dim.lgb.Dataset`'
- '`dimnames.lgb.Dataset`'
- '`get_field`'
- '`set_field`'
- '`lgb.Dataset`'
- '`lgb.Dataset.construct`'
- '`lgb.Dataset.create.valid`'
- '`lgb.Dataset.save`'
- '`lgb.Dataset.set.categorical`'
- '`lgb.Dataset.set.reference`'
- '`lgb.convert_with_rules`'
- '`lgb.slice.Dataset`'
- title: Machine Learning
desc: Train models with LightGBM and then use them to make predictions on new data
contents:
- '`lightgbm`'
- '`lgb.train`'
- '`predict.lgb.Booster`'
- '`lgb.cv`'
- '`lgb.configure_fast_predict`'
- title: Saving / Loading Models
desc: Save and load LightGBM models
contents:
- '`lgb.dump`'
- '`lgb.save`'
- '`lgb.load`'
- '`lgb.model.dt.tree`'
- '`lgb.drop_serialized`'
- '`lgb.make_serializable`'
- '`lgb.restore_handle`'
- title: Model Interpretation
desc: Analyze your models
contents:
- '`lgb.get.eval.result`'
- '`lgb.importance`'
- '`lgb.interpret`'
- '`lgb.plot.importance`'
- '`lgb.plot.interpretation`'
- '`print.lgb.Booster`'
- '`summary.lgb.Booster`'
- title: Multithreading Control
desc: Manage degree of parallelism used by LightGBM
contents:
- '`getLGBMThreads`'
- '`setLGBMThreads`'
- title: Deprecated
desc: Functionality that will be removed in the future
contents:
- '`lgb.interprete`'
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
set -e -E -u -o pipefail
# recreates 'configure' from 'configure.ac'
# this script should run on Ubuntu 22.04
AUTOCONF_VERSION=$(cat R-package/AUTOCONF_UBUNTU_VERSION)
# R packages cannot have versions like 3.0.0rc1, but
# 3.0.0-1 is acceptable
LGB_VERSION=$(sed "s/rc/-/g" < VERSION.txt)
# this script changes configure.ac. Copying to a temporary file
# so changes to configure.ac don't get committed in git
TMP_CONFIGURE_AC=".configure.ac"
echo "Creating 'configure' script with Autoconf ${AUTOCONF_VERSION}"
apt update
apt-get install \
--no-install-recommends \
-y \
autoconf="${AUTOCONF_VERSION}"
cd R-package
cp configure.ac ${TMP_CONFIGURE_AC}
sed -i.bak -e "s/~~VERSION~~/${LGB_VERSION}/" ${TMP_CONFIGURE_AC}
autoconf \
--output configure \
${TMP_CONFIGURE_AC} \
|| exit 1
rm ${TMP_CONFIGURE_AC}
rm -r autom4te.cache || echo "no autoconf cache found"
echo "done creating 'configure' script"
+59
View File
@@ -0,0 +1,59 @@
CXX_STD = CXX17
PKGROOT=.
LGB_CPPFLAGS = \
@LGB_CPPFLAGS@ \
-DUSE_SOCKET \
-DLGB_R_BUILD
PKG_CPPFLAGS = \
-I$(PKGROOT)/include \
$(LGB_CPPFLAGS)
PKG_CXXFLAGS = \
@OPENMP_CXXFLAGS@ \
-pthread
PKG_LIBS = \
@OPENMP_CXXFLAGS@ \
@OPENMP_LIB@ \
-pthread
OBJECTS = \
boosting/boosting.o \
boosting/gbdt.o \
boosting/gbdt_model_text.o \
boosting/gbdt_prediction.o \
boosting/prediction_early_stop.o \
boosting/sample_strategy.o \
io/bin.o \
io/config.o \
io/config_auto.o \
io/dataset.o \
io/dataset_loader.o \
io/file_io.o \
io/json11.o \
io/metadata.o \
io/parser.o \
io/train_share_states.o \
io/tree.o \
metric/dcg_calculator.o \
metric/metric.o \
objective/objective_function.o \
network/linker_topo.o \
network/linkers_mpi.o \
network/linkers_socket.o \
network/network.o \
treelearner/data_parallel_tree_learner.o \
treelearner/feature_histogram.o \
treelearner/feature_parallel_tree_learner.o \
treelearner/gpu_tree_learner.o \
treelearner/gradient_discretizer.o \
treelearner/linear_tree_learner.o \
treelearner/serial_tree_learner.o \
treelearner/tree_learner.o \
treelearner/voting_parallel_tree_learner.o \
utils/openmp_wrapper.o \
c_api.o \
lightgbm_R.o
+60
View File
@@ -0,0 +1,60 @@
CXX_STD = CXX17
PKGROOT=.
LGB_CPPFLAGS = \
@LGB_CPPFLAGS@ \
-DUSE_SOCKET \
-DLGB_R_BUILD
PKG_CPPFLAGS = \
-I$(PKGROOT)/include \
$(LGB_CPPFLAGS)
PKG_CXXFLAGS = \
${SHLIB_OPENMP_CXXFLAGS} \
${SHLIB_PTHREAD_FLAGS}
PKG_LIBS = \
${SHLIB_OPENMP_CXXFLAGS} \
${SHLIB_PTHREAD_FLAGS} \
-lws2_32 \
-liphlpapi
OBJECTS = \
boosting/boosting.o \
boosting/gbdt.o \
boosting/gbdt_model_text.o \
boosting/gbdt_prediction.o \
boosting/prediction_early_stop.o \
boosting/sample_strategy.o \
io/bin.o \
io/config.o \
io/config_auto.o \
io/dataset.o \
io/dataset_loader.o \
io/file_io.o \
io/json11.o \
io/metadata.o \
io/parser.o \
io/train_share_states.o \
io/tree.o \
metric/dcg_calculator.o \
metric/metric.o \
objective/objective_function.o \
network/linker_topo.o \
network/linkers_mpi.o \
network/linkers_socket.o \
network/network.o \
treelearner/data_parallel_tree_learner.o \
treelearner/feature_histogram.o \
treelearner/feature_parallel_tree_learner.o \
treelearner/gpu_tree_learner.o \
treelearner/gradient_discretizer.o \
treelearner/linear_tree_learner.o \
treelearner/serial_tree_learner.o \
treelearner/tree_learner.o \
treelearner/voting_parallel_tree_learner.o \
utils/openmp_wrapper.o \
c_api.o \
lightgbm_R.o
+242
View File
@@ -0,0 +1,242 @@
# User options
use_gpu <- FALSE
make_args_from_build_script <- character(0L)
# For Windows, the package will be built with Visual Studio
# unless you set one of these to TRUE
use_mingw <- FALSE
use_msys2 <- FALSE
if (use_mingw && use_msys2) {
stop("Cannot use both MinGW and MSYS2. Please choose only one.")
}
if (.Machine$sizeof.pointer != 8L) {
stop("LightGBM only supports 64-bit R, please check the version of R and Rtools.")
}
# Get some paths
source_dir <- file.path(R_PACKAGE_SOURCE, "src", fsep = "/")
build_dir <- file.path(source_dir, "build", fsep = "/")
inst_dir <- file.path(R_PACKAGE_SOURCE, "inst", fsep = "/")
# system() will not raise an R exception if the process called
# fails. Wrapping it here to get that behavior.
#
# system() introduces a lot of overhead, at least on Windows,
# so trying processx if it is available
.run_shell_command <- function(cmd, args, strict = TRUE) {
on_windows <- .Platform$OS.type == "windows"
has_processx <- suppressMessages({
suppressWarnings({
require("processx") # nolint: undesirable_function, unused_import.
})
})
if (has_processx && on_windows) {
result <- processx::run(
command = cmd
, args = args
, windows_verbatim_args = TRUE
, error_on_status = FALSE
, echo = TRUE
)
exit_code <- result$status
} else {
if (on_windows) {
message(paste0(
"Using system() to run shell commands. Installing "
, "'processx' with install.packages('processx') might "
, "make this faster."
))
}
cmd <- paste0(cmd, " ", paste(args, collapse = " "))
exit_code <- system(cmd)
}
if (exit_code != 0L && isTRUE(strict)) {
stop(paste0("Command failed with exit code: ", exit_code))
}
return(invisible(exit_code))
}
# try to generate Visual Studio build files
.generate_vs_makefiles <- function(cmake_args) {
vs_versions <- c(
"Visual Studio 17 2022"
, "Visual Studio 16 2019"
, "Visual Studio 15 2017"
)
working_vs_version <- NULL
for (vs_version in vs_versions) {
message(sprintf("Trying '%s'", vs_version))
# if the build directory is not empty, clean it
if (file.exists("CMakeCache.txt")) {
file.remove("CMakeCache.txt")
}
vs_cmake_args <- c(
cmake_args
, "-G"
, shQuote(vs_version)
, "-A"
, "x64"
)
exit_code <- .run_shell_command("cmake", c(vs_cmake_args, ".."), strict = FALSE)
if (exit_code == 0L) {
message(sprintf("Successfully created build files for '%s'", vs_version))
return(invisible(TRUE))
}
}
return(invisible(FALSE))
}
# Move in CMakeLists.txt
write_succeeded <- file.copy(
file.path(inst_dir, "bin", "CMakeLists.txt")
, "CMakeLists.txt"
, overwrite = TRUE
)
if (!write_succeeded) {
stop("Copying CMakeLists.txt failed")
}
# Prepare building package
dir.create(
build_dir
, recursive = TRUE
, showWarnings = FALSE
)
setwd(build_dir)
use_visual_studio <- !(use_mingw || use_msys2)
# If using MSVC to build, pull in the script used
# to create R.def from R.dll
if (WINDOWS && use_visual_studio) {
write_succeeded <- file.copy(
file.path(inst_dir, "make-r-def.R")
, file.path(build_dir, "make-r-def.R")
, overwrite = TRUE
)
if (!write_succeeded) {
stop("Copying make-r-def.R failed")
}
}
# Prepare installation steps
cmake_args <- c(
"-D__BUILD_FOR_R=ON"
# pass in R version, to help FindLibR find the R library
, sprintf("-DCMAKE_R_VERSION='%s.%s'", R.Version()[["major"]], R.Version()[["minor"]])
# ensure CMake build respects how R is configured (`R CMD config SHLIB_EXT`)
, sprintf("-DCMAKE_SHARED_LIBRARY_SUFFIX_CXX='%s'", SHLIB_EXT)
)
build_cmd <- "make"
build_args <- c("_lightgbm", make_args_from_build_script)
lib_folder <- file.path(source_dir, fsep = "/")
# add in command-line arguments
# NOTE: build_r.R replaces the line below
command_line_args <- NULL
cmake_args <- c(cmake_args, command_line_args)
WINDOWS_BUILD_TOOLS <- list(
"MinGW" = c(
build_tool = "mingw32-make.exe"
, makefile_generator = "MinGW Makefiles"
)
, "MSYS2" = c(
build_tool = "make.exe"
, makefile_generator = "MSYS Makefiles"
)
)
if (use_mingw) {
windows_toolchain <- "MinGW"
} else if (use_msys2) {
windows_toolchain <- "MSYS2"
} else {
# Rtools 4.0 moved from MinGW to MSYS toolchain. If user tries
# Visual Studio install but that fails, fall back to the toolchain
# supported in Rtools
windows_toolchain <- "MSYS2"
}
windows_build_tool <- WINDOWS_BUILD_TOOLS[[windows_toolchain]][["build_tool"]]
windows_makefile_generator <- WINDOWS_BUILD_TOOLS[[windows_toolchain]][["makefile_generator"]]
if (use_gpu) {
cmake_args <- c(cmake_args, "-DUSE_GPU=ON")
}
# the checks below might already run `cmake -G`. If they do, set this flag
# to TRUE to avoid re-running it later
makefiles_already_generated <- FALSE
# Check if Windows installation (for gcc vs Visual Studio)
if (WINDOWS) {
if (!use_visual_studio) {
message(sprintf("Trying to build with %s", windows_toolchain))
# Must build twice for Windows due sh.exe in Rtools
cmake_args <- c(cmake_args, "-G", shQuote(windows_makefile_generator))
.run_shell_command("cmake", c(cmake_args, ".."), strict = FALSE)
build_cmd <- windows_build_tool
build_args <- c("_lightgbm", make_args_from_build_script)
} else {
visual_studio_succeeded <- .generate_vs_makefiles(cmake_args)
if (!isTRUE(visual_studio_succeeded)) {
warning(sprintf("Building with Visual Studio failed. Attempting with %s", windows_toolchain))
# Must build twice for Windows due sh.exe in Rtools
cmake_args <- c(cmake_args, "-G", shQuote(windows_makefile_generator))
.run_shell_command("cmake", c(cmake_args, ".."), strict = FALSE)
build_cmd <- windows_build_tool
build_args <- c("_lightgbm", make_args_from_build_script)
} else {
build_cmd <- "cmake"
build_args <- c("--build", ".", "--target", "_lightgbm", "--config", "Release")
lib_folder <- file.path(source_dir, "Release", fsep = "/")
makefiles_already_generated <- TRUE
}
}
} else {
.run_shell_command("cmake", c(cmake_args, ".."))
makefiles_already_generated <- TRUE
}
# generate build files
if (!makefiles_already_generated) {
.run_shell_command("cmake", c(cmake_args, ".."))
}
# build the library
message(paste0("Building lightgbm", SHLIB_EXT))
.run_shell_command(build_cmd, build_args)
src <- file.path(lib_folder, paste0("lightgbm", SHLIB_EXT), fsep = "/")
# Packages with install.libs.R need to copy some artifacts into the
# expected places in the package structure.
# see https://cran.r-project.org/doc/manuals/r-devel/R-exts.html#Package-subdirectories,
# especially the paragraph on install.libs.R
dest <- file.path(R_PACKAGE_DIR, paste0("libs", R_ARCH), fsep = "/")
dir.create(dest, recursive = TRUE, showWarnings = FALSE)
if (file.exists(src)) {
message(paste0("Found library file: ", src, " to move to ", dest))
file.copy(src, dest, overwrite = TRUE)
symbols_file <- file.path(source_dir, "symbols.rds")
if (file.exists(symbols_file)) {
file.copy(symbols_file, dest, overwrite = TRUE)
}
} else {
stop(paste0("Cannot find lightgbm", SHLIB_EXT))
}
# clean up the "build" directory
if (dir.exists(build_dir)) {
message("Removing 'build/' directory")
unlink(
x = build_dir
, recursive = TRUE
, force = TRUE
)
}

Some files were not shown because too many files have changed in this diff Show More