Refactor hist to prepare for multi-target builder. (#8928)
- Extract the builder from the updater class. We need a new builder for multi-target. - Extract `UpdateTree`, it can be reused for different builders. Eventually, other tree updaters can use it as well.
This commit is contained in:
parent
36263dd109
commit
9b6cc0ed07
@ -4,59 +4,195 @@
|
|||||||
* \brief use quantized feature values to construct a tree
|
* \brief use quantized feature values to construct a tree
|
||||||
* \author Philip Cho, Tianqi Checn, Egor Smirnov
|
* \author Philip Cho, Tianqi Checn, Egor Smirnov
|
||||||
*/
|
*/
|
||||||
#include "./updater_quantile_hist.h"
|
#include <algorithm> // for max
|
||||||
|
#include <cstddef> // for size_t
|
||||||
|
#include <cstdint> // for uint32_t
|
||||||
|
#include <memory> // for unique_ptr, allocator, make_unique, make_shared
|
||||||
|
#include <ostream> // for operator<<, char_traits, basic_ostream
|
||||||
|
#include <tuple> // for apply
|
||||||
|
#include <utility> // for move, swap
|
||||||
|
#include <vector> // for vector
|
||||||
|
|
||||||
#include <algorithm>
|
#include "../collective/communicator-inl.h" // for Allreduce, IsDistributed
|
||||||
#include <cstddef>
|
#include "../collective/communicator.h" // for Operation
|
||||||
#include <memory>
|
#include "../common/hist_util.h" // for HistogramCuts, HistCollection
|
||||||
#include <string>
|
#include "../common/random.h" // for ColumnSampler
|
||||||
#include <utility>
|
#include "../common/threading_utils.h" // for ParallelFor
|
||||||
#include <vector>
|
#include "../common/timer.h" // for Monitor
|
||||||
|
#include "../data/gradient_index.h" // for GHistIndexMatrix
|
||||||
|
#include "common_row_partitioner.h" // for CommonRowPartitioner
|
||||||
|
#include "dmlc/registry.h" // for DMLC_REGISTRY_FILE_TAG
|
||||||
|
#include "driver.h" // for Driver
|
||||||
|
#include "hist/evaluate_splits.h" // for HistEvaluator, UpdatePredictionCacheImpl
|
||||||
|
#include "hist/expand_entry.h" // for CPUExpandEntry
|
||||||
|
#include "hist/histogram.h" // for HistogramBuilder, ConstructHistSpace
|
||||||
|
#include "hist/sampler.h" // for SampleGradient
|
||||||
|
#include "param.h" // for TrainParam, GradStats
|
||||||
|
#include "xgboost/base.h" // for GradientPair, GradientPairInternal, bst_node_t
|
||||||
|
#include "xgboost/context.h" // for Context
|
||||||
|
#include "xgboost/data.h" // for BatchIterator, BatchSet, DMatrix, MetaInfo
|
||||||
|
#include "xgboost/host_device_vector.h" // for HostDeviceVector
|
||||||
|
#include "xgboost/linalg.h" // for TensorView, MatrixView, UnravelIndex, All
|
||||||
|
#include "xgboost/logging.h" // for LogCheck_EQ, LogCheck_GE, CHECK_EQ, LOG, LOG...
|
||||||
|
#include "xgboost/span.h" // for Span, operator!=, SpanIterator
|
||||||
|
#include "xgboost/string_view.h" // for operator<<
|
||||||
|
#include "xgboost/task.h" // for ObjInfo
|
||||||
|
#include "xgboost/tree_model.h" // for RegTree, MTNotImplemented, RTreeNodeStat
|
||||||
|
#include "xgboost/tree_updater.h" // for TreeUpdater, TreeUpdaterReg, XGBOOST_REGISTE...
|
||||||
|
|
||||||
#include "common_row_partitioner.h"
|
namespace xgboost::tree {
|
||||||
#include "constraints.h"
|
|
||||||
#include "hist/evaluate_splits.h"
|
|
||||||
#include "hist/histogram.h"
|
|
||||||
#include "hist/sampler.h"
|
|
||||||
#include "param.h"
|
|
||||||
#include "xgboost/linalg.h"
|
|
||||||
#include "xgboost/logging.h"
|
|
||||||
#include "xgboost/tree_updater.h"
|
|
||||||
|
|
||||||
namespace xgboost {
|
|
||||||
namespace tree {
|
|
||||||
|
|
||||||
DMLC_REGISTRY_FILE_TAG(updater_quantile_hist);
|
DMLC_REGISTRY_FILE_TAG(updater_quantile_hist);
|
||||||
|
|
||||||
void QuantileHistMaker::Update(TrainParam const *param, HostDeviceVector<GradientPair> *gpair,
|
BatchParam HistBatch(TrainParam const *param) { return {param->max_bin, param->sparse_threshold}; }
|
||||||
DMatrix *dmat,
|
|
||||||
common::Span<HostDeviceVector<bst_node_t>> out_position,
|
template <typename ExpandEntry, typename Updater>
|
||||||
const std::vector<RegTree *> &trees) {
|
void UpdateTree(common::Monitor *monitor_, linalg::MatrixView<GradientPair const> gpair,
|
||||||
// build tree
|
Updater *updater, DMatrix *p_fmat, TrainParam const *param,
|
||||||
const size_t n_trees = trees.size();
|
HostDeviceVector<bst_node_t> *p_out_position, RegTree *p_tree) {
|
||||||
if (!pimpl_) {
|
monitor_->Start(__func__);
|
||||||
pimpl_.reset(new Builder(n_trees, param, dmat, *task_, ctx_));
|
updater->InitData(p_fmat, p_tree);
|
||||||
|
|
||||||
|
Driver<ExpandEntry> driver{*param};
|
||||||
|
auto const &tree = *p_tree;
|
||||||
|
driver.Push(updater->InitRoot(p_fmat, gpair, p_tree));
|
||||||
|
auto expand_set = driver.Pop();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note for update position
|
||||||
|
* Root:
|
||||||
|
* Not applied: No need to update position as initialization has got all the rows ordered.
|
||||||
|
* Applied: Update position is run on applied nodes so the rows are partitioned.
|
||||||
|
* Non-root:
|
||||||
|
* Not applied: That node is root of the subtree, same rule as root.
|
||||||
|
* Applied: Ditto
|
||||||
|
*/
|
||||||
|
while (!expand_set.empty()) {
|
||||||
|
// candidates that can be further splited.
|
||||||
|
std::vector<ExpandEntry> valid_candidates;
|
||||||
|
// candidaates that can be applied.
|
||||||
|
std::vector<ExpandEntry> applied;
|
||||||
|
for (auto const &candidate : expand_set) {
|
||||||
|
updater->ApplyTreeSplit(candidate, p_tree);
|
||||||
|
CHECK_GT(p_tree->LeftChild(candidate.nid), candidate.nid);
|
||||||
|
applied.push_back(candidate);
|
||||||
|
if (driver.IsChildValid(candidate)) {
|
||||||
|
valid_candidates.emplace_back(candidate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t t_idx{0};
|
updater->UpdatePosition(p_fmat, p_tree, applied);
|
||||||
for (auto p_tree : trees) {
|
|
||||||
auto &t_row_position = out_position[t_idx];
|
std::vector<ExpandEntry> best_splits;
|
||||||
this->pimpl_->UpdateTree(gpair, dmat, p_tree, &t_row_position);
|
if (!valid_candidates.empty()) {
|
||||||
++t_idx;
|
updater->BuildHistogram(p_fmat, p_tree, valid_candidates, gpair);
|
||||||
|
for (auto const &candidate : valid_candidates) {
|
||||||
|
auto left_child_nidx = tree.LeftChild(candidate.nid);
|
||||||
|
auto right_child_nidx = tree.RightChild(candidate.nid);
|
||||||
|
ExpandEntry l_best{left_child_nidx, tree.GetDepth(left_child_nidx)};
|
||||||
|
ExpandEntry r_best{right_child_nidx, tree.GetDepth(right_child_nidx)};
|
||||||
|
best_splits.push_back(l_best);
|
||||||
|
best_splits.push_back(r_best);
|
||||||
}
|
}
|
||||||
|
updater->EvaluateSplits(p_fmat, p_tree, &best_splits);
|
||||||
|
}
|
||||||
|
driver.Push(best_splits.begin(), best_splits.end());
|
||||||
|
expand_set = driver.Pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &h_out_position = p_out_position->HostVector();
|
||||||
|
updater->LeafPartition(tree, gpair, &h_out_position);
|
||||||
|
monitor_->Stop(__func__);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QuantileHistMaker::UpdatePredictionCache(const DMatrix *data,
|
class HistBuilder {
|
||||||
linalg::VectorView<float> out_preds) {
|
private:
|
||||||
if (pimpl_) {
|
common::Monitor *monitor_;
|
||||||
return pimpl_->UpdatePredictionCache(data, out_preds);
|
TrainParam const *param_;
|
||||||
} else {
|
std::shared_ptr<common::ColumnSampler> col_sampler_;
|
||||||
|
std::unique_ptr<HistEvaluator<CPUExpandEntry>> evaluator_;
|
||||||
|
std::vector<CommonRowPartitioner> partitioner_;
|
||||||
|
|
||||||
|
// back pointers to tree and data matrix
|
||||||
|
const RegTree *p_last_tree_{nullptr};
|
||||||
|
DMatrix const *const p_last_fmat_{nullptr};
|
||||||
|
|
||||||
|
std::unique_ptr<HistogramBuilder<CPUExpandEntry>> histogram_builder_;
|
||||||
|
ObjInfo const *task_{nullptr};
|
||||||
|
// Context for number of threads
|
||||||
|
Context const *ctx_{nullptr};
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit HistBuilder(Context const *ctx, std::shared_ptr<common::ColumnSampler> column_sampler,
|
||||||
|
TrainParam const *param, DMatrix const *fmat, ObjInfo const *task,
|
||||||
|
common::Monitor *monitor)
|
||||||
|
: monitor_{monitor},
|
||||||
|
param_{param},
|
||||||
|
col_sampler_{std::move(column_sampler)},
|
||||||
|
evaluator_{std::make_unique<HistEvaluator<CPUExpandEntry>>(ctx, param, fmat->Info(),
|
||||||
|
col_sampler_)},
|
||||||
|
p_last_fmat_(fmat),
|
||||||
|
histogram_builder_{new HistogramBuilder<CPUExpandEntry>},
|
||||||
|
task_{task},
|
||||||
|
ctx_{ctx} {
|
||||||
|
monitor_->Init(__func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UpdatePredictionCache(DMatrix const *data, linalg::VectorView<float> out_preds) const {
|
||||||
|
// p_last_fmat_ is a valid pointer as long as UpdatePredictionCache() is called in
|
||||||
|
// conjunction with Update().
|
||||||
|
if (!p_last_fmat_ || !p_last_tree_ || data != p_last_fmat_) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
monitor_->Start(__func__);
|
||||||
|
CHECK_EQ(out_preds.Size(), data->Info().num_row_);
|
||||||
|
UpdatePredictionCacheImpl(ctx_, p_last_tree_, partitioner_, out_preds);
|
||||||
|
monitor_->Stop(__func__);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
CPUExpandEntry QuantileHistMaker::Builder::InitRoot(
|
public:
|
||||||
DMatrix *p_fmat, RegTree *p_tree, const std::vector<GradientPair> &gpair_h) {
|
// initialize temp data structure
|
||||||
|
void InitData(DMatrix *fmat, RegTree const *p_tree) {
|
||||||
|
monitor_->Start(__func__);
|
||||||
|
|
||||||
|
size_t page_id{0};
|
||||||
|
bst_bin_t n_total_bins{0};
|
||||||
|
partitioner_.clear();
|
||||||
|
for (auto const &page : fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
||||||
|
if (n_total_bins == 0) {
|
||||||
|
n_total_bins = page.cut.TotalBins();
|
||||||
|
} else {
|
||||||
|
CHECK_EQ(n_total_bins, page.cut.TotalBins());
|
||||||
|
}
|
||||||
|
partitioner_.emplace_back(this->ctx_, page.Size(), page.base_rowid, fmat->IsColumnSplit());
|
||||||
|
++page_id;
|
||||||
|
}
|
||||||
|
histogram_builder_->Reset(n_total_bins, HistBatch(param_), ctx_->Threads(), page_id,
|
||||||
|
collective::IsDistributed(), fmat->IsColumnSplit());
|
||||||
|
evaluator_ = std::make_unique<HistEvaluator<CPUExpandEntry>>(ctx_, this->param_, fmat->Info(),
|
||||||
|
col_sampler_);
|
||||||
|
p_last_tree_ = p_tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EvaluateSplits(DMatrix *p_fmat, RegTree const *p_tree,
|
||||||
|
std::vector<CPUExpandEntry> *best_splits) {
|
||||||
|
monitor_->Start(__func__);
|
||||||
|
auto const &histograms = histogram_builder_->Histogram();
|
||||||
|
auto ft = p_fmat->Info().feature_types.ConstHostSpan();
|
||||||
|
for (auto const &gmat : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
||||||
|
evaluator_->EvaluateSplits(histograms, gmat.cut, ft, *p_tree, best_splits);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
monitor_->Stop(__func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplyTreeSplit(CPUExpandEntry const &candidate, RegTree *p_tree) {
|
||||||
|
this->evaluator_->ApplyTreeSplit(candidate, p_tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
CPUExpandEntry InitRoot(DMatrix *p_fmat, linalg::MatrixView<GradientPair const> gpair,
|
||||||
|
RegTree *p_tree) {
|
||||||
CPUExpandEntry node(RegTree::kRoot, p_tree->GetDepth(0));
|
CPUExpandEntry node(RegTree::kRoot, p_tree->GetDepth(0));
|
||||||
|
|
||||||
size_t page_id = 0;
|
size_t page_id = 0;
|
||||||
@ -66,7 +202,7 @@ CPUExpandEntry QuantileHistMaker::Builder::InitRoot(
|
|||||||
std::vector<CPUExpandEntry> nodes_to_sub;
|
std::vector<CPUExpandEntry> nodes_to_sub;
|
||||||
this->histogram_builder_->BuildHist(page_id, space, gidx, p_tree,
|
this->histogram_builder_->BuildHist(page_id, space, gidx, p_tree,
|
||||||
partitioner_.at(page_id).Partitions(), nodes_to_build,
|
partitioner_.at(page_id).Partitions(), nodes_to_build,
|
||||||
nodes_to_sub, gpair_h);
|
nodes_to_sub, gpair.Slice(linalg::All(), 0).Values());
|
||||||
++page_id;
|
++page_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,10 +225,12 @@ CPUExpandEntry QuantileHistMaker::Builder::InitRoot(
|
|||||||
grad_stat.Add(et.GetGrad(), et.GetHess());
|
grad_stat.Add(et.GetGrad(), et.GetHess());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
auto gpair_h = gpair.Slice(linalg::All(), 0).Values();
|
||||||
for (auto const &grad : gpair_h) {
|
for (auto const &grad : gpair_h) {
|
||||||
grad_stat.Add(grad.GetGrad(), grad.GetHess());
|
grad_stat.Add(grad.GetGrad(), grad.GetHess());
|
||||||
}
|
}
|
||||||
collective::Allreduce<collective::Operation::kSum>(reinterpret_cast<double *>(&grad_stat), 2);
|
collective::Allreduce<collective::Operation::kSum>(reinterpret_cast<double *>(&grad_stat),
|
||||||
|
2);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto weight = evaluator_->InitRoot(GradStats{grad_stat});
|
auto weight = evaluator_->InitRoot(GradStats{grad_stat});
|
||||||
@ -104,7 +242,8 @@ CPUExpandEntry QuantileHistMaker::Builder::InitRoot(
|
|||||||
monitor_->Start("EvaluateSplits");
|
monitor_->Start("EvaluateSplits");
|
||||||
auto ft = p_fmat->Info().feature_types.ConstHostSpan();
|
auto ft = p_fmat->Info().feature_types.ConstHostSpan();
|
||||||
for (auto const &gmat : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
for (auto const &gmat : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
||||||
evaluator_->EvaluateSplits(histogram_builder_->Histogram(), gmat.cut, ft, *p_tree, &entries);
|
evaluator_->EvaluateSplits(histogram_builder_->Histogram(), gmat.cut, ft, *p_tree,
|
||||||
|
&entries);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
monitor_->Stop("EvaluateSplits");
|
monitor_->Stop("EvaluateSplits");
|
||||||
@ -112,11 +251,11 @@ CPUExpandEntry QuantileHistMaker::Builder::InitRoot(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QuantileHistMaker::Builder::BuildHistogram(DMatrix *p_fmat, RegTree *p_tree,
|
void BuildHistogram(DMatrix *p_fmat, RegTree *p_tree,
|
||||||
std::vector<CPUExpandEntry> const &valid_candidates,
|
std::vector<CPUExpandEntry> const &valid_candidates,
|
||||||
std::vector<GradientPair> const &gpair) {
|
linalg::MatrixView<GradientPair const> gpair) {
|
||||||
std::vector<CPUExpandEntry> nodes_to_build(valid_candidates.size());
|
std::vector<CPUExpandEntry> nodes_to_build(valid_candidates.size());
|
||||||
std::vector<CPUExpandEntry> nodes_to_sub(valid_candidates.size());
|
std::vector<CPUExpandEntry> nodes_to_sub(valid_candidates.size());
|
||||||
|
|
||||||
@ -141,154 +280,109 @@ void QuantileHistMaker::Builder::BuildHistogram(DMatrix *p_fmat, RegTree *p_tree
|
|||||||
for (auto const &gidx : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
for (auto const &gidx : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
||||||
histogram_builder_->BuildHist(page_id, space, gidx, p_tree,
|
histogram_builder_->BuildHist(page_id, space, gidx, p_tree,
|
||||||
partitioner_.at(page_id).Partitions(), nodes_to_build,
|
partitioner_.at(page_id).Partitions(), nodes_to_build,
|
||||||
nodes_to_sub, gpair);
|
nodes_to_sub, gpair.Values());
|
||||||
++page_id;
|
++page_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void QuantileHistMaker::Builder::LeafPartition(RegTree const &tree,
|
void UpdatePosition(DMatrix *p_fmat, RegTree const *p_tree,
|
||||||
common::Span<GradientPair const> gpair,
|
std::vector<CPUExpandEntry> const &applied) {
|
||||||
|
monitor_->Start(__func__);
|
||||||
|
std::size_t page_id{0};
|
||||||
|
for (auto const &page : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(this->param_))) {
|
||||||
|
this->partitioner_.at(page_id).UpdatePosition(this->ctx_, page, applied, p_tree);
|
||||||
|
page_id++;
|
||||||
|
}
|
||||||
|
monitor_->Stop(__func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LeafPartition(RegTree const &tree, linalg::MatrixView<GradientPair const> gpair,
|
||||||
std::vector<bst_node_t> *p_out_position) {
|
std::vector<bst_node_t> *p_out_position) {
|
||||||
monitor_->Start(__func__);
|
monitor_->Start(__func__);
|
||||||
if (!task_.UpdateTreeLeaf()) {
|
if (!task_->UpdateTreeLeaf()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (auto const &part : partitioner_) {
|
for (auto const &part : partitioner_) {
|
||||||
part.LeafPartition(ctx_, tree, gpair, p_out_position);
|
part.LeafPartition(ctx_, tree, gpair, p_out_position);
|
||||||
}
|
}
|
||||||
monitor_->Stop(__func__);
|
monitor_->Stop(__func__);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
void QuantileHistMaker::Builder::ExpandTree(DMatrix *p_fmat, RegTree *p_tree,
|
/*! \brief construct a tree using quantized feature values */
|
||||||
const std::vector<GradientPair> &gpair_h,
|
class QuantileHistMaker : public TreeUpdater {
|
||||||
HostDeviceVector<bst_node_t> *p_out_position) {
|
std::unique_ptr<HistBuilder> p_impl_;
|
||||||
monitor_->Start(__func__);
|
std::shared_ptr<common::ColumnSampler> column_sampler_ =
|
||||||
|
std::make_shared<common::ColumnSampler>();
|
||||||
|
common::Monitor monitor_;
|
||||||
|
ObjInfo const *task_;
|
||||||
|
|
||||||
Driver<CPUExpandEntry> driver(*param_);
|
public:
|
||||||
driver.Push(this->InitRoot(p_fmat, p_tree, gpair_h));
|
explicit QuantileHistMaker(Context const *ctx, ObjInfo const *task)
|
||||||
auto const &tree = *p_tree;
|
: TreeUpdater{ctx}, task_{task} {}
|
||||||
auto expand_set = driver.Pop();
|
void Configure(const Args &) override {}
|
||||||
|
|
||||||
while (!expand_set.empty()) {
|
void LoadConfig(Json const &) override {}
|
||||||
// candidates that can be further splited.
|
void SaveConfig(Json *) const override {}
|
||||||
std::vector<CPUExpandEntry> valid_candidates;
|
|
||||||
// candidaates that can be applied.
|
[[nodiscard]] char const *Name() const override { return "grow_quantile_histmaker"; }
|
||||||
std::vector<CPUExpandEntry> applied;
|
|
||||||
int32_t depth = expand_set.front().depth + 1;
|
void Update(TrainParam const *param, HostDeviceVector<GradientPair> *gpair, DMatrix *p_fmat,
|
||||||
for (auto const& candidate : expand_set) {
|
common::Span<HostDeviceVector<bst_node_t>> out_position,
|
||||||
evaluator_->ApplyTreeSplit(candidate, p_tree);
|
const std::vector<RegTree *> &trees) override {
|
||||||
applied.push_back(candidate);
|
if (trees.front()->IsMultiTarget()) {
|
||||||
if (driver.IsChildValid(candidate)) {
|
CHECK(param->monotone_constraints.empty()) << "monotone constraint" << MTNotImplemented();
|
||||||
valid_candidates.emplace_back(candidate);
|
LOG(FATAL) << "Not implemented.";
|
||||||
|
} else {
|
||||||
|
if (!p_impl_) {
|
||||||
|
p_impl_ =
|
||||||
|
std::make_unique<HistBuilder>(ctx_, column_sampler_, param, p_fmat, task_, &monitor_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
monitor_->Start("UpdatePosition");
|
bst_target_t n_targets = trees.front()->NumTargets();
|
||||||
size_t page_id{0};
|
auto h_gpair =
|
||||||
for (auto const &page : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
linalg::MakeTensorView(ctx_, gpair->HostSpan(), p_fmat->Info().num_row_, n_targets);
|
||||||
partitioner_.at(page_id).UpdatePosition(ctx_, page, applied, p_tree);
|
|
||||||
++page_id;
|
|
||||||
}
|
|
||||||
monitor_->Stop("UpdatePosition");
|
|
||||||
|
|
||||||
std::vector<CPUExpandEntry> best_splits;
|
linalg::Matrix<GradientPair> sample_out;
|
||||||
if (!valid_candidates.empty()) {
|
auto h_sample_out = h_gpair;
|
||||||
this->BuildHistogram(p_fmat, p_tree, valid_candidates, gpair_h);
|
auto need_copy = [&] { return trees.size() > 1 || n_targets > 1; };
|
||||||
for (auto const &candidate : valid_candidates) {
|
if (need_copy()) {
|
||||||
int left_child_nidx = tree[candidate.nid].LeftChild();
|
// allocate buffer
|
||||||
int right_child_nidx = tree[candidate.nid].RightChild();
|
sample_out = decltype(sample_out){h_gpair.Shape(), ctx_->gpu_id, linalg::Order::kF};
|
||||||
CPUExpandEntry l_best{left_child_nidx, depth};
|
h_sample_out = sample_out.HostView();
|
||||||
CPUExpandEntry r_best{right_child_nidx, depth};
|
|
||||||
best_splits.push_back(l_best);
|
|
||||||
best_splits.push_back(r_best);
|
|
||||||
}
|
|
||||||
auto const &histograms = histogram_builder_->Histogram();
|
|
||||||
auto ft = p_fmat->Info().feature_types.ConstHostSpan();
|
|
||||||
for (auto const &gmat : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
|
||||||
evaluator_->EvaluateSplits(histograms, gmat.cut, ft, *p_tree, &best_splits);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
driver.Push(best_splits.begin(), best_splits.end());
|
|
||||||
expand_set = driver.Pop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &h_out_position = p_out_position->HostVector();
|
for (auto tree_it = trees.begin(); tree_it != trees.end(); ++tree_it) {
|
||||||
this->LeafPartition(tree, gpair_h, &h_out_position);
|
if (need_copy()) {
|
||||||
monitor_->Stop(__func__);
|
// Copy gradient into buffer for sampling.
|
||||||
}
|
std::copy(linalg::cbegin(h_gpair), linalg::cend(h_gpair), linalg::begin(h_sample_out));
|
||||||
|
}
|
||||||
void QuantileHistMaker::Builder::UpdateTree(HostDeviceVector<GradientPair> *gpair, DMatrix *p_fmat,
|
SampleGradient(ctx_, *param, h_sample_out);
|
||||||
RegTree *p_tree,
|
auto *h_out_position = &out_position[tree_it - trees.begin()];
|
||||||
HostDeviceVector<bst_node_t> *p_out_position) {
|
if ((*tree_it)->IsMultiTarget()) {
|
||||||
monitor_->Start(__func__);
|
LOG(FATAL) << "Not implemented.";
|
||||||
|
} else {
|
||||||
std::vector<GradientPair> *gpair_ptr = &(gpair->HostVector());
|
UpdateTree<CPUExpandEntry>(&monitor_, h_sample_out, p_impl_.get(), p_fmat, param,
|
||||||
// in case 'num_parallel_trees != 1' no posibility to change initial gpair
|
h_out_position, *tree_it);
|
||||||
if (GetNumberOfTrees() != 1) {
|
}
|
||||||
gpair_local_.resize(gpair_ptr->size());
|
}
|
||||||
gpair_local_ = *gpair_ptr;
|
|
||||||
gpair_ptr = &gpair_local_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this->InitData(p_fmat, *p_tree, gpair_ptr);
|
bool UpdatePredictionCache(const DMatrix *data, linalg::VectorView<float> out_preds) override {
|
||||||
|
if (p_impl_) {
|
||||||
ExpandTree(p_fmat, p_tree, *gpair_ptr, p_out_position);
|
return p_impl_->UpdatePredictionCache(data, out_preds);
|
||||||
monitor_->Stop(__func__);
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
bool QuantileHistMaker::Builder::UpdatePredictionCache(DMatrix const *data,
|
|
||||||
linalg::VectorView<float> out_preds) const {
|
|
||||||
// p_last_fmat_ is a valid pointer as long as UpdatePredictionCache() is called in
|
|
||||||
// conjunction with Update().
|
|
||||||
if (!p_last_fmat_ || !p_last_tree_ || data != p_last_fmat_) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
monitor_->Start(__func__);
|
|
||||||
CHECK_EQ(out_preds.Size(), data->Info().num_row_);
|
|
||||||
UpdatePredictionCacheImpl(ctx_, p_last_tree_, partitioner_, out_preds);
|
|
||||||
monitor_->Stop(__func__);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t QuantileHistMaker::Builder::GetNumberOfTrees() { return n_trees_; }
|
|
||||||
|
|
||||||
void QuantileHistMaker::Builder::InitData(DMatrix *fmat, const RegTree &tree,
|
|
||||||
std::vector<GradientPair> *gpair) {
|
|
||||||
monitor_->Start(__func__);
|
|
||||||
const auto& info = fmat->Info();
|
|
||||||
|
|
||||||
{
|
|
||||||
size_t page_id{0};
|
|
||||||
int32_t n_total_bins{0};
|
|
||||||
partitioner_.clear();
|
|
||||||
for (auto const &page : fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
|
||||||
if (n_total_bins == 0) {
|
|
||||||
n_total_bins = page.cut.TotalBins();
|
|
||||||
} else {
|
|
||||||
CHECK_EQ(n_total_bins, page.cut.TotalBins());
|
|
||||||
}
|
|
||||||
partitioner_.emplace_back(this->ctx_, page.Size(), page.base_rowid, fmat->IsColumnSplit());
|
|
||||||
++page_id;
|
|
||||||
}
|
|
||||||
histogram_builder_->Reset(n_total_bins, HistBatch(param_), ctx_->Threads(), page_id,
|
|
||||||
collective::IsDistributed(), fmat->IsColumnSplit());
|
|
||||||
|
|
||||||
auto m_gpair = linalg::MakeTensorView(ctx_, *gpair, gpair->size(), static_cast<std::size_t>(1));
|
|
||||||
SampleGradient(ctx_, *param_, m_gpair);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// store a pointer to the tree
|
[[nodiscard]] bool HasNodePosition() const override { return true; }
|
||||||
p_last_tree_ = &tree;
|
};
|
||||||
evaluator_.reset(new HistEvaluator<CPUExpandEntry>{ctx_, param_, info, column_sampler_});
|
|
||||||
|
|
||||||
monitor_->Stop(__func__);
|
|
||||||
}
|
|
||||||
|
|
||||||
XGBOOST_REGISTER_TREE_UPDATER(QuantileHistMaker, "grow_quantile_histmaker")
|
XGBOOST_REGISTER_TREE_UPDATER(QuantileHistMaker, "grow_quantile_histmaker")
|
||||||
.describe("Grow tree using quantized histogram.")
|
.describe("Grow tree using quantized histogram.")
|
||||||
.set_body([](Context const *ctx, ObjInfo const *task) {
|
.set_body([](Context const *ctx, ObjInfo const *task) {
|
||||||
return new QuantileHistMaker(ctx, task);
|
return new QuantileHistMaker(ctx, task);
|
||||||
});
|
});
|
||||||
} // namespace tree
|
} // namespace xgboost::tree
|
||||||
} // namespace xgboost
|
|
||||||
|
|||||||
@ -1,133 +0,0 @@
|
|||||||
/*!
|
|
||||||
* Copyright 2017-2022 by XGBoost Contributors
|
|
||||||
* \file updater_quantile_hist.h
|
|
||||||
* \brief use quantized feature values to construct a tree
|
|
||||||
* \author Philip Cho, Tianqi Chen, Egor Smirnov
|
|
||||||
*/
|
|
||||||
#ifndef XGBOOST_TREE_UPDATER_QUANTILE_HIST_H_
|
|
||||||
#define XGBOOST_TREE_UPDATER_QUANTILE_HIST_H_
|
|
||||||
|
|
||||||
#include <xgboost/tree_updater.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <limits>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "xgboost/base.h"
|
|
||||||
#include "xgboost/data.h"
|
|
||||||
#include "xgboost/json.h"
|
|
||||||
|
|
||||||
#include "hist/evaluate_splits.h"
|
|
||||||
#include "hist/histogram.h"
|
|
||||||
#include "hist/expand_entry.h"
|
|
||||||
|
|
||||||
#include "common_row_partitioner.h"
|
|
||||||
#include "constraints.h"
|
|
||||||
#include "./param.h"
|
|
||||||
#include "./driver.h"
|
|
||||||
#include "../common/random.h"
|
|
||||||
#include "../common/timer.h"
|
|
||||||
#include "../common/hist_util.h"
|
|
||||||
#include "../common/row_set.h"
|
|
||||||
#include "../common/partition_builder.h"
|
|
||||||
#include "../common/column_matrix.h"
|
|
||||||
|
|
||||||
namespace xgboost::tree {
|
|
||||||
inline BatchParam HistBatch(TrainParam const* param) {
|
|
||||||
return {param->max_bin, param->sparse_threshold};
|
|
||||||
}
|
|
||||||
|
|
||||||
/*! \brief construct a tree using quantized feature values */
|
|
||||||
class QuantileHistMaker: public TreeUpdater {
|
|
||||||
public:
|
|
||||||
explicit QuantileHistMaker(Context const* ctx, ObjInfo const* task)
|
|
||||||
: TreeUpdater(ctx), task_{task} {}
|
|
||||||
void Configure(const Args&) override {}
|
|
||||||
|
|
||||||
void Update(TrainParam const* param, HostDeviceVector<GradientPair>* gpair, DMatrix* dmat,
|
|
||||||
common::Span<HostDeviceVector<bst_node_t>> out_position,
|
|
||||||
const std::vector<RegTree*>& trees) override;
|
|
||||||
|
|
||||||
bool UpdatePredictionCache(const DMatrix *data,
|
|
||||||
linalg::VectorView<float> out_preds) override;
|
|
||||||
|
|
||||||
void LoadConfig(Json const&) override {}
|
|
||||||
void SaveConfig(Json*) const override {}
|
|
||||||
|
|
||||||
[[nodiscard]] char const* Name() const override { return "grow_quantile_histmaker"; }
|
|
||||||
[[nodiscard]] bool HasNodePosition() const override { return true; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
// actual builder that runs the algorithm
|
|
||||||
struct Builder {
|
|
||||||
public:
|
|
||||||
// constructor
|
|
||||||
explicit Builder(const size_t n_trees, TrainParam const* param, DMatrix const* fmat,
|
|
||||||
ObjInfo task, Context const* ctx)
|
|
||||||
: n_trees_(n_trees),
|
|
||||||
param_(param),
|
|
||||||
p_last_fmat_(fmat),
|
|
||||||
histogram_builder_{new HistogramBuilder<CPUExpandEntry>},
|
|
||||||
task_{task},
|
|
||||||
ctx_{ctx},
|
|
||||||
monitor_{std::make_unique<common::Monitor>()} {
|
|
||||||
monitor_->Init("Quantile::Builder");
|
|
||||||
}
|
|
||||||
// update one tree, growing
|
|
||||||
void UpdateTree(HostDeviceVector<GradientPair>* gpair, DMatrix* p_fmat, RegTree* p_tree,
|
|
||||||
HostDeviceVector<bst_node_t>* p_out_position);
|
|
||||||
|
|
||||||
bool UpdatePredictionCache(DMatrix const* data, linalg::VectorView<float> out_preds) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
// initialize temp data structure
|
|
||||||
void InitData(DMatrix* fmat, const RegTree& tree, std::vector<GradientPair>* gpair);
|
|
||||||
|
|
||||||
size_t GetNumberOfTrees();
|
|
||||||
|
|
||||||
CPUExpandEntry InitRoot(DMatrix* p_fmat, RegTree* p_tree,
|
|
||||||
const std::vector<GradientPair>& gpair_h);
|
|
||||||
|
|
||||||
void BuildHistogram(DMatrix* p_fmat, RegTree* p_tree,
|
|
||||||
std::vector<CPUExpandEntry> const& valid_candidates,
|
|
||||||
std::vector<GradientPair> const& gpair);
|
|
||||||
|
|
||||||
void LeafPartition(RegTree const& tree, common::Span<GradientPair const> gpair,
|
|
||||||
std::vector<bst_node_t>* p_out_position);
|
|
||||||
|
|
||||||
void ExpandTree(DMatrix* p_fmat, RegTree* p_tree, const std::vector<GradientPair>& gpair_h,
|
|
||||||
HostDeviceVector<bst_node_t>* p_out_position);
|
|
||||||
|
|
||||||
private:
|
|
||||||
const size_t n_trees_;
|
|
||||||
TrainParam const* param_;
|
|
||||||
std::shared_ptr<common::ColumnSampler> column_sampler_{
|
|
||||||
std::make_shared<common::ColumnSampler>()};
|
|
||||||
|
|
||||||
std::vector<GradientPair> gpair_local_;
|
|
||||||
|
|
||||||
std::unique_ptr<HistEvaluator<CPUExpandEntry>> evaluator_;
|
|
||||||
std::vector<CommonRowPartitioner> partitioner_;
|
|
||||||
|
|
||||||
// back pointers to tree and data matrix
|
|
||||||
const RegTree* p_last_tree_{nullptr};
|
|
||||||
DMatrix const* const p_last_fmat_;
|
|
||||||
|
|
||||||
std::unique_ptr<HistogramBuilder<CPUExpandEntry>> histogram_builder_;
|
|
||||||
ObjInfo task_;
|
|
||||||
// Context for number of threads
|
|
||||||
Context const* ctx_;
|
|
||||||
|
|
||||||
std::unique_ptr<common::Monitor> monitor_;
|
|
||||||
};
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::unique_ptr<Builder> pimpl_;
|
|
||||||
ObjInfo const* task_;
|
|
||||||
};
|
|
||||||
} // namespace xgboost::tree
|
|
||||||
|
|
||||||
#endif // XGBOOST_TREE_UPDATER_QUANTILE_HIST_H_
|
|
||||||
Loading…
x
Reference in New Issue
Block a user