gbdt.cpp 33.3 KB
Newer Older
1
2
3
4
/*!
 * Copyright (c) 2016 Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See LICENSE file in the project root for license information.
 */
Guolin Ke's avatar
Guolin Ke committed
5
6
7
#include "gbdt.h"

#include <LightGBM/metric.h>
Guolin Ke's avatar
Guolin Ke committed
8
#include <LightGBM/network.h>
9
10
11
12
#include <LightGBM/objective_function.h>
#include <LightGBM/prediction_early_stop.h>
#include <LightGBM/utils/common.h>
#include <LightGBM/utils/openmp_wrapper.h>
13
#include <LightGBM/sample_strategy.h>
Guolin Ke's avatar
Guolin Ke committed
14

15
16
17
18
#include <chrono>
#include <ctime>
#include <sstream>

Guolin Ke's avatar
Guolin Ke committed
19
20
namespace LightGBM {

21
22
Common::Timer global_timer;

23
24
25
int LGBM_config_::current_device = lgbm_device_cpu;
int LGBM_config_::current_learner = use_cpu_learner;

26
27
28
GBDT::GBDT()
    : iter_(0),
      train_data_(nullptr),
29
      config_(nullptr),
30
31
32
33
34
35
36
37
      objective_function_(nullptr),
      early_stopping_round_(0),
      es_first_metric_only_(false),
      max_feature_idx_(0),
      num_tree_per_iteration_(1),
      num_class_(1),
      num_iteration_for_pred_(0),
      shrinkage_rate_(0.1f),
38
      num_init_iteration_(0) {
Guolin Ke's avatar
Guolin Ke committed
39
  average_output_ = false;
Guolin Ke's avatar
Guolin Ke committed
40
  tree_learner_ = nullptr;
41
  linear_tree_ = false;
42
  data_sample_strategy_.reset(nullptr);
shiyu1994's avatar
shiyu1994 committed
43
44
45
  gradients_pointer_ = nullptr;
  hessians_pointer_ = nullptr;
  boosting_on_gpu_ = false;
Guolin Ke's avatar
Guolin Ke committed
46
47
48
49
50
}

GBDT::~GBDT() {
}

Guolin Ke's avatar
Guolin Ke committed
51
void GBDT::Init(const Config* config, const Dataset* train_data, const ObjectiveFunction* objective_function,
52
                const std::vector<const Metric*>& training_metrics) {
Nikita Titov's avatar
Nikita Titov committed
53
  CHECK_NOTNULL(train_data);
54
  train_data_ = train_data;
55
  if (!config->monotone_constraints.empty()) {
Nikita Titov's avatar
Nikita Titov committed
56
    CHECK_EQ(static_cast<size_t>(train_data_->num_total_features()), config->monotone_constraints.size());
Nikita Titov's avatar
Nikita Titov committed
57
  }
58
  if (!config->feature_contri.empty()) {
Nikita Titov's avatar
Nikita Titov committed
59
    CHECK_EQ(static_cast<size_t>(train_data_->num_total_features()), config->feature_contri.size());
60
  }
61
  iter_ = 0;
wxchan's avatar
wxchan committed
62
  num_iteration_for_pred_ = 0;
63
  max_feature_idx_ = 0;
wxchan's avatar
wxchan committed
64
  num_class_ = config->num_class;
Guolin Ke's avatar
Guolin Ke committed
65
66
  config_ = std::unique_ptr<Config>(new Config(*config));
  early_stopping_round_ = config_->early_stopping_round;
67
  es_first_metric_only_ = config_->first_metric_only;
Guolin Ke's avatar
Guolin Ke committed
68
  shrinkage_rate_ = config_->learning_rate;
69

70
  if (config_->device_type == std::string("cuda") || config_->device_type == std::string("cuda_exp")) {
71
    LGBM_config_::current_learner = use_cuda_learner;
72
73
74
75
76
77
    #ifdef USE_CUDA_EXP
    if (config_->device_type == std::string("cuda_exp")) {
      const int gpu_device_id = config_->gpu_device_id >= 0 ? config_->gpu_device_id : 0;
      CUDASUCCESS_OR_FATAL(cudaSetDevice(gpu_device_id));
    }
    #endif  // USE_CUDA_EXP
78
79
  }

80
  // load forced_splits file
81
82
83
84
85
  if (!config->forcedsplits_filename.empty()) {
    std::ifstream forced_splits_file(config->forcedsplits_filename.c_str());
    std::stringstream buffer;
    buffer << forced_splits_file.rdbuf();
    std::string err;
Guolin Ke's avatar
Guolin Ke committed
86
    forced_splits_json_ = Json::parse(buffer.str(), &err);
87
88
  }

89
90
91
  objective_function_ = objective_function;
  num_tree_per_iteration_ = num_class_;
  if (objective_function_ != nullptr) {
Guolin Ke's avatar
Guolin Ke committed
92
    num_tree_per_iteration_ = objective_function_->NumModelPerIteration();
93
94
95
    if (objective_function_->IsRenewTreeOutput() && !config->monotone_constraints.empty()) {
      Log::Fatal("Cannot use ``monotone_constraints`` in %s objective, please disable it.", objective_function_->GetName());
    }
96
97
  }

98
  data_sample_strategy_.reset(SampleStrategy::CreateSampleStrategy(config_.get(), train_data_, objective_function_, num_tree_per_iteration_));
99
100
  is_constant_hessian_ = GetIsConstHessian(objective_function);

101
102
103
  boosting_on_gpu_ = objective_function_ != nullptr && objective_function_->IsCUDAObjective() &&
                     !data_sample_strategy_->IsHessianChange();  // for sample strategy with Hessian change, fall back to boosting on CPU

104
  tree_learner_ = std::unique_ptr<TreeLearner>(TreeLearner::CreateTreeLearner(config_->tree_learner, config_->device_type,
shiyu1994's avatar
shiyu1994 committed
105
                                                                              config_.get(), boosting_on_gpu_));
106
107
108

  // init tree learner
  tree_learner_->Init(train_data_, is_constant_hessian_);
109
  tree_learner_->SetForcedSplit(&forced_splits_json_);
110
111
112
113
114
115
116
117

  // push training metrics
  training_metrics_.clear();
  for (const auto& metric : training_metrics) {
    training_metrics_.push_back(metric);
  }
  training_metrics_.shrink_to_fit();

118
119
  #ifdef USE_CUDA_EXP
  if (config_->device_type == std::string("cuda_exp")) {
shiyu1994's avatar
shiyu1994 committed
120
    train_score_updater_.reset(new CUDAScoreUpdater(train_data_, num_tree_per_iteration_, boosting_on_gpu_));
121
122
123
124
125
126
  } else {
  #endif  // USE_CUDA_EXP
    train_score_updater_.reset(new ScoreUpdater(train_data_, num_tree_per_iteration_));
  #ifdef USE_CUDA_EXP
  }
  #endif  // USE_CUDA_EXP
127
128

  num_data_ = train_data_->num_data();
shiyu1994's avatar
shiyu1994 committed
129

130
131
132
133
134
135
136
  // get max feature index
  max_feature_idx_ = train_data_->num_total_features() - 1;
  // get label index
  label_idx_ = train_data_->label_idx();
  // get feature names
  feature_names_ = train_data_->feature_names();
  feature_infos_ = train_data_->feature_infos();
137
  monotone_constraints_ = config->monotone_constraints;
138
139
  // get parser config file content
  parser_config_str_ = train_data_->parser_config_str();
140
141

  // if need bagging, create buffer
142
143
  data_sample_strategy_->ResetSampleConfig(config_.get(), true);
  ResetGradientBuffers();
144
145
146

  class_need_train_ = std::vector<bool>(num_tree_per_iteration_, true);
  if (objective_function_ != nullptr && objective_function_->SkipEmptyClass()) {
Nikita Titov's avatar
Nikita Titov committed
147
    CHECK_EQ(num_tree_per_iteration_, num_class_);
148
149
    for (int i = 0; i < num_class_; ++i) {
      class_need_train_[i] = objective_function_->ClassNeedTrain(i);
150
151
    }
  }
152
153
154
155

  if (config_->linear_tree) {
    linear_tree_ = true;
  }
wxchan's avatar
wxchan committed
156
157
158
}

void GBDT::AddValidDataset(const Dataset* valid_data,
159
                           const std::vector<const Metric*>& valid_metrics) {
wxchan's avatar
wxchan committed
160
  if (!train_data_->CheckAlign(*valid_data)) {
161
    Log::Fatal("Cannot add validation data, since it has different bin mappers with training data");
162
  }
Guolin Ke's avatar
Guolin Ke committed
163
  // for a validation dataset, we need its score and metric
164
165
166
167
168
169
170
  auto new_score_updater =
    #ifdef USE_CUDA_EXP
    config_->device_type == std::string("cuda_exp") ?
    std::unique_ptr<CUDAScoreUpdater>(new CUDAScoreUpdater(valid_data, num_tree_per_iteration_,
      objective_function_ != nullptr && objective_function_->IsCUDAObjective())) :
    #endif  // USE_CUDA_EXP
    std::unique_ptr<ScoreUpdater>(new ScoreUpdater(valid_data, num_tree_per_iteration_));
wxchan's avatar
wxchan committed
171
172
  // update score
  for (int i = 0; i < iter_; ++i) {
173
174
175
    for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
      auto curr_tree = (i + num_init_iteration_) * num_tree_per_iteration_ + cur_tree_id;
      new_score_updater->AddScore(models_[curr_tree].get(), cur_tree_id);
wxchan's avatar
wxchan committed
176
177
    }
  }
Guolin Ke's avatar
Guolin Ke committed
178
  valid_score_updater_.push_back(std::move(new_score_updater));
Guolin Ke's avatar
Guolin Ke committed
179
180
181
182
  valid_metrics_.emplace_back();
  for (const auto& metric : valid_metrics) {
    valid_metrics_.back().push_back(metric);
  }
Guolin Ke's avatar
Guolin Ke committed
183
  valid_metrics_.back().shrink_to_fit();
184

185
186
187
188
189
190
191
  if (early_stopping_round_ > 0) {
    auto num_metrics = valid_metrics.size();
    if (es_first_metric_only_) { num_metrics = 1; }
    best_iter_.emplace_back(num_metrics, 0);
    best_score_.emplace_back(num_metrics, kMinScore);
    best_msg_.emplace_back(num_metrics);
  }
Guolin Ke's avatar
Guolin Ke committed
192
193
}

