config.cpp 16 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
#include <LightGBM/config.h>

7
#include <LightGBM/cuda/vector_cudahost.h>
Guolin Ke's avatar
Guolin Ke committed
8
9
#include <LightGBM/utils/common.h>
#include <LightGBM/utils/log.h>
10
#include <LightGBM/utils/random.h>
Guolin Ke's avatar
Guolin Ke committed
11

12
13
#include <limits>

Guolin Ke's avatar
Guolin Ke committed
14
15
namespace LightGBM {

Guolin Ke's avatar
Guolin Ke committed
16
void Config::KV2Map(std::unordered_map<std::string, std::string>* params, const char* kv) {
wxchan's avatar
wxchan committed
17
  std::vector<std::string> tmp_strs = Common::Split(kv, '=');
18
  if (tmp_strs.size() == 2 || tmp_strs.size() == 1) {
wxchan's avatar
wxchan committed
19
    std::string key = Common::RemoveQuotationSymbol(Common::Trim(tmp_strs[0]));
20
21
22
23
    std::string value = "";
    if (tmp_strs.size() == 2) {
      value = Common::RemoveQuotationSymbol(Common::Trim(tmp_strs[1]));
    }
wxchan's avatar
wxchan committed
24
    if (key.size() > 0) {
Guolin Ke's avatar
Guolin Ke committed
25
26
27
      auto value_search = params->find(key);
      if (value_search == params->end()) {  // not set
        params->emplace(key, value);
wxchan's avatar
wxchan committed
28
      } else {
29
        Log::Warning("%s is set=%s, %s=%s will be ignored. Current value: %s=%s",
wxchan's avatar
wxchan committed
30
31
32
33
34
35
36
37
38
          key.c_str(), value_search->second.c_str(), key.c_str(), value.c_str(),
          key.c_str(), value_search->second.c_str());
      }
    }
  } else {
    Log::Warning("Unknown parameter %s", kv);
  }
}

Guolin Ke's avatar
Guolin Ke committed
39
std::unordered_map<std::string, std::string> Config::Str2Map(const char* parameters) {
40
  std::unordered_map<std::string, std::string> params;
41
  auto args = Common::Split(parameters, " \t\n\r");
42
  for (auto arg : args) {
Guolin Ke's avatar
Guolin Ke committed
43
    KV2Map(&params, Common::Trim(arg).c_str());
44
45
  }
  ParameterAlias::KeyAliasTransform(&params);
46
  return params;
47
48
}

Guolin Ke's avatar
Guolin Ke committed
49
void GetBoostingType(const std::unordered_map<std::string, std::string>& params, std::string* boosting) {
Guolin Ke's avatar
Guolin Ke committed
50
  std::string value;
Guolin Ke's avatar
Guolin Ke committed
51
  if (Config::GetString(params, "boosting", &value)) {
Guolin Ke's avatar
Guolin Ke committed
52
    std::transform(value.begin(), value.end(), value.begin(), Common::tolower);
Guolin Ke's avatar
Guolin Ke committed
53
    if (value == std::string("gbdt") || value == std::string("gbrt")) {
Guolin Ke's avatar
Guolin Ke committed
54
      *boosting = "gbdt";
55
    } else if (value == std::string("dart")) {
Guolin Ke's avatar
Guolin Ke committed
56
      *boosting = "dart";
Guolin Ke's avatar
Guolin Ke committed
57
    } else if (value == std::string("goss")) {
Guolin Ke's avatar
Guolin Ke committed
58
      *boosting = "goss";
59
    } else if (value == std::string("rf") || value == std::string("random_forest")) {
Guolin Ke's avatar
Guolin Ke committed
60
      *boosting = "rf";
Guolin Ke's avatar
Guolin Ke committed
61
    } else {
62
      Log::Fatal("Unknown boosting type %s", value.c_str());
Guolin Ke's avatar
Guolin Ke committed
63
64
65
66
    }
  }
}

Guolin Ke's avatar
Guolin Ke committed
67
68
69
70
71
72
73
74
75
76
77
78
79
void ParseMetrics(const std::string& value, std::vector<std::string>* out_metric) {
  std::unordered_set<std::string> metric_sets;
  out_metric->clear();
  std::vector<std::string> metrics = Common::Split(value.c_str(), ',');
  for (auto& met : metrics) {
    auto type = ParseMetricAlias(met);
    if (metric_sets.count(type) <= 0) {
      out_metric->push_back(type);
      metric_sets.insert(type);
    }
  }
}

Guolin Ke's avatar
Guolin Ke committed
80
void GetObjectiveType(const std::unordered_map<std::string, std::string>& params, std::string* objective) {
Guolin Ke's avatar
Guolin Ke committed
81
  std::string value;
Guolin Ke's avatar
Guolin Ke committed
82
  if (Config::GetString(params, "objective", &value)) {
Guolin Ke's avatar
Guolin Ke committed
83
    std::transform(value.begin(), value.end(), value.begin(), Common::tolower);
Guolin Ke's avatar
Guolin Ke committed
84
    *objective = ParseObjectiveAlias(value);
Guolin Ke's avatar
Guolin Ke committed
85
86
87
  }
}

88
void GetMetricType(const std::unordered_map<std::string, std::string>& params, const std::string& objective, std::vector<std::string>* metric) {
Guolin Ke's avatar
Guolin Ke committed
89
  std::string value;
Guolin Ke's avatar
Guolin Ke committed
90
  if (Config::GetString(params, "metric", &value)) {
Guolin Ke's avatar
Guolin Ke committed
91
    std::transform(value.begin(), value.end(), value.begin(), Common::tolower);
Guolin Ke's avatar
Guolin Ke committed
92
    ParseMetrics(value, metric);
Guolin Ke's avatar
Guolin Ke committed
93
  }
94
  // add names of objective function if not providing metric
Guolin Ke's avatar
Guolin Ke committed
95
  if (metric->empty() && value.size() == 0) {
96
    ParseMetrics(objective, metric);
97
  }
Guolin Ke's avatar
Guolin Ke committed
98
99
}

Guolin Ke's avatar
Guolin Ke committed
100
void GetTaskType(const std::unordered_map<std::string, std::string>& params, TaskType* task) {
Guolin Ke's avatar
Guolin Ke committed
101
  std::string value;
Guolin Ke's avatar
Guolin Ke committed
102
  if (Config::GetString(params, "task", &value)) {
Guolin Ke's avatar
Guolin Ke committed
103
    std::transform(value.begin(), value.end(), value.begin(), Common::tolower);
Guolin Ke's avatar
Guolin Ke committed
104
    if (value == std::string("train") || value == std::string("training")) {
Guolin Ke's avatar
Guolin Ke committed
105
      *task = TaskType::kTrain;
Guolin Ke's avatar
Guolin Ke committed
106
    } else if (value == std::string("predict") || value == std::string("prediction")
Guolin Ke's avatar
Guolin Ke committed
107
               || value == std::string("test")) {
Guolin Ke's avatar
Guolin Ke committed
108
      *task = TaskType::kPredict;
109
    } else if (value == std::string("convert_model")) {
Guolin Ke's avatar
Guolin Ke committed
110
      *task = TaskType::kConvertModel;
111
    } else if (value == std::string("refit") || value == std::string("refit_tree")) {
Guolin Ke's avatar
Guolin Ke committed
112
      *task = TaskType::KRefitTree;
113
114
    } else if (value == std::string("save_binary")) {
      *task = TaskType::kSaveBinary;
Guolin Ke's avatar
Guolin Ke committed
115
    } else {
116
      Log::Fatal("Unknown task type %s", value.c_str());
Guolin Ke's avatar
Guolin Ke committed
117
118
119
120
    }
  }
}

wxchan's avatar
wxchan committed
121
void GetDeviceType(const std::unordered_map<std::string, std::string>& params, std::string* device_type) {
Guolin Ke's avatar
Guolin Ke committed
122
  std::string value;
123
  if (Config::GetString(params, "device_type", &value)) {
Guolin Ke's avatar
Guolin Ke committed
124
125
    std::transform(value.begin(), value.end(), value.begin(), Common::tolower);
    if (value == std::string("cpu")) {
wxchan's avatar
wxchan committed
126
      *device_type = "cpu";
Guolin Ke's avatar
Guolin Ke committed
127
    } else if (value == std::string("gpu")) {
wxchan's avatar
wxchan committed
128
      *device_type = "gpu";
129
130
    } else if (value == std::string("cuda")) {
      *device_type = "cuda";
Guolin Ke's avatar
Guolin Ke committed
131
132
133
134
135
136
    } else {
      Log::Fatal("Unknown device type %s", value.c_str());
    }
  }
}

Guolin Ke's avatar
Guolin Ke committed
137
void GetTreeLearnerType(const std::unordered_map<std::string, std::string>& params, std::string* tree_learner) {
Guolin Ke's avatar
Guolin Ke committed
138
  std::string value;
Guolin Ke's avatar
Guolin Ke committed
139
  if (Config::GetString(params, "tree_learner", &value)) {
Guolin Ke's avatar
Guolin Ke committed
140
141
    std::transform(value.begin(), value.end(), value.begin(), Common::tolower);
    if (value == std::string("serial")) {
Guolin Ke's avatar
Guolin Ke committed
142
      *tree_learner = "serial";
Guolin Ke's avatar
Guolin Ke committed
143
    } else if (value == std::string("feature") || value == std::string("feature_parallel")) {
Guolin Ke's avatar
Guolin Ke committed
144
      *tree_learner = "feature";
Guolin Ke's avatar
Guolin Ke committed
145
    } else if (value == std::string("data") || value == std::string("data_parallel")) {
Guolin Ke's avatar
Guolin Ke committed
146
      *tree_learner = "data";
Guolin Ke's avatar
Guolin Ke committed
147
    } else if (value == std::string("voting") || value == std::string("voting_parallel")) {
Guolin Ke's avatar
Guolin Ke committed
148
      *tree_learner = "voting";
Guolin Ke's avatar
Guolin Ke committed
149
150
151
152
153
154
    } else {
      Log::Fatal("Unknown tree learner type %s", value.c_str());
    }
  }
}

Belinda Trotta's avatar
Belinda Trotta committed
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
void Config::GetAucMuWeights() {
  if (auc_mu_weights.empty()) {
    // equal weights for all classes
    auc_mu_weights_matrix = std::vector<std::vector<double>> (num_class, std::vector<double>(num_class, 1));
    for (size_t i = 0; i < static_cast<size_t>(num_class); ++i) {
      auc_mu_weights_matrix[i][i] = 0;
    }
  } else {
    auc_mu_weights_matrix = std::vector<std::vector<double>> (num_class, std::vector<double>(num_class, 0));
    if (auc_mu_weights.size() != static_cast<size_t>(num_class * num_class)) {
      Log::Fatal("auc_mu_weights must have %d elements, but found %d", num_class * num_class, auc_mu_weights.size());
    }
    for (size_t i = 0; i < static_cast<size_t>(num_class); ++i) {
      for (size_t j = 0; j < static_cast<size_t>(num_class); ++j) {
        if (i == j) {
          auc_mu_weights_matrix[i][j] = 0;
          if (std::fabs(auc_mu_weights[i * num_class + j]) > kZeroThreshold) {
            Log::Info("AUC-mu matrix must have zeros on diagonal. Overwriting value in position %d of auc_mu_weights with 0.", i * num_class + j);
          }
        } else {
          if (std::fabs(auc_mu_weights[i * num_class + j]) < kZeroThreshold) {
            Log::Fatal("AUC-mu matrix must have non-zero values for non-diagonal entries. Found zero value in position %d of auc_mu_weights.", i * num_class + j);
          }
          auc_mu_weights_matrix[i][j] = auc_mu_weights[i * num_class + j];
        }
      }
    }
  }
183
}
Belinda Trotta's avatar
Belinda Trotta committed
184

185
186
187
188
189
190
191
192
void Config::GetInteractionConstraints() {
  if (interaction_constraints == "") {
    interaction_constraints_vector = std::vector<std::vector<int>>();
  } else {
    interaction_constraints_vector = Common::StringToArrayofArrays<int>(interaction_constraints, '[', ']', ',');
  }
}

Guolin Ke's avatar
Guolin Ke committed
193
void Config::Set(const std::unordered_map<std::string, std::string>& params) {
Guolin Ke's avatar
Guolin Ke committed
194
195
196
  // generate seeds by seed.
  if (GetInt(params, "seed", &seed)) {
    Random rand(seed);
Guolin Ke's avatar
Guolin Ke committed
197
    int int_max = std::numeric_limits<int16_t>::max();
Guolin Ke's avatar
Guolin Ke committed
198
199
200
201
    data_random_seed = static_cast<int>(rand.NextShort(0, int_max));
    bagging_seed = static_cast<int>(rand.NextShort(0, int_max));
    drop_seed = static_cast<int>(rand.NextShort(0, int_max));
    feature_fraction_seed = static_cast<int>(rand.NextShort(0, int_max));
202
    objective_seed = static_cast<int>(rand.NextShort(0, int_max));
203
    extra_seed = static_cast<int>(rand.NextShort(0, int_max));
Guolin Ke's avatar
Guolin Ke committed
204
205
  }

Guolin Ke's avatar
Guolin Ke committed
206
207
208
  GetTaskType(params, &task);
  GetBoostingType(params, &boosting);
  GetObjectiveType(params, &objective);
209
  GetMetricType(params, objective, &metric);
Guolin Ke's avatar
Guolin Ke committed
210
  GetDeviceType(params, &device_type);
211
212
213
  if (device_type == std::string("cuda")) {
    LGBM_config_::current_device = lgbm_device_cuda;
  }
Guolin Ke's avatar
Guolin Ke committed
214
  GetTreeLearnerType(params, &tree_learner);
Guolin Ke's avatar
Guolin Ke committed
215

Guolin Ke's avatar
Guolin Ke committed
216
  GetMembersFromString(params);
217

Belinda Trotta's avatar
Belinda Trotta committed
218
219
  GetAucMuWeights();

220
221
  GetInteractionConstraints();

Guolin Ke's avatar
Guolin Ke committed
222
223
  // sort eval_at
  std::sort(eval_at.begin(), eval_at.end());
Guolin Ke's avatar
Guolin Ke committed
224

225
226
227
228
229
230
231
  std::vector<std::string> new_valid;
  for (size_t i = 0; i < valid.size(); ++i) {
    if (valid[i] != data) {
      // Only push the non-training data
      new_valid.push_back(valid[i]);
    } else {
      is_provide_training_metric = true;
232
233
    }
  }
234
  valid = new_valid;
235

236
237
238
239
240
  if ((task == TaskType::kSaveBinary) && !save_binary) {
    Log::Info("save_binary parameter set to true because task is save_binary");
    save_binary = true;
  }

Guolin Ke's avatar
Guolin Ke committed
241
  if (verbosity == 1) {
Guolin Ke's avatar
Guolin Ke committed
242
    LightGBM::Log::ResetLogLevel(LightGBM::LogLevel::Info);
Guolin Ke's avatar
Guolin Ke committed
243
  } else if (verbosity == 0) {
Guolin Ke's avatar
Guolin Ke committed
244
    LightGBM::Log::ResetLogLevel(LightGBM::LogLevel::Warning);
Guolin Ke's avatar
Guolin Ke committed
245
  } else if (verbosity >= 2) {
Guolin Ke's avatar
Guolin Ke committed
246
247
248
249
    LightGBM::Log::ResetLogLevel(LightGBM::LogLevel::Debug);
  } else {
    LightGBM::Log::ResetLogLevel(LightGBM::LogLevel::Fatal);
  }
250
251
252

  // check for conflicts
  CheckParamConflict();
Guolin Ke's avatar
Guolin Ke committed
253
254
}

Guolin Ke's avatar
Guolin Ke committed
255
bool CheckMultiClassObjective(const std::string& objective) {
Guolin Ke's avatar
Guolin Ke committed
256
  return (objective == std::string("multiclass") || objective == std::string("multiclassova"));
257
258
}

Guolin Ke's avatar
Guolin Ke committed
259
260
261
void Config::CheckParamConflict() {
  // check if objective, metric, and num_class match
  int num_class_check = num_class;
Guolin Ke's avatar
Guolin Ke committed
262
  bool objective_type_multiclass = CheckMultiClassObjective(objective) || (objective == std::string("custom") && num_class_check > 1);
263

264
  if (objective_type_multiclass) {
Guolin Ke's avatar
Guolin Ke committed
265
266
    if (num_class_check <= 1) {
      Log::Fatal("Number of classes should be specified and greater than 1 for multiclass training");
267
268
    }
  } else {
Guolin Ke's avatar
Guolin Ke committed
269
    if (task == TaskType::kTrain && num_class_check != 1) {
270
271
      Log::Fatal("Number of classes must be 1 for non-multiclass training");
    }
272
  }
Guolin Ke's avatar
Guolin Ke committed
273
  for (std::string metric_type : metric) {
274
275
276
    bool metric_type_multiclass = (CheckMultiClassObjective(metric_type)
                                   || metric_type == std::string("multi_logloss")
                                   || metric_type == std::string("multi_error")
Belinda Trotta's avatar
Belinda Trotta committed
277
                                   || metric_type == std::string("auc_mu")
Guolin Ke's avatar
Guolin Ke committed
278
                                   || (metric_type == std::string("custom") && num_class_check > 1));
Guolin Ke's avatar
Guolin Ke committed
279
    if ((objective_type_multiclass && !metric_type_multiclass)
280
281
        || (!objective_type_multiclass && metric_type_multiclass)) {
      Log::Fatal("Multiclass objective and metrics don't match");
282
    }
283
  }
284

Guolin Ke's avatar
Guolin Ke committed
285
  if (num_machines > 1) {
Guolin Ke's avatar
Guolin Ke committed
286
287
288
    is_parallel = true;
  } else {
    is_parallel = false;
Guolin Ke's avatar
Guolin Ke committed
289
    tree_learner = "serial";
Guolin Ke's avatar
Guolin Ke committed
290
291
  }

Guolin Ke's avatar
Guolin Ke committed
292
  bool is_single_tree_learner = tree_learner == std::string("serial");
Guolin Ke's avatar
Guolin Ke committed
293
294

  if (is_single_tree_learner) {
Guolin Ke's avatar
Guolin Ke committed
295
    is_parallel = false;
Guolin Ke's avatar
Guolin Ke committed
296
    num_machines = 1;
Guolin Ke's avatar
Guolin Ke committed
297
298
  }

Guolin Ke's avatar
Guolin Ke committed
299
  if (is_single_tree_learner || tree_learner == std::string("feature")) {
300
    is_data_based_parallel = false;
Guolin Ke's avatar
Guolin Ke committed
301
302
  } else if (tree_learner == std::string("data")
             || tree_learner == std::string("voting")) {
303
    is_data_based_parallel = true;
Guolin Ke's avatar
Guolin Ke committed
304
305
    if (histogram_pool_size >= 0
        && tree_learner == std::string("data")) {
306
307
      Log::Warning("Histogram LRU queue was enabled (histogram_pool_size=%f).\n"
                   "Will disable this to reduce communication costs",
Guolin Ke's avatar
Guolin Ke committed
308
                   histogram_pool_size);
tks's avatar
tks committed
309
      // Change pool size to -1 (no limit) when using data parallel to reduce communication costs
Guolin Ke's avatar
Guolin Ke committed
310
      histogram_pool_size = -1;
311
    }
Guolin Ke's avatar
Guolin Ke committed
312
  }
313
314
315
316
317
318
  if (is_data_based_parallel) {
    if (!forcedsplits_filename.empty()) {
      Log::Fatal("Don't support forcedsplits in %s tree learner",
                 tree_learner.c_str());
    }
  }
319
  // Check max_depth and num_leaves
Guolin Ke's avatar
Guolin Ke committed
320
  if (max_depth > 0) {
321
    double full_num_leaves = std::pow(2, max_depth);
322
    if (full_num_leaves > num_leaves
Guolin Ke's avatar
Guolin Ke committed
323
        && num_leaves == kDefaultNumLeaves) {
324
325
326
      Log::Warning("Accuracy may be bad since you didn't explicitly set num_leaves OR 2^max_depth > num_leaves."
                   " (num_leaves=%d).",
                   num_leaves);
327
    }
328
329
330
331
332

    if (full_num_leaves < num_leaves) {
      // Fits in an int, and is more restrictive than the current num_leaves
      num_leaves = static_cast<int>(full_num_leaves);
    }
333
  }
334
335
  // force col-wise for gpu & CUDA
  if (device_type == std::string("gpu") || device_type == std::string("cuda")) {
336
337
    force_col_wise = true;
    force_row_wise = false;
Guolin Ke's avatar
Guolin Ke committed
338
339
340
    if (deterministic) {
      Log::Warning("Although \"deterministic\" is set, the results ran by GPU may be non-deterministic.");
    }
341
  }
342
343
344
345
346
  // force gpu_use_dp for CUDA
  if (device_type == std::string("cuda") && !gpu_use_dp) {
    Log::Warning("CUDA currently requires double precision calculations.");
    gpu_use_dp = true;
  }
Andrew Ziem's avatar
Andrew Ziem committed
347
  // linear tree learner must be serial type and run on CPU device
348
  if (linear_tree) {
349
    if (device_type != std::string("cpu")) {
350
351
352
353
354
355
356
357
358
359
360
361
362
363
      device_type = "cpu";
      Log::Warning("Linear tree learner only works with CPU.");
    }
    if (tree_learner != std::string("serial")) {
      tree_learner = "serial";
      Log::Warning("Linear tree learner must be serial.");
    }
    if (zero_as_missing) {
      Log::Fatal("zero_as_missing must be false when fitting linear trees.");
    }
    if (objective == std::string("regresson_l1")) {
      Log::Fatal("Cannot use regression_l1 objective when fitting linear trees.");
    }
  }
Belinda Trotta's avatar
Belinda Trotta committed
364
365
366
367
368
369
370
371
  // min_data_in_leaf must be at least 2 if path smoothing is active. This is because when the split is calculated
  // the count is calculated using the proportion of hessian in the leaf which is rounded up to nearest int, so it can
  // be 1 when there is actually no data in the leaf. In rare cases this can cause a bug because with path smoothing the
  // calculated split gain can be positive even with zero gradient and hessian.
  if (path_smooth > kEpsilon && min_data_in_leaf < 2) {
    min_data_in_leaf = 2;
    Log::Warning("min_data_in_leaf has been increased to 2 because this is required when path smoothing is active.");
  }
372
  if (is_parallel && (monotone_constraints_method == std::string("intermediate") || monotone_constraints_method == std::string("advanced"))) {
373
    // In distributed mode, local node doesn't have histograms on all features, cannot perform "intermediate" monotone constraints.
374
    Log::Warning("Cannot use \"intermediate\" or \"advanced\" monotone constraints in distributed learning, auto set to \"basic\" method.");
375
376
    monotone_constraints_method = "basic";
  }
377
  if (feature_fraction_bynode != 1.0 && (monotone_constraints_method == std::string("intermediate") || monotone_constraints_method == std::string("advanced"))) {
378
379
    // "intermediate" monotone constraints need to recompute splits. If the features are sampled when computing the
    // split initially, then the sampling needs to be recorded or done once again, which is currently not supported
380
    Log::Warning("Cannot use \"intermediate\" or \"advanced\" monotone constraints with feature fraction different from 1, auto set monotone constraints to \"basic\" method.");
381
382
    monotone_constraints_method = "basic";
  }
383
384
385
  if (max_depth > 0 && monotone_penalty >= max_depth) {
    Log::Warning("Monotone penalty greater than tree depth. Monotone features won't be used.");
  }
386
387
388
389
390
391
  if (min_data_in_leaf <= 0 && min_sum_hessian_in_leaf <= kEpsilon) {
    Log::Warning(
        "Cannot set both min_data_in_leaf and min_sum_hessian_in_leaf to 0. "
        "Will set min_data_in_leaf to 1.");
    min_data_in_leaf = 1;
  }
Guolin Ke's avatar
Guolin Ke committed
392
393
}

Guolin Ke's avatar
Guolin Ke committed
394
395
396
397
398
399
400
401
402
std::string Config::ToString() const {
  std::stringstream str_buf;
  str_buf << "[boosting: " << boosting << "]\n";
  str_buf << "[objective: " << objective << "]\n";
  str_buf << "[metric: " << Common::Join(metric, ",") << "]\n";
  str_buf << "[tree_learner: " << tree_learner << "]\n";
  str_buf << "[device_type: " << device_type << "]\n";
  str_buf << SaveMembersToString();
  return str_buf.str();
Guolin Ke's avatar
Guolin Ke committed
403
404
405
}

}  // namespace LightGBM