config.cpp 15.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;
Guolin Ke's avatar
Guolin Ke committed
116
    } else {
117
      Log::Fatal("Unknown task type %s", value.c_str());
Guolin Ke's avatar
Guolin Ke committed
118
119
120
121
    }
  }
}

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

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

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

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

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

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

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

221
222
  GetInteractionConstraints();

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

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

Guolin Ke's avatar
Guolin Ke committed
237
238
  // check for conflicts
  CheckParamConflict();
239

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

Guolin Ke's avatar
Guolin Ke committed
251
bool CheckMultiClassObjective(const std::string& objective) {
Guolin Ke's avatar
Guolin Ke committed
252
  return (objective == std::string("multiclass") || objective == std::string("multiclassova"));
253
254
}

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

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

Guolin Ke's avatar
Guolin Ke committed
281
  if (num_machines > 1) {
Guolin Ke's avatar
Guolin Ke committed
282
283
284
    is_parallel = true;
  } else {
    is_parallel = false;
Guolin Ke's avatar
Guolin Ke committed
285
    tree_learner = "serial";
Guolin Ke's avatar
Guolin Ke committed
286
287
  }

Guolin Ke's avatar
Guolin Ke committed
288
  bool is_single_tree_learner = tree_learner == std::string("serial");
Guolin Ke's avatar
Guolin Ke committed
289
290

  if (is_single_tree_learner) {
Guolin Ke's avatar
Guolin Ke committed
291
    is_parallel = false;
Guolin Ke's avatar
Guolin Ke committed
292
    num_machines = 1;
Guolin Ke's avatar
Guolin Ke committed
293
294
  }

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

    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);
    }
327
  }
328
329
  // force col-wise for gpu & CUDA
  if (device_type == std::string("gpu") || device_type == std::string("cuda")) {
330
331
    force_col_wise = true;
    force_row_wise = false;
Guolin Ke's avatar
Guolin Ke committed
332
333
334
    if (deterministic) {
      Log::Warning("Although \"deterministic\" is set, the results ran by GPU may be non-deterministic.");
    }
335
  }
336
337
338
339
340
341
342

  // 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;
  }

Belinda Trotta's avatar
Belinda Trotta committed
343
344
345
346
347
348
349
350
  // 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.");
  }
351
  if (is_parallel && (monotone_constraints_method == std::string("intermediate") || monotone_constraints_method == std::string("advanced"))) {
352
    // In distributed mode, local node doesn't have histograms on all features, cannot perform "intermediate" monotone constraints.
353
    Log::Warning("Cannot use \"intermediate\" or \"advanced\" monotone constraints in parallel learning, auto set to \"basic\" method.");
354
355
    monotone_constraints_method = "basic";
  }
356
  if (feature_fraction_bynode != 1.0 && (monotone_constraints_method == std::string("intermediate") || monotone_constraints_method == std::string("advanced"))) {
357
358
    // "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
359
    Log::Warning("Cannot use \"intermediate\" or \"advanced\" monotone constraints with feature fraction different from 1, auto set monotone constraints to \"basic\" method.");
360
361
    monotone_constraints_method = "basic";
  }
362
363
364
  if (max_depth > 0 && monotone_penalty >= max_depth) {
    Log::Warning("Monotone penalty greater than tree depth. Monotone features won't be used.");
  }
365
366
367
368
369
370
  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
371
372
}

Guolin Ke's avatar
Guolin Ke committed
373
374
375
376
377
378
379
380
381
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
382
383
384
}

}  // namespace LightGBM