Guolin Ke's avatar
Guolin Ke committed
194
void GBDT::Boosting() {
195
  Common::FunctionTimer fun_timer("GBDT::Boosting", global_timer);
Guolin Ke's avatar
Guolin Ke committed
196
  if (objective_function_ == nullptr) {
197
    Log::Fatal("No objective function provided");
Guolin Ke's avatar
Guolin Ke committed
198
199
200
201
  }
  // objective function will calculate gradients and hessians
  int64_t num_score = 0;
  objective_function_->
202
    GetGradients(GetTrainingScore(&num_score), gradients_pointer_, hessians_pointer_);
Guolin Ke's avatar
Guolin Ke committed
203
204
}

Guolin Ke's avatar
Guolin Ke committed
205
void GBDT::Train(int snapshot_freq, const std::string& model_output_path) {
206
  Common::FunctionTimer fun_timer("GBDT::Train", global_timer);
Guolin Ke's avatar
Guolin Ke committed
207
208
  bool is_finished = false;
  auto start_time = std::chrono::steady_clock::now();
Guolin Ke's avatar
Guolin Ke committed
209
  for (int iter = 0; iter < config_->num_iterations && !is_finished; ++iter) {
Guolin Ke's avatar
Guolin Ke committed
210
211
212
213
    is_finished = TrainOneIter(nullptr, nullptr);
    if (!is_finished) {
      is_finished = EvalAndCheckEarlyStopping();
    }
Guolin Ke's avatar
Guolin Ke committed
214
215
216
217
218
219
220
    auto end_time = std::chrono::steady_clock::now();
    // output used time per iteration
    Log::Info("%f seconds elapsed, finished iteration %d", std::chrono::duration<double,
              std::milli>(end_time - start_time) * 1e-3, iter + 1);
    if (snapshot_freq > 0
        && (iter + 1) % snapshot_freq == 0) {
      std::string snapshot_out = model_output_path + ".snapshot_iter_" + std::to_string(iter + 1);
221
      SaveModelToFile(0, -1, config_->saved_feature_importance_type, snapshot_out.c_str());
Guolin Ke's avatar
Guolin Ke committed
222
223
224
225
    }
  }
}

