chore: import upstream snapshot with attribution
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
2.71-2
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
YEAR: 2016
|
||||
COPYRIGHT HOLDER: Microsoft Corporation
|
||||
@@ -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)
|
||||
@@ -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"
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -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)
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -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))
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -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))
|
||||
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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[])
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
@@ -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"
|
||||
))
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,508 @@
|
||||
# LightGBM R-package
|
||||
|
||||
[](https://cran.r-project.org/package=lightgbm)
|
||||
[](https://cran.r-project.org/package=lightgbm)
|
||||
[](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).
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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&data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&sdata=rFGf7Y4Dvo6g1kzV%2BeAJDLGm1TUtzQsLsavElTw6H1U%3D&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&data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&sdata=iyTjhoqvzj3IbQ8HGCZeh1IQl34FAGpIdVyZWkzNvO0%3D&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&data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&sdata=SY77zgtbDbHvTxTgPLOoe%2Fw5OZDhXvJoxpVOoEaKoYo%3D&reserved=0
|
||||
https://nam06.safelinks.protection.outlook.com/?url=https%3A%2F%2Fdeveloper.r-project.org%2FWindowsBuilds%2Fwinutf8%2Fucrt3%2Fhowto.html&data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&sdata=dlVJ4nhQlmDPd56bHoVsWZuRfrUUorvOWxoUTmVDM%2Bg%3D&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&data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&sdata=msqoPzqDStlAUn%2Bb6gGevwFPD%2FaNL5dTxiNud2Sqzy8%3D&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&data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&sdata=0hCwONzLmcW0GIXNqiOZQEIuhNA%2BjHhQvXsofs8J98o%3D&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&data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&sdata=WLWLbOyQKbaYz8gkfKz2sqoGknjIOtl1aGAhUF%2Bpylg%3D&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&data=04%7C01%7Cyushi2%40microsoft.com%7C8e6c353d1a8842c81eeb08d9bef5d835%7C72f988bf86f141af91ab2d7cd011db47%7C1%7C0%7C637750786169848244%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&sdata=aFFQYuC9CoBwBiLgZHi8N3yUnSiHu5Xtdqb2YBiMIHQ%3D&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'`.
|
||||
@@ -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
|
||||
@@ -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))
|
||||
@@ -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
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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))
|
||||
@@ -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)
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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))
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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)))
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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 |
@@ -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}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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!
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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}.
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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}.
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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}.
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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}.
|
||||
}
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
@@ -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}
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
@@ -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`'
|
||||
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||