Define multi-strategy parameter. (#8890)

This commit is contained in:
Jiaming Yuan 2023-03-11 02:58:01 +08:00 committed by GitHub
parent 6deaec8027
commit 2aa838c75e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 221 additions and 134 deletions

View File

@ -8,29 +8,33 @@
#ifndef XGBOOST_LEARNER_H_ #ifndef XGBOOST_LEARNER_H_
#define XGBOOST_LEARNER_H_ #define XGBOOST_LEARNER_H_
#include <dmlc/io.h> // Serializable #include <dmlc/io.h> // for Serializable
#include <xgboost/base.h> #include <xgboost/base.h> // for bst_feature_t, bst_target_t, bst_float, Args, GradientPair
#include <xgboost/context.h> // Context #include <xgboost/context.h> // for Context
#include <xgboost/feature_map.h> #include <xgboost/linalg.h> // for Tensor, TensorView
#include <xgboost/host_device_vector.h> #include <xgboost/metric.h> // for Metric
#include <xgboost/linalg.h> // Tensor #include <xgboost/model.h> // for Configurable, Model
#include <xgboost/model.h> #include <xgboost/span.h> // for Span
#include <xgboost/task.h> #include <xgboost/task.h> // for ObjInfo
#include <map> #include <algorithm> // for max
#include <memory> #include <cstdint> // for int32_t, uint32_t, uint8_t
#include <string> #include <map> // for map
#include <utility> #include <memory> // for shared_ptr, unique_ptr
#include <vector> #include <string> // for string
#include <utility> // for move
#include <vector> // for vector
namespace xgboost { namespace xgboost {
class FeatureMap;
class Metric; class Metric;
class GradientBooster; class GradientBooster;
class ObjFunction; class ObjFunction;
class DMatrix; class DMatrix;
class Json; class Json;
struct XGBAPIThreadLocalEntry; struct XGBAPIThreadLocalEntry;
template <typename T>
class HostDeviceVector;
enum class PredictionType : std::uint8_t { // NOLINT enum class PredictionType : std::uint8_t { // NOLINT
kValue = 0, kValue = 0,
@ -143,7 +147,10 @@ class Learner : public Model, public Configurable, public dmlc::Serializable {
* \brief Get number of boosted rounds from gradient booster. * \brief Get number of boosted rounds from gradient booster.
*/ */
virtual int32_t BoostedRounds() const = 0; virtual int32_t BoostedRounds() const = 0;
virtual uint32_t Groups() const = 0; /**
* \brief Get the number of output groups from the model.
*/
virtual std::uint32_t Groups() const = 0;
void LoadModel(Json const& in) override = 0; void LoadModel(Json const& in) override = 0;
void SaveModel(Json* out) const override = 0; void SaveModel(Json* out) const override = 0;
@ -275,8 +282,16 @@ class Learner : public Model, public Configurable, public dmlc::Serializable {
struct LearnerModelParamLegacy; struct LearnerModelParamLegacy;
/* /**
* \brief Basic Model Parameters, used to describe the booster. * \brief Strategy for building multi-target models.
*/
enum class MultiStrategy : std::int32_t {
kComposite = 0,
kMonolithic = 1,
};
/**
* \brief Basic model parameters, used to describe the booster.
*/ */
struct LearnerModelParam { struct LearnerModelParam {
private: private:
@ -287,30 +302,51 @@ struct LearnerModelParam {
linalg::Tensor<float, 1> base_score_; linalg::Tensor<float, 1> base_score_;
public: public:
/* \brief number of features */ /**
uint32_t num_feature { 0 }; * \brief The number of features.
/* \brief number of classes, if it is multi-class classification */ */
uint32_t num_output_group { 0 }; bst_feature_t num_feature{0};
/* \brief Current task, determined by objective. */ /**
* \brief The number of classes or targets.
*/
std::uint32_t num_output_group{0};
/**
* \brief Current task, determined by objective.
*/
ObjInfo task{ObjInfo::kRegression}; ObjInfo task{ObjInfo::kRegression};
/**
* \brief Strategy for building multi-target models.
*/
MultiStrategy multi_strategy{MultiStrategy::kComposite};
LearnerModelParam() = default; LearnerModelParam() = default;
// As the old `LearnerModelParamLegacy` is still used by binary IO, we keep // As the old `LearnerModelParamLegacy` is still used by binary IO, we keep
// this one as an immutable copy. // this one as an immutable copy.
LearnerModelParam(Context const* ctx, LearnerModelParamLegacy const& user_param, LearnerModelParam(Context const* ctx, LearnerModelParamLegacy const& user_param,
linalg::Tensor<float, 1> base_margin, ObjInfo t); linalg::Tensor<float, 1> base_margin, ObjInfo t, MultiStrategy multi_strategy);
LearnerModelParam(LearnerModelParamLegacy const& user_param, ObjInfo t); LearnerModelParam(LearnerModelParamLegacy const& user_param, ObjInfo t,
LearnerModelParam(bst_feature_t n_features, linalg::Tensor<float, 1> base_margin, MultiStrategy multi_strategy);
uint32_t n_groups) LearnerModelParam(bst_feature_t n_features, linalg::Tensor<float, 1> base_score,
: base_score_{std::move(base_margin)}, num_feature{n_features}, num_output_group{n_groups} {} std::uint32_t n_groups, bst_target_t n_targets, MultiStrategy multi_strategy)
: base_score_{std::move(base_score)},
num_feature{n_features},
num_output_group{std::max(n_groups, n_targets)},
multi_strategy{multi_strategy} {}
linalg::TensorView<float const, 1> BaseScore(Context const* ctx) const; linalg::TensorView<float const, 1> BaseScore(Context const* ctx) const;
linalg::TensorView<float const, 1> BaseScore(int32_t device) const; [[nodiscard]] linalg::TensorView<float const, 1> BaseScore(std::int32_t device) const;
void Copy(LearnerModelParam const& that); void Copy(LearnerModelParam const& that);
[[nodiscard]] bool IsVectorLeaf() const noexcept {
return multi_strategy == MultiStrategy::kMonolithic;
}
[[nodiscard]] bst_target_t OutputLength() const noexcept { return this->num_output_group; }
[[nodiscard]] bst_target_t LeafLength() const noexcept {
return this->IsVectorLeaf() ? this->OutputLength() : 1;
}
/* \brief Whether this parameter is initialized with LearnerModelParamLegacy. */ /* \brief Whether this parameter is initialized with LearnerModelParamLegacy. */
bool Initialized() const { return num_feature != 0 && num_output_group != 0; } [[nodiscard]] bool Initialized() const { return num_feature != 0 && num_output_group != 0; }
}; };
} // namespace xgboost } // namespace xgboost

View File

@ -12,10 +12,11 @@
#include <vector> #include <vector>
#include "xgboost/c_api.h" #include "xgboost/c_api.h"
#include "xgboost/data.h" // DMatrix #include "xgboost/data.h" // DMatrix
#include "xgboost/feature_map.h" // for FeatureMap
#include "xgboost/json.h" #include "xgboost/json.h"
#include "xgboost/learner.h" #include "xgboost/learner.h"
#include "xgboost/linalg.h" // ArrayInterfaceHandler #include "xgboost/linalg.h" // ArrayInterfaceHandler
#include "xgboost/logging.h" #include "xgboost/logging.h"
#include "xgboost/string_view.h" // StringView #include "xgboost/string_view.h" // StringView

View File

@ -6,54 +6,67 @@
*/ */
#include "xgboost/learner.h" #include "xgboost/learner.h"
#include <dmlc/any.h> #include <dmlc/io.h> // for Stream
#include <dmlc/io.h> #include <dmlc/parameter.h> // for FieldEntry, DMLC_DECLARE_FIELD, Parameter, DMLC...
#include <dmlc/parameter.h> #include <dmlc/thread_local.h> // for ThreadLocalStore
#include <dmlc/thread_local.h>
#include <algorithm> #include <algorithm> // for equal, max, transform, sort, find_if, all_of
#include <array> #include <array> // for array
#include <atomic> #include <atomic> // for atomic
#include <iomanip> #include <cctype> // for isalpha, isspace
#include <limits> // std::numeric_limits #include <cmath> // for isnan, isinf
#include <memory> #include <cstdint> // for int32_t, uint32_t, int64_t, uint64_t
#include <mutex> #include <cstdlib> // for atoi
#include <sstream> #include <cstring> // for memcpy, size_t, memset
#include <stack> #include <functional> // for less
#include <string> #include <iomanip> // for operator<<, setiosflags
#include <utility> // for as_const #include <iterator> // for back_insert_iterator, distance, back_inserter
#include <vector> #include <limits> // for numeric_limits
#include <memory> // for allocator, unique_ptr, shared_ptr, operator==
#include <mutex> // for mutex, lock_guard
#include <set> // for set
#include <sstream> // for operator<<, basic_ostream, basic_ostream::opera...
#include <stack> // for stack
#include <string> // for basic_string, char_traits, operator<, string
#include <system_error> // for errc
#include <tuple> // for get
#include <unordered_map> // for operator!=, unordered_map
#include <utility> // for pair, as_const, move, swap
#include <vector> // for vector
#include "collective/communicator-inl.h" #include "collective/communicator-inl.h" // for Allreduce, Broadcast, GetRank, IsDistributed
#include "common/api_entry.h" // XGBAPIThreadLocalEntry #include "collective/communicator.h" // for Operation
#include "common/charconv.h" #include "common/api_entry.h" // for XGBAPIThreadLocalEntry
#include "common/common.h" #include "common/charconv.h" // for to_chars, to_chars_result, NumericLimits, from_...
#include "common/io.h" #include "common/common.h" // for ToString, Split
#include "common/observer.h" #include "common/io.h" // for PeekableInStream, ReadAll, FixedSizeStream, Mem...
#include "common/random.h" #include "common/observer.h" // for TrainingObserver
#include "common/threading_utils.h" #include "common/random.h" // for GlobalRandom
#include "common/timer.h" #include "common/timer.h" // for Monitor
#include "common/version.h" #include "common/version.h" // for Version
#include "xgboost/base.h" #include "dmlc/endian.h" // for ByteSwap, DMLC_IO_NO_ENDIAN_SWAP
#include "xgboost/c_api.h" #include "xgboost/base.h" // for Args, bst_float, GradientPair, bst_feature_t
#include "xgboost/context.h" // Context #include "xgboost/context.h" // for Context
#include "xgboost/data.h" #include "xgboost/data.h" // for DMatrix, MetaInfo
#include "xgboost/feature_map.h" #include "xgboost/gbm.h" // for GradientBooster
#include "xgboost/gbm.h" #include "xgboost/global_config.h" // for GlobalConfiguration, GlobalConfigThreadLocalStore
#include "xgboost/host_device_vector.h" #include "xgboost/host_device_vector.h" // for HostDeviceVector
#include "xgboost/json.h" #include "xgboost/json.h" // for Json, get, Object, String, IsA, Array, ToJson
#include "xgboost/logging.h" #include "xgboost/linalg.h" // for Tensor, TensorView
#include "xgboost/metric.h" #include "xgboost/logging.h" // for CHECK, LOG, CHECK_EQ
#include "xgboost/model.h" #include "xgboost/metric.h" // for Metric
#include "xgboost/objective.h" #include "xgboost/objective.h" // for ObjFunction
#include "xgboost/parameter.h" #include "xgboost/parameter.h" // for DECLARE_FIELD_ENUM_CLASS, XGBoostParameter
#include "xgboost/predictor.h" #include "xgboost/predictor.h" // for PredictionContainer, PredictionCacheEntry
#include "xgboost/string_view.h" // for operator<<, StringView
#include "xgboost/task.h" // for ObjInfo
namespace { namespace {
const char* kMaxDeltaStepDefaultValue = "0.7"; const char* kMaxDeltaStepDefaultValue = "0.7";
} // anonymous namespace } // anonymous namespace
DECLARE_FIELD_ENUM_CLASS(xgboost::MultiStrategy);
namespace xgboost { namespace xgboost {
Learner::~Learner() = default; Learner::~Learner() = default;
namespace { namespace {
@ -86,8 +99,10 @@ struct LearnerModelParamLegacy : public dmlc::Parameter<LearnerModelParamLegacy>
/*! \brief the version of XGBoost. */ /*! \brief the version of XGBoost. */
std::uint32_t major_version; std::uint32_t major_version;
std::uint32_t minor_version; std::uint32_t minor_version;
/**
uint32_t num_target{1}; * \brief Number of target variables.
*/
bst_target_t num_target;
/** /**
* \brief Whether we should calculate the base score from training data. * \brief Whether we should calculate the base score from training data.
* *
@ -113,7 +128,7 @@ struct LearnerModelParamLegacy : public dmlc::Parameter<LearnerModelParamLegacy>
} }
// Skip other legacy fields. // Skip other legacy fields.
Json ToJson() const { [[nodiscard]] Json ToJson() const {
Json obj{Object{}}; Json obj{Object{}};
char floats[NumericLimits<float>::kToCharsSize]; char floats[NumericLimits<float>::kToCharsSize];
auto ret = to_chars(floats, floats + NumericLimits<float>::kToCharsSize, base_score); auto ret = to_chars(floats, floats + NumericLimits<float>::kToCharsSize, base_score);
@ -163,7 +178,7 @@ struct LearnerModelParamLegacy : public dmlc::Parameter<LearnerModelParamLegacy>
from_chars(str.c_str(), str.c_str() + str.size(), base_score); from_chars(str.c_str(), str.c_str() + str.size(), base_score);
} }
LearnerModelParamLegacy ByteSwap() const { [[nodiscard]] LearnerModelParamLegacy ByteSwap() const {
LearnerModelParamLegacy x = *this; LearnerModelParamLegacy x = *this;
dmlc::ByteSwap(&x.base_score, sizeof(x.base_score), 1); dmlc::ByteSwap(&x.base_score, sizeof(x.base_score), 1);
dmlc::ByteSwap(&x.num_feature, sizeof(x.num_feature), 1); dmlc::ByteSwap(&x.num_feature, sizeof(x.num_feature), 1);
@ -226,35 +241,38 @@ struct LearnerModelParamLegacy : public dmlc::Parameter<LearnerModelParamLegacy>
DMLC_DECLARE_FIELD(num_feature) DMLC_DECLARE_FIELD(num_feature)
.set_default(0) .set_default(0)
.describe( .describe(
"Number of features in training data," "Number of features in training data, this parameter will be automatically detected by "
" this parameter will be automatically detected by learner."); "learner.");
DMLC_DECLARE_FIELD(num_class).set_default(0).set_lower_bound(0).describe( DMLC_DECLARE_FIELD(num_class).set_default(0).set_lower_bound(0).describe(
"Number of class option for multi-class classifier. " "Number of class option for multi-class classifier. "
" By default equals 0 and corresponds to binary classifier."); " By default equals 0 and corresponds to binary classifier.");
DMLC_DECLARE_FIELD(num_target) DMLC_DECLARE_FIELD(num_target)
.set_default(1) .set_default(1)
.set_lower_bound(1) .set_lower_bound(1)
.describe("Number of target for multi-target regression."); .describe("Number of output targets. Can be set automatically if not specified.");
DMLC_DECLARE_FIELD(boost_from_average) DMLC_DECLARE_FIELD(boost_from_average)
.set_default(true) .set_default(true)
.describe("Whether we should calculate the base score from training data."); .describe("Whether we should calculate the base score from training data.");
} }
}; };
LearnerModelParam::LearnerModelParam(LearnerModelParamLegacy const& user_param, ObjInfo t) LearnerModelParam::LearnerModelParam(LearnerModelParamLegacy const& user_param, ObjInfo t,
: num_feature{user_param.num_feature}, task{t} { MultiStrategy multi_strategy)
auto n_classes = std::max(static_cast<uint32_t>(user_param.num_class), 1u); : num_feature{user_param.num_feature},
auto n_targets = user_param.num_target; num_output_group{
num_output_group = std::max(n_classes, n_targets); std::max(static_cast<std::uint32_t>(user_param.num_class), user_param.num_target)},
// For version < 1.6, n_targets == 0 task{t},
CHECK(n_classes <= 1 || n_targets <= 1) multi_strategy{multi_strategy} {
<< "Multi-class multi-output is not yet supported. n_classes:" << n_classes if (user_param.num_class > 1 && user_param.num_target > 1) {
<< ", n_targets:" << n_targets; LOG(FATAL) << "multi-target-multi-class is not yet supported. Output classes:"
<< user_param.num_class << ", output targets:" << user_param.num_target;
}
} }
LearnerModelParam::LearnerModelParam(Context const* ctx, LearnerModelParamLegacy const& user_param, LearnerModelParam::LearnerModelParam(Context const* ctx, LearnerModelParamLegacy const& user_param,
linalg::Tensor<float, 1> base_margin, ObjInfo t) linalg::Tensor<float, 1> base_margin, ObjInfo t,
: LearnerModelParam{user_param, t} { MultiStrategy multi_strategy)
: LearnerModelParam{user_param, t, multi_strategy} {
std::swap(base_score_, base_margin); std::swap(base_score_, base_margin);
// Make sure read access everywhere for thread-safe prediction. // Make sure read access everywhere for thread-safe prediction.
std::as_const(base_score_).HostView(); std::as_const(base_score_).HostView();
@ -297,6 +315,7 @@ void LearnerModelParam::Copy(LearnerModelParam const& that) {
num_feature = that.num_feature; num_feature = that.num_feature;
num_output_group = that.num_output_group; num_output_group = that.num_output_group;
task = that.task; task = that.task;
multi_strategy = that.multi_strategy;
} }
struct LearnerTrainParam : public XGBoostParameter<LearnerTrainParam> { struct LearnerTrainParam : public XGBoostParameter<LearnerTrainParam> {
@ -306,18 +325,26 @@ struct LearnerTrainParam : public XGBoostParameter<LearnerTrainParam> {
// specified by users. Move them to model parameter once we can get rid of binary IO. // specified by users. Move them to model parameter once we can get rid of binary IO.
std::string booster; std::string booster;
std::string objective; std::string objective;
// This is a training parameter and is not saved (nor loaded) in the model.
MultiStrategy multi_strategy{MultiStrategy::kComposite};
// declare parameters // declare parameters
DMLC_DECLARE_PARAMETER(LearnerTrainParam) { DMLC_DECLARE_PARAMETER(LearnerTrainParam) {
DMLC_DECLARE_FIELD(disable_default_eval_metric) DMLC_DECLARE_FIELD(disable_default_eval_metric)
.set_default(false) .set_default(false)
.describe("Flag to disable default metric. Set to >0 to disable"); .describe("Flag to disable default metric. Set to >0 to disable");
DMLC_DECLARE_FIELD(booster) DMLC_DECLARE_FIELD(booster).set_default("gbtree").describe(
.set_default("gbtree") "Gradient booster used for training.");
.describe("Gradient booster used for training.");
DMLC_DECLARE_FIELD(objective) DMLC_DECLARE_FIELD(objective)
.set_default("reg:squarederror") .set_default("reg:squarederror")
.describe("Objective function used for obtaining gradient."); .describe("Objective function used for obtaining gradient.");
DMLC_DECLARE_FIELD(multi_strategy)
.add_enum("composite", MultiStrategy::kComposite)
.add_enum("monolithic", MultiStrategy::kMonolithic)
.set_default(MultiStrategy::kComposite)
.describe(
"Strategy used for training multi-target models. `mono` means building one single tree "
"for all targets.");
} }
}; };
@ -379,8 +406,10 @@ class LearnerConfiguration : public Learner {
// transform to margin // transform to margin
h_base_score(0) = obj_->ProbToMargin(mparam_.base_score); h_base_score(0) = obj_->ProbToMargin(mparam_.base_score);
CHECK(tparam_.GetInitialised());
// move it to model param, which is shared with all other components. // move it to model param, which is shared with all other components.
learner_model_param_ = LearnerModelParam(Ctx(), mparam_, std::move(base_score), task); learner_model_param_ =
LearnerModelParam(Ctx(), mparam_, std::move(base_score), task, tparam_.multi_strategy);
CHECK(learner_model_param_.Initialized()); CHECK(learner_model_param_.Initialized());
CHECK_NE(learner_model_param_.BaseScore(Ctx()).Size(), 0); CHECK_NE(learner_model_param_.BaseScore(Ctx()).Size(), 0);
} }
@ -748,7 +777,6 @@ class LearnerConfiguration : public Learner {
<< "0 feature is supplied. Are you using raw Booster interface?"; << "0 feature is supplied. Are you using raw Booster interface?";
// Remove these once binary IO is gone. // Remove these once binary IO is gone.
cfg_["num_feature"] = common::ToString(mparam_.num_feature); cfg_["num_feature"] = common::ToString(mparam_.num_feature);
cfg_["num_class"] = common::ToString(mparam_.num_class);
} }
void ConfigureGBM(LearnerTrainParam const& old, Args const& args) { void ConfigureGBM(LearnerTrainParam const& old, Args const& args) {
@ -779,9 +807,17 @@ class LearnerConfiguration : public Learner {
if (obj_ == nullptr || tparam_.objective != old.objective) { if (obj_ == nullptr || tparam_.objective != old.objective) {
obj_.reset(ObjFunction::Create(tparam_.objective, &ctx_)); obj_.reset(ObjFunction::Create(tparam_.objective, &ctx_));
} }
bool has_nc {cfg_.find("num_class") != cfg_.cend()};
// Inject num_class into configuration.
// FIXME(jiamingy): Remove the duplicated parameter in softmax
cfg_["num_class"] = common::ToString(mparam_.num_class);
auto& args = *p_args; auto& args = *p_args;
args = {cfg_.cbegin(), cfg_.cend()}; // renew args = {cfg_.cbegin(), cfg_.cend()}; // renew
obj_->Configure(args); obj_->Configure(args);
if (!has_nc) {
cfg_.erase("num_class");
}
} }
void ConfigureMetrics(Args const& args) { void ConfigureMetrics(Args const& args) {
@ -805,7 +841,7 @@ class LearnerConfiguration : public Learner {
void ConfigureTargets() { void ConfigureTargets() {
CHECK(this->obj_); CHECK(this->obj_);
auto const& cache = prediction_container_.Container(); auto const& cache = prediction_container_.Container();
size_t n_targets = 1; bst_target_t n_targets = 1;
for (auto const& d : cache) { for (auto const& d : cache) {
if (n_targets == 1) { if (n_targets == 1) {
n_targets = this->obj_->Targets(d.first.ptr->Info()); n_targets = this->obj_->Targets(d.first.ptr->Info());
@ -814,7 +850,8 @@ class LearnerConfiguration : public Learner {
CHECK(n_targets == t || 1 == t) << "Inconsistent labels."; CHECK(n_targets == t || 1 == t) << "Inconsistent labels.";
} }
} }
if (mparam_.num_target != 1) {
if (mparam_.num_target > 1) {
CHECK(n_targets == 1 || n_targets == mparam_.num_target) CHECK(n_targets == 1 || n_targets == mparam_.num_target)
<< "Inconsistent configuration of num_target. Configuration result from input data:" << "Inconsistent configuration of num_target. Configuration result from input data:"
<< n_targets << ", configuration from parameter:" << mparam_.num_target; << n_targets << ", configuration from parameter:" << mparam_.num_target;
@ -974,9 +1011,6 @@ class LearnerIO : public LearnerConfiguration {
if (!DMLC_IO_NO_ENDIAN_SWAP) { if (!DMLC_IO_NO_ENDIAN_SWAP) {
mparam_ = mparam_.ByteSwap(); mparam_ = mparam_.ByteSwap();
} }
if (mparam_.num_target == 0) {
mparam_.num_target = 1;
}
CHECK(fi->Read(&tparam_.objective)) << "BoostLearner: wrong model format"; CHECK(fi->Read(&tparam_.objective)) << "BoostLearner: wrong model format";
CHECK(fi->Read(&tparam_.booster)) << "BoostLearner: wrong model format"; CHECK(fi->Read(&tparam_.booster)) << "BoostLearner: wrong model format";
@ -1030,7 +1064,7 @@ class LearnerIO : public LearnerConfiguration {
: obj_->ProbToMargin(mparam_.base_score)}, : obj_->ProbToMargin(mparam_.base_score)},
{1}, {1},
Context::kCpuId}, Context::kCpuId},
obj_->Task()); obj_->Task(), tparam_.multi_strategy);
if (attributes_.find("objective") != attributes_.cend()) { if (attributes_.find("objective") != attributes_.cend()) {
auto obj_str = attributes_.at("objective"); auto obj_str = attributes_.at("objective");
@ -1058,7 +1092,6 @@ class LearnerIO : public LearnerConfiguration {
mparam_.major_version = std::get<0>(Version::Self()); mparam_.major_version = std::get<0>(Version::Self());
mparam_.minor_version = std::get<1>(Version::Self()); mparam_.minor_version = std::get<1>(Version::Self());
cfg_["num_class"] = common::ToString(mparam_.num_class);
cfg_["num_feature"] = common::ToString(mparam_.num_feature); cfg_["num_feature"] = common::ToString(mparam_.num_feature);
auto n = tparam_.__DICT__(); auto n = tparam_.__DICT__();
@ -1071,6 +1104,8 @@ class LearnerIO : public LearnerConfiguration {
// JSON serialization format. // JSON serialization format.
void SaveModel(dmlc::Stream* fo) const override { void SaveModel(dmlc::Stream* fo) const override {
this->CheckModelInitialized(); this->CheckModelInitialized();
CHECK(!this->learner_model_param_.IsVectorLeaf())
<< "Please use JSON/UBJ format for model serialization with multi-output models.";
LearnerModelParamLegacy mparam = mparam_; // make a copy to potentially modify LearnerModelParamLegacy mparam = mparam_; // make a copy to potentially modify
std::vector<std::pair<std::string, std::string> > extra_attr; std::vector<std::pair<std::string, std::string> > extra_attr;

View File

@ -3,18 +3,19 @@
*/ */
#include "xgboost/predictor.h" #include "xgboost/predictor.h"
#include <dmlc/registry.h> #include <dmlc/registry.h> // for DMLC_REGISTRY_LINK_TAG
#include <string> // std::string #include <cstdint> // for int32_t
#include <string> // for string, to_string
#include "../gbm/gbtree.h" // GBTreeModel #include "../gbm/gbtree_model.h" // for GBTreeModel
#include "xgboost/base.h" // bst_row_t,bst_group_t #include "xgboost/base.h" // for bst_float, Args, bst_group_t, bst_row_t
#include "xgboost/context.h" // Context #include "xgboost/context.h" // for Context
#include "xgboost/data.h" // MetaInfo #include "xgboost/data.h" // for MetaInfo
#include "xgboost/host_device_vector.h" // HostDeviceVector #include "xgboost/host_device_vector.h" // for HostDeviceVector
#include "xgboost/learner.h" // LearnerModelParam #include "xgboost/learner.h" // for LearnerModelParam
#include "xgboost/linalg.h" // Tensor #include "xgboost/linalg.h" // for Tensor, TensorView
#include "xgboost/logging.h" #include "xgboost/logging.h" // for CHECK_EQ, CHECK_NE, LOG
namespace dmlc { namespace dmlc {
DMLC_REGISTRY_ENABLE(::xgboost::PredictorReg); DMLC_REGISTRY_ENABLE(::xgboost::PredictorReg);
@ -45,15 +46,16 @@ void ValidateBaseMarginShape(linalg::Tensor<float, D> const& margin, bst_row_t n
void Predictor::InitOutPredictions(const MetaInfo& info, HostDeviceVector<bst_float>* out_preds, void Predictor::InitOutPredictions(const MetaInfo& info, HostDeviceVector<bst_float>* out_preds,
const gbm::GBTreeModel& model) const { const gbm::GBTreeModel& model) const {
CHECK_NE(model.learner_model_param->num_output_group, 0); CHECK_NE(model.learner_model_param->num_output_group, 0);
size_t n_classes = model.learner_model_param->num_output_group; std::size_t n{model.learner_model_param->OutputLength() * info.num_row_};
size_t n = n_classes * info.num_row_;
const HostDeviceVector<bst_float>* base_margin = info.base_margin_.Data(); const HostDeviceVector<bst_float>* base_margin = info.base_margin_.Data();
if (ctx_->gpu_id >= 0) { if (ctx_->gpu_id >= 0) {
out_preds->SetDevice(ctx_->gpu_id); out_preds->SetDevice(ctx_->gpu_id);
} }
if (!base_margin->Empty()) { if (!base_margin->Empty()) {
out_preds->Resize(n); out_preds->Resize(n);
ValidateBaseMarginShape(info.base_margin_, info.num_row_, n_classes); ValidateBaseMarginShape(info.base_margin_, info.num_row_,
model.learner_model_param->OutputLength());
out_preds->Copy(*base_margin); out_preds->Copy(*base_margin);
} else { } else {
// cannot rely on the Resize to fill as it might skip if the size is already correct. // cannot rely on the Resize to fill as it might skip if the size is already correct.
@ -64,12 +66,10 @@ void Predictor::InitOutPredictions(const MetaInfo& info, HostDeviceVector<bst_fl
} }
} // namespace xgboost } // namespace xgboost
namespace xgboost { namespace xgboost::predictor {
namespace predictor {
// List of files that will be force linked in static links. // List of files that will be force linked in static links.
#ifdef XGBOOST_USE_CUDA #ifdef XGBOOST_USE_CUDA
DMLC_REGISTRY_LINK_TAG(gpu_predictor); DMLC_REGISTRY_LINK_TAG(gpu_predictor);
#endif // XGBOOST_USE_CUDA #endif // XGBOOST_USE_CUDA
DMLC_REGISTRY_LINK_TAG(cpu_predictor); DMLC_REGISTRY_LINK_TAG(cpu_predictor);
} // namespace predictor } // namespace xgboost::predictor
} // namespace xgboost

View File

@ -24,6 +24,7 @@
#include "../../src/data/array_interface.h" #include "../../src/data/array_interface.h"
#include "../../src/gbm/gbtree_model.h" #include "../../src/gbm/gbtree_model.h"
#include "filesystem.h" // dmlc::TemporaryDirectory #include "filesystem.h" // dmlc::TemporaryDirectory
#include "xgboost/linalg.h"
#if defined(__CUDACC__) #if defined(__CUDACC__)
#define DeclareUnifiedTest(name) GPU ## name #define DeclareUnifiedTest(name) GPU ## name
@ -461,7 +462,7 @@ inline LearnerModelParam MakeMP(bst_feature_t n_features, float base_score, uint
int32_t device = Context::kCpuId) { int32_t device = Context::kCpuId) {
size_t shape[1]{1}; size_t shape[1]{1};
LearnerModelParam mparam(n_features, linalg::Tensor<float, 1>{{base_score}, shape, device}, LearnerModelParam mparam(n_features, linalg::Tensor<float, 1>{{base_score}, shape, device},
n_groups); n_groups, 1, MultiStrategy::kComposite);
return mparam; return mparam;
} }

View File

@ -2,24 +2,26 @@
* Copyright 2023 by XGBoost Contributors * Copyright 2023 by XGBoost Contributors
*/ */
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <xgboost/base.h> // bst_target_t #include <xgboost/base.h> // for Args, bst_target_t
#include <xgboost/data.h> // DMatrix #include <xgboost/data.h> // for DMatrix, MetaInfo
#include <xgboost/json.h> // Json,Object,Number,get #include <xgboost/json.h> // for Json, get, Object, String
#include <xgboost/learner.h> // Learner #include <xgboost/learner.h> // for Learner
#include <cstddef> // size_t #include <algorithm> // for copy
#include <memory> // shared_ptr,unique_ptr #include <cstddef> // for size_t
#include <numeric> #include <memory> // for shared_ptr, allocator, __shared_ptr_access
#include <string> // stod #include <numeric> // for accumulate
#include <vector> #include <string> // for stod, string
#include <vector> // for vector
#include "../../src/common/linalg_op.h" // cbegin,cend #include "../../src/common/linalg_op.h" // for begin, cbegin, cend
#include "../../src/common/stats.h" // Median #include "../../src/common/stats.h" // for Median
#include "helpers.h" // RandomDataGenerator #include "../../src/common/transform_iterator.h" // for IndexTransformIter
#include "xgboost/linalg.h" #include "helpers.h" // for RandomDataGenerator
#include "xgboost/host_device_vector.h" // for HostDeviceVector
#include "xgboost/linalg.h" // for Tensor, All, TensorView, Vector
namespace xgboost { namespace xgboost {
class TestL1MultiTarget : public ::testing::Test { class TestL1MultiTarget : public ::testing::Test {
std::shared_ptr<DMatrix> Xy_; std::shared_ptr<DMatrix> Xy_;
std::shared_ptr<DMatrix> Xyw_; std::shared_ptr<DMatrix> Xyw_;
@ -117,4 +119,16 @@ TEST_F(TestL1MultiTarget, Approx) { this->RunTest("approx"); }
#if defined(XGBOOST_USE_CUDA) #if defined(XGBOOST_USE_CUDA)
TEST_F(TestL1MultiTarget, GpuHist) { this->RunTest("gpu_hist"); } TEST_F(TestL1MultiTarget, GpuHist) { this->RunTest("gpu_hist"); }
#endif // defined(XGBOOST_USE_CUDA) #endif // defined(XGBOOST_USE_CUDA)
TEST(MultiStrategy, Configure) {
auto p_fmat = RandomDataGenerator{12ul, 3ul, 0.0}.GenerateDMatrix();
p_fmat->Info().labels.Reshape(p_fmat->Info().num_row_, 2);
std::unique_ptr<Learner> learner{Learner::Create({p_fmat})};
learner->SetParams(Args{{"multi_strategy", "monolithic"}, {"num_target", "2"}});
learner->Configure();
ASSERT_EQ(learner->Groups(), 2);
learner->SetParams(Args{{"multi_strategy", "monolithic"}, {"num_target", "0"}});
ASSERT_THROW({ learner->Configure(); }, dmlc::Error);
}
} // namespace xgboost } // namespace xgboost