Implement feature score for linear model. (#7048)
* Add feature score support for linear model. * Port R interface to the new implementation. * Add linear model support in Python. Co-authored-by: Philip Hyunsu Cho <chohyu01@cs.washington.edu>
This commit is contained in:
@@ -927,14 +927,17 @@ XGB_DLL int XGBoosterSlice(BoosterHandle handle, int begin_layer,
|
||||
API_END();
|
||||
}
|
||||
|
||||
inline void XGBoostDumpModelImpl(BoosterHandle handle, const FeatureMap &fmap,
|
||||
inline void XGBoostDumpModelImpl(BoosterHandle handle, FeatureMap* fmap,
|
||||
int with_stats, const char *format,
|
||||
xgboost::bst_ulong *len,
|
||||
const char ***out_models) {
|
||||
auto *bst = static_cast<Learner*>(handle);
|
||||
bst->Configure();
|
||||
GenerateFeatureMap(bst, {}, bst->GetNumFeature(), fmap);
|
||||
|
||||
std::vector<std::string>& str_vecs = bst->GetThreadLocal().ret_vec_str;
|
||||
std::vector<const char*>& charp_vecs = bst->GetThreadLocal().ret_vec_charp;
|
||||
str_vecs = bst->DumpModel(fmap, with_stats != 0, format);
|
||||
str_vecs = bst->DumpModel(*fmap, with_stats != 0, format);
|
||||
charp_vecs.resize(str_vecs.size());
|
||||
for (size_t i = 0; i < str_vecs.size(); ++i) {
|
||||
charp_vecs[i] = str_vecs[i].c_str();
|
||||
@@ -962,14 +965,9 @@ XGB_DLL int XGBoosterDumpModelEx(BoosterHandle handle,
|
||||
const char*** out_models) {
|
||||
API_BEGIN();
|
||||
CHECK_HANDLE();
|
||||
FeatureMap featmap;
|
||||
if (strlen(fmap) != 0) {
|
||||
std::unique_ptr<dmlc::Stream> fs(
|
||||
dmlc::Stream::Create(fmap, "r"));
|
||||
dmlc::istream is(fs.get());
|
||||
featmap.LoadText(is);
|
||||
}
|
||||
XGBoostDumpModelImpl(handle, featmap, with_stats, format, len, out_models);
|
||||
std::string uri{fmap};
|
||||
FeatureMap featmap = LoadFeatureMap(uri);
|
||||
XGBoostDumpModelImpl(handle, &featmap, with_stats, format, len, out_models);
|
||||
API_END();
|
||||
}
|
||||
|
||||
@@ -980,8 +978,8 @@ XGB_DLL int XGBoosterDumpModelWithFeatures(BoosterHandle handle,
|
||||
int with_stats,
|
||||
xgboost::bst_ulong* len,
|
||||
const char*** out_models) {
|
||||
return XGBoosterDumpModelExWithFeatures(handle, fnum, fname, ftype, with_stats,
|
||||
"text", len, out_models);
|
||||
return XGBoosterDumpModelExWithFeatures(handle, fnum, fname, ftype,
|
||||
with_stats, "text", len, out_models);
|
||||
}
|
||||
|
||||
XGB_DLL int XGBoosterDumpModelExWithFeatures(BoosterHandle handle,
|
||||
@@ -998,7 +996,7 @@ XGB_DLL int XGBoosterDumpModelExWithFeatures(BoosterHandle handle,
|
||||
for (int i = 0; i < fnum; ++i) {
|
||||
featmap.PushBack(i, fname[i], ftype[i]);
|
||||
}
|
||||
XGBoostDumpModelImpl(handle, featmap, with_stats, format, len, out_models);
|
||||
XGBoostDumpModelImpl(handle, &featmap, with_stats, format, len, out_models);
|
||||
API_END();
|
||||
}
|
||||
|
||||
@@ -1098,11 +1096,12 @@ XGB_DLL int XGBoosterGetStrFeatureInfo(BoosterHandle handle, const char *field,
|
||||
API_END();
|
||||
}
|
||||
|
||||
XGB_DLL int XGBoosterFeatureScore(BoosterHandle handle,
|
||||
const char *json_config,
|
||||
xgboost::bst_ulong* out_length,
|
||||
const char ***out_features,
|
||||
float **out_scores) {
|
||||
XGB_DLL int XGBoosterFeatureScore(BoosterHandle handle, char const *json_config,
|
||||
xgboost::bst_ulong *out_n_features,
|
||||
char const ***out_features,
|
||||
bst_ulong *out_dim,
|
||||
bst_ulong const **out_shape,
|
||||
float const **out_scores) {
|
||||
API_BEGIN();
|
||||
CHECK_HANDLE();
|
||||
auto *learner = static_cast<Learner *>(handle);
|
||||
@@ -1113,14 +1112,17 @@ XGB_DLL int XGBoosterFeatureScore(BoosterHandle handle,
|
||||
feature_map_uri = get<String const>(config["feature_map"]);
|
||||
}
|
||||
FeatureMap feature_map = LoadFeatureMap(feature_map_uri);
|
||||
std::vector<Json> custom_feature_names;
|
||||
if (!IsA<Null>(config["feature_names"])) {
|
||||
custom_feature_names = get<Array const>(config["feature_names"]);
|
||||
}
|
||||
|
||||
auto& scores = learner->GetThreadLocal().ret_vec_float;
|
||||
std::vector<bst_feature_t> features;
|
||||
learner->CalcFeatureScore(importance, &features, &scores);
|
||||
|
||||
auto n_features = learner->GetNumFeature();
|
||||
GenerateFeatureMap(learner, n_features, &feature_map);
|
||||
CHECK_LE(features.size(), n_features);
|
||||
GenerateFeatureMap(learner, custom_feature_names, n_features, &feature_map);
|
||||
|
||||
auto& feature_names = learner->GetThreadLocal().ret_vec_str;
|
||||
feature_names.resize(features.size());
|
||||
@@ -1131,10 +1133,24 @@ XGB_DLL int XGBoosterFeatureScore(BoosterHandle handle,
|
||||
feature_names[i] = feature_map.Name(features[i]);
|
||||
feature_names_c[i] = feature_names[i].data();
|
||||
}
|
||||
*out_n_features = feature_names.size();
|
||||
|
||||
CHECK_EQ(scores.size(), features.size());
|
||||
CHECK_EQ(scores.size(), feature_names.size());
|
||||
*out_length = scores.size();
|
||||
CHECK_LE(features.size(), scores.size());
|
||||
auto &shape = learner->GetThreadLocal().prediction_shape;
|
||||
if (scores.size() > features.size()) {
|
||||
// Linear model multi-class model
|
||||
CHECK_EQ(scores.size() % features.size(), 0ul);
|
||||
auto n_classes = scores.size() / features.size();
|
||||
*out_dim = 2;
|
||||
shape = {n_features, n_classes};
|
||||
} else {
|
||||
CHECK_EQ(features.size(), scores.size());
|
||||
*out_dim = 1;
|
||||
shape.resize(1);
|
||||
shape.front() = scores.size();
|
||||
}
|
||||
|
||||
*out_shape = dmlc::BeginPtr(shape);
|
||||
*out_scores = scores.data();
|
||||
*out_features = dmlc::BeginPtr(feature_names_c);
|
||||
API_END();
|
||||
|
||||
@@ -194,8 +194,8 @@ inline FeatureMap LoadFeatureMap(std::string const& uri) {
|
||||
return feat;
|
||||
}
|
||||
|
||||
// FIXME(jiamingy): Use this for model dump.
|
||||
inline void GenerateFeatureMap(Learner const *learner,
|
||||
std::vector<Json> const &custom_feature_names,
|
||||
size_t n_features, FeatureMap *out_feature_map) {
|
||||
auto &feature_map = *out_feature_map;
|
||||
auto maybe = [&](std::vector<std::string> const &values, size_t i,
|
||||
@@ -205,15 +205,31 @@ inline void GenerateFeatureMap(Learner const *learner,
|
||||
if (feature_map.Size() == 0) {
|
||||
// Use the feature names and types from booster.
|
||||
std::vector<std::string> feature_names;
|
||||
learner->GetFeatureNames(&feature_names);
|
||||
// priority:
|
||||
// 1. feature map.
|
||||
// 2. customized feature name.
|
||||
// 3. from booster
|
||||
// 4. default feature name.
|
||||
if (!custom_feature_names.empty()) {
|
||||
CHECK_EQ(custom_feature_names.size(), n_features)
|
||||
<< "Incorrect number of feature names.";
|
||||
feature_names.resize(custom_feature_names.size());
|
||||
std::transform(custom_feature_names.begin(), custom_feature_names.end(),
|
||||
feature_names.begin(),
|
||||
[](Json const &name) { return get<String const>(name); });
|
||||
} else {
|
||||
learner->GetFeatureNames(&feature_names);
|
||||
}
|
||||
if (!feature_names.empty()) {
|
||||
CHECK_EQ(feature_names.size(), n_features) << "Incorrect number of feature names.";
|
||||
}
|
||||
|
||||
std::vector<std::string> feature_types;
|
||||
learner->GetFeatureTypes(&feature_types);
|
||||
if (!feature_types.empty()) {
|
||||
CHECK_EQ(feature_types.size(), n_features) << "Incorrect number of feature types.";
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < n_features; ++i) {
|
||||
feature_map.PushBack(
|
||||
i,
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
#include "xgboost/gbm.h"
|
||||
#include "xgboost/json.h"
|
||||
@@ -19,6 +20,7 @@
|
||||
#include "xgboost/linear_updater.h"
|
||||
#include "xgboost/logging.h"
|
||||
#include "xgboost/learner.h"
|
||||
#include "xgboost/linalg.h"
|
||||
|
||||
#include "gblinear_model.h"
|
||||
#include "../common/timer.h"
|
||||
@@ -219,6 +221,26 @@ class GBLinear : public GradientBooster {
|
||||
return model_.DumpModel(fmap, with_stats, format);
|
||||
}
|
||||
|
||||
void FeatureScore(std::string const &importance_type,
|
||||
std::vector<bst_feature_t> *out_features,
|
||||
std::vector<float> *out_scores) const override {
|
||||
CHECK(!model_.weight.empty()) << "Model is not initialized";
|
||||
CHECK_EQ(importance_type, "weight")
|
||||
<< "gblinear only has `weight` defined for feature importance.";
|
||||
out_features->resize(this->learner_model_param_->num_feature, 0);
|
||||
std::iota(out_features->begin(), out_features->end(), 0);
|
||||
// Don't include the bias term in the feature importance scores
|
||||
// The bias is the last weight
|
||||
out_scores->resize(model_.weight.size() - learner_model_param_->num_output_group, 0);
|
||||
auto n_groups = learner_model_param_->num_output_group;
|
||||
MatrixView<float> scores{out_scores, {learner_model_param_->num_feature, n_groups}};
|
||||
for (size_t i = 0; i < learner_model_param_->num_feature; ++i) {
|
||||
for (bst_group_t g = 0; g < n_groups; ++g) {
|
||||
scores(i, g) = model_[i][g];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UseGPU() const override {
|
||||
if (param_.updater == "gpu_coord_descent") {
|
||||
return true;
|
||||
|
||||
@@ -325,16 +325,19 @@ class GBTree : public GradientBooster {
|
||||
add_score([&](auto const &p_tree, bst_node_t, bst_feature_t split) {
|
||||
gain_map[split] = split_counts[split];
|
||||
});
|
||||
}
|
||||
if (importance_type == "gain" || importance_type == "total_gain") {
|
||||
} else if (importance_type == "gain" || importance_type == "total_gain") {
|
||||
add_score([&](auto const &p_tree, bst_node_t nidx, bst_feature_t split) {
|
||||
gain_map[split] += p_tree->Stat(nidx).loss_chg;
|
||||
});
|
||||
}
|
||||
if (importance_type == "cover" || importance_type == "total_cover") {
|
||||
} else if (importance_type == "cover" || importance_type == "total_cover") {
|
||||
add_score([&](auto const &p_tree, bst_node_t nidx, bst_feature_t split) {
|
||||
gain_map[split] += p_tree->Stat(nidx).sum_hess;
|
||||
});
|
||||
} else {
|
||||
LOG(FATAL)
|
||||
<< "Unknown feature importance type, expected one of: "
|
||||
<< R"({"weight", "total_gain", "total_cover", "gain", "cover"}, got: )"
|
||||
<< importance_type;
|
||||
}
|
||||
if (importance_type == "gain" || importance_type == "cover") {
|
||||
for (size_t i = 0; i < gain_map.size(); ++i) {
|
||||
|
||||
@@ -1197,23 +1197,6 @@ class LearnerImpl : public LearnerIO {
|
||||
std::vector<bst_feature_t> *features,
|
||||
std::vector<float> *scores) override {
|
||||
this->Configure();
|
||||
std::vector<std::string> allowed_importance_type = {
|
||||
"weight", "total_gain", "total_cover", "gain", "cover"
|
||||
};
|
||||
if (std::find(allowed_importance_type.begin(),
|
||||
allowed_importance_type.end(),
|
||||
importance_type) == allowed_importance_type.end()) {
|
||||
std::stringstream ss;
|
||||
ss << "importance_type mismatch, got: " << importance_type
|
||||
<< "`, expected one of ";
|
||||
for (size_t i = 0; i < allowed_importance_type.size(); ++i) {
|
||||
ss << "`" << allowed_importance_type[i] << "`";
|
||||
if (i != allowed_importance_type.size() - 1) {
|
||||
ss << ", ";
|
||||
}
|
||||
}
|
||||
LOG(FATAL) << ss.str();
|
||||
}
|
||||
gbm_->FeatureScore(importance_type, features, scores);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user