226
void GBDT::RefitTree(const std::vector<std::vector<int>>& tree_leaf_prediction) {
227
228
229
  CHECK_GT(tree_leaf_prediction.size(), 0);
  CHECK_EQ(static_cast<size_t>(num_data_), tree_leaf_prediction.size());
  CHECK_EQ(static_cast<size_t>(models_.size()), tree_leaf_prediction[0].size());
230
231
  int num_iterations = static_cast<int>(models_.size() / num_tree_per_iteration_);
  std::vector<int> leaf_pred(num_data_);
232
233
234
235
236
237
238
239
240
241
242
243
244
  if (linear_tree_) {
    std::vector<int> max_leaves_by_thread = std::vector<int>(OMP_NUM_THREADS(), 0);
    #pragma omp parallel for schedule(static)
    for (int i = 0; i < static_cast<int>(tree_leaf_prediction.size()); ++i) {
      int tid = omp_get_thread_num();
      for (size_t j = 0; j < tree_leaf_prediction[i].size(); ++j) {
        max_leaves_by_thread[tid] = std::max(max_leaves_by_thread[tid], tree_leaf_prediction[i][j]);
      }
    }
    int max_leaves = *std::max_element(max_leaves_by_thread.begin(), max_leaves_by_thread.end());
    max_leaves += 1;
    tree_learner_->InitLinear(train_data_, max_leaves);
  }
245
246
247
248
249
250
251
  for (int iter = 0; iter < num_iterations; ++iter) {
    Boosting();
    for (int tree_id = 0; tree_id < num_tree_per_iteration_; ++tree_id) {
      int model_index = iter * num_tree_per_iteration_ + tree_id;
      #pragma omp parallel for schedule(static)
      for (int i = 0; i < num_data_; ++i) {
        leaf_pred[i] = tree_leaf_prediction[i][model_index];
Nikita Titov's avatar
Nikita Titov committed
252
        CHECK_LT(leaf_pred[i], models_[model_index]->num_leaves());
253
      }
254
      size_t offset = static_cast<size_t>(tree_id) * num_data_;
255
256
      auto grad = gradients_pointer_ + offset;
      auto hess = hessians_pointer_ + offset;
257
258
259
260
261
262
263
      auto new_tree = tree_learner_->FitByExistingTree(models_[model_index].get(), leaf_pred, grad, hess);
      train_score_updater_->AddScore(tree_learner_.get(), new_tree, tree_id);
      models_[model_index].reset(new_tree);
    }
  }
}

Andrew Ziem's avatar
Andrew Ziem committed
264
/* If the custom "average" is implemented it will be used in place of the label average (if enabled)
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
*
* An improvement to this is to have options to explicitly choose
* (i) standard average
* (ii) custom average if available
* (iii) any user defined scalar bias (e.g. using a new option "init_score" that overrides (i) and (ii) )
*
* (i) and (ii) could be selected as say "auto_init_score" = 0 or 1 etc..
*
*/
double ObtainAutomaticInitialScore(const ObjectiveFunction* fobj, int class_id) {
  double init_score = 0.0;
  if (fobj != nullptr) {
    init_score = fobj->BoostFromScore(class_id);
  }
  if (Network::num_machines() > 1) {
    init_score = Network::GlobalSyncUpByMean(init_score);
  }
  return init_score;
}

Guolin Ke's avatar
Guolin Ke committed
285
double GBDT::BoostFromAverage(int class_id, bool update_scorer) {
286
  Common::FunctionTimer fun_timer("GBDT::BoostFromAverage", global_timer);
287
  // boosting from average label; or customized "average" if implemented for the current objective
288
289
290
  if (models_.empty() && !train_score_updater_->has_init_score() && objective_function_ != nullptr) {
    if (config_->boost_from_average || (train_data_ != nullptr && train_data_->num_features() == 0)) {
      double init_score = ObtainAutomaticInitialScore(objective_function_, class_id);
291
      if (std::fabs(init_score) > kEpsilon) {
Guolin Ke's avatar
Guolin Ke committed
292
293
294
295
296
        if (update_scorer) {
          train_score_updater_->AddScore(init_score, class_id);
          for (auto& score_updater : valid_score_updater_) {
            score_updater->AddScore(init_score, class_id);
          }
297
298
299
        }
        Log::Info("Start training from score %lf", init_score);
        return init_score;
Guolin Ke's avatar
Guolin Ke committed
300
      }
301
302
303
    } else if (std::string(objective_function_->GetName()) == std::string("regression_l1")
               || std::string(objective_function_->GetName()) == std::string("quantile")
               || std::string(objective_function_->GetName()) == std::string("mape")) {
304
      Log::Warning("Disabling boost_from_average in %s may cause the slow convergence", objective_function_->GetName());
305
    }
306
  }
Guolin Ke's avatar
Guolin Ke committed
307
308
  return 0.0f;
}
Guolin Ke's avatar
Guolin Ke committed
309

Guolin Ke's avatar
Guolin Ke committed
310
bool GBDT::TrainOneIter(const score_t* gradients, const score_t* hessians) {
311
  Common::FunctionTimer fun_timer("GBDT::TrainOneIter", global_timer);
312
  std::vector<double> init_scores(num_tree_per_iteration_, 0.0);
Guolin Ke's avatar
Guolin Ke committed
313
  // boosting first
Guolin Ke's avatar
Guolin Ke committed
314
  if (gradients == nullptr || hessians == nullptr) {
315
    for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
Guolin Ke's avatar
Guolin Ke committed
316
      init_scores[cur_tree_id] = BoostFromAverage(cur_tree_id, true);
317
    }
Guolin Ke's avatar
Guolin Ke committed
318
    Boosting();
319
320
321
    gradients = gradients_pointer_;
    hessians = hessians_pointer_;
  } else {
shiyu1994's avatar
shiyu1994 committed
322
323
    // use customized objective function
    CHECK(objective_function_ == nullptr);
324
    if (data_sample_strategy_->IsHessianChange()) {
shiyu1994's avatar
shiyu1994 committed
325
326
327
328
329
330
331
332
333
      // need to copy customized gradients when using GOSS
      int64_t total_size = static_cast<int64_t>(num_data_) * num_tree_per_iteration_;
      #pragma omp parallel for schedule(static)
      for (int64_t i = 0; i < total_size; ++i) {
        gradients_[i] = gradients[i];
        hessians_[i] = hessians[i];
      }
      CHECK_EQ(gradients_pointer_, gradients_.data());
      CHECK_EQ(hessians_pointer_, hessians_.data());
334
335
336
337
338
      gradients = gradients_pointer_;
      hessians = hessians_pointer_;
    }
  }

339
  // bagging logic
340
341
342
343
  data_sample_strategy_->Bagging(iter_, tree_learner_.get(), gradients_.data(), hessians_.data());
  const bool is_use_subset = data_sample_strategy_->is_use_subset();
  const data_size_t bag_data_cnt = data_sample_strategy_->bag_data_cnt();
  const std::vector<data_size_t, Common::AlignmentAllocator<data_size_t, kAlignedSize>>& bag_data_indices = data_sample_strategy_->bag_data_indices();
Guolin Ke's avatar
Guolin Ke committed
344

345
346
  if (objective_function_ == nullptr && is_use_subset && bag_data_cnt < num_data_ && !boosting_on_gpu_ && !data_sample_strategy_->IsHessianChange()) {
    ResetGradientBuffers();
shiyu1994's avatar
shiyu1994 committed
347
348
  }

Guolin Ke's avatar
Guolin Ke committed
349
  bool should_continue = false;
350
  for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
351
    const size_t offset = static_cast<size_t>(cur_tree_id) * num_data_;
352
    std::unique_ptr<Tree> new_tree(new Tree(2, false, false));
353
    if (class_need_train_[cur_tree_id] && train_data_->num_features() > 0) {
354
355
      auto grad = gradients + offset;
      auto hess = hessians + offset;
Guolin Ke's avatar
Guolin Ke committed
356
      // need to copy gradients for bagging subset.
357
358
359
360
      if (is_use_subset && bag_data_cnt < num_data_ && !boosting_on_gpu_) {
        for (int i = 0; i < bag_data_cnt; ++i) {
          gradients_pointer_[offset + i] = grad[bag_data_indices[i]];
          hessians_pointer_[offset + i] = hess[bag_data_indices[i]];
Guolin Ke's avatar
Guolin Ke committed
361
        }
362
363
        grad = gradients_pointer_ + offset;
        hess = hessians_pointer_ + offset;
Guolin Ke's avatar
Guolin Ke committed
364
      }
365
366
      bool is_first_tree = models_.size() < static_cast<size_t>(num_tree_per_iteration_);
      new_tree.reset(tree_learner_->Train(grad, hess, is_first_tree));
367
    }
Guolin Ke's avatar
Guolin Ke committed
368

Guolin Ke's avatar
Guolin Ke committed
369
    if (new_tree->num_leaves() > 1) {
Guolin Ke's avatar
Guolin Ke committed
370
      should_continue = true;
371
      auto score_ptr = train_score_updater_->score() + offset;
372
373
      auto residual_getter = [score_ptr](const label_t* label, int i) {return static_cast<double>(label[i]) - score_ptr[i]; };
      tree_learner_->RenewTreeOutput(new_tree.get(), objective_function_, residual_getter,
374
                                     num_data_, bag_data_indices.data(), bag_data_cnt, train_score_updater_->score());
Guolin Ke's avatar
Guolin Ke committed
375
376
377
      // shrinkage by learning rate
      new_tree->Shrinkage(shrinkage_rate_);
      // update score
378
      UpdateScore(new_tree.get(), cur_tree_id);
379
380
      if (std::fabs(init_scores[cur_tree_id]) > kEpsilon) {
        new_tree->AddBias(init_scores[cur_tree_id]);
Guolin Ke's avatar
Guolin Ke committed
381
      }
382
383
    } else {
      // only add default score one-time
384
      if (models_.size() < static_cast<size_t>(num_tree_per_iteration_)) {
385
386
387
388
389
390
        if (objective_function_ != nullptr && !config_->boost_from_average && !train_score_updater_->has_init_score()) {
          init_scores[cur_tree_id] = ObtainAutomaticInitialScore(objective_function_, cur_tree_id);
          // updates scores
          train_score_updater_->AddScore(init_scores[cur_tree_id], cur_tree_id);
          for (auto& score_updater : valid_score_updater_) {
            score_updater->AddScore(init_scores[cur_tree_id], cur_tree_id);
391
          }
392
        }
393
        new_tree->AsConstantTree(init_scores[cur_tree_id]);
394
395
      }
    }
Guolin Ke's avatar
Guolin Ke committed
396
397
398
    // add model
    models_.push_back(std::move(new_tree));
  }
Guolin Ke's avatar
Guolin Ke committed
399

Guolin Ke's avatar
Guolin Ke committed
400
  if (!should_continue) {
401
    Log::Warning("Stopped training because there are no more leaves that meet the split requirements");
402
403
404
405
    if (models_.size() > static_cast<size_t>(num_tree_per_iteration_)) {
      for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
        models_.pop_back();
      }
Guolin Ke's avatar
Guolin Ke committed
406
407
408
    }
    return true;
  }
409

Guolin Ke's avatar
Guolin Ke committed
410
411
  ++iter_;
  return false;
Guolin Ke's avatar
Guolin Ke committed
412
}
413

wxchan's avatar
wxchan committed
414
void GBDT::RollbackOneIter() {
415
  if (iter_ <= 0) { return; }
wxchan's avatar
wxchan committed
416
  // reset score
417
  for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
Guolin Ke's avatar
Guolin Ke committed
418
    auto curr_tree = models_.size() - num_tree_per_iteration_ + cur_tree_id;
wxchan's avatar
wxchan committed
419
    models_[curr_tree]->Shrinkage(-1.0);
420
    train_score_updater_->AddScore(models_[curr_tree].get(), cur_tree_id);
wxchan's avatar
wxchan committed
421
    for (auto& score_updater : valid_score_updater_) {
422
      score_updater->AddScore(models_[curr_tree].get(), cur_tree_id);
wxchan's avatar
wxchan committed
423
424
425
    }
  }
  // remove model
426
  for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
