diff --git a/doc/model.schema b/doc/model.schema index 8233d550905a..d20d9abb2ccf 100644 --- a/doc/model.schema +++ b/doc/model.schema @@ -168,6 +168,9 @@ "num_trees": { "type": "string" }, + "num_parallel_tree": { + "type": "string" + }, "size_leaf_vector": { "type": "string" } diff --git a/python-package/xgboost/core.py b/python-package/xgboost/core.py index 936b860232fc..88ee19ce6af4 100644 --- a/python-package/xgboost/core.py +++ b/python-package/xgboost/core.py @@ -78,11 +78,14 @@ def from_cstr_to_pystr(data: CStrPptr, length: c_bst_ulong) -> List[str]: return res +IterRange = TypeVar("IterRange", Optional[Tuple[int, int]], Tuple[int, int]) + + def _convert_ntree_limit( booster: "Booster", ntree_limit: Optional[int], - iteration_range: Optional[Tuple[int, int]] -) -> Optional[Tuple[int, int]]: + iteration_range: IterRange +) -> IterRange: if ntree_limit is not None and ntree_limit != 0: warnings.warn( "ntree_limit is deprecated, use `iteration_range` or model " @@ -1292,16 +1295,23 @@ def _get_booster_layer_trees(model: "Booster") -> Tuple[int, int]: num_parallel_tree = 0 elif booster == "dart": num_parallel_tree = int( - config["learner"]["gradient_booster"]["gbtree"]["gbtree_train_param"][ + config["learner"]["gradient_booster"]["gbtree"]["gbtree_model_param"][ "num_parallel_tree" ] ) elif booster == "gbtree": - num_parallel_tree = int( - config["learner"]["gradient_booster"]["gbtree_train_param"][ - "num_parallel_tree" - ] - ) + try: + num_parallel_tree = int( + config["learner"]["gradient_booster"]["gbtree_model_param"][ + "num_parallel_tree" + ] + ) + except KeyError: + num_parallel_tree = int( + config["learner"]["gradient_booster"]["gbtree_train_param"][ + "num_parallel_tree" + ] + ) else: raise ValueError(f"Unknown booster: {booster}") num_groups = int(config["learner"]["learner_model_param"]["num_class"]) diff --git a/src/c_api/c_api_utils.h b/src/c_api/c_api_utils.h index 1a004bcd2f0f..5a6f5ac5e09e 100644 --- a/src/c_api/c_api_utils.h +++ b/src/c_api/c_api_utils.h @@ -129,18 +129,16 @@ inline uint32_t GetIterationFromTreeLimit(uint32_t ntree_limit, Learner *learner Json config{Object()}; learner->SaveConfig(&config); - auto const &booster = - get(config["learner"]["gradient_booster"]["name"]); + auto const &booster = get(config["learner"]["gradient_booster"]["name"]); if (booster == "gblinear") { num_parallel_tree = 0; } else if (booster == "dart") { - num_parallel_tree = std::stoi( - get(config["learner"]["gradient_booster"]["gbtree"] - ["gbtree_train_param"]["num_parallel_tree"])); + num_parallel_tree = + std::stoi(get(config["learner"]["gradient_booster"]["gbtree"] + ["gbtree_model_param"]["num_parallel_tree"])); } else if (booster == "gbtree") { num_parallel_tree = std::stoi(get( - (config["learner"]["gradient_booster"]["gbtree_train_param"] - ["num_parallel_tree"]))); + (config["learner"]["gradient_booster"]["gbtree_model_param"]["num_parallel_tree"]))); } else { LOG(FATAL) << "Unknown booster:" << booster; } diff --git a/src/common/quantile.cc b/src/common/quantile.cc index 44e4178ef55f..2e9abb8f20b4 100644 --- a/src/common/quantile.cc +++ b/src/common/quantile.cc @@ -429,9 +429,11 @@ void SketchContainerImpl::AllReduce( this->GatherSketchInfo(reduced, &worker_segments, &sketches_scan, &global_sketches); std::vector final_sketches(n_columns); - QuantileAllreduce allreduce_result{global_sketches, worker_segments, - sketches_scan, n_columns}; + ParallelFor(n_columns, n_threads_, [&](auto fidx) { + // gcc raises subobject-linkage warning if we put allreduce_result as lambda capture + QuantileAllreduce allreduce_result{global_sketches, worker_segments, + sketches_scan, n_columns}; int32_t intermediate_num_cuts = num_cuts[fidx]; auto nbytes = WQSketch::SummaryContainer::CalcMemCost(intermediate_num_cuts); if (IsCat(feature_types_, fidx)) { diff --git a/src/gbm/gbtree.cc b/src/gbm/gbtree.cc index 357a25383ee6..ec611ee95a68 100644 --- a/src/gbm/gbtree.cc +++ b/src/gbm/gbtree.cc @@ -323,7 +323,7 @@ void GBTree::BoostNewTrees(HostDeviceVector* gpair, std::vector new_trees; ret->clear(); // create the trees - for (int i = 0; i < tparam_.num_parallel_tree; ++i) { + for (int i = 0; i < model_.param.num_parallel_tree; ++i) { if (tparam_.process_type == TreeProcessType::kDefault) { CHECK(!updaters_.front()->CanModifyTree()) << "Updater: `" << updaters_.front()->Name() << "` " @@ -347,7 +347,7 @@ void GBTree::BoostNewTrees(HostDeviceVector* gpair, << "boosting rounds can not exceed previous training rounds"; // move an existing tree from trees_to_update auto t = std::move(model_.trees_to_update[model_.trees.size() + - bst_group * tparam_.num_parallel_tree + i]); + bst_group * model_.param.num_parallel_tree + i]); new_trees.push_back(t.get()); ret->push_back(std::move(t)); } @@ -414,6 +414,10 @@ void GBTree::SaveConfig(Json* p_out) const { // e.g. updating a model, then saving and loading it would result in an empty // model out["gbtree_train_param"]["process_type"] = String("default"); + // Duplicated from SaveModel so that user can get `num_parallel_tree` without parsing + // the model. We might remove this once we can deprecate `best_ntree_limit` so that the + // language binding doesn't need to know about the forest size. + out["gbtree_model_param"] = ToJson(model_.param); out["updater"] = Object(); @@ -460,6 +464,7 @@ void GBTree::Slice(int32_t layer_begin, int32_t layer_end, int32_t step, std::vector &out_trees_info = out_model.tree_info; out_trees_info.resize(layer_trees * n_layers); out_model.param.num_trees = out_model.trees.size(); + out_model.param.num_parallel_tree = model_.param.num_parallel_tree; if (!this->model_.trees_to_update.empty()) { CHECK_EQ(this->model_.trees_to_update.size(), this->model_.trees.size()) << "Not all trees are updated, " @@ -512,8 +517,7 @@ void GBTree::PredictBatch(DMatrix* p_fmat, } uint32_t tree_begin, tree_end; - std::tie(tree_begin, tree_end) = - detail::LayerToTree(model_, tparam_, layer_begin, layer_end); + std::tie(tree_begin, tree_end) = detail::LayerToTree(model_, layer_begin, layer_end); CHECK_LE(tree_end, model_.trees.size()) << "Invalid number of trees."; if (tree_end > tree_begin) { predictor->PredictBatch(p_fmat, out_preds, model_, tree_begin, tree_end); @@ -723,8 +727,7 @@ class Dart : public GBTree { model_); p_out_preds->version = 0; uint32_t tree_begin, tree_end; - std::tie(tree_begin, tree_end) = - detail::LayerToTree(model_, tparam_, layer_begin, layer_end); + std::tie(tree_begin, tree_end) = detail::LayerToTree(model_, layer_begin, layer_end); auto n_groups = model_.learner_model_param->num_output_group; PredictionCacheEntry predts; // temporary storage for prediction @@ -779,7 +782,7 @@ class Dart : public GBTree { float missing, PredictionCacheEntry *out_preds, uint32_t layer_begin, unsigned layer_end) const override { uint32_t tree_begin, tree_end; - std::tie(tree_begin, tree_end) = detail::LayerToTree(model_, tparam_, layer_begin, layer_end); + std::tie(tree_begin, tree_end) = detail::LayerToTree(model_, layer_begin, layer_end); std::vector predictors{ cpu_predictor_.get(), #if defined(XGBOOST_USE_CUDA) @@ -867,7 +870,7 @@ class Dart : public GBTree { DropTrees(false); auto &predictor = this->GetPredictor(); uint32_t _, tree_end; - std::tie(_, tree_end) = detail::LayerToTree(model_, tparam_, layer_begin, layer_end); + std::tie(_, tree_end) = detail::LayerToTree(model_, layer_begin, layer_end); predictor->PredictInstance(inst, out_preds, model_, tree_end); } @@ -877,7 +880,7 @@ class Dart : public GBTree { unsigned) override { CHECK(configured_); uint32_t tree_begin, tree_end; - std::tie(tree_begin, tree_end) = detail::LayerToTree(model_, tparam_, layer_begin, layer_end); + std::tie(tree_begin, tree_end) = detail::LayerToTree(model_, layer_begin, layer_end); cpu_predictor_->PredictContribution(p_fmat, out_contribs, model_, tree_end, &weight_drop_, approximate); } @@ -887,9 +890,9 @@ class Dart : public GBTree { unsigned layer_begin, unsigned layer_end, bool approximate) override { CHECK(configured_); uint32_t tree_begin, tree_end; - std::tie(tree_begin, tree_end) = detail::LayerToTree(model_, tparam_, layer_begin, layer_end); - cpu_predictor_->PredictInteractionContributions(p_fmat, out_contribs, model_, - tree_end, &weight_drop_, approximate); + std::tie(tree_begin, tree_end) = detail::LayerToTree(model_, layer_begin, layer_end); + cpu_predictor_->PredictInteractionContributions(p_fmat, out_contribs, model_, tree_end, + &weight_drop_, approximate); } protected: diff --git a/src/gbm/gbtree.h b/src/gbm/gbtree.h index 5548a4508d93..67d9e212888a 100644 --- a/src/gbm/gbtree.h +++ b/src/gbm/gbtree.h @@ -60,11 +60,6 @@ namespace gbm { /*! \brief training parameters */ struct GBTreeTrainParam : public XGBoostParameter { - /*! - * \brief number of parallel trees constructed each iteration - * use this option to support boosted random forest - */ - int num_parallel_tree; /*! \brief tree updater sequence */ std::string updater_seq; /*! \brief type of boosting process to run */ @@ -75,11 +70,6 @@ struct GBTreeTrainParam : public XGBoostParameter { TreeMethod tree_method; // declare parameters DMLC_DECLARE_PARAMETER(GBTreeTrainParam) { - DMLC_DECLARE_FIELD(num_parallel_tree) - .set_default(1) - .set_lower_bound(1) - .describe("Number of parallel trees constructed during each iteration."\ - " This option is used to support boosted random forest."); DMLC_DECLARE_FIELD(updater_seq) .set_default("grow_colmaker,prune") .describe("Tree updater sequence."); @@ -156,12 +146,11 @@ struct DartTrainParam : public XGBoostParameter { namespace detail { // From here on, layer becomes concrete trees. inline std::pair LayerToTree(gbm::GBTreeModel const &model, - GBTreeTrainParam const &tparam, size_t layer_begin, size_t layer_end) { bst_group_t groups = model.learner_model_param->num_output_group; - uint32_t tree_begin = layer_begin * groups * tparam.num_parallel_tree; - uint32_t tree_end = layer_end * groups * tparam.num_parallel_tree; + uint32_t tree_begin = layer_begin * groups * model.param.num_parallel_tree; + uint32_t tree_end = layer_end * groups * model.param.num_parallel_tree; if (tree_end == 0) { tree_end = static_cast(model.trees.size()); } @@ -177,7 +166,7 @@ inline bool SliceTrees(int32_t layer_begin, int32_t layer_end, int32_t step, GBTreeModel const &model, GBTreeTrainParam const &tparam, uint32_t layer_trees, Func fn) { uint32_t tree_begin, tree_end; - std::tie(tree_begin, tree_end) = detail::LayerToTree(model, tparam, layer_begin, layer_end); + std::tie(tree_begin, tree_end) = detail::LayerToTree(model, layer_begin, layer_end); if (tree_end > model.trees.size()) { return true; } @@ -249,7 +238,7 @@ class GBTree : public GradientBooster { // Number of trees per layer. auto LayerTrees() const { - auto n_trees = model_.learner_model_param->num_output_group * tparam_.num_parallel_tree; + auto n_trees = model_.learner_model_param->num_output_group * model_.param.num_parallel_tree; return n_trees; } @@ -258,7 +247,7 @@ class GBTree : public GradientBooster { GradientBooster *out, bool* out_of_bound) const override; int32_t BoostedRounds() const override { - CHECK_NE(tparam_.num_parallel_tree, 0); + CHECK_NE(model_.param.num_parallel_tree, 0); CHECK_NE(model_.learner_model_param->num_output_group, 0); return model_.trees.size() / this->LayerTrees(); } @@ -271,8 +260,7 @@ class GBTree : public GradientBooster { uint32_t layer_begin, unsigned layer_end) const override { CHECK(configured_); uint32_t tree_begin, tree_end; - std::tie(tree_begin, tree_end) = - detail::LayerToTree(model_, tparam_, layer_begin, layer_end); + std::tie(tree_begin, tree_end) = detail::LayerToTree(model_, layer_begin, layer_end); CHECK_LE(tree_end, model_.trees.size()) << "Invalid number of trees."; std::vector predictors{ cpu_predictor_.get(), @@ -371,16 +359,15 @@ class GBTree : public GradientBooster { uint32_t layer_begin, uint32_t layer_end) override { CHECK(configured_); uint32_t tree_begin, tree_end; - std::tie(tree_begin, tree_end) = detail::LayerToTree(model_, tparam_, layer_begin, layer_end); - cpu_predictor_->PredictInstance(inst, out_preds, model_, - tree_end); + std::tie(tree_begin, tree_end) = detail::LayerToTree(model_, layer_begin, layer_end); + cpu_predictor_->PredictInstance(inst, out_preds, model_, tree_end); } void PredictLeaf(DMatrix* p_fmat, HostDeviceVector* out_preds, uint32_t layer_begin, uint32_t layer_end) override { uint32_t tree_begin, tree_end; - std::tie(tree_begin, tree_end) = detail::LayerToTree(model_, tparam_, layer_begin, layer_end); + std::tie(tree_begin, tree_end) = detail::LayerToTree(model_, layer_begin, layer_end); CHECK_EQ(tree_begin, 0) << "Predict leaf supports only iteration end: (0, " "n_iteration), use model slicing instead."; this->GetPredictor()->PredictLeaf(p_fmat, out_preds, model_, tree_end); @@ -392,7 +379,7 @@ class GBTree : public GradientBooster { int, unsigned) override { CHECK(configured_); uint32_t tree_begin, tree_end; - std::tie(tree_begin, tree_end) = detail::LayerToTree(model_, tparam_, layer_begin, layer_end); + std::tie(tree_begin, tree_end) = detail::LayerToTree(model_, layer_begin, layer_end); CHECK_EQ(tree_begin, 0) << "Predict contribution supports only iteration end: (0, " "n_iteration), using model slicing instead."; @@ -405,7 +392,7 @@ class GBTree : public GradientBooster { uint32_t layer_begin, uint32_t layer_end, bool approximate) override { CHECK(configured_); uint32_t tree_begin, tree_end; - std::tie(tree_begin, tree_end) = detail::LayerToTree(model_, tparam_, layer_begin, layer_end); + std::tie(tree_begin, tree_end) = detail::LayerToTree(model_, layer_begin, layer_end); CHECK_EQ(tree_begin, 0) << "Predict interaction contribution supports only iteration end: (0, " "n_iteration), using model slicing instead."; diff --git a/src/gbm/gbtree_model.h b/src/gbm/gbtree_model.h index 50e57c0103b0..1e4ac73de8a4 100644 --- a/src/gbm/gbtree_model.h +++ b/src/gbm/gbtree_model.h @@ -31,7 +31,7 @@ struct GBTreeModelParam : public dmlc::Parameter { /*! \brief number of trees */ int32_t num_trees; /*! \brief (Deprecated) number of roots */ - int32_t deprecated_num_roots; + int32_t num_parallel_tree; /*! \brief number of features to be used by trees */ int32_t deprecated_num_feature; /*! \brief pad this space, for backward compatibility reason.*/ @@ -50,7 +50,7 @@ struct GBTreeModelParam : public dmlc::Parameter { std::memset(this, 0, sizeof(GBTreeModelParam)); // FIXME(trivialfis): Why? static_assert(sizeof(GBTreeModelParam) == (4 + 2 + 2 + 32) * sizeof(int32_t), "64/32 bit compatibility issue"); - deprecated_num_roots = 1; + num_parallel_tree = 1; } // declare parameters, only declare those that need to be set. @@ -59,6 +59,12 @@ struct GBTreeModelParam : public dmlc::Parameter { .set_lower_bound(0) .set_default(0) .describe("Number of features used for training and prediction."); + DMLC_DECLARE_FIELD(num_parallel_tree) + .set_default(1) + .set_lower_bound(1) + .describe( + "Number of parallel trees constructed during each iteration." + " This option is used to support boosted random forest."); DMLC_DECLARE_FIELD(size_leaf_vector) .set_lower_bound(0) .set_default(0) @@ -70,7 +76,7 @@ struct GBTreeModelParam : public dmlc::Parameter { inline GBTreeModelParam ByteSwap() const { GBTreeModelParam x = *this; dmlc::ByteSwap(&x.num_trees, sizeof(x.num_trees), 1); - dmlc::ByteSwap(&x.deprecated_num_roots, sizeof(x.deprecated_num_roots), 1); + dmlc::ByteSwap(&x.num_parallel_tree, sizeof(x.num_parallel_tree), 1); dmlc::ByteSwap(&x.deprecated_num_feature, sizeof(x.deprecated_num_feature), 1); dmlc::ByteSwap(&x.pad_32bit, sizeof(x.pad_32bit), 1); dmlc::ByteSwap(&x.deprecated_num_pbuffer, sizeof(x.deprecated_num_pbuffer), 1); diff --git a/tests/cpp/gbm/test_gbtree.cc b/tests/cpp/gbm/test_gbtree.cc index ed2f86c6c123..c416d134307c 100644 --- a/tests/cpp/gbm/test_gbtree.cc +++ b/tests/cpp/gbm/test_gbtree.cc @@ -207,7 +207,7 @@ TEST(GBTree, JsonIO) { ASSERT_EQ(get(get(get(gbtree_model["trees"]).front()).at("id")), 0); ASSERT_EQ(get(gbtree_model["tree_info"]).size(), 1ul); - auto j_train_param = model["config"]["gbtree_train_param"]; + auto j_train_param = model["config"]["gbtree_model_param"]; ASSERT_EQ(get(j_train_param["num_parallel_tree"]), "1"); } @@ -337,6 +337,13 @@ std::pair TestModelSlice(std::string booster) { Json sliced_config {Object()}; sliced->SaveConfig(&sliced_config); + // Only num trees is changed + if (booster == "gbtree") { + sliced_config["learner"]["gradient_booster"]["gbtree_model_param"]["num_trees"] = String("60"); + } else { + sliced_config["learner"]["gradient_booster"]["gbtree"]["gbtree_model_param"]["num_trees"] = + String("60"); + } CHECK_EQ(sliced_config, config); auto get_trees = [&](Json const& model) { diff --git a/tests/python/test_basic_models.py b/tests/python/test_basic_models.py index 9597632021f8..2cfa26402de8 100644 --- a/tests/python/test_basic_models.py +++ b/tests/python/test_basic_models.py @@ -8,8 +8,6 @@ import tempfile dpath = os.path.join(tm.PROJECT_ROOT, 'demo/data/') -dtrain = xgb.DMatrix(dpath + 'agaricus.txt.train') -dtest = xgb.DMatrix(dpath + 'agaricus.txt.test') rng = np.random.RandomState(1994) @@ -38,6 +36,8 @@ def test_glm(self): param = {'verbosity': 0, 'objective': 'binary:logistic', 'booster': 'gblinear', 'alpha': 0.0001, 'lambda': 1, 'nthread': 1} + dtrain = xgb.DMatrix(dpath + 'agaricus.txt.train') + dtest = xgb.DMatrix(dpath + 'agaricus.txt.test') watchlist = [(dtest, 'eval'), (dtrain, 'train')] num_round = 4 bst = xgb.train(param, dtrain, num_round, watchlist) @@ -124,7 +124,7 @@ def test_boost_from_prediction(self): predt_1 = bst.predict(margined) assert np.any(np.abs(predt_1 - predt_0) > 1e-6) - + dtrain = xgb.DMatrix(dpath + 'agaricus.txt.train') bst = xgb.train({'tree_method': 'hist'}, dtrain, 2) predt_2 = bst.predict(dtrain) assert np.all(np.abs(predt_2 - predt_1) < 1e-6) @@ -150,6 +150,8 @@ def run_custom_objective(self, tree_method=None): 'objective': 'reg:logistic', "tree_method": tree_method } + dtrain = xgb.DMatrix(dpath + 'agaricus.txt.train') + dtest = xgb.DMatrix(dpath + 'agaricus.txt.test') watchlist = [(dtest, 'eval'), (dtrain, 'train')] num_round = 10 @@ -195,6 +197,8 @@ def test_custom_objective(self): self.run_custom_objective() def test_multi_eval_metric(self): + dtrain = xgb.DMatrix(dpath + 'agaricus.txt.train') + dtest = xgb.DMatrix(dpath + 'agaricus.txt.test') watchlist = [(dtest, 'eval'), (dtrain, 'train')] param = {'max_depth': 2, 'eta': 0.2, 'verbosity': 1, 'objective': 'binary:logistic'} @@ -216,6 +220,7 @@ def fpreproc(dtrain, dtest, param): param['scale_pos_weight'] = ratio return (dtrain, dtest, param) + dtrain = xgb.DMatrix(dpath + 'agaricus.txt.train') xgb.cv(param, dtrain, num_round, nfold=5, metrics={'auc'}, seed=0, fpreproc=fpreproc) @@ -223,6 +228,7 @@ def test_show_stdv(self): param = {'max_depth': 2, 'eta': 1, 'verbosity': 0, 'objective': 'binary:logistic'} num_round = 2 + dtrain = xgb.DMatrix(dpath + 'agaricus.txt.train') xgb.cv(param, dtrain, num_round, nfold=5, metrics={'error'}, seed=0, show_stdv=False) @@ -331,6 +337,7 @@ def test_json_io_schema(self): os.remove(model_path) try: + dtrain = xgb.DMatrix(dpath + 'agaricus.txt.train') xgb.train({'objective': 'foo'}, dtrain, num_boost_round=1) except ValueError as e: e_str = str(e) @@ -422,68 +429,58 @@ def test_attributes(self): assert cls.get_booster().best_ntree_limit == 2 assert cls.best_ntree_limit == cls.get_booster().best_ntree_limit - @pytest.mark.skipif(**tm.no_sklearn()) - @pytest.mark.parametrize('booster', ['gbtree', 'dart']) - def test_slice(self, booster): - from sklearn.datasets import make_classification - num_classes = 3 - X, y = make_classification(n_samples=1000, n_informative=5, - n_classes=num_classes) - dtrain = xgb.DMatrix(data=X, label=y) - num_parallel_tree = 4 - num_boost_round = 16 - total_trees = num_parallel_tree * num_classes * num_boost_round - booster = xgb.train({ - 'num_parallel_tree': 4, 'subsample': 0.5, 'num_class': 3, 'booster': booster, - 'objective': 'multi:softprob'}, - num_boost_round=num_boost_round, dtrain=dtrain) - booster.feature_types = ["q"] * X.shape[1] - - assert len(booster.get_dump()) == total_trees + def run_slice( + self, + booster: xgb.Booster, + dtrain: xgb.DMatrix, + num_parallel_tree: int, + num_classes: int, + num_boost_round: int + ): beg = 3 end = 7 - sliced: xgb.Booster = booster[beg: end] + sliced: xgb.Booster = booster[beg:end] assert sliced.feature_types == booster.feature_types sliced_trees = (end - beg) * num_parallel_tree * num_classes assert sliced_trees == len(sliced.get_dump()) sliced_trees = sliced_trees // 2 - sliced: xgb.Booster = booster[beg: end: 2] + sliced = booster[beg:end:2] assert sliced_trees == len(sliced.get_dump()) - sliced: xgb.Booster = booster[beg: ...] + sliced = booster[beg: ...] sliced_trees = (num_boost_round - beg) * num_parallel_tree * num_classes assert sliced_trees == len(sliced.get_dump()) - sliced: xgb.Booster = booster[beg:] + sliced = booster[beg:] sliced_trees = (num_boost_round - beg) * num_parallel_tree * num_classes assert sliced_trees == len(sliced.get_dump()) - sliced: xgb.Booster = booster[:end] + sliced = booster[:end] sliced_trees = end * num_parallel_tree * num_classes assert sliced_trees == len(sliced.get_dump()) - sliced: xgb.Booster = booster[...:end] + sliced = booster[...: end] sliced_trees = end * num_parallel_tree * num_classes assert sliced_trees == len(sliced.get_dump()) - with pytest.raises(ValueError, match=r'>= 0'): - booster[-1: 0] + with pytest.raises(ValueError, match=r">= 0"): + booster[-1:0] # we do not accept empty slice. with pytest.raises(ValueError): booster[1:1] # stop can not be smaller than begin - with pytest.raises(ValueError, match=r'Invalid.*'): + with pytest.raises(ValueError, match=r"Invalid.*"): booster[3:0] - with pytest.raises(ValueError, match=r'Invalid.*'): + with pytest.raises(ValueError, match=r"Invalid.*"): booster[3:-1] # negative step is not supported. - with pytest.raises(ValueError, match=r'.*>= 1.*'): + with pytest.raises(ValueError, match=r".*>= 1.*"): booster[0:2:-1] # step can not be 0. - with pytest.raises(ValueError, match=r'.*>= 1.*'): + with pytest.raises(ValueError, match=r".*>= 1.*"): booster[0:2:0] trees = [_ for _ in booster] @@ -492,12 +489,12 @@ def test_slice(self, booster): with pytest.raises(TypeError): booster["wrong type"] with pytest.raises(IndexError): - booster[:num_boost_round+1] + booster[: num_boost_round + 1] with pytest.raises(ValueError): - booster[1, 2] # too many dims + booster[1, 2] # too many dims # setitem is not implemented as model is immutable during slicing. with pytest.raises(TypeError): - booster[...:end] = booster + booster[...: end] = booster sliced_0 = booster[1:3] np.testing.assert_allclose( @@ -525,6 +522,44 @@ def test_slice(self, booster): single = booster[1:7].predict(dtrain, output_margin=True) np.testing.assert_allclose(merged, single, atol=1e-6) + @pytest.mark.skipif(**tm.no_sklearn()) + @pytest.mark.parametrize("booster", ["gbtree", "dart"]) + def test_slice(self, booster): + from sklearn.datasets import make_classification + + num_classes = 3 + X, y = make_classification( + n_samples=1000, n_informative=5, n_classes=num_classes + ) + dtrain = xgb.DMatrix(data=X, label=y) + num_parallel_tree = 4 + num_boost_round = 16 + total_trees = num_parallel_tree * num_classes * num_boost_round + booster = xgb.train( + { + "num_parallel_tree": num_parallel_tree, + "subsample": 0.5, + "num_class": num_classes, + "booster": booster, + "objective": "multi:softprob", + }, + num_boost_round=num_boost_round, + dtrain=dtrain, + ) + booster.feature_types = ["q"] * X.shape[1] + + assert len(booster.get_dump()) == total_trees + + self.run_slice(booster, dtrain, num_parallel_tree, num_classes, num_boost_round) + + bytesarray = booster.save_raw(raw_format="ubj") + booster = xgb.Booster(model_file=bytesarray) + self.run_slice(booster, dtrain, num_parallel_tree, num_classes, num_boost_round) + + bytesarray = booster.save_raw(raw_format="deprecated") + booster = xgb.Booster(model_file=bytesarray) + self.run_slice(booster, dtrain, num_parallel_tree, num_classes, num_boost_round) + @pytest.mark.skipif(**tm.no_pandas()) def test_feature_info(self): import pandas as pd diff --git a/tests/python/test_with_dask.py b/tests/python/test_with_dask.py index 9a68f4453241..756d81cd42d6 100644 --- a/tests/python/test_with_dask.py +++ b/tests/python/test_with_dask.py @@ -528,7 +528,7 @@ def test_dask_regressor(model: str, client: "Client") -> None: forest = int( json.loads(regressor.get_booster().save_config())["learner"][ "gradient_booster" - ]["gbtree_train_param"]["num_parallel_tree"] + ]["gbtree_model_param"]["num_parallel_tree"] ) if model == "boosting": @@ -582,7 +582,7 @@ def run_dask_classifier( assert n_threads != 0 and n_threads != os.cpu_count() forest = int( - config["learner"]["gradient_booster"]["gbtree_train_param"]["num_parallel_tree"] + config["learner"]["gradient_booster"]["gbtree_model_param"]["num_parallel_tree"] ) if model == "boosting": assert len(history["validation_0"][metric]) == 2 diff --git a/tests/python/test_with_sklearn.py b/tests/python/test_with_sklearn.py index 46c40da4fd33..a2e70ae6de2e 100644 --- a/tests/python/test_with_sklearn.py +++ b/tests/python/test_with_sklearn.py @@ -329,21 +329,27 @@ def test_select_feature(): def test_num_parallel_tree(): from sklearn.datasets import fetch_california_housing - reg = xgb.XGBRegressor(n_estimators=4, num_parallel_tree=4, - tree_method='hist') + + reg = xgb.XGBRegressor(n_estimators=4, num_parallel_tree=4, tree_method="hist") X, y = fetch_california_housing(return_X_y=True) bst = reg.fit(X=X, y=y) - dump = bst.get_booster().get_dump(dump_format='json') + dump = bst.get_booster().get_dump(dump_format="json") assert len(dump) == 16 reg = xgb.XGBRFRegressor(n_estimators=4) bst = reg.fit(X=X, y=y) - dump = bst.get_booster().get_dump(dump_format='json') + dump = bst.get_booster().get_dump(dump_format="json") assert len(dump) == 4 config = json.loads(bst.get_booster().save_config()) - assert int(config['learner']['gradient_booster']['gbtree_train_param'][ - 'num_parallel_tree']) == 4 + assert ( + int( + config["learner"]["gradient_booster"]["gbtree_model_param"][ + "num_parallel_tree" + ] + ) + == 4 + ) def test_calif_housing_regression():