Skip to content

Commit f41be2c

Browse files
authored
Replace nan with denormal floating point. (#11428)
- Remove nan as the init value for tree models. - Avoid zero division in linear models.
1 parent 39a37ab commit f41be2c

File tree

8 files changed

+51
-40
lines changed

8 files changed

+51
-40
lines changed

src/linear/coordinate_common.h

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
/**
2-
* Copyright 2018-2023 by XGBoost Contributors
2+
* Copyright 2018-2025, XGBoost Contributors
33
* \author Rory Mitchell
44
*/
55
#pragma once
66
#include <algorithm>
7+
#include <cmath> // for fpclassify
8+
#include <limits>
79
#include <string>
810
#include <utility>
911
#include <vector>
10-
#include <limits>
1112

12-
#include "xgboost/data.h"
13-
#include "xgboost/parameter.h"
14-
#include "./param.h"
15-
#include "../gbm/gblinear_model.h"
1613
#include "../common/random.h"
1714
#include "../common/threading_utils.h"
15+
#include "../gbm/gblinear_model.h"
16+
#include "./param.h"
17+
#include "xgboost/data.h"
18+
#include "xgboost/parameter.h"
1819

1920
namespace xgboost {
2021
namespace linear {
@@ -64,7 +65,11 @@ inline double CoordinateDelta(double sum_grad, double sum_hess, double w,
6465
* \return The weight update.
6566
*/
6667
inline double CoordinateDeltaBias(double sum_grad, double sum_hess) {
67-
return -sum_grad / sum_hess;
68+
auto b = -sum_grad / sum_hess;
69+
if (std::isnan(b) || std::isinf(b)) {
70+
b = 0;
71+
}
72+
return b;
6873
}
6974

7075
/**

src/linear/updater_coordinate.cc

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2018-2023 by XGBoost Contributors
2+
* Copyright 2018-2025, XGBoost Contributors
33
* \author Rory Mitchell
44
*/
55

@@ -49,21 +49,21 @@ class CoordinateUpdater : public LinearUpdater {
4949
double sum_instance_weight) override {
5050
auto gpair = in_gpair->Data();
5151
tparam_.DenormalizePenalties(sum_instance_weight);
52-
const int ngroup = model->learner_model_param->num_output_group;
52+
auto ngroup = model->learner_model_param->num_output_group;
5353
// update bias
54-
for (int group_idx = 0; group_idx < ngroup; ++group_idx) {
54+
for (decltype(ngroup) group_idx = 0; group_idx < ngroup; ++group_idx) {
5555
auto grad = GetBiasGradientParallel(group_idx, ngroup, gpair->ConstHostVector(), p_fmat,
5656
ctx_->Threads());
57-
auto dbias = static_cast<float>(tparam_.learning_rate *
58-
CoordinateDeltaBias(grad.first, grad.second));
57+
auto dbias =
58+
static_cast<float>(tparam_.learning_rate * CoordinateDeltaBias(grad.first, grad.second));
5959
model->Bias()[group_idx] += dbias;
6060
UpdateBiasResidualParallel(ctx_, group_idx, ngroup, dbias, &gpair->HostVector(), p_fmat);
6161
}
6262
// prepare for updating the weights
6363
selector_->Setup(ctx_, *model, gpair->ConstHostVector(), p_fmat, tparam_.reg_alpha_denorm,
6464
tparam_.reg_lambda_denorm, cparam_.top_k);
6565
// update weights
66-
for (int group_idx = 0; group_idx < ngroup; ++group_idx) {
66+
for (decltype(ngroup) group_idx = 0; group_idx < ngroup; ++group_idx) {
6767
for (unsigned i = 0U; i < model->learner_model_param->num_feature; i++) {
6868
int fidx =
6969
selector_->NextFeature(ctx_, i, *model, group_idx, gpair->ConstHostVector(), p_fmat,

src/tree/io_utils.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
/**
2-
* Copyright 2023-2024, XGBoost Contributors
2+
* Copyright 2023-2025, XGBoost Contributors
33
*/
44
#ifndef XGBOOST_TREE_IO_UTILS_H_
55
#define XGBOOST_TREE_IO_UTILS_H_
6-
#include <string> // for string
7-
#include <type_traits> // for enable_if_t, is_same_v, conditional_t
8-
#include <vector> // for vector
6+
#include <limits> // for numeric_limits
7+
#include <string> // for string
8+
#include <type_traits> // for enable_if_t, is_same_v, conditional_t
9+
#include <vector> // for vector
910

1011
#include "xgboost/json.h" // for Json
1112

@@ -59,5 +60,7 @@ inline std::string const kParent{"parents"};
5960
inline std::string const kLeft{"left_children"};
6061
inline std::string const kRight{"right_children"};
6162
} // namespace tree_field
63+
64+
constexpr float DftBadValue() { return std::numeric_limits<float>::denorm_min(); }
6265
} // namespace xgboost
6366
#endif // XGBOOST_TREE_IO_UTILS_H_

src/tree/multi_target_tree_model.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ MultiTargetTree::MultiTargetTree(TreeParam const* param)
2525
parent_(1ul, InvalidNodeId()),
2626
split_index_(1ul, 0),
2727
default_left_(1ul, 0),
28-
split_conds_(1ul, std::numeric_limits<float>::quiet_NaN()),
29-
weights_(param->size_leaf_vector, std::numeric_limits<float>::quiet_NaN()) {
28+
split_conds_(1ul, DftBadValue()),
29+
weights_(param->size_leaf_vector, DftBadValue()) {
3030
CHECK_GT(param_->size_leaf_vector, 1);
3131
}
3232

@@ -222,7 +222,7 @@ void MultiTargetTree::Expand(bst_node_t nidx, bst_feature_t split_idx, float spl
222222
split_index_.Resize(n);
223223
split_index_.HostVector()[nidx] = split_idx;
224224

225-
split_conds_.Resize(n, std::numeric_limits<float>::quiet_NaN());
225+
split_conds_.Resize(n, DftBadValue());
226226
split_conds_.HostVector()[nidx] = split_cond;
227227

228228
default_left_.Resize(n);

src/tree/tree_model.cc

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -891,24 +891,23 @@ void RegTree::ExpandNode(bst_node_t nidx, bst_feature_t split_index, float split
891891
this->param_.num_nodes = this->p_mt_tree_->Size();
892892
}
893893

894-
void RegTree::ExpandCategorical(bst_node_t nid, bst_feature_t split_index,
895-
common::Span<const uint32_t> split_cat, bool default_left,
896-
bst_float base_weight, bst_float left_leaf_weight,
897-
bst_float right_leaf_weight, bst_float loss_change, float sum_hess,
898-
float left_sum, float right_sum) {
894+
void RegTree::ExpandCategorical(bst_node_t nidx, bst_feature_t split_index,
895+
common::Span<common::KCatBitField::value_type> split_cat,
896+
bool default_left, bst_float base_weight,
897+
bst_float left_leaf_weight, bst_float right_leaf_weight,
898+
bst_float loss_change, float sum_hess, float left_sum,
899+
float right_sum) {
899900
CHECK(!IsMultiTarget());
900-
this->ExpandNode(nid, split_index, std::numeric_limits<float>::quiet_NaN(),
901-
default_left, base_weight,
902-
left_leaf_weight, right_leaf_weight, loss_change, sum_hess,
903-
left_sum, right_sum);
901+
this->ExpandNode(nidx, split_index, DftBadValue(), default_left, base_weight, left_leaf_weight,
902+
right_leaf_weight, loss_change, sum_hess, left_sum, right_sum);
904903

905904
size_t orig_size = split_categories_.size();
906905
this->split_categories_.resize(orig_size + split_cat.size());
907906
std::copy(split_cat.data(), split_cat.data() + split_cat.size(),
908907
split_categories_.begin() + orig_size);
909-
this->split_types_.at(nid) = FeatureType::kCategorical;
910-
this->split_categories_segments_.at(nid).beg = orig_size;
911-
this->split_categories_segments_.at(nid).size = split_cat.size();
908+
this->split_types_.at(nidx) = FeatureType::kCategorical;
909+
this->split_categories_segments_.at(nidx).beg = orig_size;
910+
this->split_categories_segments_.at(nidx).size = split_cat.size();
912911
}
913912

914913
void RegTree::Load(dmlc::Stream* fi) {

tests/cpp/tree/test_tree_model.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
/**
2-
* Copyright 2018-2024, XGBoost Contributors
2+
* Copyright 2018-2025, XGBoost Contributors
33
*/
44
#include <gtest/gtest.h>
55

66
#include "../../../src/common/bitfield.h"
77
#include "../../../src/common/categorical.h"
8+
#include "../../../src/tree/io_utils.h" // for DftBadValue
89
#include "../filesystem.h"
910
#include "../helpers.h"
1011
#include "xgboost/tree_model.h"
@@ -166,7 +167,7 @@ TEST(Tree, ExpandCategoricalFeature) {
166167
ASSERT_EQ(tree.GetSplitTypes()[1], FeatureType::kNumerical);
167168
ASSERT_EQ(tree.GetSplitTypes()[2], FeatureType::kNumerical);
168169
ASSERT_EQ(tree.GetSplitCategories().size(), 0ul);
169-
ASSERT_TRUE(std::isnan(tree[0].SplitCond()));
170+
ASSERT_EQ(tree[0].SplitCond(), DftBadValue());
170171
}
171172
{
172173
RegTree tree;

tests/cpp/tree/test_tree_stat.cc

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2020-2024, XGBoost Contributors
2+
* Copyright 2020-2025, XGBoost Contributors
33
*/
44
#include <gtest/gtest.h>
55
#include <xgboost/context.h> // for Context
@@ -9,7 +9,8 @@
99

1010
#include <memory> // for unique_ptr
1111

12-
#include "../../../src/tree/param.h" // for TrainParam
12+
#include "../../../src/tree/io_utils.h" // for DftBadValue
13+
#include "../../../src/tree/param.h" // for TrainParam
1314
#include "../helpers.h"
1415

1516
namespace xgboost {
@@ -127,8 +128,8 @@ class TestSplitWithEta : public ::testing::Test {
127128
for (std::size_t i = 0; i < leaf_0.Size(); ++i) {
128129
CHECK_EQ(leaf_0(i) * eta_ratio, leaf_1(i));
129130
}
130-
CHECK(std::isnan(p_tree0->SplitCond(nidx)));
131-
CHECK(std::isnan(p_tree1->SplitCond(nidx)));
131+
CHECK_EQ(DftBadValue(), p_tree0->SplitCond(nidx));
132+
CHECK_EQ(DftBadValue(), p_tree1->SplitCond(nidx));
132133
} else {
133134
// NON-mt tree reuses split cond for leaf value.
134135
auto leaf_0 = p_tree0->SplitCond(nidx);

tests/python/test_linear.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Dict
2+
13
from hypothesis import given, note, settings, strategies
24

35
import xgboost as xgb
@@ -20,8 +22,8 @@
2022
})
2123

2224

23-
def train_result(param, dmat, num_rounds):
24-
result = {}
25+
def train_result(param: dict, dmat: xgb.DMatrix, num_rounds: int) -> Dict[str, Dict]:
26+
result: Dict[str, Dict] = {}
2527
xgb.train(
2628
param,
2729
dmat,

0 commit comments

Comments
 (0)