wxchan's avatar
wxchan committed
427
428
429
430
431
    models_.pop_back();
  }
  --iter_;
}

Guolin Ke's avatar
Guolin Ke committed
432
bool GBDT::EvalAndCheckEarlyStopping() {
433
434
  bool is_met_early_stopping = false;
  // print message for metric
Guolin Ke's avatar
Guolin Ke committed
435
  auto best_msg = OutputMetric(iter_);
Guolin Ke's avatar
Guolin Ke committed
436
437


Guolin Ke's avatar
Guolin Ke committed
438
  is_met_early_stopping = !best_msg.empty();
439
440
  if (is_met_early_stopping) {
    Log::Info("Early stopping at iteration %d, the best iteration round is %d",
441
              iter_, iter_ - early_stopping_round_);
Guolin Ke's avatar
Guolin Ke committed
442
    Log::Info("Output of best iteration round:\n%s", best_msg.c_str());
443
    // pop last early_stopping_round_ models
444
    for (int i = 0; i < early_stopping_round_ * num_tree_per_iteration_; ++i) {
445
446
447
448
      models_.pop_back();
    }
  }
  return is_met_early_stopping;
Guolin Ke's avatar
Guolin Ke committed
449
450
}

451
void GBDT::UpdateScore(const Tree* tree, const int cur_tree_id) {
452
  Common::FunctionTimer fun_timer("GBDT::UpdateScore", global_timer);
Guolin Ke's avatar
Guolin Ke committed
453
  // update training score
454
  if (!data_sample_strategy_->is_use_subset()) {
455
    train_score_updater_->AddScore(tree_learner_.get(), tree, cur_tree_id);
Guolin Ke's avatar
Guolin Ke committed
456

457
    const data_size_t bag_data_cnt = data_sample_strategy_->bag_data_cnt();
Guolin Ke's avatar
Guolin Ke committed
458
    // we need to predict out-of-bag scores of data for boosting
459
    if (num_data_ - bag_data_cnt > 0) {
460
461
      #ifdef USE_CUDA_EXP
      if (config_->device_type == std::string("cuda_exp")) {
462
        train_score_updater_->AddScore(tree, data_sample_strategy_->cuda_bag_data_indices().RawData() + bag_data_cnt, num_data_ - bag_data_cnt, cur_tree_id);
463
464
      } else {
      #endif  // USE_CUDA_EXP
465
        train_score_updater_->AddScore(tree, data_sample_strategy_->bag_data_indices().data() + bag_data_cnt, num_data_ - bag_data_cnt, cur_tree_id);
466
467
468
      #ifdef USE_CUDA_EXP
      }
      #endif  // USE_CUDA_EXP
Guolin Ke's avatar
Guolin Ke committed
469
470
    }

Guolin Ke's avatar
Guolin Ke committed
471
  } else {
472
    train_score_updater_->AddScore(tree, cur_tree_id);
Guolin Ke's avatar
Guolin Ke committed
473
  }
Guolin Ke's avatar
Guolin Ke committed
474
475


Guolin Ke's avatar
Guolin Ke committed
476
  // update validation score
Guolin Ke's avatar
Guolin Ke committed
477
  for (auto& score_updater : valid_score_updater_) {
478
    score_updater->AddScore(tree, cur_tree_id);
Guolin Ke's avatar
Guolin Ke committed
479
480
481
  }
}

