Unverified Commit bbeecc09 authored by Atanas Dimitrov's avatar Atanas Dimitrov Committed by GitHub
Browse files

[c++] Fix `dump_model()` information for root node (#6569)


Co-authored-by: default avatarAtanas Dimitrov <nasko119@abv.bg>
Co-authored-by: default avatarJames Lamb <jaylamb20@gmail.com>
parent 562157ec
...@@ -77,7 +77,7 @@ class CUDATree : public Tree { ...@@ -77,7 +77,7 @@ class CUDATree : public Tree {
const data_size_t* used_data_indices, const data_size_t* used_data_indices,
data_size_t num_data, double* score) const override; data_size_t num_data, double* score) const override;
inline void AsConstantTree(double val) override; inline void AsConstantTree(double val, int count) override;
const int* cuda_leaf_parent() const { return cuda_leaf_parent_; } const int* cuda_leaf_parent() const { return cuda_leaf_parent_; }
......
...@@ -228,13 +228,14 @@ class Tree { ...@@ -228,13 +228,14 @@ class Tree {
shrinkage_ = 1.0f; shrinkage_ = 1.0f;
} }
virtual inline void AsConstantTree(double val) { virtual inline void AsConstantTree(double val, int count = 0) {
num_leaves_ = 1; num_leaves_ = 1;
shrinkage_ = 1.0f; shrinkage_ = 1.0f;
leaf_value_[0] = val; leaf_value_[0] = val;
if (is_linear_) { if (is_linear_) {
leaf_const_[0] = val; leaf_const_[0] = val;
} }
leaf_count_[0] = count;
} }
/*! \brief Serialize this object to string*/ /*! \brief Serialize this object to string*/
...@@ -563,7 +564,7 @@ inline void Tree::Split(int leaf, int feature, int real_feature, ...@@ -563,7 +564,7 @@ inline void Tree::Split(int leaf, int feature, int real_feature,
leaf_parent_[leaf] = new_node_idx; leaf_parent_[leaf] = new_node_idx;
leaf_parent_[num_leaves_] = new_node_idx; leaf_parent_[num_leaves_] = new_node_idx;
// save current leaf value to internal node before change // save current leaf value to internal node before change
internal_weight_[new_node_idx] = leaf_weight_[leaf]; internal_weight_[new_node_idx] = left_weight + right_weight;
internal_value_[new_node_idx] = leaf_value_[leaf]; internal_value_[new_node_idx] = leaf_value_[leaf];
internal_count_[new_node_idx] = left_cnt + right_cnt; internal_count_[new_node_idx] = left_cnt + right_cnt;
leaf_value_[leaf] = std::isnan(left_value) ? 0.0f : left_value; leaf_value_[leaf] = std::isnan(left_value) ? 0.0f : left_value;
......
...@@ -3906,7 +3906,7 @@ class Booster: ...@@ -3906,7 +3906,7 @@ class Booster:
return feature_name return feature_name
def _is_single_node_tree(tree: Dict[str, Any]) -> bool: def _is_single_node_tree(tree: Dict[str, Any]) -> bool:
return set(tree.keys()) == {"leaf_value"} return set(tree.keys()) == {"leaf_value", "leaf_count"}
# Create the node record, and populate universal data members # Create the node record, and populate universal data members
node: Dict[str, Union[int, str, None]] = OrderedDict() node: Dict[str, Union[int, str, None]] = OrderedDict()
......
...@@ -427,7 +427,10 @@ bool GBDT::TrainOneIter(const score_t* gradients, const score_t* hessians) { ...@@ -427,7 +427,10 @@ bool GBDT::TrainOneIter(const score_t* gradients, const score_t* hessians) {
score_updater->AddScore(init_scores[cur_tree_id], cur_tree_id); score_updater->AddScore(init_scores[cur_tree_id], cur_tree_id);
} }
} }
new_tree->AsConstantTree(init_scores[cur_tree_id]); new_tree->AsConstantTree(init_scores[cur_tree_id], num_data_);
} else {
// extend init_scores with zeros
new_tree->AsConstantTree(0, num_data_);
} }
} }
// add model // add model
......
...@@ -168,7 +168,7 @@ class RF : public GBDT { ...@@ -168,7 +168,7 @@ class RF : public GBDT {
output = init_scores_[cur_tree_id]; output = init_scores_[cur_tree_id];
} }
} }
new_tree->AsConstantTree(output); new_tree->AsConstantTree(output, num_data_);
MultiplyScore(cur_tree_id, (iter_ + num_init_iteration_)); MultiplyScore(cur_tree_id, (iter_ + num_init_iteration_));
UpdateScore(new_tree.get(), cur_tree_id); UpdateScore(new_tree.get(), cur_tree_id);
MultiplyScore(cur_tree_id, 1.0 / (iter_ + num_init_iteration_ + 1)); MultiplyScore(cur_tree_id, 1.0 / (iter_ + num_init_iteration_ + 1));
......
...@@ -330,9 +330,10 @@ void CUDATree::SyncLeafOutputFromCUDAToHost() { ...@@ -330,9 +330,10 @@ void CUDATree::SyncLeafOutputFromCUDAToHost() {
CopyFromCUDADeviceToHost<double>(leaf_value_.data(), cuda_leaf_value_, leaf_value_.size(), __FILE__, __LINE__); CopyFromCUDADeviceToHost<double>(leaf_value_.data(), cuda_leaf_value_, leaf_value_.size(), __FILE__, __LINE__);
} }
void CUDATree::AsConstantTree(double val) { void CUDATree::AsConstantTree(double val, int count) {
Tree::AsConstantTree(val); Tree::AsConstantTree(val, count);
CopyFromHostToCUDADevice<double>(cuda_leaf_value_, &val, 1, __FILE__, __LINE__); CopyFromHostToCUDADevice<double>(cuda_leaf_value_, &val, 1, __FILE__, __LINE__);
CopyFromHostToCUDADevice<int>(cuda_leaf_count_, &count, 1, __FILE__, __LINE__);
} }
} // namespace LightGBM } // namespace LightGBM
......
...@@ -94,7 +94,7 @@ __global__ void SplitKernel( // split information ...@@ -94,7 +94,7 @@ __global__ void SplitKernel( // split information
split_gain[new_node_index] = static_cast<float>(cuda_split_info->gain); split_gain[new_node_index] = static_cast<float>(cuda_split_info->gain);
} else if (thread_index == 4) { } else if (thread_index == 4) {
// save current leaf value to internal node before change // save current leaf value to internal node before change
internal_weight[new_node_index] = leaf_weight[leaf_index]; internal_weight[new_node_index] = cuda_split_info->left_sum_hessians + cuda_split_info->right_sum_hessians;
leaf_weight[leaf_index] = cuda_split_info->left_sum_hessians; leaf_weight[leaf_index] = cuda_split_info->left_sum_hessians;
} else if (thread_index == 5) { } else if (thread_index == 5) {
internal_value[new_node_index] = leaf_value[leaf_index]; internal_value[new_node_index] = leaf_value[leaf_index];
...@@ -210,7 +210,7 @@ __global__ void SplitCategoricalKernel( // split information ...@@ -210,7 +210,7 @@ __global__ void SplitCategoricalKernel( // split information
split_gain[new_node_index] = static_cast<float>(cuda_split_info->gain); split_gain[new_node_index] = static_cast<float>(cuda_split_info->gain);
} else if (thread_index == 4) { } else if (thread_index == 4) {
// save current leaf value to internal node before change // save current leaf value to internal node before change
internal_weight[new_node_index] = leaf_weight[leaf_index]; internal_weight[new_node_index] = cuda_split_info->left_sum_hessians + cuda_split_info->right_sum_hessians;
leaf_weight[leaf_index] = cuda_split_info->left_sum_hessians; leaf_weight[leaf_index] = cuda_split_info->left_sum_hessians;
} else if (thread_index == 5) { } else if (thread_index == 5) {
internal_value[new_node_index] = leaf_value[leaf_index]; internal_value[new_node_index] = leaf_value[leaf_index];
......
...@@ -416,12 +416,15 @@ std::string Tree::ToJSON() const { ...@@ -416,12 +416,15 @@ std::string Tree::ToJSON() const {
str_buf << "\"num_cat\":" << num_cat_ << "," << '\n'; str_buf << "\"num_cat\":" << num_cat_ << "," << '\n';
str_buf << "\"shrinkage\":" << shrinkage_ << "," << '\n'; str_buf << "\"shrinkage\":" << shrinkage_ << "," << '\n';
if (num_leaves_ == 1) { if (num_leaves_ == 1) {
str_buf << "\"tree_structure\":{";
str_buf << "\"leaf_value\":" << leaf_value_[0] << ", " << '\n';
if (is_linear_) { if (is_linear_) {
str_buf << "\"tree_structure\":{" << "\"leaf_value\":" << leaf_value_[0] << ", " << "\n"; str_buf << "\"leaf_count\":" << leaf_count_[0] << ", " << '\n';
str_buf << LinearModelToJSON(0) << "}" << "\n"; str_buf << LinearModelToJSON(0);
} else { } else {
str_buf << "\"tree_structure\":{" << "\"leaf_value\":" << leaf_value_[0] << "}" << '\n'; str_buf << "\"leaf_count\":" << leaf_count_[0];
} }
str_buf << "}" << '\n';
} else { } else {
str_buf << "\"tree_structure\":" << NodeToJSON(0) << '\n'; str_buf << "\"tree_structure\":" << NodeToJSON(0) << '\n';
} }
...@@ -731,6 +734,12 @@ Tree::Tree(const char* str, size_t* used_len) { ...@@ -731,6 +734,12 @@ Tree::Tree(const char* str, size_t* used_len) {
is_linear_ = false; is_linear_ = false;
} }
if (key_vals.count("leaf_count")) {
leaf_count_ = CommonC::StringToArrayFast<int>(key_vals["leaf_count"], num_leaves_);
} else {
leaf_count_.resize(num_leaves_);
}
#ifdef USE_CUDA #ifdef USE_CUDA
is_cuda_tree_ = false; is_cuda_tree_ = false;
#endif // USE_CUDA #endif // USE_CUDA
...@@ -793,12 +802,6 @@ Tree::Tree(const char* str, size_t* used_len) { ...@@ -793,12 +802,6 @@ Tree::Tree(const char* str, size_t* used_len) {
leaf_weight_.resize(num_leaves_); leaf_weight_.resize(num_leaves_);
} }
if (key_vals.count("leaf_count")) {
leaf_count_ = CommonC::StringToArrayFast<int>(key_vals["leaf_count"], num_leaves_);
} else {
leaf_count_.resize(num_leaves_);
}
if (key_vals.count("decision_type")) { if (key_vals.count("decision_type")) {
decision_type_ = CommonC::StringToArrayFast<int8_t>(key_vals["decision_type"], num_leaves_ - 1); decision_type_ = CommonC::StringToArrayFast<int8_t>(key_vals["decision_type"], num_leaves_ - 1);
} else { } else {
......
...@@ -38,12 +38,14 @@ void CUDALeafSplits::InitValues( ...@@ -38,12 +38,14 @@ void CUDALeafSplits::InitValues(
const double lambda_l1, const double lambda_l2, const double lambda_l1, const double lambda_l2,
const score_t* cuda_gradients, const score_t* cuda_hessians, const score_t* cuda_gradients, const score_t* cuda_hessians,
const data_size_t* cuda_bagging_data_indices, const data_size_t* cuda_data_indices_in_leaf, const data_size_t* cuda_bagging_data_indices, const data_size_t* cuda_data_indices_in_leaf,
const data_size_t num_used_indices, hist_t* cuda_hist_in_leaf, double* root_sum_hessians) { const data_size_t num_used_indices, hist_t* cuda_hist_in_leaf,
double* root_sum_gradients, double* root_sum_hessians) {
cuda_gradients_ = cuda_gradients; cuda_gradients_ = cuda_gradients;
cuda_hessians_ = cuda_hessians; cuda_hessians_ = cuda_hessians;
cuda_sum_of_gradients_buffer_.SetValue(0); cuda_sum_of_gradients_buffer_.SetValue(0);
cuda_sum_of_hessians_buffer_.SetValue(0); cuda_sum_of_hessians_buffer_.SetValue(0);
LaunchInitValuesKernal(lambda_l1, lambda_l2, cuda_bagging_data_indices, cuda_data_indices_in_leaf, num_used_indices, cuda_hist_in_leaf); LaunchInitValuesKernal(lambda_l1, lambda_l2, cuda_bagging_data_indices, cuda_data_indices_in_leaf, num_used_indices, cuda_hist_in_leaf);
CopyFromCUDADeviceToHost<double>(root_sum_gradients, cuda_sum_of_gradients_buffer_.RawData(), 1, __FILE__, __LINE__);
CopyFromCUDADeviceToHost<double>(root_sum_hessians, cuda_sum_of_hessians_buffer_.RawData(), 1, __FILE__, __LINE__); CopyFromCUDADeviceToHost<double>(root_sum_hessians, cuda_sum_of_hessians_buffer_.RawData(), 1, __FILE__, __LINE__);
SynchronizeCUDADevice(__FILE__, __LINE__); SynchronizeCUDADevice(__FILE__, __LINE__);
} }
...@@ -53,11 +55,12 @@ void CUDALeafSplits::InitValues( ...@@ -53,11 +55,12 @@ void CUDALeafSplits::InitValues(
const int16_t* cuda_gradients_and_hessians, const int16_t* cuda_gradients_and_hessians,
const data_size_t* cuda_bagging_data_indices, const data_size_t* cuda_bagging_data_indices,
const data_size_t* cuda_data_indices_in_leaf, const data_size_t num_used_indices, const data_size_t* cuda_data_indices_in_leaf, const data_size_t num_used_indices,
hist_t* cuda_hist_in_leaf, double* root_sum_hessians, hist_t* cuda_hist_in_leaf, double* root_sum_gradients, double* root_sum_hessians,
const score_t* grad_scale, const score_t* hess_scale) { const score_t* grad_scale, const score_t* hess_scale) {
cuda_gradients_ = reinterpret_cast<const score_t*>(cuda_gradients_and_hessians); cuda_gradients_ = reinterpret_cast<const score_t*>(cuda_gradients_and_hessians);
cuda_hessians_ = nullptr; cuda_hessians_ = nullptr;
LaunchInitValuesKernal(lambda_l1, lambda_l2, cuda_bagging_data_indices, cuda_data_indices_in_leaf, num_used_indices, cuda_hist_in_leaf, grad_scale, hess_scale); LaunchInitValuesKernal(lambda_l1, lambda_l2, cuda_bagging_data_indices, cuda_data_indices_in_leaf, num_used_indices, cuda_hist_in_leaf, grad_scale, hess_scale);
CopyFromCUDADeviceToHost<double>(root_sum_gradients, cuda_sum_of_gradients_buffer_.RawData(), 1, __FILE__, __LINE__);
CopyFromCUDADeviceToHost<double>(root_sum_hessians, cuda_sum_of_hessians_buffer_.RawData(), 1, __FILE__, __LINE__); CopyFromCUDADeviceToHost<double>(root_sum_hessians, cuda_sum_of_hessians_buffer_.RawData(), 1, __FILE__, __LINE__);
SynchronizeCUDADevice(__FILE__, __LINE__); SynchronizeCUDADevice(__FILE__, __LINE__);
} }
......
...@@ -44,14 +44,14 @@ class CUDALeafSplits { ...@@ -44,14 +44,14 @@ class CUDALeafSplits {
const score_t* cuda_gradients, const score_t* cuda_hessians, const score_t* cuda_gradients, const score_t* cuda_hessians,
const data_size_t* cuda_bagging_data_indices, const data_size_t* cuda_bagging_data_indices,
const data_size_t* cuda_data_indices_in_leaf, const data_size_t num_used_indices, const data_size_t* cuda_data_indices_in_leaf, const data_size_t num_used_indices,
hist_t* cuda_hist_in_leaf, double* root_sum_hessians); hist_t* cuda_hist_in_leaf, double* root_sum_gradients, double* root_sum_hessians);
void InitValues( void InitValues(
const double lambda_l1, const double lambda_l2, const double lambda_l1, const double lambda_l2,
const int16_t* cuda_gradients_and_hessians, const int16_t* cuda_gradients_and_hessians,
const data_size_t* cuda_bagging_data_indices, const data_size_t* cuda_bagging_data_indices,
const data_size_t* cuda_data_indices_in_leaf, const data_size_t num_used_indices, const data_size_t* cuda_data_indices_in_leaf, const data_size_t num_used_indices,
hist_t* cuda_hist_in_leaf, double* root_sum_hessians, hist_t* cuda_hist_in_leaf, double* root_sum_gradients, double* root_sum_hessians,
const score_t* grad_scale, const score_t* hess_scale); const score_t* grad_scale, const score_t* hess_scale);
void InitValues(); void InitValues();
......
...@@ -66,6 +66,7 @@ void CUDASingleGPUTreeLearner::Init(const Dataset* train_data, bool is_constant_ ...@@ -66,6 +66,7 @@ void CUDASingleGPUTreeLearner::Init(const Dataset* train_data, bool is_constant_
leaf_best_split_default_left_.resize(config_->num_leaves, 0); leaf_best_split_default_left_.resize(config_->num_leaves, 0);
leaf_num_data_.resize(config_->num_leaves, 0); leaf_num_data_.resize(config_->num_leaves, 0);
leaf_data_start_.resize(config_->num_leaves, 0); leaf_data_start_.resize(config_->num_leaves, 0);
leaf_sum_gradients_.resize(config_->num_leaves, 0.0f);
leaf_sum_hessians_.resize(config_->num_leaves, 0.0f); leaf_sum_hessians_.resize(config_->num_leaves, 0.0f);
if (!boosting_on_cuda_) { if (!boosting_on_cuda_) {
...@@ -122,6 +123,7 @@ void CUDASingleGPUTreeLearner::BeforeTrain() { ...@@ -122,6 +123,7 @@ void CUDASingleGPUTreeLearner::BeforeTrain() {
cuda_data_partition_->cuda_data_indices(), cuda_data_partition_->cuda_data_indices(),
root_num_data, root_num_data,
cuda_histogram_constructor_->cuda_hist_pointer(), cuda_histogram_constructor_->cuda_hist_pointer(),
&leaf_sum_gradients_[0],
&leaf_sum_hessians_[0], &leaf_sum_hessians_[0],
cuda_gradient_discretizer_->grad_scale_ptr(), cuda_gradient_discretizer_->grad_scale_ptr(),
cuda_gradient_discretizer_->hess_scale_ptr()); cuda_gradient_discretizer_->hess_scale_ptr());
...@@ -137,6 +139,7 @@ void CUDASingleGPUTreeLearner::BeforeTrain() { ...@@ -137,6 +139,7 @@ void CUDASingleGPUTreeLearner::BeforeTrain() {
cuda_data_partition_->cuda_data_indices(), cuda_data_partition_->cuda_data_indices(),
root_num_data, root_num_data,
cuda_histogram_constructor_->cuda_hist_pointer(), cuda_histogram_constructor_->cuda_hist_pointer(),
&leaf_sum_gradients_[0],
&leaf_sum_hessians_[0]); &leaf_sum_hessians_[0]);
} }
leaf_num_data_[0] = root_num_data; leaf_num_data_[0] = root_num_data;
...@@ -162,6 +165,12 @@ Tree* CUDASingleGPUTreeLearner::Train(const score_t* gradients, ...@@ -162,6 +165,12 @@ Tree* CUDASingleGPUTreeLearner::Train(const score_t* gradients,
const bool track_branch_features = !(config_->interaction_constraints_vector.empty()); const bool track_branch_features = !(config_->interaction_constraints_vector.empty());
std::unique_ptr<CUDATree> tree(new CUDATree(config_->num_leaves, track_branch_features, std::unique_ptr<CUDATree> tree(new CUDATree(config_->num_leaves, track_branch_features,
config_->linear_tree, config_->gpu_device_id, has_categorical_feature_)); config_->linear_tree, config_->gpu_device_id, has_categorical_feature_));
// set the root value by hand, as it is not handled by splits
tree->SetLeafOutput(0, CUDALeafSplits::CalculateSplittedLeafOutput<true, false>(
leaf_sum_gradients_[smaller_leaf_index_], leaf_sum_hessians_[smaller_leaf_index_],
config_->lambda_l1, config_->lambda_l2, config_->path_smooth,
static_cast<data_size_t>(num_data_), 0));
tree->SyncLeafOutputFromHostToCUDA();
for (int i = 0; i < config_->num_leaves - 1; ++i) { for (int i = 0; i < config_->num_leaves - 1; ++i) {
global_timer.Start("CUDASingleGPUTreeLearner::ConstructHistogramForLeaf"); global_timer.Start("CUDASingleGPUTreeLearner::ConstructHistogramForLeaf");
const data_size_t num_data_in_smaller_leaf = leaf_num_data_[smaller_leaf_index_]; const data_size_t num_data_in_smaller_leaf = leaf_num_data_[smaller_leaf_index_];
...@@ -293,8 +302,6 @@ Tree* CUDASingleGPUTreeLearner::Train(const score_t* gradients, ...@@ -293,8 +302,6 @@ Tree* CUDASingleGPUTreeLearner::Train(const score_t* gradients,
best_split_info); best_split_info);
} }
double sum_left_gradients = 0.0f;
double sum_right_gradients = 0.0f;
cuda_data_partition_->Split(best_split_info, cuda_data_partition_->Split(best_split_info,
best_leaf_index_, best_leaf_index_,
right_leaf_index, right_leaf_index,
...@@ -313,10 +320,10 @@ Tree* CUDASingleGPUTreeLearner::Train(const score_t* gradients, ...@@ -313,10 +320,10 @@ Tree* CUDASingleGPUTreeLearner::Train(const score_t* gradients,
&leaf_data_start_[right_leaf_index], &leaf_data_start_[right_leaf_index],
&leaf_sum_hessians_[best_leaf_index_], &leaf_sum_hessians_[best_leaf_index_],
&leaf_sum_hessians_[right_leaf_index], &leaf_sum_hessians_[right_leaf_index],
&sum_left_gradients, &leaf_sum_gradients_[best_leaf_index_],
&sum_right_gradients); &leaf_sum_gradients_[right_leaf_index]);
#ifdef DEBUG #ifdef DEBUG
CheckSplitValid(best_leaf_index_, right_leaf_index, sum_left_gradients, sum_right_gradients); CheckSplitValid(best_leaf_index_, right_leaf_index);
#endif // DEBUG #endif // DEBUG
smaller_leaf_index_ = (leaf_num_data_[best_leaf_index_] < leaf_num_data_[right_leaf_index] ? best_leaf_index_ : right_leaf_index); smaller_leaf_index_ = (leaf_num_data_[best_leaf_index_] < leaf_num_data_[right_leaf_index] ? best_leaf_index_ : right_leaf_index);
larger_leaf_index_ = (smaller_leaf_index_ == best_leaf_index_ ? right_leaf_index : best_leaf_index_); larger_leaf_index_ = (smaller_leaf_index_ == best_leaf_index_ ? right_leaf_index : best_leaf_index_);
...@@ -374,6 +381,7 @@ void CUDASingleGPUTreeLearner::ResetConfig(const Config* config) { ...@@ -374,6 +381,7 @@ void CUDASingleGPUTreeLearner::ResetConfig(const Config* config) {
leaf_best_split_default_left_.resize(config_->num_leaves, 0); leaf_best_split_default_left_.resize(config_->num_leaves, 0);
leaf_num_data_.resize(config_->num_leaves, 0); leaf_num_data_.resize(config_->num_leaves, 0);
leaf_data_start_.resize(config_->num_leaves, 0); leaf_data_start_.resize(config_->num_leaves, 0);
leaf_sum_gradients_.resize(config_->num_leaves, 0.0f);
leaf_sum_hessians_.resize(config_->num_leaves, 0.0f); leaf_sum_hessians_.resize(config_->num_leaves, 0.0f);
} }
cuda_histogram_constructor_->ResetConfig(config); cuda_histogram_constructor_->ResetConfig(config);
...@@ -562,9 +570,7 @@ void CUDASingleGPUTreeLearner::SelectFeatureByNode(const Tree* tree) { ...@@ -562,9 +570,7 @@ void CUDASingleGPUTreeLearner::SelectFeatureByNode(const Tree* tree) {
#ifdef DEBUG #ifdef DEBUG
void CUDASingleGPUTreeLearner::CheckSplitValid( void CUDASingleGPUTreeLearner::CheckSplitValid(
const int left_leaf, const int left_leaf,
const int right_leaf, const int right_leaf) {
const double split_sum_left_gradients,
const double split_sum_right_gradients) {
std::vector<data_size_t> left_data_indices(leaf_num_data_[left_leaf]); std::vector<data_size_t> left_data_indices(leaf_num_data_[left_leaf]);
std::vector<data_size_t> right_data_indices(leaf_num_data_[right_leaf]); std::vector<data_size_t> right_data_indices(leaf_num_data_[right_leaf]);
CopyFromCUDADeviceToHost<data_size_t>(left_data_indices.data(), CopyFromCUDADeviceToHost<data_size_t>(left_data_indices.data(),
...@@ -585,9 +591,9 @@ void CUDASingleGPUTreeLearner::CheckSplitValid( ...@@ -585,9 +591,9 @@ void CUDASingleGPUTreeLearner::CheckSplitValid(
sum_right_gradients += host_gradients_[index]; sum_right_gradients += host_gradients_[index];
sum_right_hessians += host_hessians_[index]; sum_right_hessians += host_hessians_[index];
} }
CHECK_LE(std::fabs(sum_left_gradients - split_sum_left_gradients), 1e-6f); CHECK_LE(std::fabs(sum_left_gradients - leaf_sum_gradients_[left_leaf]), 1e-6f);
CHECK_LE(std::fabs(sum_left_hessians - leaf_sum_hessians_[left_leaf]), 1e-6f); CHECK_LE(std::fabs(sum_left_hessians - leaf_sum_hessians_[left_leaf]), 1e-6f);
CHECK_LE(std::fabs(sum_right_gradients - split_sum_right_gradients), 1e-6f); CHECK_LE(std::fabs(sum_right_gradients - leaf_sum_gradients_[right_leaf]), 1e-6f);
CHECK_LE(std::fabs(sum_right_hessians - leaf_sum_hessians_[right_leaf]), 1e-6f); CHECK_LE(std::fabs(sum_right_hessians - leaf_sum_hessians_[right_leaf]), 1e-6f);
} }
#endif // DEBUG #endif // DEBUG
......
...@@ -71,8 +71,7 @@ class CUDASingleGPUTreeLearner: public SerialTreeLearner { ...@@ -71,8 +71,7 @@ class CUDASingleGPUTreeLearner: public SerialTreeLearner {
#ifdef DEBUG #ifdef DEBUG
void CheckSplitValid( void CheckSplitValid(
const int left_leaf, const int right_leaf, const int left_leaf, const int right_leaf);
const double sum_left_gradients, const double sum_right_gradients);
#endif // DEBUG #endif // DEBUG
void RenewDiscretizedTreeLeaves(CUDATree* cuda_tree); void RenewDiscretizedTreeLeaves(CUDATree* cuda_tree);
...@@ -103,6 +102,7 @@ class CUDASingleGPUTreeLearner: public SerialTreeLearner { ...@@ -103,6 +102,7 @@ class CUDASingleGPUTreeLearner: public SerialTreeLearner {
std::vector<uint8_t> leaf_best_split_default_left_; std::vector<uint8_t> leaf_best_split_default_left_;
std::vector<data_size_t> leaf_num_data_; std::vector<data_size_t> leaf_num_data_;
std::vector<data_size_t> leaf_data_start_; std::vector<data_size_t> leaf_data_start_;
std::vector<double> leaf_sum_gradients_;
std::vector<double> leaf_sum_hessians_; std::vector<double> leaf_sum_hessians_;
int smaller_leaf_index_; int smaller_leaf_index_;
int larger_leaf_index_; int larger_leaf_index_;
......
...@@ -201,6 +201,12 @@ Tree* SerialTreeLearner::Train(const score_t* gradients, const score_t *hessians ...@@ -201,6 +201,12 @@ Tree* SerialTreeLearner::Train(const score_t* gradients, const score_t *hessians
auto tree_ptr = tree.get(); auto tree_ptr = tree.get();
constraints_->ShareTreePointer(tree_ptr); constraints_->ShareTreePointer(tree_ptr);
// set the root value by hand, as it is not handled by splits
tree->SetLeafOutput(0, FeatureHistogram::CalculateSplittedLeafOutput<true, true, true, false>(
smaller_leaf_splits_->sum_gradients(), smaller_leaf_splits_->sum_hessians(),
config_->lambda_l1, config_->lambda_l2, config_->max_delta_step,
BasicConstraint(), config_->path_smooth, static_cast<data_size_t>(num_data_), 0));
// root leaf // root leaf
int left_leaf = 0; int left_leaf = 0;
int cur_depth = 1; int cur_depth = 1;
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
"""Tests for lightgbm.dask module""" """Tests for lightgbm.dask module"""
import inspect import inspect
import random
import socket import socket
from itertools import groupby from itertools import groupby
from os import getenv from os import getenv
...@@ -1443,7 +1442,7 @@ def test_training_succeeds_when_data_is_dataframe_and_label_is_column_array(task ...@@ -1443,7 +1442,7 @@ def test_training_succeeds_when_data_is_dataframe_and_label_is_column_array(task
@pytest.mark.parametrize("task", tasks) @pytest.mark.parametrize("task", tasks)
@pytest.mark.parametrize("output", data_output) @pytest.mark.parametrize("output", data_output)
def test_init_score(task, output, cluster): def test_init_score(task, output, cluster, rng):
if task == "ranking" and output == "scipy_csr_matrix": if task == "ranking" and output == "scipy_csr_matrix":
pytest.skip("LGBMRanker is not currently tested on sparse matrices") pytest.skip("LGBMRanker is not currently tested on sparse matrices")
...@@ -1452,20 +1451,35 @@ def test_init_score(task, output, cluster): ...@@ -1452,20 +1451,35 @@ def test_init_score(task, output, cluster):
model_factory = task_to_dask_factory[task] model_factory = task_to_dask_factory[task]
params = {"n_estimators": 1, "num_leaves": 2, "time_out": 5} params = {
init_score = random.random() "n_estimators": 1,
size_factor = 1 "num_leaves": 2,
"time_out": 5,
"seed": 708,
"deterministic": True,
"force_row_wise": True,
"num_thread": 1,
}
num_classes = 1
if task == "multiclass-classification": if task == "multiclass-classification":
size_factor = 3 # number of classes num_classes = 3
if output.startswith("dataframe"): if output.startswith("dataframe"):
init_scores = dy.map_partitions(lambda x: pd.DataFrame([[init_score] * size_factor] * x.size)) init_scores = dy.map_partitions(lambda x: pd.DataFrame(rng.uniform(size=(x.size, num_classes))))
else: else:
init_scores = dy.map_blocks(lambda x: np.full((x.size, size_factor), init_score)) init_scores = dy.map_blocks(lambda x: rng.uniform(size=(x.size, num_classes)))
model = model_factory(client=client, **params) model = model_factory(client=client, **params)
model.fit(dX, dy, sample_weight=dw, init_score=init_scores, group=dg) model.fit(dX, dy, sample_weight=dw, group=dg)
# value of the root node is 0 when init_score is set pred = model.predict(dX, raw_score=True)
assert model.booster_.trees_to_dataframe()["value"][0] == 0
model_init_score = model_factory(client=client, **params)
model_init_score.fit(dX, dy, sample_weight=dw, init_score=init_scores, group=dg)
pred_init_score = model_init_score.predict(dX, raw_score=True)
# check if init score changes predictions
with pytest.raises(AssertionError):
assert_eq(pred, pred_init_score)
def sklearn_checks_to_run(): def sklearn_checks_to_run():
...@@ -1527,6 +1541,39 @@ def test_predict_with_raw_score(task, output, cluster): ...@@ -1527,6 +1541,39 @@ def test_predict_with_raw_score(task, output, cluster):
assert_eq(raw_predictions, pred_proba_raw) assert_eq(raw_predictions, pred_proba_raw)
@pytest.mark.parametrize("output", data_output)
@pytest.mark.parametrize("use_init_score", [False, True])
def test_predict_stump(output, use_init_score, cluster, rng):
with Client(cluster) as client:
_, _, _, _, dX, dy, _, _ = _create_data(objective="binary-classification", n_samples=1_000, output=output)
params = {"objective": "binary", "n_estimators": 5, "min_data_in_leaf": 1_000}
if not use_init_score:
init_scores = None
elif output.startswith("dataframe"):
init_scores = dy.map_partitions(lambda x: pd.DataFrame(rng.uniform(size=x.size)))
else:
init_scores = dy.map_blocks(lambda x: rng.uniform(size=x.size))
model = lgb.DaskLGBMClassifier(client=client, **params)
model.fit(dX, dy, init_score=init_scores)
preds_1 = model.predict(dX, raw_score=True, num_iteration=1).compute()
preds_all = model.predict(dX, raw_score=True).compute()
if use_init_score:
# if init_score was provided, a model of stumps should predict all 0s
all_zeroes = np.full_like(preds_1, fill_value=0.0)
assert_eq(preds_1, all_zeroes)
assert_eq(preds_all, all_zeroes)
else:
# if init_score was not provided, prediction for a model of stumps should be
# the "average" of the labels
y_avg = np.log(dy.mean() / (1.0 - dy.mean()))
assert_eq(preds_1, np.full_like(preds_1, fill_value=y_avg))
assert_eq(preds_all, np.full_like(preds_all, fill_value=y_avg))
def test_distributed_quantized_training(tmp_path, cluster): def test_distributed_quantized_training(tmp_path, cluster):
with Client(cluster) as client: with Client(cluster) as client:
X, y, w, _, dX, dy, dw, _ = _create_data(objective="regression", output="array") X, y, w, _, dX, dy, dw, _ = _create_data(objective="regression", output="array")
......
...@@ -24,6 +24,7 @@ from lightgbm.compat import PANDAS_INSTALLED, pd_DataFrame, pd_Series ...@@ -24,6 +24,7 @@ from lightgbm.compat import PANDAS_INSTALLED, pd_DataFrame, pd_Series
from .utils import ( from .utils import (
SERIALIZERS, SERIALIZERS,
assert_all_trees_valid,
assert_silent, assert_silent,
dummy_obj, dummy_obj,
load_breast_cancer, load_breast_cancer,
...@@ -3811,6 +3812,34 @@ def test_predict_with_start_iteration(): ...@@ -3811,6 +3812,34 @@ def test_predict_with_start_iteration():
inner_test(X, y, params, early_stopping_rounds=None) inner_test(X, y, params, early_stopping_rounds=None)
@pytest.mark.parametrize("use_init_score", [False, True])
def test_predict_stump(rng, use_init_score):
X, y = load_breast_cancer(return_X_y=True)
dataset_kwargs = {"data": X, "label": y}
if use_init_score:
dataset_kwargs.update({"init_score": rng.uniform(size=y.shape)})
bst = lgb.train(
train_set=lgb.Dataset(**dataset_kwargs),
params={"objective": "binary", "min_data_in_leaf": X.shape[0]},
num_boost_round=5,
)
# checking prediction from 1 iteration and the whole model, to prevent bugs
# of the form "a model of n stumps predicts n * initial_score"
preds_1 = bst.predict(X, raw_score=True, num_iteration=1)
preds_all = bst.predict(X, raw_score=True)
if use_init_score:
# if init_score was provided, a model of stumps should predict all 0s
all_zeroes = np.full_like(preds_1, fill_value=0.0)
np.testing.assert_allclose(preds_1, all_zeroes)
np.testing.assert_allclose(preds_all, all_zeroes)
else:
# if init_score was not provided, prediction for a model of stumps should be
# the "average" of the labels
y_avg = np.log(y.mean() / (1.0 - y.mean()))
np.testing.assert_allclose(preds_1, np.full_like(preds_1, fill_value=y_avg))
np.testing.assert_allclose(preds_all, np.full_like(preds_all, fill_value=y_avg))
def test_average_precision_metric(): def test_average_precision_metric():
# test against sklearn average precision metric # test against sklearn average precision metric
X, y = load_breast_cancer(return_X_y=True) X, y = load_breast_cancer(return_X_y=True)
...@@ -3855,21 +3884,60 @@ def test_reset_params_works_with_metric_num_class_and_boosting(): ...@@ -3855,21 +3884,60 @@ def test_reset_params_works_with_metric_num_class_and_boosting():
assert new_bst.params == expected_params assert new_bst.params == expected_params
def test_dump_model(): @pytest.mark.parametrize("linear_tree", [False, True])
def test_dump_model_stump(linear_tree):
X, y = load_breast_cancer(return_X_y=True) X, y = load_breast_cancer(return_X_y=True)
train_data = lgb.Dataset(X, label=y) train_data = lgb.Dataset(X, label=y)
params = {"objective": "binary", "verbose": -1} params = {"objective": "binary", "verbose": -1, "linear_tree": linear_tree, "min_data_in_leaf": len(y)}
bst = lgb.train(params, train_data, num_boost_round=5)
dumped_model = bst.dump_model(num_iteration=5, start_iteration=0)
tree_structure = dumped_model["tree_info"][0]["tree_structure"]
assert len(dumped_model["tree_info"]) == 1
assert "leaf_value" in tree_structure
assert tree_structure["leaf_count"] == len(y)
def test_dump_model():
initial_score_offset = 57.5
X, y = make_synthetic_regression()
train_data = lgb.Dataset(X, label=y + initial_score_offset)
params = {
"objective": "regression",
"verbose": -1,
"boost_from_average": True,
}
bst = lgb.train(params, train_data, num_boost_round=5) bst = lgb.train(params, train_data, num_boost_round=5)
dumped_model_str = str(bst.dump_model(5, 0)) dumped_model = bst.dump_model(num_iteration=5, start_iteration=0)
dumped_model_str = str(dumped_model)
assert "leaf_features" not in dumped_model_str assert "leaf_features" not in dumped_model_str
assert "leaf_coeff" not in dumped_model_str assert "leaf_coeff" not in dumped_model_str
assert "leaf_const" not in dumped_model_str assert "leaf_const" not in dumped_model_str
assert "leaf_value" in dumped_model_str assert "leaf_value" in dumped_model_str
assert "leaf_count" in dumped_model_str assert "leaf_count" in dumped_model_str
params["linear_tree"] = True
for tree in dumped_model["tree_info"]:
assert tree["tree_structure"]["internal_value"] != 0
assert dumped_model["tree_info"][0]["tree_structure"]["internal_value"] == pytest.approx(
initial_score_offset, abs=1
)
assert_all_trees_valid(dumped_model)
def test_dump_model_linear():
X, y = load_breast_cancer(return_X_y=True)
params = {
"objective": "binary",
"verbose": -1,
"linear_tree": True,
}
train_data = lgb.Dataset(X, label=y) train_data = lgb.Dataset(X, label=y)
bst = lgb.train(params, train_data, num_boost_round=5) bst = lgb.train(params, train_data, num_boost_round=5)
dumped_model_str = str(bst.dump_model(5, 0)) dumped_model = bst.dump_model(num_iteration=5, start_iteration=0)
assert_all_trees_valid(dumped_model)
dumped_model_str = str(dumped_model)
assert "leaf_features" in dumped_model_str assert "leaf_features" in dumped_model_str
assert "leaf_coeff" in dumped_model_str assert "leaf_coeff" in dumped_model_str
assert "leaf_const" in dumped_model_str assert "leaf_const" in dumped_model_str
......
...@@ -225,3 +225,42 @@ def np_assert_array_equal(*args, **kwargs): ...@@ -225,3 +225,42 @@ def np_assert_array_equal(*args, **kwargs):
if not _numpy_testing_supports_strict_kwarg: if not _numpy_testing_supports_strict_kwarg:
kwargs.pop("strict") kwargs.pop("strict")
np.testing.assert_array_equal(*args, **kwargs) np.testing.assert_array_equal(*args, **kwargs)
def assert_subtree_valid(root):
"""Recursively checks the validity of a subtree rooted at `root`.
Currently it only checks whether weights and counts are consistent between
all parent nodes and their children.
Parameters
----------
root : dict
A dictionary representing the root of the subtree.
It should be produced by dump_model()
Returns
-------
tuple
A tuple containing the weight and count of the subtree rooted at `root`.
"""
if "leaf_count" in root:
return (root["leaf_weight"], root["leaf_count"])
left_child = root["left_child"]
right_child = root["right_child"]
(l_w, l_c) = assert_subtree_valid(left_child)
(r_w, r_c) = assert_subtree_valid(right_child)
assert (
abs(root["internal_weight"] - (l_w + r_w)) <= 1e-3
), "root node's internal weight should be approximately the sum of its child nodes' internal weights"
assert (
root["internal_count"] == l_c + r_c
), "root node's internal count should be exactly the sum of its child nodes' internal counts"
return (root["internal_weight"], root["internal_count"])
def assert_all_trees_valid(model_dump):
for idx, tree in enumerate(model_dump["tree_info"]):
assert tree["tree_index"] == idx, f"tree {idx} should have tree_index={idx}. Full tree: {tree}"
assert_subtree_valid(tree["tree_structure"])
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment