From bc0ea4aa10b9c8789cae81fc54f1d18b23bb8432 Mon Sep 17 00:00:00 2001 From: Tsukasa OMOTO Date: Tue, 18 Dec 2018 01:42:10 +0900 Subject: [PATCH 01/19] Divide metric into train_metric and valid_metric Close #1911 --- include/LightGBM/c_api.h | 4 +-- include/LightGBM/config.h | 3 +- python-package/lightgbm/basic.py | 53 +++++++++++++++----------------- src/application/application.cpp | 6 ++-- src/c_api.cpp | 50 +++++++++++++++++++++--------- src/io/config.cpp | 50 +++++++++++++++++++++++++++--- src/io/config_auto.cpp | 4 +-- src/lightgbm_R.cpp | 9 +++--- 8 files changed, 119 insertions(+), 60 deletions(-) diff --git a/include/LightGBM/c_api.h b/include/LightGBM/c_api.h index 4d50788efea0..3d4825078897 100644 --- a/include/LightGBM/c_api.h +++ b/include/LightGBM/c_api.h @@ -484,7 +484,7 @@ LIGHTGBM_C_EXPORT int LGBM_BoosterNumberOfTotalModel(BoosterHandle handle, int* * \param out_len total number of eval results * \return 0 when succeed, -1 when failure happens */ -LIGHTGBM_C_EXPORT int LGBM_BoosterGetEvalCounts(BoosterHandle handle, int* out_len); +LIGHTGBM_C_EXPORT int LGBM_BoosterGetEvalCounts(BoosterHandle handle, int data_idx, int* out_len); /*! * \brief Get name of eval @@ -492,7 +492,7 @@ LIGHTGBM_C_EXPORT int LGBM_BoosterGetEvalCounts(BoosterHandle handle, int* out_l * \param out_strs names of eval result, need to pre-allocate memory before call this * \return 0 when succeed, -1 when failure happens */ -LIGHTGBM_C_EXPORT int LGBM_BoosterGetEvalNames(BoosterHandle handle, int* out_len, char** out_strs); +LIGHTGBM_C_EXPORT int LGBM_BoosterGetEvalNames(BoosterHandle handle, int data_idx, int* out_len, char** out_strs); /*! * \brief Get name of features diff --git a/include/LightGBM/config.h b/include/LightGBM/config.h index 7f29b0595395..b73a691827fb 100644 --- a/include/LightGBM/config.h +++ b/include/LightGBM/config.h @@ -697,7 +697,8 @@ struct Config { // descl2 = ``xentlambda``, "intensity-weighted" cross-entropy, aliases: ``cross_entropy_lambda`` // descl2 = ``kldiv``, `Kullback-Leibler divergence `__, aliases: ``kullback_leibler`` // desc = support multiple metrics, separated by ``,`` - std::vector metric; + std::vector train_metric; + std::vector valid_metric; // check = >0 // alias = output_freq diff --git a/python-package/lightgbm/basic.py b/python-package/lightgbm/basic.py index b347f73ce41a..d6fd7edb172c 100644 --- a/python-package/lightgbm/basic.py +++ b/python-package/lightgbm/basic.py @@ -1472,7 +1472,6 @@ def __init__(self, params=None, train_set=None, model_file=None, silent=False): """ self.handle = None self.network = False - self.__need_reload_eval_info = True self.__train_data_name = "training" self.__attr = {} self.__set_objective_to_none = False @@ -1528,7 +1527,7 @@ def __init__(self, params=None, train_set=None, model_file=None, silent=False): # buffer for inner predict self.__inner_predict_buffer = [None] self.__is_predicted_cur_iter = [False] - self.__get_eval_info() + self.__get_eval_info(0) # init by train data (data_idx=0) self.pandas_categorical = train_set.pandas_categorical elif model_file is not None: # Prediction task @@ -1708,8 +1707,6 @@ def reset_parameter(self, params): self : Booster Booster with new parameters. """ - if any(metric_alias in params for metric_alias in ('metric', 'metrics', 'metric_types')): - self.__need_reload_eval_info = True params_str = param_dict_to_str(params) if params_str: _safe_call(_LIB.LGBM_BoosterResetParameter( @@ -2310,7 +2307,7 @@ def __inner_eval(self, data_name, data_idx, feval=None): """Evaluate training or validation data.""" if data_idx >= self.__num_dataset: raise ValueError("Data_idx should be smaller than number of dataset") - self.__get_eval_info() + self.__get_eval_info(data_idx) ret = [] if self.__num_inner_eval > 0: result = np.zeros(self.__num_inner_eval, dtype=np.float64) @@ -2363,31 +2360,31 @@ def __inner_predict(self, data_idx): self.__is_predicted_cur_iter[data_idx] = True return self.__inner_predict_buffer[data_idx] - def __get_eval_info(self): + def __get_eval_info(self, data_idx): """Get inner evaluation count and names.""" - if self.__need_reload_eval_info: - self.__need_reload_eval_info = False - out_num_eval = ctypes.c_int(0) - # Get num of inner evals - _safe_call(_LIB.LGBM_BoosterGetEvalCounts( + out_num_eval = ctypes.c_int(0) + # Get num of inner evals + _safe_call(_LIB.LGBM_BoosterGetEvalCounts( + self.handle, + ctypes.c_int(data_idx), + ctypes.byref(out_num_eval))) + self.__num_inner_eval = out_num_eval.value + if self.__num_inner_eval > 0: + # Get name of evals + tmp_out_len = ctypes.c_int(0) + string_buffers = [ctypes.create_string_buffer(255) for i in range_(self.__num_inner_eval)] + ptr_string_buffers = (ctypes.c_char_p * self.__num_inner_eval)(*map(ctypes.addressof, string_buffers)) + _safe_call(_LIB.LGBM_BoosterGetEvalNames( self.handle, - ctypes.byref(out_num_eval))) - self.__num_inner_eval = out_num_eval.value - if self.__num_inner_eval > 0: - # Get name of evals - tmp_out_len = ctypes.c_int(0) - string_buffers = [ctypes.create_string_buffer(255) for i in range_(self.__num_inner_eval)] - ptr_string_buffers = (ctypes.c_char_p * self.__num_inner_eval)(*map(ctypes.addressof, string_buffers)) - _safe_call(_LIB.LGBM_BoosterGetEvalNames( - self.handle, - ctypes.byref(tmp_out_len), - ptr_string_buffers)) - if self.__num_inner_eval != tmp_out_len.value: - raise ValueError("Length of eval names doesn't equal with num_evals") - self.__name_inner_eval = \ - [string_buffers[i].value.decode() for i in range_(self.__num_inner_eval)] - self.__higher_better_inner_eval = \ - [name.startswith(('auc', 'ndcg@', 'map@')) for name in self.__name_inner_eval] + ctypes.c_int(data_idx), + ctypes.byref(tmp_out_len), + ptr_string_buffers)) + if self.__num_inner_eval != tmp_out_len.value: + raise ValueError("Length of eval names doesn't equal with num_evals") + self.__name_inner_eval = \ + [string_buffers[i].value.decode() for i in range_(self.__num_inner_eval)] + self.__higher_better_inner_eval = \ + [name.startswith(('auc', 'ndcg@', 'map@')) for name in self.__name_inner_eval] def attr(self, key): """Get attribute string from the Booster. diff --git a/src/application/application.cpp b/src/application/application.cpp index 3fbc7d3a0d91..15363ee70813 100644 --- a/src/application/application.cpp +++ b/src/application/application.cpp @@ -116,7 +116,7 @@ void Application::LoadData() { } // create training metric if (config_.is_provide_training_metric) { - for (auto metric_type : config_.metric) { + for (auto metric_type : config_.train_metric) { auto metric = std::unique_ptr(Metric::CreateMetric(metric_type, config_)); if (metric == nullptr) { continue; } metric->Init(train_data_->metadata(), train_data_->num_data()); @@ -126,7 +126,7 @@ void Application::LoadData() { train_metric_.shrink_to_fit(); - if (!config_.metric.empty()) { + if (!config_.train_metric.empty()) { // only when have metrics then need to construct validation data // Add validation data, if it exists @@ -146,7 +146,7 @@ void Application::LoadData() { // add metric for validation data valid_metrics_.emplace_back(); - for (auto metric_type : config_.metric) { + for (auto metric_type : config_.valid_metric) { auto metric = std::unique_ptr(Metric::CreateMetric(metric_type, config_)); if (metric == nullptr) { continue; } metric->Init(valid_datas_.back()->metadata(), diff --git a/src/c_api.cpp b/src/c_api.cpp index 3a8fbe86079e..4ddc510aed98 100644 --- a/src/c_api.cpp +++ b/src/c_api.cpp @@ -103,7 +103,7 @@ class Booster { // create training metric train_metric_.clear(); - for (auto metric_type : config_.metric) { + for (auto metric_type : config_.train_metric) { auto metric = std::unique_ptr( Metric::CreateMetric(metric_type, config_)); if (metric == nullptr) { continue; } @@ -164,7 +164,7 @@ class Booster { void AddValidData(const Dataset* valid_data) { std::lock_guard lock(mutex_); valid_metrics_.emplace_back(); - for (auto metric_type : config_.metric) { + for (auto metric_type : config_.valid_metric) { auto metric = std::unique_ptr(Metric::CreateMetric(metric_type, config_)); if (metric == nullptr) { continue; } metric->Init(valid_data->metadata(), valid_data->num_data()); @@ -297,21 +297,41 @@ class Booster { boosting_->ShuffleModels(start_iter, end_iter); } - int GetEvalCounts() const { + int GetEvalCounts(int data_idx) const { int ret = 0; - for (const auto& metric : train_metric_) { - ret += static_cast(metric->GetName().size()); + if (data_idx == 0) { + for (const auto& metric : train_metric_) { + ret += static_cast(metric->GetName().size()); + } + } else { + for (size_t i = 0; i < valid_metrics_.size(); ++i) { + for (size_t j = 0; j < valid_metrics_[i].size(); ++j) { + ret += static_cast(valid_metrics_[i][j]->GetName().size()); + } + } } return ret; } - int GetEvalNames(char** out_strs) const { + int GetEvalNames(int data_idx, char** out_strs) const { int idx = 0; - for (const auto& metric : train_metric_) { - for (const auto& name : metric->GetName()) { - std::memcpy(out_strs[idx], name.c_str(), name.size() + 1); - ++idx; - } + if (data_idx == 0) { + for (const auto& metric : train_metric_) { + for (const auto& name : metric->GetName()) { + std::memcpy(out_strs[idx], name.c_str(), name.size() + 1); + ++idx; + } + } + } else { + for (size_t i = 0; i < valid_metrics_.size(); ++i) { + for (size_t j = 0; j < valid_metrics_[i].size(); ++j) { + auto& metric = valid_metrics_[i][j]; + for (const auto& name : metric->GetName()) { + std::memcpy(out_strs[idx], name.c_str(), name.size() + 1); + ++idx; + } + } + } } return idx; } @@ -1029,17 +1049,17 @@ int LGBM_BoosterNumberOfTotalModel(BoosterHandle handle, int* out_models) { API_END(); } -int LGBM_BoosterGetEvalCounts(BoosterHandle handle, int* out_len) { +int LGBM_BoosterGetEvalCounts(BoosterHandle handle, int data_idx, int* out_len) { API_BEGIN(); Booster* ref_booster = reinterpret_cast(handle); - *out_len = ref_booster->GetEvalCounts(); + *out_len = ref_booster->GetEvalCounts(data_idx); API_END(); } -int LGBM_BoosterGetEvalNames(BoosterHandle handle, int* out_len, char** out_strs) { +int LGBM_BoosterGetEvalNames(BoosterHandle handle, int data_idx, int* out_len, char** out_strs) { API_BEGIN(); Booster* ref_booster = reinterpret_cast(handle); - *out_len = ref_booster->GetEvalNames(out_strs); + *out_len = ref_booster->GetEvalNames(data_idx, out_strs); API_END(); } diff --git a/src/io/config.cpp b/src/io/config.cpp index 414ecafcfd79..f67a4d49a854 100644 --- a/src/io/config.cpp +++ b/src/io/config.cpp @@ -68,9 +68,9 @@ void GetObjectiveType(const std::unordered_map& params } } -void GetMetricType(const std::unordered_map& params, std::vector* metric) { +void GetTrainMetricType(const std::unordered_map& params, std::vector* metric) { std::string value; - if (Config::GetString(params, "metric", &value)) { + if (Config::GetString(params, "train_metric", &value)) { // clear old metrics metric->clear(); // to lower @@ -99,6 +99,31 @@ void GetMetricType(const std::unordered_map& params, s } } +void GetValidMetricType(const std::unordered_map& params, std::vector* metric) { + std::string value; + if (Config::GetString(params, "valid_metric", &value)) { + // clear old metrics + metric->clear(); + // to lower + std::transform(value.begin(), value.end(), value.begin(), Common::tolower); + // split + std::vector metrics = Common::Split(value.c_str(), ','); + // remove duplicate + std::unordered_set metric_sets; + for (auto& met : metrics) { + std::transform(met.begin(), met.end(), met.begin(), Common::tolower); + if (metric_sets.count(met) <= 0) { + metric_sets.insert(met); + } + } + for (auto& met : metric_sets) { + metric->push_back(met); + } + metric->shrink_to_fit(); + } +} + + void GetTaskType(const std::unordered_map& params, TaskType* task) { std::string value; if (Config::GetString(params, "task", &value)) { @@ -164,7 +189,8 @@ void Config::Set(const std::unordered_map& params) { GetTaskType(params, &task); GetBoostingType(params, &boosting); - GetMetricType(params, &metric); + GetTrainMetricType(params, &train_metric); + GetValidMetricType(params, &valid_metric); GetObjectiveType(params, &objective); GetDeviceType(params, &device_type); GetTreeLearnerType(params, &tree_learner); @@ -215,7 +241,7 @@ void Config::CheckParamConflict() { Log::Fatal("Number of classes must be 1 for non-multiclass training"); } } - for (std::string metric_type : metric) { + for (std::string metric_type : train_metric) { bool metric_custom_or_none = metric_type == std::string("none") || metric_type == std::string("null") || metric_type == std::string("custom") || metric_type == std::string("na"); bool metric_type_multiclass = (CheckMultiClassObjective(metric_type) @@ -227,6 +253,19 @@ void Config::CheckParamConflict() { Log::Fatal("Multiclass objective and metrics don't match"); } } + for (std::string metric_type : valid_metric) { + bool metric_custom_or_none = metric_type == std::string("none") || metric_type == std::string("null") + || metric_type == std::string("custom") || metric_type == std::string("na"); + bool metric_type_multiclass = (CheckMultiClassObjective(metric_type) + || metric_type == std::string("multi_logloss") + || metric_type == std::string("multi_error") + || (metric_custom_or_none && num_class_check > 1)); + if ((objective_type_multiclass && !metric_type_multiclass) + || (!objective_type_multiclass && metric_type_multiclass)) { + Log::Fatal("Multiclass objective and metrics don't match"); + } + } + if (num_machines > 1) { is_parallel = true; @@ -270,7 +309,8 @@ 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 << "[train_metric: " << Common::Join(train_metric, ",") << "]\n"; + str_buf << "[valid_metric: " << Common::Join(valid_metric, ",") << "]\n"; str_buf << "[tree_learner: " << tree_learner << "]\n"; str_buf << "[device_type: " << device_type << "]\n"; str_buf << SaveMembersToString(); diff --git a/src/io/config_auto.cpp b/src/io/config_auto.cpp index 3803d986da5e..809eed4e1b39 100644 --- a/src/io/config_auto.cpp +++ b/src/io/config_auto.cpp @@ -140,7 +140,6 @@ std::unordered_map Config::alias_table({ {"output_freq", "metric_freq"}, {"training_metric", "is_provide_training_metric"}, {"is_training_metric", "is_provide_training_metric"}, - {"train_metric", "is_provide_training_metric"}, {"ndcg_eval_at", "eval_at"}, {"ndcg_at", "eval_at"}, {"map_eval_at", "eval_at"}, @@ -249,7 +248,8 @@ std::unordered_set Config::parameter_set({ "tweedie_variance_power", "max_position", "label_gain", - "metric", + "train_metric", + "valid_metric", "metric_freq", "is_provide_training_metric", "eval_at", diff --git a/src/lightgbm_R.cpp b/src/lightgbm_R.cpp index b140cae0ff04..6a715bcfe0d4 100644 --- a/src/lightgbm_R.cpp +++ b/src/lightgbm_R.cpp @@ -426,11 +426,12 @@ LGBM_SE LGBM_BoosterGetEvalNames_R(LGBM_SE handle, LGBM_SE buf_len, LGBM_SE actual_len, LGBM_SE eval_names, - LGBM_SE call_state) { + LGBM_SE call_state, + LGBM_SE data_idx) { R_API_BEGIN(); int len; - CHECK_CALL(LGBM_BoosterGetEvalCounts(R_GET_PTR(handle), &len)); + CHECK_CALL(LGBM_BoosterGetEvalCounts(R_GET_PTR(handle), R_AS_INT(data_idx), &len)); std::vector> names(len); std::vector ptr_names(len); for (int i = 0; i < len; ++i) { @@ -438,7 +439,7 @@ LGBM_SE LGBM_BoosterGetEvalNames_R(LGBM_SE handle, ptr_names[i] = names[i].data(); } int out_len; - CHECK_CALL(LGBM_BoosterGetEvalNames(R_GET_PTR(handle), &out_len, ptr_names.data())); + CHECK_CALL(LGBM_BoosterGetEvalNames(R_GET_PTR(handle), R_AS_INT(data_idx), &out_len, ptr_names.data())); CHECK(out_len == len); auto merge_names = Common::Join(ptr_names, "\t"); EncodeChar(eval_names, merge_names.c_str(), buf_len, actual_len, merge_names.size() + 1); @@ -451,7 +452,7 @@ LGBM_SE LGBM_BoosterGetEval_R(LGBM_SE handle, LGBM_SE call_state) { R_API_BEGIN(); int len; - CHECK_CALL(LGBM_BoosterGetEvalCounts(R_GET_PTR(handle), &len)); + CHECK_CALL(LGBM_BoosterGetEvalCounts(R_GET_PTR(handle), R_AS_INT(data_idx), &len)); double* ptr_ret = R_REAL_PTR(out_result); int out_len; CHECK_CALL(LGBM_BoosterGetEval(R_GET_PTR(handle), R_AS_INT(data_idx), &out_len, ptr_ret)); From 7ce4d594def4474c6b3a4f1de62152aa5aa73589 Mon Sep 17 00:00:00 2001 From: Tsukasa OMOTO Date: Tue, 18 Dec 2018 17:24:24 +0900 Subject: [PATCH 02/19] fix sklearn API --- python-package/lightgbm/sklearn.py | 3 ++- tests/python_package_test/test_plotting.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/python-package/lightgbm/sklearn.py b/python-package/lightgbm/sklearn.py index 821feebcbc28..5db0bdb82b54 100644 --- a/python-package/lightgbm/sklearn.py +++ b/python-package/lightgbm/sklearn.py @@ -477,7 +477,7 @@ def fit(self, X, y, # concatenate metric from params (or default if not provided in params) and eval_metric original_metric = [original_metric] if isinstance(original_metric, (string_type, type(None))) else original_metric eval_metric = [eval_metric] if isinstance(eval_metric, (string_type, type(None))) else eval_metric - params['metric'] = set(original_metric + eval_metric) + params['valid_metric'] = set(original_metric + eval_metric) # TODO: train_metric if not isinstance(X, DataFrame): X, y = _LGBMCheckXY(X, y, accept_sparse=True, force_all_finite=False, ensure_min_samples=2) @@ -518,6 +518,7 @@ def _get_meta_data(collection, i): # reduce cost for prediction training data if valid_data[0] is X and valid_data[1] is y: valid_set = train_set + # params['train_metric'] = set(original_metric + eval_metric) else: valid_weight = _get_meta_data(eval_sample_weight, i) if _get_meta_data(eval_class_weight, i) is not None: diff --git a/tests/python_package_test/test_plotting.py b/tests/python_package_test/test_plotting.py index 05a25e64d4a2..6ee332355ae6 100644 --- a/tests/python_package_test/test_plotting.py +++ b/tests/python_package_test/test_plotting.py @@ -104,7 +104,10 @@ def test_create_tree_digraph(self): @unittest.skipIf(not MATPLOTLIB_INSTALLED, 'matplotlib is not installed') def test_plot_metrics(self): test_data = lgb.Dataset(self.X_test, self.y_test, reference=self.train_data) - self.params.update({"metric": {"binary_logloss", "binary_error"}}) + self.params.update({ + "train_metric": ["binary_logloss", "binary_error"], + "valid_metric": ["binary_logloss", "binary_error"] + }) evals_result0 = {} gbm0 = lgb.train(self.params, self.train_data, From e1f2bba50bb1737964e3235737d8a0595e49287f Mon Sep 17 00:00:00 2001 From: Tsukasa OMOTO Date: Tue, 18 Dec 2018 18:09:43 +0900 Subject: [PATCH 03/19] fix tests --- python-package/lightgbm/engine.py | 2 +- src/io/config.cpp | 7 ++++++ tests/python_package_test/test_engine.py | 32 ++++++++++++------------ 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/python-package/lightgbm/engine.py b/python-package/lightgbm/engine.py index da7459fced71..da4e42069a29 100644 --- a/python-package/lightgbm/engine.py +++ b/python-package/lightgbm/engine.py @@ -446,7 +446,7 @@ def cv(params, train_set, num_boost_round=100, .set_categorical_feature(categorical_feature) if metrics is not None: - params['metric'] = metrics + params['valid_metric'] = metrics results = collections.defaultdict(list) cvfolds = _make_n_folds(train_set, folds=folds, nfold=nfold, diff --git a/src/io/config.cpp b/src/io/config.cpp index f67a4d49a854..4ee8f1b0a54f 100644 --- a/src/io/config.cpp +++ b/src/io/config.cpp @@ -121,6 +121,13 @@ void GetValidMetricType(const std::unordered_map& para } metric->shrink_to_fit(); } + // add names of objective function if not providing metric + if (metric->empty() && value.size() == 0) { + if (Config::GetString(params, "objective", &value)) { + std::transform(value.begin(), value.end(), value.begin(), Common::tolower); + metric->push_back(value); + } + } } diff --git a/tests/python_package_test/test_engine.py b/tests/python_package_test/test_engine.py index e89b59661f9f..fc73169c5b66 100644 --- a/tests/python_package_test/test_engine.py +++ b/tests/python_package_test/test_engine.py @@ -30,7 +30,7 @@ def test_binary(self): X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) params = { 'objective': 'binary', - 'metric': 'binary_logloss', + 'valid_metric': 'binary_logloss', 'verbose': -1, 'num_iteration': 50 # test num_iteration in dict here } @@ -57,7 +57,7 @@ def test_rf(self): 'bagging_fraction': 0.5, 'feature_fraction': 0.5, 'num_leaves': 50, - 'metric': 'binary_logloss', + 'valid_metric': 'binary_logloss', 'verbose': -1 } lgb_train = lgb.Dataset(X_train, y_train) @@ -76,7 +76,7 @@ def test_regression(self): X, y = load_boston(True) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) params = { - 'metric': 'l2', + 'valid_metric': 'l2', 'verbose': -1 } lgb_train = lgb.Dataset(X_train, y_train) @@ -102,7 +102,7 @@ def test_missing_value_handle(self): lgb_eval = lgb.Dataset(X_train, y_train) params = { - 'metric': 'l2', + 'valid_metric': 'l2', 'verbose': -1, 'boost_from_average': False } @@ -127,7 +127,7 @@ def test_missing_value_handle_na(self): params = { 'objective': 'regression', - 'metric': 'auc', + 'valid_metric': 'auc', 'verbose': -1, 'boost_from_average': False, 'min_data': 1, @@ -159,7 +159,7 @@ def test_missing_value_handle_zero(self): params = { 'objective': 'regression', - 'metric': 'auc', + 'valid_metric': 'auc', 'verbose': -1, 'boost_from_average': False, 'min_data': 1, @@ -191,7 +191,7 @@ def test_missing_value_handle_none(self): params = { 'objective': 'regression', - 'metric': 'auc', + 'valid_metric': 'auc', 'verbose': -1, 'boost_from_average': False, 'min_data': 1, @@ -224,7 +224,7 @@ def test_categorical_handle(self): params = { 'objective': 'regression', - 'metric': 'auc', + 'valid_metric': 'auc', 'verbose': -1, 'boost_from_average': False, 'min_data': 1, @@ -261,7 +261,7 @@ def test_categorical_handle_na(self): params = { 'objective': 'regression', - 'metric': 'auc', + 'valid_metric': 'auc', 'verbose': -1, 'boost_from_average': False, 'min_data': 1, @@ -292,7 +292,7 @@ def test_multiclass(self): X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) params = { 'objective': 'multiclass', - 'metric': 'multi_logloss', + 'valid_metric': 'multi_logloss', 'num_class': 10, 'verbose': -1 } @@ -314,7 +314,7 @@ def test_multiclass_rf(self): params = { 'boosting_type': 'rf', 'objective': 'multiclass', - 'metric': 'multi_logloss', + 'valid_metric': 'multi_logloss', 'bagging_freq': 1, 'bagging_fraction': 0.6, 'feature_fraction': 0.6, @@ -365,7 +365,7 @@ def test_early_stopping(self): X, y = load_breast_cancer(True) params = { 'objective': 'binary', - 'metric': 'binary_logloss', + 'valid_metric': 'binary_logloss', 'verbose': -1 } X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) @@ -397,7 +397,7 @@ def test_continue_train(self): X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) params = { 'objective': 'regression', - 'metric': 'l1', + 'valid_metric': 'l1', 'verbose': -1 } lgb_train = lgb.Dataset(X_train, y_train, free_raw_data=False) @@ -426,7 +426,7 @@ def test_continue_train_multiclass(self): X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) params = { 'objective': 'multiclass', - 'metric': 'multi_logloss', + 'valid_metric': 'multi_logloss', 'num_class': 3, 'verbose': -1 } @@ -450,7 +450,7 @@ def test_cv(self): params = {'verbose': -1} lgb_train = lgb.Dataset(X_train, y_train) # shuffle = False, override metric in params - params_with_metric = {'metric': 'l2', 'verbose': -1} + params_with_metric = {'valid_metric': 'l2', 'verbose': -1} cv_res = lgb.cv(params_with_metric, lgb_train, num_boost_round=10, nfold=3, stratified=False, shuffle=False, metrics='l1', verbose_eval=False) @@ -595,7 +595,7 @@ def test_reference_chain(self): # take subsets and train tmp_dat_train = tmp_dat.subset(np.arange(80)) tmp_dat_val = tmp_dat.subset(np.arange(80, 100)).subset(np.arange(18)) - params = {'objective': 'regression_l2', 'metric': 'rmse'} + params = {'objective': 'regression_l2', 'train_metric': 'rmse', 'valid_metric': 'rmse'} evals_result = {} gbm = lgb.train(params, tmp_dat_train, num_boost_round=20, valid_sets=[tmp_dat_train, tmp_dat_val], evals_result=evals_result) From ab23d73b22e4eaa8dc64ae5ec7e08b7b18860c2a Mon Sep 17 00:00:00 2001 From: Tsukasa OMOTO Date: Tue, 18 Dec 2018 18:37:26 +0900 Subject: [PATCH 04/19] fix examples --- examples/python-guide/advanced_example.py | 2 +- examples/python-guide/plot_example.py | 3 ++- examples/python-guide/simple_example.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/python-guide/advanced_example.py b/examples/python-guide/advanced_example.py index cef8ad6c944a..865b1307249d 100644 --- a/examples/python-guide/advanced_example.py +++ b/examples/python-guide/advanced_example.py @@ -36,7 +36,7 @@ params = { 'boosting_type': 'gbdt', 'objective': 'binary', - 'metric': 'binary_logloss', + 'train_metric': 'binary_logloss', 'num_leaves': 31, 'learning_rate': 0.05, 'feature_fraction': 0.9, diff --git a/examples/python-guide/plot_example.py b/examples/python-guide/plot_example.py index eca896982fa9..e935930aa95b 100644 --- a/examples/python-guide/plot_example.py +++ b/examples/python-guide/plot_example.py @@ -25,7 +25,8 @@ # specify your configurations as a dict params = { 'num_leaves': 5, - 'metric': ('l1', 'l2'), + 'train_metric': ('l1', 'l2'), + 'valid_metric': ('l1', 'l2'), 'verbose': 0 } diff --git a/examples/python-guide/simple_example.py b/examples/python-guide/simple_example.py index 18f07cd601cd..403956d8642a 100644 --- a/examples/python-guide/simple_example.py +++ b/examples/python-guide/simple_example.py @@ -22,7 +22,7 @@ params = { 'boosting_type': 'gbdt', 'objective': 'regression', - 'metric': {'l2', 'l1'}, + 'valid_metric': {'l2', 'l1'}, 'num_leaves': 31, 'learning_rate': 0.05, 'feature_fraction': 0.9, From 9327ffb1a62dcddc343d771608c294f63aa37ca5 Mon Sep 17 00:00:00 2001 From: Tsukasa OMOTO Date: Tue, 18 Dec 2018 18:54:10 +0900 Subject: [PATCH 05/19] fix notebook --- .../notebooks/interactive_plot_example.ipynb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/python-guide/notebooks/interactive_plot_example.ipynb b/examples/python-guide/notebooks/interactive_plot_example.ipynb index e472404d9aa4..f86377a52950 100644 --- a/examples/python-guide/notebooks/interactive_plot_example.ipynb +++ b/examples/python-guide/notebooks/interactive_plot_example.ipynb @@ -103,7 +103,8 @@ "source": [ "params = {\n", " 'num_leaves': 5,\n", - " 'metric': ['l1', 'l2'],\n", + " 'train_metric': ['l1', 'l2'],\n", + " 'valid_metric': ['l1', 'l2'],\n", " 'verbose': -1\n", "}" ] @@ -197,9 +198,9 @@ "source": [ "if INTERACTIVE:\n", " # create widget to switch between metrics\n", - " interact(render_metric, metric_name=params['metric'])\n", + " interact(render_metric, metric_name=params['valid_metric'])\n", "else:\n", - " render_metric(params['metric'][0])" + " render_metric(params['valid_metric'][0])" ] }, { @@ -336,7 +337,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.3" + "version": "3.6.6" }, "varInspector": { "cols": { From b0e7a5ca6fb1097a8248e0de0794b9cabdc006c2 Mon Sep 17 00:00:00 2001 From: Tsukasa OMOTO Date: Tue, 18 Dec 2018 19:01:36 +0900 Subject: [PATCH 06/19] fix docs --- include/LightGBM/config.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/include/LightGBM/config.h b/include/LightGBM/config.h index b73a691827fb..a4357853cf1d 100644 --- a/include/LightGBM/config.h +++ b/include/LightGBM/config.h @@ -698,6 +698,10 @@ struct Config { // descl2 = ``kldiv``, `Kullback-Leibler divergence `__, aliases: ``kullback_leibler`` // desc = support multiple metrics, separated by ``,`` std::vector train_metric; + + // type = multi-enum + // default = "" + // desc = metric(s) to be evaluated on the evaluation set(s) std::vector valid_metric; // check = >0 @@ -705,7 +709,7 @@ struct Config { // desc = frequency for metric output int metric_freq = 1; - // alias = training_metric, is_training_metric, train_metric + // alias = training_metric, is_training_metric // desc = set this to ``true`` to output metric result over training dataset // desc = **Note**: can be used only in CLI version bool is_provide_training_metric = false; From 4a98ec0f7788fef7243e34239f7fe304607b9ed0 Mon Sep 17 00:00:00 2001 From: Tsukasa OMOTO Date: Wed, 19 Dec 2018 00:04:58 +0900 Subject: [PATCH 07/19] update Parameters.rst and config_auto.cpp `python helpers/parameter_generator.py` --- docs/Parameters.rst | 8 ++++++-- src/io/config_auto.cpp | 9 +++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/Parameters.rst b/docs/Parameters.rst index 6cac09e0588a..acda6a9291a2 100644 --- a/docs/Parameters.rst +++ b/docs/Parameters.rst @@ -739,7 +739,7 @@ Objective Parameters Metric Parameters ----------------- -- ``metric`` :raw-html:`🔗︎`, default = ``""``, type = multi-enum, aliases: ``metrics``, ``metric_types`` +- ``train_metric`` :raw-html:`🔗︎`, default = ``""``, type = multi-enum, aliases: ``metrics``, ``metric_types`` - metric(s) to be evaluated on the evaluation set(s) @@ -791,11 +791,15 @@ Metric Parameters - support multiple metrics, separated by ``,`` +- ``valid_metric`` :raw-html:`🔗︎`, default = ``""``, type = multi-enum + + - metric(s) to be evaluated on the evaluation set(s) + - ``metric_freq`` :raw-html:`🔗︎`, default = ``1``, type = int, aliases: ``output_freq``, constraints: ``metric_freq > 0`` - frequency for metric output -- ``is_provide_training_metric`` :raw-html:`🔗︎`, default = ``false``, type = bool, aliases: ``training_metric``, ``is_training_metric``, ``train_metric`` +- ``is_provide_training_metric`` :raw-html:`🔗︎`, default = ``false``, type = bool, aliases: ``training_metric``, ``is_training_metric`` - set this to ``true`` to output metric result over training dataset diff --git a/src/io/config_auto.cpp b/src/io/config_auto.cpp index 809eed4e1b39..3964c3e7af9c 100644 --- a/src/io/config_auto.cpp +++ b/src/io/config_auto.cpp @@ -135,8 +135,8 @@ std::unordered_map Config::alias_table({ {"num_classes", "num_class"}, {"unbalance", "is_unbalance"}, {"unbalanced_sets", "is_unbalance"}, - {"metrics", "metric"}, - {"metric_types", "metric"}, + {"metrics", "train_metric"}, + {"metric_types", "train_metric"}, {"output_freq", "metric_freq"}, {"training_metric", "is_provide_training_metric"}, {"is_training_metric", "is_provide_training_metric"}, @@ -491,6 +491,10 @@ void Config::GetMembersFromString(const std::unordered_map(tmp_str, ','); } + if (GetString(params, "valid_metric", &tmp_str)) { + valid_metric = Common::Split(tmp_str.c_str(), ','); + } + GetInt(params, "metric_freq", &metric_freq); CHECK(metric_freq >0); @@ -609,6 +613,7 @@ std::string Config::SaveMembersToString() const { str_buf << "[tweedie_variance_power: " << tweedie_variance_power << "]\n"; str_buf << "[max_position: " << max_position << "]\n"; str_buf << "[label_gain: " << Common::Join(label_gain,",") << "]\n"; + str_buf << "[valid_metric: " << Common::Join(valid_metric,",") << "]\n"; str_buf << "[metric_freq: " << metric_freq << "]\n"; str_buf << "[is_provide_training_metric: " << is_provide_training_metric << "]\n"; str_buf << "[eval_at: " << Common::Join(eval_at,",") << "]\n"; From a276abbfc683ed33e118cb3cd7b7a5374d522d9a Mon Sep 17 00:00:00 2001 From: Tsukasa OMOTO Date: Wed, 19 Dec 2018 00:29:00 +0900 Subject: [PATCH 08/19] fix --- docs/Parameters.rst | 2 +- include/LightGBM/config.h | 2 +- src/io/config_auto.cpp | 7 ------- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/docs/Parameters.rst b/docs/Parameters.rst index acda6a9291a2..baac3ce806ea 100644 --- a/docs/Parameters.rst +++ b/docs/Parameters.rst @@ -739,7 +739,7 @@ Objective Parameters Metric Parameters ----------------- -- ``train_metric`` :raw-html:`🔗︎`, default = ``""``, type = multi-enum, aliases: ``metrics``, ``metric_types`` +- ``train_metric`` :raw-html:`🔗︎`, default = ``""``, type = multi-enum - metric(s) to be evaluated on the evaluation set(s) diff --git a/include/LightGBM/config.h b/include/LightGBM/config.h index a4357853cf1d..0702046529d0 100644 --- a/include/LightGBM/config.h +++ b/include/LightGBM/config.h @@ -669,7 +669,6 @@ struct Config { #pragma region Metric Parameters // [doc-only] - // alias = metrics, metric_types // default = "" // type = multi-enum // desc = metric(s) to be evaluated on the evaluation set(s) @@ -699,6 +698,7 @@ struct Config { // desc = support multiple metrics, separated by ``,`` std::vector train_metric; + // [doc-only] // type = multi-enum // default = "" // desc = metric(s) to be evaluated on the evaluation set(s) diff --git a/src/io/config_auto.cpp b/src/io/config_auto.cpp index 3964c3e7af9c..d341f844d2f4 100644 --- a/src/io/config_auto.cpp +++ b/src/io/config_auto.cpp @@ -135,8 +135,6 @@ std::unordered_map Config::alias_table({ {"num_classes", "num_class"}, {"unbalance", "is_unbalance"}, {"unbalanced_sets", "is_unbalance"}, - {"metrics", "train_metric"}, - {"metric_types", "train_metric"}, {"output_freq", "metric_freq"}, {"training_metric", "is_provide_training_metric"}, {"is_training_metric", "is_provide_training_metric"}, @@ -491,10 +489,6 @@ void Config::GetMembersFromString(const std::unordered_map(tmp_str, ','); } - if (GetString(params, "valid_metric", &tmp_str)) { - valid_metric = Common::Split(tmp_str.c_str(), ','); - } - GetInt(params, "metric_freq", &metric_freq); CHECK(metric_freq >0); @@ -613,7 +607,6 @@ std::string Config::SaveMembersToString() const { str_buf << "[tweedie_variance_power: " << tweedie_variance_power << "]\n"; str_buf << "[max_position: " << max_position << "]\n"; str_buf << "[label_gain: " << Common::Join(label_gain,",") << "]\n"; - str_buf << "[valid_metric: " << Common::Join(valid_metric,",") << "]\n"; str_buf << "[metric_freq: " << metric_freq << "]\n"; str_buf << "[is_provide_training_metric: " << is_provide_training_metric << "]\n"; str_buf << "[eval_at: " << Common::Join(eval_at,",") << "]\n"; From bea28512f2fbd7212d3e700c814104b39d1b6e4d Mon Sep 17 00:00:00 2001 From: Tsukasa OMOTO Date: Wed, 19 Dec 2018 20:18:51 +0900 Subject: [PATCH 09/19] backward compatibility --- docs/Parameters.rst | 4 +++ include/LightGBM/config.h | 6 ++++ src/io/config.cpp | 69 +++++++++++++++++++-------------------- src/io/config_auto.cpp | 1 + 4 files changed, 45 insertions(+), 35 deletions(-) diff --git a/docs/Parameters.rst b/docs/Parameters.rst index baac3ce806ea..e92fb2a2a5eb 100644 --- a/docs/Parameters.rst +++ b/docs/Parameters.rst @@ -795,6 +795,10 @@ Metric Parameters - metric(s) to be evaluated on the evaluation set(s) +- ``metric`` :raw-html:`🔗︎`, default = ``""``, type = multi-enum + + - dummpy parameter + - ``metric_freq`` :raw-html:`🔗︎`, default = ``1``, type = int, aliases: ``output_freq``, constraints: ``metric_freq > 0`` - frequency for metric output diff --git a/include/LightGBM/config.h b/include/LightGBM/config.h index 0702046529d0..97777faca389 100644 --- a/include/LightGBM/config.h +++ b/include/LightGBM/config.h @@ -704,6 +704,12 @@ struct Config { // desc = metric(s) to be evaluated on the evaluation set(s) std::vector valid_metric; + // [doc-only] + // type = multi-enum + // default = "" + // desc = dummpy parameter + std::vector metric; + // check = >0 // alias = output_freq // desc = frequency for metric output diff --git a/src/io/config.cpp b/src/io/config.cpp index 4ee8f1b0a54f..5fec4653464a 100644 --- a/src/io/config.cpp +++ b/src/io/config.cpp @@ -68,9 +68,12 @@ void GetObjectiveType(const std::unordered_map& params } } -void GetTrainMetricType(const std::unordered_map& params, std::vector* metric) { + +void SetMetircType(const std::unordered_map& params, + const std::string& name, + std::vector* metric) { std::string value; - if (Config::GetString(params, "train_metric", &value)) { + if (Config::GetString(params, name, &value)) { // clear old metrics metric->clear(); // to lower @@ -90,47 +93,44 @@ void GetTrainMetricType(const std::unordered_map& para } metric->shrink_to_fit(); } - // add names of objective function if not providing metric - if (metric->empty() && value.size() == 0) { - if (Config::GetString(params, "objective", &value)) { - std::transform(value.begin(), value.end(), value.begin(), Common::tolower); - metric->push_back(value); - } - } } -void GetValidMetricType(const std::unordered_map& params, std::vector* metric) { +void SetDefaultMetircType(const std::unordered_map& params, + std::vector* metric) { std::string value; - if (Config::GetString(params, "valid_metric", &value)) { - // clear old metrics - metric->clear(); - // to lower + // add names of objective function if not providing metric + if (Config::GetString(params, "objective", &value)) { std::transform(value.begin(), value.end(), value.begin(), Common::tolower); - // split - std::vector metrics = Common::Split(value.c_str(), ','); - // remove duplicate - std::unordered_set metric_sets; - for (auto& met : metrics) { - std::transform(met.begin(), met.end(), met.begin(), Common::tolower); - if (metric_sets.count(met) <= 0) { - metric_sets.insert(met); - } + metric->push_back(value); + } +} + +void GetMetricType(const std::unordered_map& params, + std::vector* train_metric, std::vector* valid_metric) { + if (params.count("metric") > 0) { + //printf("HOGE\n"); + //printf("metric: %s\n", params.at("metric").c_str()); + SetMetircType(params, "metric", train_metric); + SetMetircType(params, "metric", valid_metric); + } else { + //printf("FOOO\n"); + if (params.count("train_metric") > 0) { + //printf("train_metric: %s\n", params.at("train_metric").c_str()); + SetMetircType(params, "train_metric", train_metric); } - for (auto& met : metric_sets) { - metric->push_back(met); + if (params.count("valid_metric") > 0) { + //printf("valid_metric: %s\n", params.at("valid_metric").c_str()); + SetMetircType(params, "valid_metric", valid_metric); } - metric->shrink_to_fit(); } - // add names of objective function if not providing metric - if (metric->empty() && value.size() == 0) { - if (Config::GetString(params, "objective", &value)) { - std::transform(value.begin(), value.end(), value.begin(), Common::tolower); - metric->push_back(value); - } + if (train_metric->empty()) { + SetDefaultMetircType(params, train_metric); + } + if (valid_metric->empty()) { + SetDefaultMetircType(params, valid_metric); } } - void GetTaskType(const std::unordered_map& params, TaskType* task) { std::string value; if (Config::GetString(params, "task", &value)) { @@ -196,8 +196,7 @@ void Config::Set(const std::unordered_map& params) { GetTaskType(params, &task); GetBoostingType(params, &boosting); - GetTrainMetricType(params, &train_metric); - GetValidMetricType(params, &valid_metric); + GetMetricType(params, &train_metric, &valid_metric); GetObjectiveType(params, &objective); GetDeviceType(params, &device_type); GetTreeLearnerType(params, &tree_learner); diff --git a/src/io/config_auto.cpp b/src/io/config_auto.cpp index d341f844d2f4..e8feaa405abe 100644 --- a/src/io/config_auto.cpp +++ b/src/io/config_auto.cpp @@ -248,6 +248,7 @@ std::unordered_set Config::parameter_set({ "label_gain", "train_metric", "valid_metric", + "metric", "metric_freq", "is_provide_training_metric", "eval_at", From e0ca9e0cdcc0e1098297d971bfb3cb8b93eda998 Mon Sep 17 00:00:00 2001 From: Tsukasa OMOTO Date: Wed, 19 Dec 2018 22:05:01 +0900 Subject: [PATCH 10/19] reset --- examples/python-guide/advanced_example.py | 2 +- .../notebooks/interactive_plot_example.ipynb | 9 +++--- examples/python-guide/plot_example.py | 3 +- examples/python-guide/simple_example.py | 2 +- python-package/lightgbm/engine.py | 2 +- python-package/lightgbm/sklearn.py | 2 +- tests/python_package_test/test_engine.py | 32 +++++++++---------- tests/python_package_test/test_plotting.py | 5 +-- 8 files changed, 26 insertions(+), 31 deletions(-) diff --git a/examples/python-guide/advanced_example.py b/examples/python-guide/advanced_example.py index 865b1307249d..cef8ad6c944a 100644 --- a/examples/python-guide/advanced_example.py +++ b/examples/python-guide/advanced_example.py @@ -36,7 +36,7 @@ params = { 'boosting_type': 'gbdt', 'objective': 'binary', - 'train_metric': 'binary_logloss', + 'metric': 'binary_logloss', 'num_leaves': 31, 'learning_rate': 0.05, 'feature_fraction': 0.9, diff --git a/examples/python-guide/notebooks/interactive_plot_example.ipynb b/examples/python-guide/notebooks/interactive_plot_example.ipynb index f86377a52950..e472404d9aa4 100644 --- a/examples/python-guide/notebooks/interactive_plot_example.ipynb +++ b/examples/python-guide/notebooks/interactive_plot_example.ipynb @@ -103,8 +103,7 @@ "source": [ "params = {\n", " 'num_leaves': 5,\n", - " 'train_metric': ['l1', 'l2'],\n", - " 'valid_metric': ['l1', 'l2'],\n", + " 'metric': ['l1', 'l2'],\n", " 'verbose': -1\n", "}" ] @@ -198,9 +197,9 @@ "source": [ "if INTERACTIVE:\n", " # create widget to switch between metrics\n", - " interact(render_metric, metric_name=params['valid_metric'])\n", + " interact(render_metric, metric_name=params['metric'])\n", "else:\n", - " render_metric(params['valid_metric'][0])" + " render_metric(params['metric'][0])" ] }, { @@ -337,7 +336,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.6" + "version": "3.6.3" }, "varInspector": { "cols": { diff --git a/examples/python-guide/plot_example.py b/examples/python-guide/plot_example.py index e935930aa95b..eca896982fa9 100644 --- a/examples/python-guide/plot_example.py +++ b/examples/python-guide/plot_example.py @@ -25,8 +25,7 @@ # specify your configurations as a dict params = { 'num_leaves': 5, - 'train_metric': ('l1', 'l2'), - 'valid_metric': ('l1', 'l2'), + 'metric': ('l1', 'l2'), 'verbose': 0 } diff --git a/examples/python-guide/simple_example.py b/examples/python-guide/simple_example.py index 403956d8642a..18f07cd601cd 100644 --- a/examples/python-guide/simple_example.py +++ b/examples/python-guide/simple_example.py @@ -22,7 +22,7 @@ params = { 'boosting_type': 'gbdt', 'objective': 'regression', - 'valid_metric': {'l2', 'l1'}, + 'metric': {'l2', 'l1'}, 'num_leaves': 31, 'learning_rate': 0.05, 'feature_fraction': 0.9, diff --git a/python-package/lightgbm/engine.py b/python-package/lightgbm/engine.py index da4e42069a29..da7459fced71 100644 --- a/python-package/lightgbm/engine.py +++ b/python-package/lightgbm/engine.py @@ -446,7 +446,7 @@ def cv(params, train_set, num_boost_round=100, .set_categorical_feature(categorical_feature) if metrics is not None: - params['valid_metric'] = metrics + params['metric'] = metrics results = collections.defaultdict(list) cvfolds = _make_n_folds(train_set, folds=folds, nfold=nfold, diff --git a/python-package/lightgbm/sklearn.py b/python-package/lightgbm/sklearn.py index 5db0bdb82b54..f69d28f683ed 100644 --- a/python-package/lightgbm/sklearn.py +++ b/python-package/lightgbm/sklearn.py @@ -477,7 +477,7 @@ def fit(self, X, y, # concatenate metric from params (or default if not provided in params) and eval_metric original_metric = [original_metric] if isinstance(original_metric, (string_type, type(None))) else original_metric eval_metric = [eval_metric] if isinstance(eval_metric, (string_type, type(None))) else eval_metric - params['valid_metric'] = set(original_metric + eval_metric) # TODO: train_metric + params['metric'] = set(original_metric + eval_metric) # TODO: train_metric if not isinstance(X, DataFrame): X, y = _LGBMCheckXY(X, y, accept_sparse=True, force_all_finite=False, ensure_min_samples=2) diff --git a/tests/python_package_test/test_engine.py b/tests/python_package_test/test_engine.py index fc73169c5b66..e89b59661f9f 100644 --- a/tests/python_package_test/test_engine.py +++ b/tests/python_package_test/test_engine.py @@ -30,7 +30,7 @@ def test_binary(self): X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) params = { 'objective': 'binary', - 'valid_metric': 'binary_logloss', + 'metric': 'binary_logloss', 'verbose': -1, 'num_iteration': 50 # test num_iteration in dict here } @@ -57,7 +57,7 @@ def test_rf(self): 'bagging_fraction': 0.5, 'feature_fraction': 0.5, 'num_leaves': 50, - 'valid_metric': 'binary_logloss', + 'metric': 'binary_logloss', 'verbose': -1 } lgb_train = lgb.Dataset(X_train, y_train) @@ -76,7 +76,7 @@ def test_regression(self): X, y = load_boston(True) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) params = { - 'valid_metric': 'l2', + 'metric': 'l2', 'verbose': -1 } lgb_train = lgb.Dataset(X_train, y_train) @@ -102,7 +102,7 @@ def test_missing_value_handle(self): lgb_eval = lgb.Dataset(X_train, y_train) params = { - 'valid_metric': 'l2', + 'metric': 'l2', 'verbose': -1, 'boost_from_average': False } @@ -127,7 +127,7 @@ def test_missing_value_handle_na(self): params = { 'objective': 'regression', - 'valid_metric': 'auc', + 'metric': 'auc', 'verbose': -1, 'boost_from_average': False, 'min_data': 1, @@ -159,7 +159,7 @@ def test_missing_value_handle_zero(self): params = { 'objective': 'regression', - 'valid_metric': 'auc', + 'metric': 'auc', 'verbose': -1, 'boost_from_average': False, 'min_data': 1, @@ -191,7 +191,7 @@ def test_missing_value_handle_none(self): params = { 'objective': 'regression', - 'valid_metric': 'auc', + 'metric': 'auc', 'verbose': -1, 'boost_from_average': False, 'min_data': 1, @@ -224,7 +224,7 @@ def test_categorical_handle(self): params = { 'objective': 'regression', - 'valid_metric': 'auc', + 'metric': 'auc', 'verbose': -1, 'boost_from_average': False, 'min_data': 1, @@ -261,7 +261,7 @@ def test_categorical_handle_na(self): params = { 'objective': 'regression', - 'valid_metric': 'auc', + 'metric': 'auc', 'verbose': -1, 'boost_from_average': False, 'min_data': 1, @@ -292,7 +292,7 @@ def test_multiclass(self): X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) params = { 'objective': 'multiclass', - 'valid_metric': 'multi_logloss', + 'metric': 'multi_logloss', 'num_class': 10, 'verbose': -1 } @@ -314,7 +314,7 @@ def test_multiclass_rf(self): params = { 'boosting_type': 'rf', 'objective': 'multiclass', - 'valid_metric': 'multi_logloss', + 'metric': 'multi_logloss', 'bagging_freq': 1, 'bagging_fraction': 0.6, 'feature_fraction': 0.6, @@ -365,7 +365,7 @@ def test_early_stopping(self): X, y = load_breast_cancer(True) params = { 'objective': 'binary', - 'valid_metric': 'binary_logloss', + 'metric': 'binary_logloss', 'verbose': -1 } X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) @@ -397,7 +397,7 @@ def test_continue_train(self): X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) params = { 'objective': 'regression', - 'valid_metric': 'l1', + 'metric': 'l1', 'verbose': -1 } lgb_train = lgb.Dataset(X_train, y_train, free_raw_data=False) @@ -426,7 +426,7 @@ def test_continue_train_multiclass(self): X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) params = { 'objective': 'multiclass', - 'valid_metric': 'multi_logloss', + 'metric': 'multi_logloss', 'num_class': 3, 'verbose': -1 } @@ -450,7 +450,7 @@ def test_cv(self): params = {'verbose': -1} lgb_train = lgb.Dataset(X_train, y_train) # shuffle = False, override metric in params - params_with_metric = {'valid_metric': 'l2', 'verbose': -1} + params_with_metric = {'metric': 'l2', 'verbose': -1} cv_res = lgb.cv(params_with_metric, lgb_train, num_boost_round=10, nfold=3, stratified=False, shuffle=False, metrics='l1', verbose_eval=False) @@ -595,7 +595,7 @@ def test_reference_chain(self): # take subsets and train tmp_dat_train = tmp_dat.subset(np.arange(80)) tmp_dat_val = tmp_dat.subset(np.arange(80, 100)).subset(np.arange(18)) - params = {'objective': 'regression_l2', 'train_metric': 'rmse', 'valid_metric': 'rmse'} + params = {'objective': 'regression_l2', 'metric': 'rmse'} evals_result = {} gbm = lgb.train(params, tmp_dat_train, num_boost_round=20, valid_sets=[tmp_dat_train, tmp_dat_val], evals_result=evals_result) diff --git a/tests/python_package_test/test_plotting.py b/tests/python_package_test/test_plotting.py index 6ee332355ae6..05a25e64d4a2 100644 --- a/tests/python_package_test/test_plotting.py +++ b/tests/python_package_test/test_plotting.py @@ -104,10 +104,7 @@ def test_create_tree_digraph(self): @unittest.skipIf(not MATPLOTLIB_INSTALLED, 'matplotlib is not installed') def test_plot_metrics(self): test_data = lgb.Dataset(self.X_test, self.y_test, reference=self.train_data) - self.params.update({ - "train_metric": ["binary_logloss", "binary_error"], - "valid_metric": ["binary_logloss", "binary_error"] - }) + self.params.update({"metric": {"binary_logloss", "binary_error"}}) evals_result0 = {} gbm0 = lgb.train(self.params, self.train_data, From 95729b2103cedc2cdc2fa8782e00d0f396a168f1 Mon Sep 17 00:00:00 2001 From: Tsukasa OMOTO Date: Wed, 19 Dec 2018 22:19:53 +0900 Subject: [PATCH 11/19] add a test --- tests/python_package_test/test_engine.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/python_package_test/test_engine.py b/tests/python_package_test/test_engine.py index e89b59661f9f..3cfeb7054ff0 100644 --- a/tests/python_package_test/test_engine.py +++ b/tests/python_package_test/test_engine.py @@ -808,3 +808,23 @@ def test_constant_features_multiclassova(self): } self.test_constant_features([0.0, 1.0, 2.0, 0.0], [0.5, 0.25, 0.25], params) self.test_constant_features([0.0, 1.0, 2.0, 1.0], [0.25, 0.5, 0.25], params) + + def test_train_and_valid_metric(self): + X, y = load_boston(True) + X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) + params = { + 'train_metric': ['l2'], + 'valid_metric': ['l1', 'l2'], + 'verbose': -1 + } + lgb_train = lgb.Dataset(X_train, y_train) + lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train) + evals_result = {} + gbm = lgb.train(params, lgb_train, + num_boost_round=50, + valid_sets=[lgb_train, lgb_eval], + verbose_eval=False, + evals_result=evals_result) + self.assertTrue('l2' in evals_result['training']) + self.assertTrue('l1' in evals_result['valid_1']) + self.assertTrue('l2' in evals_result['valid_1']) From 452d21f1d83cc6f2dbd0022fef4afb27c8e91ce5 Mon Sep 17 00:00:00 2001 From: Tsukasa OMOTO Date: Thu, 20 Dec 2018 00:16:58 +0900 Subject: [PATCH 12/19] fix c_api --- src/c_api.cpp | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/c_api.cpp b/src/c_api.cpp index 4ddc510aed98..298ed854dc5b 100644 --- a/src/c_api.cpp +++ b/src/c_api.cpp @@ -300,15 +300,13 @@ class Booster { int GetEvalCounts(int data_idx) const { int ret = 0; if (data_idx == 0) { - for (const auto& metric : train_metric_) { - ret += static_cast(metric->GetName().size()); - } + for (const auto& metric : train_metric_) { + ret += static_cast(metric->GetName().size()); + } } else { - for (size_t i = 0; i < valid_metrics_.size(); ++i) { - for (size_t j = 0; j < valid_metrics_[i].size(); ++j) { - ret += static_cast(valid_metrics_[i][j]->GetName().size()); - } - } + for (size_t j = 0; j < valid_metrics_[data_idx - 1].size(); ++j) { + ret += static_cast(valid_metrics_[data_idx - 1][j]->GetName().size()); + } } return ret; } @@ -316,22 +314,20 @@ class Booster { int GetEvalNames(int data_idx, char** out_strs) const { int idx = 0; if (data_idx == 0) { - for (const auto& metric : train_metric_) { - for (const auto& name : metric->GetName()) { - std::memcpy(out_strs[idx], name.c_str(), name.size() + 1); - ++idx; - } + for (const auto& metric : train_metric_) { + for (const auto& name : metric->GetName()) { + std::memcpy(out_strs[idx], name.c_str(), name.size() + 1); + ++idx; } + } } else { - for (size_t i = 0; i < valid_metrics_.size(); ++i) { - for (size_t j = 0; j < valid_metrics_[i].size(); ++j) { - auto& metric = valid_metrics_[i][j]; - for (const auto& name : metric->GetName()) { - std::memcpy(out_strs[idx], name.c_str(), name.size() + 1); - ++idx; - } - } + for (size_t j = 0; j < valid_metrics_[data_idx - 1].size(); ++j) { + auto& metric = valid_metrics_[data_idx - 1][j]; + for (const auto& name : metric->GetName()) { + std::memcpy(out_strs[idx], name.c_str(), name.size() + 1); + ++idx; } + } } return idx; } From 6892408a4027736c271a3ff4a27b22f899004c78 Mon Sep 17 00:00:00 2001 From: Tsukasa OMOTO Date: Thu, 20 Dec 2018 00:38:13 +0900 Subject: [PATCH 13/19] update sklearn API [ci skip] --- python-package/lightgbm/sklearn.py | 76 ++++++++++++++--------- tests/python_package_test/test_sklearn.py | 16 ++++- 2 files changed, 60 insertions(+), 32 deletions(-) diff --git a/python-package/lightgbm/sklearn.py b/python-package/lightgbm/sklearn.py index f69d28f683ed..5c053cc128f6 100644 --- a/python-package/lightgbm/sklearn.py +++ b/python-package/lightgbm/sklearn.py @@ -330,7 +330,8 @@ def fit(self, X, y, sample_weight=None, init_score=None, group=None, eval_set=None, eval_names=None, eval_sample_weight=None, eval_class_weight=None, eval_init_score=None, eval_group=None, - eval_metric=None, early_stopping_rounds=None, verbose=True, + eval_metric=None, eval_train_metric=None, eval_valid_metric=None, + early_stopping_rounds=None, verbose=True, feature_name='auto', categorical_feature='auto', callbacks=None): """Build a gradient boosting model from the training set (X, y). @@ -460,24 +461,29 @@ def fit(self, X, y, feval = _eval_function_wrapper(eval_metric) else: feval = None - # register default metric for consistency with callable eval_metric case - original_metric = self._objective if isinstance(self._objective, string_type) else None - if original_metric is None: - # try to deduce from class instance - if isinstance(self, LGBMRegressor): - original_metric = "l2" - elif isinstance(self, LGBMClassifier): - original_metric = "multi_logloss" if self._n_classes > 2 else "binary_logloss" - elif isinstance(self, LGBMRanker): - original_metric = "ndcg" - # overwrite default metric by explicitly set metric - for metric_alias in ['metric', 'metrics', 'metric_types']: - if metric_alias in params: - original_metric = params.pop(metric_alias) - # concatenate metric from params (or default if not provided in params) and eval_metric - original_metric = [original_metric] if isinstance(original_metric, (string_type, type(None))) else original_metric - eval_metric = [eval_metric] if isinstance(eval_metric, (string_type, type(None))) else eval_metric - params['metric'] = set(original_metric + eval_metric) # TODO: train_metric + if eval_metric: + params['metric'] = eval_metric + else: + params['train_metric'] = eval_train_metric + params['valid_metric'] = eval_valid_metric + # # register default metric for consistency with callable eval_metric case + # original_metric = self._objective if isinstance(self._objective, string_type) else None + # if original_metric is None: + # # try to deduce from class instance + # if isinstance(self, LGBMRegressor): + # original_metric = "l2" + # elif isinstance(self, LGBMClassifier): + # original_metric = "multi_logloss" if self._n_classes > 2 else "binary_logloss" + # elif isinstance(self, LGBMRanker): + # original_metric = "ndcg" + # # overwrite default metric by explicitly set metric + # for metric_alias in ['metric', 'metrics', 'metric_types']: + # if metric_alias in params: + # original_metric = params.pop(metric_alias) + # # concatenate metric from params (or default if not provided in params) and eval_metric + # original_metric = [original_metric] if isinstance(original_metric, (string_type, type(None))) else original_metric + # eval_metric = [eval_metric] if isinstance(eval_metric, (string_type, type(None))) else eval_metric + # params['metric'] = set(original_metric + eval_metric) # TODO: train_metric if not isinstance(X, DataFrame): X, y = _LGBMCheckXY(X, y, accept_sparse=True, force_all_finite=False, ensure_min_samples=2) @@ -518,7 +524,6 @@ def _get_meta_data(collection, i): # reduce cost for prediction training data if valid_data[0] is X and valid_data[1] is y: valid_set = train_set - # params['train_metric'] = set(original_metric + eval_metric) else: valid_weight = _get_meta_data(eval_sample_weight, i) if _get_meta_data(eval_class_weight, i) is not None: @@ -669,7 +674,8 @@ class LGBMRegressor(LGBMModel, _LGBMRegressorBase): def fit(self, X, y, sample_weight=None, init_score=None, eval_set=None, eval_names=None, eval_sample_weight=None, - eval_init_score=None, eval_metric=None, early_stopping_rounds=None, + eval_init_score=None, eval_metric=None, eval_train_metric=None, + eval_valid_metric=None, early_stopping_rounds=None, verbose=True, feature_name='auto', categorical_feature='auto', callbacks=None): """Docstring is inherited from the LGBMModel.""" super(LGBMRegressor, self).fit(X, y, sample_weight=sample_weight, @@ -678,6 +684,8 @@ def fit(self, X, y, eval_sample_weight=eval_sample_weight, eval_init_score=eval_init_score, eval_metric=eval_metric, + eval_train_metric=eval_train_metric, + eval_valid_metric=eval_valid_metric, early_stopping_rounds=early_stopping_rounds, verbose=verbose, feature_name=feature_name, categorical_feature=categorical_feature, @@ -696,6 +704,7 @@ def fit(self, X, y, sample_weight=None, init_score=None, eval_set=None, eval_names=None, eval_sample_weight=None, eval_class_weight=None, eval_init_score=None, eval_metric=None, + eval_train_metric=None, eval_valid_metric=None, early_stopping_rounds=None, verbose=True, feature_name='auto', categorical_feature='auto', callbacks=None): """Docstring is inherited from the LGBMModel.""" @@ -711,15 +720,17 @@ def fit(self, X, y, ova_aliases = ("multiclassova", "multiclass_ova", "ova", "ovr") if self._objective not in ova_aliases and not callable(self._objective): self._objective = "multiclass" - if eval_metric in ('logloss', 'binary_logloss'): - eval_metric = "multi_logloss" - elif eval_metric in ('error', 'binary_error'): - eval_metric = "multi_error" + for _metric in (eval_metric, eval_train_metric, eval_valid_metric): + if _metric in ('logloss', 'binary_logloss'): + _metric = "multi_logloss" + elif _metric in ('error', 'binary_error'): + _metric = "multi_error" else: - if eval_metric in ('logloss', 'multi_logloss'): - eval_metric = 'binary_logloss' - elif eval_metric in ('error', 'multi_error'): - eval_metric = 'binary_error' + for _metric in (eval_metric, eval_train_metric, eval_valid_metric): + if _metric in ('logloss', 'multi_logloss'): + _metric = 'binary_logloss' + elif _metric in ('error', 'multi_error'): + _metric = 'binary_error' if eval_set is not None: if isinstance(eval_set, tuple): @@ -737,6 +748,8 @@ def fit(self, X, y, eval_class_weight=eval_class_weight, eval_init_score=eval_init_score, eval_metric=eval_metric, + eval_train_metric=eval_train_metric, + eval_valid_metric=eval_valid_metric, early_stopping_rounds=early_stopping_rounds, verbose=verbose, feature_name=feature_name, categorical_feature=categorical_feature, @@ -824,7 +837,8 @@ def fit(self, X, y, sample_weight=None, init_score=None, group=None, eval_set=None, eval_names=None, eval_sample_weight=None, eval_init_score=None, eval_group=None, eval_metric=None, - eval_at=[1], early_stopping_rounds=None, verbose=True, + eval_train_metric=None, eval_valid_metric=None, eval_at=[1], + early_stopping_rounds=None, verbose=True, feature_name='auto', categorical_feature='auto', callbacks=None): """Docstring is inherited from the LGBMModel.""" # check group data @@ -850,6 +864,8 @@ def fit(self, X, y, eval_sample_weight=eval_sample_weight, eval_init_score=eval_init_score, eval_group=eval_group, eval_metric=eval_metric, + eval_train_metric=eval_train_metric, + eval_valid_metric=eval_valid_metric, early_stopping_rounds=early_stopping_rounds, verbose=verbose, feature_name=feature_name, categorical_feature=categorical_feature, diff --git a/tests/python_package_test/test_sklearn.py b/tests/python_package_test/test_sklearn.py index 7ee85649bfa0..e2c4495ff32f 100644 --- a/tests/python_package_test/test_sklearn.py +++ b/tests/python_package_test/test_sklearn.py @@ -82,7 +82,8 @@ def objective_ls(y_true, y_pred): X, y = load_boston(True) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) gbm = lgb.LGBMRegressor(n_estimators=50, silent=True, objective=objective_ls) - gbm.fit(X_train, y_train, eval_set=[(X_test, y_test)], early_stopping_rounds=5, verbose=False) + gbm.fit(X_train, y_train, eval_set=[(X_test, y_test)], eval_metric='regression', + early_stopping_rounds=5, verbose=False) ret = mean_squared_error(y_test, gbm.predict(X_test)) self.assertLess(ret, 100) self.assertAlmostEqual(ret, gbm.evals_result_['valid_0']['l2'][gbm.best_iteration_ - 1], places=5) @@ -100,7 +101,8 @@ def binary_error(y_test, y_pred): X, y = load_digits(2, True) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) gbm = lgb.LGBMClassifier(n_estimators=50, silent=True, objective=logregobj) - gbm.fit(X_train, y_train, eval_set=[(X_test, y_test)], early_stopping_rounds=5, verbose=False) + gbm.fit(X_train, y_train, eval_set=[(X_test, y_test)], eval_metric='binary_error', + early_stopping_rounds=5, verbose=False) ret = binary_error(y_test, gbm.predict(X_test)) self.assertLess(ret, 0.1) @@ -279,3 +281,13 @@ def test_predict(self): self.assertRaises(AssertionError, np.testing.assert_allclose, res_engine, res_sklearn_params) + + def test_regressor_train_and_valid_metric(self): + X, y = load_boston(True) + X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) + gbm = lgb.LGBMRegressor(n_estimators=10, silent=True) + gbm.fit(X_train, y_train, eval_set=[(X_train, y_train), (X_test, y_test)], + eval_train_metric='l2', eval_valid_metric=['l1', 'l2'], verbose=False) + self.assertTrue('l2' in gbm.evals_result_['training']) + self.assertTrue('l1' in gbm.evals_result_['valid_1']) + self.assertTrue('l2' in gbm.evals_result_['valid_1']) From c6062de560dc5ef618e85e8b84152e01282c01c6 Mon Sep 17 00:00:00 2001 From: Tsukasa OMOTO Date: Thu, 20 Dec 2018 12:36:34 +0900 Subject: [PATCH 14/19] remove printf --- src/io/config.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/io/config.cpp b/src/io/config.cpp index 5fec4653464a..0da8ff313e5c 100644 --- a/src/io/config.cpp +++ b/src/io/config.cpp @@ -108,18 +108,13 @@ void SetDefaultMetircType(const std::unordered_map& pa void GetMetricType(const std::unordered_map& params, std::vector* train_metric, std::vector* valid_metric) { if (params.count("metric") > 0) { - //printf("HOGE\n"); - //printf("metric: %s\n", params.at("metric").c_str()); SetMetircType(params, "metric", train_metric); SetMetircType(params, "metric", valid_metric); } else { - //printf("FOOO\n"); if (params.count("train_metric") > 0) { - //printf("train_metric: %s\n", params.at("train_metric").c_str()); SetMetircType(params, "train_metric", train_metric); } if (params.count("valid_metric") > 0) { - //printf("valid_metric: %s\n", params.at("valid_metric").c_str()); SetMetircType(params, "valid_metric", valid_metric); } } From a3fb1d632c3490e680152546d3695bac6b93106a Mon Sep 17 00:00:00 2001 From: Tsukasa OMOTO Date: Thu, 20 Dec 2018 12:51:10 +0900 Subject: [PATCH 15/19] add tests --- tests/python_package_test/test_sklearn.py | 40 ++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/tests/python_package_test/test_sklearn.py b/tests/python_package_test/test_sklearn.py index fa60cf3e4a7d..e41fa2509a5b 100644 --- a/tests/python_package_test/test_sklearn.py +++ b/tests/python_package_test/test_sklearn.py @@ -297,7 +297,39 @@ def test_regressor_train_and_valid_metric(self): X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) gbm = lgb.LGBMRegressor(n_estimators=10, silent=True) gbm.fit(X_train, y_train, eval_set=[(X_train, y_train), (X_test, y_test)], - eval_train_metric='l2', eval_valid_metric=['l1', 'l2'], verbose=False) - self.assertTrue('l2' in gbm.evals_result_['training']) - self.assertTrue('l1' in gbm.evals_result_['valid_1']) - self.assertTrue('l2' in gbm.evals_result_['valid_1']) + eval_train_metric='l2', eval_valid_metric=['l1', 'l2'], + early_stopping_rounds=5, verbose=False) + self.assertIn('l2', gbm.evals_result_['training']) + self.assertIn('l1', gbm.evals_result_['valid_1']) + self.assertIn('l2', gbm.evals_result_['valid_1']) + + def test_classifier_train_and_valid_metric(self): + X, y = load_breast_cancer(True) + X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=42) + gbm = lgb.LGBMClassifier(n_estimators=10, silent=True) + gbm.fit(X_train, y_train, eval_set=[(X_train, y_train), (X_test, y_test)], + eval_train_metric='binary_error', eval_valid_metric=['binary_logloss', 'binary_error'], + early_stopping_rounds=5, verbose=False) + self.assertIn('binary_error', gbm.evals_result_['training']) + self.assertIn('binary_error', gbm.evals_result_['valid_1']) + self.assertIn('binary_logloss', gbm.evals_result_['valid_1']) + + def test_ranker_train_and_valid_metric(self): + X_train, y_train = load_svmlight_file(os.path.join(os.path.dirname(os.path.realpath(__file__)), + '../../examples/lambdarank/rank.train')) + X_test, y_test = load_svmlight_file(os.path.join(os.path.dirname(os.path.realpath(__file__)), + '../../examples/lambdarank/rank.test')) + q_train = np.loadtxt(os.path.join(os.path.dirname(os.path.realpath(__file__)), + '../../examples/lambdarank/rank.train.query')) + q_test = np.loadtxt(os.path.join(os.path.dirname(os.path.realpath(__file__)), + '../../examples/lambdarank/rank.test.query')) + gbm = lgb.LGBMRanker(n_estimators=10, silent=True) + gbm.fit(X_train, y_train, group=q_train, eval_set=[(X_train, y_train), (X_test, y_test)], + eval_group=[q_train, q_test], eval_at=[1, 3], + eval_train_metric='ndcg', eval_valid_metric=['l2', 'ndcg'], + early_stopping_rounds=5, verbose=False) + self.assertIn('ndcg@1', gbm.evals_result_['training']) + self.assertIn('ndcg@3', gbm.evals_result_['training']) + self.assertIn('ndcg@1', gbm.evals_result_['valid_1']) + self.assertIn('ndcg@3', gbm.evals_result_['valid_1']) + self.assertIn('l2', gbm.evals_result_['valid_1']) From 9d5882b6ed5213f987b06a16fce429e35a437863 Mon Sep 17 00:00:00 2001 From: Tsukasa OMOTO Date: Thu, 20 Dec 2018 12:53:07 +0900 Subject: [PATCH 16/19] cleanup --- python-package/lightgbm/sklearn.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/python-package/lightgbm/sklearn.py b/python-package/lightgbm/sklearn.py index f85a555efb36..6b4cca23736e 100644 --- a/python-package/lightgbm/sklearn.py +++ b/python-package/lightgbm/sklearn.py @@ -466,24 +466,6 @@ def fit(self, X, y, else: params['train_metric'] = eval_train_metric params['valid_metric'] = eval_valid_metric - # # register default metric for consistency with callable eval_metric case - # original_metric = self._objective if isinstance(self._objective, string_type) else None - # if original_metric is None: - # # try to deduce from class instance - # if isinstance(self, LGBMRegressor): - # original_metric = "l2" - # elif isinstance(self, LGBMClassifier): - # original_metric = "multi_logloss" if self._n_classes > 2 else "binary_logloss" - # elif isinstance(self, LGBMRanker): - # original_metric = "ndcg" - # # overwrite default metric by explicitly set metric - # for metric_alias in ['metric', 'metrics', 'metric_types']: - # if metric_alias in params: - # original_metric = params.pop(metric_alias) - # # concatenate metric from params (or default if not provided in params) and eval_metric - # original_metric = [original_metric] if isinstance(original_metric, (string_type, type(None))) else original_metric - # eval_metric = [eval_metric] if isinstance(eval_metric, (string_type, type(None))) else eval_metric - # params['metric'] = set(original_metric + eval_metric) # TODO: train_metric if not isinstance(X, DataFrame): _X, _y = _LGBMCheckXY(X, y, accept_sparse=True, force_all_finite=False, ensure_min_samples=2) From b137ad5f630ff9f4530d8949fba299925d07cd63 Mon Sep 17 00:00:00 2001 From: Tsukasa OMOTO Date: Thu, 20 Dec 2018 13:01:02 +0900 Subject: [PATCH 17/19] update --- python-package/lightgbm/basic.py | 1 - tests/python_package_test/test_engine.py | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/python-package/lightgbm/basic.py b/python-package/lightgbm/basic.py index d6fd7edb172c..ccd31fb59996 100644 --- a/python-package/lightgbm/basic.py +++ b/python-package/lightgbm/basic.py @@ -1527,7 +1527,6 @@ def __init__(self, params=None, train_set=None, model_file=None, silent=False): # buffer for inner predict self.__inner_predict_buffer = [None] self.__is_predicted_cur_iter = [False] - self.__get_eval_info(0) # init by train data (data_idx=0) self.pandas_categorical = train_set.pandas_categorical elif model_file is not None: # Prediction task diff --git a/tests/python_package_test/test_engine.py b/tests/python_package_test/test_engine.py index 3cfeb7054ff0..1725b1d72ef7 100644 --- a/tests/python_package_test/test_engine.py +++ b/tests/python_package_test/test_engine.py @@ -821,10 +821,12 @@ def test_train_and_valid_metric(self): lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train) evals_result = {} gbm = lgb.train(params, lgb_train, - num_boost_round=50, - valid_sets=[lgb_train, lgb_eval], + num_boost_round=10, + valid_sets=[lgb_train, lgb_eval, lgb_eval], verbose_eval=False, evals_result=evals_result) self.assertTrue('l2' in evals_result['training']) self.assertTrue('l1' in evals_result['valid_1']) self.assertTrue('l2' in evals_result['valid_1']) + self.assertTrue('l1' in evals_result['valid_2']) + self.assertTrue('l2' in evals_result['valid_2']) From 2fb6066cb3409f190dfa8ab3f20b9d554ee78267 Mon Sep 17 00:00:00 2001 From: Tsukasa OMOTO Date: Thu, 20 Dec 2018 13:10:48 +0900 Subject: [PATCH 18/19] backward compatibility of config --- docs/Parameters.rst | 8 ++++---- include/LightGBM/config.h | 13 +++++++------ src/io/config_auto.cpp | 4 +++- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/docs/Parameters.rst b/docs/Parameters.rst index e92fb2a2a5eb..e34b360dc4b2 100644 --- a/docs/Parameters.rst +++ b/docs/Parameters.rst @@ -739,7 +739,7 @@ Objective Parameters Metric Parameters ----------------- -- ``train_metric`` :raw-html:`🔗︎`, default = ``""``, type = multi-enum +- ``metric`` :raw-html:`🔗︎`, default = ``""``, type = multi-enum, aliases: ``metrics``, ``metric_types`` - metric(s) to be evaluated on the evaluation set(s) @@ -791,13 +791,13 @@ Metric Parameters - support multiple metrics, separated by ``,`` -- ``valid_metric`` :raw-html:`🔗︎`, default = ``""``, type = multi-enum +- ``train_metric`` :raw-html:`🔗︎`, default = ``""``, type = multi-enum - metric(s) to be evaluated on the evaluation set(s) -- ``metric`` :raw-html:`🔗︎`, default = ``""``, type = multi-enum +- ``valid_metric`` :raw-html:`🔗︎`, default = ``""``, type = multi-enum - - dummpy parameter + - metric(s) to be evaluated on the evaluation set(s) - ``metric_freq`` :raw-html:`🔗︎`, default = ``1``, type = int, aliases: ``output_freq``, constraints: ``metric_freq > 0`` diff --git a/include/LightGBM/config.h b/include/LightGBM/config.h index 97777faca389..5f8303c8a7f4 100644 --- a/include/LightGBM/config.h +++ b/include/LightGBM/config.h @@ -669,6 +669,7 @@ struct Config { #pragma region Metric Parameters // [doc-only] + // alias = metrics, metric_types // default = "" // type = multi-enum // desc = metric(s) to be evaluated on the evaluation set(s) @@ -696,19 +697,19 @@ struct Config { // descl2 = ``xentlambda``, "intensity-weighted" cross-entropy, aliases: ``cross_entropy_lambda`` // descl2 = ``kldiv``, `Kullback-Leibler divergence `__, aliases: ``kullback_leibler`` // desc = support multiple metrics, separated by ``,`` - std::vector train_metric; + std::vector metric; // [doc-only] - // type = multi-enum // default = "" + // type = multi-enum // desc = metric(s) to be evaluated on the evaluation set(s) - std::vector valid_metric; + std::vector train_metric; // [doc-only] - // type = multi-enum // default = "" - // desc = dummpy parameter - std::vector metric; + // type = multi-enum + // desc = metric(s) to be evaluated on the evaluation set(s) + std::vector valid_metric; // check = >0 // alias = output_freq diff --git a/src/io/config_auto.cpp b/src/io/config_auto.cpp index e8feaa405abe..d2105c340f0d 100644 --- a/src/io/config_auto.cpp +++ b/src/io/config_auto.cpp @@ -135,6 +135,8 @@ std::unordered_map Config::alias_table({ {"num_classes", "num_class"}, {"unbalance", "is_unbalance"}, {"unbalanced_sets", "is_unbalance"}, + {"metrics", "metric"}, + {"metric_types", "metric"}, {"output_freq", "metric_freq"}, {"training_metric", "is_provide_training_metric"}, {"is_training_metric", "is_provide_training_metric"}, @@ -246,9 +248,9 @@ std::unordered_set Config::parameter_set({ "tweedie_variance_power", "max_position", "label_gain", + "metric", "train_metric", "valid_metric", - "metric", "metric_freq", "is_provide_training_metric", "eval_at", From b89c1ecb60f7a09fabcfa2ce5972251b2311cd02 Mon Sep 17 00:00:00 2001 From: Tsukasa OMOTO Date: Thu, 20 Dec 2018 23:24:54 +0900 Subject: [PATCH 19/19] update documents --- docs/Parameters.rst | 8 ++++++-- include/LightGBM/config.h | 6 ++++-- python-package/lightgbm/sklearn.py | 7 +++++++ src/io/config.cpp | 1 + 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/Parameters.rst b/docs/Parameters.rst index e34b360dc4b2..686bc16a960d 100644 --- a/docs/Parameters.rst +++ b/docs/Parameters.rst @@ -793,11 +793,15 @@ Metric Parameters - ``train_metric`` :raw-html:`🔗︎`, default = ``""``, type = multi-enum - - metric(s) to be evaluated on the evaluation set(s) + - metric(s) to be evaluated on the train set passed into evaluation set + + - if ``metric`` is set, ``train_metric`` will be overwritten - ``valid_metric`` :raw-html:`🔗︎`, default = ``""``, type = multi-enum - - metric(s) to be evaluated on the evaluation set(s) + - metric(s) to be evaluated on the valid set passed into evaluation set + + - if ``metric`` is set, ``valid_metric`` will be overwritten - ``metric_freq`` :raw-html:`🔗︎`, default = ``1``, type = int, aliases: ``output_freq``, constraints: ``metric_freq > 0`` diff --git a/include/LightGBM/config.h b/include/LightGBM/config.h index 5f8303c8a7f4..e6363352edf3 100644 --- a/include/LightGBM/config.h +++ b/include/LightGBM/config.h @@ -702,13 +702,15 @@ struct Config { // [doc-only] // default = "" // type = multi-enum - // desc = metric(s) to be evaluated on the evaluation set(s) + // desc = metric(s) to be evaluated on the train set passed into evaluation set + // desc = if ``metric`` is set, ``train_metric`` will be overwritten std::vector train_metric; // [doc-only] // default = "" // type = multi-enum - // desc = metric(s) to be evaluated on the evaluation set(s) + // desc = metric(s) to be evaluated on the valid set passed into evaluation set + // desc = if ``metric`` is set, ``valid_metric`` will be overwritten std::vector valid_metric; // check = >0 diff --git a/python-package/lightgbm/sklearn.py b/python-package/lightgbm/sklearn.py index 6b4cca23736e..5f87ae8b7679 100644 --- a/python-package/lightgbm/sklearn.py +++ b/python-package/lightgbm/sklearn.py @@ -364,6 +364,12 @@ def fit(self, X, y, If callable, it should be a custom evaluation metric, see note below for more details. In either case, the ``metric`` from the model parameters will be evaluated and used as well. Default: 'l2' for LGBMRegressor, 'logloss' for LGBMClassifier, 'ndcg' for LGBMRanker. + eval_train_metric : string, list of strings, callable or None, optional (default=None) + metric(s) to be evaluated on the train set passed into ``eval_set`` + if ``metric`` is set, ``eval_train_metric`` will be overwritten + eval_valid_metric : string, list of strings, callable or None, optional (default=None) + metric(s) to be evaluated on the valid set passed into ``eval_set`` + if ``metric`` is set, ``eval_valid_metric`` will be overwritten early_stopping_rounds : int or None, optional (default=None) Activates early stopping. The model will train until the validation score stops improving. Validation score needs to improve at least every ``early_stopping_rounds`` round(s) @@ -462,6 +468,7 @@ def fit(self, X, y, else: feval = None if eval_metric: + # for backward compatibility, `metric` is prefered to `train_metric` and `valid_metric` params['metric'] = eval_metric else: params['train_metric'] = eval_train_metric diff --git a/src/io/config.cpp b/src/io/config.cpp index 0da8ff313e5c..d18c559f7fca 100644 --- a/src/io/config.cpp +++ b/src/io/config.cpp @@ -108,6 +108,7 @@ void SetDefaultMetircType(const std::unordered_map& pa void GetMetricType(const std::unordered_map& params, std::vector* train_metric, std::vector* valid_metric) { if (params.count("metric") > 0) { + // for backward compatibility, `metric` is prefered to `train_metric` and `valid_metric` SetMetircType(params, "metric", train_metric); SetMetircType(params, "metric", valid_metric); } else {