482
483
484
485
486
#ifdef USE_CUDA_EXP
std::vector<double> GBDT::EvalOneMetric(const Metric* metric, const double* score, const data_size_t num_data) const {
#else
std::vector<double> GBDT::EvalOneMetric(const Metric* metric, const double* score, const data_size_t /*num_data*/) const {
#endif  // USE_CUDA_EXP
487
488
  #ifdef USE_CUDA_EXP
  const bool evaluation_on_cuda = metric->IsCUDAMetric();
shiyu1994's avatar
shiyu1994 committed
489
  if ((boosting_on_gpu_ && evaluation_on_cuda) || (!boosting_on_gpu_ && !evaluation_on_cuda)) {
490
491
492
  #endif  // USE_CUDA_EXP
    return metric->Eval(score, objective_function_);
  #ifdef USE_CUDA_EXP
shiyu1994's avatar
shiyu1994 committed
493
  } else if (boosting_on_gpu_ && !evaluation_on_cuda) {
494
    const size_t total_size = static_cast<size_t>(num_data) * static_cast<size_t>(num_tree_per_iteration_);
495
496
497
498
499
500
    if (total_size > host_score_.size()) {
      host_score_.resize(total_size, 0.0f);
    }
    CopyFromCUDADeviceToHost<double>(host_score_.data(), score, total_size, __FILE__, __LINE__);
    return metric->Eval(host_score_.data(), objective_function_);
  } else {
501
    const size_t total_size = static_cast<size_t>(num_data) * static_cast<size_t>(num_tree_per_iteration_);
502
503
504
505
506
507
508
    if (total_size > cuda_score_.Size()) {
      cuda_score_.Resize(total_size);
    }
    CopyFromHostToCUDADevice<double>(cuda_score_.RawData(), score, total_size, __FILE__, __LINE__);
    return metric->Eval(cuda_score_.RawData(), objective_function_);
  }
  #endif  // USE_CUDA_EXP
Guolin Ke's avatar
Guolin Ke committed
509
510
}

Guolin Ke's avatar
Guolin Ke committed
511
std::string GBDT::OutputMetric(int iter) {
Guolin Ke's avatar
Guolin Ke committed
512
  bool need_output = (iter % config_->metric_freq) == 0;
Guolin Ke's avatar
Guolin Ke committed
513
514
  std::string ret = "";
  std::stringstream msg_buf;
515
  std::vector<std::pair<size_t, size_t>> meet_early_stopping_pairs;
Guolin Ke's avatar
Guolin Ke committed
516
  // print training metric
Guolin Ke's avatar
Guolin Ke committed
517
  if (need_output) {
518
519
    for (auto& sub_metric : training_metrics_) {
      auto name = sub_metric->GetName();
520
      auto scores = EvalOneMetric(sub_metric, train_score_updater_->score(), train_score_updater_->num_data());
Guolin Ke's avatar
Guolin Ke committed
521
      for (size_t k = 0; k < name.size(); ++k) {
Guolin Ke's avatar
Guolin Ke committed
522
523
524
525
526
527
        std::stringstream tmp_buf;
        tmp_buf << "Iteration:" << iter
          << ", training " << name[k]
          << " : " << scores[k];
        Log::Info(tmp_buf.str().c_str());
        if (early_stopping_round_ > 0) {
528
          msg_buf << tmp_buf.str() << '\n';
Guolin Ke's avatar
Guolin Ke committed
529
        }
530
      }
531
    }
Guolin Ke's avatar
Guolin Ke committed
532
533
  }
  // print validation metric
Guolin Ke's avatar
Guolin Ke committed
534
  if (need_output || early_stopping_round_ > 0) {
535
536
    for (size_t i = 0; i < valid_metrics_.size(); ++i) {
      for (size_t j = 0; j < valid_metrics_[i].size(); ++j) {
537
        auto test_scores = EvalOneMetric(valid_metrics_[i][j], valid_score_updater_[i]->score(), valid_score_updater_[i]->num_data());
Guolin Ke's avatar
Guolin Ke committed
538
539
540
541
542
543
544
545
546
547
        auto name = valid_metrics_[i][j]->GetName();
        for (size_t k = 0; k < name.size(); ++k) {
          std::stringstream tmp_buf;
          tmp_buf << "Iteration:" << iter
            << ", valid_" << i + 1 << " " << name[k]
            << " : " << test_scores[k];
          if (need_output) {
            Log::Info(tmp_buf.str().c_str());
          }
          if (early_stopping_round_ > 0) {
548
            msg_buf << tmp_buf.str() << '\n';
549
          }
wxchan's avatar
wxchan committed
550
        }
551
        if (es_first_metric_only_ && j > 0) { continue; }
Guolin Ke's avatar
Guolin Ke committed
552
        if (ret.empty() && early_stopping_round_ > 0) {
553
554
555
          auto cur_score = valid_metrics_[i][j]->factor_to_bigger_better() * test_scores.back();
          if (cur_score > best_score_[i][j]) {
            best_score_[i][j] = cur_score;
556
            best_iter_[i][j] = iter;
Guolin Ke's avatar
Guolin Ke committed
557
            meet_early_stopping_pairs.emplace_back(i, j);
558
          } else {
Guolin Ke's avatar
Guolin Ke committed
559
            if (iter - best_iter_[i][j] >= early_stopping_round_) { ret = best_msg_[i][j]; }
560
          }
wxchan's avatar
wxchan committed
561
562
        }
      }
Guolin Ke's avatar
Guolin Ke committed
563
564
    }
  }
Guolin Ke's avatar
Guolin Ke committed
565
566
567
  for (auto& pair : meet_early_stopping_pairs) {
    best_msg_[pair.first][pair.second] = msg_buf.str();
  }
wxchan's avatar
wxchan committed
568
  return ret;
Guolin Ke's avatar
Guolin Ke committed
569
570
}

571
/*! \brief Get eval result */
572
std::vector<double> GBDT::GetEvalAt(int data_idx) const {
Guolin Ke's avatar
Guolin Ke committed
573
  CHECK(data_idx >= 0 && data_idx <= static_cast<int>(valid_score_updater_.size()));
574
575
  std::vector<double> ret;
  if (data_idx == 0) {
576
    for (auto& sub_metric : training_metrics_) {
577
      auto scores = EvalOneMetric(sub_metric, train_score_updater_->score(), train_score_updater_->num_data());
578
579
580
      for (auto score : scores) {
        ret.push_back(score);
      }
581
    }
582
  } else {
583
584
    auto used_idx = data_idx - 1;
    for (size_t j = 0; j < valid_metrics_[used_idx].size(); ++j) {
585
      auto test_scores = EvalOneMetric(valid_metrics_[used_idx][j], valid_score_updater_[used_idx]->score(), valid_score_updater_[used_idx]->num_data());
586
587
588
      for (auto score : test_scores) {
        ret.push_back(score);
      }
589
590
591
592
593
    }
  }
  return ret;
}

Guolin Ke's avatar
Guolin Ke committed
594
/*! \brief Get training scores result */
595
const double* GBDT::GetTrainingScore(int64_t* out_len) {
596
  *out_len = static_cast<int64_t>(train_score_updater_->num_data()) * num_class_;
Guolin Ke's avatar
Guolin Ke committed
597
  return train_score_updater_->score();
598
599
}

600
void GBDT::PredictContrib(const double* features, double* output) const {
601
  // set zero
Guolin Ke's avatar
Guolin Ke committed
602
603
  const int num_features = max_feature_idx_ + 1;
  std::memset(output, 0, sizeof(double) * num_tree_per_iteration_ * (num_features + 1));
604
605
  const int end_iteration_for_pred = start_iteration_for_pred_ + num_iteration_for_pred_;
  for (int i = start_iteration_for_pred_; i < end_iteration_for_pred; ++i) {
606
607
    // predict all the trees for one iteration
    for (int k = 0; k < num_tree_per_iteration_; ++k) {
Guolin Ke's avatar
Guolin Ke committed
608
      models_[i * num_tree_per_iteration_ + k]->PredictContrib(features, num_features, output + k*(num_features + 1));
609
    }
610
611
612
613
614
615
  }
}

void GBDT::PredictContribByMap(const std::unordered_map<int, double>& features,
                               std::vector<std::unordered_map<int, double>>* output) const {
  const int num_features = max_feature_idx_ + 1;
616
617
  const int end_iteration_for_pred = start_iteration_for_pred_ + num_iteration_for_pred_;
  for (int i = start_iteration_for_pred_; i < end_iteration_for_pred; ++i) {
618
619
620
    // predict all the trees for one iteration
    for (int k = 0; k < num_tree_per_iteration_; ++k) {
      models_[i * num_tree_per_iteration_ + k]->PredictContribByMap(features, num_features, &((*output)[k]));
621
622
623
624
    }
  }
}

Guolin Ke's avatar
Guolin Ke committed
625
626
void GBDT::GetPredictAt(int data_idx, double* out_result, int64_t* out_len) {
  CHECK(data_idx >= 0 && data_idx <= static_cast<int>(valid_score_updater_.size()));
Guolin Ke's avatar
Guolin Ke committed
627

628
  const double* raw_scores = nullptr;
Guolin Ke's avatar
Guolin Ke committed
629
630
  data_size_t num_data = 0;
  if (data_idx == 0) {
wxchan's avatar
wxchan committed
631
    raw_scores = GetTrainingScore(out_len);
Guolin Ke's avatar
Guolin Ke committed
632
633
634
635
636
    num_data = train_score_updater_->num_data();
  } else {
    auto used_idx = data_idx - 1;
    raw_scores = valid_score_updater_[used_idx]->score();
    num_data = valid_score_updater_[used_idx]->num_data();
637
    *out_len = static_cast<int64_t>(num_data) * num_class_;
Guolin Ke's avatar
Guolin Ke committed
638
  }
639
640
641
642
643
644
645
646
  #ifdef USE_CUDA_EXP
  std::vector<double> host_raw_scores;
  if (boosting_on_gpu_) {
    host_raw_scores.resize(static_cast<size_t>(*out_len), 0.0);
    CopyFromCUDADeviceToHost<double>(host_raw_scores.data(), raw_scores, static_cast<size_t>(*out_len), __FILE__, __LINE__);
    raw_scores = host_raw_scores.data();
  }
  #endif  // USE_CUDA_EXP
Guolin Ke's avatar
Guolin Ke committed
647
  if (objective_function_ != nullptr) {
Guolin Ke's avatar
Guolin Ke committed
648
649
    #pragma omp parallel for schedule(static)
    for (data_size_t i = 0; i < num_data; ++i) {
Guolin Ke's avatar
Guolin Ke committed
650
      std::vector<double> tree_pred(num_tree_per_iteration_);
651
      for (int j = 0; j < num_tree_per_iteration_; ++j) {
Guolin Ke's avatar
Guolin Ke committed
652
        tree_pred[j] = raw_scores[j * num_data + i];
653
      }
Guolin Ke's avatar
Guolin Ke committed
654
655
      std::vector<double> tmp_result(num_class_);
      objective_function_->ConvertOutput(tree_pred.data(), tmp_result.data());
Guolin Ke's avatar
Guolin Ke committed
656
      for (int j = 0; j < num_class_; ++j) {
657
        out_result[j * num_data + i] = static_cast<double>(tmp_result[j]);
Guolin Ke's avatar
Guolin Ke committed
658
659
      }
    }
660
  } else {
Guolin Ke's avatar
Guolin Ke committed
661
    #pragma omp parallel for schedule(static)
Guolin Ke's avatar
Guolin Ke committed
662
    for (data_size_t i = 0; i < num_data; ++i) {
663
      for (int j = 0; j < num_tree_per_iteration_; ++j) {
Guolin Ke's avatar
Guolin Ke committed
664
        out_result[j * num_data + i] = static_cast<double>(raw_scores[j * num_data + i]);
Guolin Ke's avatar
Guolin Ke committed
665
666
667
668
669
      }
    }
  }
}

670
671
double GBDT::GetUpperBoundValue() const {
  double max_value = 0.0;
Nikita Titov's avatar
Nikita Titov committed
672
  for (const auto &tree : models_) {
673
674
675
676
677
678
679
    max_value += tree->GetUpperBoundValue();
  }
  return max_value;
}

double GBDT::GetLowerBoundValue() const {
  double min_value = 0.0;
Nikita Titov's avatar
Nikita Titov committed
680
  for (const auto &tree : models_) {
681
682
683
684
685
    min_value += tree->GetLowerBoundValue();
  }
  return min_value;
}

Guolin Ke's avatar
Guolin Ke committed
686
687
688
void GBDT::ResetTrainingData(const Dataset* train_data, const ObjectiveFunction* objective_function,
                             const std::vector<const Metric*>& training_metrics) {
  if (train_data != train_data_ && !train_data_->CheckAlign(*train_data)) {
689
    Log::Fatal("Cannot reset training data, since new training data has different bin mappers");
wxchan's avatar
wxchan committed
690
691
  }

Guolin Ke's avatar
Guolin Ke committed
692
  objective_function_ = objective_function;
693
  data_sample_strategy_->UpdateObjectiveFunction(objective_function);
Guolin Ke's avatar
Guolin Ke committed
694
  if (objective_function_ != nullptr) {
Nikita Titov's avatar
Nikita Titov committed
695
    CHECK_EQ(num_tree_per_iteration_, objective_function_->NumModelPerIteration());
696
697
698
    if (objective_function_->IsRenewTreeOutput() && !config_->monotone_constraints.empty()) {
      Log::Fatal("Cannot use ``monotone_constraints`` in %s objective, please disable it.", objective_function_->GetName());
    }
699
  }
700
  is_constant_hessian_ = GetIsConstHessian(objective_function);
701

Guolin Ke's avatar
Guolin Ke committed
702
703
704
705
  // push training metrics
  training_metrics_.clear();
  for (const auto& metric : training_metrics) {
    training_metrics_.push_back(metric);
706
  }
Guolin Ke's avatar
Guolin Ke committed
707
  training_metrics_.shrink_to_fit();
708

709
710
711
  #ifdef USE_CUDA_EXP
  boosting_on_gpu_ = objective_function_ != nullptr && objective_function_->IsCUDAObjective() &&
                    !data_sample_strategy_->IsHessianChange();  // for sample strategy with Hessian change, fall back to boosting on CPU
shiyu1994's avatar
shiyu1994 committed
712
  tree_learner_->ResetBoostingOnGPU(boosting_on_gpu_);
713
  #endif  // USE_CUDA_EXP
714

Guolin Ke's avatar
Guolin Ke committed
715
716
  if (train_data != train_data_) {
    train_data_ = train_data;
717
    data_sample_strategy_->UpdateTrainingData(train_data);
Guolin Ke's avatar
Guolin Ke committed
718
719
    // not same training data, need reset score and others
    // create score tracker
720
721
    #ifdef USE_CUDA_EXP
    if (config_->device_type == std::string("cuda_exp")) {
shiyu1994's avatar
shiyu1994 committed
722
      train_score_updater_.reset(new CUDAScoreUpdater(train_data_, num_tree_per_iteration_, boosting_on_gpu_));
723
724
725
726
727
728
    } else {
    #endif  // USE_CUDA_EXP
      train_score_updater_.reset(new ScoreUpdater(train_data_, num_tree_per_iteration_));
    #ifdef USE_CUDA_EXP
    }
    #endif  // USE_CUDA_EXP
729

Guolin Ke's avatar
Guolin Ke committed
730
731
732
733
734
735
    // update score
    for (int i = 0; i < iter_; ++i) {
      for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
        auto curr_tree = (i + num_init_iteration_) * num_tree_per_iteration_ + cur_tree_id;
        train_score_updater_->AddScore(models_[curr_tree].get(), cur_tree_id);
      }
736
737
    }

Guolin Ke's avatar
Guolin Ke committed
738
    num_data_ = train_data_->num_data();
739

740
    ResetGradientBuffers();
741

Guolin Ke's avatar
Guolin Ke committed
742
743
744
745
    max_feature_idx_ = train_data_->num_total_features() - 1;
    label_idx_ = train_data_->label_idx();
    feature_names_ = train_data_->feature_names();
    feature_infos_ = train_data_->feature_infos();
746
    parser_config_str_ = train_data_->parser_config_str();
747

748
    tree_learner_->ResetTrainingData(train_data, is_constant_hessian_);
749
    data_sample_strategy_->ResetSampleConfig(config_.get(), true);
750
751
  } else {
    tree_learner_->ResetIsConstantHessian(is_constant_hessian_);
752
  }
753
754
}

