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:
* \param feature_values Feature value on 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
* \param feature_values Feature value on 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
......@@ -132,7 +132,7 @@ public:
* \param output Prediction result for this record
*/
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
......@@ -202,9 +202,8 @@ public:
/*!
* \brief Initial work for the prediction
* \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
......
......@@ -111,13 +111,6 @@ public:
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*/
std::string ToString();
......@@ -201,8 +194,6 @@ private:
std::vector<int> leaf_depth_;
double shrinkage_;
bool has_categorical_;
/*! \brief buffer of mapped split_feature_ */
std::vector<int> mapped_feature_;
};
inline double Tree::Predict(const double* feature_values) const {
......@@ -228,7 +219,7 @@ inline int Tree::GetLeaf(const double* feature_values) const {
if (has_categorical_) {
while (node >= 0) {
if (decision_funs[decision_type_[node]](
feature_values[mapped_feature_[node]],
feature_values[split_feature_[node]],
threshold_[node])) {
node = left_child_[node];
} else {
......@@ -238,7 +229,7 @@ inline int Tree::GetLeaf(const double* feature_values) const {
} else {
while (node >= 0) {
if (NumericalDecision<double>(
feature_values[mapped_feature_[node]],
feature_values[split_feature_[node]],
threshold_[node])) {
node = left_child_[node];
} else {
......
......@@ -33,39 +33,36 @@ public:
Predictor(Boosting* boosting, int num_iteration,
bool is_raw_score, bool is_predict_leaf_index) {
feature_mapper_ = boosting->InitPredict(num_iteration);
boosting->InitPredict(num_iteration);
boosting_ = boosting;
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) {
predict_fun_ = [this](const std::vector<std::pair<int, double>>& features, double* output) {
PutFeatureValuesToBuffer(features);
CopyToPredictBuffer(features);
// get result for leaf index
boosting_->PredictLeafIndex(features_.data(), output);
boosting_->PredictLeafIndex(predict_buf_.data(), output);
ClearPredictBuffer(features);
};
} else {
if (is_raw_score) {
predict_fun_ = [this](const std::vector<std::pair<int, double>>& features, double* output) {
PutFeatureValuesToBuffer(features);
// get result without sigmoid transformation
boosting_->PredictRaw(features_.data(), output);
CopyToPredictBuffer(features);
boosting_->PredictRaw(predict_buf_.data(), output);
ClearPredictBuffer(features);
};
} else {
predict_fun_ = [this](const std::vector<std::pair<int, double>>& features, double* output) {
PutFeatureValuesToBuffer(features);
boosting_->Predict(features_.data(), output);
CopyToPredictBuffer(features);
boosting_->Predict(predict_buf_.data(), output);
ClearPredictBuffer(features);
};
}
}
}
/*!
* \brief Destructor
*/
......@@ -93,7 +90,7 @@ public:
if (result_file == NULL) {
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) {
Log::Fatal("Could not recognize the data format of data file %s", data_filename);
......@@ -128,30 +125,33 @@ public:
}
private:
void PutFeatureValuesToBuffer(const std::vector<std::pair<int, double>>& features) {
std::memset(features_.data(), 0, sizeof(double)*num_used_features_);
// put feature value
void CopyToPredictBuffer(const std::vector<std::pair<int, double>>& features) {
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) {
if (features[i].first >= num_total_features_) continue;
auto fidx = feature_mapper_[features[i].first];
if (fidx >= 0) {
features_[fidx] = features[i].second;
predict_buf_[features[i].first] = 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 */
const Boosting* boosting_;
/*! \brief Buffer for feature values */
std::vector<double> features_;
/*! \brief Number of features */
int num_used_features_;
/*! \brief function for prediction */
PredictFunction predict_fun_;
int num_pred_one_row_;
std::vector<int> feature_mapper_;
int num_total_features_;
std::vector<double> predict_buf_;
};
} // namespace LightGBM
......
......@@ -870,14 +870,12 @@ std::vector<std::pair<size_t, std::string>> GBDT::FeatureImportance() const {
return pairs;
}
void GBDT::PredictRaw(const double* value, double* output) const {
void GBDT::PredictRaw(const double* features, double* output) const {
if (num_threads_ <= num_tree_per_iteration_) {
#pragma omp parallel for schedule(static)
for (int k = 0; k < num_tree_per_iteration_; ++k) {
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 {
......@@ -885,19 +883,19 @@ void GBDT::PredictRaw(const double* value, double* output) const {
double t = 0.0f;
#pragma omp parallel for schedule(static) reduction(+:t)
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;
}
}
}
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_) {
#pragma omp parallel for schedule(static)
for (int k = 0; k < num_tree_per_iteration_; ++k) {
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 {
......@@ -905,7 +903,7 @@ void GBDT::Predict(const double* value, double* output) const {
double t = 0.0f;
#pragma omp parallel for schedule(static) reduction(+:t)
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;
}
......@@ -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_;
#pragma omp parallel for schedule(static)
for (int i = 0; i < total_tree; ++i) {
output[i] = models_[i]->PredictLeafIndex(value);
output[i] = models_[i]->PredictLeafIndex(features);
}
}
......
......@@ -9,6 +9,7 @@
#include <string>
#include <fstream>
#include <memory>
#include <mutex>
namespace LightGBM {
/*!
......@@ -135,11 +136,11 @@ public:
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
......@@ -203,39 +204,11 @@ public:
*/
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_;
if (num_iteration > 0) {
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 {
......@@ -297,6 +270,7 @@ protected:
* \brief Calculate feature importances
*/
std::vector<std::pair<size_t, std::string>> FeatureImportance() const;
/*! \brief current iteration */
int iter_;
/*! \brief Pointer to training data */
......@@ -373,6 +347,7 @@ protected:
std::vector<double> class_default_output_;
bool is_constant_hessian_;
std::unique_ptr<ObjectiveFunction> loaded_objective_;
};
} // 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