From e2f98bd2b786c88bce4011fee9ef35689bf19a7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Morales?= Date: Wed, 29 Jun 2022 16:27:39 -0500 Subject: [PATCH 1/8] dump sorted parameter aliases --- R-package/tests/testthat/test_parameters.R | 2 +- helpers/parameter_generator.py | 56 +++- include/LightGBM/config.h | 19 +- python-package/lightgbm/basic.py | 46 ++-- src/io/config_auto.cpp | 294 +++++++++++---------- tests/python_package_test/test_basic.py | 18 +- 6 files changed, 254 insertions(+), 181 deletions(-) diff --git a/R-package/tests/testthat/test_parameters.R b/R-package/tests/testthat/test_parameters.R index 610db6bd9d4b..97bf678b43a7 100644 --- a/R-package/tests/testthat/test_parameters.R +++ b/R-package/tests/testthat/test_parameters.R @@ -55,7 +55,7 @@ test_that(".PARAMETER_ALIASES() returns a named list of character vectors, where expect_true(length(names(param_aliases)) == length(param_aliases)) expect_true(all(sapply(param_aliases, is.character))) expect_true(length(unique(names(param_aliases))) == length(param_aliases)) - expect_equal(sort(param_aliases[["task"]]), c("task", "task_type")) + expect_equal(param_aliases[["bagging_fraction"]], c("bagging_fraction", "bagging", "sub_row", "subsample")) }) test_that(".PARAMETER_ALIASES() uses the internal session cache", { diff --git a/helpers/parameter_generator.py b/helpers/parameter_generator.py index abc770bc8e43..a63b28793376 100644 --- a/helpers/parameter_generator.py +++ b/helpers/parameter_generator.py @@ -359,19 +359,49 @@ def gen_parameter_code( str_to_write += " return str_buf.str();\n" str_to_write += "}\n\n" - str_to_write += "const std::string Config::DumpAliases() {\n" - str_to_write += " std::stringstream str_buf;\n" - str_to_write += ' str_buf << "{";\n' - for idx, name in enumerate(names): - if idx > 0: - str_to_write += ', ";\n' - aliases = '\\", \\"'.join([alias for alias in names_with_aliases[name]]) - aliases = f'[\\"{aliases}\\"]' if aliases else '[]' - str_to_write += f' str_buf << "\\"{name}\\": {aliases}' - str_to_write += '";\n' - str_to_write += ' str_buf << "}";\n' - str_to_write += " return str_buf.str();\n" - str_to_write += "}\n\n" + str_to_write += """ +const std::unordered_map>& Config::parameter2aliases() { + static std::unordered_map> map({""" + for name in names: + str_to_write += '\n {"' + name + '", ' + if names_with_aliases[name]: + str_to_write += '{"' + '", "'.join(names_with_aliases[name]) + '"}},' + else: + str_to_write += '{}},' + str_to_write += """ + }); + return map; +} + +""" + + str_to_write += r""" +const std::string Config::DumpAliases() { + auto map = Config::parameter2aliases(); + for (auto& pair : map) { + std::sort(pair.second.begin(), pair.second.end(), SortAlias); + } + std::stringstream str_buf; + str_buf << "{\n"; + bool first = true; + for (const auto& pair : map) { + if (first) { + str_buf << " \""; + first = false; + } else { + str_buf << " , \""; + } + str_buf << pair.first << "\": ["; + if (pair.second.size() > 0) { + str_buf << "\"" << CommonC::Join(pair.second, "\", \"") << "\""; + } + str_buf << "]\n"; + } + str_buf << "}\n"; + return str_buf.str(); +} + +""" str_to_write += "} // namespace LightGBM\n" with open(config_out_cpp, "w") as config_out_cpp_file: diff --git a/include/LightGBM/config.h b/include/LightGBM/config.h index 900a43301e1c..d5a3df009edd 100644 --- a/include/LightGBM/config.h +++ b/include/LightGBM/config.h @@ -78,6 +78,16 @@ struct Config { const std::unordered_map& params, const std::string& name, bool* out); + /*! + * \brief Sort aliases by length and then alphabetically + * \param x Alias 1 + * \param y Alias 2 + * \return true if x has higher priority than y + */ + inline static bool SortAlias( + const std::string& x, const std::string& y); + + static void KV2Map(std::unordered_map* params, const char* kv); static std::unordered_map Str2Map(const char* parameters); @@ -1063,6 +1073,7 @@ struct Config { bool is_data_based_parallel = false; LIGHTGBM_EXPORT void Set(const std::unordered_map& params); static const std::unordered_map& alias_table(); + static const std::unordered_map>& parameter2aliases(); static const std::unordered_set& parameter_set(); std::vector> auc_mu_weights_matrix; std::vector> interaction_constraints_vector; @@ -1131,6 +1142,10 @@ inline bool Config::GetBool( return false; } +inline bool Config::SortAlias(const std::string& x, const std::string& y) { + return x.size() < y.size() || (x.size() == y.size() && x < y); +} + struct ParameterAlias { static void KeyAliasTransform(std::unordered_map* params) { std::unordered_map tmp_map; @@ -1139,9 +1154,7 @@ struct ParameterAlias { if (alias != Config::alias_table().end()) { // found alias auto alias_set = tmp_map.find(alias->second); if (alias_set != tmp_map.end()) { // alias already set - // set priority by length & alphabetically to ensure reproducible behavior - if (alias_set->second.size() < pair.first.size() || - (alias_set->second.size() == pair.first.size() && alias_set->second < pair.first)) { + if (Config::SortAlias(alias_set->second, pair.first)) { Log::Warning("%s is set with %s=%s, %s=%s will be ignored. Current value: %s=%s", alias->second.c_str(), alias_set->second.c_str(), params->at(alias_set->second).c_str(), pair.first.c_str(), pair.second.c_str(), alias->second.c_str(), params->at(alias_set->second).c_str()); diff --git a/python-package/lightgbm/basic.py b/python-package/lightgbm/basic.py index df373835b9b5..0313ed78222c 100644 --- a/python-package/lightgbm/basic.py +++ b/python-package/lightgbm/basic.py @@ -361,7 +361,7 @@ def _get_all_param_aliases() -> Dict[str, Set[str]]: ptr_string_buffer)) aliases = json.loads( string_buffer.value.decode('utf-8'), - object_hook=lambda obj: {k: set(v) | {k} for k, v in obj.items()} + object_hook=lambda obj: {k: [k] + v for k, v in obj.items()} ) return aliases @@ -371,9 +371,15 @@ def get(cls, *args) -> Set[str]: cls.aliases = cls._get_all_param_aliases() ret = set() for i in args: - ret |= cls.aliases.get(i, {i}) + ret.update(cls.get_sorted(i)) return ret + @classmethod + def get_sorted(cls, name: str) -> List[str]: + if cls.aliases is None: + cls.aliases = cls._get_all_param_aliases() + return cls.aliases.get(name, [name]) + @classmethod def get_by_alias(cls, *args) -> Set[str]: if cls.aliases is None: @@ -382,7 +388,7 @@ def get_by_alias(cls, *args) -> Set[str]: for arg in args: for aliases in cls.aliases.values(): if arg in aliases: - ret |= aliases + ret.update(aliases) break return ret @@ -408,27 +414,19 @@ def _choose_param_value(main_param_name: str, params: Dict[str, Any], default_va # avoid side effects on passed-in parameters params = deepcopy(params) - aliases = _ConfigAliases.get(main_param_name) - {main_param_name} - - # if main_param_name was provided, keep that value and remove all aliases - if main_param_name in params.keys(): - for param in aliases: - params.pop(param, None) - return params - - # if main param name was not found, search for an alias - for param in aliases: - if param in params.keys(): - params[main_param_name] = params[param] - break - - if main_param_name in params.keys(): - for param in aliases: - params.pop(param, None) - return params - - # neither of main_param_name, aliases were found - params[main_param_name] = default_value + aliases = _ConfigAliases.get_sorted(main_param_name) + found = False + for alias in aliases: + if not found and alias in params.keys(): + found = True + params[main_param_name] = params[alias] + if alias == main_param_name: + continue + if found: + params.pop(alias, None) + + if not found: + params[main_param_name] = default_value return params diff --git a/src/io/config_auto.cpp b/src/io/config_auto.cpp index 9f3dd7a188f1..6bc11367fd16 100644 --- a/src/io/config_auto.cpp +++ b/src/io/config_auto.cpp @@ -756,142 +756,168 @@ std::string Config::SaveMembersToString() const { return str_buf.str(); } + +const std::unordered_map>& Config::parameter2aliases() { + static std::unordered_map> map({ + {"config", {"config_file"}}, + {"task", {"task_type"}}, + {"objective", {"objective_type", "app", "application", "loss"}}, + {"boosting", {"boosting_type", "boost"}}, + {"data", {"train", "train_data", "train_data_file", "data_filename"}}, + {"valid", {"test", "valid_data", "valid_data_file", "test_data", "test_data_file", "valid_filenames"}}, + {"num_iterations", {"num_iteration", "n_iter", "num_tree", "num_trees", "num_round", "num_rounds", "nrounds", "num_boost_round", "n_estimators", "max_iter"}}, + {"learning_rate", {"shrinkage_rate", "eta"}}, + {"num_leaves", {"num_leaf", "max_leaves", "max_leaf", "max_leaf_nodes"}}, + {"tree_learner", {"tree", "tree_type", "tree_learner_type"}}, + {"num_threads", {"num_thread", "nthread", "nthreads", "n_jobs"}}, + {"device_type", {"device"}}, + {"seed", {"random_seed", "random_state"}}, + {"deterministic", {}}, + {"force_col_wise", {}}, + {"force_row_wise", {}}, + {"histogram_pool_size", {"hist_pool_size"}}, + {"max_depth", {}}, + {"min_data_in_leaf", {"min_data_per_leaf", "min_data", "min_child_samples", "min_samples_leaf"}}, + {"min_sum_hessian_in_leaf", {"min_sum_hessian_per_leaf", "min_sum_hessian", "min_hessian", "min_child_weight"}}, + {"bagging_fraction", {"sub_row", "subsample", "bagging"}}, + {"pos_bagging_fraction", {"pos_sub_row", "pos_subsample", "pos_bagging"}}, + {"neg_bagging_fraction", {"neg_sub_row", "neg_subsample", "neg_bagging"}}, + {"bagging_freq", {"subsample_freq"}}, + {"bagging_seed", {"bagging_fraction_seed"}}, + {"feature_fraction", {"sub_feature", "colsample_bytree"}}, + {"feature_fraction_bynode", {"sub_feature_bynode", "colsample_bynode"}}, + {"feature_fraction_seed", {}}, + {"extra_trees", {"extra_tree"}}, + {"extra_seed", {}}, + {"early_stopping_round", {"early_stopping_rounds", "early_stopping", "n_iter_no_change"}}, + {"first_metric_only", {}}, + {"max_delta_step", {"max_tree_output", "max_leaf_output"}}, + {"lambda_l1", {"reg_alpha", "l1_regularization"}}, + {"lambda_l2", {"reg_lambda", "lambda", "l2_regularization"}}, + {"linear_lambda", {}}, + {"min_gain_to_split", {"min_split_gain"}}, + {"drop_rate", {"rate_drop"}}, + {"max_drop", {}}, + {"skip_drop", {}}, + {"xgboost_dart_mode", {}}, + {"uniform_drop", {}}, + {"drop_seed", {}}, + {"top_rate", {}}, + {"other_rate", {}}, + {"min_data_per_group", {}}, + {"max_cat_threshold", {}}, + {"cat_l2", {}}, + {"cat_smooth", {}}, + {"max_cat_to_onehot", {}}, + {"top_k", {"topk"}}, + {"monotone_constraints", {"mc", "monotone_constraint", "monotonic_cst"}}, + {"monotone_constraints_method", {"monotone_constraining_method", "mc_method"}}, + {"monotone_penalty", {"monotone_splits_penalty", "ms_penalty", "mc_penalty"}}, + {"feature_contri", {"feature_contrib", "fc", "fp", "feature_penalty"}}, + {"forcedsplits_filename", {"fs", "forced_splits_filename", "forced_splits_file", "forced_splits"}}, + {"refit_decay_rate", {}}, + {"cegb_tradeoff", {}}, + {"cegb_penalty_split", {}}, + {"cegb_penalty_feature_lazy", {}}, + {"cegb_penalty_feature_coupled", {}}, + {"path_smooth", {}}, + {"interaction_constraints", {}}, + {"verbosity", {"verbose"}}, + {"input_model", {"model_input", "model_in"}}, + {"output_model", {"model_output", "model_out"}}, + {"saved_feature_importance_type", {}}, + {"snapshot_freq", {"save_period"}}, + {"linear_tree", {"linear_trees"}}, + {"max_bin", {"max_bins"}}, + {"max_bin_by_feature", {}}, + {"min_data_in_bin", {}}, + {"bin_construct_sample_cnt", {"subsample_for_bin"}}, + {"data_random_seed", {"data_seed"}}, + {"is_enable_sparse", {"is_sparse", "enable_sparse", "sparse"}}, + {"enable_bundle", {"is_enable_bundle", "bundle"}}, + {"use_missing", {}}, + {"zero_as_missing", {}}, + {"feature_pre_filter", {}}, + {"pre_partition", {"is_pre_partition"}}, + {"two_round", {"two_round_loading", "use_two_round_loading"}}, + {"header", {"has_header"}}, + {"label_column", {"label"}}, + {"weight_column", {"weight"}}, + {"group_column", {"group", "group_id", "query_column", "query", "query_id"}}, + {"ignore_column", {"ignore_feature", "blacklist"}}, + {"categorical_feature", {"cat_feature", "categorical_column", "cat_column", "categorical_features"}}, + {"forcedbins_filename", {}}, + {"save_binary", {"is_save_binary", "is_save_binary_file"}}, + {"precise_float_parser", {}}, + {"parser_config_file", {}}, + {"start_iteration_predict", {}}, + {"num_iteration_predict", {}}, + {"predict_raw_score", {"is_predict_raw_score", "predict_rawscore", "raw_score"}}, + {"predict_leaf_index", {"is_predict_leaf_index", "leaf_index"}}, + {"predict_contrib", {"is_predict_contrib", "contrib"}}, + {"predict_disable_shape_check", {}}, + {"pred_early_stop", {}}, + {"pred_early_stop_freq", {}}, + {"pred_early_stop_margin", {}}, + {"output_result", {"predict_result", "prediction_result", "predict_name", "prediction_name", "pred_name", "name_pred"}}, + {"convert_model_language", {}}, + {"convert_model", {"convert_model_file"}}, + {"objective_seed", {}}, + {"num_class", {"num_classes"}}, + {"is_unbalance", {"unbalance", "unbalanced_sets"}}, + {"scale_pos_weight", {}}, + {"sigmoid", {}}, + {"boost_from_average", {}}, + {"reg_sqrt", {}}, + {"alpha", {}}, + {"fair_c", {}}, + {"poisson_max_delta_step", {}}, + {"tweedie_variance_power", {}}, + {"lambdarank_truncation_level", {}}, + {"lambdarank_norm", {}}, + {"label_gain", {}}, + {"metric", {"metrics", "metric_types"}}, + {"metric_freq", {"output_freq"}}, + {"is_provide_training_metric", {"training_metric", "is_training_metric", "train_metric"}}, + {"eval_at", {"ndcg_eval_at", "ndcg_at", "map_eval_at", "map_at"}}, + {"multi_error_top_k", {}}, + {"auc_mu_weights", {}}, + {"num_machines", {"num_machine"}}, + {"local_listen_port", {"local_port", "port"}}, + {"time_out", {}}, + {"machine_list_filename", {"machine_list_file", "machine_list", "mlist"}}, + {"machines", {"workers", "nodes"}}, + {"gpu_platform_id", {}}, + {"gpu_device_id", {}}, + {"gpu_use_dp", {}}, + {"num_gpu", {}}, + }); + return map; +} + + const std::string Config::DumpAliases() { + auto map = Config::parameter2aliases(); + for (auto& pair : map) { + std::sort(pair.second.begin(), pair.second.end(), SortAlias); + } std::stringstream str_buf; - str_buf << "{"; - str_buf << "\"config\": [\"config_file\"], "; - str_buf << "\"task\": [\"task_type\"], "; - str_buf << "\"objective\": [\"objective_type\", \"app\", \"application\", \"loss\"], "; - str_buf << "\"boosting\": [\"boosting_type\", \"boost\"], "; - str_buf << "\"data\": [\"train\", \"train_data\", \"train_data_file\", \"data_filename\"], "; - str_buf << "\"valid\": [\"test\", \"valid_data\", \"valid_data_file\", \"test_data\", \"test_data_file\", \"valid_filenames\"], "; - str_buf << "\"num_iterations\": [\"num_iteration\", \"n_iter\", \"num_tree\", \"num_trees\", \"num_round\", \"num_rounds\", \"nrounds\", \"num_boost_round\", \"n_estimators\", \"max_iter\"], "; - str_buf << "\"learning_rate\": [\"shrinkage_rate\", \"eta\"], "; - str_buf << "\"num_leaves\": [\"num_leaf\", \"max_leaves\", \"max_leaf\", \"max_leaf_nodes\"], "; - str_buf << "\"tree_learner\": [\"tree\", \"tree_type\", \"tree_learner_type\"], "; - str_buf << "\"num_threads\": [\"num_thread\", \"nthread\", \"nthreads\", \"n_jobs\"], "; - str_buf << "\"device_type\": [\"device\"], "; - str_buf << "\"seed\": [\"random_seed\", \"random_state\"], "; - str_buf << "\"deterministic\": [], "; - str_buf << "\"force_col_wise\": [], "; - str_buf << "\"force_row_wise\": [], "; - str_buf << "\"histogram_pool_size\": [\"hist_pool_size\"], "; - str_buf << "\"max_depth\": [], "; - str_buf << "\"min_data_in_leaf\": [\"min_data_per_leaf\", \"min_data\", \"min_child_samples\", \"min_samples_leaf\"], "; - str_buf << "\"min_sum_hessian_in_leaf\": [\"min_sum_hessian_per_leaf\", \"min_sum_hessian\", \"min_hessian\", \"min_child_weight\"], "; - str_buf << "\"bagging_fraction\": [\"sub_row\", \"subsample\", \"bagging\"], "; - str_buf << "\"pos_bagging_fraction\": [\"pos_sub_row\", \"pos_subsample\", \"pos_bagging\"], "; - str_buf << "\"neg_bagging_fraction\": [\"neg_sub_row\", \"neg_subsample\", \"neg_bagging\"], "; - str_buf << "\"bagging_freq\": [\"subsample_freq\"], "; - str_buf << "\"bagging_seed\": [\"bagging_fraction_seed\"], "; - str_buf << "\"feature_fraction\": [\"sub_feature\", \"colsample_bytree\"], "; - str_buf << "\"feature_fraction_bynode\": [\"sub_feature_bynode\", \"colsample_bynode\"], "; - str_buf << "\"feature_fraction_seed\": [], "; - str_buf << "\"extra_trees\": [\"extra_tree\"], "; - str_buf << "\"extra_seed\": [], "; - str_buf << "\"early_stopping_round\": [\"early_stopping_rounds\", \"early_stopping\", \"n_iter_no_change\"], "; - str_buf << "\"first_metric_only\": [], "; - str_buf << "\"max_delta_step\": [\"max_tree_output\", \"max_leaf_output\"], "; - str_buf << "\"lambda_l1\": [\"reg_alpha\", \"l1_regularization\"], "; - str_buf << "\"lambda_l2\": [\"reg_lambda\", \"lambda\", \"l2_regularization\"], "; - str_buf << "\"linear_lambda\": [], "; - str_buf << "\"min_gain_to_split\": [\"min_split_gain\"], "; - str_buf << "\"drop_rate\": [\"rate_drop\"], "; - str_buf << "\"max_drop\": [], "; - str_buf << "\"skip_drop\": [], "; - str_buf << "\"xgboost_dart_mode\": [], "; - str_buf << "\"uniform_drop\": [], "; - str_buf << "\"drop_seed\": [], "; - str_buf << "\"top_rate\": [], "; - str_buf << "\"other_rate\": [], "; - str_buf << "\"min_data_per_group\": [], "; - str_buf << "\"max_cat_threshold\": [], "; - str_buf << "\"cat_l2\": [], "; - str_buf << "\"cat_smooth\": [], "; - str_buf << "\"max_cat_to_onehot\": [], "; - str_buf << "\"top_k\": [\"topk\"], "; - str_buf << "\"monotone_constraints\": [\"mc\", \"monotone_constraint\", \"monotonic_cst\"], "; - str_buf << "\"monotone_constraints_method\": [\"monotone_constraining_method\", \"mc_method\"], "; - str_buf << "\"monotone_penalty\": [\"monotone_splits_penalty\", \"ms_penalty\", \"mc_penalty\"], "; - str_buf << "\"feature_contri\": [\"feature_contrib\", \"fc\", \"fp\", \"feature_penalty\"], "; - str_buf << "\"forcedsplits_filename\": [\"fs\", \"forced_splits_filename\", \"forced_splits_file\", \"forced_splits\"], "; - str_buf << "\"refit_decay_rate\": [], "; - str_buf << "\"cegb_tradeoff\": [], "; - str_buf << "\"cegb_penalty_split\": [], "; - str_buf << "\"cegb_penalty_feature_lazy\": [], "; - str_buf << "\"cegb_penalty_feature_coupled\": [], "; - str_buf << "\"path_smooth\": [], "; - str_buf << "\"interaction_constraints\": [], "; - str_buf << "\"verbosity\": [\"verbose\"], "; - str_buf << "\"input_model\": [\"model_input\", \"model_in\"], "; - str_buf << "\"output_model\": [\"model_output\", \"model_out\"], "; - str_buf << "\"saved_feature_importance_type\": [], "; - str_buf << "\"snapshot_freq\": [\"save_period\"], "; - str_buf << "\"linear_tree\": [\"linear_trees\"], "; - str_buf << "\"max_bin\": [\"max_bins\"], "; - str_buf << "\"max_bin_by_feature\": [], "; - str_buf << "\"min_data_in_bin\": [], "; - str_buf << "\"bin_construct_sample_cnt\": [\"subsample_for_bin\"], "; - str_buf << "\"data_random_seed\": [\"data_seed\"], "; - str_buf << "\"is_enable_sparse\": [\"is_sparse\", \"enable_sparse\", \"sparse\"], "; - str_buf << "\"enable_bundle\": [\"is_enable_bundle\", \"bundle\"], "; - str_buf << "\"use_missing\": [], "; - str_buf << "\"zero_as_missing\": [], "; - str_buf << "\"feature_pre_filter\": [], "; - str_buf << "\"pre_partition\": [\"is_pre_partition\"], "; - str_buf << "\"two_round\": [\"two_round_loading\", \"use_two_round_loading\"], "; - str_buf << "\"header\": [\"has_header\"], "; - str_buf << "\"label_column\": [\"label\"], "; - str_buf << "\"weight_column\": [\"weight\"], "; - str_buf << "\"group_column\": [\"group\", \"group_id\", \"query_column\", \"query\", \"query_id\"], "; - str_buf << "\"ignore_column\": [\"ignore_feature\", \"blacklist\"], "; - str_buf << "\"categorical_feature\": [\"cat_feature\", \"categorical_column\", \"cat_column\", \"categorical_features\"], "; - str_buf << "\"forcedbins_filename\": [], "; - str_buf << "\"save_binary\": [\"is_save_binary\", \"is_save_binary_file\"], "; - str_buf << "\"precise_float_parser\": [], "; - str_buf << "\"parser_config_file\": [], "; - str_buf << "\"start_iteration_predict\": [], "; - str_buf << "\"num_iteration_predict\": [], "; - str_buf << "\"predict_raw_score\": [\"is_predict_raw_score\", \"predict_rawscore\", \"raw_score\"], "; - str_buf << "\"predict_leaf_index\": [\"is_predict_leaf_index\", \"leaf_index\"], "; - str_buf << "\"predict_contrib\": [\"is_predict_contrib\", \"contrib\"], "; - str_buf << "\"predict_disable_shape_check\": [], "; - str_buf << "\"pred_early_stop\": [], "; - str_buf << "\"pred_early_stop_freq\": [], "; - str_buf << "\"pred_early_stop_margin\": [], "; - str_buf << "\"output_result\": [\"predict_result\", \"prediction_result\", \"predict_name\", \"prediction_name\", \"pred_name\", \"name_pred\"], "; - str_buf << "\"convert_model_language\": [], "; - str_buf << "\"convert_model\": [\"convert_model_file\"], "; - str_buf << "\"objective_seed\": [], "; - str_buf << "\"num_class\": [\"num_classes\"], "; - str_buf << "\"is_unbalance\": [\"unbalance\", \"unbalanced_sets\"], "; - str_buf << "\"scale_pos_weight\": [], "; - str_buf << "\"sigmoid\": [], "; - str_buf << "\"boost_from_average\": [], "; - str_buf << "\"reg_sqrt\": [], "; - str_buf << "\"alpha\": [], "; - str_buf << "\"fair_c\": [], "; - str_buf << "\"poisson_max_delta_step\": [], "; - str_buf << "\"tweedie_variance_power\": [], "; - str_buf << "\"lambdarank_truncation_level\": [], "; - str_buf << "\"lambdarank_norm\": [], "; - str_buf << "\"label_gain\": [], "; - str_buf << "\"metric\": [\"metrics\", \"metric_types\"], "; - str_buf << "\"metric_freq\": [\"output_freq\"], "; - str_buf << "\"is_provide_training_metric\": [\"training_metric\", \"is_training_metric\", \"train_metric\"], "; - str_buf << "\"eval_at\": [\"ndcg_eval_at\", \"ndcg_at\", \"map_eval_at\", \"map_at\"], "; - str_buf << "\"multi_error_top_k\": [], "; - str_buf << "\"auc_mu_weights\": [], "; - str_buf << "\"num_machines\": [\"num_machine\"], "; - str_buf << "\"local_listen_port\": [\"local_port\", \"port\"], "; - str_buf << "\"time_out\": [], "; - str_buf << "\"machine_list_filename\": [\"machine_list_file\", \"machine_list\", \"mlist\"], "; - str_buf << "\"machines\": [\"workers\", \"nodes\"], "; - str_buf << "\"gpu_platform_id\": [], "; - str_buf << "\"gpu_device_id\": [], "; - str_buf << "\"gpu_use_dp\": [], "; - str_buf << "\"num_gpu\": []"; - str_buf << "}"; + str_buf << "{\n"; + bool first = true; + for (const auto& pair : map) { + if (first) { + str_buf << " \""; + first = false; + } else { + str_buf << " , \""; + } + str_buf << pair.first << "\": ["; + if (pair.second.size() > 0) { + str_buf << "\"" << CommonC::Join(pair.second, "\", \"") << "\""; + } + str_buf << "]\n"; + } + str_buf << "}\n"; return str_buf.str(); } diff --git a/tests/python_package_test/test_basic.py b/tests/python_package_test/test_basic.py index 93f0b215874d..b7f8b11e5cda 100644 --- a/tests/python_package_test/test_basic.py +++ b/tests/python_package_test/test_basic.py @@ -473,7 +473,8 @@ def test_choose_param_value(): "local_listen_port": 1234, "port": 2222, "metric": "auc", - "num_trees": 81 + "num_trees": 81, + "n_iter": 10, } # should resolve duplicate aliases, and prefer the main parameter @@ -485,15 +486,16 @@ def test_choose_param_value(): assert params["local_listen_port"] == 1234 assert "port" not in params - # should choose a value from an alias and set that value on main param - # if only an alias is used + # should choose the highest priority alias and set that value on main param + # if only aliases are used params = lgb.basic._choose_param_value( main_param_name="num_iterations", params=params, default_value=17 ) - assert params["num_iterations"] == 81 + assert params["num_iterations"] == 10 assert "num_trees" not in params + assert "n_iter" not in params # should use the default if main param and aliases are missing params = lgb.basic._choose_param_value( @@ -508,7 +510,8 @@ def test_choose_param_value(): "local_listen_port": 1234, "port": 2222, "metric": "auc", - "num_trees": 81 + "num_trees": 81, + "n_iter": 10, } assert original_params == expected_params @@ -644,10 +647,13 @@ def test_param_aliases(): aliases = lgb.basic._ConfigAliases.aliases assert isinstance(aliases, dict) assert len(aliases) > 100 - assert all(isinstance(i, set) for i in aliases.values()) + assert all(isinstance(i, list) for i in aliases.values()) assert all(len(i) >= 1 for i in aliases.values()) assert all(k in v for k, v in aliases.items()) assert lgb.basic._ConfigAliases.get('config', 'task') == {'config', 'config_file', 'task', 'task_type'} + assert lgb.basic._ConfigAliases.get_sorted('min_data_in_leaf') == [ + 'min_data_in_leaf', 'min_data', 'min_samples_leaf', 'min_child_samples', 'min_data_per_leaf' + ] def _bad_gradients(preds, _): From 9ef559dd022edf56beed833a1eda892589316772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Morales?= Date: Thu, 30 Jun 2022 10:11:30 -0500 Subject: [PATCH 2/8] update lgb.check.wrapper_param --- R-package/R/utils.R | 36 +++++++++++---------------- R-package/tests/testthat/test_utils.R | 6 ++--- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/R-package/R/utils.R b/R-package/R/utils.R index 53c88475b589..c54fd66bd375 100644 --- a/R-package/R/utils.R +++ b/R-package/R/utils.R @@ -177,7 +177,7 @@ lgb.check.eval <- function(params, eval) { # ways, the first item in this list is used: # # 1. the main (non-alias) parameter found in `params` -# 2. the first alias of that parameter found in `params` +# 2. the alias with the highest priority found in `params` # 3. the keyword argument passed in # # For example, "num_iterations" can also be provided to lgb.train() @@ -185,7 +185,7 @@ lgb.check.eval <- function(params, eval) { # based on the first match in this list: # # 1. params[["num_iterations]] -# 2. the first alias of "num_iterations" found in params +# 2. the highest priority alias of "num_iterations" found in params # 3. the nrounds keyword argument # # If multiple aliases are found in `params` for the same parameter, they are @@ -197,30 +197,22 @@ lgb.check.eval <- function(params, eval) { lgb.check.wrapper_param <- function(main_param_name, params, alternative_kwarg_value) { aliases <- .PARAMETER_ALIASES()[[main_param_name]] - aliases_provided <- names(params)[names(params) %in% aliases] - aliases_provided <- aliases_provided[aliases_provided != main_param_name] + aliases_provided <- aliases[aliases %in% names(params)] - # prefer the main parameter - if (!is.null(params[[main_param_name]])) { - for (param in aliases_provided) { - params[[param]] <- NULL - } - return(params) - } - - # if the main parameter wasn't provided, prefer the first alias if (length(aliases_provided) > 0L) { - first_param <- aliases_provided[1L] - params[[main_param_name]] <- params[[first_param]] - for (param in aliases_provided) { - params[[param]] <- NULL + # set the first alias (has the highest priority) and could be the main param + params[[main_param_name]] <- params[[aliases_provided[[1L]]]] + # remove the aliases + for (alias in aliases_provided) { + if (alias != main_param_name) { + params[[alias]] <- NULL + } } - return(params) + } else { + # if not provided in params at all, use the alternative value provided + # through a keyword argument from lgb.train(), lgb.cv(), etc. + params[[main_param_name]] <- alternative_kwarg_value } - - # if not provided in params at all, use the alternative value provided - # through a keyword argument from lgb.train(), lgb.cv(), etc. - params[[main_param_name]] <- alternative_kwarg_value return(params) } diff --git a/R-package/tests/testthat/test_utils.R b/R-package/tests/testthat/test_utils.R index a0866b00d6b4..d9bcd6464ad9 100644 --- a/R-package/tests/testthat/test_utils.R +++ b/R-package/tests/testthat/test_utils.R @@ -123,7 +123,7 @@ test_that("lgb.check.wrapper_param() prefers alias to keyword arg", { expect_equal(params[["num_iterations"]], num_tree) expect_identical(params, list(num_iterations = num_tree)) - # switching the order should switch which one is chosen + # switching the order shouldn't switch which one is chosen params2 <- lgb.check.wrapper_param( main_param_name = "num_iterations" , params = list( @@ -132,6 +132,6 @@ test_that("lgb.check.wrapper_param() prefers alias to keyword arg", { ) , alternative_kwarg_value = kwarg_val ) - expect_equal(params2[["num_iterations"]], n_estimators) - expect_identical(params2, list(num_iterations = n_estimators)) + expect_equal(params2[["num_iterations"]], num_tree) + expect_identical(params2, list(num_iterations = num_tree)) }) From 1e94f1787ed6c75473282a14e79c607a444075c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Morales?= Date: Thu, 30 Jun 2022 10:15:29 -0500 Subject: [PATCH 3/8] update _choose_param_value to look like lgb.check.wrapper_param --- python-package/lightgbm/basic.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/python-package/lightgbm/basic.py b/python-package/lightgbm/basic.py index 0313ed78222c..578018aaf5fb 100644 --- a/python-package/lightgbm/basic.py +++ b/python-package/lightgbm/basic.py @@ -415,17 +415,16 @@ def _choose_param_value(main_param_name: str, params: Dict[str, Any], default_va params = deepcopy(params) aliases = _ConfigAliases.get_sorted(main_param_name) - found = False - for alias in aliases: - if not found and alias in params.keys(): - found = True - params[main_param_name] = params[alias] - if alias == main_param_name: - continue - if found: - params.pop(alias, None) - - if not found: + aliases_provided = [a for a in aliases if a in params.keys()] + if aliases_provided: + # set the first alias (has the highest priority) and could be the main param + params[main_param_name] = params[aliases_provided[0]] + # remove the aliases + for alias in aliases_provided: + if alias != main_param_name: + params.pop(alias, None) + else: + # if no alias was provided in params we fallback to the default value params[main_param_name] = default_value return params From 50be5af956fbe0a9769c014f6d4839c9e461c248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Morales?= Date: Fri, 1 Jul 2022 02:22:18 -0500 Subject: [PATCH 4/8] apply suggestions from review --- R-package/R/utils.R | 30 ++++++++++++++++--------- python-package/lightgbm/basic.py | 28 ++++++++++++++--------- tests/python_package_test/test_basic.py | 6 ++--- 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/R-package/R/utils.R b/R-package/R/utils.R index c54fd66bd375..4c4460fc8feb 100644 --- a/R-package/R/utils.R +++ b/R-package/R/utils.R @@ -198,21 +198,29 @@ lgb.check.wrapper_param <- function(main_param_name, params, alternative_kwarg_v aliases <- .PARAMETER_ALIASES()[[main_param_name]] aliases_provided <- aliases[aliases %in% names(params)] + aliases_provided <- aliases[aliases != main_param_name] + # prefer the main parameter + if (!is.null(params[[main_param_name]])) { + for (param in aliases_provided) { + params[[param]] <- NULL + } + return(params) + } + + # if the main parameter wasn't provided, prefer the first alias if (length(aliases_provided) > 0L) { - # set the first alias (has the highest priority) and could be the main param - params[[main_param_name]] <- params[[aliases_provided[[1L]]]] - # remove the aliases - for (alias in aliases_provided) { - if (alias != main_param_name) { - params[[alias]] <- NULL - } + first_param <- aliases_provided[1L] + params[[main_param_name]] <- params[[first_param]] + for (param in aliases_provided) { + params[[param]] <- NULL } - } else { - # if not provided in params at all, use the alternative value provided - # through a keyword argument from lgb.train(), lgb.cv(), etc. - params[[main_param_name]] <- alternative_kwarg_value + return(params) } + + # if not provided in params at all, use the alternative value provided + # through a keyword argument from lgb.train(), lgb.cv(), etc. + params[[main_param_name]] <- alternative_kwarg_value return(params) } diff --git a/python-package/lightgbm/basic.py b/python-package/lightgbm/basic.py index 578018aaf5fb..1356ad20af39 100644 --- a/python-package/lightgbm/basic.py +++ b/python-package/lightgbm/basic.py @@ -341,7 +341,7 @@ class _ConfigAliases: aliases = None @staticmethod - def _get_all_param_aliases() -> Dict[str, Set[str]]: + def _get_all_param_aliases() -> Dict[str, List[str]]: buffer_len = 1 << 20 tmp_out_len = ctypes.c_int64(0) string_buffer = ctypes.create_string_buffer(buffer_len) @@ -415,18 +415,24 @@ def _choose_param_value(main_param_name: str, params: Dict[str, Any], default_va params = deepcopy(params) aliases = _ConfigAliases.get_sorted(main_param_name) - aliases_provided = [a for a in aliases if a in params.keys()] - if aliases_provided: - # set the first alias (has the highest priority) and could be the main param - params[main_param_name] = params[aliases_provided[0]] - # remove the aliases + aliases_provided = [a for a in aliases if a in params.keys() and a != main_param_name] + + # prefer the main parameter + if main_param_name in params: for alias in aliases_provided: - if alias != main_param_name: - params.pop(alias, None) - else: - # if no alias was provided in params we fallback to the default value - params[main_param_name] = default_value + params.pop(alias, None) + return params + # if the main parameter wasn't provided, prefer the first alias + if aliases_provided: + first_param = aliases_provided[0] + params[main_param_name] = params[first_param] + for param in aliases_provided: + params.pop(param, None) + return params + + # if no alias was provided in params we fallback to the default valu + params[main_param_name] = default_value return params diff --git a/tests/python_package_test/test_basic.py b/tests/python_package_test/test_basic.py index b7f8b11e5cda..57d32c21f4c5 100644 --- a/tests/python_package_test/test_basic.py +++ b/tests/python_package_test/test_basic.py @@ -474,7 +474,7 @@ def test_choose_param_value(): "port": 2222, "metric": "auc", "num_trees": 81, - "n_iter": 10, + "n_iter": 13, } # should resolve duplicate aliases, and prefer the main parameter @@ -493,7 +493,7 @@ def test_choose_param_value(): params=params, default_value=17 ) - assert params["num_iterations"] == 10 + assert params["num_iterations"] == 13 assert "num_trees" not in params assert "n_iter" not in params @@ -511,7 +511,7 @@ def test_choose_param_value(): "port": 2222, "metric": "auc", "num_trees": 81, - "n_iter": 10, + "n_iter": 13, } assert original_params == expected_params From 3f493b5a325d4db68410c6ff64b5fb57393b1951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Morales?= Date: Fri, 1 Jul 2022 04:07:34 -0500 Subject: [PATCH 5/8] reduce diff --- R-package/R/utils.R | 2 +- include/LightGBM/config.h | 3 +-- python-package/lightgbm/basic.py | 26 +++++++++++++++----------- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/R-package/R/utils.R b/R-package/R/utils.R index 4c4460fc8feb..56e499360836 100644 --- a/R-package/R/utils.R +++ b/R-package/R/utils.R @@ -198,7 +198,7 @@ lgb.check.wrapper_param <- function(main_param_name, params, alternative_kwarg_v aliases <- .PARAMETER_ALIASES()[[main_param_name]] aliases_provided <- aliases[aliases %in% names(params)] - aliases_provided <- aliases[aliases != main_param_name] + aliases_provided <- aliases_provided[aliases_provided != main_param_name] # prefer the main parameter if (!is.null(params[[main_param_name]])) { diff --git a/include/LightGBM/config.h b/include/LightGBM/config.h index d5a3df009edd..00806c10642e 100644 --- a/include/LightGBM/config.h +++ b/include/LightGBM/config.h @@ -84,8 +84,7 @@ struct Config { * \param y Alias 2 * \return true if x has higher priority than y */ - inline static bool SortAlias( - const std::string& x, const std::string& y); + inline static bool SortAlias(const std::string& x, const std::string& y); static void KV2Map(std::unordered_map* params, const char* kv); diff --git a/python-package/lightgbm/basic.py b/python-package/lightgbm/basic.py index 1356ad20af39..01c5a37db343 100644 --- a/python-package/lightgbm/basic.py +++ b/python-package/lightgbm/basic.py @@ -415,24 +415,28 @@ def _choose_param_value(main_param_name: str, params: Dict[str, Any], default_va params = deepcopy(params) aliases = _ConfigAliases.get_sorted(main_param_name) - aliases_provided = [a for a in aliases if a in params.keys() and a != main_param_name] + aliases = [a for a in aliases if a in params.keys() and a != main_param_name] - # prefer the main parameter - if main_param_name in params: - for alias in aliases_provided: - params.pop(alias, None) + # if main_param_name was provided, keep that value and remove all aliases + if main_param_name in params.keys(): + for param in aliases: + params.pop(param, None) return params - # if the main parameter wasn't provided, prefer the first alias - if aliases_provided: - first_param = aliases_provided[0] - params[main_param_name] = params[first_param] - for param in aliases_provided: + # if main param name was not found, search for an alias + for param in aliases: + if param in params.keys(): + params[main_param_name] = params[param] + break + + if main_param_name in params.keys(): + for param in aliases: params.pop(param, None) return params - # if no alias was provided in params we fallback to the default valu + # neither of main_param_name, aliases were found params[main_param_name] = default_value + return params From eb0778daec1f9bab3b48c3ed8ac418cbc80e4ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Morales?= Date: Fri, 1 Jul 2022 23:42:46 -0500 Subject: [PATCH 6/8] move DumpAliases to config --- helpers/parameter_generator.py | 32 +------------------------------- include/LightGBM/config.h | 1 - src/io/config.cpp | 25 +++++++++++++++++++++++++ src/io/config_auto.cpp | 27 --------------------------- 4 files changed, 26 insertions(+), 59 deletions(-) diff --git a/helpers/parameter_generator.py b/helpers/parameter_generator.py index a63b28793376..9bc62b093a26 100644 --- a/helpers/parameter_generator.py +++ b/helpers/parameter_generator.py @@ -359,8 +359,7 @@ def gen_parameter_code( str_to_write += " return str_buf.str();\n" str_to_write += "}\n\n" - str_to_write += """ -const std::unordered_map>& Config::parameter2aliases() { + str_to_write += """const std::unordered_map>& Config::parameter2aliases() { static std::unordered_map> map({""" for name in names: str_to_write += '\n {"' + name + '", ' @@ -374,35 +373,6 @@ def gen_parameter_code( } """ - - str_to_write += r""" -const std::string Config::DumpAliases() { - auto map = Config::parameter2aliases(); - for (auto& pair : map) { - std::sort(pair.second.begin(), pair.second.end(), SortAlias); - } - std::stringstream str_buf; - str_buf << "{\n"; - bool first = true; - for (const auto& pair : map) { - if (first) { - str_buf << " \""; - first = false; - } else { - str_buf << " , \""; - } - str_buf << pair.first << "\": ["; - if (pair.second.size() > 0) { - str_buf << "\"" << CommonC::Join(pair.second, "\", \"") << "\""; - } - str_buf << "]\n"; - } - str_buf << "}\n"; - return str_buf.str(); -} - -""" - str_to_write += "} // namespace LightGBM\n" with open(config_out_cpp, "w") as config_out_cpp_file: config_out_cpp_file.write(str_to_write) diff --git a/include/LightGBM/config.h b/include/LightGBM/config.h index 00806c10642e..e88c4d7b70b7 100644 --- a/include/LightGBM/config.h +++ b/include/LightGBM/config.h @@ -86,7 +86,6 @@ struct Config { */ inline static bool SortAlias(const std::string& x, const std::string& y); - static void KV2Map(std::unordered_map* params, const char* kv); static std::unordered_map Str2Map(const char* parameters); diff --git a/src/io/config.cpp b/src/io/config.cpp index 298ce79e661d..90232045f488 100644 --- a/src/io/config.cpp +++ b/src/io/config.cpp @@ -411,4 +411,29 @@ std::string Config::ToString() const { return str_buf.str(); } +const std::string Config::DumpAliases() { + auto map = Config::parameter2aliases(); + for (auto& pair : map) { + std::sort(pair.second.begin(), pair.second.end(), SortAlias); + } + std::stringstream str_buf; + str_buf << "{\n"; + bool first = true; + for (const auto& pair : map) { + if (first) { + str_buf << " \""; + first = false; + } else { + str_buf << " , \""; + } + str_buf << pair.first << "\": ["; + if (pair.second.size() > 0) { + str_buf << "\"" << CommonC::Join(pair.second, "\", \"") << "\""; + } + str_buf << "]\n"; + } + str_buf << "}\n"; + return str_buf.str(); +} + } // namespace LightGBM diff --git a/src/io/config_auto.cpp b/src/io/config_auto.cpp index 6bc11367fd16..6c2e3cabad00 100644 --- a/src/io/config_auto.cpp +++ b/src/io/config_auto.cpp @@ -756,7 +756,6 @@ std::string Config::SaveMembersToString() const { return str_buf.str(); } - const std::unordered_map>& Config::parameter2aliases() { static std::unordered_map> map({ {"config", {"config_file"}}, @@ -895,30 +894,4 @@ const std::unordered_map>& Config::paramet return map; } - -const std::string Config::DumpAliases() { - auto map = Config::parameter2aliases(); - for (auto& pair : map) { - std::sort(pair.second.begin(), pair.second.end(), SortAlias); - } - std::stringstream str_buf; - str_buf << "{\n"; - bool first = true; - for (const auto& pair : map) { - if (first) { - str_buf << " \""; - first = false; - } else { - str_buf << " , \""; - } - str_buf << pair.first << "\": ["; - if (pair.second.size() > 0) { - str_buf << "\"" << CommonC::Join(pair.second, "\", \"") << "\""; - } - str_buf << "]\n"; - } - str_buf << "}\n"; - return str_buf.str(); -} - } // namespace LightGBM From 4c4910f752b04f3f77db481e763d873288cb4929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Morales?= Date: Mon, 4 Jul 2022 09:57:07 -0500 Subject: [PATCH 7/8] remove unnecessary check --- python-package/lightgbm/basic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python-package/lightgbm/basic.py b/python-package/lightgbm/basic.py index 01c5a37db343..97006fa774a8 100644 --- a/python-package/lightgbm/basic.py +++ b/python-package/lightgbm/basic.py @@ -415,7 +415,7 @@ def _choose_param_value(main_param_name: str, params: Dict[str, Any], default_va params = deepcopy(params) aliases = _ConfigAliases.get_sorted(main_param_name) - aliases = [a for a in aliases if a in params.keys() and a != main_param_name] + aliases = [a for a in aliases if a != main_param_name] # if main_param_name was provided, keep that value and remove all aliases if main_param_name in params.keys(): From e45fc48405e9877138ffb5f7e1fd4c449752d323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Morales?= Date: Mon, 11 Jul 2022 10:16:11 -0500 Subject: [PATCH 8/8] restore parameter check --- R-package/tests/testthat/test_parameters.R | 1 + 1 file changed, 1 insertion(+) diff --git a/R-package/tests/testthat/test_parameters.R b/R-package/tests/testthat/test_parameters.R index 97bf678b43a7..3f98f8d2907e 100644 --- a/R-package/tests/testthat/test_parameters.R +++ b/R-package/tests/testthat/test_parameters.R @@ -55,6 +55,7 @@ test_that(".PARAMETER_ALIASES() returns a named list of character vectors, where expect_true(length(names(param_aliases)) == length(param_aliases)) expect_true(all(sapply(param_aliases, is.character))) expect_true(length(unique(names(param_aliases))) == length(param_aliases)) + expect_equal(sort(param_aliases[["task"]]), c("task", "task_type")) expect_equal(param_aliases[["bagging_fraction"]], c("bagging_fraction", "bagging", "sub_row", "subsample")) })