Guolin Ke's avatar
Guolin Ke committed
755
756
void GBDT::ResetConfig(const Config* config) {
  auto new_config = std::unique_ptr<Config>(new Config(*config));
757
  if (!config->monotone_constraints.empty()) {
Nikita Titov's avatar
Nikita Titov committed
758
    CHECK_EQ(static_cast<size_t>(train_data_->num_total_features()), config->monotone_constraints.size());
759
760
  }
  if (!config->feature_contri.empty()) {
Nikita Titov's avatar
Nikita Titov committed
761
    CHECK_EQ(static_cast<size_t>(train_data_->num_total_features()), config->feature_contri.size());
762
  }
763
764
765
  if (objective_function_ != nullptr && objective_function_->IsRenewTreeOutput() && !config->monotone_constraints.empty()) {
    Log::Fatal("Cannot use ``monotone_constraints`` in %s objective, please disable it.", objective_function_->GetName());
  }
Guolin Ke's avatar
Guolin Ke committed
766
767
768
  early_stopping_round_ = new_config->early_stopping_round;
  shrinkage_rate_ = new_config->learning_rate;
  if (tree_learner_ != nullptr) {
Guolin Ke's avatar
Guolin Ke committed
769
    tree_learner_->ResetConfig(new_config.get());
770
  }
shiyu1994's avatar
shiyu1994 committed
771

772
773
  boosting_on_gpu_ = objective_function_ != nullptr && objective_function_->IsCUDAObjective() &&
                    !data_sample_strategy_->IsHessianChange();  // for sample strategy with Hessian change, fall back to boosting on CPU
shiyu1994's avatar
shiyu1994 committed
774
775
  tree_learner_->ResetBoostingOnGPU(boosting_on_gpu_);

Guolin Ke's avatar
Guolin Ke committed
776
  if (train_data_ != nullptr) {
777
778
779
780
781
    data_sample_strategy_->ResetSampleConfig(new_config.get(), false);
    if (data_sample_strategy_->NeedResizeGradients()) {
      // resize gradient vectors to copy the customized gradients for goss or bagging with subset
      ResetGradientBuffers();
    }
782
  }
783
  if (config_.get() != nullptr && config_->forcedsplits_filename != new_config->forcedsplits_filename) {
784
785
786
787
788
789
790
    // load forced_splits file
    if (!new_config->forcedsplits_filename.empty()) {
      std::ifstream forced_splits_file(
          new_config->forcedsplits_filename.c_str());
      std::stringstream buffer;
      buffer << forced_splits_file.rdbuf();
      std::string err;
Guolin Ke's avatar
Guolin Ke committed
791
      forced_splits_json_ = Json::parse(buffer.str(), &err);
792
793
794
795
796
797
      tree_learner_->SetForcedSplit(&forced_splits_json_);
    } else {
      forced_splits_json_ = Json();
      tree_learner_->SetForcedSplit(nullptr);
    }
  }
Guolin Ke's avatar
Guolin Ke committed
798
  config_.reset(new_config.release());
Guolin Ke's avatar
Guolin Ke committed
799
800
}

