Extract interaction constraint from split evaluator. (#5034)
* Extract interaction constraints from split evaluator. The reason for doing so is mostly for model IO, where num_feature and interaction_constraints are copied in split evaluator. Also interaction constraint by itself is a feature selector, acting like column sampler and it's inefficient to bury it deep in the evaluator chain. Lastly removing one another copied parameter is a win. * Enable inc for approx tree method. As now the implementation is spited up from evaluator class, it's also enabled for approx method. * Removing obsoleted code in colmaker. They are never documented nor actually used in real world. Also there isn't a single test for those code blocks. * Unifying the types used for row and column. As the size of input dataset is marching to billion, incorrect use of int is subject to overflow, also singed integer overflow is undefined behaviour. This PR starts the procedure for unifying used index type to unsigned integers. There's optimization that can utilize this undefined behaviour, but after some testings I don't see the optimization is beneficial to XGBoost.
This commit is contained in:
@@ -56,7 +56,7 @@ void TestDeviceSketch(bool use_external_memory) {
|
||||
size_t row_stride = DeviceSketch(device, max_bin, gpu_batch_nrows, dmat->get(), &hmat_gpu);
|
||||
|
||||
// compare the row stride with the one obtained from the dmatrix
|
||||
size_t expected_row_stride = 0;
|
||||
bst_row_t expected_row_stride = 0;
|
||||
for (const auto &batch : dmat->get()->GetBatches<xgboost::SparsePage>()) {
|
||||
const auto &offset_vec = batch.offset.ConstHostVector();
|
||||
for (int i = 1; i <= offset_vec.size() -1; ++i) {
|
||||
|
||||
@@ -55,7 +55,7 @@ TEST(ColumnSampler, ThreadSynchronisation) {
|
||||
int n = 128;
|
||||
size_t iterations = 10;
|
||||
size_t levels = 5;
|
||||
std::vector<int> reference_result;
|
||||
std::vector<bst_feature_t> reference_result;
|
||||
bool success =
|
||||
true; // Cannot use google test asserts in multithreaded region
|
||||
#pragma omp parallel num_threads(num_threads)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
namespace xgboost {
|
||||
TEST(SparsePage, PushCSC) {
|
||||
std::vector<size_t> offset {0};
|
||||
std::vector<bst_row_t> offset {0};
|
||||
std::vector<Entry> data;
|
||||
SparsePage page;
|
||||
page.offset.HostVector() = offset;
|
||||
|
||||
@@ -99,7 +99,7 @@ TEST(MetaInfo, LoadQid) {
|
||||
const std::vector<xgboost::bst_uint> expected_group_ptr{0, 4, 8, 12};
|
||||
CHECK(info.group_ptr_ == expected_group_ptr);
|
||||
|
||||
const std::vector<size_t> expected_offset{
|
||||
const std::vector<xgboost::bst_row_t> expected_offset{
|
||||
0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60
|
||||
};
|
||||
const std::vector<xgboost::Entry> expected_data{
|
||||
|
||||
@@ -249,7 +249,7 @@ inline std::unique_ptr<EllpackPageImpl> BuildEllpackPage(
|
||||
0.26f, 0.71f, 1.83f});
|
||||
cmat.SetMins({0.1f, 0.2f, 0.3f, 0.1f, 0.2f, 0.3f, 0.2f, 0.2f});
|
||||
|
||||
size_t row_stride = 0;
|
||||
bst_row_t row_stride = 0;
|
||||
const auto &offset_vec = batch.offset.ConstHostVector();
|
||||
for (size_t i = 1; i < offset_vec.size(); ++i) {
|
||||
row_stride = std::max(row_stride, offset_vec[i] - offset_vec[i-1]);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
int main(int argc, char ** argv) {
|
||||
xgboost::Args args {{"verbosity", "2"}};
|
||||
xgboost::ConsoleLogger::Configure(args);
|
||||
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
testing::FLAGS_gtest_death_test_style = "threadsafe";
|
||||
return RUN_ALL_TESTS();
|
||||
|
||||
@@ -91,7 +91,7 @@ void TestUpdatePosition() {
|
||||
EXPECT_EQ(rp.GetRows(3).size(), 2);
|
||||
EXPECT_EQ(rp.GetRows(4).size(), 3);
|
||||
// Check position is as expected
|
||||
EXPECT_EQ(rp.GetPositionHost(), std::vector<RowPartitioner::TreePositionT>({3,3,4,4,4,2,2,2,2,2}));
|
||||
EXPECT_EQ(rp.GetPositionHost(), std::vector<bst_node_t>({3,3,4,4,4,2,2,2,2,2}));
|
||||
}
|
||||
|
||||
TEST(RowPartitioner, Basic) { TestUpdatePosition(); }
|
||||
|
||||
60
tests/cpp/tree/test_constraints.cc
Normal file
60
tests/cpp/tree/test_constraints.cc
Normal file
@@ -0,0 +1,60 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <xgboost/base.h>
|
||||
#include <xgboost/logging.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "../../../src/tree/constraints.h"
|
||||
|
||||
namespace xgboost {
|
||||
namespace tree {
|
||||
|
||||
TEST(CPUFeatureInteractionConstraint, Empty) {
|
||||
TrainParam param;
|
||||
param.UpdateAllowUnknown(Args{});
|
||||
bst_feature_t constexpr kFeatures = 6;
|
||||
|
||||
FeatureInteractionConstraintHost constraints;
|
||||
constraints.Configure(param, kFeatures);
|
||||
|
||||
// no-op
|
||||
constraints.Split(/*node_id=*/0, /*feature_id=*/2, /*left_id=*/1, /*right_id=*/2);
|
||||
|
||||
std::vector<bst_feature_t> h_input_feature_list {0, 1, 2, 3, 4, 5};
|
||||
common::Span<bst_feature_t> s_input_feature_list = common::Span<bst_feature_t>{h_input_feature_list};
|
||||
|
||||
for (auto f : h_input_feature_list) {
|
||||
constraints.Query(f, 1);
|
||||
}
|
||||
|
||||
// no-op
|
||||
ASSERT_TRUE(constraints.Query(94389, 12309));
|
||||
}
|
||||
|
||||
TEST(CPUFeatureInteractionConstraint, Basic) {
|
||||
std::string const constraints_str = R"constraint([[1, 2], [2, 3, 4]])constraint";
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> args{
|
||||
{"interaction_constraints", constraints_str}};
|
||||
TrainParam param;
|
||||
param.interaction_constraints = constraints_str;
|
||||
bst_feature_t constexpr kFeatures = 6;
|
||||
|
||||
FeatureInteractionConstraintHost constraints;
|
||||
constraints.Configure(param, kFeatures);
|
||||
constraints.Split(/*node_id=*/0, /*feature_id=*/2, /*left_id=*/1, /*right_id=*/2);
|
||||
|
||||
std::vector<bst_feature_t> h_input_feature_list{0, 1, 2, 3, 4, 5};
|
||||
|
||||
ASSERT_TRUE(constraints.Query(1, 1));
|
||||
ASSERT_TRUE(constraints.Query(1, 2));
|
||||
ASSERT_TRUE(constraints.Query(1, 3));
|
||||
ASSERT_TRUE(constraints.Query(1, 4));
|
||||
|
||||
ASSERT_FALSE(constraints.Query(1, 0));
|
||||
ASSERT_FALSE(constraints.Query(1, 5));
|
||||
}
|
||||
|
||||
} // namespace tree
|
||||
} // namespace xgboost
|
||||
@@ -19,13 +19,13 @@ struct FConstraintWrapper : public FeatureInteractionConstraint {
|
||||
common::Span<LBitField64> GetNodeConstraints() {
|
||||
return FeatureInteractionConstraint::s_node_constraints_;
|
||||
}
|
||||
FConstraintWrapper(tree::TrainParam param, int32_t n_features) :
|
||||
FConstraintWrapper(tree::TrainParam param, bst_feature_t n_features) :
|
||||
FeatureInteractionConstraint(param, n_features) {}
|
||||
|
||||
dh::device_vector<int32_t> const& GetDSets() const {
|
||||
dh::device_vector<bst_feature_t> const& GetDSets() const {
|
||||
return d_sets_;
|
||||
}
|
||||
dh::device_vector<int32_t> const& GetDSetsPtr() const {
|
||||
dh::device_vector<size_t> const& GetDSetsPtr() const {
|
||||
return d_sets_ptr_;
|
||||
}
|
||||
};
|
||||
@@ -65,7 +65,7 @@ void CompareBitField(LBitField64 d_field, std::set<uint32_t> positions) {
|
||||
} // anonymous namespace
|
||||
|
||||
|
||||
TEST(FeatureInteractionConstraint, Init) {
|
||||
TEST(GPUFeatureInteractionConstraint, Init) {
|
||||
{
|
||||
int32_t constexpr kFeatures = 6;
|
||||
tree::TrainParam param = GetParameter();
|
||||
@@ -123,7 +123,7 @@ TEST(FeatureInteractionConstraint, Init) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FeatureInteractionConstraint, Split) {
|
||||
TEST(GPUFeatureInteractionConstraint, Split) {
|
||||
tree::TrainParam param = GetParameter();
|
||||
int32_t constexpr kFeatures = 6;
|
||||
FConstraintWrapper constraints(param, kFeatures);
|
||||
@@ -152,9 +152,9 @@ TEST(FeatureInteractionConstraint, Split) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FeatureInteractionConstraint, QueryNode) {
|
||||
TEST(GPUFeatureInteractionConstraint, QueryNode) {
|
||||
tree::TrainParam param = GetParameter();
|
||||
int32_t constexpr kFeatures = 6;
|
||||
bst_feature_t constexpr kFeatures = 6;
|
||||
FConstraintWrapper constraints(param, kFeatures);
|
||||
|
||||
{
|
||||
@@ -165,9 +165,9 @@ TEST(FeatureInteractionConstraint, QueryNode) {
|
||||
{
|
||||
constraints.Split(/*node_id=*/ 0, /*feature_id=*/ 1, 1, 2);
|
||||
auto span = constraints.QueryNode(0);
|
||||
std::vector<int32_t> h_result (span.size());
|
||||
thrust::copy(thrust::device_ptr<int32_t>(span.data()),
|
||||
thrust::device_ptr<int32_t>(span.data() + span.size()),
|
||||
std::vector<bst_feature_t> h_result (span.size());
|
||||
thrust::copy(thrust::device_ptr<bst_feature_t>(span.data()),
|
||||
thrust::device_ptr<bst_feature_t>(span.data() + span.size()),
|
||||
h_result.begin());
|
||||
ASSERT_EQ(h_result.size(), 2);
|
||||
ASSERT_EQ(h_result[0], 1);
|
||||
@@ -177,9 +177,9 @@ TEST(FeatureInteractionConstraint, QueryNode) {
|
||||
{
|
||||
constraints.Split(1, /*feature_id=*/0, 3, 4);
|
||||
auto span = constraints.QueryNode(1);
|
||||
std::vector<int32_t> h_result (span.size());
|
||||
thrust::copy(thrust::device_ptr<int32_t>(span.data()),
|
||||
thrust::device_ptr<int32_t>(span.data() + span.size()),
|
||||
std::vector<bst_feature_t> h_result (span.size());
|
||||
thrust::copy(thrust::device_ptr<bst_feature_t>(span.data()),
|
||||
thrust::device_ptr<bst_feature_t>(span.data() + span.size()),
|
||||
h_result.begin());
|
||||
ASSERT_EQ(h_result.size(), 3);
|
||||
ASSERT_EQ(h_result[0], 0);
|
||||
@@ -189,8 +189,8 @@ TEST(FeatureInteractionConstraint, QueryNode) {
|
||||
// same as parent
|
||||
span = constraints.QueryNode(3);
|
||||
h_result.resize(span.size());
|
||||
thrust::copy(thrust::device_ptr<int32_t>(span.data()),
|
||||
thrust::device_ptr<int32_t>(span.data() + span.size()),
|
||||
thrust::copy(thrust::device_ptr<bst_feature_t>(span.data()),
|
||||
thrust::device_ptr<bst_feature_t>(span.data() + span.size()),
|
||||
h_result.begin());
|
||||
ASSERT_EQ(h_result.size(), 3);
|
||||
ASSERT_EQ(h_result[0], 0);
|
||||
@@ -204,9 +204,9 @@ TEST(FeatureInteractionConstraint, QueryNode) {
|
||||
FConstraintWrapper large_features(large_param, 256);
|
||||
large_features.Split(0, 139, 1, 2);
|
||||
auto span = large_features.QueryNode(0);
|
||||
std::vector<int32_t> h_result (span.size());
|
||||
thrust::copy(thrust::device_ptr<int32_t>(span.data()),
|
||||
thrust::device_ptr<int32_t>(span.data() + span.size()),
|
||||
std::vector<bst_feature_t> h_result (span.size());
|
||||
thrust::copy(thrust::device_ptr<bst_feature_t>(span.data()),
|
||||
thrust::device_ptr<bst_feature_t>(span.data() + span.size()),
|
||||
h_result.begin());
|
||||
ASSERT_EQ(h_result.size(), 3);
|
||||
ASSERT_EQ(h_result[0], 1);
|
||||
@@ -217,10 +217,10 @@ TEST(FeatureInteractionConstraint, QueryNode) {
|
||||
|
||||
namespace {
|
||||
|
||||
void CompareFeatureList(common::Span<int32_t> s_output, std::vector<int32_t> solution) {
|
||||
std::vector<int32_t> h_output(s_output.size());
|
||||
thrust::copy(thrust::device_ptr<int32_t>(s_output.data()),
|
||||
thrust::device_ptr<int32_t>(s_output.data() + s_output.size()),
|
||||
void CompareFeatureList(common::Span<bst_feature_t> s_output, std::vector<bst_feature_t> solution) {
|
||||
std::vector<bst_feature_t> h_output(s_output.size());
|
||||
thrust::copy(thrust::device_ptr<bst_feature_t>(s_output.data()),
|
||||
thrust::device_ptr<bst_feature_t>(s_output.data() + s_output.size()),
|
||||
h_output.begin());
|
||||
ASSERT_EQ(h_output.size(), solution.size());
|
||||
for (size_t i = 0; i < solution.size(); ++i) {
|
||||
@@ -230,21 +230,21 @@ void CompareFeatureList(common::Span<int32_t> s_output, std::vector<int32_t> sol
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
TEST(FeatureInteractionConstraint, Query) {
|
||||
TEST(GPUFeatureInteractionConstraint, Query) {
|
||||
{
|
||||
tree::TrainParam param = GetParameter();
|
||||
int32_t constexpr kFeatures = 6;
|
||||
bst_feature_t constexpr kFeatures = 6;
|
||||
FConstraintWrapper constraints(param, kFeatures);
|
||||
std::vector<int32_t> h_input_feature_list {0, 1, 2, 3, 4, 5};
|
||||
dh::device_vector<int32_t> d_input_feature_list (h_input_feature_list);
|
||||
common::Span<int32_t> s_input_feature_list = dh::ToSpan(d_input_feature_list);
|
||||
std::vector<bst_feature_t> h_input_feature_list {0, 1, 2, 3, 4, 5};
|
||||
dh::device_vector<bst_feature_t> d_input_feature_list (h_input_feature_list);
|
||||
common::Span<bst_feature_t> s_input_feature_list = dh::ToSpan(d_input_feature_list);
|
||||
|
||||
auto s_output = constraints.Query(s_input_feature_list, 0);
|
||||
CompareFeatureList(s_output, h_input_feature_list);
|
||||
}
|
||||
{
|
||||
tree::TrainParam param = GetParameter();
|
||||
int32_t constexpr kFeatures = 6;
|
||||
bst_feature_t constexpr kFeatures = 6;
|
||||
FConstraintWrapper constraints(param, kFeatures);
|
||||
constraints.Split(/*node_id=*/0, /*feature_id=*/1, /*left_id=*/1, /*right_id=*/2);
|
||||
constraints.Split(/*node_id=*/1, /*feature_id=*/0, /*left_id=*/3, /*right_id=*/4);
|
||||
@@ -257,16 +257,16 @@ TEST(FeatureInteractionConstraint, Query) {
|
||||
* {split at 0} \
|
||||
* / \
|
||||
* (1)[0, 1, 2] (2)[1, 2]
|
||||
* / \
|
||||
* / {split at 3}
|
||||
* / \
|
||||
* (3)[0, 1, 2] (4)[0, 1, 2, 3, 4, 5]
|
||||
* / \
|
||||
* / {split at 3}
|
||||
* / \
|
||||
* (3)[0, 1, 2] (4)[0, 1, 2, 3, 4, 5]
|
||||
*
|
||||
*/
|
||||
|
||||
std::vector<int32_t> h_input_feature_list {0, 1, 2, 3, 4, 5};
|
||||
dh::device_vector<int32_t> d_input_feature_list (h_input_feature_list);
|
||||
common::Span<int32_t> s_input_feature_list = dh::ToSpan(d_input_feature_list);
|
||||
std::vector<bst_feature_t> h_input_feature_list {0, 1, 2, 3, 4, 5};
|
||||
dh::device_vector<bst_feature_t> d_input_feature_list (h_input_feature_list);
|
||||
common::Span<bst_feature_t> s_input_feature_list = dh::ToSpan(d_input_feature_list);
|
||||
|
||||
auto s_output = constraints.Query(s_input_feature_list, 1);
|
||||
CompareFeatureList(s_output, {0, 1, 2});
|
||||
@@ -285,16 +285,16 @@ TEST(FeatureInteractionConstraint, Query) {
|
||||
// Test shared feature
|
||||
{
|
||||
tree::TrainParam param = GetParameter();
|
||||
int32_t constexpr kFeatures = 6;
|
||||
bst_feature_t constexpr kFeatures = 6;
|
||||
std::string const constraints_str = R"constraint([[1, 2], [2, 3, 4]])constraint";
|
||||
param.interaction_constraints = constraints_str;
|
||||
|
||||
FConstraintWrapper constraints(param, kFeatures);
|
||||
constraints.Split(/*node_id=*/0, /*feature_id=*/2, /*left_id=*/1, /*right_id=*/2);
|
||||
|
||||
std::vector<int32_t> h_input_feature_list {0, 1, 2, 3, 4, 5};
|
||||
dh::device_vector<int32_t> d_input_feature_list (h_input_feature_list);
|
||||
common::Span<int32_t> s_input_feature_list = dh::ToSpan(d_input_feature_list);
|
||||
std::vector<bst_feature_t> h_input_feature_list {0, 1, 2, 3, 4, 5};
|
||||
dh::device_vector<bst_feature_t> d_input_feature_list (h_input_feature_list);
|
||||
common::Span<bst_feature_t> s_input_feature_list = dh::ToSpan(d_input_feature_list);
|
||||
|
||||
auto s_output = constraints.Query(s_input_feature_list, 1);
|
||||
CompareFeatureList(s_output, {1, 2, 3, 4});
|
||||
@@ -303,13 +303,13 @@ TEST(FeatureInteractionConstraint, Query) {
|
||||
// Test choosing free feature in root
|
||||
{
|
||||
tree::TrainParam param = GetParameter();
|
||||
int32_t constexpr kFeatures = 6;
|
||||
bst_feature_t constexpr kFeatures = 6;
|
||||
std::string const constraints_str = R"constraint([[0, 1]])constraint";
|
||||
param.interaction_constraints = constraints_str;
|
||||
FConstraintWrapper constraints(param, kFeatures);
|
||||
std::vector<int32_t> h_input_feature_list {0, 1, 2, 3, 4, 5};
|
||||
dh::device_vector<int32_t> d_input_feature_list (h_input_feature_list);
|
||||
common::Span<int32_t> s_input_feature_list = dh::ToSpan(d_input_feature_list);
|
||||
std::vector<bst_feature_t> h_input_feature_list {0, 1, 2, 3, 4, 5};
|
||||
dh::device_vector<bst_feature_t> d_input_feature_list (h_input_feature_list);
|
||||
common::Span<bst_feature_t> s_input_feature_list = dh::ToSpan(d_input_feature_list);
|
||||
constraints.Split(/*node_id=*/0, /*feature_id=*/2, /*left_id=*/1, /*right_id=*/2);
|
||||
auto s_output = constraints.Query(s_input_feature_list, 1);
|
||||
CompareFeatureList(s_output, {2});
|
||||
|
||||
@@ -100,7 +100,7 @@ void TestBuildHist(bool use_shared_memory_histograms) {
|
||||
auto page = BuildEllpackPage(kNRows, kNCols);
|
||||
GPUHistMakerDevice<GradientSumT> maker(0, page.get(), kNRows, param, kNCols, kNCols);
|
||||
maker.InitHistogram();
|
||||
|
||||
|
||||
xgboost::SimpleLCG gen;
|
||||
xgboost::SimpleRealUniformDistribution<bst_float> dist(0.0f, 1.0f);
|
||||
std::vector<GradientPair> h_gpair(kNRows);
|
||||
|
||||
69
tests/cpp/tree/test_histmaker.cc
Normal file
69
tests/cpp/tree/test_histmaker.cc
Normal file
@@ -0,0 +1,69 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <xgboost/tree_model.h>
|
||||
#include <xgboost/tree_updater.h>
|
||||
|
||||
#include "../helpers.h"
|
||||
|
||||
namespace xgboost {
|
||||
namespace tree {
|
||||
|
||||
TEST(GrowHistMaker, InteractionConstraint) {
|
||||
size_t constexpr kRows = 32;
|
||||
size_t constexpr kCols = 16;
|
||||
|
||||
GenericParameter param;
|
||||
param.UpdateAllowUnknown(Args{{"gpu_id", "0"}});
|
||||
|
||||
auto pp_dmat = CreateDMatrix(kRows, kCols, 0.6, 3);
|
||||
auto p_dmat = *pp_dmat;
|
||||
|
||||
HostDeviceVector<GradientPair> gradients (kRows);
|
||||
std::vector<GradientPair>& h_gradients = gradients.HostVector();
|
||||
|
||||
xgboost::SimpleLCG gen;
|
||||
xgboost::SimpleRealUniformDistribution<bst_float> dist(0.0f, 1.0f);
|
||||
|
||||
for (size_t i = 0; i < kRows; ++i) {
|
||||
bst_float grad = dist(&gen);
|
||||
bst_float hess = dist(&gen);
|
||||
h_gradients[i] = GradientPair(grad, hess);
|
||||
}
|
||||
|
||||
{
|
||||
// With constraints
|
||||
RegTree tree;
|
||||
tree.param.num_feature = kCols;
|
||||
|
||||
std::unique_ptr<TreeUpdater> updater { TreeUpdater::Create("grow_histmaker", ¶m) };
|
||||
updater->Configure(Args{
|
||||
{"interaction_constraints", "[[0, 1]]"},
|
||||
{"num_feature", std::to_string(kCols)}});
|
||||
updater->Update(&gradients, p_dmat.get(), {&tree});
|
||||
|
||||
ASSERT_EQ(tree.NumExtraNodes(), 4);
|
||||
ASSERT_EQ(tree[0].SplitIndex(), 1);
|
||||
|
||||
ASSERT_EQ(tree[tree[0].LeftChild()].SplitIndex(), 0);
|
||||
ASSERT_EQ(tree[tree[0].RightChild()].SplitIndex(), 0);
|
||||
}
|
||||
{
|
||||
// Without constraints
|
||||
RegTree tree;
|
||||
tree.param.num_feature = kCols;
|
||||
|
||||
std::unique_ptr<TreeUpdater> updater { TreeUpdater::Create("grow_histmaker", ¶m) };
|
||||
updater->Configure(Args{{"num_feature", std::to_string(kCols)}});
|
||||
updater->Update(&gradients, p_dmat.get(), {&tree});
|
||||
|
||||
ASSERT_EQ(tree.NumExtraNodes(), 10);
|
||||
ASSERT_EQ(tree[0].SplitIndex(), 1);
|
||||
|
||||
ASSERT_NE(tree[tree[0].LeftChild()].SplitIndex(), 0);
|
||||
ASSERT_NE(tree[tree[0].RightChild()].SplitIndex(), 0);
|
||||
}
|
||||
delete pp_dmat;
|
||||
}
|
||||
|
||||
} // namespace tree
|
||||
} // namespace xgboost
|
||||
@@ -25,8 +25,9 @@ class QuantileHistMock : public QuantileHistMaker {
|
||||
|
||||
BuilderMock(const TrainParam& param,
|
||||
std::unique_ptr<TreeUpdater> pruner,
|
||||
std::unique_ptr<SplitEvaluator> spliteval)
|
||||
: RealImpl(param, std::move(pruner), std::move(spliteval)) {}
|
||||
std::unique_ptr<SplitEvaluator> spliteval,
|
||||
FeatureInteractionConstraintHost int_constraint)
|
||||
: RealImpl(param, std::move(pruner), std::move(spliteval), std::move(int_constraint)) {}
|
||||
|
||||
public:
|
||||
void TestInitData(const GHistIndexMatrix& gmat,
|
||||
@@ -238,7 +239,8 @@ class QuantileHistMock : public QuantileHistMaker {
|
||||
new BuilderMock(
|
||||
param_,
|
||||
std::move(pruner_),
|
||||
std::unique_ptr<SplitEvaluator>(spliteval_->GetHostClone())));
|
||||
std::unique_ptr<SplitEvaluator>(spliteval_->GetHostClone()),
|
||||
int_constraint_));
|
||||
dmat_ = CreateDMatrix(kNRows, kNCols, 0.8, 3);
|
||||
}
|
||||
~QuantileHistMock() override { delete dmat_; }
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <xgboost/logging.h>
|
||||
#include <memory>
|
||||
#include "../../../src/tree/split_evaluator.h"
|
||||
|
||||
namespace xgboost {
|
||||
namespace tree {
|
||||
|
||||
TEST(SplitEvaluator, Interaction) {
|
||||
std::string constraints_str = R"interaction([[0, 1], [1, 2, 3]])interaction";
|
||||
std::vector<std::pair<std::string, std::string>> args{
|
||||
{"interaction_constraints", constraints_str},
|
||||
{"num_feature", "8"}};
|
||||
{
|
||||
std::unique_ptr<SplitEvaluator> eval{
|
||||
SplitEvaluator::Create("elastic_net,interaction")};
|
||||
eval->Init(args);
|
||||
|
||||
eval->AddSplit(0, 1, 2, /*feature_id=*/4, 0, 0);
|
||||
eval->AddSplit(2, 3, 4, /*feature_id=*/5, 0, 0);
|
||||
ASSERT_FALSE(eval->CheckFeatureConstraint(2, /*feature_id=*/0));
|
||||
ASSERT_FALSE(eval->CheckFeatureConstraint(2, /*feature_id=*/1));
|
||||
|
||||
ASSERT_TRUE(eval->CheckFeatureConstraint(2, /*feature_id=*/4));
|
||||
ASSERT_FALSE(eval->CheckFeatureConstraint(2, /*feature_id=*/5));
|
||||
|
||||
std::vector<int32_t> accepted_features; // for node 3
|
||||
for (int32_t f = 0; f < 8; ++f) {
|
||||
if (eval->CheckFeatureConstraint(3, f)) {
|
||||
accepted_features.emplace_back(f);
|
||||
}
|
||||
}
|
||||
std::vector<int32_t> solutions{4, 5};
|
||||
ASSERT_EQ(accepted_features.size(), solutions.size());
|
||||
for (size_t f = 0; f < accepted_features.size(); ++f) {
|
||||
ASSERT_EQ(accepted_features[f], solutions[f]);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_ptr<SplitEvaluator> eval{
|
||||
SplitEvaluator::Create("elastic_net,interaction")};
|
||||
eval->Init(args);
|
||||
eval->AddSplit(/*node_id=*/0, /*left_id=*/1, /*right_id=*/2, /*feature_id=*/4, 0, 0);
|
||||
std::vector<int32_t> accepted_features; // for node 1
|
||||
for (int32_t f = 0; f < 8; ++f) {
|
||||
if (eval->CheckFeatureConstraint(1, f)) {
|
||||
accepted_features.emplace_back(f);
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(accepted_features.size(), 1);
|
||||
ASSERT_EQ(accepted_features[0], 4);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace tree
|
||||
} // namespace xgboost
|
||||
Reference in New Issue
Block a user