Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions src/tree/tree_model.cc
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ std::string ToStr(linalg::VectorView<Float> value, bst_target_t truncate_limit =
ss << value(value.Size() - 1) << "]";
return ss.str();
}

std::string ToJsonStr(float value) {
if (std::isfinite(value)) {
return ToStr(value);
}
// The stream writes `inf` and `nan`, which are not JSON. Write them as the model JSON does.
std::string str;
Json::Dump(Json{JsonNumber{value}}, &str);
return str;
}
} // namespace

/**
Expand Down Expand Up @@ -406,6 +416,9 @@ class JsonGenerator : public TreeGenerator<TreeView> {

std::string Integer(TreeView tree, int32_t nid, uint32_t depth) const override {
auto cond = tree.SplitCond(nid);
if (!std::isfinite(cond)) {
return this->Quantitive(tree, nid, depth); // No integer form, write it as a float.
}
const bst_float floored = std::floor(cond);
const int32_t integer_threshold =
(floored == cond) ? static_cast<int32_t>(floored) : static_cast<int32_t>(floored) + 1;
Expand All @@ -422,7 +435,7 @@ class JsonGenerator : public TreeGenerator<TreeView> {
R"I("split_condition": {cond}, "yes": {left}, "no": {right}, )I"
R"I("missing": {missing})I";
bst_float cond = tree.SplitCond(nid);
return SplitNodeImpl(tree, nid, kQuantitiveTemplate, ToStr(cond), depth);
return SplitNodeImpl(tree, nid, kQuantitiveTemplate, ToJsonStr(cond), depth);
}

std::string PlainNode(TreeView tree, int32_t nid, uint32_t depth) const override {
Expand All @@ -431,7 +444,7 @@ class JsonGenerator : public TreeGenerator<TreeView> {
R"I( "nodeid": {nid}, "depth": {depth}, "split": "{fname}", )I"
R"I("split_condition": {cond}, "yes": {left}, "no": {right}, )I"
R"I("missing": {missing})I";
return SplitNodeImpl(tree, nid, kNodeTemplate, ToStr(cond), depth);
return SplitNodeImpl(tree, nid, kNodeTemplate, ToJsonStr(cond), depth);
}

std::string NodeStat(TreeView tree, int32_t nid) const override {
Expand Down
18 changes: 18 additions & 0 deletions tests/cpp/tree/test_tree_model.cc
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,24 @@ TEST(Tree, DumpText) {

TEST(Tree, DumpTextCategorical) { TestCategoricalTreeDump("text", ","); }

TEST(Tree, DumpNonFiniteSplit) {
// GPU hist splits at the `-inf` lower bound of the first bin, sending every value right.
auto constexpr kNegInf = -std::numeric_limits<float>::infinity();
RegTree tree;
tree.Expand({{0, 0, kNegInf, true}, {0.0f, 0.0f}, {0.0f, 0.0f}, {0.0f, 0.0f}, 0.0f});
tree.FinalizeLeaves(1.0f);

FeatureMap fmap;
auto str = tree.DumpModel(fmap, false, "json");
auto j_tree = Json::Load({str.c_str(), str.size()});
ASSERT_EQ(get<Number const>(j_tree["split_condition"]), kNegInf);

fmap.PushBack(0, "feat_0", "int");
str = tree.DumpModel(fmap, false, "json");
j_tree = Json::Load({str.c_str(), str.size()});
ASSERT_EQ(get<Number const>(j_tree["split_condition"]), kNegInf);
}

TEST(Tree, DumpDot) {
auto tree = ConstructTree();
FeatureMap fmap;
Expand Down
Loading