config.cpp 16.4 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";
131
132
    } else if (value == std::string("cuda_exp")) {
      *device_type = "cuda_exp";
Guolin Ke's avatar
Guolin Ke committed
133
134
135
136
137
138
    } else {
      Log::Fatal("Unknown device type %s", value.c_str());
    }
  }
}

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

Belinda Trotta's avatar
Belinda Trotta committed
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
183
184
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];
        }
      }
    }
  }
185
}
Belinda Trotta's avatar
Belinda Trotta committed
186

187
188
189
190
191
192
193
194
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
195
void Config::Set(const std::unordered_map<std::string, std::string>& params) {
Guolin Ke's avatar
Guolin Ke committed
196
197
198
  // generate seeds by seed.
  if (GetInt(params, "seed", &seed)) {
    Random rand(seed);
Guolin Ke's avatar
Guolin Ke committed
199
    int int_max = std::numeric_limits<int16_t>::max();
Guolin Ke's avatar
Guolin Ke committed
200
201
202
203
    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));
204
    objective_seed = static_cast<int>(rand.NextShort(0, int_max));
205
    extra_seed = static_cast<int>(rand.NextShort(0, int_max));
Guolin Ke's avatar
Guolin Ke committed
206
207
  }

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

Guolin Ke's avatar
Guolin Ke committed
218
  GetMembersFromString(params);
219

Belinda Trotta's avatar
Belinda Trotta committed
220
221
  GetAucMuWeights();

222
223
  GetInteractionConstraints();

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

227
228
229
230
231
232
233
  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;
234
235
    }
  }
236
  valid = new_valid;
237

238
239
240
241
242
  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
243
  if (verbosity == 1) {
Guolin Ke's avatar
Guolin Ke committed
244
    LightGBM::Log::ResetLogLevel(LightGBM::LogLevel::Info);
Guolin Ke's avatar
Guolin Ke committed
245
  } else if (verbosity == 0) {
Guolin Ke's avatar
Guolin Ke committed
246
    LightGBM::Log::ResetLogLevel(LightGBM::LogLevel::Warning);
Guolin Ke's avatar
Guolin Ke committed
247
  } else if (verbosity >= 2) {
Guolin Ke's avatar
Guolin Ke committed
248
249
250
251
    LightGBM::Log::ResetLogLevel(LightGBM::LogLevel::Debug);
  } else {
    LightGBM::Log::ResetLogLevel(LightGBM::LogLevel::Fatal);
  }
252
253
254

  // check for conflicts
  CheckParamConflict();
Guolin Ke's avatar
Guolin Ke committed
255
256
}

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

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

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

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

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

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

Guolin Ke's avatar
Guolin Ke committed
301
  if (is_single_tree_learner || tree_learner == std::string("feature")) {
302
    is_data_based_parallel = false;
Guolin Ke's avatar
Guolin Ke committed
303
304
  } else if (tree_learner == std::string("data")
             || tree_learner == std::string("voting")) {
305
    is_data_based_parallel = true;
Guolin Ke's avatar
Guolin Ke committed
306
307
    if (histogram_pool_size >= 0
        && tree_learner == std::string("data")) {
308
309
      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
310
                   histogram_pool_size);
tks's avatar
tks committed
311
      // Change pool size to -1 (no limit) when using data parallel to reduce communication costs
Guolin Ke's avatar
Guolin Ke committed
312
      histogram_pool_size = -1;
313
    }
Guolin Ke's avatar
Guolin Ke committed
314
  }
315
316
317
318
319
320
  if (is_data_based_parallel) {
    if (!forcedsplits_filename.empty()) {
      Log::Fatal("Don't support forcedsplits in %s tree learner",
                 tree_learner.c_str());
    }
  }
321
  // Check max_depth and num_leaves
Guolin Ke's avatar
Guolin Ke committed
322
  if (max_depth > 0) {
323
    double full_num_leaves = std::pow(2, max_depth);
324
    if (full_num_leaves > num_leaves
Guolin Ke's avatar
Guolin Ke committed
325
        && num_leaves == kDefaultNumLeaves) {
326
327
328
      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);
329
    }
330
331
332
333
334

    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);
    }
335
  }
336
  if (device_type == std::string("gpu") || device_type == std::string("cuda")) {
337
    // force col-wise for gpu, and cuda version
338
339
    force_col_wise = true;
    force_row_wise = false;
Guolin Ke's avatar
Guolin Ke committed
340
341
342
    if (deterministic) {
      Log::Warning("Although \"deterministic\" is set, the results ran by GPU may be non-deterministic.");
    }
343
344
345
346
347
348
349
  } else if (device_type == std::string("cuda_exp")) {
    // force row-wise for cuda_exp version
    force_col_wise = false;
    force_row_wise = true;
    if (deterministic) {
      Log::Warning("Although \"deterministic\" is set, the results ran by GPU may be non-deterministic.");
    }
350
  }
351
352
353
354
355
  // 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
356
  // linear tree learner must be serial type and run on CPU device
357
  if (linear_tree) {
358
    if (device_type != std::string("cpu")) {
359
360
361
362
363
364
365
366
367
368
369
370
371
372
      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
373
374
375
376
377
378
379
380
  // 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.");
  }
381
  if (is_parallel && (monotone_constraints_method == std::string("intermediate") || monotone_constraints_method == std::string("advanced"))) {
382
    // In distributed mode, local node doesn't have histograms on all features, cannot perform "intermediate" monotone constraints.
383
    Log::Warning("Cannot use \"intermediate\" or \"advanced\" monotone constraints in distributed learning, auto set to \"basic\" method.");
384
385
    monotone_constraints_method = "basic";
  }
386
  if (feature_fraction_bynode != 1.0 && (monotone_constraints_method == std::string("intermediate") || monotone_constraints_method == std::string("advanced"))) {
387
388
    // "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
389
    Log::Warning("Cannot use \"intermediate\" or \"advanced\" monotone constraints with feature fraction different from 1, auto set monotone constraints to \"basic\" method.");
390
391
    monotone_constraints_method = "basic";
  }
392
393
394
  if (max_depth > 0 && monotone_penalty >= max_depth) {
    Log::Warning("Monotone penalty greater than tree depth. Monotone features won't be used.");
  }
395
396
397
398
399
400
  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
401
402
}

Guolin Ke's avatar
Guolin Ke committed
403
404
405
406
407
408
409
410
411
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
412
413
414
}

}  // namespace LightGBM