We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Inconsistent learning rates shown in output when specifying learning rate in config 
When specifying the learning_rate parameter in the training section of the config, `description.json` shows a different default learning rate in the optimizer section:

config.yaml:
<img width="231" alt="Screen Shot 2021-12-21 at 4 46 42 PM" src="https://user-images.githubusercontent.com/687280/147016130-2856cfda-c399-4fc8-b79b-2fe96003b559.png">

description.json:
<img width="698" alt="Screen Shot 2021-12-21 at 4 47 04 PM" src="https://user-images.githubusercontent.com/687280/147016222-60e44fd7-fd92-474d-b73d-17c5c62d5954.png">

Expected behavior is that there is one learning rate, and if it is specified in the config it should match that.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `ludwig/utils/defaults.py`
Content:
```
1 #! /usr/bin/env python
2 # Copyright (c) 2019 Uber Technologies, Inc.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 # ==============================================================================
16 import argparse
17 import copy
18 import logging
19 import sys
20 
21 import yaml
22 
23 from ludwig.constants import BINARY, CATEGORY, COLUMN, COMBINED, LOSS, NAME, PROC_COLUMN, TRAINING, TYPE
24 from ludwig.contrib import add_contrib_callback_args
25 from ludwig.features.feature_registries import base_type_registry, input_type_registry, output_type_registry
26 from ludwig.features.feature_utils import compute_feature_hash
27 from ludwig.globals import LUDWIG_VERSION
28 from ludwig.utils.data_utils import load_config_from_str
29 from ludwig.utils.misc_utils import get_from_registry, merge_dict, set_default_value
30 from ludwig.utils.print_utils import print_ludwig
31 
32 logger = logging.getLogger(__name__)
33 
34 default_random_seed = 42
35 
36 default_preprocessing_force_split = False
37 default_preprocessing_split_probabilities = (0.7, 0.1, 0.2)
38 default_preprocessing_stratify = None
39 
40 default_preprocessing_parameters = {
41     "force_split": default_preprocessing_force_split,
42     "split_probabilities": default_preprocessing_split_probabilities,
43     "stratify": default_preprocessing_stratify,
44 }
45 default_preprocessing_parameters.update(
46     {name: base_type.preprocessing_defaults() for name, base_type in base_type_registry.items()}
47 )
48 
49 default_combiner_type = "concat"
50 
51 default_training_params = {
52     "optimizer": {TYPE: "adam"},
53     "epochs": 100,
54     "regularization_lambda": 0,
55     "regularization_type": "l2",
56     "learning_rate": 0.001,
57     "batch_size": 128,
58     "eval_batch_size": None,
59     "early_stop": 5,
60     "reduce_learning_rate_on_plateau": 0,
61     "reduce_learning_rate_on_plateau_patience": 5,
62     "reduce_learning_rate_on_plateau_rate": 0.5,
63     "increase_batch_size_on_plateau": 0,
64     "increase_batch_size_on_plateau_patience": 5,
65     "increase_batch_size_on_plateau_rate": 2,
66     "increase_batch_size_on_plateau_max": 512,
67     "decay": False,
68     "decay_steps": 10000,
69     "decay_rate": 0.96,
70     "staircase": False,
71     "gradient_clipping": None,
72     "validation_field": COMBINED,
73     "validation_metric": LOSS,
74     "bucketing_field": None,
75     "learning_rate_warmup_epochs": 1,
76 }
77 
78 default_optimizer_params_registry = {
79     "sgd": {"lr": 0.001},
80     "stochastic_gradient_descent": {"lr": 0.001},
81     "gd": {"lr": 0.001},
82     "gradient_descent": {"lr": 0.001},
83     "adam": {
84         "betas": (0.9, 0.999),
85         # 'beta_1': 0.9,
86         # 'beta_2': 0.999,
87         # 'epsilon': 1e-08
88         "eps": 1e-08,
89     },
90     "adadelta": {
91         "rho": 0.95,
92         "eps": 1e-08
93         # 'epsilon': 1e-08
94     },
95     "adagrad": {"initial_accumulator_value": 0.1},
96     "adamax": {},
97     "ftrl": {
98         "learning_rate_power": -0.5,
99         "initial_accumulator_value": 0.1,
100         "l1_regularization_strength": 0.0,
101         "l2_regularization_strength": 0.0,
102     },
103     "nadam": {},
104     "rmsprop": {
105         "weight_decay": 0.9,
106         "momentum": 0.0,
107         # 'epsilon': 1e-10,
108         "eps": 1e-10,
109         "centered": False,
110     },
111 }
112 default_optimizer_params_registry["stochastic_gradient_descent"] = default_optimizer_params_registry["sgd"]
113 default_optimizer_params_registry["gd"] = default_optimizer_params_registry["sgd"]
114 default_optimizer_params_registry["gradient_descent"] = default_optimizer_params_registry["sgd"]
115 
116 
117 def get_default_optimizer_params(optimizer_type):
118     if optimizer_type in default_optimizer_params_registry:
119         return default_optimizer_params_registry[optimizer_type]
120     else:
121         raise ValueError("Incorrect optimizer type: " + optimizer_type)
122 
123 
124 def _perform_sanity_checks(config):
125     assert "input_features" in config, "config does not define any input features"
126 
127     assert "output_features" in config, "config does not define any output features"
128 
129     assert isinstance(config["input_features"], list), (
130         "Ludwig expects input features in a list. Check your model " "config format"
131     )
132 
133     assert isinstance(config["output_features"], list), (
134         "Ludwig expects output features in a list. Check your model " "config format"
135     )
136 
137     assert len(config["input_features"]) > 0, "config needs to have at least one input feature"
138 
139     assert len(config["output_features"]) > 0, "config needs to have at least one output feature"
140 
141     if TRAINING in config:
142         assert isinstance(config[TRAINING], dict), (
143             "There is an issue while reading the training section of the "
144             "config. The parameters are expected to be"
145             "read as a dictionary. Please check your config format."
146         )
147 
148     if "preprocessing" in config:
149         assert isinstance(config["preprocessing"], dict), (
150             "There is an issue while reading the preprocessing section of the "
151             "config. The parameters are expected to be read"
152             "as a dictionary. Please check your config format."
153         )
154 
155     if "combiner" in config:
156         assert isinstance(config["combiner"], dict), (
157             "There is an issue while reading the combiner section of the "
158             "config. The parameters are expected to be read"
159             "as a dictionary. Please check your config format."
160         )
161 
162 
163 def _set_feature_column(config: dict) -> None:
164     for feature in config["input_features"] + config["output_features"]:
165         if COLUMN not in feature:
166             feature[COLUMN] = feature[NAME]
167 
168 
169 def _set_proc_column(config: dict) -> None:
170     for feature in config["input_features"] + config["output_features"]:
171         if PROC_COLUMN not in feature:
172             feature[PROC_COLUMN] = compute_feature_hash(feature)
173 
174 
175 def _merge_hyperopt_with_training(config: dict) -> None:
176     if "hyperopt" not in config:
177         return
178 
179     scheduler = config["hyperopt"].get("sampler", {}).get("scheduler")
180     if not scheduler:
181         return
182 
183     if TRAINING not in config:
184         config[TRAINING] = {}
185 
186     # Disable early stopping when using a scheduler. We achieve this by setting the parameter
187     # to -1, which ensures the condition to apply early stopping is never met.
188     training = config[TRAINING]
189     early_stop = training.get("early_stop")
190     if early_stop is not None and early_stop != -1:
191         raise ValueError(
192             "Cannot set training parameter `early_stop` when using a hyperopt scheduler. "
193             "Unset this parameter in your config."
194         )
195     training["early_stop"] = -1
196 
197     # At most one of max_t and epochs may be specified by the user, and we set them to be equal to
198     # ensure that Ludwig does not stop training before the scheduler has finished the trial, unless
199     # max_t is in time_total_s, in which case we set epochs very high to continue train until stopped.
200     max_t = scheduler.get("max_t")
201     time_attr = scheduler.get("time_attr")
202     epochs = training.get("epochs")
203     if max_t is not None and epochs is not None and max_t != epochs and time_attr != "time_total_s":
204         raise ValueError(
205             "Cannot set training parameter `epochs` when using a hyperopt scheduler with `max_t`. "
206             "Unset one of these parameters in your config."
207         )
208     elif max_t is not None:
209         if time_attr == "time_total_s":
210             training["epochs"] = sys.maxsize  # essentially continue training until stopped
211         else:
212             training["epochs"] = max_t
213     elif epochs is not None:
214         scheduler["max_t"] = epochs
215 
216 
217 def merge_with_defaults(config):
218     config = copy.deepcopy(config)
219     _perform_sanity_checks(config)
220     _set_feature_column(config)
221     _set_proc_column(config)
222     _merge_hyperopt_with_training(config)
223 
224     # ===== Preprocessing =====
225     config["preprocessing"] = merge_dict(default_preprocessing_parameters, config.get("preprocessing", {}))
226 
227     stratify = config["preprocessing"]["stratify"]
228     if stratify is not None:
229         features = config["input_features"] + config["output_features"]
230         feature_names = {f[COLUMN] for f in features}
231         if stratify not in feature_names:
232             logger.warning("Stratify is not among the features. " "Cannot establish if it is a binary or category")
233         elif [f for f in features if f[COLUMN] == stratify][0][TYPE] not in {BINARY, CATEGORY}:
234             raise ValueError("Stratify feature must be binary or category")
235 
236     # ===== Training =====
237     set_default_value(config, TRAINING, default_training_params)
238 
239     for param, value in default_training_params.items():
240         set_default_value(config[TRAINING], param, value)
241 
242     set_default_value(
243         config[TRAINING],
244         "validation_metric",
245         output_type_registry[config["output_features"][0][TYPE]].default_validation_metric,
246     )
247 
248     # ===== Training Optimizer =====
249     optimizer = config[TRAINING]["optimizer"]
250     default_optimizer_params = get_default_optimizer_params(optimizer[TYPE])
251     for param in default_optimizer_params:
252         set_default_value(optimizer, param, default_optimizer_params[param])
253 
254     # ===== Input Features =====
255     for input_feature in config["input_features"]:
256         get_from_registry(input_feature[TYPE], input_type_registry).populate_defaults(input_feature)
257 
258     # ===== Combiner =====
259     set_default_value(config, "combiner", {TYPE: default_combiner_type})
260 
261     # ===== Output features =====
262     for output_feature in config["output_features"]:
263         get_from_registry(output_feature[TYPE], output_type_registry).populate_defaults(output_feature)
264 
265     return config
266 
267 
268 def render_config(config=None, output=None, **kwargs):
269     output_config = merge_with_defaults(config)
270     if output is None:
271         print(yaml.safe_dump(output_config, None, sort_keys=False))
272     else:
273         with open(output, "w") as f:
274             yaml.safe_dump(output_config, f, sort_keys=False)
275 
276 
277 def cli_render_config(sys_argv):
278     parser = argparse.ArgumentParser(
279         description="This script renders the full config from a user config.",
280         prog="ludwig render_config",
281         usage="%(prog)s [options]",
282     )
283     parser.add_argument(
284         "-c",
285         "--config",
286         type=load_config_from_str,
287         help="input user YAML config path",
288     )
289     parser.add_argument(
290         "-o",
291         "--output",
292         type=str,
293         help="output rendered YAML config path",
294         required=False,
295     )
296 
297     add_contrib_callback_args(parser)
298     args = parser.parse_args(sys_argv)
299 
300     args.callbacks = args.callbacks or []
301     for callback in args.callbacks:
302         callback.on_cmdline("render_config", *sys_argv)
303 
304     print_ludwig("Render Config", LUDWIG_VERSION)
305     render_config(**vars(args))
306
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/ludwig/utils/defaults.py b/ludwig/utils/defaults.py
--- a/ludwig/utils/defaults.py
+++ b/ludwig/utils/defaults.py
@@ -76,10 +76,10 @@
 }
 
 default_optimizer_params_registry = {
-    "sgd": {"lr": 0.001},
-    "stochastic_gradient_descent": {"lr": 0.001},
-    "gd": {"lr": 0.001},
-    "gradient_descent": {"lr": 0.001},
+    "sgd": {},
+    "stochastic_gradient_descent": {},
+    "gd": {},
+    "gradient_descent": {},
     "adam": {
         "betas": (0.9, 0.999),
         # 'beta_1': 0.9,
@@ -247,6 +247,7 @@
 
     # ===== Training Optimizer =====
     optimizer = config[TRAINING]["optimizer"]
+    set_default_value(optimizer, "lr", config[TRAINING]["learning_rate"])
     default_optimizer_params = get_default_optimizer_params(optimizer[TYPE])
     for param in default_optimizer_params:
         set_default_value(optimizer, param, default_optimizer_params[param])

{"golden_diff": "diff --git a/ludwig/utils/defaults.py b/ludwig/utils/defaults.py\n--- a/ludwig/utils/defaults.py\n+++ b/ludwig/utils/defaults.py\n@@ -76,10 +76,10 @@\n }\n \n default_optimizer_params_registry = {\n-    \"sgd\": {\"lr\": 0.001},\n-    \"stochastic_gradient_descent\": {\"lr\": 0.001},\n-    \"gd\": {\"lr\": 0.001},\n-    \"gradient_descent\": {\"lr\": 0.001},\n+    \"sgd\": {},\n+    \"stochastic_gradient_descent\": {},\n+    \"gd\": {},\n+    \"gradient_descent\": {},\n     \"adam\": {\n         \"betas\": (0.9, 0.999),\n         # 'beta_1': 0.9,\n@@ -247,6 +247,7 @@\n \n     # ===== Training Optimizer =====\n     optimizer = config[TRAINING][\"optimizer\"]\n+    set_default_value(optimizer, \"lr\", config[TRAINING][\"learning_rate\"])\n     default_optimizer_params = get_default_optimizer_params(optimizer[TYPE])\n     for param in default_optimizer_params:\n         set_default_value(optimizer, param, default_optimizer_params[param])\n", "issue": "Inconsistent learning rates shown in output when specifying learning rate in config \nWhen specifying the learning_rate parameter in the training section of the config, `description.json` shows a different default learning rate in the optimizer section:\r\n\r\nconfig.yaml:\r\n<img width=\"231\" alt=\"Screen Shot 2021-12-21 at 4 46 42 PM\" src=\"https://user-images.githubusercontent.com/687280/147016130-2856cfda-c399-4fc8-b79b-2fe96003b559.png\">\r\n\r\ndescription.json:\r\n<img width=\"698\" alt=\"Screen Shot 2021-12-21 at 4 47 04 PM\" src=\"https://user-images.githubusercontent.com/687280/147016222-60e44fd7-fd92-474d-b73d-17c5c62d5954.png\">\r\n\r\nExpected behavior is that there is one learning rate, and if it is specified in the config it should match that.\r\n\n", "before_files": [{"content": "#! /usr/bin/env python\n# Copyright (c) 2019 Uber Technologies, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# ==============================================================================\nimport argparse\nimport copy\nimport logging\nimport sys\n\nimport yaml\n\nfrom ludwig.constants import BINARY, CATEGORY, COLUMN, COMBINED, LOSS, NAME, PROC_COLUMN, TRAINING, TYPE\nfrom ludwig.contrib import add_contrib_callback_args\nfrom ludwig.features.feature_registries import base_type_registry, input_type_registry, output_type_registry\nfrom ludwig.features.feature_utils import compute_feature_hash\nfrom ludwig.globals import LUDWIG_VERSION\nfrom ludwig.utils.data_utils import load_config_from_str\nfrom ludwig.utils.misc_utils import get_from_registry, merge_dict, set_default_value\nfrom ludwig.utils.print_utils import print_ludwig\n\nlogger = logging.getLogger(__name__)\n\ndefault_random_seed = 42\n\ndefault_preprocessing_force_split = False\ndefault_preprocessing_split_probabilities = (0.7, 0.1, 0.2)\ndefault_preprocessing_stratify = None\n\ndefault_preprocessing_parameters = {\n    \"force_split\": default_preprocessing_force_split,\n    \"split_probabilities\": default_preprocessing_split_probabilities,\n    \"stratify\": default_preprocessing_stratify,\n}\ndefault_preprocessing_parameters.update(\n    {name: base_type.preprocessing_defaults() for name, base_type in base_type_registry.items()}\n)\n\ndefault_combiner_type = \"concat\"\n\ndefault_training_params = {\n    \"optimizer\": {TYPE: \"adam\"},\n    \"epochs\": 100,\n    \"regularization_lambda\": 0,\n    \"regularization_type\": \"l2\",\n    \"learning_rate\": 0.001,\n    \"batch_size\": 128,\n    \"eval_batch_size\": None,\n    \"early_stop\": 5,\n    \"reduce_learning_rate_on_plateau\": 0,\n    \"reduce_learning_rate_on_plateau_patience\": 5,\n    \"reduce_learning_rate_on_plateau_rate\": 0.5,\n    \"increase_batch_size_on_plateau\": 0,\n    \"increase_batch_size_on_plateau_patience\": 5,\n    \"increase_batch_size_on_plateau_rate\": 2,\n    \"increase_batch_size_on_plateau_max\": 512,\n    \"decay\": False,\n    \"decay_steps\": 10000,\n    \"decay_rate\": 0.96,\n    \"staircase\": False,\n    \"gradient_clipping\": None,\n    \"validation_field\": COMBINED,\n    \"validation_metric\": LOSS,\n    \"bucketing_field\": None,\n    \"learning_rate_warmup_epochs\": 1,\n}\n\ndefault_optimizer_params_registry = {\n    \"sgd\": {\"lr\": 0.001},\n    \"stochastic_gradient_descent\": {\"lr\": 0.001},\n    \"gd\": {\"lr\": 0.001},\n    \"gradient_descent\": {\"lr\": 0.001},\n    \"adam\": {\n        \"betas\": (0.9, 0.999),\n        # 'beta_1': 0.9,\n        # 'beta_2': 0.999,\n        # 'epsilon': 1e-08\n        \"eps\": 1e-08,\n    },\n    \"adadelta\": {\n        \"rho\": 0.95,\n        \"eps\": 1e-08\n        # 'epsilon': 1e-08\n    },\n    \"adagrad\": {\"initial_accumulator_value\": 0.1},\n    \"adamax\": {},\n    \"ftrl\": {\n        \"learning_rate_power\": -0.5,\n        \"initial_accumulator_value\": 0.1,\n        \"l1_regularization_strength\": 0.0,\n        \"l2_regularization_strength\": 0.0,\n    },\n    \"nadam\": {},\n    \"rmsprop\": {\n        \"weight_decay\": 0.9,\n        \"momentum\": 0.0,\n        # 'epsilon': 1e-10,\n        \"eps\": 1e-10,\n        \"centered\": False,\n    },\n}\ndefault_optimizer_params_registry[\"stochastic_gradient_descent\"] = default_optimizer_params_registry[\"sgd\"]\ndefault_optimizer_params_registry[\"gd\"] = default_optimizer_params_registry[\"sgd\"]\ndefault_optimizer_params_registry[\"gradient_descent\"] = default_optimizer_params_registry[\"sgd\"]\n\n\ndef get_default_optimizer_params(optimizer_type):\n    if optimizer_type in default_optimizer_params_registry:\n        return default_optimizer_params_registry[optimizer_type]\n    else:\n        raise ValueError(\"Incorrect optimizer type: \" + optimizer_type)\n\n\ndef _perform_sanity_checks(config):\n    assert \"input_features\" in config, \"config does not define any input features\"\n\n    assert \"output_features\" in config, \"config does not define any output features\"\n\n    assert isinstance(config[\"input_features\"], list), (\n        \"Ludwig expects input features in a list. Check your model \" \"config format\"\n    )\n\n    assert isinstance(config[\"output_features\"], list), (\n        \"Ludwig expects output features in a list. Check your model \" \"config format\"\n    )\n\n    assert len(config[\"input_features\"]) > 0, \"config needs to have at least one input feature\"\n\n    assert len(config[\"output_features\"]) > 0, \"config needs to have at least one output feature\"\n\n    if TRAINING in config:\n        assert isinstance(config[TRAINING], dict), (\n            \"There is an issue while reading the training section of the \"\n            \"config. The parameters are expected to be\"\n            \"read as a dictionary. Please check your config format.\"\n        )\n\n    if \"preprocessing\" in config:\n        assert isinstance(config[\"preprocessing\"], dict), (\n            \"There is an issue while reading the preprocessing section of the \"\n            \"config. The parameters are expected to be read\"\n            \"as a dictionary. Please check your config format.\"\n        )\n\n    if \"combiner\" in config:\n        assert isinstance(config[\"combiner\"], dict), (\n            \"There is an issue while reading the combiner section of the \"\n            \"config. The parameters are expected to be read\"\n            \"as a dictionary. Please check your config format.\"\n        )\n\n\ndef _set_feature_column(config: dict) -> None:\n    for feature in config[\"input_features\"] + config[\"output_features\"]:\n        if COLUMN not in feature:\n            feature[COLUMN] = feature[NAME]\n\n\ndef _set_proc_column(config: dict) -> None:\n    for feature in config[\"input_features\"] + config[\"output_features\"]:\n        if PROC_COLUMN not in feature:\n            feature[PROC_COLUMN] = compute_feature_hash(feature)\n\n\ndef _merge_hyperopt_with_training(config: dict) -> None:\n    if \"hyperopt\" not in config:\n        return\n\n    scheduler = config[\"hyperopt\"].get(\"sampler\", {}).get(\"scheduler\")\n    if not scheduler:\n        return\n\n    if TRAINING not in config:\n        config[TRAINING] = {}\n\n    # Disable early stopping when using a scheduler. We achieve this by setting the parameter\n    # to -1, which ensures the condition to apply early stopping is never met.\n    training = config[TRAINING]\n    early_stop = training.get(\"early_stop\")\n    if early_stop is not None and early_stop != -1:\n        raise ValueError(\n            \"Cannot set training parameter `early_stop` when using a hyperopt scheduler. \"\n            \"Unset this parameter in your config.\"\n        )\n    training[\"early_stop\"] = -1\n\n    # At most one of max_t and epochs may be specified by the user, and we set them to be equal to\n    # ensure that Ludwig does not stop training before the scheduler has finished the trial, unless\n    # max_t is in time_total_s, in which case we set epochs very high to continue train until stopped.\n    max_t = scheduler.get(\"max_t\")\n    time_attr = scheduler.get(\"time_attr\")\n    epochs = training.get(\"epochs\")\n    if max_t is not None and epochs is not None and max_t != epochs and time_attr != \"time_total_s\":\n        raise ValueError(\n            \"Cannot set training parameter `epochs` when using a hyperopt scheduler with `max_t`. \"\n            \"Unset one of these parameters in your config.\"\n        )\n    elif max_t is not None:\n        if time_attr == \"time_total_s\":\n            training[\"epochs\"] = sys.maxsize  # essentially continue training until stopped\n        else:\n            training[\"epochs\"] = max_t\n    elif epochs is not None:\n        scheduler[\"max_t\"] = epochs\n\n\ndef merge_with_defaults(config):\n    config = copy.deepcopy(config)\n    _perform_sanity_checks(config)\n    _set_feature_column(config)\n    _set_proc_column(config)\n    _merge_hyperopt_with_training(config)\n\n    # ===== Preprocessing =====\n    config[\"preprocessing\"] = merge_dict(default_preprocessing_parameters, config.get(\"preprocessing\", {}))\n\n    stratify = config[\"preprocessing\"][\"stratify\"]\n    if stratify is not None:\n        features = config[\"input_features\"] + config[\"output_features\"]\n        feature_names = {f[COLUMN] for f in features}\n        if stratify not in feature_names:\n            logger.warning(\"Stratify is not among the features. \" \"Cannot establish if it is a binary or category\")\n        elif [f for f in features if f[COLUMN] == stratify][0][TYPE] not in {BINARY, CATEGORY}:\n            raise ValueError(\"Stratify feature must be binary or category\")\n\n    # ===== Training =====\n    set_default_value(config, TRAINING, default_training_params)\n\n    for param, value in default_training_params.items():\n        set_default_value(config[TRAINING], param, value)\n\n    set_default_value(\n        config[TRAINING],\n        \"validation_metric\",\n        output_type_registry[config[\"output_features\"][0][TYPE]].default_validation_metric,\n    )\n\n    # ===== Training Optimizer =====\n    optimizer = config[TRAINING][\"optimizer\"]\n    default_optimizer_params = get_default_optimizer_params(optimizer[TYPE])\n    for param in default_optimizer_params:\n        set_default_value(optimizer, param, default_optimizer_params[param])\n\n    # ===== Input Features =====\n    for input_feature in config[\"input_features\"]:\n        get_from_registry(input_feature[TYPE], input_type_registry).populate_defaults(input_feature)\n\n    # ===== Combiner =====\n    set_default_value(config, \"combiner\", {TYPE: default_combiner_type})\n\n    # ===== Output features =====\n    for output_feature in config[\"output_features\"]:\n        get_from_registry(output_feature[TYPE], output_type_registry).populate_defaults(output_feature)\n\n    return config\n\n\ndef render_config(config=None, output=None, **kwargs):\n    output_config = merge_with_defaults(config)\n    if output is None:\n        print(yaml.safe_dump(output_config, None, sort_keys=False))\n    else:\n        with open(output, \"w\") as f:\n            yaml.safe_dump(output_config, f, sort_keys=False)\n\n\ndef cli_render_config(sys_argv):\n    parser = argparse.ArgumentParser(\n        description=\"This script renders the full config from a user config.\",\n        prog=\"ludwig render_config\",\n        usage=\"%(prog)s [options]\",\n    )\n    parser.add_argument(\n        \"-c\",\n        \"--config\",\n        type=load_config_from_str,\n        help=\"input user YAML config path\",\n    )\n    parser.add_argument(\n        \"-o\",\n        \"--output\",\n        type=str,\n        help=\"output rendered YAML config path\",\n        required=False,\n    )\n\n    add_contrib_callback_args(parser)\n    args = parser.parse_args(sys_argv)\n\n    args.callbacks = args.callbacks or []\n    for callback in args.callbacks:\n        callback.on_cmdline(\"render_config\", *sys_argv)\n\n    print_ludwig(\"Render Config\", LUDWIG_VERSION)\n    render_config(**vars(args))\n", "path": "ludwig/utils/defaults.py"}], "after_files": [{"content": "#! /usr/bin/env python\n# Copyright (c) 2019 Uber Technologies, Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# ==============================================================================\nimport argparse\nimport copy\nimport logging\nimport sys\n\nimport yaml\n\nfrom ludwig.constants import BINARY, CATEGORY, COLUMN, COMBINED, LOSS, NAME, PROC_COLUMN, TRAINING, TYPE\nfrom ludwig.contrib import add_contrib_callback_args\nfrom ludwig.features.feature_registries import base_type_registry, input_type_registry, output_type_registry\nfrom ludwig.features.feature_utils import compute_feature_hash\nfrom ludwig.globals import LUDWIG_VERSION\nfrom ludwig.utils.data_utils import load_config_from_str\nfrom ludwig.utils.misc_utils import get_from_registry, merge_dict, set_default_value\nfrom ludwig.utils.print_utils import print_ludwig\n\nlogger = logging.getLogger(__name__)\n\ndefault_random_seed = 42\n\ndefault_preprocessing_force_split = False\ndefault_preprocessing_split_probabilities = (0.7, 0.1, 0.2)\ndefault_preprocessing_stratify = None\n\ndefault_preprocessing_parameters = {\n    \"force_split\": default_preprocessing_force_split,\n    \"split_probabilities\": default_preprocessing_split_probabilities,\n    \"stratify\": default_preprocessing_stratify,\n}\ndefault_preprocessing_parameters.update(\n    {name: base_type.preprocessing_defaults() for name, base_type in base_type_registry.items()}\n)\n\ndefault_combiner_type = \"concat\"\n\ndefault_training_params = {\n    \"optimizer\": {TYPE: \"adam\"},\n    \"epochs\": 100,\n    \"regularization_lambda\": 0,\n    \"regularization_type\": \"l2\",\n    \"learning_rate\": 0.001,\n    \"batch_size\": 128,\n    \"eval_batch_size\": None,\n    \"early_stop\": 5,\n    \"reduce_learning_rate_on_plateau\": 0,\n    \"reduce_learning_rate_on_plateau_patience\": 5,\n    \"reduce_learning_rate_on_plateau_rate\": 0.5,\n    \"increase_batch_size_on_plateau\": 0,\n    \"increase_batch_size_on_plateau_patience\": 5,\n    \"increase_batch_size_on_plateau_rate\": 2,\n    \"increase_batch_size_on_plateau_max\": 512,\n    \"decay\": False,\n    \"decay_steps\": 10000,\n    \"decay_rate\": 0.96,\n    \"staircase\": False,\n    \"gradient_clipping\": None,\n    \"validation_field\": COMBINED,\n    \"validation_metric\": LOSS,\n    \"bucketing_field\": None,\n    \"learning_rate_warmup_epochs\": 1,\n}\n\ndefault_optimizer_params_registry = {\n    \"sgd\": {},\n    \"stochastic_gradient_descent\": {},\n    \"gd\": {},\n    \"gradient_descent\": {},\n    \"adam\": {\n        \"betas\": (0.9, 0.999),\n        # 'beta_1': 0.9,\n        # 'beta_2': 0.999,\n        # 'epsilon': 1e-08\n        \"eps\": 1e-08,\n    },\n    \"adadelta\": {\n        \"rho\": 0.95,\n        \"eps\": 1e-08\n        # 'epsilon': 1e-08\n    },\n    \"adagrad\": {\"initial_accumulator_value\": 0.1},\n    \"adamax\": {},\n    \"ftrl\": {\n        \"learning_rate_power\": -0.5,\n        \"initial_accumulator_value\": 0.1,\n        \"l1_regularization_strength\": 0.0,\n        \"l2_regularization_strength\": 0.0,\n    },\n    \"nadam\": {},\n    \"rmsprop\": {\n        \"weight_decay\": 0.9,\n        \"momentum\": 0.0,\n        # 'epsilon': 1e-10,\n        \"eps\": 1e-10,\n        \"centered\": False,\n    },\n}\ndefault_optimizer_params_registry[\"stochastic_gradient_descent\"] = default_optimizer_params_registry[\"sgd\"]\ndefault_optimizer_params_registry[\"gd\"] = default_optimizer_params_registry[\"sgd\"]\ndefault_optimizer_params_registry[\"gradient_descent\"] = default_optimizer_params_registry[\"sgd\"]\n\n\ndef get_default_optimizer_params(optimizer_type):\n    if optimizer_type in default_optimizer_params_registry:\n        return default_optimizer_params_registry[optimizer_type]\n    else:\n        raise ValueError(\"Incorrect optimizer type: \" + optimizer_type)\n\n\ndef _perform_sanity_checks(config):\n    assert \"input_features\" in config, \"config does not define any input features\"\n\n    assert \"output_features\" in config, \"config does not define any output features\"\n\n    assert isinstance(config[\"input_features\"], list), (\n        \"Ludwig expects input features in a list. Check your model \" \"config format\"\n    )\n\n    assert isinstance(config[\"output_features\"], list), (\n        \"Ludwig expects output features in a list. Check your model \" \"config format\"\n    )\n\n    assert len(config[\"input_features\"]) > 0, \"config needs to have at least one input feature\"\n\n    assert len(config[\"output_features\"]) > 0, \"config needs to have at least one output feature\"\n\n    if TRAINING in config:\n        assert isinstance(config[TRAINING], dict), (\n            \"There is an issue while reading the training section of the \"\n            \"config. The parameters are expected to be\"\n            \"read as a dictionary. Please check your config format.\"\n        )\n\n    if \"preprocessing\" in config:\n        assert isinstance(config[\"preprocessing\"], dict), (\n            \"There is an issue while reading the preprocessing section of the \"\n            \"config. The parameters are expected to be read\"\n            \"as a dictionary. Please check your config format.\"\n        )\n\n    if \"combiner\" in config:\n        assert isinstance(config[\"combiner\"], dict), (\n            \"There is an issue while reading the combiner section of the \"\n            \"config. The parameters are expected to be read\"\n            \"as a dictionary. Please check your config format.\"\n        )\n\n\ndef _set_feature_column(config: dict) -> None:\n    for feature in config[\"input_features\"] + config[\"output_features\"]:\n        if COLUMN not in feature:\n            feature[COLUMN] = feature[NAME]\n\n\ndef _set_proc_column(config: dict) -> None:\n    for feature in config[\"input_features\"] + config[\"output_features\"]:\n        if PROC_COLUMN not in feature:\n            feature[PROC_COLUMN] = compute_feature_hash(feature)\n\n\ndef _merge_hyperopt_with_training(config: dict) -> None:\n    if \"hyperopt\" not in config:\n        return\n\n    scheduler = config[\"hyperopt\"].get(\"sampler\", {}).get(\"scheduler\")\n    if not scheduler:\n        return\n\n    if TRAINING not in config:\n        config[TRAINING] = {}\n\n    # Disable early stopping when using a scheduler. We achieve this by setting the parameter\n    # to -1, which ensures the condition to apply early stopping is never met.\n    training = config[TRAINING]\n    early_stop = training.get(\"early_stop\")\n    if early_stop is not None and early_stop != -1:\n        raise ValueError(\n            \"Cannot set training parameter `early_stop` when using a hyperopt scheduler. \"\n            \"Unset this parameter in your config.\"\n        )\n    training[\"early_stop\"] = -1\n\n    # At most one of max_t and epochs may be specified by the user, and we set them to be equal to\n    # ensure that Ludwig does not stop training before the scheduler has finished the trial, unless\n    # max_t is in time_total_s, in which case we set epochs very high to continue train until stopped.\n    max_t = scheduler.get(\"max_t\")\n    time_attr = scheduler.get(\"time_attr\")\n    epochs = training.get(\"epochs\")\n    if max_t is not None and epochs is not None and max_t != epochs and time_attr != \"time_total_s\":\n        raise ValueError(\n            \"Cannot set training parameter `epochs` when using a hyperopt scheduler with `max_t`. \"\n            \"Unset one of these parameters in your config.\"\n        )\n    elif max_t is not None:\n        if time_attr == \"time_total_s\":\n            training[\"epochs\"] = sys.maxsize  # essentially continue training until stopped\n        else:\n            training[\"epochs\"] = max_t\n    elif epochs is not None:\n        scheduler[\"max_t\"] = epochs\n\n\ndef merge_with_defaults(config):\n    config = copy.deepcopy(config)\n    _perform_sanity_checks(config)\n    _set_feature_column(config)\n    _set_proc_column(config)\n    _merge_hyperopt_with_training(config)\n\n    # ===== Preprocessing =====\n    config[\"preprocessing\"] = merge_dict(default_preprocessing_parameters, config.get(\"preprocessing\", {}))\n\n    stratify = config[\"preprocessing\"][\"stratify\"]\n    if stratify is not None:\n        features = config[\"input_features\"] + config[\"output_features\"]\n        feature_names = {f[COLUMN] for f in features}\n        if stratify not in feature_names:\n            logger.warning(\"Stratify is not among the features. \" \"Cannot establish if it is a binary or category\")\n        elif [f for f in features if f[COLUMN] == stratify][0][TYPE] not in {BINARY, CATEGORY}:\n            raise ValueError(\"Stratify feature must be binary or category\")\n\n    # ===== Training =====\n    set_default_value(config, TRAINING, default_training_params)\n\n    for param, value in default_training_params.items():\n        set_default_value(config[TRAINING], param, value)\n\n    set_default_value(\n        config[TRAINING],\n        \"validation_metric\",\n        output_type_registry[config[\"output_features\"][0][TYPE]].default_validation_metric,\n    )\n\n    # ===== Training Optimizer =====\n    optimizer = config[TRAINING][\"optimizer\"]\n    set_default_value(optimizer, \"lr\", config[TRAINING][\"learning_rate\"])\n    default_optimizer_params = get_default_optimizer_params(optimizer[TYPE])\n    for param in default_optimizer_params:\n        set_default_value(optimizer, param, default_optimizer_params[param])\n\n    # ===== Input Features =====\n    for input_feature in config[\"input_features\"]:\n        get_from_registry(input_feature[TYPE], input_type_registry).populate_defaults(input_feature)\n\n    # ===== Combiner =====\n    set_default_value(config, \"combiner\", {TYPE: default_combiner_type})\n\n    # ===== Output features =====\n    for output_feature in config[\"output_features\"]:\n        get_from_registry(output_feature[TYPE], output_type_registry).populate_defaults(output_feature)\n\n    return config\n\n\ndef render_config(config=None, output=None, **kwargs):\n    output_config = merge_with_defaults(config)\n    if output is None:\n        print(yaml.safe_dump(output_config, None, sort_keys=False))\n    else:\n        with open(output, \"w\") as f:\n            yaml.safe_dump(output_config, f, sort_keys=False)\n\n\ndef cli_render_config(sys_argv):\n    parser = argparse.ArgumentParser(\n        description=\"This script renders the full config from a user config.\",\n        prog=\"ludwig render_config\",\n        usage=\"%(prog)s [options]\",\n    )\n    parser.add_argument(\n        \"-c\",\n        \"--config\",\n        type=load_config_from_str,\n        help=\"input user YAML config path\",\n    )\n    parser.add_argument(\n        \"-o\",\n        \"--output\",\n        type=str,\n        help=\"output rendered YAML config path\",\n        required=False,\n    )\n\n    add_contrib_callback_args(parser)\n    args = parser.parse_args(sys_argv)\n\n    args.callbacks = args.callbacks or []\n    for callback in args.callbacks:\n        callback.on_cmdline(\"render_config\", *sys_argv)\n\n    print_ludwig(\"Render Config\", LUDWIG_VERSION)\n    render_config(**vars(args))\n", "path": "ludwig/utils/defaults.py"}]}