From ae0cd5dfab4c0ea7d669e5bba538d0946f29de47 Mon Sep 17 00:00:00 2001 From: James Lamb Date: Wed, 30 Nov 2022 15:01:41 +0000 Subject: [PATCH 01/25] [ci] detect non-default dynamic symbols in check_dynamic_dependencies.py (#5610) --- helpers/check_dynamic_dependencies.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/helpers/check_dynamic_dependencies.py b/helpers/check_dynamic_dependencies.py index bd22672466ed..5372356a6ce3 100644 --- a/helpers/check_dynamic_dependencies.py +++ b/helpers/check_dynamic_dependencies.py @@ -25,7 +25,7 @@ def check_dependencies(objdump_string: str) -> None: objdump_string : str The dynamic symbol table entries of the file (result of `objdump -T` command). """ - GLIBC_version = re.compile(r'0{16}[ \t]+GLIBC_(\d{1,2})[.](\d{1,3})[.]?\d{,3}[ \t]+') + GLIBC_version = re.compile(r'0{16}[ \(\t]+GLIBC_(\d{1,2})[.](\d{1,3})[.]?\d{,3}[ \)\t]+') versions = GLIBC_version.findall(objdump_string) assert len(versions) > 1 for major, minor in versions: @@ -33,7 +33,7 @@ def check_dependencies(objdump_string: str) -> None: assert int(major) <= 2, error_msg assert int(minor) <= 28, error_msg - GLIBCXX_version = re.compile(r'0{16}[ \t]+GLIBCXX_(\d{1,2})[.](\d{1,2})[.]?(\d{,3})[ \t]+') + GLIBCXX_version = re.compile(r'0{16}[ \(\t]+GLIBCXX_(\d{1,2})[.](\d{1,2})[.]?(\d{,3})[ \)\t]+') versions = GLIBCXX_version.findall(objdump_string) assert len(versions) > 1 for major, minor, patch in versions: @@ -42,7 +42,7 @@ def check_dependencies(objdump_string: str) -> None: assert int(minor) == 4, error_msg assert patch == '' or int(patch) <= 22, error_msg - GOMP_version = re.compile(r'0{16}[ \t]+G?OMP_(\d{1,2})[.](\d{1,2})[.]?\d{,3}[ \t]+') + GOMP_version = re.compile(r'0{16}[ \(\t]+G?OMP_(\d{1,2})[.](\d{1,2})[.]?\d{,3}[ \)\t]+') versions = GOMP_version.findall(objdump_string) assert len(versions) > 1 for major, minor in versions: From 90047b96686c4e18ee06934ad097c738d66e251f Mon Sep 17 00:00:00 2001 From: Omar Salman Date: Thu, 1 Dec 2022 06:17:22 +0500 Subject: [PATCH 02/25] [python-package] prefix basic.convert_from_sliced_object with _ (#5613) --- python-package/lightgbm/basic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python-package/lightgbm/basic.py b/python-package/lightgbm/basic.py index be640af22ec2..6854a97e80e5 100644 --- a/python-package/lightgbm/basic.py +++ b/python-package/lightgbm/basic.py @@ -501,7 +501,7 @@ def _choose_param_value(main_param_name: str, params: Dict[str, Any], default_va "gain": C_API_FEATURE_IMPORTANCE_GAIN} -def convert_from_sliced_object(data): +def _convert_from_sliced_object(data): """Fix the memory of multi-dimensional sliced object.""" if isinstance(data, np.ndarray) and isinstance(data.base, np.ndarray): if not data.flags.c_contiguous: @@ -516,7 +516,7 @@ def c_float_array(data): if _is_1d_list(data): data = np.array(data, copy=False) if _is_numpy_1d_array(data): - data = convert_from_sliced_object(data) + data = _convert_from_sliced_object(data) assert data.flags.c_contiguous if data.dtype == np.float32: ptr_data = data.ctypes.data_as(ctypes.POINTER(ctypes.c_float)) @@ -536,7 +536,7 @@ def c_int_array(data): if _is_1d_list(data): data = np.array(data, copy=False) if _is_numpy_1d_array(data): - data = convert_from_sliced_object(data) + data = _convert_from_sliced_object(data) assert data.flags.c_contiguous if data.dtype == np.int32: ptr_data = data.ctypes.data_as(ctypes.POINTER(ctypes.c_int32)) From 38a1f5821acb516e6036df45deaa39185b88de6e Mon Sep 17 00:00:00 2001 From: Jonathan Giannuzzi Date: Fri, 2 Dec 2022 03:10:31 +0000 Subject: [PATCH 03/25] [ci] Build integrated OpenCL Linux wheels (#5252) * Add integrated OpenCL build on Linux * Build integrated OpenCL Linux wheel in CI * Fix test_dual.py on Linux arm64 * Enable integrated OpenCL Linux wheel arm64 testing in CI * Update documentation * Add comment about gpu_use_dp * add missing fi dropped in merge conflict resolution * install opencl-headers on bdist task * use new CI image for x86_64 * update check_dynamic_dependencies script * use main CI image Co-authored-by: James Lamb --- .ci/setup.sh | 44 ++++++++++++- .ci/test.sh | 4 +- .vsts-ci.yml | 3 +- CMakeLists.txt | 8 +-- cmake/IntegratedOpenCL.cmake | 87 ++++++++++++++++---------- python-package/README.rst | 2 +- tests/python_package_test/test_dual.py | 7 ++- 7 files changed, 110 insertions(+), 45 deletions(-) diff --git a/.ci/setup.sh b/.ci/setup.sh index 87d7294288e0..d53a5a80c2c4 100755 --- a/.ci/setup.sh +++ b/.ci/setup.sh @@ -79,8 +79,7 @@ else # Linux sudo apt-get update sudo apt-get install --no-install-recommends -y \ libboost1.74-dev \ - ocl-icd-opencl-dev \ - pocl-opencl-icd + ocl-icd-opencl-dev else # in manylinux image sudo yum update -y sudo yum install -y \ @@ -90,6 +89,47 @@ else # Linux || exit -1 fi fi + if [[ $TASK == "gpu" || $TASK == "bdist" ]]; then + if [[ $IN_UBUNTU_LATEST_CONTAINER == "true" ]]; then + sudo apt-get update + sudo apt-get install --no-install-recommends -y \ + pocl-opencl-icd + elif [[ $(uname -m) == "aarch64" ]]; then + yum install -y \ + epel-release \ + gcc-c++ \ + hwloc-devel \ + sudo + yum install -y \ + llvm-toolset-7.0-clang-devel \ + llvm-toolset-7.0-llvm-devel \ + ocl-icd-devel + git clone --depth 1 --branch v1.8 https://github.com/pocl/pocl.git + cmake \ + -B pocl/build \ + -S pocl \ + -DCMAKE_BUILD_TYPE=release \ + -DCMAKE_C_COMPILER=/usr/bin/gcc \ + -DCMAKE_CXX_COMPILER=/usr/bin/g++ \ + -DCMAKE_C_FLAGS=-std=gnu99 \ + -DPOCL_INSTALL_ICD_VENDORDIR=/etc/OpenCL/vendors \ + -DPOCL_DEBUG_MESSAGES=OFF \ + -DINSTALL_OPENCL_HEADERS=OFF \ + -DENABLE_SPIR=OFF \ + -DENABLE_POCLCC=OFF \ + -DENABLE_TESTS=OFF \ + -DENABLE_EXAMPLES=OFF \ + -DLLC_HOST_CPU=generic + cmake --build pocl/build -j4 + sudo cmake --install pocl/build + elif [[ $(uname -m) == "x86_64" ]]; then + sudo yum update -y + sudo yum install -y \ + ocl-icd-devel \ + opencl-headers \ + || exit -1 + fi + fi if [[ $TASK == "cuda" || $TASK == "cuda_exp" ]]; then echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections apt-get update diff --git a/.ci/test.sh b/.ci/test.sh index f73fbe915cc6..fbe0835838f0 100755 --- a/.ci/test.sh +++ b/.ci/test.sh @@ -161,10 +161,12 @@ elif [[ $TASK == "bdist" ]]; then else PLATFORM="manylinux2014_$ARCH" fi - cd $BUILD_DIRECTORY/python-package && python setup.py bdist_wheel --plat-name=$PLATFORM --python-tag py3 || exit -1 + cd $BUILD_DIRECTORY/python-package && python setup.py bdist_wheel --integrated-opencl --plat-name=$PLATFORM --python-tag py3 || exit -1 if [[ $PRODUCES_ARTIFACTS == "true" ]]; then cp dist/lightgbm-$LGB_VER-py3-none-$PLATFORM.whl $BUILD_ARTIFACTSTAGINGDIRECTORY fi + # Make sure we can do both CPU and GPU; see tests/python_package_test/test_dual.py + export LIGHTGBM_TEST_DUAL_CPU_GPU=1 fi pip install --user $BUILD_DIRECTORY/python-package/dist/*.whl || exit -1 pytest $BUILD_DIRECTORY/tests || exit -1 diff --git a/.vsts-ci.yml b/.vsts-ci.yml index 6d7628b1f2df..d68d3bd93824 100644 --- a/.vsts-ci.yml +++ b/.vsts-ci.yml @@ -187,7 +187,8 @@ jobs: EOF cat > docker-script.sh <`_ You may need to install `wheel `_ via ``pip install wheel`` first. -Compiled library that is included in the wheel file supports both **GPU** and **CPU** versions out of the box. This feature is experimental and available only for **Windows** currently. To use **GPU** version you only need to install OpenCL Runtime libraries. For NVIDIA and AMD GPU they are included in the ordinary drivers for your graphics card, so no action is required. If you would like your AMD or Intel CPU to act like a GPU (for testing and debugging) you can install `AMD APP SDK `_. +Compiled library that is included in the wheel file supports both **GPU** and **CPU** versions out of the box. This feature is experimental and available only for **Windows** and **Linux** currently. To use **GPU** version you only need to install OpenCL Runtime libraries. For NVIDIA and AMD GPU they are included in the ordinary drivers for your graphics card, so no action is required. If you would like your AMD or Intel CPU to act like a GPU (for testing and debugging) you can install `AMD APP SDK `_ on **Windows** and `PoCL `_ on **Linux**. Many modern Linux distributions provide packages for PoCL, look for ``pocl-opencl-icd`` on Debian-based distributions and ``pocl`` on RedHat-based distributions. For **Windows** users, `VC runtime `_ is needed if **Visual Studio** (2015 or newer) is not installed. diff --git a/tests/python_package_test/test_dual.py b/tests/python_package_test/test_dual.py index cd31a7d9b3b9..75c54c83eb94 100644 --- a/tests/python_package_test/test_dual.py +++ b/tests/python_package_test/test_dual.py @@ -2,6 +2,7 @@ """Tests for dual GPU+CPU support.""" import os +import platform import pytest from sklearn.metrics import log_loss @@ -26,9 +27,11 @@ def test_cpu_and_gpu_work(): params_gpu = params_cpu.copy() params_gpu["device"] = "gpu" - params_gpu["gpu_use_dp"] = True + # Double-precision floats are only supported on x86_64 with PoCL + params_gpu["gpu_use_dp"] = (platform.machine() == "x86_64") gpu_bst = lgb.train(params_gpu, data, num_boost_round=10) gpu_score = log_loss(y, gpu_bst.predict(X)) - assert cpu_score == pytest.approx(gpu_score) + rel = 1e-6 if params_gpu["gpu_use_dp"] else 1e-4 + assert cpu_score == pytest.approx(gpu_score, rel=rel) assert gpu_score < 0.242 From f0cfbff63f8a228784e52e033533e5cb3fa1b97b Mon Sep 17 00:00:00 2001 From: shiyu1994 Date: Fri, 2 Dec 2022 12:50:48 +0800 Subject: [PATCH 04/25] [CUDA] Add rmse metric for new CUDA version (#5611) * add rmse metric for new cuda version * add Init for CUDAMetricInterface * fix lint errors --- CMakeLists.txt | 2 + include/LightGBM/cuda/cuda_metric.hpp | 41 +++++++++++++ .../LightGBM/cuda/cuda_objective_function.hpp | 12 +--- include/LightGBM/objective_function.h | 6 +- src/cuda/cuda_algorithms.cu | 1 + src/metric/cuda/cuda_regression_metric.cpp | 43 +++++++++++++ src/metric/cuda/cuda_regression_metric.cu | 61 +++++++++++++++++++ src/metric/cuda/cuda_regression_metric.hpp | 57 +++++++++++++++++ src/metric/metric.cpp | 5 +- src/metric/regression_metric.hpp | 2 +- src/objective/cuda/cuda_binary_objective.cu | 3 +- src/objective/cuda/cuda_binary_objective.hpp | 2 +- .../cuda/cuda_multiclass_objective.cpp | 3 +- .../cuda/cuda_multiclass_objective.cu | 3 +- .../cuda/cuda_multiclass_objective.hpp | 4 +- .../cuda/cuda_regression_objective.cu | 6 +- .../cuda/cuda_regression_objective.hpp | 4 +- 17 files changed, 230 insertions(+), 25 deletions(-) create mode 100644 include/LightGBM/cuda/cuda_metric.hpp create mode 100644 src/metric/cuda/cuda_regression_metric.cpp create mode 100644 src/metric/cuda/cuda_regression_metric.cu create mode 100644 src/metric/cuda/cuda_regression_metric.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c011f51fb961..b7fa5dc8f330 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -417,6 +417,8 @@ endif() if(USE_CUDA_EXP) src/boosting/cuda/*.cpp src/boosting/cuda/*.cu + src/metric/cuda/*.cpp + src/metric/cuda/*.cu src/objective/cuda/*.cpp src/objective/cuda/*.cu src/treelearner/cuda/*.cpp diff --git a/include/LightGBM/cuda/cuda_metric.hpp b/include/LightGBM/cuda/cuda_metric.hpp new file mode 100644 index 000000000000..caeff267e8ef --- /dev/null +++ b/include/LightGBM/cuda/cuda_metric.hpp @@ -0,0 +1,41 @@ +/*! + * Copyright (c) 2021 Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for + * license information. + */ + +#ifndef LIGHTGBM_CUDA_CUDA_METRIC_HPP_ +#define LIGHTGBM_CUDA_CUDA_METRIC_HPP_ + +#ifdef USE_CUDA_EXP + +#include + +namespace LightGBM { + +template +class CUDAMetricInterface: public HOST_METRIC { + public: + explicit CUDAMetricInterface(const Config& config): HOST_METRIC(config) { + cuda_labels_ = nullptr; + cuda_weights_ = nullptr; + } + + void Init(const Metadata& metadata, data_size_t num_data) override { + HOST_METRIC::Init(metadata, num_data); + cuda_labels_ = metadata.cuda_metadata()->cuda_label(); + cuda_weights_ = metadata.cuda_metadata()->cuda_weights(); + } + + bool IsCUDAMetric() const { return true; } + + protected: + const label_t* cuda_labels_; + const label_t* cuda_weights_; +}; + +} // namespace LightGBM + +#endif // USE_CUDA_EXP + +#endif // LIGHTGBM_CUDA_CUDA_METRIC_HPP_ diff --git a/include/LightGBM/cuda/cuda_objective_function.hpp b/include/LightGBM/cuda/cuda_objective_function.hpp index fbcad87a57b6..1010895e9d7a 100644 --- a/include/LightGBM/cuda/cuda_objective_function.hpp +++ b/include/LightGBM/cuda/cuda_objective_function.hpp @@ -31,14 +31,8 @@ class CUDAObjectiveInterface: public HOST_OBJECTIVE { cuda_weights_ = metadata.cuda_metadata()->cuda_weights(); } - virtual void ConvertOutputCUDA(const data_size_t num_data, const double* input, double* output) const { - LaunchConvertOutputCUDAKernel(num_data, input, output); - } - - std::function GetCUDAConvertOutputFunc() const override { - return [this] (data_size_t num_data, const double* input, double* output) { - ConvertOutputCUDA(num_data, input, output); - }; + virtual const double* ConvertOutputCUDA(const data_size_t num_data, const double* input, double* output) const { + return LaunchConvertOutputCUDAKernel(num_data, input, output); } double BoostFromScore(int class_id) const override { @@ -67,7 +61,7 @@ class CUDAObjectiveInterface: public HOST_OBJECTIVE { return HOST_OBJECTIVE::BoostFromScore(class_id); } - virtual void LaunchConvertOutputCUDAKernel(const data_size_t /*num_data*/, const double* /*input*/, double* /*output*/) const {} + virtual const double* LaunchConvertOutputCUDAKernel(const data_size_t /*num_data*/, const double* input, double* /*output*/) const { return input; } virtual void LaunchRenewTreeOutputCUDAKernel( const double* /*score*/, const data_size_t* /*data_indices_in_leaf*/, const data_size_t* /*num_data_in_leaf*/, diff --git a/include/LightGBM/objective_function.h b/include/LightGBM/objective_function.h index ff648b1f5fc3..0d28bc57eb4a 100644 --- a/include/LightGBM/objective_function.h +++ b/include/LightGBM/objective_function.h @@ -99,10 +99,10 @@ class ObjectiveFunction { #ifdef USE_CUDA_EXP /*! - * \brief Get output convert function for CUDA version + * \brief Convert output for CUDA version */ - virtual std::function GetCUDAConvertOutputFunc() const { - return [] (data_size_t, const double*, double*) {}; + const double* ConvertOutputCUDA(data_size_t /*num_data*/, const double* input, double* /*output*/) const { + return input; } #endif // USE_CUDA_EXP }; diff --git a/src/cuda/cuda_algorithms.cu b/src/cuda/cuda_algorithms.cu index b19321eb8935..5a6b3eb74ef0 100644 --- a/src/cuda/cuda_algorithms.cu +++ b/src/cuda/cuda_algorithms.cu @@ -127,6 +127,7 @@ void ShuffleReduceSumGlobal(const VAL_T* values, size_t n, REDUCE_T* block_buffe } template void ShuffleReduceSumGlobal(const label_t* values, size_t n, double* block_buffer); +template void ShuffleReduceSumGlobal(const double* values, size_t n, double* block_buffer); template __global__ void ShuffleReduceMinGlobalKernel(const VAL_T* values, const data_size_t num_value, REDUCE_T* block_buffer) { diff --git a/src/metric/cuda/cuda_regression_metric.cpp b/src/metric/cuda/cuda_regression_metric.cpp new file mode 100644 index 000000000000..5d8ae39fd3e4 --- /dev/null +++ b/src/metric/cuda/cuda_regression_metric.cpp @@ -0,0 +1,43 @@ +/*! + * Copyright (c) 2022 Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for + * license information. + */ + +#ifdef USE_CUDA_EXP + +#include + +#include "cuda_regression_metric.hpp" + +namespace LightGBM { + +template +void CUDARegressionMetricInterface::Init(const Metadata& metadata, data_size_t num_data) { + CUDAMetricInterface::Init(metadata, num_data); + const int max_num_reduce_blocks = (this->num_data_ + NUM_DATA_PER_EVAL_THREAD - 1) / NUM_DATA_PER_EVAL_THREAD; + if (this->cuda_weights_ == nullptr) { + reduce_block_buffer_.Resize(max_num_reduce_blocks); + } else { + reduce_block_buffer_.Resize(max_num_reduce_blocks * 2); + } + const int max_num_reduce_blocks_inner = (max_num_reduce_blocks + NUM_DATA_PER_EVAL_THREAD - 1) / NUM_DATA_PER_EVAL_THREAD; + if (this->cuda_weights_ == nullptr) { + reduce_block_buffer_inner_.Resize(max_num_reduce_blocks_inner); + } else { + reduce_block_buffer_inner_.Resize(max_num_reduce_blocks_inner * 2); + } +} + +template +std::vector CUDARegressionMetricInterface::Eval(const double* score, const ObjectiveFunction* objective) const { + const double* score_convert = objective->ConvertOutputCUDA(this->num_data_, score, score_convert_buffer_.RawData()); + const double eval_score = LaunchEvalKernel(score_convert); + return std::vector{eval_score}; +} + +CUDARMSEMetric::CUDARMSEMetric(const Config& config): CUDARegressionMetricInterface(config) {} + +} // namespace LightGBM + +#endif // USE_CUDA_EXP diff --git a/src/metric/cuda/cuda_regression_metric.cu b/src/metric/cuda/cuda_regression_metric.cu new file mode 100644 index 000000000000..0442416459c5 --- /dev/null +++ b/src/metric/cuda/cuda_regression_metric.cu @@ -0,0 +1,61 @@ +/*! + * Copyright (c) 2022 Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for + * license information. + */ + +#ifdef USE_CUDA_EXP + +#include + +#include "cuda_regression_metric.hpp" + +namespace LightGBM { + +template +__global__ void EvalKernel(const data_size_t num_data, const label_t* labels, const label_t* weights, + const double* scores, double* reduce_block_buffer) { + __shared__ double shared_mem_buffer[32]; + const data_size_t index = static_cast(threadIdx.x + blockIdx.x * blockDim.x); + double point_metric = 0.0; + if (index < num_data) { + point_metric = CUDA_METRIC::MetricOnPointCUDA(labels[index], scores[index]); + } + const double block_sum_point_metric = ShuffleReduceSum(point_metric, shared_mem_buffer, NUM_DATA_PER_EVAL_THREAD); + reduce_block_buffer[blockIdx.x] = block_sum_point_metric; + if (USE_WEIGHTS) { + double weight = 0.0; + if (index < num_data) { + weight = static_cast(weights[index]); + const double block_sum_weight = ShuffleReduceSum(weight, shared_mem_buffer, NUM_DATA_PER_EVAL_THREAD); + reduce_block_buffer[blockIdx.x + blockDim.x] = block_sum_weight; + } + } +} + +template +double CUDARegressionMetricInterface::LaunchEvalKernel(const double* score) const { + const int num_blocks = (this->num_data_ + NUM_DATA_PER_EVAL_THREAD - 1) / NUM_DATA_PER_EVAL_THREAD; + if (this->cuda_weights_ != nullptr) { + EvalKernel<<>>( + this->num_data_, this->cuda_labels_, this->cuda_weights_, score, reduce_block_buffer_.RawData()); + } else { + EvalKernel<<>>( + this->num_data_, this->cuda_labels_, this->cuda_weights_, score, reduce_block_buffer_.RawData()); + } + ShuffleReduceSumGlobal(reduce_block_buffer_.RawData(), num_blocks, reduce_block_buffer_inner_.RawData()); + double sum_loss = 0.0; + CopyFromCUDADeviceToHost(&sum_loss, reduce_block_buffer_inner_.RawData(), 1, __FILE__, __LINE__); + double sum_weight = static_cast(this->num_data_); + if (this->cuda_weights_ != nullptr) { + ShuffleReduceSumGlobal(reduce_block_buffer_.RawData() + num_blocks, num_blocks, reduce_block_buffer_inner_.RawData()); + CopyFromCUDADeviceToHost(&sum_weight, reduce_block_buffer_inner_.RawData(), 1, __FILE__, __LINE__); + } + return this->AverageLoss(sum_loss, sum_weight); +} + +template double CUDARegressionMetricInterface::LaunchEvalKernel(const double* score) const; + +} // namespace LightGBM + +#endif // USE_CUDA_EXP diff --git a/src/metric/cuda/cuda_regression_metric.hpp b/src/metric/cuda/cuda_regression_metric.hpp new file mode 100644 index 000000000000..fe49bd0d729d --- /dev/null +++ b/src/metric/cuda/cuda_regression_metric.hpp @@ -0,0 +1,57 @@ +/*! + * Copyright (c) 2022 Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for + * license information. + */ + +#ifndef LIGHTGBM_METRIC_CUDA_CUDA_REGRESSION_METRIC_HPP_ +#define LIGHTGBM_METRIC_CUDA_CUDA_REGRESSION_METRIC_HPP_ + +#ifdef USE_CUDA_EXP + +#include +#include + +#include + +#include "../regression_metric.hpp" + +#define NUM_DATA_PER_EVAL_THREAD (1024) + +namespace LightGBM { + +template +class CUDARegressionMetricInterface: public CUDAMetricInterface { + public: + explicit CUDARegressionMetricInterface(const Config& config): CUDAMetricInterface(config) {} + + virtual ~CUDARegressionMetricInterface() {} + + void Init(const Metadata& metadata, data_size_t num_data) override; + + std::vector Eval(const double* score, const ObjectiveFunction* objective) const override; + + protected: + double LaunchEvalKernel(const double* score_convert) const; + + CUDAVector score_convert_buffer_; + CUDAVector reduce_block_buffer_; + CUDAVector reduce_block_buffer_inner_; +}; + +class CUDARMSEMetric: public CUDARegressionMetricInterface { + public: + explicit CUDARMSEMetric(const Config& config); + + virtual ~CUDARMSEMetric() {} + + __device__ static double MetricOnPointCUDA(label_t label, double score) { + return (score - static_cast(label)); + } +}; + +} // namespace LightGBM + +#endif // USE_CUDA_EXP + +#endif // LIGHTGBM_METRIC_CUDA_CUDA_REGRESSION_METRIC_HPP_ diff --git a/src/metric/metric.cpp b/src/metric/metric.cpp index dacdb0961c87..32241656cc4c 100644 --- a/src/metric/metric.cpp +++ b/src/metric/metric.cpp @@ -11,6 +11,8 @@ #include "regression_metric.hpp" #include "xentropy_metric.hpp" +#include "cuda/cuda_regression_metric.hpp" + namespace LightGBM { Metric* Metric::CreateMetric(const std::string& type, const Config& config) { @@ -20,8 +22,7 @@ Metric* Metric::CreateMetric(const std::string& type, const Config& config) { Log::Warning("Metric l2 is not implemented in cuda_exp version. Fall back to evaluation on CPU."); return new L2Metric(config); } else if (type == std::string("rmse")) { - Log::Warning("Metric rmse is not implemented in cuda_exp version. Fall back to evaluation on CPU."); - return new RMSEMetric(config); + return new CUDARMSEMetric(config); } else if (type == std::string("l1")) { Log::Warning("Metric l1 is not implemented in cuda_exp version. Fall back to evaluation on CPU."); return new L1Metric(config); diff --git a/src/metric/regression_metric.hpp b/src/metric/regression_metric.hpp index 379c36c46aca..3c4124aad4b9 100644 --- a/src/metric/regression_metric.hpp +++ b/src/metric/regression_metric.hpp @@ -101,7 +101,7 @@ class RegressionMetric: public Metric { inline static void CheckLabel(label_t) { } - private: + protected: /*! \brief Number of data */ data_size_t num_data_; /*! \brief Pointer of label */ diff --git a/src/objective/cuda/cuda_binary_objective.cu b/src/objective/cuda/cuda_binary_objective.cu index 45fa46cf333d..9726f3eda66d 100644 --- a/src/objective/cuda/cuda_binary_objective.cu +++ b/src/objective/cuda/cuda_binary_objective.cu @@ -182,9 +182,10 @@ __global__ void ConvertOutputCUDAKernel_BinaryLogloss(const double sigmoid, cons } } -void CUDABinaryLogloss::LaunchConvertOutputCUDAKernel(const data_size_t num_data, const double* input, double* output) const { +const double* CUDABinaryLogloss::LaunchConvertOutputCUDAKernel(const data_size_t num_data, const double* input, double* output) const { const int num_blocks = (num_data + GET_GRADIENTS_BLOCK_SIZE_BINARY - 1) / GET_GRADIENTS_BLOCK_SIZE_BINARY; ConvertOutputCUDAKernel_BinaryLogloss<<>>(sigmoid_, num_data, input, output); + return output; } __global__ void ResetOVACUDALabelKernel( diff --git a/src/objective/cuda/cuda_binary_objective.hpp b/src/objective/cuda/cuda_binary_objective.hpp index 0de5be0b6331..9434e64602b2 100644 --- a/src/objective/cuda/cuda_binary_objective.hpp +++ b/src/objective/cuda/cuda_binary_objective.hpp @@ -38,7 +38,7 @@ class CUDABinaryLogloss : public CUDAObjectiveInterface { double LaunchCalcInitScoreKernel(const int class_id) const override; - void LaunchConvertOutputCUDAKernel(const data_size_t num_data, const double* input, double* output) const override; + const double* LaunchConvertOutputCUDAKernel(const data_size_t num_data, const double* input, double* output) const override; void LaunchResetOVACUDALabelKernel() const; diff --git a/src/objective/cuda/cuda_multiclass_objective.cpp b/src/objective/cuda/cuda_multiclass_objective.cpp index 0f70b0d66897..2ea3de870e99 100644 --- a/src/objective/cuda/cuda_multiclass_objective.cpp +++ b/src/objective/cuda/cuda_multiclass_objective.cpp @@ -49,10 +49,11 @@ void CUDAMulticlassOVA::GetGradients(const double* score, score_t* gradients, sc } } -void CUDAMulticlassOVA::ConvertOutputCUDA(const data_size_t num_data, const double* input, double* output) const { +const double* CUDAMulticlassOVA::ConvertOutputCUDA(const data_size_t num_data, const double* input, double* output) const { for (int i = 0; i < num_class_; ++i) { cuda_binary_loss_[i]->ConvertOutputCUDA(num_data, input + i * num_data, output + i * num_data); } + return output; } diff --git a/src/objective/cuda/cuda_multiclass_objective.cu b/src/objective/cuda/cuda_multiclass_objective.cu index 480ce32a9a57..797c7cec7bf0 100644 --- a/src/objective/cuda/cuda_multiclass_objective.cu +++ b/src/objective/cuda/cuda_multiclass_objective.cu @@ -95,11 +95,12 @@ __global__ void ConvertOutputCUDAKernel_MulticlassSoftmax( } } -void CUDAMulticlassSoftmax::LaunchConvertOutputCUDAKernel( +const double* CUDAMulticlassSoftmax::LaunchConvertOutputCUDAKernel( const data_size_t num_data, const double* input, double* output) const { const int num_blocks = (num_data_ + GET_GRADIENTS_BLOCK_SIZE_MULTICLASS - 1) / GET_GRADIENTS_BLOCK_SIZE_MULTICLASS; ConvertOutputCUDAKernel_MulticlassSoftmax<<>>( num_class_, num_data, input, cuda_softmax_buffer_.RawData(), output); + return output; } } // namespace LightGBM diff --git a/src/objective/cuda/cuda_multiclass_objective.hpp b/src/objective/cuda/cuda_multiclass_objective.hpp index 37c3087c9e2f..e6e326306e31 100644 --- a/src/objective/cuda/cuda_multiclass_objective.hpp +++ b/src/objective/cuda/cuda_multiclass_objective.hpp @@ -34,7 +34,7 @@ class CUDAMulticlassSoftmax: public CUDAObjectiveInterface { private: void LaunchGetGradientsKernel(const double* scores, score_t* gradients, score_t* hessians) const; - void LaunchConvertOutputCUDAKernel(const data_size_t num_data, const double* input, double* output) const; + const double* LaunchConvertOutputCUDAKernel(const data_size_t num_data, const double* input, double* output) const; // CUDA memory, held by this object CUDAVector cuda_softmax_buffer_; @@ -51,7 +51,7 @@ class CUDAMulticlassOVA: public CUDAObjectiveInterface { void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override; - void ConvertOutputCUDA(const data_size_t num_data, const double* input, double* output) const override; + const double* ConvertOutputCUDA(const data_size_t num_data, const double* input, double* output) const override; double BoostFromScore(int class_id) const override { return cuda_binary_loss_[class_id]->BoostFromScore(0); diff --git a/src/objective/cuda/cuda_regression_objective.cu b/src/objective/cuda/cuda_regression_objective.cu index 8bb257673f7d..d231cb890624 100644 --- a/src/objective/cuda/cuda_regression_objective.cu +++ b/src/objective/cuda/cuda_regression_objective.cu @@ -68,9 +68,10 @@ __global__ void ConvertOutputCUDAKernel_Regression(const bool sqrt, const data_s } } -void CUDARegressionL2loss::LaunchConvertOutputCUDAKernel(const data_size_t num_data, const double* input, double* output) const { +const double* CUDARegressionL2loss::LaunchConvertOutputCUDAKernel(const data_size_t num_data, const double* input, double* output) const { const int num_blocks = (num_data + GET_GRADIENTS_BLOCK_SIZE_REGRESSION - 1) / GET_GRADIENTS_BLOCK_SIZE_REGRESSION; ConvertOutputCUDAKernel_Regression<<>>(sqrt_, num_data, input, output); + return output; } template @@ -339,9 +340,10 @@ __global__ void ConvertOutputCUDAKernel_Regression_Poisson(const data_size_t num } } -void CUDARegressionPoissonLoss::LaunchConvertOutputCUDAKernel(const data_size_t num_data, const double* input, double* output) const { +const double* CUDARegressionPoissonLoss::LaunchConvertOutputCUDAKernel(const data_size_t num_data, const double* input, double* output) const { const int num_blocks = (num_data + GET_GRADIENTS_BLOCK_SIZE_REGRESSION - 1) / GET_GRADIENTS_BLOCK_SIZE_REGRESSION; ConvertOutputCUDAKernel_Regression_Poisson<<>>(num_data, input, output); + return output; } diff --git a/src/objective/cuda/cuda_regression_objective.hpp b/src/objective/cuda/cuda_regression_objective.hpp index 2e5b9e8506e9..2e9a747e3a25 100644 --- a/src/objective/cuda/cuda_regression_objective.hpp +++ b/src/objective/cuda/cuda_regression_objective.hpp @@ -49,7 +49,7 @@ class CUDARegressionL2loss : public CUDARegressionObjectiveInterface Date: Tue, 6 Dec 2022 19:16:18 -0600 Subject: [PATCH 05/25] [ci] test against R 4.2.2 (#5621) --- .ci/test_r_package.sh | 9 +++++---- .ci/test_r_package_windows.ps1 | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.ci/test_r_package.sh b/.ci/test_r_package.sh index c15b5c59df7b..7a12d3fdc1d4 100755 --- a/.ci/test_r_package.sh +++ b/.ci/test_r_package.sh @@ -21,9 +21,9 @@ if [[ "${R_MAJOR_VERSION}" == "3" ]]; then export R_LINUX_VERSION="3.6.3-1bionic" export R_APT_REPO="bionic-cran35/" elif [[ "${R_MAJOR_VERSION}" == "4" ]]; then - export R_MAC_VERSION=4.2.1 + export R_MAC_VERSION=4.2.2 export R_MAC_PKG_URL=${CRAN_MIRROR}/bin/macosx/base/R-${R_MAC_VERSION}.pkg - export R_LINUX_VERSION="4.2.1-1.2004.0" + export R_LINUX_VERSION="4.2.2-1.2004.0" export R_APT_REPO="focal-cran40/" else echo "Unrecognized R version: ${R_VERSION}" @@ -76,7 +76,7 @@ if [[ $OS_NAME == "macos" ]]; then brew install --cask basictex || exit -1 export PATH="/Library/TeX/texbin:$PATH" sudo tlmgr --verify-repo=none update --self || exit -1 - sudo tlmgr --verify-repo=none install inconsolata helvetic || exit -1 + sudo tlmgr --verify-repo=none install inconsolata helvetic rsfs || exit -1 curl -sL ${R_MAC_PKG_URL} -o R.pkg || exit -1 sudo installer \ @@ -163,11 +163,12 @@ elif [[ $R_BUILD_TYPE == "cran" ]]; then || (cat ${RCHK_LOG_FILE} && exit -1) cat ${RCHK_LOG_FILE} - # the exception below is from R itself and not LightGBM: + # the exceptions below are from R itself and not LightGBM: # https://github.com/kalibera/rchk/issues/22#issuecomment-656036156 exit $( cat ${RCHK_LOG_FILE} \ | grep -v "in function strptime_internal" \ + | grep -v "in function RunGenCollect" \ | grep --count -E '\[PB\]|ERROR' ) fi diff --git a/.ci/test_r_package_windows.ps1 b/.ci/test_r_package_windows.ps1 index 2005ad5adeeb..e4d20de50b90 100644 --- a/.ci/test_r_package_windows.ps1 +++ b/.ci/test_r_package_windows.ps1 @@ -80,7 +80,7 @@ if ($env:R_MAJOR_VERSION -eq "3") { $env:RTOOLS_BIN = "$RTOOLS_INSTALL_PATH\usr\bin" $env:RTOOLS_MINGW_BIN = "$RTOOLS_INSTALL_PATH\x86_64-w64-mingw32.static.posix\bin" $env:RTOOLS_EXE_FILE = "rtools42-5253-5107.exe" - $env:R_WINDOWS_VERSION = "4.2.1" + $env:R_WINDOWS_VERSION = "4.2.2" } else { Write-Output "[ERROR] Unrecognized R version: $env:R_VERSION" Check-Output $false From 6fa4673f69fa377477e74f986e216c5c186b5b1f Mon Sep 17 00:00:00 2001 From: Omar Salman Date: Thu, 8 Dec 2022 06:17:19 +0500 Subject: [PATCH 06/25] [python-package] prefix c_int_array and c_float_array with _ (#5614) --- python-package/lightgbm/basic.py | 50 ++++++++++++++++---------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/python-package/lightgbm/basic.py b/python-package/lightgbm/basic.py index 6854a97e80e5..53d4a1b61172 100644 --- a/python-package/lightgbm/basic.py +++ b/python-package/lightgbm/basic.py @@ -511,7 +511,7 @@ def _convert_from_sliced_object(data): return data -def c_float_array(data): +def _c_float_array(data): """Get pointer of float numpy array / list.""" if _is_1d_list(data): data = np.array(data, copy=False) @@ -531,7 +531,7 @@ def c_float_array(data): return (ptr_data, type_data, data) # return `data` to avoid the temporary copy is freed -def c_int_array(data): +def _c_int_array(data): """Get pointer of int numpy array / list.""" if _is_1d_list(data): data = np.array(data, copy=False) @@ -922,7 +922,7 @@ def inner_predict(mat, start_iteration, num_iteration, predict_type, preds=None) data = np.array(mat.reshape(mat.size), dtype=mat.dtype, copy=False) else: # change non-float data to float data, need to copy data = np.array(mat.reshape(mat.size), dtype=np.float32) - ptr_data, type_ptr_data, _ = c_float_array(data) + ptr_data, type_ptr_data, _ = _c_float_array(data) n_preds = self.__get_num_preds(start_iteration, num_iteration, mat.shape[0], predict_type) if preds is None: preds = np.empty(n_preds, dtype=np.float64) @@ -1018,8 +1018,8 @@ def inner_predict(csr, start_iteration, num_iteration, predict_type, preds=None) raise ValueError("Wrong length of pre-allocated predict array") out_num_preds = ctypes.c_int64(0) - ptr_indptr, type_ptr_indptr, __ = c_int_array(csr.indptr) - ptr_data, type_ptr_data, _ = c_float_array(csr.data) + ptr_indptr, type_ptr_indptr, __ = _c_int_array(csr.indptr) + ptr_data, type_ptr_data, _ = _c_float_array(csr.data) assert csr.shape[1] <= MAX_INT32 csr_indices = csr.indices.astype(np.int32, copy=False) @@ -1045,8 +1045,8 @@ def inner_predict(csr, start_iteration, num_iteration, predict_type, preds=None) return preds, nrow def inner_predict_sparse(csr, start_iteration, num_iteration, predict_type): - ptr_indptr, type_ptr_indptr, __ = c_int_array(csr.indptr) - ptr_data, type_ptr_data, _ = c_float_array(csr.data) + ptr_indptr, type_ptr_indptr, __ = _c_int_array(csr.indptr) + ptr_data, type_ptr_data, _ = _c_float_array(csr.data) csr_indices = csr.indices.astype(np.int32, copy=False) matrix_type = C_API_MATRIX_TYPE_CSR if type_ptr_indptr == C_API_DTYPE_INT32: @@ -1103,8 +1103,8 @@ def inner_predict_sparse(csr, start_iteration, num_iteration, predict_type): def __pred_for_csc(self, csc, start_iteration, num_iteration, predict_type): """Predict for a CSC data.""" def inner_predict_sparse(csc, start_iteration, num_iteration, predict_type): - ptr_indptr, type_ptr_indptr, __ = c_int_array(csc.indptr) - ptr_data, type_ptr_data, _ = c_float_array(csc.data) + ptr_indptr, type_ptr_indptr, __ = _c_int_array(csc.indptr) + ptr_data, type_ptr_data, _ = _c_float_array(csc.data) csc_indices = csc.indices.astype(np.int32, copy=False) matrix_type = C_API_MATRIX_TYPE_CSC if type_ptr_indptr == C_API_DTYPE_INT32: @@ -1150,8 +1150,8 @@ def inner_predict_sparse(csc, start_iteration, num_iteration, predict_type): preds = np.empty(n_preds, dtype=np.float64) out_num_preds = ctypes.c_int64(0) - ptr_indptr, type_ptr_indptr, __ = c_int_array(csc.indptr) - ptr_data, type_ptr_data, _ = c_float_array(csc.data) + ptr_indptr, type_ptr_indptr, __ = _c_int_array(csc.indptr) + ptr_data, type_ptr_data, _ = _c_float_array(csc.data) assert csc.shape[0] <= MAX_INT32 csc_indices = csc.indices.astype(np.int32, copy=False) @@ -1293,7 +1293,7 @@ def _create_sample_indices(self, total_nrow: int) -> np.ndarray: param_str = param_dict_to_str(self.get_params()) sample_cnt = _get_sample_count(total_nrow, param_str) indices = np.empty(sample_cnt, dtype=np.int32) - ptr_data, _, _ = c_int_array(indices) + ptr_data, _, _ = _c_int_array(indices) actual_sample_cnt = ctypes.c_int32(0) _safe_call(_LIB.LGBM_SampleIndices( @@ -1373,11 +1373,11 @@ def _init_from_sample( # each int* points to start of indices for each column indices_col_ptr = (ctypes.POINTER(ctypes.c_int32) * ncol)() for i in range(ncol): - sample_col_ptr[i] = c_float_array(sample_data[i])[0] - indices_col_ptr[i] = c_int_array(sample_indices[i])[0] + sample_col_ptr[i] = _c_float_array(sample_data[i])[0] + indices_col_ptr[i] = _c_int_array(sample_indices[i])[0] num_per_col = np.array([len(d) for d in sample_indices], dtype=np.int32) - num_per_col_ptr, _, _ = c_int_array(num_per_col) + num_per_col_ptr, _, _ = _c_int_array(num_per_col) self.handle = ctypes.c_void_p() params_str = param_dict_to_str(self.get_params()) @@ -1409,7 +1409,7 @@ def _push_rows(self, data: np.ndarray) -> 'Dataset': """ nrow, ncol = data.shape data = data.reshape(data.size) - data_ptr, data_type, _ = c_float_array(data) + data_ptr, data_type, _ = _c_float_array(data) _safe_call(_LIB.LGBM_DatasetPushRows( self.handle, @@ -1704,7 +1704,7 @@ def __init_from_np2d( else: # change non-float data to float data, need to copy data = np.array(mat.reshape(mat.size), dtype=np.float32) - ptr_data, type_ptr_data, _ = c_float_array(data) + ptr_data, type_ptr_data, _ = _c_float_array(data) _safe_call(_LIB.LGBM_DatasetCreateFromMat( ptr_data, ctypes.c_int(type_ptr_data), @@ -1747,7 +1747,7 @@ def __init_from_list_np2d( else: # change non-float data to float data, need to copy mats[i] = np.array(mat.reshape(mat.size), dtype=np.float32) - chunk_ptr_data, chunk_type_ptr_data, holder = c_float_array(mats[i]) + chunk_ptr_data, chunk_type_ptr_data, holder = _c_float_array(mats[i]) if type_ptr_data is not None and chunk_type_ptr_data != type_ptr_data: raise ValueError('Input chunks must have same type') ptr_data[i] = chunk_ptr_data @@ -1778,8 +1778,8 @@ def __init_from_csr( raise ValueError(f'Length mismatch: {len(csr.indices)} vs {len(csr.data)}') self.handle = ctypes.c_void_p() - ptr_indptr, type_ptr_indptr, __ = c_int_array(csr.indptr) - ptr_data, type_ptr_data, _ = c_float_array(csr.data) + ptr_indptr, type_ptr_indptr, __ = _c_int_array(csr.indptr) + ptr_data, type_ptr_data, _ = _c_float_array(csr.data) assert csr.shape[1] <= MAX_INT32 csr_indices = csr.indices.astype(np.int32, copy=False) @@ -1809,8 +1809,8 @@ def __init_from_csc( raise ValueError(f'Length mismatch: {len(csc.indices)} vs {len(csc.data)}') self.handle = ctypes.c_void_p() - ptr_indptr, type_ptr_indptr, __ = c_int_array(csc.indptr) - ptr_data, type_ptr_data, _ = c_float_array(csc.data) + ptr_indptr, type_ptr_indptr, __ = _c_int_array(csc.indptr) + ptr_data, type_ptr_data, _ = _c_float_array(csc.data) assert csc.shape[0] <= MAX_INT32 csc_indices = csc.indices.astype(np.int32, copy=False) @@ -2104,9 +2104,9 @@ def set_field( data = _list_to_1d_numpy(data, dtype, name=field_name) if data.dtype == np.float32 or data.dtype == np.float64: - ptr_data, type_data, _ = c_float_array(data) + ptr_data, type_data, _ = _c_float_array(data) elif data.dtype == np.int32: - ptr_data, type_data, _ = c_int_array(data) + ptr_data, type_data, _ = _c_int_array(data) else: raise TypeError(f"Expected np.float32/64 or np.int32, met type({data.dtype})") if type_data != FIELD_TYPE_MAPPER[field_name]: @@ -3884,7 +3884,7 @@ def refit( new_booster.handle, predictor.handle)) leaf_preds = leaf_preds.reshape(-1) - ptr_data, _, _ = c_int_array(leaf_preds) + ptr_data, _, _ = _c_int_array(leaf_preds) _safe_call(_LIB.LGBM_BoosterRefit( new_booster.handle, ptr_data, From 9afd8b937b6ba1f68fcb6dbc5481859fe3a442b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Morales?= Date: Mon, 12 Dec 2022 23:22:39 -0600 Subject: [PATCH 07/25] [tests][python-package] remove remaining tests using load_boston (fixes #4793) (#5581) --- tests/python_package_test/test_engine.py | 90 ++++++++++------------- tests/python_package_test/test_sklearn.py | 44 +++++------ tests/python_package_test/utils.py | 5 -- 3 files changed, 62 insertions(+), 77 deletions(-) diff --git a/tests/python_package_test/test_engine.py b/tests/python_package_test/test_engine.py index 7d50a1ff390d..1172c620e6ba 100644 --- a/tests/python_package_test/test_engine.py +++ b/tests/python_package_test/test_engine.py @@ -21,7 +21,7 @@ import lightgbm as lgb from lightgbm.compat import PANDAS_INSTALLED, pd_DataFrame -from .utils import (SERIALIZERS, dummy_obj, load_boston, load_breast_cancer, load_digits, load_iris, logistic_sigmoid, +from .utils import (SERIALIZERS, dummy_obj, load_breast_cancer, load_digits, load_iris, logistic_sigmoid, make_synthetic_regression, mse_obj, pickle_and_unpickle_object, sklearn_multiclass_custom_objective, softmax) @@ -114,7 +114,8 @@ def test_rf(): @pytest.mark.parametrize('objective', ['regression', 'regression_l1', 'huber', 'fair', 'poisson']) def test_regression(objective): - X, y = load_boston(return_X_y=True) + X, y = make_synthetic_regression() + y = np.abs(y) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) params = { 'objective': objective, @@ -133,13 +134,13 @@ def test_regression(objective): ) ret = mean_squared_error(y_test, gbm.predict(X_test)) if objective == 'huber': - assert ret < 35 + assert ret < 430 elif objective == 'fair': - assert ret < 17 + assert ret < 296 elif objective == 'poisson': - assert ret < 8 + assert ret < 193 else: - assert ret < 7 + assert ret < 338 assert evals_result['valid_0']['l2'][-1] == pytest.approx(ret) @@ -924,7 +925,7 @@ def test_early_stopping_min_delta(first_only, single_metric, greater_is_better): def test_continue_train(): - X, y = load_boston(return_X_y=True) + X, y = make_synthetic_regression() X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) params = { 'objective': 'regression', @@ -948,7 +949,7 @@ def test_continue_train(): init_model='model.txt' ) ret = mean_absolute_error(y_test, gbm.predict(X_test)) - assert ret < 2.0 + assert ret < 13.6 assert evals_result['valid_0']['l1'][-1] == pytest.approx(ret) np.testing.assert_allclose(evals_result['valid_0']['l1'], evals_result['valid_0']['custom_mae']) @@ -968,7 +969,7 @@ def test_continue_train_reused_dataset(): def test_continue_train_dart(): - X, y = load_boston(return_X_y=True) + X, y = make_synthetic_regression() X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) params = { 'boosting_type': 'dart', @@ -989,7 +990,7 @@ def test_continue_train_dart(): init_model=init_gbm ) ret = mean_absolute_error(y_test, gbm.predict(X_test)) - assert ret < 2.0 + assert ret < 13.6 assert evals_result['valid_0']['l1'][-1] == pytest.approx(ret) @@ -1920,10 +1921,12 @@ def test_refit_dataset_params(): np.testing.assert_allclose(stored_weights, refit_weight) -def test_mape_rf(): - X, y = load_boston(return_X_y=True) +@pytest.mark.parametrize('boosting_type', ['rf', 'dart']) +def test_mape_for_specific_boosting_types(boosting_type): + X, y = make_synthetic_regression() + y = abs(y) params = { - 'boosting_type': 'rf', + 'boosting_type': boosting_type, 'objective': 'mape', 'verbose': -1, 'bagging_freq': 1, @@ -1935,25 +1938,9 @@ def test_mape_rf(): gbm = lgb.train(params, lgb_train, num_boost_round=20) pred = gbm.predict(X) pred_mean = pred.mean() - assert pred_mean > 20 - - -def test_mape_dart(): - X, y = load_boston(return_X_y=True) - params = { - 'boosting_type': 'dart', - 'objective': 'mape', - 'verbose': -1, - 'bagging_freq': 1, - 'bagging_fraction': 0.8, - 'feature_fraction': 0.8, - 'boost_from_average': False - } - lgb_train = lgb.Dataset(X, y) - gbm = lgb.train(params, lgb_train, num_boost_round=40) - pred = gbm.predict(X) - pred_mean = pred.mean() - assert pred_mean > 18 + # the following checks that dart and rf with mape can predict outside the 0-1 range + # https://github.com/microsoft/LightGBM/issues/1579 + assert pred_mean > 8 def check_constant_features(y_true, expected_pred, more_params): @@ -2667,19 +2654,22 @@ def test_model_size(): @pytest.mark.skipif(getenv('TASK', '') == 'cuda_exp', reason='Skip due to differences in implementation details of CUDA Experimental version') def test_get_split_value_histogram(): - X, y = load_boston(return_X_y=True) + X, y = make_synthetic_regression() + X = np.repeat(X, 3, axis=0) + y = np.repeat(y, 3, axis=0) + X[:, 2] = np.random.default_rng(0).integers(0, 20, size=X.shape[0]) lgb_train = lgb.Dataset(X, y, categorical_feature=[2]) gbm = lgb.train({'verbose': -1}, lgb_train, num_boost_round=20) # test XGBoost-style return value params = {'feature': 0, 'xgboost_style': True} - assert gbm.get_split_value_histogram(**params).shape == (9, 2) - assert gbm.get_split_value_histogram(bins=999, **params).shape == (9, 2) + assert gbm.get_split_value_histogram(**params).shape == (12, 2) + assert gbm.get_split_value_histogram(bins=999, **params).shape == (12, 2) assert gbm.get_split_value_histogram(bins=-1, **params).shape == (1, 2) assert gbm.get_split_value_histogram(bins=0, **params).shape == (1, 2) assert gbm.get_split_value_histogram(bins=1, **params).shape == (1, 2) assert gbm.get_split_value_histogram(bins=2, **params).shape == (2, 2) - assert gbm.get_split_value_histogram(bins=6, **params).shape == (5, 2) - assert gbm.get_split_value_histogram(bins=7, **params).shape == (6, 2) + assert gbm.get_split_value_histogram(bins=6, **params).shape == (6, 2) + assert gbm.get_split_value_histogram(bins=7, **params).shape == (7, 2) if lgb.compat.PANDAS_INSTALLED: np.testing.assert_allclose( gbm.get_split_value_histogram(0, xgboost_style=True).values, @@ -2700,8 +2690,8 @@ def test_get_split_value_histogram(): ) # test numpy-style return value hist, bins = gbm.get_split_value_histogram(0) - assert len(hist) == 23 - assert len(bins) == 24 + assert len(hist) == 20 + assert len(bins) == 21 hist, bins = gbm.get_split_value_histogram(0, bins=999) assert len(hist) == 999 assert len(bins) == 1000 @@ -2790,7 +2780,7 @@ def metrics_combination_cv_regression(metric_list, assumed_iteration, ) assert assumed_iteration == len(ret[list(ret.keys())[0]]) - X, y = load_boston(return_X_y=True) + X, y = make_synthetic_regression() X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) X_test1, X_test2, y_test1, y_test2 = train_test_split(X_test, y_test, test_size=0.5, random_state=73) lgb_train = lgb.Dataset(X_train, y_train) @@ -2798,16 +2788,16 @@ def metrics_combination_cv_regression(metric_list, assumed_iteration, lgb_valid2 = lgb.Dataset(X_test2, y_test2, reference=lgb_train) iter_valid1_l1 = 3 - iter_valid1_l2 = 14 - iter_valid2_l1 = 2 + iter_valid1_l2 = 3 + iter_valid2_l1 = 3 iter_valid2_l2 = 15 - assert len(set([iter_valid1_l1, iter_valid1_l2, iter_valid2_l1, iter_valid2_l2])) == 4 + assert len(set([iter_valid1_l1, iter_valid1_l2, iter_valid2_l1, iter_valid2_l2])) == 2 iter_min_l1 = min([iter_valid1_l1, iter_valid2_l1]) iter_min_l2 = min([iter_valid1_l2, iter_valid2_l2]) iter_min_valid1 = min([iter_valid1_l1, iter_valid1_l2]) - iter_cv_l1 = 4 - iter_cv_l2 = 12 + iter_cv_l1 = 15 + iter_cv_l2 = 13 assert len(set([iter_cv_l1, iter_cv_l2])) == 2 iter_cv_min = min([iter_cv_l1, iter_cv_l2]) @@ -3153,7 +3143,7 @@ def _imptcs_to_numpy(X, impcts_dict): def test_interaction_constraints(): - X, y = load_boston(return_X_y=True) + X, y = make_synthetic_regression(n_samples=200) num_features = X.shape[1] train_data = lgb.Dataset(X, label=y) # check that constraint containing all features is equivalent to no constraint @@ -3166,9 +3156,7 @@ def test_interaction_constraints(): pred2 = est.predict(X) np.testing.assert_allclose(pred1, pred2) # check that constraint partitioning the features reduces train accuracy - est = lgb.train(dict(params, interaction_constraints=[list(range(num_features // 2)), - list(range(num_features // 2, num_features))]), - train_data, num_boost_round=10) + est = lgb.train(dict(params, interaction_constraints=[[0, 2], [1, 3]]), train_data, num_boost_round=10) pred3 = est.predict(X) assert mean_squared_error(y, pred1) < mean_squared_error(y, pred3) # check that constraints consisting of single features reduce accuracy further @@ -3568,7 +3556,7 @@ def hook(obj): @pytest.mark.skipif(getenv('TASK', '') == 'cuda_exp', reason='Forced splits are not yet supported by CUDA Experimental version') def test_force_split_with_feature_fraction(tmp_path): - X, y = load_boston(return_X_y=True) + X, y = make_synthetic_regression() X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) lgb_train = lgb.Dataset(X_train, y_train) @@ -3595,7 +3583,7 @@ def test_force_split_with_feature_fraction(tmp_path): gbm = lgb.train(params, lgb_train) ret = mean_absolute_error(y_test, gbm.predict(X_test)) - assert ret < 2.0 + assert ret < 15.7 tree_info = gbm.dump_model()["tree_info"] assert len(tree_info) > 1 diff --git a/tests/python_package_test/test_sklearn.py b/tests/python_package_test/test_sklearn.py index c09be27f1adb..5873bf9112c3 100644 --- a/tests/python_package_test/test_sklearn.py +++ b/tests/python_package_test/test_sklearn.py @@ -21,8 +21,8 @@ import lightgbm as lgb from lightgbm.compat import PANDAS_INSTALLED, pd_DataFrame -from .utils import (load_boston, load_breast_cancer, load_digits, load_iris, load_linnerud, make_ranking, - make_synthetic_regression, sklearn_multiclass_custom_objective, softmax) +from .utils import (load_breast_cancer, load_digits, load_iris, load_linnerud, make_ranking, make_synthetic_regression, + sklearn_multiclass_custom_objective, softmax) decreasing_generator = itertools.count(0, -1) task_to_model_factory = { @@ -112,12 +112,12 @@ def test_binary(): def test_regression(): - X, y = load_boston(return_X_y=True) + X, y = make_synthetic_regression() X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) gbm = lgb.LGBMRegressor(n_estimators=50, verbose=-1) gbm.fit(X_train, y_train, eval_set=[(X_test, y_test)], callbacks=[lgb.early_stopping(5)]) ret = mean_squared_error(y_test, gbm.predict(X_test)) - assert ret < 7 + assert ret < 174 assert gbm.evals_result_['valid_0']['l2'][gbm.best_iteration_ - 1] == pytest.approx(ret) @@ -226,12 +226,12 @@ def test_objective_aliases(custom_objective): def test_regression_with_custom_objective(): - X, y = load_boston(return_X_y=True) + X, y = make_synthetic_regression() X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) gbm = lgb.LGBMRegressor(n_estimators=50, verbose=-1, objective=objective_ls) gbm.fit(X_train, y_train, eval_set=[(X_test, y_test)], callbacks=[lgb.early_stopping(5)]) ret = mean_squared_error(y_test, gbm.predict(X_test)) - assert ret < 7.0 + assert ret < 174 assert gbm.evals_result_['valid_0']['l2'][gbm.best_iteration_ - 1] == pytest.approx(ret) @@ -249,13 +249,12 @@ def test_binary_classification_with_custom_objective(): def test_dart(): - X, y = load_boston(return_X_y=True) + X, y = make_synthetic_regression() X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) gbm = lgb.LGBMRegressor(boosting_type='dart', n_estimators=50) gbm.fit(X_train, y_train) score = gbm.score(X_test, y_test) - assert score >= 0.8 - assert score <= 1. + assert 0.8 <= score <= 1.0 def test_stacking_classifier(): @@ -280,7 +279,9 @@ def test_stacking_classifier(): def test_stacking_regressor(): - X, y = load_boston(return_X_y=True) + X, y = make_synthetic_regression(n_samples=200) + n_features = X.shape[1] + n_input_models = 2 X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42) regressors = [('gbm1', lgb.LGBMRegressor(n_estimators=3)), ('gbm2', lgb.LGBMRegressor(n_estimators=3))] @@ -291,11 +292,11 @@ def test_stacking_regressor(): score = reg.score(X_test, y_test) assert score >= 0.2 assert score <= 1. - assert reg.n_features_in_ == 13 # number of input features - assert len(reg.named_estimators_['gbm1'].feature_importances_) == 13 + assert reg.n_features_in_ == n_features # number of input features + assert len(reg.named_estimators_['gbm1'].feature_importances_) == n_features assert reg.named_estimators_['gbm1'].n_features_in_ == reg.named_estimators_['gbm2'].n_features_in_ - assert reg.final_estimator_.n_features_in_ == 15 # number of concatenated features - assert len(reg.final_estimator_.feature_importances_) == 15 + assert reg.final_estimator_.n_features_in_ == n_features + n_input_models # number of concatenated features + assert len(reg.final_estimator_.feature_importances_) == n_features + n_input_models def test_grid_search(): @@ -765,7 +766,8 @@ def test_evaluate_train_set(): def test_metrics(): - X, y = load_boston(return_X_y=True) + X, y = make_synthetic_regression() + y = abs(y) params = {'n_estimators': 2, 'verbose': -1} params_fit = {'X': X, 'y': y, 'eval_set': (X, y)} @@ -1102,7 +1104,7 @@ def fit_and_check(eval_set_names, metric_names, assumed_iteration, first_metric_ else: assert gbm.n_estimators == gbm.best_iteration_ - X, y = load_boston(return_X_y=True) + X, y = make_synthetic_regression(n_samples=300) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) X_test1, X_test2, y_test1, y_test2 = train_test_split(X_test, y_test, test_size=0.5, random_state=72) params = {'n_estimators': 30, @@ -1114,11 +1116,11 @@ def fit_and_check(eval_set_names, metric_names, assumed_iteration, first_metric_ params_fit = {'X': X_train, 'y': y_train} - iter_valid1_l1 = 3 - iter_valid1_l2 = 18 - iter_valid2_l1 = 11 - iter_valid2_l2 = 7 - assert len(set([iter_valid1_l1, iter_valid1_l2, iter_valid2_l1, iter_valid2_l2])) == 4 + iter_valid1_l1 = 4 + iter_valid1_l2 = 4 + iter_valid2_l1 = 2 + iter_valid2_l2 = 2 + assert len(set([iter_valid1_l1, iter_valid1_l2, iter_valid2_l1, iter_valid2_l2])) == 2 iter_min_l1 = min([iter_valid1_l1, iter_valid2_l1]) iter_min_l2 = min([iter_valid1_l2, iter_valid2_l2]) iter_min = min([iter_min_l1, iter_min_l2]) diff --git a/tests/python_package_test/utils.py b/tests/python_package_test/utils.py index e2c0a29effc1..bb8dcee5ecfd 100644 --- a/tests/python_package_test/utils.py +++ b/tests/python_package_test/utils.py @@ -13,11 +13,6 @@ SERIALIZERS = ["pickle", "joblib", "cloudpickle"] -@lru_cache(maxsize=None) -def load_boston(**kwargs): - return sklearn.datasets.load_boston(**kwargs) - - @lru_cache(maxsize=None) def load_breast_cancer(**kwargs): return sklearn.datasets.load_breast_cancer(**kwargs) From a17489328c4f81819b5a4131c9a386b09b90b04f Mon Sep 17 00:00:00 2001 From: Nikita Titov Date: Thu, 15 Dec 2022 18:05:45 +0300 Subject: [PATCH 08/25] [ci] Use Ubuntu 22.04 as `ubuntu-latest` at CI (fixes #5186) (#5288) --- .ci/setup.sh | 12 +- .github/workflows/linkchecker.yml | 2 +- .github/workflows/no-response.yml | 2 +- .github/workflows/optional_checks.yml | 2 +- .github/workflows/r_configure.yml | 2 +- .github/workflows/r_package.yml | 24 +- .github/workflows/static_analysis.yml | 6 +- .vsts-ci.yml | 8 +- R-package/AUTOCONF_UBUNTU_VERSION | 2 +- R-package/README.md | 8 +- R-package/configure | 712 ++++++++++++++------------ R-package/recreate-configure.sh | 2 +- 12 files changed, 412 insertions(+), 370 deletions(-) diff --git a/.ci/setup.sh b/.ci/setup.sh index d53a5a80c2c4..443aecaf42b5 100755 --- a/.ci/setup.sh +++ b/.ci/setup.sh @@ -42,13 +42,13 @@ else # Linux iputils-ping \ jq \ libcurl4 \ - libicu66 \ - libssl1.1 \ + libicu-dev \ + libssl-dev \ libunwind8 \ locales \ netcat \ unzip \ - zip + zip || exit -1 if [[ $COMPILER == "clang" ]]; then sudo apt-get install --no-install-recommends -y \ clang \ @@ -60,6 +60,10 @@ else # Linux sudo locale-gen ${LANG} sudo update-locale fi + if [[ $TASK == "r-package" ]] && [[ $COMPILER == "clang" ]]; then + sudo apt-get install --no-install-recommends -y \ + libomp-dev + fi if [[ $TASK == "mpi" ]]; then if [[ $IN_UBUNTU_LATEST_CONTAINER == "true" ]]; then sudo apt-get update @@ -75,10 +79,10 @@ else # Linux fi if [[ $TASK == "gpu" ]]; then if [[ $IN_UBUNTU_LATEST_CONTAINER == "true" ]]; then - sudo add-apt-repository ppa:mhier/libboost-latest -y sudo apt-get update sudo apt-get install --no-install-recommends -y \ libboost1.74-dev \ + libboost-filesystem1.74-dev \ ocl-icd-opencl-dev else # in manylinux image sudo yum update -y diff --git a/.github/workflows/linkchecker.yml b/.github/workflows/linkchecker.yml index f4aad95618ea..02a25f01230a 100644 --- a/.github/workflows/linkchecker.yml +++ b/.github/workflows/linkchecker.yml @@ -17,7 +17,7 @@ env: jobs: check-links: timeout-minutes: 60 - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout repository uses: actions/checkout@v3 diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml index a731941c21eb..30d767dd5444 100644 --- a/.github/workflows/no-response.yml +++ b/.github/workflows/no-response.yml @@ -9,7 +9,7 @@ on: jobs: noResponse: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: lee-dohm/no-response@v0.5.0 with: diff --git a/.github/workflows/optional_checks.yml b/.github/workflows/optional_checks.yml index bcb380e3006c..1ebe223ce88d 100644 --- a/.github/workflows/optional_checks.yml +++ b/.github/workflows/optional_checks.yml @@ -9,7 +9,7 @@ on: jobs: all-successful: timeout-minutes: 120 - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout repository uses: actions/checkout@v3 diff --git a/.github/workflows/r_configure.yml b/.github/workflows/r_configure.yml index e399f5316410..fb1e014016ac 100644 --- a/.github/workflows/r_configure.yml +++ b/.github/workflows/r_configure.yml @@ -9,7 +9,7 @@ jobs: name: r-configure timeout-minutes: 60 runs-on: ubuntu-latest - container: "ubuntu:20.04" + container: "ubuntu:22.04" steps: - name: Install essential software before checkout run: | diff --git a/.github/workflows/r_package.yml b/.github/workflows/r_package.yml index dbceb9c69f6b..5ff7a528e349 100644 --- a/.github/workflows/r_package.yml +++ b/.github/workflows/r_package.yml @@ -33,22 +33,22 @@ jobs: ################ # CMake builds # ################ - - os: ubuntu-latest + - os: ubuntu-22.04 task: r-package compiler: gcc r_version: 3.6 build_type: cmake - - os: ubuntu-latest + - os: ubuntu-22.04 task: r-package compiler: gcc r_version: 4.2 build_type: cmake - - os: ubuntu-latest + - os: ubuntu-22.04 task: r-package compiler: clang r_version: 3.6 build_type: cmake - - os: ubuntu-latest + - os: ubuntu-22.04 task: r-package compiler: clang r_version: 4.2 @@ -114,7 +114,7 @@ jobs: toolchain: MSYS r_version: 4.2 build_type: cran - - os: ubuntu-latest + - os: ubuntu-22.04 task: r-package compiler: gcc r_version: 4.2 @@ -127,7 +127,7 @@ jobs: ################ # Other checks # ################ - - os: ubuntu-latest + - os: ubuntu-22.04 task: r-rchk compiler: gcc r_version: 4.2 @@ -151,7 +151,7 @@ jobs: CTAN_MIRROR: https://ctan.math.illinois.edu/systems/win32/miktex TINYTEX_INSTALLER: TinyTeX - name: Setup and run tests on Linux and macOS - if: matrix.os == 'macOS-latest' || matrix.os == 'ubuntu-latest' + if: matrix.os == 'macOS-latest' || matrix.os == 'ubuntu-22.04' shell: bash run: | export TASK="${{ matrix.task }}" @@ -159,7 +159,7 @@ jobs: export GITHUB_ACTIONS="true" if [[ "${{ matrix.os }}" == "macOS-latest" ]]; then export OS_NAME="macos" - elif [[ "${{ matrix.os }}" == "ubuntu-latest" ]]; then + elif [[ "${{ matrix.os }}" == "ubuntu-22.04" ]]; then export OS_NAME="linux" fi export BUILD_DIRECTORY="$GITHUB_WORKSPACE" @@ -181,9 +181,9 @@ jobs: $env:TASK = "${{ matrix.task }}" & "$env:GITHUB_WORKSPACE/.ci/test_windows.ps1" test-r-sanitizers: - name: r-sanitizers (ubuntu-latest, R-devel, ${{ matrix.compiler }} ASAN/UBSAN) + name: r-sanitizers (ubuntu-22.04, R-devel, ${{ matrix.compiler }} ASAN/UBSAN) timeout-minutes: 60 - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 container: wch1/r-debug strategy: fail-fast: false @@ -219,7 +219,7 @@ jobs: test-r-debian-clang: name: r-package (debian, R-devel, clang) timeout-minutes: 60 - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 container: rhub/debian-clang-devel steps: - name: Install Git before checkout @@ -248,7 +248,7 @@ jobs: fi all-successful: # https://github.community/t/is-it-possible-to-require-all-github-actions-tasks-to-pass-without-enumerating-them/117957/4?u=graingert - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: [test, test-r-sanitizers, test-r-debian-clang] steps: - name: Note that all tests succeeded diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 424ae49b4e5f..37b7d8582dd6 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -21,7 +21,7 @@ env: jobs: test: name: ${{ matrix.task }} - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 60 strategy: fail-fast: false @@ -47,7 +47,7 @@ jobs: r-check-docs: name: r-package-check-docs timeout-minutes: 60 - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 container: rocker/verse steps: - name: Trust git cloning LightGBM @@ -82,7 +82,7 @@ jobs: fi all-successful: # https://github.community/t/is-it-possible-to-require-all-github-actions-tasks-to-pass-without-enumerating-them/117957/4?u=graingert - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: [test, r-check-docs] steps: - name: Note that all tests succeeded diff --git a/.vsts-ci.yml b/.vsts-ci.yml index d68d3bd93824..b73676ae58b3 100644 --- a/.vsts-ci.yml +++ b/.vsts-ci.yml @@ -21,7 +21,7 @@ resources: - container: linux-artifact-builder image: lightgbm/vsts-agent:manylinux_2_28_x86_64 - container: ubuntu-latest - image: 'ubuntu:20.04' + image: 'ubuntu:22.04' options: "--name ci-container -v /usr/bin/docker:/tmp/docker:ro" - container: rbase image: wch1/r-debug @@ -151,7 +151,7 @@ jobs: OS_NAME: 'linux' PRODUCES_ARTIFACTS: 'true' pool: - vmImage: ubuntu-latest + vmImage: ubuntu-22.04 timeoutInMinutes: 180 strategy: matrix: @@ -299,7 +299,7 @@ jobs: ########################################### condition: not(startsWith(variables['Build.SourceBranch'], 'refs/pull/')) pool: - vmImage: 'ubuntu-latest' + vmImage: 'ubuntu-22.04' container: rbase steps: - script: | @@ -330,7 +330,7 @@ jobs: - R_artifact condition: and(succeeded(), not(startsWith(variables['Build.SourceBranch'], 'refs/pull/'))) pool: - vmImage: 'ubuntu-latest' + vmImage: 'ubuntu-22.04' steps: # Create archives with complete source code included (with git submodules) - task: ArchiveFiles@2 diff --git a/R-package/AUTOCONF_UBUNTU_VERSION b/R-package/AUTOCONF_UBUNTU_VERSION index 2eee9f218a39..e522974be6a2 100644 --- a/R-package/AUTOCONF_UBUNTU_VERSION +++ b/R-package/AUTOCONF_UBUNTU_VERSION @@ -1 +1 @@ -2.69-11.1 +2.71-2 diff --git a/R-package/README.md b/R-package/README.md index e08d9312a4ec..b3f1d822697f 100644 --- a/R-package/README.md +++ b/R-package/README.md @@ -352,22 +352,22 @@ This section briefly explains the key files for building a CRAN package. To upda 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 20.04. +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 20.04 environment available, run the provided script from the root of the `LightGBM` repository. + 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 20.04 environment, the `configure` script can be generated using Docker by running the code below from the root of this repo. + 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 \ - -t ubuntu:20.04 \ + -t ubuntu:22.04 \ ./R-package/recreate-configure.sh ``` diff --git a/R-package/configure b/R-package/configure index 86918954634e..4529c4de5398 100755 --- a/R-package/configure +++ b/R-package/configure @@ -1,9 +1,10 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for lightgbm 3.3.3.99. +# Generated by GNU Autoconf 2.71 for lightgbm 3.3.3.99. # # -# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. +# Copyright (C) 1992-1996, 1998-2017, 2020-2021 Free Software Foundation, +# Inc. # # # This configure script is free software; the Free Software Foundation @@ -14,14 +15,16 @@ # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh -if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : +as_nop=: +if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 +then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST -else +else $as_nop case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( @@ -31,46 +34,46 @@ esac fi + +# Reset variables that may have inherited troublesome values from +# the environment. + +# IFS needs to be set, to space, tab, and newline, in precisely that order. +# (If _AS_PATH_WALK were called with IFS unset, it would have the +# side effect of setting IFS to empty, thus disabling word splitting.) +# Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl -# Printing a long string crashes Solaris 7 /usr/bin/printf. -as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' -as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo -as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo -# Prefer a ksh shell builtin over an external printf program on Solaris, -# but without wasting forks for bash or zsh. -if test -z "$BASH_VERSION$ZSH_VERSION" \ - && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then - as_echo='print -r --' - as_echo_n='print -rn --' -elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then - as_echo='printf %s\n' - as_echo_n='printf %s' -else - if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then - as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' - as_echo_n='/usr/ucb/echo -n' - else - as_echo_body='eval expr "X$1" : "X\\(.*\\)"' - as_echo_n_body='eval - arg=$1; - case $arg in #( - *"$as_nl"*) - expr "X$arg" : "X\\(.*\\)$as_nl"; - arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; - esac; - expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" - ' - export as_echo_n_body - as_echo_n='sh -c $as_echo_n_body as_echo' - fi - export as_echo_body - as_echo='sh -c $as_echo_body as_echo' -fi +IFS=" "" $as_nl" + +PS1='$ ' +PS2='> ' +PS4='+ ' + +# Ensure predictable behavior from utilities with locale-dependent output. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# We cannot yet rely on "unset" to work, but we need these variables +# to be unset--not just set to an empty or harmless value--now, to +# avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct +# also avoids known problems related to "unset" and subshell syntax +# in other old shells (e.g. bash 2.01 and pdksh 5.2.14). +for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH +do eval test \${$as_var+y} \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done + +# Ensure that fds 0, 1, and 2 are open. +if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi +if (exec 3>&2) ; then :; else exec 2>/dev/null; fi # The user is always right. -if test "${PATH_SEPARATOR+set}" != set; then +if ${PATH_SEPARATOR+false} :; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || @@ -79,13 +82,6 @@ if test "${PATH_SEPARATOR+set}" != set; then fi -# IFS -# We need space, tab and new line, in precisely that order. Quoting is -# there to prevent editors from complaining about space-tab. -# (If _AS_PATH_WALK were called with IFS unset, it would disable word -# splitting by setting IFS to empty value.) -IFS=" "" $as_nl" - # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( @@ -94,8 +90,12 @@ case $0 in #(( for as_dir in $PATH do IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + test -r "$as_dir$0" && as_myself=$as_dir$0 && break done IFS=$as_save_IFS @@ -107,30 +107,10 @@ if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then - $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi -# Unset variables that we do not need and which cause bugs (e.g. in -# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" -# suppresses any "Segmentation fault" message there. '((' could -# trigger a bug in pdksh 5.2.14. -for as_var in BASH_ENV ENV MAIL MAILPATH -do eval test x\${$as_var+set} = xset \ - && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : -done -PS1='$ ' -PS2='> ' -PS4='+ ' - -# NLS nuisances. -LC_ALL=C -export LC_ALL -LANGUAGE=C -export LANGUAGE - -# CDPATH. -(unset CDPATH) >/dev/null 2>&1 && unset CDPATH # Use a proper internal environment variable to ensure we don't fall # into an infinite loop, continuously re-executing ourselves. @@ -152,20 +132,22 @@ esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed `exec'. -$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 -as_fn_exit 255 +printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 +exit 255 fi # We don't want this to propagate to other subprocesses. { _as_can_reexec=; unset _as_can_reexec;} if test "x$CONFIG_SHELL" = x; then - as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : + as_bourne_compatible="as_nop=: +if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 +then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST -else +else \$as_nop case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( @@ -185,41 +167,52 @@ as_fn_success || { exitcode=1; echo as_fn_success failed.; } as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } -if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : +if ( set x; as_fn_ret_success y && test x = \"\$1\" ) +then : -else +else \$as_nop exitcode=1; echo positional parameters were not saved. fi test x\$exitcode = x0 || exit 1 +blah=\$(echo \$(echo blah)) +test x\"\$blah\" = xblah || exit 1 test -x / || exit 1" as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1" - if (eval "$as_required") 2>/dev/null; then : + if (eval "$as_required") 2>/dev/null +then : as_have_required=yes -else +else $as_nop as_have_required=no fi - if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : + if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null +then : -else +else $as_nop as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac as_found=: case $as_dir in #( /*) for as_base in sh bash ksh sh5; do # Try only shells that exist, to save several forks. - as_shell=$as_dir/$as_base + as_shell=$as_dir$as_base if { test -f "$as_shell" || test -f "$as_shell.exe"; } && - { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : + as_run=a "$as_shell" -c "$as_bourne_compatible""$as_required" 2>/dev/null +then : CONFIG_SHELL=$as_shell as_have_required=yes - if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : + if as_run=a "$as_shell" -c "$as_bourne_compatible""$as_suggested" 2>/dev/null +then : break 2 fi fi @@ -227,14 +220,21 @@ fi esac as_found=false done -$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && - { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : - CONFIG_SHELL=$SHELL as_have_required=yes -fi; } IFS=$as_save_IFS +if $as_found +then : +else $as_nop + if { test -f "$SHELL" || test -f "$SHELL.exe"; } && + as_run=a "$SHELL" -c "$as_bourne_compatible""$as_required" 2>/dev/null +then : + CONFIG_SHELL=$SHELL as_have_required=yes +fi +fi - if test "x$CONFIG_SHELL" != x; then : + + if test "x$CONFIG_SHELL" != x +then : export CONFIG_SHELL # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also @@ -252,18 +252,19 @@ esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail # out after a failed `exec'. -$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi - if test x$as_have_required = xno; then : - $as_echo "$0: This script requires a shell more modern than all" - $as_echo "$0: the shells that I found on your system." - if test x${ZSH_VERSION+set} = xset ; then - $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" - $as_echo "$0: be upgraded to zsh 4.3.4 or later." + if test x$as_have_required = xno +then : + printf "%s\n" "$0: This script requires a shell more modern than all" + printf "%s\n" "$0: the shells that I found on your system." + if test ${ZSH_VERSION+y} ; then + printf "%s\n" "$0: In particular, zsh $ZSH_VERSION has bugs and should" + printf "%s\n" "$0: be upgraded to zsh 4.3.4 or later." else - $as_echo "$0: Please tell bug-autoconf@gnu.org about your system, + printf "%s\n" "$0: Please tell bug-autoconf@gnu.org about your system, $0: including any error possibly output before this $0: message. Then install a modern shell, or manually run $0: the script under such a shell if you do have one." @@ -290,6 +291,7 @@ as_fn_unset () } as_unset=as_fn_unset + # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. @@ -307,6 +309,14 @@ as_fn_exit () as_fn_set_status $1 exit $1 } # as_fn_exit +# as_fn_nop +# --------- +# Do nothing but, unlike ":", preserve the value of $?. +as_fn_nop () +{ + return $? +} +as_nop=as_fn_nop # as_fn_mkdir_p # ------------- @@ -321,7 +331,7 @@ as_fn_mkdir_p () as_dirs= while :; do case $as_dir in #( - *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" @@ -330,7 +340,7 @@ $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X"$as_dir" | +printf "%s\n" X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -369,12 +379,13 @@ as_fn_executable_p () # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. -if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null +then : eval 'as_fn_append () { eval $1+=\$2 }' -else +else $as_nop as_fn_append () { eval $1=\$$1\$2 @@ -386,18 +397,27 @@ fi # as_fn_append # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. -if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null +then : eval 'as_fn_arith () { as_val=$(( $* )) }' -else +else $as_nop as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` } fi # as_fn_arith +# as_fn_nop +# --------- +# Do nothing but, unlike ":", preserve the value of $?. +as_fn_nop () +{ + return $? +} +as_nop=as_fn_nop # as_fn_error STATUS ERROR [LINENO LOG_FD] # ---------------------------------------- @@ -409,9 +429,9 @@ as_fn_error () as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi - $as_echo "$as_me: error: $2" >&2 + printf "%s\n" "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error @@ -438,7 +458,7 @@ as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X/"$0" | +printf "%s\n" X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q @@ -482,7 +502,7 @@ as_cr_alnum=$as_cr_Letters$as_cr_digits s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || - { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + { printf "%s\n" "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } # If we had to re-execute with $CONFIG_SHELL, we're ensured to have # already done that, so ensure we don't try to do so again and fall @@ -496,6 +516,10 @@ as_cr_alnum=$as_cr_Letters$as_cr_digits exit } + +# Determine whether it's possible to make 'echo' print without a newline. +# These variables are no longer used directly by Autoconf, but are AC_SUBSTed +# for compatibility with existing Makefiles. ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) @@ -509,6 +533,13 @@ case `echo -n x` in #((((( ECHO_N='-n';; esac +# For backward compatibility with old third-party macros, we provide +# the shell variables $as_echo and $as_echo_n. New code should use +# AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. +as_echo='printf %s\n' +as_echo_n='printf %s' + + rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file @@ -700,8 +731,6 @@ do *) ac_optarg=yes ;; esac - # Accept the important Cygnus configure options, so we can diagnose typos. - case $ac_dashdash$ac_option in --) ac_dashdash=yes ;; @@ -742,9 +771,9 @@ do ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid feature name: $ac_useropt" + as_fn_error $? "invalid feature name: \`$ac_useropt'" ac_useropt_orig=$ac_useropt - ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" @@ -768,9 +797,9 @@ do ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid feature name: $ac_useropt" + as_fn_error $? "invalid feature name: \`$ac_useropt'" ac_useropt_orig=$ac_useropt - ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" @@ -981,9 +1010,9 @@ do ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid package name: $ac_useropt" + as_fn_error $? "invalid package name: \`$ac_useropt'" ac_useropt_orig=$ac_useropt - ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" @@ -997,9 +1026,9 @@ do ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid package name: $ac_useropt" + as_fn_error $? "invalid package name: \`$ac_useropt'" ac_useropt_orig=$ac_useropt - ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" @@ -1043,9 +1072,9 @@ Try \`$0 --help' for more information" *) # FIXME: should be removed in autoconf 3.0. - $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 + printf "%s\n" "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && - $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 + printf "%s\n" "$as_me: WARNING: invalid host type: $ac_option" >&2 : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" ;; @@ -1061,7 +1090,7 @@ if test -n "$ac_unrecognized_opts"; then case $enable_option_checking in no) ;; fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; - *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + *) printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; esac fi @@ -1125,7 +1154,7 @@ $as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_myself" : 'X\(//\)[^/]' \| \ X"$as_myself" : 'X\(//\)$' \| \ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X"$as_myself" | +printf "%s\n" X"$as_myself" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -1264,9 +1293,9 @@ if test "$ac_init_help" = "recursive"; then case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) - ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. - ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; @@ -1294,7 +1323,8 @@ esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix cd "$ac_dir" || { ac_status=$?; continue; } - # Check for guested configure. + # Check for configure.gnu first; this name is used for a wrapper for + # Metaconfig's "Configure" on case-insensitive file systems. if test -f "$ac_srcdir/configure.gnu"; then echo && $SHELL "$ac_srcdir/configure.gnu" --help=recursive @@ -1302,7 +1332,7 @@ ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix echo && $SHELL "$ac_srcdir/configure" --help=recursive else - $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + printf "%s\n" "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done @@ -1312,9 +1342,9 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF lightgbm configure 3.3.3.99 -generated by GNU Autoconf 2.69 +generated by GNU Autoconf 2.71 -Copyright (C) 2012 Free Software Foundation, Inc. +Copyright (C) 2021 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF @@ -1324,14 +1354,34 @@ fi ## ------------------------ ## ## Autoconf initialization. ## ## ------------------------ ## +ac_configure_args_raw= +for ac_arg +do + case $ac_arg in + *\'*) + ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + as_fn_append ac_configure_args_raw " '$ac_arg'" +done + +case $ac_configure_args_raw in + *$as_nl*) + ac_safe_unquote= ;; + *) + ac_unsafe_z='|&;<>()$`\\"*?[ '' ' # This string ends in space, tab. + ac_unsafe_a="$ac_unsafe_z#~" + ac_safe_unquote="s/ '\\([^$ac_unsafe_a][^$ac_unsafe_z]*\\)'/ \\1/g" + ac_configure_args_raw=` printf "%s\n" "$ac_configure_args_raw" | sed "$ac_safe_unquote"`;; +esac + cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by lightgbm $as_me 3.3.3.99, which was -generated by GNU Autoconf 2.69. Invocation command line was +generated by GNU Autoconf 2.71. Invocation command line was - $ $0 $@ + $ $0$ac_configure_args_raw _ACEOF exec 5>>config.log @@ -1364,8 +1414,12 @@ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - $as_echo "PATH: $as_dir" + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + printf "%s\n" "PATH: $as_dir" done IFS=$as_save_IFS @@ -1400,7 +1454,7 @@ do | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) - ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; @@ -1435,11 +1489,13 @@ done # WARNING: Use '\'' to represent an apostrophe within the trap. # WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. trap 'exit_status=$? + # Sanitize IFS. + IFS=" "" $as_nl" # Save into config.log some information that might help in debugging. { echo - $as_echo "## ---------------- ## + printf "%s\n" "## ---------------- ## ## Cache variables. ## ## ---------------- ##" echo @@ -1450,8 +1506,8 @@ trap 'exit_status=$? case $ac_val in #( *${as_nl}*) case $ac_var in #( - *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 -$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( @@ -1475,7 +1531,7 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; ) echo - $as_echo "## ----------------- ## + printf "%s\n" "## ----------------- ## ## Output variables. ## ## ----------------- ##" echo @@ -1483,14 +1539,14 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; do eval ac_val=\$$ac_var case $ac_val in - *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac - $as_echo "$ac_var='\''$ac_val'\''" + printf "%s\n" "$ac_var='\''$ac_val'\''" done | sort echo if test -n "$ac_subst_files"; then - $as_echo "## ------------------- ## + printf "%s\n" "## ------------------- ## ## File substitutions. ## ## ------------------- ##" echo @@ -1498,15 +1554,15 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; do eval ac_val=\$$ac_var case $ac_val in - *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac - $as_echo "$ac_var='\''$ac_val'\''" + printf "%s\n" "$ac_var='\''$ac_val'\''" done | sort echo fi if test -s confdefs.h; then - $as_echo "## ----------- ## + printf "%s\n" "## ----------- ## ## confdefs.h. ## ## ----------- ##" echo @@ -1514,8 +1570,8 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; echo fi test "$ac_signal" != 0 && - $as_echo "$as_me: caught signal $ac_signal" - $as_echo "$as_me: exit $exit_status" + printf "%s\n" "$as_me: caught signal $ac_signal" + printf "%s\n" "$as_me: exit $exit_status" } >&5 rm -f core *.core core.conftest.* && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && @@ -1529,63 +1585,48 @@ ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h -$as_echo "/* confdefs.h */" > confdefs.h +printf "%s\n" "/* confdefs.h */" > confdefs.h # Predefined preprocessor variables. -cat >>confdefs.h <<_ACEOF -#define PACKAGE_NAME "$PACKAGE_NAME" -_ACEOF +printf "%s\n" "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >>confdefs.h -cat >>confdefs.h <<_ACEOF -#define PACKAGE_TARNAME "$PACKAGE_TARNAME" -_ACEOF +printf "%s\n" "#define PACKAGE_TARNAME \"$PACKAGE_TARNAME\"" >>confdefs.h -cat >>confdefs.h <<_ACEOF -#define PACKAGE_VERSION "$PACKAGE_VERSION" -_ACEOF +printf "%s\n" "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >>confdefs.h -cat >>confdefs.h <<_ACEOF -#define PACKAGE_STRING "$PACKAGE_STRING" -_ACEOF +printf "%s\n" "#define PACKAGE_STRING \"$PACKAGE_STRING\"" >>confdefs.h -cat >>confdefs.h <<_ACEOF -#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" -_ACEOF +printf "%s\n" "#define PACKAGE_BUGREPORT \"$PACKAGE_BUGREPORT\"" >>confdefs.h -cat >>confdefs.h <<_ACEOF -#define PACKAGE_URL "$PACKAGE_URL" -_ACEOF +printf "%s\n" "#define PACKAGE_URL \"$PACKAGE_URL\"" >>confdefs.h # Let the site file select an alternate cache file if it wants to. # Prefer an explicitly selected file to automatically selected ones. -ac_site_file1=NONE -ac_site_file2=NONE if test -n "$CONFIG_SITE"; then - # We do not want a PATH search for config.site. - case $CONFIG_SITE in #(( - -*) ac_site_file1=./$CONFIG_SITE;; - */*) ac_site_file1=$CONFIG_SITE;; - *) ac_site_file1=./$CONFIG_SITE;; - esac + ac_site_files="$CONFIG_SITE" elif test "x$prefix" != xNONE; then - ac_site_file1=$prefix/share/config.site - ac_site_file2=$prefix/etc/config.site + ac_site_files="$prefix/share/config.site $prefix/etc/config.site" else - ac_site_file1=$ac_default_prefix/share/config.site - ac_site_file2=$ac_default_prefix/etc/config.site + ac_site_files="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" fi -for ac_site_file in "$ac_site_file1" "$ac_site_file2" + +for ac_site_file in $ac_site_files do - test "x$ac_site_file" = xNONE && continue - if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 -$as_echo "$as_me: loading site script $ac_site_file" >&6;} + case $ac_site_file in #( + */*) : + ;; #( + *) : + ac_site_file=./$ac_site_file ;; +esac + if test -f "$ac_site_file" && test -r "$ac_site_file"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +printf "%s\n" "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ - || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} + || { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file See \`config.log' for more details" "$LINENO" 5; } fi @@ -1595,16 +1636,16 @@ if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special files # actually), so we avoid doing that. DJGPP emulates it as a regular file. if test /dev/null != "$cache_file" && test -f "$cache_file"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 -$as_echo "$as_me: loading cache $cache_file" >&6;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 +printf "%s\n" "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else - { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 -$as_echo "$as_me: creating cache $cache_file" >&6;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 +printf "%s\n" "$as_me: creating cache $cache_file" >&6;} >$cache_file fi @@ -1618,12 +1659,12 @@ for ac_var in $ac_precious_vars; do eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) - { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 -$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 +printf "%s\n" "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) - { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 -$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 +printf "%s\n" "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) @@ -1632,24 +1673,24 @@ $as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} ac_old_val_w=`echo x $ac_old_val` ac_new_val_w=`echo x $ac_new_val` if test "$ac_old_val_w" != "$ac_new_val_w"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 -$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 +printf "%s\n" "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} ac_cache_corrupted=: else - { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 -$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 +printf "%s\n" "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} eval $ac_var=\$ac_old_val fi - { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 -$as_echo "$as_me: former value: \`$ac_old_val'" >&2;} - { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 -$as_echo "$as_me: current value: \`$ac_new_val'" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 +printf "%s\n" "$as_me: former value: \`$ac_old_val'" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 +printf "%s\n" "$as_me: current value: \`$ac_new_val'" >&2;} fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in - *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *\'*) ac_arg=$ac_var=`printf "%s\n" "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in @@ -1659,11 +1700,12 @@ $as_echo "$as_me: current value: \`$ac_new_val'" >&2;} fi done if $ac_cache_corrupted; then - { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} - { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 -$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} - as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in \`$ac_pwd':" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +printf "%s\n" "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error $? "run \`${MAKE-make} distclean' and/or \`rm $cache_file' + and start over" "$LINENO" 5 fi ## -------------------- ## ## Main body of script. ## @@ -1681,10 +1723,10 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu # find compiler and flags # ########################### -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking location of R" >&5 -$as_echo_n "checking location of R... " >&6; } -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: ${R_HOME}" >&5 -$as_echo "${R_HOME}" >&6; } +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking location of R" >&5 +printf %s "checking location of R... " >&6; } +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: ${R_HOME}" >&5 +printf "%s\n" "${R_HOME}" >&6; } # set up CPP flags # find the compiler and compiler flags used by R. @@ -1719,8 +1761,8 @@ LGB_CPPFLAGS="${LGB_CPPFLAGS} -DEIGEN_MPL2_ONLY -DEIGEN_DONT_PARALLELIZE" # MM_PREFETCH # ############### -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether MM_PREFETCH works" >&5 -$as_echo_n "checking whether MM_PREFETCH works... " >&6; } +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether MM_PREFETCH works" >&5 +printf %s "checking whether MM_PREFETCH works... " >&6; } ac_mmprefetch=no cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -1729,7 +1771,7 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext #include int -main () +main (void) { int a = 0; @@ -1744,8 +1786,8 @@ main () _ACEOF ${CXX} ${CPPFLAGS} ${CXXFLAGS} -o conftest conftest.cpp 2>/dev/null && ./conftest && ac_mmprefetch=yes -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: ${ac_mmprefetch}" >&5 -$as_echo "${ac_mmprefetch}" >&6; } +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: ${ac_mmprefetch}" >&5 +printf "%s\n" "${ac_mmprefetch}" >&6; } if test "${ac_mmprefetch}" = yes; then LGB_CPPFLAGS+=" -DMM_PREFETCH=1" fi @@ -1754,8 +1796,8 @@ fi # MM_ALLOC # ############ -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether MM_MALLOC works" >&5 -$as_echo_n "checking whether MM_MALLOC works... " >&6; } +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether MM_MALLOC works" >&5 +printf %s "checking whether MM_MALLOC works... " >&6; } ac_mm_malloc=no cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -1764,7 +1806,7 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext #include int -main () +main (void) { char *a = (char*)_mm_malloc(8, 16); @@ -1779,8 +1821,8 @@ main () _ACEOF ${CXX} ${CPPFLAGS} ${CXXFLAGS} -o conftest conftest.cpp 2>/dev/null && ./conftest && ac_mm_malloc=yes -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: ${ac_mm_malloc}" >&5 -$as_echo "${ac_mm_malloc}" >&6; } +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: ${ac_mm_malloc}" >&5 +printf "%s\n" "${ac_mm_malloc}" >&6; } if test "${ac_mm_malloc}" = yes; then LGB_CPPFLAGS+=" -DMM_MALLOC=1" fi @@ -1810,11 +1852,11 @@ then HOMEBREW_LIBOMP_PREFIX="" if command -v brew &> /dev/null; then ac_brew_openmp=no - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether OpenMP was installed via Homebrew" >&5 -$as_echo_n "checking whether OpenMP was installed via Homebrew... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether OpenMP was installed via Homebrew" >&5 +printf %s "checking whether OpenMP was installed via Homebrew... " >&6; } brew --prefix libomp &>/dev/null && ac_brew_openmp=yes - { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${ac_brew_openmp}" >&5 -$as_echo "${ac_brew_openmp}" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: ${ac_brew_openmp}" >&5 +printf "%s\n" "${ac_brew_openmp}" >&6; } if test "${ac_brew_openmp}" = yes; then HOMEBREW_LIBOMP_PREFIX=`brew --prefix libomp` OPENMP_CXXFLAGS="${OPENMP_CXXFLAGS} -I${HOMEBREW_LIBOMP_PREFIX}/include" @@ -1822,8 +1864,8 @@ $as_echo "${ac_brew_openmp}" >&6; } fi fi ac_pkg_openmp=no - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether OpenMP will work in a package" >&5 -$as_echo_n "checking whether OpenMP will work in a package... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether OpenMP will work in a package" >&5 +printf %s "checking whether OpenMP will work in a package... " >&6; } cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -1831,7 +1873,7 @@ $as_echo_n "checking whether OpenMP will work in a package... " >&6; } #include int -main () +main (void) { return (omp_get_max_threads() <= 1); @@ -1855,8 +1897,8 @@ _ACEOF ${CXX} ${CPPFLAGS} ${CXXFLAGS} ${LDFLAGS} ${OPENMP_CXXFLAGS} ${OPENMP_LIB} -o conftest conftest.cpp 2>/dev/null && ./conftest && ac_pkg_openmp=yes fi - { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${ac_pkg_openmp}" >&5 -$as_echo "${ac_pkg_openmp}" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: ${ac_pkg_openmp}" >&5 +printf "%s\n" "${ac_pkg_openmp}" >&6; } if test "${ac_pkg_openmp}" = no; then OPENMP_CXXFLAGS='' OPENMP_LIB='' @@ -1904,8 +1946,8 @@ _ACEOF case $ac_val in #( *${as_nl}*) case $ac_var in #( - *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 -$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( @@ -1935,15 +1977,15 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; /^ac_cv_env_/b end t clear :clear - s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ + s/^\([^=]*\)=\(.*[{}].*\)$/test ${\1+y} || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then if test "x$cache_file" != "x/dev/null"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 -$as_echo "$as_me: updating cache $cache_file" >&6;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +printf "%s\n" "$as_me: updating cache $cache_file" >&6;} if test ! -f "$cache_file" || test -h "$cache_file"; then cat confcache >"$cache_file" else @@ -1957,8 +1999,8 @@ $as_echo "$as_me: updating cache $cache_file" >&6;} fi fi else - { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 -$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +printf "%s\n" "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache @@ -2011,7 +2053,7 @@ U= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' - ac_i=`$as_echo "$ac_i" | sed "$ac_script"` + ac_i=`printf "%s\n" "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" @@ -2027,8 +2069,8 @@ LTLIBOBJS=$ac_ltlibobjs ac_write_fail=0 ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" -{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 -$as_echo "$as_me: creating $CONFIG_STATUS" >&6;} +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +printf "%s\n" "$as_me: creating $CONFIG_STATUS" >&6;} as_write_fail=0 cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 #! $SHELL @@ -2051,14 +2093,16 @@ cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh -if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : +as_nop=: +if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 +then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST -else +else $as_nop case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( @@ -2068,46 +2112,46 @@ esac fi + +# Reset variables that may have inherited troublesome values from +# the environment. + +# IFS needs to be set, to space, tab, and newline, in precisely that order. +# (If _AS_PATH_WALK were called with IFS unset, it would have the +# side effect of setting IFS to empty, thus disabling word splitting.) +# Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl -# Printing a long string crashes Solaris 7 /usr/bin/printf. -as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' -as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo -as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo -# Prefer a ksh shell builtin over an external printf program on Solaris, -# but without wasting forks for bash or zsh. -if test -z "$BASH_VERSION$ZSH_VERSION" \ - && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then - as_echo='print -r --' - as_echo_n='print -rn --' -elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then - as_echo='printf %s\n' - as_echo_n='printf %s' -else - if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then - as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' - as_echo_n='/usr/ucb/echo -n' - else - as_echo_body='eval expr "X$1" : "X\\(.*\\)"' - as_echo_n_body='eval - arg=$1; - case $arg in #( - *"$as_nl"*) - expr "X$arg" : "X\\(.*\\)$as_nl"; - arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; - esac; - expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" - ' - export as_echo_n_body - as_echo_n='sh -c $as_echo_n_body as_echo' - fi - export as_echo_body - as_echo='sh -c $as_echo_body as_echo' -fi +IFS=" "" $as_nl" + +PS1='$ ' +PS2='> ' +PS4='+ ' + +# Ensure predictable behavior from utilities with locale-dependent output. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# We cannot yet rely on "unset" to work, but we need these variables +# to be unset--not just set to an empty or harmless value--now, to +# avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct +# also avoids known problems related to "unset" and subshell syntax +# in other old shells (e.g. bash 2.01 and pdksh 5.2.14). +for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH +do eval test \${$as_var+y} \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done + +# Ensure that fds 0, 1, and 2 are open. +if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi +if (exec 3>&2) ; then :; else exec 2>/dev/null; fi # The user is always right. -if test "${PATH_SEPARATOR+set}" != set; then +if ${PATH_SEPARATOR+false} :; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || @@ -2116,13 +2160,6 @@ if test "${PATH_SEPARATOR+set}" != set; then fi -# IFS -# We need space, tab and new line, in precisely that order. Quoting is -# there to prevent editors from complaining about space-tab. -# (If _AS_PATH_WALK were called with IFS unset, it would disable word -# splitting by setting IFS to empty value.) -IFS=" "" $as_nl" - # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( @@ -2131,8 +2168,12 @@ case $0 in #(( for as_dir in $PATH do IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + test -r "$as_dir$0" && as_myself=$as_dir$0 && break done IFS=$as_save_IFS @@ -2144,30 +2185,10 @@ if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then - $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi -# Unset variables that we do not need and which cause bugs (e.g. in -# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" -# suppresses any "Segmentation fault" message there. '((' could -# trigger a bug in pdksh 5.2.14. -for as_var in BASH_ENV ENV MAIL MAILPATH -do eval test x\${$as_var+set} = xset \ - && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : -done -PS1='$ ' -PS2='> ' -PS4='+ ' - -# NLS nuisances. -LC_ALL=C -export LC_ALL -LANGUAGE=C -export LANGUAGE - -# CDPATH. -(unset CDPATH) >/dev/null 2>&1 && unset CDPATH # as_fn_error STATUS ERROR [LINENO LOG_FD] @@ -2180,13 +2201,14 @@ as_fn_error () as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi - $as_echo "$as_me: error: $2" >&2 + printf "%s\n" "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error + # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. @@ -2213,18 +2235,20 @@ as_fn_unset () { eval $1=; unset $1;} } as_unset=as_fn_unset + # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. -if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null +then : eval 'as_fn_append () { eval $1+=\$2 }' -else +else $as_nop as_fn_append () { eval $1=\$$1\$2 @@ -2236,12 +2260,13 @@ fi # as_fn_append # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. -if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null +then : eval 'as_fn_arith () { as_val=$(( $* )) }' -else +else $as_nop as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` @@ -2272,7 +2297,7 @@ as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X/"$0" | +printf "%s\n" X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q @@ -2294,6 +2319,10 @@ as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits + +# Determine whether it's possible to make 'echo' print without a newline. +# These variables are no longer used directly by Autoconf, but are AC_SUBSTed +# for compatibility with existing Makefiles. ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) @@ -2307,6 +2336,12 @@ case `echo -n x` in #((((( ECHO_N='-n';; esac +# For backward compatibility with old third-party macros, we provide +# the shell variables $as_echo and $as_echo_n. New code should use +# AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. +as_echo='printf %s\n' +as_echo_n='printf %s' + rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file @@ -2348,7 +2383,7 @@ as_fn_mkdir_p () as_dirs= while :; do case $as_dir in #( - *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" @@ -2357,7 +2392,7 @@ $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X"$as_dir" | +printf "%s\n" X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -2420,7 +2455,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # values after options handling. ac_log=" This file was extended by lightgbm $as_me 3.3.3.99, which was -generated by GNU Autoconf 2.69. Invocation command line was +generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS @@ -2469,14 +2504,16 @@ $config_files Report bugs to the package provider." _ACEOF +ac_cs_config=`printf "%s\n" "$ac_configure_args" | sed "$ac_safe_unquote"` +ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\''/g"` cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 -ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" +ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ lightgbm config.status 3.3.3.99 -configured by $0, generated by GNU Autoconf 2.69, +configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" -Copyright (C) 2012 Free Software Foundation, Inc. +Copyright (C) 2021 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." @@ -2513,21 +2550,21 @@ do -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) - $as_echo "$ac_cs_version"; exit ;; + printf "%s\n" "$ac_cs_version"; exit ;; --config | --confi | --conf | --con | --co | --c ) - $as_echo "$ac_cs_config"; exit ;; + printf "%s\n" "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift case $ac_optarg in - *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + *\'*) ac_optarg=`printf "%s\n" "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; '') as_fn_error $? "missing file argument" ;; esac as_fn_append CONFIG_FILES " '$ac_optarg'" ac_need_defaults=false;; --he | --h | --help | --hel | -h ) - $as_echo "$ac_cs_usage"; exit ;; + printf "%s\n" "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; @@ -2555,7 +2592,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift - \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 + \printf "%s\n" "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' export CONFIG_SHELL exec "\$@" @@ -2569,7 +2606,7 @@ exec 5>>config.log sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX - $as_echo "$ac_log" + printf "%s\n" "$ac_log" } >&5 _ACEOF @@ -2594,7 +2631,7 @@ done # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then - test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files + test ${CONFIG_FILES+y} || CONFIG_FILES=$config_files fi # Have a temporary directory for convenience. Make it in the build tree @@ -2822,7 +2859,7 @@ do esac || as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; esac - case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + case $ac_f in *\'*) ac_f=`printf "%s\n" "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done @@ -2830,17 +2867,17 @@ do # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` - $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + printf "%s\n" "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" - { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 -$as_echo "$as_me: creating $ac_file" >&6;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +printf "%s\n" "$as_me: creating $ac_file" >&6;} fi # Neutralize special characters interpreted by sed in replacement strings. case $configure_input in #( *\&* | *\|* | *\\* ) - ac_sed_conf_input=`$as_echo "$configure_input" | + ac_sed_conf_input=`printf "%s\n" "$configure_input" | sed 's/[\\\\&|]/\\\\&/g'`;; #( *) ac_sed_conf_input=$configure_input;; esac @@ -2857,7 +2894,7 @@ $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X"$ac_file" | +printf "%s\n" X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -2881,9 +2918,9 @@ $as_echo X"$ac_file" | case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) - ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. - ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; @@ -2936,8 +2973,8 @@ ac_sed_dataroot=' case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 -$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +printf "%s\n" "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_datarootdir_hack=' @@ -2979,9 +3016,9 @@ test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ "$ac_tmp/out"`; test -z "$ac_out"; } && - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&5 -$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' +printf "%s\n" "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} rm -f "$ac_tmp/stdin" @@ -3028,7 +3065,8 @@ if test "$no_create" != yes; then $ac_cs_success || as_fn_exit 1 fi if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 -$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi + diff --git a/R-package/recreate-configure.sh b/R-package/recreate-configure.sh index 2df5ffa64f6f..df3586fd2af2 100755 --- a/R-package/recreate-configure.sh +++ b/R-package/recreate-configure.sh @@ -1,7 +1,7 @@ #!/bin/bash # recreates 'configure' from 'configure.ac' -# this script should run on Ubuntu 20.04 +# 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 From 08f0853db2cf609bad43d5df5a4d92c3fe2d7caf Mon Sep 17 00:00:00 2001 From: James Lamb Date: Thu, 22 Dec 2022 10:00:29 -0600 Subject: [PATCH 09/25] [docs] add postgresml to list of external repos (#5565) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5c575518e031..0ade6706b7ba 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,8 @@ MLflow (experiment tracking, model monitoring framework): https://github.com/mlf lightgbm-transform (feature transformation binding): https://github.com/microsoft/lightgbm-transform +`postgresml` (LightGBM training and prediction in SQL, via a Postgres extension): https://github.com/postgresml/postgresml + Support ------- From 36055d410f5c75bc17454823b6aa2b3cc95264dc Mon Sep 17 00:00:00 2001 From: James Lamb Date: Sun, 25 Dec 2022 09:37:46 -0600 Subject: [PATCH 10/25] [docs] update cran-comments.md for v3.3.4 release (#5646) --- R-package/cran-comments.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/R-package/cran-comments.md b/R-package/cran-comments.md index 70f89a89159f..e52f2f15dd01 100644 --- a/R-package/cran-comments.md +++ b/R-package/cran-comments.md @@ -1,9 +1,31 @@ # CRAN Submission History +## 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.3. + +> 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: From 6482b47e9d3384a25e51f22ad063e599c24dd19d Mon Sep 17 00:00:00 2001 From: shiyu1994 Date: Tue, 27 Dec 2022 15:46:39 +0800 Subject: [PATCH 11/25] [CUDA] Add L2 metric for new CUDA version (#5633) * add rmse metric for new cuda version * add Init for CUDAMetricInterface * fix lint errors * fix rmse and add l2 metric for new cuda version * use CUDAL2Metric * explicit template instantiation * write result only with the first thread * pre allocate buffer for output converting * fix l2 regression with cuda metric evaluation * weighting loss in cuda metric evaluation * mark CUDATree::AsConstantTree as override --- include/LightGBM/cuda/cuda_tree.hpp | 2 ++ include/LightGBM/cuda/cuda_utils.h | 3 +++ include/LightGBM/objective_function.h | 5 ++++- include/LightGBM/tree.h | 2 +- src/io/cuda/cuda_tree.cpp | 4 ++++ src/metric/cuda/cuda_regression_metric.cpp | 8 +++++++- src/metric/cuda/cuda_regression_metric.cu | 13 +++++++++--- src/metric/cuda/cuda_regression_metric.hpp | 20 +++++++++++++++---- src/metric/metric.cpp | 3 +-- .../cuda/cuda_regression_objective.cu | 8 ++++++-- .../cuda/cuda_regression_objective.hpp | 4 ++++ src/objective/regression_objective.hpp | 1 - 12 files changed, 58 insertions(+), 15 deletions(-) diff --git a/include/LightGBM/cuda/cuda_tree.hpp b/include/LightGBM/cuda/cuda_tree.hpp index 9d89dc3b7465..d557798270e0 100644 --- a/include/LightGBM/cuda/cuda_tree.hpp +++ b/include/LightGBM/cuda/cuda_tree.hpp @@ -77,6 +77,8 @@ class CUDATree : public Tree { const data_size_t* used_data_indices, data_size_t num_data, double* score) const override; + inline void AsConstantTree(double val) override; + const int* cuda_leaf_parent() const { return cuda_leaf_parent_; } const int* cuda_left_child() const { return cuda_left_child_; } diff --git a/include/LightGBM/cuda/cuda_utils.h b/include/LightGBM/cuda/cuda_utils.h index 797e9f1b44d5..c74016a72ea1 100644 --- a/include/LightGBM/cuda/cuda_utils.h +++ b/include/LightGBM/cuda/cuda_utils.h @@ -119,6 +119,9 @@ class CUDAVector { } void Resize(size_t size) { + if (size == size_) { + return; + } if (size == 0) { Clear(); } diff --git a/include/LightGBM/objective_function.h b/include/LightGBM/objective_function.h index 0d28bc57eb4a..376a6f1a071d 100644 --- a/include/LightGBM/objective_function.h +++ b/include/LightGBM/objective_function.h @@ -101,9 +101,12 @@ class ObjectiveFunction { /*! * \brief Convert output for CUDA version */ - const double* ConvertOutputCUDA(data_size_t /*num_data*/, const double* input, double* /*output*/) const { + virtual const double* ConvertOutputCUDA(data_size_t /*num_data*/, const double* input, double* /*output*/) const { return input; } + + virtual bool NeedConvertOutputCUDA () const { return false; } + #endif // USE_CUDA_EXP }; diff --git a/include/LightGBM/tree.h b/include/LightGBM/tree.h index 6ff0370e2ea6..3e403b16e89b 100644 --- a/include/LightGBM/tree.h +++ b/include/LightGBM/tree.h @@ -228,7 +228,7 @@ class Tree { shrinkage_ = 1.0f; } - inline void AsConstantTree(double val) { + virtual inline void AsConstantTree(double val) { num_leaves_ = 1; shrinkage_ = 1.0f; leaf_value_[0] = val; diff --git a/src/io/cuda/cuda_tree.cpp b/src/io/cuda/cuda_tree.cpp index b7ecee6e6167..196563340ae5 100644 --- a/src/io/cuda/cuda_tree.cpp +++ b/src/io/cuda/cuda_tree.cpp @@ -330,6 +330,10 @@ void CUDATree::SyncLeafOutputFromCUDAToHost() { CopyFromCUDADeviceToHost(leaf_value_.data(), cuda_leaf_value_, leaf_value_.size(), __FILE__, __LINE__); } +void CUDATree::AsConstantTree(double val) { + Tree::AsConstantTree(val); + CopyFromHostToCUDADevice(cuda_leaf_value_, &val, 1, __FILE__, __LINE__); +} } // namespace LightGBM diff --git a/src/metric/cuda/cuda_regression_metric.cpp b/src/metric/cuda/cuda_regression_metric.cpp index 5d8ae39fd3e4..f8232b9d1f2e 100644 --- a/src/metric/cuda/cuda_regression_metric.cpp +++ b/src/metric/cuda/cuda_regression_metric.cpp @@ -31,13 +31,19 @@ void CUDARegressionMetricInterface::Init(const Metadat template std::vector CUDARegressionMetricInterface::Eval(const double* score, const ObjectiveFunction* objective) const { - const double* score_convert = objective->ConvertOutputCUDA(this->num_data_, score, score_convert_buffer_.RawData()); + const double* score_convert = score; + if (objective != nullptr && objective->NeedConvertOutputCUDA()) { + score_convert_buffer_.Resize(static_cast(this->num_data_) * static_cast(this->num_class_)); + score_convert = objective->ConvertOutputCUDA(this->num_data_, score, score_convert_buffer_.RawData()); + } const double eval_score = LaunchEvalKernel(score_convert); return std::vector{eval_score}; } CUDARMSEMetric::CUDARMSEMetric(const Config& config): CUDARegressionMetricInterface(config) {} +CUDAL2Metric::CUDAL2Metric(const Config& config): CUDARegressionMetricInterface(config) {} + } // namespace LightGBM #endif // USE_CUDA_EXP diff --git a/src/metric/cuda/cuda_regression_metric.cu b/src/metric/cuda/cuda_regression_metric.cu index 0442416459c5..e6f37d5cb131 100644 --- a/src/metric/cuda/cuda_regression_metric.cu +++ b/src/metric/cuda/cuda_regression_metric.cu @@ -19,16 +19,22 @@ __global__ void EvalKernel(const data_size_t num_data, const label_t* labels, co const data_size_t index = static_cast(threadIdx.x + blockIdx.x * blockDim.x); double point_metric = 0.0; if (index < num_data) { - point_metric = CUDA_METRIC::MetricOnPointCUDA(labels[index], scores[index]); + point_metric = USE_WEIGHTS ? + CUDA_METRIC::MetricOnPointCUDA(labels[index], scores[index]) * weights[index] : + CUDA_METRIC::MetricOnPointCUDA(labels[index], scores[index]); } const double block_sum_point_metric = ShuffleReduceSum(point_metric, shared_mem_buffer, NUM_DATA_PER_EVAL_THREAD); - reduce_block_buffer[blockIdx.x] = block_sum_point_metric; + if (threadIdx.x == 0) { + reduce_block_buffer[blockIdx.x] = block_sum_point_metric; + } if (USE_WEIGHTS) { double weight = 0.0; if (index < num_data) { weight = static_cast(weights[index]); const double block_sum_weight = ShuffleReduceSum(weight, shared_mem_buffer, NUM_DATA_PER_EVAL_THREAD); - reduce_block_buffer[blockIdx.x + blockDim.x] = block_sum_weight; + if (threadIdx.x == 0) { + reduce_block_buffer[blockIdx.x + gridDim.x] = block_sum_weight; + } } } } @@ -55,6 +61,7 @@ double CUDARegressionMetricInterface::LaunchEvalKernel } template double CUDARegressionMetricInterface::LaunchEvalKernel(const double* score) const; +template double CUDARegressionMetricInterface::LaunchEvalKernel(const double* score) const; } // namespace LightGBM diff --git a/src/metric/cuda/cuda_regression_metric.hpp b/src/metric/cuda/cuda_regression_metric.hpp index fe49bd0d729d..aece087dc448 100644 --- a/src/metric/cuda/cuda_regression_metric.hpp +++ b/src/metric/cuda/cuda_regression_metric.hpp @@ -23,7 +23,7 @@ namespace LightGBM { template class CUDARegressionMetricInterface: public CUDAMetricInterface { public: - explicit CUDARegressionMetricInterface(const Config& config): CUDAMetricInterface(config) {} + explicit CUDARegressionMetricInterface(const Config& config): CUDAMetricInterface(config), num_class_(config.num_class) {} virtual ~CUDARegressionMetricInterface() {} @@ -34,9 +34,10 @@ class CUDARegressionMetricInterface: public CUDAMetricInterface { protected: double LaunchEvalKernel(const double* score_convert) const; - CUDAVector score_convert_buffer_; + mutable CUDAVector score_convert_buffer_; CUDAVector reduce_block_buffer_; CUDAVector reduce_block_buffer_inner_; + const int num_class_; }; class CUDARMSEMetric: public CUDARegressionMetricInterface { @@ -45,8 +46,19 @@ class CUDARMSEMetric: public CUDARegressionMetricInterface(label)); + __device__ inline static double MetricOnPointCUDA(label_t label, double score) { + return (score - label) * (score - label); + } +}; + +class CUDAL2Metric : public CUDARegressionMetricInterface { + public: + explicit CUDAL2Metric(const Config& config); + + virtual ~CUDAL2Metric() {} + + __device__ inline static double MetricOnPointCUDA(label_t label, double score) { + return (score - label) * (score - label); } }; diff --git a/src/metric/metric.cpp b/src/metric/metric.cpp index 32241656cc4c..9cbd72c76188 100644 --- a/src/metric/metric.cpp +++ b/src/metric/metric.cpp @@ -19,8 +19,7 @@ Metric* Metric::CreateMetric(const std::string& type, const Config& config) { #ifdef USE_CUDA_EXP if (config.device_type == std::string("cuda_exp")) { if (type == std::string("l2")) { - Log::Warning("Metric l2 is not implemented in cuda_exp version. Fall back to evaluation on CPU."); - return new L2Metric(config); + return new CUDAL2Metric(config); } else if (type == std::string("rmse")) { return new CUDARMSEMetric(config); } else if (type == std::string("l1")) { diff --git a/src/objective/cuda/cuda_regression_objective.cu b/src/objective/cuda/cuda_regression_objective.cu index d231cb890624..99feec132508 100644 --- a/src/objective/cuda/cuda_regression_objective.cu +++ b/src/objective/cuda/cuda_regression_objective.cu @@ -70,8 +70,12 @@ __global__ void ConvertOutputCUDAKernel_Regression(const bool sqrt, const data_s const double* CUDARegressionL2loss::LaunchConvertOutputCUDAKernel(const data_size_t num_data, const double* input, double* output) const { const int num_blocks = (num_data + GET_GRADIENTS_BLOCK_SIZE_REGRESSION - 1) / GET_GRADIENTS_BLOCK_SIZE_REGRESSION; - ConvertOutputCUDAKernel_Regression<<>>(sqrt_, num_data, input, output); - return output; + if (sqrt_) { + ConvertOutputCUDAKernel_Regression<<>>(sqrt_, num_data, input, output); + return output; + } else { + return input; + } } template diff --git a/src/objective/cuda/cuda_regression_objective.hpp b/src/objective/cuda/cuda_regression_objective.hpp index 2e9a747e3a25..593fcf1cfcb6 100644 --- a/src/objective/cuda/cuda_regression_objective.hpp +++ b/src/objective/cuda/cuda_regression_objective.hpp @@ -50,6 +50,8 @@ class CUDARegressionL2loss : public CUDARegressionObjectiveInterface(config.poisson_max_delta_step); if (sqrt_) { Log::Warning("Cannot use sqrt transform in %s Regression, will auto disable it", GetName()); From a2ae6b95fc4bfdb839068f8ab9b746fb3e80e6e1 Mon Sep 17 00:00:00 2001 From: James Lamb Date: Tue, 27 Dec 2022 20:53:35 -0600 Subject: [PATCH 12/25] [ci] make GitHub Actions branch protection stricter (fixes #5501) (#5645) --- .github/workflows/cuda.yml | 8 +++++--- .github/workflows/optional_checks.yml | 2 +- .github/workflows/python_package.yml | 8 +++++--- .github/workflows/r_package.yml | 10 ++++++---- .github/workflows/static_analysis.yml | 10 ++++++---- 5 files changed, 23 insertions(+), 15 deletions(-) diff --git a/.github/workflows/cuda.yml b/.github/workflows/cuda.yml index a8d69ddcaa33..50a3aa71dde8 100644 --- a/.github/workflows/cuda.yml +++ b/.github/workflows/cuda.yml @@ -107,10 +107,12 @@ jobs: docker_img="${docker_img}-ubuntu$(lsb_release -rs)" fi docker run --env-file docker.env -v "$GITHUB_WORKSPACE":"$ROOT_DOCKER_FOLDER" --rm --gpus all "$docker_img" /bin/bash $ROOT_DOCKER_FOLDER/docker-script.sh - all-successful: - # https://github.community/t/is-it-possible-to-require-all-github-actions-tasks-to-pass-without-enumerating-them/117957/4?u=graingert + all-cuda-jobs-successful: + if: always() runs-on: ubuntu-latest needs: [test] steps: - name: Note that all tests succeeded - run: echo "🎉" + uses: re-actors/alls-green@v1.2.2 + with: + jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/optional_checks.yml b/.github/workflows/optional_checks.yml index 1ebe223ce88d..d35bca295dd9 100644 --- a/.github/workflows/optional_checks.yml +++ b/.github/workflows/optional_checks.yml @@ -7,7 +7,7 @@ on: - release/* jobs: - all-successful: + all-optional-checks-successful: timeout-minutes: 120 runs-on: ubuntu-22.04 steps: diff --git a/.github/workflows/python_package.yml b/.github/workflows/python_package.yml index 877a5d623bac..edab1276921c 100644 --- a/.github/workflows/python_package.yml +++ b/.github/workflows/python_package.yml @@ -71,10 +71,12 @@ jobs: export PATH=${CONDA}/bin:${PATH} $GITHUB_WORKSPACE/.ci/setup.sh || exit -1 $GITHUB_WORKSPACE/.ci/test.sh || exit -1 - all-successful: - # https://github.community/t/is-it-possible-to-require-all-github-actions-tasks-to-pass-without-enumerating-them/117957/4?u=graingert + all-python-package-jobs-successful: + if: always() runs-on: ubuntu-latest needs: [test] steps: - name: Note that all tests succeeded - run: echo "🎉" + uses: re-actors/alls-green@v1.2.2 + with: + jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/r_package.yml b/.github/workflows/r_package.yml index 5ff7a528e349..1995068c2484 100644 --- a/.github/workflows/r_package.yml +++ b/.github/workflows/r_package.yml @@ -246,10 +246,12 @@ jobs: echo "NOTEs, WARNINGs, or ERRORs have been found by R CMD check" exit -1 fi - all-successful: - # https://github.community/t/is-it-possible-to-require-all-github-actions-tasks-to-pass-without-enumerating-them/117957/4?u=graingert - runs-on: ubuntu-22.04 + all-r-package-jobs-successful: + if: always() + runs-on: ubuntu-latest needs: [test, test-r-sanitizers, test-r-debian-clang] steps: - name: Note that all tests succeeded - run: echo "🎉" + uses: re-actors/alls-green@v1.2.2 + with: + jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 37b7d8582dd6..663ea8536f11 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -80,10 +80,12 @@ jobs: echo "" exit -1 fi - all-successful: - # https://github.community/t/is-it-possible-to-require-all-github-actions-tasks-to-pass-without-enumerating-them/117957/4?u=graingert - runs-on: ubuntu-22.04 + all-static-analysis-jobs-successful: + if: always() + runs-on: ubuntu-latest needs: [test, r-check-docs] steps: - name: Note that all tests succeeded - run: echo "🎉" + uses: re-actors/alls-green@v1.2.2 + with: + jobs: ${{ toJSON(needs) }} From fffd066cb331a3573fc8565915c914ae5b6b8313 Mon Sep 17 00:00:00 2001 From: Yifei Liu <55793884+lyf-00@users.noreply.github.com> Date: Wed, 28 Dec 2022 14:09:11 +0800 Subject: [PATCH 13/25] Decouple Boosting Types (fixes #3128) (#4827) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add parameter data_sample_strategy * abstract GOSS as a sample strategy(GOSS1), togetherwith origial GOSS (Normal Bagging has not been abstracted, so do NOT use it now) * abstract Bagging as a subclass (BAGGING), but original Bagging members in GBDT are still kept * fix some variables * remove GOSS(as boost) and Bagging logic in GBDT * rename GOSS1 to GOSS(as sample strategy) * add warning about use GOSS as boosting_type * a little ; bug * remove CHECK when "gradients != nullptr" * rename DataSampleStrategy to avoid confusion * remove and add some ccomments, followingconvention * fix bug about GBDT::ResetConfig (ObjectiveFunction inconsistencty bet… * add std::ignore to avoid compiler warnings (anpotential fails) * update Makevars and vcxproj * handle constant hessian move resize of gradient vectors out of sample strategy * mark override for IsHessianChange * fix lint errors * rerun parameter_generator.py * update config_auto.cpp * delete redundant blank line * update num_data_ when train_data_ is updated set gradients and hessians when GOSS * check bagging_freq is not zero * reset config_ value merge ResetBaggingConfig and ResetGOSS * remove useless check * add ttests in test_engine.py * remove whitespace in blank line * remove arguments verbose_eval and evals_result * Update tests/python_package_test/test_engine.py reduce num_boost_round Co-authored-by: James Lamb * Update tests/python_package_test/test_engine.py reduce num_boost_round Co-authored-by: James Lamb * Update tests/python_package_test/test_engine.py reduce num_boost_round Co-authored-by: James Lamb * Update tests/python_package_test/test_engine.py reduce num_boost_round Co-authored-by: James Lamb * Update tests/python_package_test/test_engine.py reduce num_boost_round Co-authored-by: James Lamb * Update tests/python_package_test/test_engine.py reduce num_boost_round Co-authored-by: James Lamb * Update src/boosting/sample_strategy.cpp modify warning about setting goss as `boosting_type` Co-authored-by: James Lamb * Update tests/python_package_test/test_engine.py replace load_boston() with make_regression() remove value checks of mean_squared_error in test_sample_strategy_with_boosting() * Update tests/python_package_test/test_engine.py add value checks of mean_squared_error in test_sample_strategy_with_boosting() * Modify warnning about using goss as boosting type * Update tests/python_package_test/test_engine.py add random_state=42 for make_regression() reduce the threshold of mean_square_error * Update src/boosting/sample_strategy.cpp Co-authored-by: James Lamb * remove goss from boosting types in documentation * Update src/boosting/bagging.hpp Co-authored-by: Nikita Titov * Update src/boosting/bagging.hpp Co-authored-by: Nikita Titov * Update src/boosting/goss.hpp Co-authored-by: Nikita Titov * Update src/boosting/goss.hpp Co-authored-by: Nikita Titov * rename GOSS with GOSSStrategy * update doc * address comments * fix table in doc * Update include/LightGBM/config.h Co-authored-by: Nikita Titov * update documentation * update test case * revert useless change in test_engine.py * add tests for evaluation results in test_sample_strategy_with_boosting * include * change to assert_allclose in test_goss_boosting_and_strategy_equivalent * more tolerance in result checking, due to minor difference in results of gpu versions * change == to np.testing.assert_allclose * fix test case * set gpu_use_dp to true * change --report to --report-level for rstcheck * use gpu_use_dp=true in test_goss_boosting_and_strategy_equivalent * revert unexpected changes of non-ascii characters * revert unexpected changes of non-ascii characters * remove useless changes * allocate gradients_pointer_ and hessians_pointer when necessary * add spaces * remove redundant virtual * include for USE_CUDA * check for in test_goss_boosting_and_strategy_equivalent * check for identity in test_sample_strategy_with_boosting * remove cuda option in test_sample_strategy_with_boosting * Update tests/python_package_test/test_engine.py Co-authored-by: Nikita Titov * Update tests/python_package_test/test_engine.py Co-authored-by: James Lamb * ResetGradientBuffers after ResetSampleConfig * ResetGradientBuffers after ResetSampleConfig * ResetGradientBuffers after bagging * remove useless code * check objective_function_ instead of gradients * enable rf with goss simplify params in test cases * remove useless changes * allow rf with feature subsampling alone * change position of ResetGradientBuffers * check for dask * add parameter types for data_sample_strategy Co-authored-by: Guangda Liu Co-authored-by: Yu Shi Co-authored-by: GuangdaLiu <90019144+GuangdaLiu@users.noreply.github.com> Co-authored-by: James Lamb Co-authored-by: Nikita Titov --- R-package/src/Makevars.in | 1 + R-package/src/Makevars.win.in | 1 + docs/Development-Guide.rst | 2 +- docs/Parameters.rst | 14 +- include/LightGBM/config.h | 13 +- include/LightGBM/cuda/cuda_utils.h | 3 +- include/LightGBM/sample_strategy.h | 83 ++++++ python-package/lightgbm/dask.py | 2 + python-package/lightgbm/sklearn.py | 1 - src/boosting/bagging.hpp | 209 ++++++++++++++ src/boosting/boosting.cpp | 5 +- src/boosting/gbdt.cpp | 332 +++++------------------ src/boosting/gbdt.h | 36 +-- src/boosting/goss.hpp | 187 +++++-------- src/boosting/rf.hpp | 37 ++- src/boosting/sample_strategy.cpp | 24 ++ src/io/config.cpp | 21 ++ src/io/config_auto.cpp | 3 + src/objective/objective_function.cpp | 4 +- tests/python_package_test/test_engine.py | 136 ++++++++++ tests/python_package_test/utils.py | 5 +- windows/LightGBM.vcxproj | 2 + windows/LightGBM.vcxproj.filters | 6 + 23 files changed, 694 insertions(+), 433 deletions(-) create mode 100644 include/LightGBM/sample_strategy.h create mode 100644 src/boosting/bagging.hpp create mode 100644 src/boosting/sample_strategy.cpp diff --git a/R-package/src/Makevars.in b/R-package/src/Makevars.in index 4d0e1d40a9e2..b95897af9844 100644 --- a/R-package/src/Makevars.in +++ b/R-package/src/Makevars.in @@ -26,6 +26,7 @@ OBJECTS = \ 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 \ diff --git a/R-package/src/Makevars.win.in b/R-package/src/Makevars.win.in index 10e3ce949396..befd2df65052 100644 --- a/R-package/src/Makevars.win.in +++ b/R-package/src/Makevars.win.in @@ -27,6 +27,7 @@ OBJECTS = \ 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 \ diff --git a/docs/Development-Guide.rst b/docs/Development-Guide.rst index c8b30173da79..6c4819e45209 100644 --- a/docs/Development-Guide.rst +++ b/docs/Development-Guide.rst @@ -19,7 +19,7 @@ Important Classes +-------------------------+----------------------------------------------------------------------------------------+ | ``Bin`` | Data structure used for storing feature discrete values (converted from float values) | +-------------------------+----------------------------------------------------------------------------------------+ -| ``Boosting`` | Boosting interface (GBDT, DART, GOSS, etc.) | +| ``Boosting`` | Boosting interface (GBDT, DART, etc.) | +-------------------------+----------------------------------------------------------------------------------------+ | ``Config`` | Stores parameters and configurations | +-------------------------+----------------------------------------------------------------------------------------+ diff --git a/docs/Parameters.rst b/docs/Parameters.rst index c4196cca7a65..4ac77d407ba6 100644 --- a/docs/Parameters.rst +++ b/docs/Parameters.rst @@ -127,7 +127,7 @@ Core Parameters - label should be ``int`` type, and larger number represents the higher relevance (e.g. 0:bad, 1:fair, 2:good, 3:perfect) -- ``boosting`` :raw-html:`🔗︎`, default = ``gbdt``, type = enum, options: ``gbdt``, ``rf``, ``dart``, ``goss``, aliases: ``boosting_type``, ``boost`` +- ``boosting`` :raw-html:`🔗︎`, default = ``gbdt``, type = enum, options: ``gbdt``, ``rf``, ``dart``, aliases: ``boosting_type``, ``boost`` - ``gbdt``, traditional Gradient Boosting Decision Tree, aliases: ``gbrt`` @@ -135,10 +135,16 @@ Core Parameters - ``dart``, `Dropouts meet Multiple Additive Regression Trees `__ - - ``goss``, Gradient-based One-Side Sampling - - **Note**: internally, LightGBM uses ``gbdt`` mode for the first ``1 / learning_rate`` iterations +- ``data_sample_strategy`` :raw-html:`🔗︎`, default = ``bagging``, type = enum, options: ``bagging``, ``goss`` + + - ``bagging``, Randomly Bagging Sampling + + - **Note**: ``bagging`` is only effective when ``bagging_freq > 0`` and ``bagging_fraction < 1.0`` + + - ``goss``, Gradient-based One-Side Sampling + - ``data`` :raw-html:`🔗︎`, default = ``""``, type = string, aliases: ``train``, ``train_data``, ``train_data_file``, ``data_filename`` - path of training data, LightGBM will train from this data @@ -268,7 +274,7 @@ Learning Control Parameters - ``num_threads`` is relatively small, e.g. ``<= 16`` - - you want to use small ``bagging_fraction`` or ``goss`` boosting to speed up + - you want to use small ``bagging_fraction`` or ``goss`` sample strategy to speed up - **Note**: setting this to ``true`` will double the memory cost for Dataset object. If you have not enough memory, you can try setting ``force_col_wise=true`` diff --git a/include/LightGBM/config.h b/include/LightGBM/config.h index 2de8c984f70b..4456d19b4da3 100644 --- a/include/LightGBM/config.h +++ b/include/LightGBM/config.h @@ -153,14 +153,21 @@ struct Config { // [doc-only] // type = enum // alias = boosting_type, boost - // options = gbdt, rf, dart, goss + // options = gbdt, rf, dart // desc = ``gbdt``, traditional Gradient Boosting Decision Tree, aliases: ``gbrt`` // desc = ``rf``, Random Forest, aliases: ``random_forest`` // desc = ``dart``, `Dropouts meet Multiple Additive Regression Trees `__ - // desc = ``goss``, Gradient-based One-Side Sampling // descl2 = **Note**: internally, LightGBM uses ``gbdt`` mode for the first ``1 / learning_rate`` iterations std::string boosting = "gbdt"; + // [doc-only] + // type = enum + // options = bagging, goss + // desc = ``bagging``, Randomly Bagging Sampling + // descl2 = **Note**: ``bagging`` is only effective when ``bagging_freq > 0`` and ``bagging_fraction < 1.0`` + // desc = ``goss``, Gradient-based One-Side Sampling + std::string data_sample_strategy = "bagging"; + // alias = train, train_data, train_data_file, data_filename // desc = path of training data, LightGBM will train from this data // desc = **Note**: can be used only in CLI version @@ -263,7 +270,7 @@ struct Config { // desc = enabling this is recommended when: // descl2 = the number of data points is large, and the total number of bins is relatively small // descl2 = ``num_threads`` is relatively small, e.g. ``<= 16`` - // descl2 = you want to use small ``bagging_fraction`` or ``goss`` boosting to speed up + // descl2 = you want to use small ``bagging_fraction`` or ``goss`` sample strategy to speed up // desc = **Note**: setting this to ``true`` will double the memory cost for Dataset object. If you have not enough memory, you can try setting ``force_col_wise=true`` // desc = **Note**: when both ``force_col_wise`` and ``force_row_wise`` are ``false``, LightGBM will firstly try them both, and then use the faster one. To remove the overhead of testing set the faster one to ``true`` manually // desc = **Note**: this parameter cannot be used at the same time with ``force_col_wise``, choose only one of them diff --git a/include/LightGBM/cuda/cuda_utils.h b/include/LightGBM/cuda/cuda_utils.h index c74016a72ea1..d5b94bc89e4a 100644 --- a/include/LightGBM/cuda/cuda_utils.h +++ b/include/LightGBM/cuda/cuda_utils.h @@ -10,10 +10,10 @@ #include #include #include +#include #endif // USE_CUDA || USE_CUDA_EXP #ifdef USE_CUDA_EXP -#include #include #endif // USE_CUDA_EXP @@ -124,6 +124,7 @@ class CUDAVector { } if (size == 0) { Clear(); + return; } T* new_data = nullptr; AllocateCUDAMemory(&new_data, size, __FILE__, __LINE__); diff --git a/include/LightGBM/sample_strategy.h b/include/LightGBM/sample_strategy.h new file mode 100644 index 000000000000..765632f7ecbf --- /dev/null +++ b/include/LightGBM/sample_strategy.h @@ -0,0 +1,83 @@ +/*! + * Copyright (c) 2021 Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + */ + +#ifndef LIGHTGBM_SAMPLE_STRATEGY_H_ +#define LIGHTGBM_SAMPLE_STRATEGY_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace LightGBM { + +class SampleStrategy { + public: + SampleStrategy() : balanced_bagging_(false), bagging_runner_(0, bagging_rand_block_), need_resize_gradients_(false) {} + + virtual ~SampleStrategy() {} + + static SampleStrategy* CreateSampleStrategy(const Config* config, const Dataset* train_data, const ObjectiveFunction* objective_function, int num_tree_per_iteration); + + virtual void Bagging(int iter, TreeLearner* tree_learner, score_t* gradients, score_t* hessians) = 0; + + virtual void ResetSampleConfig(const Config* config, bool is_change_dataset) = 0; + + bool is_use_subset() const { return is_use_subset_; } + + data_size_t bag_data_cnt() const { return bag_data_cnt_; } + + std::vector>& bag_data_indices() { return bag_data_indices_; } + + #ifdef USE_CUDA_EXP + CUDAVector& cuda_bag_data_indices() { return cuda_bag_data_indices_; } + #endif // USE_CUDA_EXP + + void UpdateObjectiveFunction(const ObjectiveFunction* objective_function) { + objective_function_ = objective_function; + } + + void UpdateTrainingData(const Dataset* train_data) { + train_data_ = train_data; + num_data_ = train_data->num_data(); + } + + virtual bool IsHessianChange() const = 0; + + bool NeedResizeGradients() const { return need_resize_gradients_; } + + protected: + const Config* config_; + const Dataset* train_data_; + const ObjectiveFunction* objective_function_; + std::vector> bag_data_indices_; + data_size_t bag_data_cnt_; + data_size_t num_data_; + int num_tree_per_iteration_; + std::unique_ptr tmp_subset_; + bool is_use_subset_; + bool balanced_bagging_; + const int bagging_rand_block_ = 1024; + std::vector bagging_rands_; + ParallelPartitionRunner bagging_runner_; + /*! \brief whether need to resize the gradient vectors */ + bool need_resize_gradients_; + + #ifdef USE_CUDA_EXP + /*! \brief Buffer for bag_data_indices_ on GPU, used only with cuda_exp */ + CUDAVector cuda_bag_data_indices_; + #endif // USE_CUDA_EXP +}; + +} // namespace LightGBM + +#endif // LIGHTGBM_SAMPLE_STRATEGY_H_ diff --git a/python-package/lightgbm/dask.py b/python-package/lightgbm/dask.py index c71ce6799c32..aaf8c35fa0fa 100644 --- a/python-package/lightgbm/dask.py +++ b/python-package/lightgbm/dask.py @@ -1042,6 +1042,8 @@ def _lgb_dask_fit( eval_at: Optional[Iterable[int]] = None, **kwargs: Any ) -> "_DaskLGBMModel": + if not DASK_INSTALLED: + raise LightGBMError('dask is required for lightgbm.dask') if not all((DASK_INSTALLED, PANDAS_INSTALLED, SKLEARN_INSTALLED)): raise LightGBMError('dask, pandas and scikit-learn are required for lightgbm.dask') diff --git a/python-package/lightgbm/sklearn.py b/python-package/lightgbm/sklearn.py index 05219eaf3d5e..fe75a8f5c827 100644 --- a/python-package/lightgbm/sklearn.py +++ b/python-package/lightgbm/sklearn.py @@ -382,7 +382,6 @@ def __init__( boosting_type : str, optional (default='gbdt') 'gbdt', traditional Gradient Boosting Decision Tree. 'dart', Dropouts meet Multiple Additive Regression Trees. - 'goss', Gradient-based One-Side Sampling. 'rf', Random Forest. num_leaves : int, optional (default=31) Maximum tree leaves for base learners. diff --git a/src/boosting/bagging.hpp b/src/boosting/bagging.hpp new file mode 100644 index 000000000000..65a937435105 --- /dev/null +++ b/src/boosting/bagging.hpp @@ -0,0 +1,209 @@ +/*! + * Copyright (c) 2021 Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + */ + +#ifndef LIGHTGBM_BOOSTING_BAGGING_HPP_ +#define LIGHTGBM_BOOSTING_BAGGING_HPP_ + +#include + +namespace LightGBM { + +class BaggingSampleStrategy : public SampleStrategy { + public: + BaggingSampleStrategy(const Config* config, const Dataset* train_data, const ObjectiveFunction* objective_function, int num_tree_per_iteration) + : need_re_bagging_(false) { + config_ = config; + train_data_ = train_data; + num_data_ = train_data->num_data(); + objective_function_ = objective_function; + num_tree_per_iteration_ = num_tree_per_iteration; + } + + ~BaggingSampleStrategy() {} + + void Bagging(int iter, TreeLearner* tree_learner, score_t* /*gradients*/, score_t* /*hessians*/) override { + Common::FunctionTimer fun_timer("GBDT::Bagging", global_timer); + // if need bagging + if ((bag_data_cnt_ < num_data_ && iter % config_->bagging_freq == 0) || + need_re_bagging_) { + need_re_bagging_ = false; + auto left_cnt = bagging_runner_.Run( + num_data_, + [=](int, data_size_t cur_start, data_size_t cur_cnt, data_size_t* left, + data_size_t*) { + data_size_t cur_left_count = 0; + if (balanced_bagging_) { + cur_left_count = + BalancedBaggingHelper(cur_start, cur_cnt, left); + } else { + cur_left_count = BaggingHelper(cur_start, cur_cnt, left); + } + return cur_left_count; + }, + bag_data_indices_.data()); + bag_data_cnt_ = left_cnt; + Log::Debug("Re-bagging, using %d data to train", bag_data_cnt_); + // set bagging data to tree learner + if (!is_use_subset_) { + #ifdef USE_CUDA_EXP + if (config_->device_type == std::string("cuda_exp")) { + CopyFromHostToCUDADevice(cuda_bag_data_indices_.RawData(), bag_data_indices_.data(), static_cast(num_data_), __FILE__, __LINE__); + tree_learner->SetBaggingData(nullptr, cuda_bag_data_indices_.RawData(), bag_data_cnt_); + } else { + #endif // USE_CUDA_EXP + tree_learner->SetBaggingData(nullptr, bag_data_indices_.data(), bag_data_cnt_); + #ifdef USE_CUDA_EXP + } + #endif // USE_CUDA_EXP + } else { + // get subset + tmp_subset_->ReSize(bag_data_cnt_); + tmp_subset_->CopySubrow(train_data_, bag_data_indices_.data(), + bag_data_cnt_, false); + #ifdef USE_CUDA_EXP + if (config_->device_type == std::string("cuda_exp")) { + CopyFromHostToCUDADevice(cuda_bag_data_indices_.RawData(), bag_data_indices_.data(), static_cast(num_data_), __FILE__, __LINE__); + tree_learner->SetBaggingData(tmp_subset_.get(), cuda_bag_data_indices_.RawData(), + bag_data_cnt_); + } else { + #endif // USE_CUDA_EXP + tree_learner->SetBaggingData(tmp_subset_.get(), bag_data_indices_.data(), + bag_data_cnt_); + #ifdef USE_CUDA_EXP + } + #endif // USE_CUDA_EXP + } + } + } + + void ResetSampleConfig(const Config* config, bool is_change_dataset) override { + need_resize_gradients_ = false; + // if need bagging, create buffer + data_size_t num_pos_data = 0; + if (objective_function_ != nullptr) { + num_pos_data = objective_function_->NumPositiveData(); + } + bool balance_bagging_cond = (config->pos_bagging_fraction < 1.0 || config->neg_bagging_fraction < 1.0) && (num_pos_data > 0); + if ((config->bagging_fraction < 1.0 || balance_bagging_cond) && config->bagging_freq > 0) { + need_re_bagging_ = false; + if (!is_change_dataset && + config_ != nullptr && config_->bagging_fraction == config->bagging_fraction && config_->bagging_freq == config->bagging_freq + && config_->pos_bagging_fraction == config->pos_bagging_fraction && config_->neg_bagging_fraction == config->neg_bagging_fraction) { + config_ = config; + return; + } + config_ = config; + if (balance_bagging_cond) { + balanced_bagging_ = true; + bag_data_cnt_ = static_cast(num_pos_data * config_->pos_bagging_fraction) + + static_cast((num_data_ - num_pos_data) * config_->neg_bagging_fraction); + } else { + bag_data_cnt_ = static_cast(config_->bagging_fraction * num_data_); + } + bag_data_indices_.resize(num_data_); + #ifdef USE_CUDA_EXP + if (config_->device_type == std::string("cuda_exp")) { + cuda_bag_data_indices_.Resize(num_data_); + } + #endif // USE_CUDA_EXP + bagging_runner_.ReSize(num_data_); + bagging_rands_.clear(); + for (int i = 0; + i < (num_data_ + bagging_rand_block_ - 1) / bagging_rand_block_; ++i) { + bagging_rands_.emplace_back(config_->bagging_seed + i); + } + + double average_bag_rate = + (static_cast(bag_data_cnt_) / num_data_) / config_->bagging_freq; + is_use_subset_ = false; + if (config_->device_type != std::string("cuda_exp")) { + const int group_threshold_usesubset = 100; + const double average_bag_rate_threshold = 0.5; + if (average_bag_rate <= average_bag_rate_threshold + && (train_data_->num_feature_groups() < group_threshold_usesubset)) { + if (tmp_subset_ == nullptr || is_change_dataset) { + tmp_subset_.reset(new Dataset(bag_data_cnt_)); + tmp_subset_->CopyFeatureMapperFrom(train_data_); + } + is_use_subset_ = true; + Log::Debug("Use subset for bagging"); + } + } + + need_re_bagging_ = true; + + if (is_use_subset_ && bag_data_cnt_ < num_data_) { + // resize gradient vectors to copy the customized gradients for using subset data + need_resize_gradients_ = true; + } + } else { + bag_data_cnt_ = num_data_; + bag_data_indices_.clear(); + #ifdef USE_CUDA_EXP + cuda_bag_data_indices_.Clear(); + #endif // USE_CUDA_EXP + bagging_runner_.ReSize(0); + is_use_subset_ = false; + } + } + + bool IsHessianChange() const override { + return false; + } + + private: + data_size_t BaggingHelper(data_size_t start, data_size_t cnt, data_size_t* buffer) { + if (cnt <= 0) { + return 0; + } + data_size_t cur_left_cnt = 0; + data_size_t cur_right_pos = cnt; + // random bagging, minimal unit is one record + for (data_size_t i = 0; i < cnt; ++i) { + auto cur_idx = start + i; + if (bagging_rands_[cur_idx / bagging_rand_block_].NextFloat() < config_->bagging_fraction) { + buffer[cur_left_cnt++] = cur_idx; + } else { + buffer[--cur_right_pos] = cur_idx; + } + } + return cur_left_cnt; + } + + data_size_t BalancedBaggingHelper(data_size_t start, data_size_t cnt, data_size_t* buffer) { + if (cnt <= 0) { + return 0; + } + auto label_ptr = train_data_->metadata().label(); + data_size_t cur_left_cnt = 0; + data_size_t cur_right_pos = cnt; + // random bagging, minimal unit is one record + for (data_size_t i = 0; i < cnt; ++i) { + auto cur_idx = start + i; + bool is_pos = label_ptr[start + i] > 0; + bool is_in_bag = false; + if (is_pos) { + is_in_bag = bagging_rands_[cur_idx / bagging_rand_block_].NextFloat() < + config_->pos_bagging_fraction; + } else { + is_in_bag = bagging_rands_[cur_idx / bagging_rand_block_].NextFloat() < + config_->neg_bagging_fraction; + } + if (is_in_bag) { + buffer[cur_left_cnt++] = cur_idx; + } else { + buffer[--cur_right_pos] = cur_idx; + } + } + return cur_left_cnt; + } + + /*! \brief whether need restart bagging in continued training */ + bool need_re_bagging_; +}; + +} // namespace LightGBM + +#endif // LIGHTGBM_BOOSTING_BAGGING_HPP_ diff --git a/src/boosting/boosting.cpp b/src/boosting/boosting.cpp index 91fa318a0f18..98f2554b1388 100644 --- a/src/boosting/boosting.cpp +++ b/src/boosting/boosting.cpp @@ -6,7 +6,6 @@ #include "dart.hpp" #include "gbdt.h" -#include "goss.hpp" #include "rf.hpp" namespace LightGBM { @@ -39,7 +38,7 @@ Boosting* Boosting::CreateBoosting(const std::string& type, const char* filename } else if (type == std::string("dart")) { return new DART(); } else if (type == std::string("goss")) { - return new GOSS(); + return new GBDT(); } else if (type == std::string("rf")) { return new RF(); } else { @@ -53,7 +52,7 @@ Boosting* Boosting::CreateBoosting(const std::string& type, const char* filename } else if (type == std::string("dart")) { ret.reset(new DART()); } else if (type == std::string("goss")) { - ret.reset(new GOSS()); + ret.reset(new GBDT()); } else if (type == std::string("rf")) { return new RF(); } else { diff --git a/src/boosting/gbdt.cpp b/src/boosting/gbdt.cpp index c2a38417091a..f7ac445131dd 100644 --- a/src/boosting/gbdt.cpp +++ b/src/boosting/gbdt.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -34,13 +35,11 @@ GBDT::GBDT() num_class_(1), num_iteration_for_pred_(0), shrinkage_rate_(0.1f), - num_init_iteration_(0), - need_re_bagging_(false), - balanced_bagging_(false), - bagging_runner_(0, bagging_rand_block_) { + num_init_iteration_(0) { average_output_ = false; tree_learner_ = nullptr; linear_tree_ = false; + data_sample_strategy_.reset(nullptr); gradients_pointer_ = nullptr; hessians_pointer_ = nullptr; boosting_on_gpu_ = false; @@ -96,9 +95,12 @@ void GBDT::Init(const Config* config, const Dataset* train_data, const Objective } } + data_sample_strategy_.reset(SampleStrategy::CreateSampleStrategy(config_.get(), train_data_, objective_function_, num_tree_per_iteration_)); is_constant_hessian_ = GetIsConstHessian(objective_function); - boosting_on_gpu_ = objective_function_ != nullptr && objective_function_->IsCUDAObjective(); + boosting_on_gpu_ = objective_function_ != nullptr && objective_function_->IsCUDAObjective() && + !data_sample_strategy_->IsHessianChange(); // for sample strategy with Hessian change, fall back to boosting on CPU + tree_learner_ = std::unique_ptr(TreeLearner::CreateTreeLearner(config_->tree_learner, config_->device_type, config_.get(), boosting_on_gpu_)); @@ -124,34 +126,6 @@ void GBDT::Init(const Config* config, const Dataset* train_data, const Objective #endif // USE_CUDA_EXP num_data_ = train_data_->num_data(); - // create buffer for gradients and Hessians - if (objective_function_ != nullptr) { - const size_t total_size = static_cast(num_data_) * num_tree_per_iteration_; - #ifdef USE_CUDA_EXP - if (config_->device_type == std::string("cuda_exp") && boosting_on_gpu_) { - if (gradients_pointer_ != nullptr) { - CHECK_NOTNULL(hessians_pointer_); - DeallocateCUDAMemory(&gradients_pointer_, __FILE__, __LINE__); - DeallocateCUDAMemory(&hessians_pointer_, __FILE__, __LINE__); - } - AllocateCUDAMemory(&gradients_pointer_, total_size, __FILE__, __LINE__); - AllocateCUDAMemory(&hessians_pointer_, total_size, __FILE__, __LINE__); - } else { - #endif // USE_CUDA_EXP - gradients_.resize(total_size); - hessians_.resize(total_size); - gradients_pointer_ = gradients_.data(); - hessians_pointer_ = hessians_.data(); - #ifdef USE_CUDA_EXP - } - #endif // USE_CUDA_EXP - } else if (config_->boosting == std::string("goss")) { - const size_t total_size = static_cast(num_data_) * num_tree_per_iteration_; - gradients_.resize(total_size); - hessians_.resize(total_size); - gradients_pointer_ = gradients_.data(); - hessians_pointer_ = hessians_.data(); - } // get max feature index max_feature_idx_ = train_data_->num_total_features() - 1; @@ -165,7 +139,8 @@ void GBDT::Init(const Config* config, const Dataset* train_data, const Objective parser_config_str_ = train_data_->parser_config_str(); // if need bagging, create buffer - ResetBaggingConfig(config_.get(), true); + data_sample_strategy_->ResetSampleConfig(config_.get(), true); + ResetGradientBuffers(); class_need_train_ = std::vector(num_tree_per_iteration_, true); if (objective_function_ != nullptr && objective_function_->SkipEmptyClass()) { @@ -227,108 +202,6 @@ void GBDT::Boosting() { GetGradients(GetTrainingScore(&num_score), gradients_pointer_, hessians_pointer_); } -data_size_t GBDT::BaggingHelper(data_size_t start, data_size_t cnt, data_size_t* buffer) { - if (cnt <= 0) { - return 0; - } - data_size_t cur_left_cnt = 0; - data_size_t cur_right_pos = cnt; - // random bagging, minimal unit is one record - for (data_size_t i = 0; i < cnt; ++i) { - auto cur_idx = start + i; - if (bagging_rands_[cur_idx / bagging_rand_block_].NextFloat() < config_->bagging_fraction) { - buffer[cur_left_cnt++] = cur_idx; - } else { - buffer[--cur_right_pos] = cur_idx; - } - } - return cur_left_cnt; -} - -data_size_t GBDT::BalancedBaggingHelper(data_size_t start, data_size_t cnt, - data_size_t* buffer) { - if (cnt <= 0) { - return 0; - } - auto label_ptr = train_data_->metadata().label(); - data_size_t cur_left_cnt = 0; - data_size_t cur_right_pos = cnt; - // random bagging, minimal unit is one record - for (data_size_t i = 0; i < cnt; ++i) { - auto cur_idx = start + i; - bool is_pos = label_ptr[start + i] > 0; - bool is_in_bag = false; - if (is_pos) { - is_in_bag = bagging_rands_[cur_idx / bagging_rand_block_].NextFloat() < - config_->pos_bagging_fraction; - } else { - is_in_bag = bagging_rands_[cur_idx / bagging_rand_block_].NextFloat() < - config_->neg_bagging_fraction; - } - if (is_in_bag) { - buffer[cur_left_cnt++] = cur_idx; - } else { - buffer[--cur_right_pos] = cur_idx; - } - } - return cur_left_cnt; -} - -void GBDT::Bagging(int iter) { - Common::FunctionTimer fun_timer("GBDT::Bagging", global_timer); - // if need bagging - if ((bag_data_cnt_ < num_data_ && iter % config_->bagging_freq == 0) || - need_re_bagging_) { - need_re_bagging_ = false; - auto left_cnt = bagging_runner_.Run( - num_data_, - [=](int, data_size_t cur_start, data_size_t cur_cnt, data_size_t* left, - data_size_t*) { - data_size_t cur_left_count = 0; - if (balanced_bagging_) { - cur_left_count = - BalancedBaggingHelper(cur_start, cur_cnt, left); - } else { - cur_left_count = BaggingHelper(cur_start, cur_cnt, left); - } - return cur_left_count; - }, - bag_data_indices_.data()); - bag_data_cnt_ = left_cnt; - Log::Debug("Re-bagging, using %d data to train", bag_data_cnt_); - // set bagging data to tree learner - if (!is_use_subset_) { - #ifdef USE_CUDA_EXP - if (config_->device_type == std::string("cuda_exp")) { - CopyFromHostToCUDADevice(cuda_bag_data_indices_.RawData(), bag_data_indices_.data(), static_cast(num_data_), __FILE__, __LINE__); - tree_learner_->SetBaggingData(nullptr, cuda_bag_data_indices_.RawData(), bag_data_cnt_); - } else { - #endif // USE_CUDA_EXP - tree_learner_->SetBaggingData(nullptr, bag_data_indices_.data(), bag_data_cnt_); - #ifdef USE_CUDA_EXP - } - #endif // USE_CUDA_EXP - } else { - // get subset - tmp_subset_->ReSize(bag_data_cnt_); - tmp_subset_->CopySubrow(train_data_, bag_data_indices_.data(), - bag_data_cnt_, false); - #ifdef USE_CUDA_EXP - if (config_->device_type == std::string("cuda_exp")) { - CopyFromHostToCUDADevice(cuda_bag_data_indices_.RawData(), bag_data_indices_.data(), static_cast(num_data_), __FILE__, __LINE__); - tree_learner_->SetBaggingData(tmp_subset_.get(), cuda_bag_data_indices_.RawData(), - bag_data_cnt_); - } else { - #endif // USE_CUDA_EXP - tree_learner_->SetBaggingData(tmp_subset_.get(), bag_data_indices_.data(), - bag_data_cnt_); - #ifdef USE_CUDA_EXP - } - #endif // USE_CUDA_EXP - } - } -} - void GBDT::Train(int snapshot_freq, const std::string& model_output_path) { Common::FunctionTimer fun_timer("GBDT::Train", global_timer); bool is_finished = false; @@ -448,7 +321,7 @@ bool GBDT::TrainOneIter(const score_t* gradients, const score_t* hessians) { } else { // use customized objective function CHECK(objective_function_ == nullptr); - if (config_->boosting == std::string("goss")) { + if (data_sample_strategy_->IsHessianChange()) { // need to copy customized gradients when using GOSS int64_t total_size = static_cast(num_data_) * num_tree_per_iteration_; #pragma omp parallel for schedule(static) @@ -464,15 +337,13 @@ bool GBDT::TrainOneIter(const score_t* gradients, const score_t* hessians) { } // bagging logic - Bagging(iter_); + data_sample_strategy_->Bagging(iter_, tree_learner_.get(), gradients_.data(), hessians_.data()); + const bool is_use_subset = data_sample_strategy_->is_use_subset(); + const data_size_t bag_data_cnt = data_sample_strategy_->bag_data_cnt(); + const std::vector>& bag_data_indices = data_sample_strategy_->bag_data_indices(); - if (gradients != nullptr && is_use_subset_ && bag_data_cnt_ < num_data_ && !boosting_on_gpu_ && config_->boosting != std::string("goss")) { - // allocate gradients_ and hessians_ for copy gradients for using data subset - int64_t total_size = static_cast(num_data_) * num_tree_per_iteration_; - gradients_.resize(total_size); - hessians_.resize(total_size); - gradients_pointer_ = gradients_.data(); - hessians_pointer_ = hessians_.data(); + if (objective_function_ == nullptr && is_use_subset && bag_data_cnt < num_data_ && !boosting_on_gpu_ && !data_sample_strategy_->IsHessianChange()) { + ResetGradientBuffers(); } bool should_continue = false; @@ -483,10 +354,10 @@ bool GBDT::TrainOneIter(const score_t* gradients, const score_t* hessians) { auto grad = gradients + offset; auto hess = hessians + offset; // need to copy gradients for bagging subset. - if (is_use_subset_ && bag_data_cnt_ < num_data_ && !boosting_on_gpu_) { - for (int i = 0; i < bag_data_cnt_; ++i) { - gradients_pointer_[offset + i] = grad[bag_data_indices_[i]]; - hessians_pointer_[offset + i] = hess[bag_data_indices_[i]]; + if (is_use_subset && bag_data_cnt < num_data_ && !boosting_on_gpu_) { + for (int i = 0; i < bag_data_cnt; ++i) { + gradients_pointer_[offset + i] = grad[bag_data_indices[i]]; + hessians_pointer_[offset + i] = hess[bag_data_indices[i]]; } grad = gradients_pointer_ + offset; hess = hessians_pointer_ + offset; @@ -500,7 +371,7 @@ bool GBDT::TrainOneIter(const score_t* gradients, const score_t* hessians) { auto score_ptr = train_score_updater_->score() + offset; auto residual_getter = [score_ptr](const label_t* label, int i) {return static_cast(label[i]) - score_ptr[i]; }; tree_learner_->RenewTreeOutput(new_tree.get(), objective_function_, residual_getter, - num_data_, bag_data_indices_.data(), bag_data_cnt_, train_score_updater_->score()); + num_data_, bag_data_indices.data(), bag_data_cnt, train_score_updater_->score()); // shrinkage by learning rate new_tree->Shrinkage(shrinkage_rate_); // update score @@ -580,17 +451,18 @@ bool GBDT::EvalAndCheckEarlyStopping() { void GBDT::UpdateScore(const Tree* tree, const int cur_tree_id) { Common::FunctionTimer fun_timer("GBDT::UpdateScore", global_timer); // update training score - if (!is_use_subset_) { + if (!data_sample_strategy_->is_use_subset()) { train_score_updater_->AddScore(tree_learner_.get(), tree, cur_tree_id); + const data_size_t bag_data_cnt = data_sample_strategy_->bag_data_cnt(); // we need to predict out-of-bag scores of data for boosting - if (num_data_ - bag_data_cnt_ > 0) { + if (num_data_ - bag_data_cnt > 0) { #ifdef USE_CUDA_EXP if (config_->device_type == std::string("cuda_exp")) { - train_score_updater_->AddScore(tree, cuda_bag_data_indices_.RawData() + bag_data_cnt_, num_data_ - bag_data_cnt_, cur_tree_id); + train_score_updater_->AddScore(tree, data_sample_strategy_->cuda_bag_data_indices().RawData() + bag_data_cnt, num_data_ - bag_data_cnt, cur_tree_id); } else { #endif // USE_CUDA_EXP - train_score_updater_->AddScore(tree, bag_data_indices_.data() + bag_data_cnt_, num_data_ - bag_data_cnt_, cur_tree_id); + train_score_updater_->AddScore(tree, data_sample_strategy_->bag_data_indices().data() + bag_data_cnt, num_data_ - bag_data_cnt, cur_tree_id); #ifdef USE_CUDA_EXP } #endif // USE_CUDA_EXP @@ -818,6 +690,7 @@ void GBDT::ResetTrainingData(const Dataset* train_data, const ObjectiveFunction* } objective_function_ = objective_function; + data_sample_strategy_->UpdateObjectiveFunction(objective_function); if (objective_function_ != nullptr) { CHECK_EQ(num_tree_per_iteration_, objective_function_->NumModelPerIteration()); if (objective_function_->IsRenewTreeOutput() && !config_->monotone_constraints.empty()) { @@ -833,11 +706,15 @@ void GBDT::ResetTrainingData(const Dataset* train_data, const ObjectiveFunction* } training_metrics_.shrink_to_fit(); - boosting_on_gpu_ = objective_function_ != nullptr && objective_function_->IsCUDAObjective(); + #ifdef USE_CUDA_EXP + boosting_on_gpu_ = objective_function_ != nullptr && objective_function_->IsCUDAObjective() && + !data_sample_strategy_->IsHessianChange(); // for sample strategy with Hessian change, fall back to boosting on CPU tree_learner_->ResetBoostingOnGPU(boosting_on_gpu_); + #endif // USE_CUDA_EXP if (train_data != train_data_) { train_data_ = train_data; + data_sample_strategy_->UpdateTrainingData(train_data); // not same training data, need reset score and others // create score tracker #ifdef USE_CUDA_EXP @@ -860,34 +737,7 @@ void GBDT::ResetTrainingData(const Dataset* train_data, const ObjectiveFunction* num_data_ = train_data_->num_data(); - // create buffer for gradients and hessians - if (objective_function_ != nullptr) { - const size_t total_size = static_cast(num_data_) * num_tree_per_iteration_; - #ifdef USE_CUDA_EXP - if (config_->device_type == std::string("cuda_exp") && boosting_on_gpu_) { - if (gradients_pointer_ != nullptr) { - CHECK_NOTNULL(hessians_pointer_); - DeallocateCUDAMemory(&gradients_pointer_, __FILE__, __LINE__); - DeallocateCUDAMemory(&hessians_pointer_, __FILE__, __LINE__); - } - AllocateCUDAMemory(&gradients_pointer_, total_size, __FILE__, __LINE__); - AllocateCUDAMemory(&hessians_pointer_, total_size, __FILE__, __LINE__); - } else { - #endif // USE_CUDA_EXP - gradients_.resize(total_size); - hessians_.resize(total_size); - gradients_pointer_ = gradients_.data(); - hessians_pointer_ = hessians_.data(); - #ifdef USE_CUDA_EXP - } - #endif // USE_CUDA_EXP - } else if (config_->boosting == std::string("goss")) { - const size_t total_size = static_cast(num_data_) * num_tree_per_iteration_; - gradients_.resize(total_size); - hessians_.resize(total_size); - gradients_pointer_ = gradients_.data(); - hessians_pointer_ = hessians_.data(); - } + ResetGradientBuffers(); max_feature_idx_ = train_data_->num_total_features() - 1; label_idx_ = train_data_->label_idx(); @@ -896,7 +746,7 @@ void GBDT::ResetTrainingData(const Dataset* train_data, const ObjectiveFunction* parser_config_str_ = train_data_->parser_config_str(); tree_learner_->ResetTrainingData(train_data, is_constant_hessian_); - ResetBaggingConfig(config_.get(), true); + data_sample_strategy_->ResetSampleConfig(config_.get(), true); } else { tree_learner_->ResetIsConstantHessian(is_constant_hessian_); } @@ -919,11 +769,16 @@ void GBDT::ResetConfig(const Config* config) { tree_learner_->ResetConfig(new_config.get()); } - boosting_on_gpu_ = objective_function_ != nullptr && objective_function_->IsCUDAObjective(); + boosting_on_gpu_ = objective_function_ != nullptr && objective_function_->IsCUDAObjective() && + !data_sample_strategy_->IsHessianChange(); // for sample strategy with Hessian change, fall back to boosting on CPU tree_learner_->ResetBoostingOnGPU(boosting_on_gpu_); if (train_data_ != nullptr) { - ResetBaggingConfig(new_config.get(), false); + data_sample_strategy_->ResetSampleConfig(new_config.get(), false); + if (data_sample_strategy_->NeedResizeGradients()) { + // resize gradient vectors to copy the customized gradients for goss or bagging with subset + ResetGradientBuffers(); + } } if (config_.get() != nullptr && config_->forcedsplits_filename != new_config->forcedsplits_filename) { // load forced_splits file @@ -943,96 +798,37 @@ void GBDT::ResetConfig(const Config* config) { config_.reset(new_config.release()); } -void GBDT::ResetBaggingConfig(const Config* config, bool is_change_dataset) { - // if need bagging, create buffer - data_size_t num_pos_data = 0; +void GBDT::ResetGradientBuffers() { + const size_t total_size = static_cast(num_data_) * num_tree_per_iteration_; + const bool is_use_subset = data_sample_strategy_->is_use_subset(); + const data_size_t bag_data_cnt = data_sample_strategy_->bag_data_cnt(); if (objective_function_ != nullptr) { - num_pos_data = objective_function_->NumPositiveData(); - } - bool balance_bagging_cond = (config->pos_bagging_fraction < 1.0 || config->neg_bagging_fraction < 1.0) && (num_pos_data > 0); - if ((config->bagging_fraction < 1.0 || balance_bagging_cond) && config->bagging_freq > 0) { - need_re_bagging_ = false; - if (!is_change_dataset && - config_.get() != nullptr && config_->bagging_fraction == config->bagging_fraction && config_->bagging_freq == config->bagging_freq - && config_->pos_bagging_fraction == config->pos_bagging_fraction && config_->neg_bagging_fraction == config->neg_bagging_fraction) { - return; - } - if (balance_bagging_cond) { - balanced_bagging_ = true; - bag_data_cnt_ = static_cast(num_pos_data * config->pos_bagging_fraction) - + static_cast((num_data_ - num_pos_data) * config->neg_bagging_fraction); - } else { - bag_data_cnt_ = static_cast(config->bagging_fraction * num_data_); - } - bag_data_indices_.resize(num_data_); #ifdef USE_CUDA_EXP - if (config->device_type == std::string("cuda_exp")) { - cuda_bag_data_indices_.Resize(num_data_); - } - #endif // USE_CUDA_EXP - bagging_runner_.ReSize(num_data_); - bagging_rands_.clear(); - for (int i = 0; - i < (num_data_ + bagging_rand_block_ - 1) / bagging_rand_block_; ++i) { - bagging_rands_.emplace_back(config_->bagging_seed + i); - } - - double average_bag_rate = - (static_cast(bag_data_cnt_) / num_data_) / config->bagging_freq; - is_use_subset_ = false; - if (config_->device_type != std::string("cuda_exp")) { - const int group_threshold_usesubset = 100; - if (average_bag_rate <= 0.5 - && (train_data_->num_feature_groups() < group_threshold_usesubset)) { - if (tmp_subset_ == nullptr || is_change_dataset) { - tmp_subset_.reset(new Dataset(bag_data_cnt_)); - tmp_subset_->CopyFeatureMapperFrom(train_data_); - } - is_use_subset_ = true; - Log::Debug("Use subset for bagging"); + if (config_->device_type == std::string("cuda_exp") && boosting_on_gpu_) { + if (cuda_gradients_.Size() < total_size) { + cuda_gradients_.Resize(total_size); + cuda_hessians_.Resize(total_size); } - } - - need_re_bagging_ = true; - - if (is_use_subset_ && bag_data_cnt_ < num_data_) { - // resize gradient vectors to copy the customized gradients for goss or bagging with subset - if (objective_function_ != nullptr) { - const size_t total_size = static_cast(num_data_) * num_tree_per_iteration_; - #ifdef USE_CUDA_EXP - if (config_->device_type == std::string("cuda_exp") && boosting_on_gpu_) { - if (gradients_pointer_ != nullptr) { - CHECK_NOTNULL(hessians_pointer_); - DeallocateCUDAMemory(&gradients_pointer_, __FILE__, __LINE__); - DeallocateCUDAMemory(&hessians_pointer_, __FILE__, __LINE__); - } - AllocateCUDAMemory(&gradients_pointer_, total_size, __FILE__, __LINE__); - AllocateCUDAMemory(&hessians_pointer_, total_size, __FILE__, __LINE__); - } else { - #endif // USE_CUDA_EXP - gradients_.resize(total_size); - hessians_.resize(total_size); - gradients_pointer_ = gradients_.data(); - hessians_pointer_ = hessians_.data(); - #ifdef USE_CUDA_EXP - } - #endif // USE_CUDA_EXP - } else if (config_->boosting == std::string("goss")) { - const size_t total_size = static_cast(num_data_) * num_tree_per_iteration_; + gradients_pointer_ = cuda_gradients_.RawData(); + hessians_pointer_ = cuda_hessians_.RawData(); + } else { + #endif // USE_CUDA_EXP + if (gradients_.size() < total_size) { gradients_.resize(total_size); hessians_.resize(total_size); - gradients_pointer_ = gradients_.data(); - hessians_pointer_ = hessians_.data(); } - } - } else { - bag_data_cnt_ = num_data_; - bag_data_indices_.clear(); + gradients_pointer_ = gradients_.data(); + hessians_pointer_ = hessians_.data(); #ifdef USE_CUDA_EXP - cuda_bag_data_indices_.Clear(); + } #endif // USE_CUDA_EXP - bagging_runner_.ReSize(0); - is_use_subset_ = false; + } else if (data_sample_strategy_->IsHessianChange() || (is_use_subset && bag_data_cnt < num_data_ && !boosting_on_gpu_)) { + if (gradients_.size() < total_size) { + gradients_.resize(total_size); + hessians_.resize(total_size); + } + gradients_pointer_ = gradients_.data(); + hessians_pointer_ = hessians_.data(); } } diff --git a/src/boosting/gbdt.h b/src/boosting/gbdt.h index 5cc3cc7541b0..e1749bd94749 100644 --- a/src/boosting/gbdt.h +++ b/src/boosting/gbdt.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -453,7 +454,7 @@ class GBDT : public GBDTBase { protected: virtual bool GetIsConstHessian(const ObjectiveFunction* objective_function) { - if (objective_function != nullptr) { + if (objective_function != nullptr && !data_sample_strategy_->IsHessianChange()) { return objective_function->IsConstantHessian(); } else { return false; @@ -469,18 +470,6 @@ class GBDT : public GBDTBase { */ void ResetBaggingConfig(const Config* config, bool is_change_dataset); - /*! - * \brief Implement bagging logic - * \param iter Current interation - */ - virtual void Bagging(int iter); - - virtual data_size_t BaggingHelper(data_size_t start, data_size_t cnt, - data_size_t* buffer); - - data_size_t BalancedBaggingHelper(data_size_t start, data_size_t cnt, - data_size_t* buffer); - /*! * \brief calculate the objective function */ @@ -508,6 +497,11 @@ class GBDT : public GBDTBase { double BoostFromAverage(int class_id, bool update_scorer); + /*! + * \brief Reset gradient buffers, must be called after sample strategy is reset + */ + void ResetGradientBuffers(); + /*! \brief current iteration */ int iter_; /*! \brief Pointer to training data */ @@ -561,18 +555,16 @@ class GBDT : public GBDTBase { /*! \brief Whether boosting is done on GPU, used for cuda_exp */ bool boosting_on_gpu_; #ifdef USE_CUDA_EXP + /*! \brief Gradient vector on GPU */ + CUDAVector cuda_gradients_; + /*! \brief Hessian vector on GPU */ + CUDAVector cuda_hessians_; /*! \brief Buffer for scores when boosting is on GPU but evaluation is not, used only with cuda_exp */ mutable std::vector host_score_; /*! \brief Buffer for scores when boosting is not on GPU but evaluation is, used only with cuda_exp */ mutable CUDAVector cuda_score_; - /*! \brief Buffer for bag_data_indices_ on GPU, used only with cuda_exp */ - CUDAVector cuda_bag_data_indices_; #endif // USE_CUDA_EXP - /*! \brief Store the indices of in-bag data */ - std::vector> bag_data_indices_; - /*! \brief Number of in-bag data */ - data_size_t bag_data_cnt_; /*! \brief Number of training data */ data_size_t num_data_; /*! \brief Number of trees per iterations */ @@ -592,8 +584,6 @@ class GBDT : public GBDTBase { /*! \brief Feature names */ std::vector feature_names_; std::vector feature_infos_; - std::unique_ptr tmp_subset_; - bool is_use_subset_; std::vector class_need_train_; bool is_constant_hessian_; std::unique_ptr loaded_objective_; @@ -602,11 +592,9 @@ class GBDT : public GBDTBase { bool balanced_bagging_; std::string loaded_parameter_; std::vector monotone_constraints_; - const int bagging_rand_block_ = 1024; - std::vector bagging_rands_; - ParallelPartitionRunner bagging_runner_; Json forced_splits_json_; bool linear_tree_; + std::unique_ptr data_sample_strategy_; }; } // namespace LightGBM diff --git a/src/boosting/goss.hpp b/src/boosting/goss.hpp index 09c63d9728f3..34b099e051bb 100644 --- a/src/boosting/goss.hpp +++ b/src/boosting/goss.hpp @@ -1,79 +1,87 @@ /*! - * Copyright (c) 2017 Microsoft Corporation. All rights reserved. + * Copyright (c) 2021 Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See LICENSE file in the project root for license information. */ -#ifndef LIGHTGBM_BOOSTING_GOSS_H_ -#define LIGHTGBM_BOOSTING_GOSS_H_ -#include +#ifndef LIGHTGBM_BOOSTING_GOSS_HPP_ +#define LIGHTGBM_BOOSTING_GOSS_HPP_ + #include -#include +#include -#include #include -#include -#include -#include -#include +#include #include -#include "gbdt.h" -#include "score_updater.hpp" - namespace LightGBM { -class GOSS: public GBDT { +class GOSSStrategy : public SampleStrategy { public: - /*! - * \brief Constructor - */ - GOSS() : GBDT() { - } - - ~GOSS() { - } - - void Init(const Config* config, const Dataset* train_data, const ObjectiveFunction* objective_function, - const std::vector& training_metrics) override { - GBDT::Init(config, train_data, objective_function, training_metrics); - ResetGoss(); - if (objective_function_ == nullptr) { - // use customized objective function - size_t total_size = static_cast(num_data_) * num_tree_per_iteration_; - gradients_.resize(total_size, 0.0f); - hessians_.resize(total_size, 0.0f); - } - } - - void ResetTrainingData(const Dataset* train_data, const ObjectiveFunction* objective_function, - const std::vector& training_metrics) override { - GBDT::ResetTrainingData(train_data, objective_function, training_metrics); - ResetGoss(); + GOSSStrategy(const Config* config, const Dataset* train_data, int num_tree_per_iteration) { + config_ = config; + train_data_ = train_data; + num_tree_per_iteration_ = num_tree_per_iteration; + num_data_ = train_data->num_data(); } - void ResetConfig(const Config* config) override { - GBDT::ResetConfig(config); - ResetGoss(); + ~GOSSStrategy() { } - bool TrainOneIter(const score_t* gradients, const score_t* hessians) override { - if (gradients != nullptr) { - // use customized objective function - CHECK(hessians != nullptr && objective_function_ == nullptr); - int64_t total_size = static_cast(num_data_) * num_tree_per_iteration_; - #pragma omp parallel for schedule(static) - for (int64_t i = 0; i < total_size; ++i) { - gradients_[i] = gradients[i]; - hessians_[i] = hessians[i]; + void Bagging(int iter, TreeLearner* tree_learner, score_t* gradients, score_t* hessians) override { + bag_data_cnt_ = num_data_; + // not subsample for first iterations + if (iter < static_cast(1.0f / config_->learning_rate)) { return; } + auto left_cnt = bagging_runner_.Run( + num_data_, + [=](int, data_size_t cur_start, data_size_t cur_cnt, data_size_t* left, + data_size_t*) { + data_size_t cur_left_count = 0; + cur_left_count = Helper(cur_start, cur_cnt, left, gradients, hessians); + return cur_left_count; + }, + bag_data_indices_.data()); + bag_data_cnt_ = left_cnt; + // set bagging data to tree learner + if (!is_use_subset_) { + #ifdef USE_CUDA_EXP + if (config_->device_type == std::string("cuda_exp")) { + CopyFromHostToCUDADevice(cuda_bag_data_indices_.RawData(), bag_data_indices_.data(), static_cast(num_data_), __FILE__, __LINE__); + tree_learner->SetBaggingData(nullptr, cuda_bag_data_indices_.RawData(), bag_data_cnt_); + } else { + #endif // USE_CUDA_EXP + tree_learner->SetBaggingData(nullptr, bag_data_indices_.data(), bag_data_cnt_); + #ifdef USE_CUDA_EXP } - return GBDT::TrainOneIter(gradients_.data(), hessians_.data()); + #endif // USE_CUDA_EXP } else { - CHECK(hessians == nullptr); - return GBDT::TrainOneIter(nullptr, nullptr); + // get subset + tmp_subset_->ReSize(bag_data_cnt_); + tmp_subset_->CopySubrow(train_data_, bag_data_indices_.data(), + bag_data_cnt_, false); + #ifdef USE_CUDA_EXP + if (config_->device_type == std::string("cuda_exp")) { + CopyFromHostToCUDADevice(cuda_bag_data_indices_.RawData(), bag_data_indices_.data(), static_cast(num_data_), __FILE__, __LINE__); + tree_learner->SetBaggingData(tmp_subset_.get(), cuda_bag_data_indices_.RawData(), + bag_data_cnt_); + } else { + #endif // USE_CUDA_EXP + tree_learner->SetBaggingData(tmp_subset_.get(), bag_data_indices_.data(), + bag_data_cnt_); + #ifdef USE_CUDA_EXP + } + #endif // USE_CUDA_EXP } } - void ResetGoss() { + void ResetSampleConfig(const Config* config, bool /*is_change_dataset*/) override { + // Cannot use bagging in GOSS + config_ = config; + need_resize_gradients_ = false; + if (objective_function_ == nullptr) { + // resize gradient vectors to copy the customized gradients for goss + need_resize_gradients_ = true; + } + CHECK_LE(config_->top_rate + config_->other_rate, 1.0f); CHECK(config_->top_rate > 0.0f && config_->other_rate > 0.0f); if (config_->bagging_freq > 0 && config_->bagging_fraction != 1.0f) { @@ -100,7 +108,12 @@ class GOSS: public GBDT { bag_data_cnt_ = num_data_; } - data_size_t BaggingHelper(data_size_t start, data_size_t cnt, data_size_t* buffer) override { + bool IsHessianChange() const override { + return true; + } + + private: + data_size_t Helper(data_size_t start, data_size_t cnt, data_size_t* buffer, score_t* gradients, score_t* hessians) { if (cnt <= 0) { return 0; } @@ -108,7 +121,7 @@ class GOSS: public GBDT { for (data_size_t i = 0; i < cnt; ++i) { for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) { size_t idx = static_cast(cur_tree_id) * num_data_ + start + i; - tmp_gradients[i] += std::fabs(gradients_[idx] * hessians_[idx]); + tmp_gradients[i] += std::fabs(gradients[idx] * hessians[idx]); } } data_size_t top_k = static_cast(cnt * config_->top_rate); @@ -126,7 +139,7 @@ class GOSS: public GBDT { score_t grad = 0.0f; for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) { size_t idx = static_cast(cur_tree_id) * num_data_ + cur_idx; - grad += std::fabs(gradients_[idx] * hessians_[idx]); + grad += std::fabs(gradients[idx] * hessians[idx]); } if (grad >= threshold) { buffer[cur_left_cnt++] = cur_idx; @@ -140,8 +153,8 @@ class GOSS: public GBDT { buffer[cur_left_cnt++] = cur_idx; for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) { size_t idx = static_cast(cur_tree_id) * num_data_ + cur_idx; - gradients_[idx] *= multiply; - hessians_[idx] *= multiply; + gradients[idx] *= multiply; + hessians[idx] *= multiply; } } else { buffer[--cur_right_pos] = cur_idx; @@ -150,58 +163,8 @@ class GOSS: public GBDT { } return cur_left_cnt; } - - void Bagging(int iter) override { - bag_data_cnt_ = num_data_; - // not subsample for first iterations - if (iter < static_cast(1.0f / config_->learning_rate)) { return; } - auto left_cnt = bagging_runner_.Run( - num_data_, - [=](int, data_size_t cur_start, data_size_t cur_cnt, data_size_t* left, - data_size_t*) { - data_size_t cur_left_count = 0; - cur_left_count = BaggingHelper(cur_start, cur_cnt, left); - return cur_left_count; - }, - bag_data_indices_.data()); - bag_data_cnt_ = left_cnt; - // set bagging data to tree learner - if (!is_use_subset_) { - #ifdef USE_CUDA_EXP - if (config_->device_type == std::string("cuda_exp")) { - CopyFromHostToCUDADevice(cuda_bag_data_indices_.RawData(), bag_data_indices_.data(), static_cast(num_data_), __FILE__, __LINE__); - tree_learner_->SetBaggingData(nullptr, cuda_bag_data_indices_.RawData(), bag_data_cnt_); - } else { - #endif // USE_CUDA_EXP - tree_learner_->SetBaggingData(nullptr, bag_data_indices_.data(), bag_data_cnt_); - #ifdef USE_CUDA_EXP - } - #endif // USE_CUDA_EXP - } else { - // get subset - tmp_subset_->ReSize(bag_data_cnt_); - tmp_subset_->CopySubrow(train_data_, bag_data_indices_.data(), - bag_data_cnt_, false); - #ifdef USE_CUDA_EXP - if (config_->device_type == std::string("cuda_exp")) { - CopyFromHostToCUDADevice(cuda_bag_data_indices_.RawData(), bag_data_indices_.data(), static_cast(num_data_), __FILE__, __LINE__); - tree_learner_->SetBaggingData(tmp_subset_.get(), cuda_bag_data_indices_.RawData(), - bag_data_cnt_); - } else { - #endif // USE_CUDA_EXP - tree_learner_->SetBaggingData(tmp_subset_.get(), bag_data_indices_.data(), - bag_data_cnt_); - #ifdef USE_CUDA_EXP - } - #endif // USE_CUDA_EXP - } - } - - protected: - bool GetIsConstHessian(const ObjectiveFunction*) override { - return false; - } }; } // namespace LightGBM -#endif // LIGHTGBM_BOOSTING_GOSS_H_ + +#endif // LIGHTGBM_BOOSTING_GOSS_HPP_ diff --git a/src/boosting/rf.hpp b/src/boosting/rf.hpp index ac1008f88516..9a87e982483e 100644 --- a/src/boosting/rf.hpp +++ b/src/boosting/rf.hpp @@ -32,8 +32,12 @@ class RF : public GBDT { void Init(const Config* config, const Dataset* train_data, const ObjectiveFunction* objective_function, const std::vector& training_metrics) override { - CHECK(config->bagging_freq > 0 && config->bagging_fraction < 1.0f && config->bagging_fraction > 0.0f); - CHECK(config->feature_fraction <= 1.0f && config->feature_fraction > 0.0f); + if (config->data_sample_strategy == std::string("bagging")) { + CHECK((config->bagging_freq > 0 && config->bagging_fraction < 1.0f && config->bagging_fraction > 0.0f) || + (config->feature_fraction < 1.0f && config->feature_fraction > 0.0f)); + } else { + CHECK_EQ(config->data_sample_strategy, std::string("goss")); + } GBDT::Init(config, train_data, objective_function, training_metrics); if (num_init_iteration_ > 0) { @@ -48,15 +52,19 @@ class RF : public GBDT { shrinkage_rate_ = 1.0f; // only boosting one time Boosting(); - if (is_use_subset_ && bag_data_cnt_ < num_data_) { + if (data_sample_strategy_->is_use_subset() && data_sample_strategy_->bag_data_cnt() < num_data_) { tmp_grad_.resize(num_data_); tmp_hess_.resize(num_data_); } } void ResetConfig(const Config* config) override { - CHECK(config->bagging_freq > 0 && config->bagging_fraction < 1.0f && config->bagging_fraction > 0.0f); - CHECK(config->feature_fraction <= 1.0f && config->feature_fraction > 0.0f); + if (config->data_sample_strategy == std::string("bagging")) { + CHECK((config->bagging_freq > 0 && config->bagging_fraction < 1.0f && config->bagging_fraction > 0.0f) || + (config->feature_fraction < 1.0f && config->feature_fraction > 0.0f)); + } else { + CHECK_EQ(config->data_sample_strategy, std::string("goss")); + } GBDT::ResetConfig(config); // not shrinkage rate for the RF shrinkage_rate_ = 1.0f; @@ -73,7 +81,7 @@ class RF : public GBDT { CHECK_EQ(num_tree_per_iteration_, num_class_); // only boosting one time Boosting(); - if (is_use_subset_ && bag_data_cnt_ < num_data_) { + if (data_sample_strategy_->is_use_subset() && data_sample_strategy_->bag_data_cnt() < num_data_) { tmp_grad_.resize(num_data_); tmp_hess_.resize(num_data_); } @@ -102,7 +110,11 @@ class RF : public GBDT { bool TrainOneIter(const score_t* gradients, const score_t* hessians) override { // bagging logic - Bagging(iter_); + data_sample_strategy_ ->Bagging(iter_, tree_learner_.get(), gradients_.data(), hessians_.data()); + const bool is_use_subset = data_sample_strategy_->is_use_subset(); + const data_size_t bag_data_cnt = data_sample_strategy_->bag_data_cnt(); + const std::vector>& bag_data_indices = data_sample_strategy_->bag_data_indices(); + CHECK_EQ(gradients, nullptr); CHECK_EQ(hessians, nullptr); @@ -115,11 +127,10 @@ class RF : public GBDT { auto grad = gradients + offset; auto hess = hessians + offset; - // need to copy gradients for bagging subset. - if (is_use_subset_ && bag_data_cnt_ < num_data_ && !boosting_on_gpu_) { - for (int i = 0; i < bag_data_cnt_; ++i) { - tmp_grad_[i] = grad[bag_data_indices_[i]]; - tmp_hess_[i] = hess[bag_data_indices_[i]]; + if (is_use_subset && bag_data_cnt < num_data_ && !boosting_on_gpu_) { + for (int i = 0; i < bag_data_cnt; ++i) { + tmp_grad_[i] = grad[bag_data_indices[i]]; + tmp_hess_[i] = hess[bag_data_indices[i]]; } grad = tmp_grad_.data(); hess = tmp_hess_.data(); @@ -132,7 +143,7 @@ class RF : public GBDT { double pred = init_scores_[cur_tree_id]; auto residual_getter = [pred](const label_t* label, int i) {return static_cast(label[i]) - pred; }; tree_learner_->RenewTreeOutput(new_tree.get(), objective_function_, residual_getter, - num_data_, bag_data_indices_.data(), bag_data_cnt_, train_score_updater_->score()); + num_data_, bag_data_indices.data(), bag_data_cnt, train_score_updater_->score()); if (std::fabs(init_scores_[cur_tree_id]) > kEpsilon) { new_tree->AddBias(init_scores_[cur_tree_id]); } diff --git a/src/boosting/sample_strategy.cpp b/src/boosting/sample_strategy.cpp new file mode 100644 index 000000000000..71c4ac1755de --- /dev/null +++ b/src/boosting/sample_strategy.cpp @@ -0,0 +1,24 @@ +/*! + * Copyright (c) 2021 Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for license information. + */ + +#include +#include "goss.hpp" +#include "bagging.hpp" + +namespace LightGBM { + +SampleStrategy* SampleStrategy::CreateSampleStrategy( + const Config* config, + const Dataset* train_data, + const ObjectiveFunction* objective_function, + int num_tree_per_iteration) { + if (config->data_sample_strategy == std::string("goss")) { + return new GOSSStrategy(config, train_data, num_tree_per_iteration); + } else { + return new BaggingSampleStrategy(config, train_data, objective_function, num_tree_per_iteration); + } +} + +} // namespace LightGBM diff --git a/src/io/config.cpp b/src/io/config.cpp index 72006eb50cdb..8827414c2e99 100644 --- a/src/io/config.cpp +++ b/src/io/config.cpp @@ -99,6 +99,20 @@ void GetBoostingType(const std::unordered_map& params, } } +void GetDataSampleStrategy(const std::unordered_map& params, std::string* strategy) { + std::string value; + if (Config::GetString(params, "data_sample_strategy", &value)) { + std::transform(value.begin(), value.end(), value.begin(), Common::tolower); + if (value == std::string("goss")) { + *strategy = "goss"; + } else if (value == std::string("bagging")) { + *strategy = "bagging"; + } else { + Log::Fatal("Unknown sample strategy %s", value.c_str()); + } + } +} + void ParseMetrics(const std::string& value, std::vector* out_metric) { std::unordered_set metric_sets; out_metric->clear(); @@ -242,6 +256,7 @@ void Config::Set(const std::unordered_map& params) { GetTaskType(params, &task); GetBoostingType(params, &boosting); + GetDataSampleStrategy(params, &data_sample_strategy); GetObjectiveType(params, &objective); GetMetricType(params, objective, &metric); GetDeviceType(params, &device_type); @@ -423,6 +438,12 @@ void Config::CheckParamConflict() { "Will set min_data_in_leaf to 1."); min_data_in_leaf = 1; } + if (boosting == std::string("goss")) { + boosting = std::string("gbdt"); + data_sample_strategy = std::string("goss"); + Log::Warning("Found boosting=goss. For backwards compatibility reasons, LightGBM interprets this as boosting=gbdt, data_sample_strategy=goss." + "To suppress this warning, set data_sample_strategy=goss instead."); + } } std::string Config::ToString() const { diff --git a/src/io/config_auto.cpp b/src/io/config_auto.cpp index a86abd3a2c1d..b1dbcc378a27 100644 --- a/src/io/config_auto.cpp +++ b/src/io/config_auto.cpp @@ -186,6 +186,7 @@ const std::unordered_set& Config::parameter_set() { "task", "objective", "boosting", + "data_sample_strategy", "data", "valid", "num_iterations", @@ -762,6 +763,7 @@ const std::unordered_map>& Config::paramet {"task", {"task_type"}}, {"objective", {"objective_type", "app", "application", "loss"}}, {"boosting", {"boosting_type", "boost"}}, + {"data_sample_strategy", {}}, {"data", {"train", "train_data", "train_data_file", "data_filename"}}, {"valid", {"test", "valid_data", "valid_data_file", "test_data", "test_data_file", "valid_filenames"}}, {"num_iterations", {"num_iteration", "n_iter", "num_tree", "num_trees", "num_round", "num_rounds", "nrounds", "num_boost_round", "n_estimators", "max_iter"}}, @@ -899,6 +901,7 @@ const std::unordered_map& Config::ParameterTypes() { {"config", "string"}, {"objective", "string"}, {"boosting", "string"}, + {"data_sample_strategy", "string"}, {"data", "string"}, {"valid", "vector"}, {"num_iterations", "int"}, diff --git a/src/objective/objective_function.cpp b/src/objective/objective_function.cpp index 96b512e009fd..79749570d672 100644 --- a/src/objective/objective_function.cpp +++ b/src/objective/objective_function.cpp @@ -19,7 +19,9 @@ namespace LightGBM { ObjectiveFunction* ObjectiveFunction::CreateObjectiveFunction(const std::string& type, const Config& config) { #ifdef USE_CUDA_EXP - if (config.device_type == std::string("cuda_exp") && config.boosting == std::string("gbdt")) { + if (config.device_type == std::string("cuda_exp") && + config.data_sample_strategy != std::string("goss") && + config.boosting != std::string("rf")) { if (type == std::string("regression")) { return new CUDARegressionL2loss(config); } else if (type == std::string("regression_l1")) { diff --git a/tests/python_package_test/test_engine.py b/tests/python_package_test/test_engine.py index 1172c620e6ba..f6ddcbb72ed8 100644 --- a/tests/python_package_test/test_engine.py +++ b/tests/python_package_test/test_engine.py @@ -3592,6 +3592,142 @@ def test_force_split_with_feature_fraction(tmp_path): assert tree_structure['split_feature'] == 0 +def test_goss_boosting_and_strategy_equivalent(): + X, y = make_synthetic_regression(n_samples=10_000, n_features=10, n_informative=5, random_state=42) + X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) + lgb_train = lgb.Dataset(X_train, y_train) + lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train) + base_params = { + 'metric': 'l2', + 'verbose': -1, + 'bagging_seed': 0, + 'learning_rate': 0.05, + 'num_threads': 1, + 'force_row_wise': True, + 'gpu_use_dp': True, + } + params1 = {**base_params, 'boosting': 'goss'} + evals_result1 = {} + lgb.train(params1, lgb_train, + num_boost_round=10, + valid_sets=lgb_eval, + callbacks=[lgb.record_evaluation(evals_result1)]) + params2 = {**base_params, 'data_sample_strategy': 'goss'} + evals_result2 = {} + lgb.train(params2, lgb_train, + num_boost_round=10, + valid_sets=lgb_eval, + callbacks=[lgb.record_evaluation(evals_result2)]) + assert evals_result1['valid_0']['l2'] == evals_result2['valid_0']['l2'] + + +def test_sample_strategy_with_boosting(): + X, y = make_synthetic_regression(n_samples=10_000, n_features=10, n_informative=5, random_state=42) + X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) + lgb_train = lgb.Dataset(X_train, y_train) + lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train) + + base_params = { + 'metric': 'l2', + 'verbose': -1, + 'num_threads': 1, + 'force_row_wise': True, + 'gpu_use_dp': True, + } + + params1 = {**base_params, 'boosting': 'dart', 'data_sample_strategy': 'goss'} + evals_result = {} + gbm = lgb.train(params1, lgb_train, + num_boost_round=10, + valid_sets=lgb_eval, + callbacks=[lgb.record_evaluation(evals_result)]) + eval_res1 = evals_result['valid_0']['l2'][-1] + test_res1 = mean_squared_error(y_test, gbm.predict(X_test)) + assert test_res1 == pytest.approx(3149.393862, abs=1.0) + assert eval_res1 == pytest.approx(test_res1) + + params2 = {**base_params, 'boosting': 'gbdt', 'data_sample_strategy': 'goss'} + evals_result = {} + gbm = lgb.train(params2, lgb_train, + num_boost_round=10, + valid_sets=lgb_eval, + callbacks=[lgb.record_evaluation(evals_result)]) + eval_res2 = evals_result['valid_0']['l2'][-1] + test_res2 = mean_squared_error(y_test, gbm.predict(X_test)) + assert test_res2 == pytest.approx(2547.715968, abs=1.0) + assert eval_res2 == pytest.approx(test_res2) + + params3 = {**base_params, 'boosting': 'goss', 'data_sample_strategy': 'goss'} + evals_result = {} + gbm = lgb.train(params3, lgb_train, + num_boost_round=10, + valid_sets=lgb_eval, + callbacks=[lgb.record_evaluation(evals_result)]) + eval_res3 = evals_result['valid_0']['l2'][-1] + test_res3 = mean_squared_error(y_test, gbm.predict(X_test)) + assert test_res3 == pytest.approx(2547.715968, abs=1.0) + assert eval_res3 == pytest.approx(test_res3) + + params4 = {**base_params, 'boosting': 'rf', 'data_sample_strategy': 'goss'} + evals_result = {} + gbm = lgb.train(params4, lgb_train, + num_boost_round=10, + valid_sets=lgb_eval, + callbacks=[lgb.record_evaluation(evals_result)]) + eval_res4 = evals_result['valid_0']['l2'][-1] + test_res4 = mean_squared_error(y_test, gbm.predict(X_test)) + assert test_res4 == pytest.approx(2095.538735, abs=1.0) + assert eval_res4 == pytest.approx(test_res4) + + assert test_res1 != test_res2 + assert eval_res1 != eval_res2 + assert test_res2 == test_res3 + assert eval_res2 == eval_res3 + assert eval_res1 != eval_res4 + assert test_res1 != test_res4 + assert eval_res2 != eval_res4 + assert test_res2 != test_res4 + + params5 = {**base_params, 'boosting': 'dart', 'data_sample_strategy': 'bagging', 'bagging_freq': 1, 'bagging_fraction': 0.5} + evals_result = {} + gbm = lgb.train(params5, lgb_train, + num_boost_round=10, + valid_sets=lgb_eval, + callbacks=[lgb.record_evaluation(evals_result)]) + eval_res5 = evals_result['valid_0']['l2'][-1] + test_res5 = mean_squared_error(y_test, gbm.predict(X_test)) + assert test_res5 == pytest.approx(3134.866931, abs=1.0) + assert eval_res5 == pytest.approx(test_res5) + + params6 = {**base_params, 'boosting': 'gbdt', 'data_sample_strategy': 'bagging', 'bagging_freq': 1, 'bagging_fraction': 0.5} + evals_result = {} + gbm = lgb.train(params6, lgb_train, + num_boost_round=10, + valid_sets=lgb_eval, + callbacks=[lgb.record_evaluation(evals_result)]) + eval_res6 = evals_result['valid_0']['l2'][-1] + test_res6 = mean_squared_error(y_test, gbm.predict(X_test)) + assert test_res6 == pytest.approx(2539.792378, abs=1.0) + assert eval_res6 == pytest.approx(test_res6) + assert test_res5 != test_res6 + assert eval_res5 != eval_res6 + + params7 = {**base_params, 'boosting': 'rf', 'data_sample_strategy': 'bagging', 'bagging_freq': 1, 'bagging_fraction': 0.5} + evals_result = {} + gbm = lgb.train(params7, lgb_train, + num_boost_round=10, + valid_sets=lgb_eval, + callbacks=[lgb.record_evaluation(evals_result)]) + eval_res7 = evals_result['valid_0']['l2'][-1] + test_res7 = mean_squared_error(y_test, gbm.predict(X_test)) + assert test_res7 == pytest.approx(1518.704481, abs=1.0) + assert eval_res7 == pytest.approx(test_res7) + assert test_res5 != test_res7 + assert eval_res5 != eval_res7 + assert test_res6 != test_res7 + assert eval_res6 != eval_res7 + + def test_record_evaluation_with_train(): X, y = make_synthetic_regression() ds = lgb.Dataset(X, y) diff --git a/tests/python_package_test/utils.py b/tests/python_package_test/utils.py index bb8dcee5ecfd..29183713d714 100644 --- a/tests/python_package_test/utils.py +++ b/tests/python_package_test/utils.py @@ -114,8 +114,9 @@ def make_ranking(n_samples=100, n_features=20, n_informative=5, gmax=2, @lru_cache(maxsize=None) -def make_synthetic_regression(n_samples=100): - return sklearn.datasets.make_regression(n_samples, n_features=4, n_informative=2, random_state=42) +def make_synthetic_regression(n_samples=100, n_features=4, n_informative=2, random_state=42): + return sklearn.datasets.make_regression(n_samples=n_samples, n_features=n_features, + n_informative=n_informative, random_state=random_state) def dummy_obj(preds, train_data): diff --git a/windows/LightGBM.vcxproj b/windows/LightGBM.vcxproj index 653b86c00ac5..876ddda4cf64 100644 --- a/windows/LightGBM.vcxproj +++ b/windows/LightGBM.vcxproj @@ -253,6 +253,7 @@ + @@ -311,6 +312,7 @@ + diff --git a/windows/LightGBM.vcxproj.filters b/windows/LightGBM.vcxproj.filters index 0f48c7564580..56b4e29287d5 100644 --- a/windows/LightGBM.vcxproj.filters +++ b/windows/LightGBM.vcxproj.filters @@ -129,6 +129,9 @@ include\LightGBM + + include\LightGBM + include\LightGBM @@ -311,6 +314,9 @@ src\boosting + + src\boosting + src\io From 59c73133041e20cf049441b6683424c077815e0e Mon Sep 17 00:00:00 2001 From: James Lamb Date: Wed, 28 Dec 2022 01:19:46 -0600 Subject: [PATCH 14/25] [ci] fix locale-setting in jobs running in ubuntu container (#5643) --- .ci/setup.sh | 12 ++++++------ .ci/test.sh | 5 +++++ .vsts-ci.yml | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.ci/setup.sh b/.ci/setup.sh index 443aecaf42b5..6ed714f3a1a2 100755 --- a/.ci/setup.sh +++ b/.ci/setup.sh @@ -23,7 +23,7 @@ if [[ $OS_NAME == "macos" ]]; then -o miniforge.sh \ https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-MacOSX-x86_64.sh else # Linux - if [[ $IN_UBUNTU_LATEST_CONTAINER == "true" ]]; then + if [[ $IN_UBUNTU_BASE_CONTAINER == "true" ]]; then # fixes error "unable to initialize frontend: Dialog" # https://github.com/moby/moby/issues/27988#issuecomment-462809153 echo 'debconf debconf/frontend select Noninteractive' | sudo debconf-set-selections @@ -46,6 +46,7 @@ else # Linux libssl-dev \ libunwind8 \ locales \ + locales-all \ netcat \ unzip \ zip || exit -1 @@ -56,16 +57,15 @@ else # Linux fi export LANG="en_US.UTF-8" + sudo update-locale LANG=${LANG} export LC_ALL="${LANG}" - sudo locale-gen ${LANG} - sudo update-locale fi if [[ $TASK == "r-package" ]] && [[ $COMPILER == "clang" ]]; then sudo apt-get install --no-install-recommends -y \ libomp-dev fi if [[ $TASK == "mpi" ]]; then - if [[ $IN_UBUNTU_LATEST_CONTAINER == "true" ]]; then + if [[ $IN_UBUNTU_BASE_CONTAINER == "true" ]]; then sudo apt-get update sudo apt-get install --no-install-recommends -y \ libopenmpi-dev \ @@ -78,7 +78,7 @@ else # Linux fi fi if [[ $TASK == "gpu" ]]; then - if [[ $IN_UBUNTU_LATEST_CONTAINER == "true" ]]; then + if [[ $IN_UBUNTU_BASE_CONTAINER == "true" ]]; then sudo apt-get update sudo apt-get install --no-install-recommends -y \ libboost1.74-dev \ @@ -94,7 +94,7 @@ else # Linux fi fi if [[ $TASK == "gpu" || $TASK == "bdist" ]]; then - if [[ $IN_UBUNTU_LATEST_CONTAINER == "true" ]]; then + if [[ $IN_UBUNTU_BASE_CONTAINER == "true" ]]; then sudo apt-get update sudo apt-get install --no-install-recommends -y \ pocl-opencl-icd diff --git a/.ci/test.sh b/.ci/test.sh index fbe0835838f0..b8ab8d690fc6 100755 --- a/.ci/test.sh +++ b/.ci/test.sh @@ -8,6 +8,11 @@ elif [[ $OS_NAME == "linux" ]] && [[ $COMPILER == "clang" ]]; then export CC=clang fi +if [[ $IN_UBUNTU_BASE_CONTAINER == "true" ]]; then + export LANG="en_US.UTF-8" + export LC_ALL="en_US.UTF-8" +fi + if [[ "${TASK}" == "r-package" ]] || [[ "${TASK}" == "r-rchk" ]]; then bash ${BUILD_DIRECTORY}/.ci/test_r_package.sh || exit -1 exit 0 diff --git a/.vsts-ci.yml b/.vsts-ci.yml index b73676ae58b3..a01e994e0a91 100644 --- a/.vsts-ci.yml +++ b/.vsts-ci.yml @@ -84,7 +84,7 @@ jobs: variables: COMPILER: clang DEBIAN_FRONTEND: 'noninteractive' - IN_UBUNTU_LATEST_CONTAINER: 'true' + IN_UBUNTU_BASE_CONTAINER: 'true' OS_NAME: 'linux' SETUP_CONDA: 'true' pool: sh-ubuntu From 4131ef8fe3a5a07e39c67b93042db941490a3755 Mon Sep 17 00:00:00 2001 From: James Lamb Date: Wed, 28 Dec 2022 12:27:45 -0600 Subject: [PATCH 15/25] [ci] allow ubuntu-latest to float for some GitHub Actions jobs (#5644) --- .github/workflows/linkchecker.yml | 2 +- .github/workflows/optional_checks.yml | 2 +- .github/workflows/r_package.yml | 6 +++--- .github/workflows/static_analysis.yml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/linkchecker.yml b/.github/workflows/linkchecker.yml index 02a25f01230a..f4aad95618ea 100644 --- a/.github/workflows/linkchecker.yml +++ b/.github/workflows/linkchecker.yml @@ -17,7 +17,7 @@ env: jobs: check-links: timeout-minutes: 60 - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v3 diff --git a/.github/workflows/optional_checks.yml b/.github/workflows/optional_checks.yml index d35bca295dd9..6ee6e9e46296 100644 --- a/.github/workflows/optional_checks.yml +++ b/.github/workflows/optional_checks.yml @@ -9,7 +9,7 @@ on: jobs: all-optional-checks-successful: timeout-minutes: 120 - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v3 diff --git a/.github/workflows/r_package.yml b/.github/workflows/r_package.yml index 1995068c2484..ae4e44da7ed2 100644 --- a/.github/workflows/r_package.yml +++ b/.github/workflows/r_package.yml @@ -181,9 +181,9 @@ jobs: $env:TASK = "${{ matrix.task }}" & "$env:GITHUB_WORKSPACE/.ci/test_windows.ps1" test-r-sanitizers: - name: r-sanitizers (ubuntu-22.04, R-devel, ${{ matrix.compiler }} ASAN/UBSAN) + name: r-sanitizers (ubuntu-latest, R-devel, ${{ matrix.compiler }} ASAN/UBSAN) timeout-minutes: 60 - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest container: wch1/r-debug strategy: fail-fast: false @@ -219,7 +219,7 @@ jobs: test-r-debian-clang: name: r-package (debian, R-devel, clang) timeout-minutes: 60 - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest container: rhub/debian-clang-devel steps: - name: Install Git before checkout diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 663ea8536f11..0419e4783030 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -47,7 +47,7 @@ jobs: r-check-docs: name: r-package-check-docs timeout-minutes: 60 - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest container: rocker/verse steps: - name: Trust git cloning LightGBM From 7c1ab96fc135281edb1ac2595e361f6a77c296b3 Mon Sep 17 00:00:00 2001 From: James Lamb Date: Wed, 28 Dec 2022 14:45:01 -0600 Subject: [PATCH 16/25] [ci] use LightGBM CI image for building aarch64 wheels (fixes #5595) (#5622) --- .ci/setup.sh | 28 ---------------------------- .vsts-ci.yml | 2 +- 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/.ci/setup.sh b/.ci/setup.sh index 6ed714f3a1a2..89fbed442ed1 100755 --- a/.ci/setup.sh +++ b/.ci/setup.sh @@ -98,34 +98,6 @@ else # Linux sudo apt-get update sudo apt-get install --no-install-recommends -y \ pocl-opencl-icd - elif [[ $(uname -m) == "aarch64" ]]; then - yum install -y \ - epel-release \ - gcc-c++ \ - hwloc-devel \ - sudo - yum install -y \ - llvm-toolset-7.0-clang-devel \ - llvm-toolset-7.0-llvm-devel \ - ocl-icd-devel - git clone --depth 1 --branch v1.8 https://github.com/pocl/pocl.git - cmake \ - -B pocl/build \ - -S pocl \ - -DCMAKE_BUILD_TYPE=release \ - -DCMAKE_C_COMPILER=/usr/bin/gcc \ - -DCMAKE_CXX_COMPILER=/usr/bin/g++ \ - -DCMAKE_C_FLAGS=-std=gnu99 \ - -DPOCL_INSTALL_ICD_VENDORDIR=/etc/OpenCL/vendors \ - -DPOCL_DEBUG_MESSAGES=OFF \ - -DINSTALL_OPENCL_HEADERS=OFF \ - -DENABLE_SPIR=OFF \ - -DENABLE_POCLCC=OFF \ - -DENABLE_TESTS=OFF \ - -DENABLE_EXAMPLES=OFF \ - -DLLC_HOST_CPU=generic - cmake --build pocl/build -j4 - sudo cmake --install pocl/build elif [[ $(uname -m) == "x86_64" ]]; then sudo yum update -y sudo yum install -y \ diff --git a/.vsts-ci.yml b/.vsts-ci.yml index a01e994e0a91..61012d73aaea 100644 --- a/.vsts-ci.yml +++ b/.vsts-ci.yml @@ -192,7 +192,7 @@ jobs: $ROOT_DOCKER_FOLDER/.ci/setup.sh || exit -1 $ROOT_DOCKER_FOLDER/.ci/test.sh || exit -1 EOF - IMAGE_URI="quay.io/pypa/manylinux2014_${ARCH}" + IMAGE_URI="lightgbm/vsts-agent:manylinux2014_aarch64" docker pull "${IMAGE_URI}" || exit -1 PLATFORM=$(docker inspect --format='{{.Os}}/{{.Architecture}}' "${IMAGE_URI}") || exit -1 echo "detected image platform: ${PLATFORM}" From 7f4dbc8191ff391eb424a9954bf649e9b8a66d49 Mon Sep 17 00:00:00 2001 From: James Lamb Date: Wed, 28 Dec 2022 20:16:26 -0600 Subject: [PATCH 17/25] [ci] [python-package] fix missing import, test that lightgbm can be imported with only required dependencies (fixes #5631) (#5632) --- .ci/test.sh | 11 +++++++++++ python-package/lightgbm/compat.py | 1 + 2 files changed, 12 insertions(+) diff --git a/.ci/test.sh b/.ci/test.sh index b8ab8d690fc6..f18198b5924f 100755 --- a/.ci/test.sh +++ b/.ci/test.sh @@ -282,4 +282,15 @@ matplotlib.use\(\"Agg\"\)\ cd $BUILD_DIRECTORY/examples/python-guide/notebooks sed -i'.bak' 's/INTERACTIVE = False/assert False, \\"Interactive mode disabled\\"/' interactive_plot_example.ipynb jupyter nbconvert --ExecutePreprocessor.timeout=180 --to notebook --execute --inplace *.ipynb || exit -1 # run all notebooks + + # importing the library should succeed even if all optional dependencies are not present + conda uninstall --force --yes \ + dask \ + distributed \ + joblib \ + matplotlib \ + psutil \ + python-graphviz \ + scikit-learn || exit -1 + python -c "import lightgbm" || exit -1 fi diff --git a/python-package/lightgbm/compat.py b/python-package/lightgbm/compat.py index 65c044ffc883..adbc5f62593e 100644 --- a/python-package/lightgbm/compat.py +++ b/python-package/lightgbm/compat.py @@ -122,6 +122,7 @@ class _LGBMRegressorBase: # type: ignore pass + _LGBMBaseCrossValidator = None _LGBMLabelEncoder = None LGBMNotFittedError = ValueError _LGBMStratifiedKFold = None From b88cf8afaba4c69aff90e968c38fdb6cc7fbfa01 Mon Sep 17 00:00:00 2001 From: James Lamb Date: Wed, 28 Dec 2022 22:43:50 -0600 Subject: [PATCH 18/25] [docs] update CPU dockerfiles (fixes #5550) (#5601) --- docker/README.md | 190 +++++++++++++++++++++++++++------------ docker/dockerfile-cli | 20 +++-- docker/dockerfile-python | 12 ++- docker/dockerfile-r | 16 ++-- 4 files changed, 165 insertions(+), 73 deletions(-) diff --git a/docker/README.md b/docker/README.md index ab9bd75ef919..7e9e3276dd33 100644 --- a/docker/README.md +++ b/docker/README.md @@ -2,6 +2,8 @@ This directory contains `Dockerfile`s to make it easy to build and run LightGBM via [Docker](https://www.docker.com/). +These builds of LightGBM all train on the CPU. For GPU-enabled builds, see [the gpu/ directory](./gpu). + ## Installing Docker Follow the general installation instructions [on the Docker site](https://docs.docker.com/install/): @@ -12,113 +14,183 @@ Follow the general installation instructions [on the Docker site](https://docs.d ## Using CLI Version of LightGBM via Docker -Build a Docker image with LightGBM CLI: +Build an image with the LightGBM CLI. -``` +```shell mkdir lightgbm-docker cd lightgbm-docker wget https://raw.githubusercontent.com/Microsoft/LightGBM/master/docker/dockerfile-cli -docker build -t lightgbm-cli -f dockerfile-cli . +docker build \ + -t lightgbm-cli \ + -f dockerfile-cli \ + . ``` -where `lightgbm-cli` is the desired Docker image name. +Once that completes, the built image can be used to run the CLI in a container. +To try it out, run the following. -Run the CLI from the container: +```shell +# configure the CLI +cat << EOF > train.conf +task = train +objective = binary +data = binary.train +num_trees = 10 +output_model = LightGBM-CLI-model.txt +EOF -``` -docker run --rm -it \ ---volume $HOME/lgbm.conf:/lgbm.conf \ ---volume $HOME/model.txt:/model.txt \ ---volume $HOME/tmp:/out \ -lightgbm-cli \ -config=lgbm.conf -``` - -In the above example, three volumes are [mounted](https://docs.docker.com/engine/reference/commandline/run/#mount-volume--v---read-only) -from the host machine to the Docker container: +# get training data +curl -O https://raw.githubusercontent.com/Microsoft/LightGBM/master/examples/binary_classification/binary.train -* `lgbm.conf` - task config, for example - -``` -app=multiclass -num_class=3 -task=convert_model -input_model=model.txt -convert_model=/out/predict.cpp -convert_model_language=cpp +# train, and save model to a text file +docker run \ + --rm \ + --volume "${PWD}":/opt/training \ + --workdir /opt/training \ + lightgbm-cli \ + config=train.conf ``` -* `model.txt` - an input file for the task, could be training data or, in this case, a pre-trained model. -* `out` - a directory to store the output of the task, notice that `convert_model` in the task config is using it. +After this runs, a LightGBM model can be found at `LightGBM-CLI-model.txt`. -`config=lgbm.conf` is a command-line argument passed to the `lightgbm` executable, more arguments can be passed if required. +For more details on how to configure and use the LightGBM CLI, see https://lightgbm.readthedocs.io/en/latest/Quick-Start.html. ## Running the Python-package Сontainer -Build the container, for Python users: +Build an image with the LightGBM Python package installed. -``` +```shell mkdir lightgbm-docker cd lightgbm-docker wget https://raw.githubusercontent.com/Microsoft/LightGBM/master/docker/dockerfile-python -docker build -t lightgbm -f dockerfile-python . +docker build \ + -t lightgbm-python \ + -f dockerfile-python \ + . ``` -After build finished, run the container: +Once that completes, the built image can be used to run LightGBM's Python package in a container. +Run the following to produce a model using the Python package. + +```shell +# get training data +curl -O https://raw.githubusercontent.com/Microsoft/LightGBM/master/examples/binary_classification/binary.train + +# create training script +cat << EOF > train.py +import lightgbm as lgb +import numpy as np +params = { + "objective": "binary", + "num_trees": 10 +} +bst = lgb.train( + train_set=lgb.Dataset("binary.train"), + params=params +) +bst.save_model("LightGBM-python-model.txt") +EOF + +# run training in a container +docker run \ + --rm \ + --volume "${PWD}":/opt/training \ + --workdir /opt/training \ + lightgbm-python \ + python train.py ``` -docker run --rm -it lightgbm + +After this runs, a LightGBM model can be found at `LightGBM-python-model.txt`. + +Or run an interactive Python session in a container. + +```shell +docker run \ + --rm \ + --volume "${PWD}":/opt/training \ + --workdir /opt/training \ + -it lightgbm-python \ + python ``` ## Running the R-package Сontainer -Build the container based on the [`verse` Rocker image](https://www.rocker-project.org/images/), for R users: +Build an image with the LightGBM R package installed. -``` +```shell mkdir lightgbm-docker cd lightgbm-docker wget https://raw.githubusercontent.com/Microsoft/LightGBM/master/docker/dockerfile-r -docker build -t lightgbm-r -f dockerfile-r . -``` - -This will default to the latest version of R. If you want to try with an older `rocker` container to run a particular version of R, pass in a build arg with [a valid tag](https://hub.docker.com/r/rocker/verse/tags). -For example, to test with R 3.5: - -``` docker build \ - -t lightgbm-r-35 \ + -t lightgbm-r \ -f dockerfile-r \ - --build-arg R_VERSION=3.5 \ . ``` -After the build is finished you have two options to run the container: +Once that completes, the built image can be used to run LightGBM's R package in a container. +Run the following to produce a model using the R package. -1. Start [RStudio](https://www.rstudio.com/products/rstudio/), an interactive development environment, so that you can develop your analysis using LightGBM or simply try out the R package. You can open RStudio in your web browser. -2. Start a regular R session. +```shell +# get training data +curl -O https://raw.githubusercontent.com/Microsoft/LightGBM/master/examples/binary_classification/binary.train -In both cases you can simply call +# create training script +cat << EOF > train.R +library(lightgbm) +params <- list( + objective = "binary" + , num_trees = 10L +) -``` -library("lightgbm") +bst <- lgb.train( + data = lgb.Dataset("binary.train"), + params = params +) +lgb.save(bst, "LightGBM-R-model.txt") +EOF + +# run training in a container +docker run \ + --rm \ + --volume "${PWD}":/opt/training \ + --workdir /opt/training \ + lightgbm-r \ + Rscript train.R ``` -to load the installed LightGBM R package. +After this runs, a LightGBM model can be found at `LightGBM-R-model.txt`. -**RStudio** +Run the following to get an interactive R session in a container. +```shell +docker run \ + --rm \ + -it lightgbm-r \ + R ``` -docker run --rm -it -e PASSWORD=lightgbm -p 8787:8787 lightgbm-r + +To use [RStudio](https://www.rstudio.com/products/rstudio/), an interactive development environment, run the following. + +```shell +docker run \ + --rm \ + --env PASSWORD="lightgbm" \ + -p 8787:8787 \ + lightgbm-r ``` -Open the browser at http://localhost:8787 and log in. -See the [`rocker/rstudio`](https://hub.docker.com/r/rocker/rstudio) image documentation for further configuration options. +Then navigate to `localhost:8787` in your local web browser, and log in with username `rstudio` and password `lightgbm`. -**Regular R** +To target a different R version, pass any [valid rocker/verse tag](https://hub.docker.com/r/rocker/verse/tags) to `docker build`. -If you just want a vanilla R process, change the executable of the container: +For example, to test LightGBM with R 3.5: -``` -docker run --rm -it lightgbm-r R +```shell +docker build \ + -t lightgbm-r-35 \ + -f dockerfile-r \ + --build-arg R_VERSION=3.5 \ + . ``` diff --git a/docker/dockerfile-cli b/docker/dockerfile-cli index e033f1c51de5..dc1972ab7df4 100644 --- a/docker/dockerfile-cli +++ b/docker/dockerfile-cli @@ -1,22 +1,32 @@ -FROM ubuntu:16.04 +FROM ubuntu:20.04 -RUN apt-get update && \ +ENV \ + DEBIAN_FRONTEND=noninteractive \ + LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 + +RUN apt-get update -y && \ apt-get install -y --no-install-recommends \ ca-certificates \ cmake \ build-essential \ gcc \ g++ \ - git && \ + git \ + libomp-dev && \ rm -rf /var/lib/apt/lists/* -RUN git clone --recursive --branch stable --depth 1 https://github.com/Microsoft/LightGBM && \ +RUN git clone \ + --recursive \ + --branch stable \ + --depth 1 \ + https://github.com/Microsoft/LightGBM && \ mkdir LightGBM/build && \ cd LightGBM/build && \ cmake .. && \ make -j4 && \ make install && \ - cd ../.. && \ + cd "${HOME}" && \ rm -rf LightGBM ENTRYPOINT ["lightgbm"] diff --git a/docker/dockerfile-python b/docker/dockerfile-python index 3e473f5e9686..6c5ca6501ac3 100644 --- a/docker/dockerfile-python +++ b/docker/dockerfile-python @@ -1,7 +1,12 @@ -FROM ubuntu:16.04 +FROM ubuntu:20.04 ARG CONDA_DIR=/opt/miniforge -ENV PATH $CONDA_DIR/bin:$PATH + +ENV \ + DEBIAN_FRONTEND=noninteractive \ + LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 \ + PATH=$CONDA_DIR/bin:$PATH RUN apt-get update && \ apt-get install -y --no-install-recommends \ @@ -11,7 +16,8 @@ RUN apt-get update && \ gcc \ g++ \ curl \ - git && \ + git \ + libomp-dev && \ # python environment curl -sL https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-x86_64.sh -o miniforge.sh && \ /bin/bash miniforge.sh -f -b -p $CONDA_DIR && \ diff --git a/docker/dockerfile-r b/docker/dockerfile-r index 1f173cdcdafc..65c20165f83e 100644 --- a/docker/dockerfile-r +++ b/docker/dockerfile-r @@ -1,12 +1,16 @@ ARG R_VERSION=latest FROM rocker/verse:${R_VERSION} -WORKDIR /lgbm - RUN apt-get update && \ apt-get install -y --no-install-recommends \ build-essential \ - cmake && \ - git clone --recursive --branch stable --depth 1 https://github.com/Microsoft/LightGBM && \ - cd LightGBM && \ - Rscript build_r.R + libomp-dev && \ + git clone \ + --recursive \ + --branch stable \ + --depth 1 https://github.com/Microsoft/LightGBM && \ + cd ./LightGBM && \ + sh build-cran-package.sh --no-build-vignettes && \ + R CMD INSTALL ./lightgbm_*.tar.gz && \ + cd .. && \ + rm -rf ./LightGBM From 73531662e01384eefdd86ec81f4ba12901791ed2 Mon Sep 17 00:00:00 2001 From: shiyu1994 Date: Thu, 29 Dec 2022 14:43:03 +0800 Subject: [PATCH 19/25] [CUDA] Add binary logloss metric for new CUDA version (#5635) --- src/metric/binary_metric.hpp | 2 +- src/metric/cuda/cuda_binary_metric.cpp | 31 ++++++++++ src/metric/cuda/cuda_binary_metric.hpp | 57 +++++++++++++++++++ src/metric/cuda/cuda_pointwise_metric.cpp | 38 +++++++++++++ ...ion_metric.cu => cuda_pointwise_metric.cu} | 19 ++++--- src/metric/cuda/cuda_pointwise_metric.hpp | 43 ++++++++++++++ src/metric/cuda/cuda_regression_metric.cpp | 25 ++------ src/metric/cuda/cuda_regression_metric.hpp | 18 ++---- src/metric/metric.cpp | 6 +- src/objective/cuda/cuda_binary_objective.hpp | 2 + 10 files changed, 194 insertions(+), 47 deletions(-) create mode 100644 src/metric/cuda/cuda_binary_metric.cpp create mode 100644 src/metric/cuda/cuda_binary_metric.hpp create mode 100644 src/metric/cuda/cuda_pointwise_metric.cpp rename src/metric/cuda/{cuda_regression_metric.cu => cuda_pointwise_metric.cu} (69%) create mode 100644 src/metric/cuda/cuda_pointwise_metric.hpp diff --git a/src/metric/binary_metric.hpp b/src/metric/binary_metric.hpp index f70a4ef4ac14..037f54ba091a 100644 --- a/src/metric/binary_metric.hpp +++ b/src/metric/binary_metric.hpp @@ -96,7 +96,7 @@ class BinaryMetric: public Metric { return std::vector(1, loss); } - private: + protected: /*! \brief Number of data */ data_size_t num_data_; /*! \brief Pointer of label */ diff --git a/src/metric/cuda/cuda_binary_metric.cpp b/src/metric/cuda/cuda_binary_metric.cpp new file mode 100644 index 000000000000..d526fddeecb2 --- /dev/null +++ b/src/metric/cuda/cuda_binary_metric.cpp @@ -0,0 +1,31 @@ +/*! + * Copyright (c) 2022 Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for + * license information. + */ + +#ifdef USE_CUDA_EXP + +#include "cuda_binary_metric.hpp" + +namespace LightGBM { + +CUDABinaryLoglossMetric::CUDABinaryLoglossMetric(const Config& config): + CUDABinaryMetricInterface(config) {} + +template +std::vector CUDABinaryMetricInterface::Eval(const double* score, const ObjectiveFunction* objective) const { + const double* score_convert = score; + if (objective != nullptr && objective->NeedConvertOutputCUDA()) { + this->score_convert_buffer_.Resize(static_cast(this->num_data_) * static_cast(this->num_class_)); + score_convert = objective->ConvertOutputCUDA(this->num_data_, score, this->score_convert_buffer_.RawData()); + } + double sum_loss = 0.0, sum_weight = 0.0; + this->LaunchEvalKernel(score_convert, &sum_loss, &sum_weight); + const double eval_score = sum_loss / sum_weight; + return std::vector{eval_score}; +} + +} // namespace LightGBM + +#endif // USE_CUDA_EXP diff --git a/src/metric/cuda/cuda_binary_metric.hpp b/src/metric/cuda/cuda_binary_metric.hpp new file mode 100644 index 000000000000..ae50dac381dd --- /dev/null +++ b/src/metric/cuda/cuda_binary_metric.hpp @@ -0,0 +1,57 @@ +/*! + * Copyright (c) 2022 Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for + * license information. + */ + +#ifndef LIGHTGBM_METRIC_CUDA_CUDA_BINARY_METRIC_HPP_ +#define LIGHTGBM_METRIC_CUDA_CUDA_BINARY_METRIC_HPP_ + +#ifdef USE_CUDA_EXP + +#include +#include + +#include + +#include "cuda_regression_metric.hpp" +#include "../binary_metric.hpp" + +namespace LightGBM { + +template +class CUDABinaryMetricInterface: public CUDAPointwiseMetricInterface { + public: + explicit CUDABinaryMetricInterface(const Config& config): CUDAPointwiseMetricInterface(config) {} + + virtual ~CUDABinaryMetricInterface() {} + + std::vector Eval(const double* score, const ObjectiveFunction* objective) const override; +}; + +class CUDABinaryLoglossMetric: public CUDABinaryMetricInterface { + public: + explicit CUDABinaryLoglossMetric(const Config& config); + + virtual ~CUDABinaryLoglossMetric() {} + + __device__ static double MetricOnPointCUDA(label_t label, double score) { + // score should have been converted to probability + if (label <= 0) { + if (1.0f - score > kEpsilon) { + return -log(1.0f - score); + } + } else { + if (score > kEpsilon) { + return -log(score); + } + } + return -log(kEpsilon); + } +}; + +} // namespace LightGBM + +#endif // USE_CUDA_EXP + +#endif // LIGHTGBM_METRIC_CUDA_CUDA_BINARY_METRIC_HPP_ diff --git a/src/metric/cuda/cuda_pointwise_metric.cpp b/src/metric/cuda/cuda_pointwise_metric.cpp new file mode 100644 index 000000000000..aacd85e50e87 --- /dev/null +++ b/src/metric/cuda/cuda_pointwise_metric.cpp @@ -0,0 +1,38 @@ +/*! + * Copyright (c) 2022 Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for + * license information. + */ + +#ifdef USE_CUDA_EXP + +#include "cuda_binary_metric.hpp" +#include "cuda_pointwise_metric.hpp" +#include "cuda_regression_metric.hpp" + +namespace LightGBM { + +template +void CUDAPointwiseMetricInterface::Init(const Metadata& metadata, data_size_t num_data) { + CUDAMetricInterface::Init(metadata, num_data); + const int max_num_reduce_blocks = (this->num_data_ + NUM_DATA_PER_EVAL_THREAD - 1) / NUM_DATA_PER_EVAL_THREAD; + if (this->cuda_weights_ == nullptr) { + reduce_block_buffer_.Resize(max_num_reduce_blocks); + } else { + reduce_block_buffer_.Resize(max_num_reduce_blocks * 2); + } + const int max_num_reduce_blocks_inner = (max_num_reduce_blocks + NUM_DATA_PER_EVAL_THREAD - 1) / NUM_DATA_PER_EVAL_THREAD; + if (this->cuda_weights_ == nullptr) { + reduce_block_buffer_inner_.Resize(max_num_reduce_blocks_inner); + } else { + reduce_block_buffer_inner_.Resize(max_num_reduce_blocks_inner * 2); + } +} + +template void CUDAPointwiseMetricInterface::Init(const Metadata& metadata, data_size_t num_data); +template void CUDAPointwiseMetricInterface::Init(const Metadata& metadata, data_size_t num_data); +template void CUDAPointwiseMetricInterface::Init(const Metadata& metadata, data_size_t num_data); + +} // namespace LightGBM + +#endif // USE_CUDA_EXP diff --git a/src/metric/cuda/cuda_regression_metric.cu b/src/metric/cuda/cuda_pointwise_metric.cu similarity index 69% rename from src/metric/cuda/cuda_regression_metric.cu rename to src/metric/cuda/cuda_pointwise_metric.cu index e6f37d5cb131..4650eb2593cf 100644 --- a/src/metric/cuda/cuda_regression_metric.cu +++ b/src/metric/cuda/cuda_pointwise_metric.cu @@ -8,13 +8,15 @@ #include +#include "cuda_binary_metric.hpp" +#include "cuda_pointwise_metric.hpp" #include "cuda_regression_metric.hpp" namespace LightGBM { template __global__ void EvalKernel(const data_size_t num_data, const label_t* labels, const label_t* weights, - const double* scores, double* reduce_block_buffer) { + const double* scores, double* reduce_block_buffer) { __shared__ double shared_mem_buffer[32]; const data_size_t index = static_cast(threadIdx.x + blockIdx.x * blockDim.x); double point_metric = 0.0; @@ -40,7 +42,7 @@ __global__ void EvalKernel(const data_size_t num_data, const label_t* labels, co } template -double CUDARegressionMetricInterface::LaunchEvalKernel(const double* score) const { +void CUDAPointwiseMetricInterface::LaunchEvalKernel(const double* score, double* sum_loss, double* sum_weight) const { const int num_blocks = (this->num_data_ + NUM_DATA_PER_EVAL_THREAD - 1) / NUM_DATA_PER_EVAL_THREAD; if (this->cuda_weights_ != nullptr) { EvalKernel<<>>( @@ -50,18 +52,17 @@ double CUDARegressionMetricInterface::LaunchEvalKernel this->num_data_, this->cuda_labels_, this->cuda_weights_, score, reduce_block_buffer_.RawData()); } ShuffleReduceSumGlobal(reduce_block_buffer_.RawData(), num_blocks, reduce_block_buffer_inner_.RawData()); - double sum_loss = 0.0; - CopyFromCUDADeviceToHost(&sum_loss, reduce_block_buffer_inner_.RawData(), 1, __FILE__, __LINE__); - double sum_weight = static_cast(this->num_data_); + CopyFromCUDADeviceToHost(sum_loss, reduce_block_buffer_inner_.RawData(), 1, __FILE__, __LINE__); + *sum_weight = static_cast(this->num_data_); if (this->cuda_weights_ != nullptr) { ShuffleReduceSumGlobal(reduce_block_buffer_.RawData() + num_blocks, num_blocks, reduce_block_buffer_inner_.RawData()); - CopyFromCUDADeviceToHost(&sum_weight, reduce_block_buffer_inner_.RawData(), 1, __FILE__, __LINE__); + CopyFromCUDADeviceToHost(sum_weight, reduce_block_buffer_inner_.RawData(), 1, __FILE__, __LINE__); } - return this->AverageLoss(sum_loss, sum_weight); } -template double CUDARegressionMetricInterface::LaunchEvalKernel(const double* score) const; -template double CUDARegressionMetricInterface::LaunchEvalKernel(const double* score) const; +template void CUDAPointwiseMetricInterface::LaunchEvalKernel(const double* score, double* sum_loss, double* sum_weight) const; +template void CUDAPointwiseMetricInterface::LaunchEvalKernel(const double* score, double* sum_loss, double* sum_weight) const; +template void CUDAPointwiseMetricInterface::LaunchEvalKernel(const double* score, double* sum_loss, double* sum_weight) const; } // namespace LightGBM diff --git a/src/metric/cuda/cuda_pointwise_metric.hpp b/src/metric/cuda/cuda_pointwise_metric.hpp new file mode 100644 index 000000000000..4d635da5739e --- /dev/null +++ b/src/metric/cuda/cuda_pointwise_metric.hpp @@ -0,0 +1,43 @@ +/*! + * Copyright (c) 2022 Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE file in the project root for + * license information. + */ + +#ifndef LIGHTGBM_METRIC_CUDA_CUDA_POINTWISE_METRIC_HPP_ +#define LIGHTGBM_METRIC_CUDA_CUDA_POINTWISE_METRIC_HPP_ + +#ifdef USE_CUDA_EXP + +#include +#include + +#include + +#define NUM_DATA_PER_EVAL_THREAD (1024) + +namespace LightGBM { + +template +class CUDAPointwiseMetricInterface: public CUDAMetricInterface { + public: + explicit CUDAPointwiseMetricInterface(const Config& config): CUDAMetricInterface(config), num_class_(config.num_class) {} + + virtual ~CUDAPointwiseMetricInterface() {} + + void Init(const Metadata& metadata, data_size_t num_data) override; + + protected: + void LaunchEvalKernel(const double* score_convert, double* sum_loss, double* sum_weight) const; + + mutable CUDAVector score_convert_buffer_; + CUDAVector reduce_block_buffer_; + CUDAVector reduce_block_buffer_inner_; + const int num_class_; +}; + +} // namespace LightGBM + +#endif // USE_CUDA_EXP + +#endif // LIGHTGBM_METRIC_CUDA_CUDA_POINTWISE_METRIC_HPP_ diff --git a/src/metric/cuda/cuda_regression_metric.cpp b/src/metric/cuda/cuda_regression_metric.cpp index f8232b9d1f2e..15b219160a30 100644 --- a/src/metric/cuda/cuda_regression_metric.cpp +++ b/src/metric/cuda/cuda_regression_metric.cpp @@ -12,31 +12,16 @@ namespace LightGBM { -template -void CUDARegressionMetricInterface::Init(const Metadata& metadata, data_size_t num_data) { - CUDAMetricInterface::Init(metadata, num_data); - const int max_num_reduce_blocks = (this->num_data_ + NUM_DATA_PER_EVAL_THREAD - 1) / NUM_DATA_PER_EVAL_THREAD; - if (this->cuda_weights_ == nullptr) { - reduce_block_buffer_.Resize(max_num_reduce_blocks); - } else { - reduce_block_buffer_.Resize(max_num_reduce_blocks * 2); - } - const int max_num_reduce_blocks_inner = (max_num_reduce_blocks + NUM_DATA_PER_EVAL_THREAD - 1) / NUM_DATA_PER_EVAL_THREAD; - if (this->cuda_weights_ == nullptr) { - reduce_block_buffer_inner_.Resize(max_num_reduce_blocks_inner); - } else { - reduce_block_buffer_inner_.Resize(max_num_reduce_blocks_inner * 2); - } -} - template std::vector CUDARegressionMetricInterface::Eval(const double* score, const ObjectiveFunction* objective) const { const double* score_convert = score; if (objective != nullptr && objective->NeedConvertOutputCUDA()) { - score_convert_buffer_.Resize(static_cast(this->num_data_) * static_cast(this->num_class_)); - score_convert = objective->ConvertOutputCUDA(this->num_data_, score, score_convert_buffer_.RawData()); + this->score_convert_buffer_.Resize(static_cast(this->num_data_) * static_cast(this->num_class_)); + score_convert = objective->ConvertOutputCUDA(this->num_data_, score, this->score_convert_buffer_.RawData()); } - const double eval_score = LaunchEvalKernel(score_convert); + double sum_loss = 0.0, sum_weight = 0.0; + this->LaunchEvalKernel(score_convert, &sum_loss, &sum_weight); + const double eval_score = this->AverageLoss(sum_loss, sum_weight); return std::vector{eval_score}; } diff --git a/src/metric/cuda/cuda_regression_metric.hpp b/src/metric/cuda/cuda_regression_metric.hpp index aece087dc448..342e49542eb4 100644 --- a/src/metric/cuda/cuda_regression_metric.hpp +++ b/src/metric/cuda/cuda_regression_metric.hpp @@ -14,30 +14,20 @@ #include +#include "cuda_pointwise_metric.hpp" #include "../regression_metric.hpp" -#define NUM_DATA_PER_EVAL_THREAD (1024) - namespace LightGBM { template -class CUDARegressionMetricInterface: public CUDAMetricInterface { +class CUDARegressionMetricInterface: public CUDAPointwiseMetricInterface { public: - explicit CUDARegressionMetricInterface(const Config& config): CUDAMetricInterface(config), num_class_(config.num_class) {} + explicit CUDARegressionMetricInterface(const Config& config): + CUDAPointwiseMetricInterface(config) {} virtual ~CUDARegressionMetricInterface() {} - void Init(const Metadata& metadata, data_size_t num_data) override; - std::vector Eval(const double* score, const ObjectiveFunction* objective) const override; - - protected: - double LaunchEvalKernel(const double* score_convert) const; - - mutable CUDAVector score_convert_buffer_; - CUDAVector reduce_block_buffer_; - CUDAVector reduce_block_buffer_inner_; - const int num_class_; }; class CUDARMSEMetric: public CUDARegressionMetricInterface { diff --git a/src/metric/metric.cpp b/src/metric/metric.cpp index 9cbd72c76188..a393f1b2021a 100644 --- a/src/metric/metric.cpp +++ b/src/metric/metric.cpp @@ -11,13 +11,14 @@ #include "regression_metric.hpp" #include "xentropy_metric.hpp" +#include "cuda/cuda_binary_metric.hpp" #include "cuda/cuda_regression_metric.hpp" namespace LightGBM { Metric* Metric::CreateMetric(const std::string& type, const Config& config) { #ifdef USE_CUDA_EXP - if (config.device_type == std::string("cuda_exp")) { + if (config.device_type == std::string("cuda_exp") && config.boosting == std::string("gbdt")) { if (type == std::string("l2")) { return new CUDAL2Metric(config); } else if (type == std::string("rmse")) { @@ -38,8 +39,7 @@ Metric* Metric::CreateMetric(const std::string& type, const Config& config) { Log::Warning("Metric poisson is not implemented in cuda_exp version. Fall back to evaluation on CPU."); return new PoissonMetric(config); } else if (type == std::string("binary_logloss")) { - Log::Warning("Metric binary_logloss is not implemented in cuda_exp version. Fall back to evaluation on CPU."); - return new BinaryLoglossMetric(config); + return new CUDABinaryLoglossMetric(config); } else if (type == std::string("binary_error")) { Log::Warning("Metric binary_error is not implemented in cuda_exp version. Fall back to evaluation on CPU."); return new BinaryErrorMetric(config); diff --git a/src/objective/cuda/cuda_binary_objective.hpp b/src/objective/cuda/cuda_binary_objective.hpp index 9434e64602b2..77f58d8318f1 100644 --- a/src/objective/cuda/cuda_binary_objective.hpp +++ b/src/objective/cuda/cuda_binary_objective.hpp @@ -33,6 +33,8 @@ class CUDABinaryLogloss : public CUDAObjectiveInterface { void Init(const Metadata& metadata, data_size_t num_data) override; + bool NeedConvertOutputCUDA() const override { return true; } + private: void LaunchGetGradientsKernel(const double* scores, score_t* gradients, score_t* hessians) const override; From 46278af56d5c171230975028b84c1602f559b394 Mon Sep 17 00:00:00 2001 From: superlaut <55950563+superlaut@users.noreply.github.com> Date: Thu, 29 Dec 2022 09:18:19 +0100 Subject: [PATCH 20/25] [python-package] replace .values usage with .to_numpy() (#5612) --- python-package/lightgbm/basic.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/python-package/lightgbm/basic.py b/python-package/lightgbm/basic.py index 53d4a1b61172..7e83fab15f7b 100644 --- a/python-package/lightgbm/basic.py +++ b/python-package/lightgbm/basic.py @@ -602,7 +602,16 @@ def _data_from_pandas(data, feature_name, categorical_feature, pandas_categorica df_dtypes = [dtype.type for dtype in data.dtypes] df_dtypes.append(np.float32) # so that the target dtype considers floats target_dtype = np.find_common_type(df_dtypes, []) - data = data.astype(target_dtype, copy=False).values + try: + # most common case (no nullable dtypes) + data = data.to_numpy(dtype=target_dtype, copy=False) + except TypeError: + # 1.0 <= pd version < 1.1 and nullable dtypes, least common case + # raises error because array is casted to type(pd.NA) and there's no na_value argument + data = data.astype(target_dtype, copy=False).values + except ValueError: + # data has nullable dtypes, but we can specify na_value argument and copy will be made + data = data.to_numpy(dtype=target_dtype, na_value=np.nan) else: if feature_name == 'auto': feature_name = None @@ -2291,7 +2300,17 @@ def set_label(self, label: Optional[_LGBM_LabelType]) -> "Dataset": if len(label.columns) > 1: raise ValueError('DataFrame for label cannot have multiple columns') _check_for_bad_pandas_dtypes(label.dtypes) - label_array = np.ravel(label.values.astype(np.float32, copy=False)) + try: + # most common case (no nullable dtypes) + label = label.to_numpy(dtype=np.float32, copy=False) + except TypeError: + # 1.0 <= pd version < 1.1 and nullable dtypes, least common case + # raises error because array is casted to type(pd.NA) and there's no na_value argument + label = label.astype(np.float32, copy=False).values + except ValueError: + # data has nullable dtypes, but we can specify na_value argument and copy will be made + label = label.to_numpy(dtype=np.float32, na_value=np.nan) + label_array = np.ravel(label) else: label_array = _list_to_1d_numpy(label, name='label') self.set_field('label', label_array) From 3d33c756bd313f43ec55071b1954f6a63e75cc40 Mon Sep 17 00:00:00 2001 From: James Lamb Date: Thu, 29 Dec 2022 10:29:19 -0600 Subject: [PATCH 21/25] [ci] automatically cancel GitHub Actions runs for outdated commits (#5651) --- .github/workflows/cuda.yml | 5 +++++ .github/workflows/python_package.yml | 5 +++++ .github/workflows/r_package.yml | 5 +++++ .github/workflows/static_analysis.yml | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/.github/workflows/cuda.yml b/.github/workflows/cuda.yml index 50a3aa71dde8..946f548784a6 100644 --- a/.github/workflows/cuda.yml +++ b/.github/workflows/cuda.yml @@ -9,6 +9,11 @@ on: - master - release/* +# automatically cancel in-progress builds if another commit is pushed +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: github_actions: 'true' os_name: linux diff --git a/.github/workflows/python_package.yml b/.github/workflows/python_package.yml index edab1276921c..f046aecc1d9d 100644 --- a/.github/workflows/python_package.yml +++ b/.github/workflows/python_package.yml @@ -9,6 +9,11 @@ on: - master - release/* +# automatically cancel in-progress builds if another commit is pushed +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: CONDA_ENV: test-env GITHUB_ACTIONS: 'true' diff --git a/.github/workflows/r_package.yml b/.github/workflows/r_package.yml index ae4e44da7ed2..72d24eb9a39a 100644 --- a/.github/workflows/r_package.yml +++ b/.github/workflows/r_package.yml @@ -9,6 +9,11 @@ on: - master - release/* +# automatically cancel in-progress builds if another commit is pushed +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: # hack to get around this: # https://stat.ethz.ch/pipermail/r-package-devel/2020q3/005930.html diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 0419e4783030..415cbb66086a 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -11,6 +11,11 @@ on: - master - release/* +# automatically cancel in-progress builds if another commit is pushed +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: COMPILER: 'gcc' CONDA_ENV: test-env From 42b6322c7dddc89e5e2b479d7a499bb48ad0e358 Mon Sep 17 00:00:00 2001 From: James Lamb Date: Thu, 29 Dec 2022 12:23:39 -0600 Subject: [PATCH 22/25] [ci] speed up Windows jobs (fixes #5647) (#5648) --- .appveyor.yml | 2 ++ .ci/test_windows.ps1 | 33 +++++++++++++++++++++------------ .vsts-ci.yml | 2 ++ 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index ba85fac817ab..2d279b0f33e3 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -36,6 +36,8 @@ install: build: false test_script: + - conda config --remove channels defaults + - conda config --add channels nodefaults - conda config --add channels conda-forge - conda config --set channel_priority strict - conda init powershell diff --git a/.ci/test_windows.ps1 b/.ci/test_windows.ps1 index 6c0ffb8249f0..79b35faaff10 100644 --- a/.ci/test_windows.ps1 +++ b/.ci/test_windows.ps1 @@ -25,16 +25,6 @@ if ($env:TASK -eq "cpp-tests") { Exit 0 } -# setup for Python -conda init powershell -conda activate -conda config --set always_yes yes --set changeps1 no -conda update -q -y conda -conda create -q -y -n $env:CONDA_ENV "python=$env:PYTHON_VERSION[build=*cpython]" ; Check-Output $? -if ($env:TASK -ne "bdist") { - conda activate $env:CONDA_ENV -} - if ($env:TASK -eq "swig") { $env:JAVA_HOME = $env:JAVA_HOME_8_X64 # there is pre-installed Eclipse Temurin 8 somewhere $ProgressPreference = "SilentlyContinue" # progress bar bug extremely slows down download speed @@ -50,8 +40,27 @@ if ($env:TASK -eq "swig") { Exit 0 } -# re-including python=version[build=*cpython] to ensure that conda doesn't fall back to pypy -conda install -q -y -n $env:CONDA_ENV cloudpickle joblib matplotlib numpy pandas psutil pytest "python=$env:PYTHON_VERSION[build=*cpython]" python-graphviz scikit-learn scipy ; Check-Output $? +# setup for Python +conda init powershell +conda activate +conda config --set always_yes yes --set changeps1 no +conda update -q -y conda +conda create -q -y -n $env:CONDA_ENV ` + cloudpickle ` + joblib ` + matplotlib ` + numpy ` + pandas ` + psutil ` + pytest ` + "python=$env:PYTHON_VERSION[build=*cpython]" ` + python-graphviz ` + scikit-learn ` + scipy ; Check-Output $? + +if ($env:TASK -ne "bdist") { + conda activate $env:CONDA_ENV +} if ($env:TASK -eq "regular") { mkdir $env:BUILD_SOURCESDIRECTORY/build; cd $env:BUILD_SOURCESDIRECTORY/build diff --git a/.vsts-ci.yml b/.vsts-ci.yml index 61012d73aaea..194aa5471131 100644 --- a/.vsts-ci.yml +++ b/.vsts-ci.yml @@ -283,6 +283,8 @@ jobs: condition: eq(variables['TASK'], 'bdist') displayName: 'Install OpenCL' - script: | + cmd /c "conda config --remove channels defaults" + cmd /c "conda config --add channels nodefaults" cmd /c "conda config --add channels conda-forge" cmd /c "conda config --set channel_priority strict" cmd /c "conda init powershell" From 51edbda7869e815c9390dc9a7550583ca95f866b Mon Sep 17 00:00:00 2001 From: James Lamb Date: Thu, 29 Dec 2022 12:46:57 -0600 Subject: [PATCH 23/25] fix feature index in Dataset::AddFeaturesFrom (fixes #5410) (#5650) --- src/io/dataset.cpp | 2 +- tests/python_package_test/test_basic.py | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/io/dataset.cpp b/src/io/dataset.cpp index 2842551cf2ee..a237e79b2680 100644 --- a/src/io/dataset.cpp +++ b/src/io/dataset.cpp @@ -1495,7 +1495,7 @@ void Dataset::AddFeaturesFrom(Dataset* other) { other->max_bin_by_feature_, other->num_total_features_, -1); num_total_features_ += other->num_total_features_; for (size_t i = 0; i < (other->numeric_feature_map_).size(); ++i) { - int feat_ind = numeric_feature_map_[i]; + int feat_ind = other->numeric_feature_map_[i]; if (feat_ind > -1) { numeric_feature_map_.push_back(feat_ind + num_numeric_features_); } else { diff --git a/tests/python_package_test/test_basic.py b/tests/python_package_test/test_basic.py index bd047557dffb..f3fc65d3e4c1 100644 --- a/tests/python_package_test/test_basic.py +++ b/tests/python_package_test/test_basic.py @@ -376,6 +376,29 @@ def test_add_features_from_different_sources(): assert d1.feature_name == res_feature_names +def test_add_features_does_not_fail_if_initial_dataset_has_zero_informative_features(capsys): + + arr_a = np.zeros((100, 1), dtype=np.float32) + arr_b = np.random.normal(size=(100, 5)) + + dataset_a = lgb.Dataset(arr_a).construct() + expected_msg = ( + '[LightGBM] [Warning] There are no meaningful features which satisfy ' + 'the provided configuration. Decreasing Dataset parameters min_data_in_bin ' + 'or min_data_in_leaf and re-constructing Dataset might resolve this warning.\n' + ) + log_lines = capsys.readouterr().out + assert expected_msg in log_lines + + dataset_b = lgb.Dataset(arr_b).construct() + + original_handle = dataset_a.handle.value + dataset_a.add_features_from(dataset_b) + assert dataset_a.num_feature() == 6 + assert dataset_a.num_data() == 100 + assert dataset_a.handle.value == original_handle + + def test_cegb_affects_behavior(tmp_path): X = np.random.random((100, 5)) X[:, [1, 3]] = 0 From f84bfcf9ae5f978c5742a20a744dfdd5d991f401 Mon Sep 17 00:00:00 2001 From: Belinda Trotta Date: Fri, 30 Dec 2022 17:35:16 +1100 Subject: [PATCH 24/25] Check feature indexes in forced split file (fixes #5517) (#5653) --- src/boosting/gbdt.cpp | 24 ++++++++++++++++++++++++ src/boosting/gbdt.h | 5 +++++ tests/python_package_test/test_engine.py | 19 +++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/src/boosting/gbdt.cpp b/src/boosting/gbdt.cpp index f7ac445131dd..72e16ee7e707 100644 --- a/src/boosting/gbdt.cpp +++ b/src/boosting/gbdt.cpp @@ -14,6 +14,7 @@ #include #include +#include #include namespace LightGBM { @@ -138,6 +139,9 @@ void GBDT::Init(const Config* config, const Dataset* train_data, const Objective // get parser config file content parser_config_str_ = train_data_->parser_config_str(); + // check that forced splits does not use feature indices larger than dataset size + CheckForcedSplitFeatures(); + // if need bagging, create buffer data_sample_strategy_->ResetSampleConfig(config_.get(), true); ResetGradientBuffers(); @@ -155,6 +159,26 @@ void GBDT::Init(const Config* config, const Dataset* train_data, const Objective } } +void GBDT::CheckForcedSplitFeatures() { + std::queue forced_split_nodes; + forced_split_nodes.push(forced_splits_json_); + while (!forced_split_nodes.empty()) { + Json node = forced_split_nodes.front(); + forced_split_nodes.pop(); + const int feature_index = node["feature"].int_value(); + if (feature_index > max_feature_idx_) { + Log::Fatal("Forced splits file includes feature index %d, but maximum feature index in dataset is %d", + feature_index, max_feature_idx_); + } + if (node.object_items().count("left") > 0) { + forced_split_nodes.push(node["left"]); + } + if (node.object_items().count("right") > 0) { + forced_split_nodes.push(node["right"]); + } + } +} + void GBDT::AddValidDataset(const Dataset* valid_data, const std::vector& valid_metrics) { if (!train_data_->CheckAlign(*valid_data)) { diff --git a/src/boosting/gbdt.h b/src/boosting/gbdt.h index e1749bd94749..d71245980b36 100644 --- a/src/boosting/gbdt.h +++ b/src/boosting/gbdt.h @@ -58,6 +58,11 @@ class GBDT : public GBDTBase { const ObjectiveFunction* objective_function, const std::vector& training_metrics) override; + /*! + * \brief Traverse the tree of forced splits and check that all indices are less than the number of features. + */ + void CheckForcedSplitFeatures(); + /*! * \brief Merge model from other boosting object. Will insert to the front of current boosting object * \param other diff --git a/tests/python_package_test/test_engine.py b/tests/python_package_test/test_engine.py index f6ddcbb72ed8..93e0b4f648ba 100644 --- a/tests/python_package_test/test_engine.py +++ b/tests/python_package_test/test_engine.py @@ -2887,6 +2887,25 @@ def test_node_level_subcol(): assert ret != ret2 +def test_forced_split_feature_indices(tmp_path): + X, y = make_synthetic_regression() + forced_split = { + "feature": 0, + "threshold": 0.5, + "left": {"feature": X.shape[1], "threshold": 0.5}, + } + tmp_split_file = tmp_path / "forced_split.json" + with open(tmp_split_file, "w") as f: + f.write(json.dumps(forced_split)) + lgb_train = lgb.Dataset(X, y) + params = { + "objective": "regression", + "forcedsplits_filename": tmp_split_file + } + with pytest.raises(lgb.basic.LightGBMError, match="Forced splits file includes feature index"): + bst = lgb.train(params, lgb_train) + + def test_forced_bins(): x = np.empty((100, 2)) x[:, 0] = np.arange(0, 1, 0.01) From 80f5666c69eb8ce216989dcf9a568cb4c4a2c706 Mon Sep 17 00:00:00 2001 From: James Lamb Date: Fri, 30 Dec 2022 13:51:00 -0600 Subject: [PATCH 25/25] [python-package] prefix internal objects with '_' (#5654) --- python-package/lightgbm/basic.py | 162 ++++++++++++++++--------------- 1 file changed, 83 insertions(+), 79 deletions(-) diff --git a/python-package/lightgbm/basic.py b/python-package/lightgbm/basic.py index 7e83fab15f7b..10e4710bcd4d 100644 --- a/python-package/lightgbm/basic.py +++ b/python-package/lightgbm/basic.py @@ -304,7 +304,7 @@ def _c_array(ctype: type, values: List[Any]) -> ctypes.Array: return (ctype * len(values))(*values) -def json_default_with_numpy(obj: Any) -> Any: +def _json_default_with_numpy(obj: Any) -> Any: """Convert numpy classes to JSON serializable objects.""" if isinstance(obj, (np.integer, np.floating, np.bool_)): return obj.item() @@ -314,7 +314,7 @@ def json_default_with_numpy(obj: Any) -> Any: return obj -def param_dict_to_str(data: Optional[Dict[str, Any]]) -> str: +def _param_dict_to_str(data: Optional[Dict[str, Any]]) -> str: """Convert Python dictionary to string, which is passed to C API.""" if data is None or not data: return "" @@ -465,40 +465,44 @@ def _choose_param_value(main_param_name: str, params: Dict[str, Any], default_va return params -MAX_INT32 = (1 << 31) - 1 +_MAX_INT32 = (1 << 31) - 1 """Macro definition of data type in C API of LightGBM""" -C_API_DTYPE_FLOAT32 = 0 -C_API_DTYPE_FLOAT64 = 1 -C_API_DTYPE_INT32 = 2 -C_API_DTYPE_INT64 = 3 +_C_API_DTYPE_FLOAT32 = 0 +_C_API_DTYPE_FLOAT64 = 1 +_C_API_DTYPE_INT32 = 2 +_C_API_DTYPE_INT64 = 3 """Matrix is row major in Python""" -C_API_IS_ROW_MAJOR = 1 +_C_API_IS_ROW_MAJOR = 1 """Macro definition of prediction type in C API of LightGBM""" -C_API_PREDICT_NORMAL = 0 -C_API_PREDICT_RAW_SCORE = 1 -C_API_PREDICT_LEAF_INDEX = 2 -C_API_PREDICT_CONTRIB = 3 +_C_API_PREDICT_NORMAL = 0 +_C_API_PREDICT_RAW_SCORE = 1 +_C_API_PREDICT_LEAF_INDEX = 2 +_C_API_PREDICT_CONTRIB = 3 """Macro definition of sparse matrix type""" -C_API_MATRIX_TYPE_CSR = 0 -C_API_MATRIX_TYPE_CSC = 1 +_C_API_MATRIX_TYPE_CSR = 0 +_C_API_MATRIX_TYPE_CSC = 1 """Macro definition of feature importance type""" -C_API_FEATURE_IMPORTANCE_SPLIT = 0 -C_API_FEATURE_IMPORTANCE_GAIN = 1 +_C_API_FEATURE_IMPORTANCE_SPLIT = 0 +_C_API_FEATURE_IMPORTANCE_GAIN = 1 """Data type of data field""" -FIELD_TYPE_MAPPER = {"label": C_API_DTYPE_FLOAT32, - "weight": C_API_DTYPE_FLOAT32, - "init_score": C_API_DTYPE_FLOAT64, - "group": C_API_DTYPE_INT32} +_FIELD_TYPE_MAPPER = { + "label": _C_API_DTYPE_FLOAT32, + "weight": _C_API_DTYPE_FLOAT32, + "init_score": _C_API_DTYPE_FLOAT64, + "group": _C_API_DTYPE_INT32 +} """String name to int feature importance type mapper""" -FEATURE_IMPORTANCE_TYPE_MAPPER = {"split": C_API_FEATURE_IMPORTANCE_SPLIT, - "gain": C_API_FEATURE_IMPORTANCE_GAIN} +_FEATURE_IMPORTANCE_TYPE_MAPPER = { + "split": _C_API_FEATURE_IMPORTANCE_SPLIT, + "gain": _C_API_FEATURE_IMPORTANCE_GAIN +} def _convert_from_sliced_object(data): @@ -520,10 +524,10 @@ def _c_float_array(data): assert data.flags.c_contiguous if data.dtype == np.float32: ptr_data = data.ctypes.data_as(ctypes.POINTER(ctypes.c_float)) - type_data = C_API_DTYPE_FLOAT32 + type_data = _C_API_DTYPE_FLOAT32 elif data.dtype == np.float64: ptr_data = data.ctypes.data_as(ctypes.POINTER(ctypes.c_double)) - type_data = C_API_DTYPE_FLOAT64 + type_data = _C_API_DTYPE_FLOAT64 else: raise TypeError(f"Expected np.float32 or np.float64, met type({data.dtype})") else: @@ -540,10 +544,10 @@ def _c_int_array(data): assert data.flags.c_contiguous if data.dtype == np.int32: ptr_data = data.ctypes.data_as(ctypes.POINTER(ctypes.c_int32)) - type_data = C_API_DTYPE_INT32 + type_data = _C_API_DTYPE_INT32 elif data.dtype == np.int64: ptr_data = data.ctypes.data_as(ctypes.POINTER(ctypes.c_int64)) - type_data = C_API_DTYPE_INT64 + type_data = _C_API_DTYPE_INT64 else: raise TypeError(f"Expected np.int32 or np.int64, met type({data.dtype})") else: @@ -621,7 +625,7 @@ def _data_from_pandas(data, feature_name, categorical_feature, pandas_categorica def _dump_pandas_categorical(pandas_categorical, file_name=None): - categorical_json = json.dumps(pandas_categorical, default=json_default_with_numpy) + categorical_json = json.dumps(pandas_categorical, default=_json_default_with_numpy) pandas_str = f'\npandas_categorical:{categorical_json}\n' if file_name is not None: with open(file_name, 'a') as f: @@ -782,7 +786,7 @@ def __init__( raise TypeError('Need model_file or booster_handle to create a predictor') pred_parameter = {} if pred_parameter is None else pred_parameter - self.pred_parameter = param_dict_to_str(pred_parameter) + self.pred_parameter = _param_dict_to_str(pred_parameter) def __del__(self) -> None: try: @@ -851,13 +855,13 @@ def predict( ) ) data = _data_from_pandas(data, None, None, self.pandas_categorical)[0] - predict_type = C_API_PREDICT_NORMAL + predict_type = _C_API_PREDICT_NORMAL if raw_score: - predict_type = C_API_PREDICT_RAW_SCORE + predict_type = _C_API_PREDICT_RAW_SCORE if pred_leaf: - predict_type = C_API_PREDICT_LEAF_INDEX + predict_type = _C_API_PREDICT_LEAF_INDEX if pred_contrib: - predict_type = C_API_PREDICT_CONTRIB + predict_type = _C_API_PREDICT_CONTRIB int_data_has_header = 1 if data_has_header else 0 if isinstance(data, (str, Path)): @@ -906,9 +910,9 @@ def predict( def __get_num_preds(self, start_iteration, num_iteration, nrow, predict_type): """Get size of prediction result.""" - if nrow > MAX_INT32: + if nrow > _MAX_INT32: raise LightGBMError('LightGBM cannot perform prediction for data ' - f'with number of rows greater than MAX_INT32 ({MAX_INT32}).\n' + f'with number of rows greater than MAX_INT32 ({_MAX_INT32}).\n' 'You can split your data into chunks ' 'and then concatenate predictions for them') n_preds = ctypes.c_int64(0) @@ -944,7 +948,7 @@ def inner_predict(mat, start_iteration, num_iteration, predict_type, preds=None) ctypes.c_int(type_ptr_data), ctypes.c_int32(mat.shape[0]), ctypes.c_int32(mat.shape[1]), - ctypes.c_int(C_API_IS_ROW_MAJOR), + ctypes.c_int(_C_API_IS_ROW_MAJOR), ctypes.c_int(predict_type), ctypes.c_int(start_iteration), ctypes.c_int(num_iteration), @@ -956,8 +960,8 @@ def inner_predict(mat, start_iteration, num_iteration, predict_type, preds=None) return preds, mat.shape[0] nrow = mat.shape[0] - if nrow > MAX_INT32: - sections = np.arange(start=MAX_INT32, stop=nrow, step=MAX_INT32) + if nrow > _MAX_INT32: + sections = np.arange(start=_MAX_INT32, stop=nrow, step=_MAX_INT32) # __get_num_preds() cannot work with nrow > MAX_INT32, so calculate overall number of predictions piecemeal n_preds = [self.__get_num_preds(start_iteration, num_iteration, i, predict_type) for i in np.diff([0] + list(sections) + [nrow])] n_preds_sections = np.array([0] + n_preds, dtype=np.intp).cumsum() @@ -975,15 +979,15 @@ def __create_sparse_native(self, cs, out_shape, out_ptr_indptr, out_ptr_indices, # create numpy array from output arrays data_indices_len = out_shape[0] indptr_len = out_shape[1] - if indptr_type == C_API_DTYPE_INT32: + if indptr_type == _C_API_DTYPE_INT32: out_indptr = _cint32_array_to_numpy(out_ptr_indptr, indptr_len) - elif indptr_type == C_API_DTYPE_INT64: + elif indptr_type == _C_API_DTYPE_INT64: out_indptr = _cint64_array_to_numpy(out_ptr_indptr, indptr_len) else: raise TypeError("Expected int32 or int64 type for indptr") - if data_type == C_API_DTYPE_FLOAT32: + if data_type == _C_API_DTYPE_FLOAT32: out_data = _cfloat32_array_to_numpy(out_ptr_data, data_indices_len) - elif data_type == C_API_DTYPE_FLOAT64: + elif data_type == _C_API_DTYPE_FLOAT64: out_data = _cfloat64_array_to_numpy(out_ptr_data, data_indices_len) else: raise TypeError("Expected float32 or float64 type for data") @@ -1030,7 +1034,7 @@ def inner_predict(csr, start_iteration, num_iteration, predict_type, preds=None) ptr_indptr, type_ptr_indptr, __ = _c_int_array(csr.indptr) ptr_data, type_ptr_data, _ = _c_float_array(csr.data) - assert csr.shape[1] <= MAX_INT32 + assert csr.shape[1] <= _MAX_INT32 csr_indices = csr.indices.astype(np.int32, copy=False) _safe_call(_LIB.LGBM_BoosterPredictForCSR( @@ -1057,13 +1061,13 @@ def inner_predict_sparse(csr, start_iteration, num_iteration, predict_type): ptr_indptr, type_ptr_indptr, __ = _c_int_array(csr.indptr) ptr_data, type_ptr_data, _ = _c_float_array(csr.data) csr_indices = csr.indices.astype(np.int32, copy=False) - matrix_type = C_API_MATRIX_TYPE_CSR - if type_ptr_indptr == C_API_DTYPE_INT32: + matrix_type = _C_API_MATRIX_TYPE_CSR + if type_ptr_indptr == _C_API_DTYPE_INT32: out_ptr_indptr = ctypes.POINTER(ctypes.c_int32)() else: out_ptr_indptr = ctypes.POINTER(ctypes.c_int64)() out_ptr_indices = ctypes.POINTER(ctypes.c_int32)() - if type_ptr_data == C_API_DTYPE_FLOAT32: + if type_ptr_data == _C_API_DTYPE_FLOAT32: out_ptr_data = ctypes.POINTER(ctypes.c_float)() else: out_ptr_data = ctypes.POINTER(ctypes.c_double)() @@ -1092,11 +1096,11 @@ def inner_predict_sparse(csr, start_iteration, num_iteration, predict_type): nrow = len(csr.indptr) - 1 return matrices, nrow - if predict_type == C_API_PREDICT_CONTRIB: + if predict_type == _C_API_PREDICT_CONTRIB: return inner_predict_sparse(csr, start_iteration, num_iteration, predict_type) nrow = len(csr.indptr) - 1 - if nrow > MAX_INT32: - sections = [0] + list(np.arange(start=MAX_INT32, stop=nrow, step=MAX_INT32)) + [nrow] + if nrow > _MAX_INT32: + sections = [0] + list(np.arange(start=_MAX_INT32, stop=nrow, step=_MAX_INT32)) + [nrow] # __get_num_preds() cannot work with nrow > MAX_INT32, so calculate overall number of predictions piecemeal n_preds = [self.__get_num_preds(start_iteration, num_iteration, i, predict_type) for i in np.diff(sections)] n_preds_sections = np.array([0] + n_preds, dtype=np.intp).cumsum() @@ -1115,13 +1119,13 @@ def inner_predict_sparse(csc, start_iteration, num_iteration, predict_type): ptr_indptr, type_ptr_indptr, __ = _c_int_array(csc.indptr) ptr_data, type_ptr_data, _ = _c_float_array(csc.data) csc_indices = csc.indices.astype(np.int32, copy=False) - matrix_type = C_API_MATRIX_TYPE_CSC - if type_ptr_indptr == C_API_DTYPE_INT32: + matrix_type = _C_API_MATRIX_TYPE_CSC + if type_ptr_indptr == _C_API_DTYPE_INT32: out_ptr_indptr = ctypes.POINTER(ctypes.c_int32)() else: out_ptr_indptr = ctypes.POINTER(ctypes.c_int64)() out_ptr_indices = ctypes.POINTER(ctypes.c_int32)() - if type_ptr_data == C_API_DTYPE_FLOAT32: + if type_ptr_data == _C_API_DTYPE_FLOAT32: out_ptr_data = ctypes.POINTER(ctypes.c_float)() else: out_ptr_data = ctypes.POINTER(ctypes.c_double)() @@ -1151,9 +1155,9 @@ def inner_predict_sparse(csc, start_iteration, num_iteration, predict_type): return matrices, nrow nrow = csc.shape[0] - if nrow > MAX_INT32: + if nrow > _MAX_INT32: return self.__pred_for_csr(csc.tocsr(), start_iteration, num_iteration, predict_type) - if predict_type == C_API_PREDICT_CONTRIB: + if predict_type == _C_API_PREDICT_CONTRIB: return inner_predict_sparse(csc, start_iteration, num_iteration, predict_type) n_preds = self.__get_num_preds(start_iteration, num_iteration, nrow, predict_type) preds = np.empty(n_preds, dtype=np.float64) @@ -1162,7 +1166,7 @@ def inner_predict_sparse(csc, start_iteration, num_iteration, predict_type): ptr_indptr, type_ptr_indptr, __ = _c_int_array(csc.indptr) ptr_data, type_ptr_data, _ = _c_float_array(csc.data) - assert csc.shape[0] <= MAX_INT32 + assert csc.shape[0] <= _MAX_INT32 csc_indices = csc.indices.astype(np.int32, copy=False) _safe_call(_LIB.LGBM_BoosterPredictForCSC( @@ -1299,7 +1303,7 @@ def _create_sample_indices(self, total_nrow: int) -> np.ndarray: indices : numpy array Indices for sampled data. """ - param_str = param_dict_to_str(self.get_params()) + param_str = _param_dict_to_str(self.get_params()) sample_cnt = _get_sample_count(total_nrow, param_str) indices = np.empty(sample_cnt, dtype=np.int32) ptr_data, _, _ = _c_int_array(indices) @@ -1389,7 +1393,7 @@ def _init_from_sample( num_per_col_ptr, _, _ = _c_int_array(num_per_col) self.handle = ctypes.c_void_p() - params_str = param_dict_to_str(self.get_params()) + params_str = _param_dict_to_str(self.get_params()) _safe_call(_LIB.LGBM_DatasetCreateFromSampledColumn( ctypes.cast(sample_col_ptr, ctypes.POINTER(ctypes.POINTER(ctypes.c_double))), ctypes.cast(indices_col_ptr, ctypes.POINTER(ctypes.POINTER(ctypes.c_int32))), @@ -1563,7 +1567,7 @@ def _lazy_init( params.pop(cat_alias, None) params['categorical_column'] = sorted(categorical_indices) - params_str = param_dict_to_str(params) + params_str = _param_dict_to_str(params) self.params = params # process for reference dataset ref_dataset = None @@ -1683,7 +1687,7 @@ def __init_from_seqs( if ref_dataset is not None: self._init_from_ref_dataset(total_nrow, ref_dataset) else: - param_str = param_dict_to_str(self.get_params()) + param_str = _param_dict_to_str(self.get_params()) sample_cnt = _get_sample_count(total_nrow, param_str) sample_data, col_indices = self.__sample(seqs, total_nrow) @@ -1719,7 +1723,7 @@ def __init_from_np2d( ctypes.c_int(type_ptr_data), ctypes.c_int32(mat.shape[0]), ctypes.c_int32(mat.shape[1]), - ctypes.c_int(C_API_IS_ROW_MAJOR), + ctypes.c_int(_C_API_IS_ROW_MAJOR), _c_str(params_str), ref_dataset, ctypes.byref(self.handle))) @@ -1770,7 +1774,7 @@ def __init_from_list_np2d( ctypes.c_int(type_ptr_data), nrow.ctypes.data_as(ctypes.POINTER(ctypes.c_int32)), ctypes.c_int32(ncol), - ctypes.c_int(C_API_IS_ROW_MAJOR), + ctypes.c_int(_C_API_IS_ROW_MAJOR), _c_str(params_str), ref_dataset, ctypes.byref(self.handle))) @@ -1790,7 +1794,7 @@ def __init_from_csr( ptr_indptr, type_ptr_indptr, __ = _c_int_array(csr.indptr) ptr_data, type_ptr_data, _ = _c_float_array(csr.data) - assert csr.shape[1] <= MAX_INT32 + assert csr.shape[1] <= _MAX_INT32 csr_indices = csr.indices.astype(np.int32, copy=False) _safe_call(_LIB.LGBM_DatasetCreateFromCSR( @@ -1821,7 +1825,7 @@ def __init_from_csc( ptr_indptr, type_ptr_indptr, __ = _c_int_array(csc.indptr) ptr_data, type_ptr_data, _ = _c_float_array(csc.data) - assert csc.shape[0] <= MAX_INT32 + assert csc.shape[0] <= _MAX_INT32 csc_indices = csc.indices.astype(np.int32, copy=False) _safe_call(_LIB.LGBM_DatasetCreateFromCSC( @@ -1911,7 +1915,7 @@ def construct(self) -> "Dataset": _, self.group = np.unique(np.repeat(range(len(group_info)), repeats=group_info)[self.used_indices], return_counts=True) self.handle = ctypes.c_void_p() - params_str = param_dict_to_str(self.params) + params_str = _param_dict_to_str(self.params) _safe_call(_LIB.LGBM_DatasetGetSubset( self.reference.construct().handle, used_indices.ctypes.data_as(ctypes.POINTER(ctypes.c_int32)), @@ -2049,8 +2053,8 @@ def update(): update() elif params is not None: ret = _LIB.LGBM_DatasetUpdateParamChecking( - _c_str(param_dict_to_str(self.params)), - _c_str(param_dict_to_str(params))) + _c_str(_param_dict_to_str(self.params)), + _c_str(_param_dict_to_str(params))) if ret != 0: # could be updated if data is not freed if self.data is not None: @@ -2094,7 +2098,7 @@ def set_field( _c_str(field_name), None, ctypes.c_int(0), - ctypes.c_int(FIELD_TYPE_MAPPER[field_name]))) + ctypes.c_int(_FIELD_TYPE_MAPPER[field_name]))) return self if field_name == 'init_score': dtype = np.float64 @@ -2118,7 +2122,7 @@ def set_field( ptr_data, type_data, _ = _c_int_array(data) else: raise TypeError(f"Expected np.float32/64 or np.int32, met type({data.dtype})") - if type_data != FIELD_TYPE_MAPPER[field_name]: + if type_data != _FIELD_TYPE_MAPPER[field_name]: raise TypeError("Input type error for set_field") _safe_call(_LIB.LGBM_DatasetSetField( self.handle, @@ -2153,15 +2157,15 @@ def get_field(self, field_name: str) -> Optional[np.ndarray]: ctypes.byref(tmp_out_len), ctypes.byref(ret), ctypes.byref(out_type))) - if out_type.value != FIELD_TYPE_MAPPER[field_name]: + if out_type.value != _FIELD_TYPE_MAPPER[field_name]: raise TypeError("Return type error for get_field") if tmp_out_len.value == 0: return None - if out_type.value == C_API_DTYPE_INT32: + if out_type.value == _C_API_DTYPE_INT32: arr = _cint32_array_to_numpy(ctypes.cast(ret, ctypes.POINTER(ctypes.c_int32)), tmp_out_len.value) - elif out_type.value == C_API_DTYPE_FLOAT32: + elif out_type.value == _C_API_DTYPE_FLOAT32: arr = _cfloat32_array_to_numpy(ctypes.cast(ret, ctypes.POINTER(ctypes.c_float)), tmp_out_len.value) - elif out_type.value == C_API_DTYPE_FLOAT64: + elif out_type.value == _C_API_DTYPE_FLOAT64: arr = _cfloat64_array_to_numpy(ctypes.cast(ret, ctypes.POINTER(ctypes.c_double)), tmp_out_len.value) else: raise TypeError("Unknown type") @@ -2794,7 +2798,7 @@ def __init__( train_set.construct() # copy the parameters from train_set params.update(train_set.get_params()) - params_str = param_dict_to_str(params) + params_str = _param_dict_to_str(params) self.handle = ctypes.c_void_p() _safe_call(_LIB.LGBM_BoosterCreate( train_set.handle, @@ -3167,7 +3171,7 @@ def reset_parameter(self, params: Dict[str, Any]) -> "Booster": self : Booster Booster with new parameters. """ - params_str = param_dict_to_str(params) + params_str = _param_dict_to_str(params) if params_str: _safe_call(_LIB.LGBM_BoosterResetParameter( self.handle, @@ -3537,7 +3541,7 @@ def save_model( """ if num_iteration is None: num_iteration = self.best_iteration - importance_type_int = FEATURE_IMPORTANCE_TYPE_MAPPER[importance_type] + importance_type_int = _FEATURE_IMPORTANCE_TYPE_MAPPER[importance_type] _safe_call(_LIB.LGBM_BoosterSaveModel( self.handle, ctypes.c_int(start_iteration), @@ -3631,7 +3635,7 @@ def model_to_string( """ if num_iteration is None: num_iteration = self.best_iteration - importance_type_int = FEATURE_IMPORTANCE_TYPE_MAPPER[importance_type] + importance_type_int = _FEATURE_IMPORTANCE_TYPE_MAPPER[importance_type] buffer_len = 1 << 20 tmp_out_len = ctypes.c_int64(0) string_buffer = ctypes.create_string_buffer(buffer_len) @@ -3699,7 +3703,7 @@ def dump_model( """ if num_iteration is None: num_iteration = self.best_iteration - importance_type_int = FEATURE_IMPORTANCE_TYPE_MAPPER[importance_type] + importance_type_int = _FEATURE_IMPORTANCE_TYPE_MAPPER[importance_type] buffer_len = 1 << 20 tmp_out_len = ctypes.c_int64(0) string_buffer = ctypes.create_string_buffer(buffer_len) @@ -3727,7 +3731,7 @@ def dump_model( ptr_string_buffer)) ret = json.loads(string_buffer.value.decode('utf-8'), object_hook=object_hook) ret['pandas_categorical'] = json.loads(json.dumps(self.pandas_categorical, - default=json_default_with_numpy)) + default=_json_default_with_numpy)) return ret def predict( @@ -4021,14 +4025,14 @@ def feature_importance( """ if iteration is None: iteration = self.best_iteration - importance_type_int = FEATURE_IMPORTANCE_TYPE_MAPPER[importance_type] + importance_type_int = _FEATURE_IMPORTANCE_TYPE_MAPPER[importance_type] result = np.empty(self.num_feature(), dtype=np.float64) _safe_call(_LIB.LGBM_BoosterFeatureImportance( self.handle, ctypes.c_int(iteration), ctypes.c_int(importance_type_int), result.ctypes.data_as(ctypes.POINTER(ctypes.c_double)))) - if importance_type_int == C_API_FEATURE_IMPORTANCE_SPLIT: + if importance_type_int == _C_API_FEATURE_IMPORTANCE_SPLIT: return result.astype(np.int32) else: return result