357 lines
13 KiB
C++
357 lines
13 KiB
C++
/*!
|
|
* Copyright (c) 2022-2026 Microsoft Corporation. All rights reserved.
|
|
* Copyright (c) 2022-2026 The LightGBM developers. All rights reserved.
|
|
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
|
*/
|
|
|
|
#include <gtest/gtest.h>
|
|
#include <testutils.h>
|
|
#include <LightGBM/utils/log.h>
|
|
#include <LightGBM/c_api.h>
|
|
#include <LightGBM/dataset.h>
|
|
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
using LightGBM::Dataset;
|
|
using LightGBM::Log;
|
|
using LightGBM::TestUtils;
|
|
|
|
void test_stream_dense(
|
|
int8_t creation_type,
|
|
DatasetHandle ref_dataset_handle,
|
|
int32_t nrows,
|
|
int32_t ncols,
|
|
int32_t nclasses,
|
|
int batch_count,
|
|
const std::vector<double>* features,
|
|
const std::vector<float>* labels,
|
|
const std::vector<float>* weights,
|
|
const std::vector<double>* init_scores,
|
|
const std::vector<int32_t>* groups) {
|
|
Log::Info("Streaming %d rows dense data with a batch size of %d", nrows, batch_count);
|
|
DatasetHandle dataset_handle = nullptr;
|
|
Dataset* dataset = nullptr;
|
|
|
|
int has_weights = weights != nullptr;
|
|
int has_init_scores = init_scores != nullptr;
|
|
int has_queries = groups != nullptr;
|
|
|
|
bool succeeded = true;
|
|
std::string exceptionText("");
|
|
|
|
try {
|
|
int result = 0;
|
|
switch (creation_type) {
|
|
case 0: {
|
|
Log::Info("Creating Dataset using LGBM_DatasetCreateFromSampledColumn, %d rows dense data with a batch size of %d", nrows, batch_count);
|
|
|
|
// construct sample data first (use all data for convenience and since size is small)
|
|
std::vector<std::vector<double>> sample_values(ncols);
|
|
std::vector<std::vector<int>> sample_idx(ncols);
|
|
const double* current_val = features->data();
|
|
for (int32_t idx = 0; idx < nrows; ++idx) {
|
|
for (int32_t k = 0; k < ncols; ++k) {
|
|
if (std::fabs(*current_val) > 1e-35f || std::isnan(*current_val)) {
|
|
sample_values[k].emplace_back(*current_val);
|
|
sample_idx[k].emplace_back(static_cast<int>(idx));
|
|
}
|
|
current_val++;
|
|
}
|
|
}
|
|
|
|
std::vector<int> sample_sizes;
|
|
std::vector<double*> sample_values_ptrs;
|
|
std::vector<int*> sample_idx_ptrs;
|
|
for (int32_t i = 0; i < ncols; ++i) {
|
|
sample_values_ptrs.push_back(sample_values[i].data());
|
|
sample_idx_ptrs.push_back(sample_idx[i].data());
|
|
sample_sizes.push_back(static_cast<int>(sample_values[i].size()));
|
|
}
|
|
|
|
result = LGBM_DatasetCreateFromSampledColumn(
|
|
sample_values_ptrs.data(),
|
|
sample_idx_ptrs.data(),
|
|
ncols,
|
|
sample_sizes.data(),
|
|
nrows,
|
|
nrows,
|
|
nrows,
|
|
"max_bin=15",
|
|
&dataset_handle);
|
|
EXPECT_EQ(0, result) << "LGBM_DatasetCreateFromSampledColumn result code: " << result;
|
|
|
|
result = LGBM_DatasetInitStreaming(dataset_handle, has_weights, has_init_scores, has_queries, nclasses, 1, -1);
|
|
EXPECT_EQ(0, result) << "LGBM_DatasetInitStreaming result code: " << result;
|
|
break;
|
|
}
|
|
|
|
case 1:
|
|
Log::Info("Creating Dataset using LGBM_DatasetCreateByReference, %d rows dense data with a batch size of %d", nrows, batch_count);
|
|
result = LGBM_DatasetCreateByReference(ref_dataset_handle, nrows, &dataset_handle);
|
|
EXPECT_EQ(0, result) << "LGBM_DatasetCreateByReference result code: " << result;
|
|
break;
|
|
}
|
|
|
|
dataset = static_cast<Dataset*>(dataset_handle);
|
|
|
|
Log::Info("Streaming dense dataset, %d rows dense data with a batch size of %d", nrows, batch_count);
|
|
TestUtils::StreamDenseDataset(
|
|
dataset_handle,
|
|
nrows,
|
|
ncols,
|
|
nclasses,
|
|
batch_count,
|
|
features,
|
|
labels,
|
|
weights,
|
|
init_scores,
|
|
groups);
|
|
|
|
dataset->FinishLoad();
|
|
|
|
TestUtils::AssertMetadata(&dataset->metadata(),
|
|
labels,
|
|
weights,
|
|
init_scores,
|
|
groups);
|
|
}
|
|
catch (std::exception& ex) {
|
|
succeeded = false;
|
|
exceptionText = std::string(ex.what());
|
|
}
|
|
|
|
if (dataset_handle) {
|
|
int result = LGBM_DatasetFree(dataset_handle);
|
|
EXPECT_EQ(0, result) << "LGBM_DatasetFree result code: " << result;
|
|
}
|
|
|
|
if (!succeeded) {
|
|
FAIL() << "Test Dense Stream failed with exception: " << exceptionText;
|
|
}
|
|
}
|
|
|
|
void test_stream_sparse(
|
|
int8_t creation_type,
|
|
DatasetHandle ref_dataset_handle,
|
|
int32_t nrows,
|
|
int32_t ncols,
|
|
int32_t nclasses,
|
|
int batch_count,
|
|
const std::vector<int32_t>* indptr,
|
|
const std::vector<int32_t>* indices,
|
|
const std::vector<double>* vals,
|
|
const std::vector<float>* labels,
|
|
const std::vector<float>* weights,
|
|
const std::vector<double>* init_scores,
|
|
const std::vector<int32_t>* groups) {
|
|
Log::Info("Streaming %d rows sparse data with a batch size of %d", nrows, batch_count);
|
|
DatasetHandle dataset_handle = nullptr;
|
|
Dataset* dataset = nullptr;
|
|
|
|
int has_weights = weights != nullptr;
|
|
int has_init_scores = init_scores != nullptr;
|
|
int has_queries = groups != nullptr;
|
|
|
|
bool succeeded = true;
|
|
std::string exceptionText("");
|
|
|
|
try {
|
|
int result = 0;
|
|
switch (creation_type) {
|
|
case 0: {
|
|
Log::Info("Creating Dataset using LGBM_DatasetCreateFromSampledColumn, %d rows sparse data with a batch size of %d", nrows, batch_count);
|
|
|
|
std::vector<std::vector<double>> sample_values(ncols);
|
|
std::vector<std::vector<int>> sample_idx(ncols);
|
|
for (size_t i = 0; i < indptr->size() - 1; ++i) {
|
|
int start_index = indptr->at(i);
|
|
int stop_index = indptr->at(i + 1);
|
|
for (int32_t j = start_index; j < stop_index; ++j) {
|
|
auto val = vals->at(j);
|
|
auto idx = indices->at(j);
|
|
if (std::fabs(val) > 1e-35f || std::isnan(val)) {
|
|
sample_values[idx].emplace_back(val);
|
|
sample_idx[idx].emplace_back(static_cast<int>(i));
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<int> sample_sizes;
|
|
std::vector<double*> sample_values_ptrs;
|
|
std::vector<int*> sample_idx_ptrs;
|
|
for (int32_t i = 0; i < ncols; ++i) {
|
|
sample_values_ptrs.push_back(sample_values[i].data());
|
|
sample_idx_ptrs.push_back(sample_idx[i].data());
|
|
sample_sizes.push_back(static_cast<int>(sample_values[i].size()));
|
|
}
|
|
|
|
result = LGBM_DatasetCreateFromSampledColumn(
|
|
sample_values_ptrs.data(),
|
|
sample_idx_ptrs.data(),
|
|
ncols,
|
|
sample_sizes.data(),
|
|
nrows,
|
|
nrows,
|
|
nrows,
|
|
"max_bin=15",
|
|
&dataset_handle);
|
|
EXPECT_EQ(0, result) << "LGBM_DatasetCreateFromSampledColumn result code: " << result;
|
|
|
|
dataset = static_cast<Dataset*>(dataset_handle);
|
|
dataset->InitStreaming(nrows, has_weights, has_init_scores, has_queries, nclasses, 2, -1);
|
|
break;
|
|
}
|
|
|
|
case 1:
|
|
Log::Info("Creating Dataset using LGBM_DatasetCreateByReference, %d rows sparse data with a batch size of %d", nrows, batch_count);
|
|
result = LGBM_DatasetCreateByReference(ref_dataset_handle, nrows, &dataset_handle);
|
|
EXPECT_EQ(0, result) << "LGBM_DatasetCreateByReference result code: " << result;
|
|
break;
|
|
}
|
|
|
|
dataset = static_cast<Dataset*>(dataset_handle);
|
|
|
|
Log::Info("Streaming sparse dataset, %d rows sparse data with a batch size of %d", nrows, batch_count);
|
|
TestUtils::StreamSparseDataset(
|
|
dataset_handle,
|
|
nrows,
|
|
nclasses,
|
|
batch_count,
|
|
indptr,
|
|
indices,
|
|
vals,
|
|
labels,
|
|
weights,
|
|
init_scores,
|
|
groups);
|
|
|
|
dataset->FinishLoad();
|
|
|
|
TestUtils::AssertMetadata(&dataset->metadata(),
|
|
labels,
|
|
weights,
|
|
init_scores,
|
|
groups);
|
|
}
|
|
catch (std::exception& ex) {
|
|
succeeded = false;
|
|
exceptionText = std::string(ex.what());
|
|
}
|
|
|
|
if (dataset_handle) {
|
|
int result = LGBM_DatasetFree(dataset_handle);
|
|
EXPECT_EQ(0, result) << "LGBM_DatasetFree result code: " << result;
|
|
}
|
|
|
|
if (!succeeded) {
|
|
FAIL() << "Test Sparse Stream failed with exception: " << exceptionText;
|
|
}
|
|
}
|
|
|
|
TEST(Stream, PushDenseRowsWithMetadata) {
|
|
// Load some test data
|
|
DatasetHandle ref_dataset_handle;
|
|
const char* params = "max_bin=15";
|
|
// Use the smaller ".test" data because we don't care about the actual data and it's smaller
|
|
int result = TestUtils::LoadDatasetFromExamples("binary_classification/binary.test", params, &ref_dataset_handle);
|
|
EXPECT_EQ(0, result) << "LoadDatasetFromExamples result code: " << result;
|
|
|
|
Dataset* ref_dataset = static_cast<Dataset*>(ref_dataset_handle);
|
|
auto noriginalrows = ref_dataset->num_data();
|
|
Log::Info("Row count: %d", noriginalrows);
|
|
Log::Info("Feature group count: %d", ref_dataset->num_features());
|
|
|
|
// Add some fake initial_scores and groups so we can test streaming them
|
|
int nclasses = 2; // choose > 1 just to test multi-class handling
|
|
std::vector<double> unused_init_scores;
|
|
unused_init_scores.resize(noriginalrows * nclasses);
|
|
std::vector<int32_t> unused_groups;
|
|
unused_groups.assign(noriginalrows, 1);
|
|
result = LGBM_DatasetSetField(ref_dataset_handle, "init_score", unused_init_scores.data(), noriginalrows * nclasses, 1);
|
|
EXPECT_EQ(0, result) << "LGBM_DatasetSetField init_score result code: " << result;
|
|
result = LGBM_DatasetSetField(ref_dataset_handle, "group", unused_groups.data(), noriginalrows, 2);
|
|
EXPECT_EQ(0, result) << "LGBM_DatasetSetField group result code: " << result;
|
|
|
|
// Now use the reference dataset schema to make some testable Datasets with N rows each
|
|
int32_t nrows = 1000;
|
|
int32_t ncols = ref_dataset->num_features();
|
|
std::vector<double> features;
|
|
std::vector<float> labels;
|
|
std::vector<float> weights;
|
|
std::vector<double> init_scores;
|
|
std::vector<int32_t> groups;
|
|
|
|
Log::Info("Creating random data");
|
|
TestUtils::CreateRandomDenseData(nrows, ncols, nclasses, &features, &labels, &weights, &init_scores, &groups);
|
|
|
|
const std::vector<int32_t> batch_counts = { 1, nrows / 100, nrows / 10, nrows };
|
|
const std::vector<int8_t> creation_types = { 0, 1 };
|
|
|
|
for (size_t i = 0; i < creation_types.size(); ++i) { // from sampled data or reference
|
|
for (size_t j = 0; j < batch_counts.size(); ++j) {
|
|
auto type = creation_types[i];
|
|
auto batch_count = batch_counts[j];
|
|
test_stream_dense(type, ref_dataset_handle, nrows, ncols, nclasses, batch_count, &features, &labels, &weights, &init_scores, &groups);
|
|
}
|
|
}
|
|
|
|
result = LGBM_DatasetFree(ref_dataset_handle);
|
|
EXPECT_EQ(0, result) << "LGBM_DatasetFree result code: " << result;
|
|
}
|
|
|
|
TEST(Stream, PushSparseRowsWithMetadata) {
|
|
// Load some test data
|
|
DatasetHandle ref_dataset_handle;
|
|
const char* params = "max_bin=15";
|
|
// Use the smaller ".test" data because we don't care about the actual data and it's smaller
|
|
int result = TestUtils::LoadDatasetFromExamples("binary_classification/binary.test", params, &ref_dataset_handle);
|
|
EXPECT_EQ(0, result) << "LoadDatasetFromExamples result code: " << result;
|
|
|
|
Dataset* ref_dataset = static_cast<Dataset*>(ref_dataset_handle);
|
|
auto noriginalrows = ref_dataset->num_data();
|
|
Log::Info("Row count: %d", noriginalrows);
|
|
Log::Info("Feature group count: %d", ref_dataset->num_features());
|
|
|
|
// Add some fake initial_scores and groups so we can test streaming them
|
|
int32_t nclasses = 2;
|
|
std::vector<double> unused_init_scores;
|
|
unused_init_scores.resize(noriginalrows * nclasses);
|
|
std::vector<int32_t> unused_groups;
|
|
unused_groups.assign(noriginalrows, 1);
|
|
result = LGBM_DatasetSetField(ref_dataset_handle, "init_score", unused_init_scores.data(), noriginalrows * nclasses, 1);
|
|
EXPECT_EQ(0, result) << "LGBM_DatasetSetField init_score result code: " << result;
|
|
result = LGBM_DatasetSetField(ref_dataset_handle, "group", unused_groups.data(), noriginalrows, 2);
|
|
EXPECT_EQ(0, result) << "LGBM_DatasetSetField group result code: " << result;
|
|
|
|
// Now use the reference dataset schema to make some testable Datasets with N rows each
|
|
int32_t nrows = 1000;
|
|
int32_t ncols = ref_dataset->num_features();
|
|
std::vector<int32_t> indptr;
|
|
std::vector<int32_t> indices;
|
|
std::vector<double> vals;
|
|
std::vector<float> labels;
|
|
std::vector<float> weights;
|
|
std::vector<double> init_scores;
|
|
std::vector<int32_t> groups;
|
|
|
|
Log::Info("Creating random data");
|
|
float sparse_percent = .1f;
|
|
TestUtils::CreateRandomSparseData(nrows, ncols, nclasses, sparse_percent, &indptr, &indices, &vals, &labels, &weights, &init_scores, &groups);
|
|
|
|
const std::vector<int32_t> batch_counts = { 1, nrows / 100, nrows / 10, nrows };
|
|
const std::vector<int8_t> creation_types = { 0, 1 };
|
|
|
|
for (size_t i = 0; i < creation_types.size(); ++i) { // from sampled data or reference
|
|
for (size_t j = 0; j < batch_counts.size(); ++j) {
|
|
auto type = creation_types[i];
|
|
auto batch_count = batch_counts[j];
|
|
test_stream_sparse(type, ref_dataset_handle, nrows, ncols, nclasses, batch_count, &indptr, &indices, &vals, &labels, &weights, &init_scores, &groups);
|
|
}
|
|
}
|
|
|
|
result = LGBM_DatasetFree(ref_dataset_handle);
|
|
EXPECT_EQ(0, result) << "LGBM_DatasetFree result code: " << result;
|
|
}
|