801
802
803
804
void GBDT::ResetGradientBuffers() {
  const size_t total_size = static_cast<size_t>(num_data_) * num_tree_per_iteration_;
  const bool is_use_subset = data_sample_strategy_->is_use_subset();
  const data_size_t bag_data_cnt = data_sample_strategy_->bag_data_cnt();
Guolin Ke's avatar
Guolin Ke committed
805
  if (objective_function_ != nullptr) {
806
    #ifdef USE_CUDA_EXP
807
808
809
810
    if (config_->device_type == std::string("cuda_exp") && boosting_on_gpu_) {
      if (cuda_gradients_.Size() < total_size) {
        cuda_gradients_.Resize(total_size);
        cuda_hessians_.Resize(total_size);
Guolin Ke's avatar
Guolin Ke committed
811
      }
812
813
814
815
816
      gradients_pointer_ = cuda_gradients_.RawData();
      hessians_pointer_ = cuda_hessians_.RawData();
    } else {
    #endif  // USE_CUDA_EXP
      if (gradients_.size() < total_size) {
shiyu1994's avatar
shiyu1994 committed
817
818
        gradients_.resize(total_size);
        hessians_.resize(total_size);
819
      }
820
821
      gradients_pointer_ = gradients_.data();
      hessians_pointer_ = hessians_.data();
822
    #ifdef USE_CUDA_EXP
823
    }
824
    #endif  // USE_CUDA_EXP
825
826
827
828
829
830
831
  } else if (data_sample_strategy_->IsHessianChange() || (is_use_subset && bag_data_cnt < num_data_ && !boosting_on_gpu_)) {
    if (gradients_.size() < total_size) {
      gradients_.resize(total_size);
      hessians_.resize(total_size);
    }
    gradients_pointer_ = gradients_.data();
    hessians_pointer_ = hessians_.data();
832
  }
wxchan's avatar
wxchan committed
833
834
}

Guolin Ke's avatar
Guolin Ke committed
835
}  // namespace LightGBM