Commit ab559101 authored by Guolin Ke's avatar Guolin Ke Committed by GitHub
Browse files

remove additional cost for prediction task. (#404)

* refine prediction logic.

* fix test.

* fix out_len in training score of Dart.

* improve predict speed for high dimension data.

* try use unordered_map for sparse prediction.

* avoid using unordered_map.

* clean code.

* fix test.

* move predict buffer to Predictor.
parent 6be08748
...@@ -117,14 +117,14 @@ public: ...@@ -117,14 +117,14 @@ public:
* \param feature_values Feature value on this record * \param feature_values Feature value on this record
* \param output Prediction result for this record * \param output Prediction result for this record
*/ */
virtual void PredictRaw(const double* feature_values, double* output) const = 0; virtual void PredictRaw(const double* features, double* output) const = 0;
/*! /*!
* \brief Prediction for one record, sigmoid transformation will be used if needed * \brief Prediction for one record, sigmoid transformation will be used if needed
* \param feature_values Feature value on this record * \param feature_values Feature value on this record
* \param output Prediction result for this record * \param output Prediction result for this record
*/ */
virtual void Predict(const double* feature_values, double* output) const = 0; virtual void Predict(const double* features, double* output) const = 0;
/*! /*!
* \brief Prediction for one record with leaf index * \brief Prediction for one record with leaf index
...@@ -132,7 +132,7 @@ public: ...@@ -132,7 +132,7 @@ public:
* \param output Prediction result for this record * \param output Prediction result for this record
*/ */
virtual void PredictLeafIndex( virtual void PredictLeafIndex(
const double* feature_values, double* output) const = 0; const double* features, double* output) const = 0;
/*! /*!
* \brief Dump model to json format string * \brief Dump model to json format string
...@@ -202,9 +202,8 @@ public: ...@@ -202,9 +202,8 @@ public:
/*! /*!
* \brief Initial work for the prediction * \brief Initial work for the prediction
* \param num_iteration number of used iteration * \param num_iteration number of used iteration
* \return the feature indices mapper
*/ */
virtual std::vector<int> InitPredict(int num_iteration) = 0; virtual void InitPredict(int num_iteration) = 0;
/*! /*!
* \brief Name of submodel * \brief Name of submodel
......
...@@ -111,13 +111,6 @@ public: ...@@ -111,13 +111,6 @@ public:
shrinkage_ *= rate; shrinkage_ *= rate;
} }
inline void ReMapFeature(const std::vector<int>& feature_mapper) {
mapped_feature_ = split_feature_;
for (int i = 0; i < num_leaves_ - 1; ++i) {
mapped_feature_[i] = feature_mapper[split_feature_[i]];
}
}
/*! \brief Serialize this object to string*/ /*! \brief Serialize this object to string*/
std::string ToString(); std::string ToString();
...@@ -201,8 +194,6 @@ private: ...@@ -201,8 +194,6 @@ private:
std::vector<int> leaf_depth_; std::vector<int> leaf_depth_;
double shrinkage_; double shrinkage_;
bool has_categorical_; bool has_categorical_;
/*! \brief buffer of mapped split_feature_ */
std::vector<int> mapped_feature_;
}; };
inline double Tree::Predict(const double* feature_values) const { inline double Tree::Predict(const double* feature_values) const {
...@@ -228,7 +219,7 @@ inline int Tree::GetLeaf(const double* feature_values) const { ...@@ -228,7 +219,7 @@ inline int Tree::GetLeaf(const double* feature_values) const {
if (has_categorical_) { if (has_categorical_) {
while (node >= 0) { while (node >= 0) {
if (decision_funs[decision_type_[node]]( if (decision_funs[decision_type_[node]](
feature_values[mapped_feature_[node]], feature_values[split_feature_[node]],
threshold_[node])) { threshold_[node])) {
node = left_child_[node]; node = left_child_[node];
} else { } else {
...@@ -238,7 +229,7 @@ inline int Tree::GetLeaf(const double* feature_values) const { ...@@ -238,7 +229,7 @@ inline int Tree::GetLeaf(const double* feature_values) const {
} else { } else {
while (node >= 0) { while (node >= 0) {
if (NumericalDecision<double>( if (NumericalDecision<double>(
feature_values[mapped_feature_[node]], feature_values[split_feature_[node]],
threshold_[node])) { threshold_[node])) {
node = left_child_[node]; node = left_child_[node];
} else { } else {
......
...@@ -33,39 +33,36 @@ public: ...@@ -33,39 +33,36 @@ public:
Predictor(Boosting* boosting, int num_iteration, Predictor(Boosting* boosting, int num_iteration,
bool is_raw_score, bool is_predict_leaf_index) { bool is_raw_score, bool is_predict_leaf_index) {
feature_mapper_ = boosting->InitPredict(num_iteration); boosting->InitPredict(num_iteration);
boosting_ = boosting; boosting_ = boosting;
num_pred_one_row_ = boosting_->NumPredictOneRow(num_iteration, is_predict_leaf_index); num_pred_one_row_ = boosting_->NumPredictOneRow(num_iteration, is_predict_leaf_index);
predict_buf_ = std::vector<double>(boosting_->MaxFeatureIdx() + 1, 0.0f);
num_total_features_ = static_cast<int>(feature_mapper_.size());
num_used_features_ = 1;
for (auto fidx : feature_mapper_) {
num_used_features_ = std::max(num_used_features_, fidx + 1);
}
features_ = std::vector<double>(num_used_features_);
if (is_predict_leaf_index) { if (is_predict_leaf_index) {
predict_fun_ = [this](const std::vector<std::pair<int, double>>& features, double* output) { predict_fun_ = [this](const std::vector<std::pair<int, double>>& features, double* output) {
PutFeatureValuesToBuffer(features); CopyToPredictBuffer(features);
// get result for leaf index // get result for leaf index
boosting_->PredictLeafIndex(features_.data(), output); boosting_->PredictLeafIndex(predict_buf_.data(), output);
ClearPredictBuffer(features);
}; };
} else { } else {
if (is_raw_score) { if (is_raw_score) {
predict_fun_ = [this](const std::vector<std::pair<int, double>>& features, double* output) { predict_fun_ = [this](const std::vector<std::pair<int, double>>& features, double* output) {
PutFeatureValuesToBuffer(features); CopyToPredictBuffer(features);
// get result without sigmoid transformation boosting_->PredictRaw(predict_buf_.data(), output);
boosting_->PredictRaw(features_.data(), output); ClearPredictBuffer(features);
}; };
} else { } else {
predict_fun_ = [this](const std::vector<std::pair<int, double>>& features, double* output) { predict_fun_ = [this](const std::vector<std::pair<int, double>>& features, double* output) {
PutFeatureValuesToBuffer(features); CopyToPredictBuffer(features);
boosting_->Predict(features_.data(), output); boosting_->Predict(predict_buf_.data(), output);
ClearPredictBuffer(features);
}; };
} }
} }
} }
/*! /*!
* \brief Destructor * \brief Destructor
*/ */
...@@ -93,7 +90,7 @@ public: ...@@ -93,7 +90,7 @@ public:
if (result_file == NULL) { if (result_file == NULL) {
Log::Fatal("Prediction results file %s doesn't exist", data_filename); Log::Fatal("Prediction results file %s doesn't exist", data_filename);
} }
auto parser = std::unique_ptr<Parser>(Parser::CreateParser(data_filename, has_header, num_used_features_, boosting_->LabelIdx())); auto parser = std::unique_ptr<Parser>(Parser::CreateParser(data_filename, has_header, boosting_->MaxFeatureIdx() + 1, boosting_->LabelIdx()));
if (parser == nullptr) { if (parser == nullptr) {
Log::Fatal("Could not recognize the data format of data file %s", data_filename); Log::Fatal("Could not recognize the data format of data file %s", data_filename);
...@@ -128,30 +125,33 @@ public: ...@@ -128,30 +125,33 @@ public:
} }
private: private:
void PutFeatureValuesToBuffer(const std::vector<std::pair<int, double>>& features) {
std::memset(features_.data(), 0, sizeof(double)*num_used_features_); void CopyToPredictBuffer(const std::vector<std::pair<int, double>>& features) {
// put feature value
int loop_size = static_cast<int>(features.size()); int loop_size = static_cast<int>(features.size());
#pragma omp parallel for schedule(static, 512) if(loop_size >= 1024) #pragma omp parallel for schedule(static,128) if (loop_size >= 256)
for (int i = 0; i < loop_size; ++i) { for (int i = 0; i < loop_size; ++i) {
if (features[i].first >= num_total_features_) continue; predict_buf_[features[i].first] = features[i].second;
auto fidx = feature_mapper_[features[i].first]; }
if (fidx >= 0) { }
features_[fidx] = features[i].second;
void ClearPredictBuffer(const std::vector<std::pair<int, double>>& features) {
if (features.size() < static_cast<size_t>(predict_buf_.size() / 2)) {
std::memset(predict_buf_.data(), 0, sizeof(double)*(predict_buf_.size()));
} else {
int loop_size = static_cast<int>(features.size());
#pragma omp parallel for schedule(static,128) if (loop_size >= 256)
for (int i = 0; i < loop_size; ++i) {
predict_buf_[features[i].first] = 0.0f;
} }
} }
} }
/*! \brief Boosting model */ /*! \brief Boosting model */
const Boosting* boosting_; const Boosting* boosting_;
/*! \brief Buffer for feature values */
std::vector<double> features_;
/*! \brief Number of features */
int num_used_features_;
/*! \brief function for prediction */ /*! \brief function for prediction */
PredictFunction predict_fun_; PredictFunction predict_fun_;
int num_pred_one_row_; int num_pred_one_row_;
std::vector<int> feature_mapper_; std::vector<double> predict_buf_;
int num_total_features_;
}; };
} // namespace LightGBM } // namespace LightGBM
......
...@@ -870,14 +870,12 @@ std::vector<std::pair<size_t, std::string>> GBDT::FeatureImportance() const { ...@@ -870,14 +870,12 @@ std::vector<std::pair<size_t, std::string>> GBDT::FeatureImportance() const {
return pairs; return pairs;
} }
void GBDT::PredictRaw(const double* features, double* output) const {
void GBDT::PredictRaw(const double* value, double* output) const {
if (num_threads_ <= num_tree_per_iteration_) { if (num_threads_ <= num_tree_per_iteration_) {
#pragma omp parallel for schedule(static) #pragma omp parallel for schedule(static)
for (int k = 0; k < num_tree_per_iteration_; ++k) { for (int k = 0; k < num_tree_per_iteration_; ++k) {
for (int i = 0; i < num_iteration_for_pred_; ++i) { for (int i = 0; i < num_iteration_for_pred_; ++i) {
output[k] += models_[i * num_tree_per_iteration_ + k]->Predict(value); output[k] += models_[i * num_tree_per_iteration_ + k]->Predict(features);
} }
} }
} else { } else {
...@@ -885,19 +883,19 @@ void GBDT::PredictRaw(const double* value, double* output) const { ...@@ -885,19 +883,19 @@ void GBDT::PredictRaw(const double* value, double* output) const {
double t = 0.0f; double t = 0.0f;
#pragma omp parallel for schedule(static) reduction(+:t) #pragma omp parallel for schedule(static) reduction(+:t)
for (int i = 0; i < num_iteration_for_pred_; ++i) { for (int i = 0; i < num_iteration_for_pred_; ++i) {
t += models_[i * num_tree_per_iteration_ + k]->Predict(value); t += models_[i * num_tree_per_iteration_ + k]->Predict(features);
} }
output[k] = t; output[k] = t;
} }
} }
} }
void GBDT::Predict(const double* value, double* output) const { void GBDT::Predict(const double* features, double* output) const {
if (num_threads_ <= num_tree_per_iteration_) { if (num_threads_ <= num_tree_per_iteration_) {
#pragma omp parallel for schedule(static) #pragma omp parallel for schedule(static)
for (int k = 0; k < num_tree_per_iteration_; ++k) { for (int k = 0; k < num_tree_per_iteration_; ++k) {
for (int i = 0; i < num_iteration_for_pred_; ++i) { for (int i = 0; i < num_iteration_for_pred_; ++i) {
output[k] += models_[i * num_tree_per_iteration_ + k]->Predict(value); output[k] += models_[i * num_tree_per_iteration_ + k]->Predict(features);
} }
} }
} else { } else {
...@@ -905,7 +903,7 @@ void GBDT::Predict(const double* value, double* output) const { ...@@ -905,7 +903,7 @@ void GBDT::Predict(const double* value, double* output) const {
double t = 0.0f; double t = 0.0f;
#pragma omp parallel for schedule(static) reduction(+:t) #pragma omp parallel for schedule(static) reduction(+:t)
for (int i = 0; i < num_iteration_for_pred_; ++i) { for (int i = 0; i < num_iteration_for_pred_; ++i) {
t += models_[i * num_tree_per_iteration_ + k]->Predict(value); t += models_[i * num_tree_per_iteration_ + k]->Predict(features);
} }
output[k] = t; output[k] = t;
} }
...@@ -915,11 +913,11 @@ void GBDT::Predict(const double* value, double* output) const { ...@@ -915,11 +913,11 @@ void GBDT::Predict(const double* value, double* output) const {
} }
} }
void GBDT::PredictLeafIndex(const double* value, double* output) const { void GBDT::PredictLeafIndex(const double* features, double* output) const {
int total_tree = num_iteration_for_pred_ * num_tree_per_iteration_; int total_tree = num_iteration_for_pred_ * num_tree_per_iteration_;
#pragma omp parallel for schedule(static) #pragma omp parallel for schedule(static)
for (int i = 0; i < total_tree; ++i) { for (int i = 0; i < total_tree; ++i) {
output[i] = models_[i]->PredictLeafIndex(value); output[i] = models_[i]->PredictLeafIndex(features);
} }
} }
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include <string> #include <string>
#include <fstream> #include <fstream>
#include <memory> #include <memory>
#include <mutex>
namespace LightGBM { namespace LightGBM {
/*! /*!
...@@ -135,11 +136,11 @@ public: ...@@ -135,11 +136,11 @@ public:
return num_preb_in_one_row; return num_preb_in_one_row;
} }
void PredictRaw(const double* feature_values, double* output) const override; void PredictRaw(const double* features, double* output) const override;
void Predict(const double* feature_values, double* output) const override; void Predict(const double* features, double* output) const override;
void PredictLeafIndex(const double* value, double* output) const override; void PredictLeafIndex(const double* features, double* output) const override;
/*! /*!
* \brief Dump model to json format string * \brief Dump model to json format string
...@@ -203,39 +204,11 @@ public: ...@@ -203,39 +204,11 @@ public:
*/ */
inline int NumberOfClasses() const override { return num_class_; } inline int NumberOfClasses() const override { return num_class_; }
inline std::vector<int> InitPredict(int num_iteration) override { inline void InitPredict(int num_iteration) override {
num_iteration_for_pred_ = static_cast<int>(models_.size()) / num_tree_per_iteration_; num_iteration_for_pred_ = static_cast<int>(models_.size()) / num_tree_per_iteration_;
if (num_iteration > 0) { if (num_iteration > 0) {
num_iteration_for_pred_ = std::min(num_iteration + (boost_from_average_ ? 1 : 0), num_iteration_for_pred_); num_iteration_for_pred_ = std::min(num_iteration + (boost_from_average_ ? 1 : 0), num_iteration_for_pred_);
} }
int used_fidx = 0;
// Construct used feature mapper
std::vector<int> feature_mapper(max_feature_idx_ + 1, -1);
int total_tree = num_iteration_for_pred_ * num_tree_per_iteration_;
#pragma omp parallel for schedule(static, 64) if (total_tree >= 128)
for (int i = 0; i < total_tree; ++i) {
int num_leaves = models_[i]->num_leaves();
for (int j = 0; j < num_leaves - 1; ++j) {
int fidx = models_[i]->split_feature(j);
if (feature_mapper[fidx] == -1) {
#pragma omp critical
{
if (feature_mapper[fidx] == -1) {
feature_mapper[fidx] = used_fidx;
++used_fidx;
}
}
}
}
}
#pragma omp parallel for schedule(static, 64) if (total_tree >= 128)
for (int i = 0; i < total_tree; ++i) {
models_[i]->ReMapFeature(feature_mapper);
}
return feature_mapper;
} }
inline double GetLeafValue(int tree_idx, int leaf_idx) const { inline double GetLeafValue(int tree_idx, int leaf_idx) const {
...@@ -297,6 +270,7 @@ protected: ...@@ -297,6 +270,7 @@ protected:
* \brief Calculate feature importances * \brief Calculate feature importances
*/ */
std::vector<std::pair<size_t, std::string>> FeatureImportance() const; std::vector<std::pair<size_t, std::string>> FeatureImportance() const;
/*! \brief current iteration */ /*! \brief current iteration */
int iter_; int iter_;
/*! \brief Pointer to training data */ /*! \brief Pointer to training data */
...@@ -373,6 +347,7 @@ protected: ...@@ -373,6 +347,7 @@ protected:
std::vector<double> class_default_output_; std::vector<double> class_default_output_;
bool is_constant_hessian_; bool is_constant_hessian_;
std::unique_ptr<ObjectiveFunction> loaded_objective_; std::unique_ptr<ObjectiveFunction> loaded_objective_;
}; };
} // namespace LightGBM } // namespace LightGBM
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment