config.cpp 16.1 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
  }
}

Guolin Ke's avatar
Guolin Ke committed
88
void GetMetricType(const std::unordered_map<std::string, std::string>& params, 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
96
  if (metric->empty() && value.size() == 0) {
    if (Config::GetString(params, "objective", &value)) {
97
      std::transform(value.begin(), value.end(), value.begin(), Common::tolower);
Guolin Ke's avatar
Guolin Ke committed
98
      ParseMetrics(value, metric);
99
100
    }
  }
Guolin Ke's avatar
Guolin Ke committed
101
102
}

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

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

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

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

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

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

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

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

223
224
  GetInteractionConstraints();

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

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

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

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

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

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

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

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

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

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

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

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

Guolin Ke's avatar
Guolin Ke committed
397
398
399
400
401
402
403
404
405
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
406
407
408
}

}  // namespace LightGBM