Repository: huggingface/huggingface_hub

Issue:
Hyperparameters should be parsed recursively for Keras mixin
In this [model card](https://huggingface.co/keras-io/SimSiam) I realized that the learning rate scheduler break the table parsing (I didn't observe this case for other learning rates 😅).
The ideal table looks like the [one in this model card](https://huggingface.co/keras-io/ProbabalisticBayesianModel-Wine).
Would you like to take a look? 👀  @ariG23498 



Discussion:
# Problem

With the [`_create_hyperparameter_table`](https://github.com/huggingface/huggingface_hub/blob/00fad17efbcd0d0a2b77cde03aa01036b4983562/src/huggingface_hub/keras_mixin.py#L29) function, we are essentially picking up the configurations of the optimizer (`model.optimizer.get_config()`). The configuration of the optimier is a `dict`. The problem begins when this `dict` becomes a `nested dict`. With the [model card of SimSiam](https://huggingface.co/keras-io/SimSiam) the configuration is nested due to the learning rate schedule (which has its own configurations like `initial_learning_rate `, `decay_steps` etc.).

# Proposed Solution

We could flatten out the nested dictionary like so
```python
config = {
    "a": 1,
    "b": 2,
    "c": {
        "c_1": 3,
        "c_2": 3,
        "c_3": 3,
        "c_4": {
            "c_4_1": 4,
            "c_4_2": 4,
        },

    }
}
```
to
```python
config = {
    "a": 1,
    "b": 2,
    "c.c_1": 3,
    "c.c_2": 3,
    "c.c_3": 3,
    "c.c_4.c_4_1": 4,
    "c.c_4.c_4_2": 4,
}
```
and then begin with our creation of tables. This will ensure that the  [`_create_hyperparameter_table`](https://github.com/huggingface/huggingface_hub/blob/00fad17efbcd0d0a2b77cde03aa01036b4983562/src/huggingface_hub/keras_mixin.py#L29) function is left untouched and we are not specifying our solution to only `learning_rate_schedulers`.

I have built a [colab notebook](https://colab.research.google.com/gist/ariG23498/407ca943d82168081b3ec77529ba1b9b/scratchpad.ipynb) for the same to demonstrate how the functions would work together.

# Some results:
- With a normal learning rate the table looks like this:

| name | learning_rate | decay | beta_1 | beta_2 | epsilon | amsgrad | training_precision |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| Adam | 0.001 | 0.0 | 0.9 | 0.999 | 1e-07 | False | float32 |

- With a learning rate schedule the table looks like this:

| name | learning_rate.class_name | learning_rate.config.initial_learning_rate | learning_rate.config.decay_steps | learning_rate.config.alpha | learning_rate.config.name | decay | beta_1 | beta_2 | epsilon | amsgrad | training_precision |
| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: |
| Adam | CosineDecay | 0.03 | 10 | 0.0 | None | 0.0 | 0.9 | 0.999 | 1e-07 | False | float32 |


Hope this helps!
this is super neat, would you like to open a PR to mixin? @ariG23498 

Resolving patch:
diff --git a/src/huggingface_hub/keras_mixin.py b/src/huggingface_hub/keras_mixin.py
--- a/src/huggingface_hub/keras_mixin.py
+++ b/src/huggingface_hub/keras_mixin.py
@@ -1,3 +1,4 @@
+import collections.abc as collections
 import json
 import os
 import warnings
@@ -26,10 +27,41 @@
     import tensorflow as tf
 
 
+def _flatten_dict(dictionary, parent_key=""):
+    """Flatten a nested dictionary.
+    Reference: https://stackoverflow.com/a/6027615/10319735
+
+    Args:
+        dictionary (`dict`):
+            The nested dictionary to be flattened.
+        parent_key (`str`):
+            The parent key to be prefixed to the childer keys.
+            Necessary for recursing over the nested dictionary.
+
+    Returns:
+        The flattened dictionary.
+    """
+    items = []
+    for key, value in dictionary.items():
+        new_key = f"{parent_key}.{key}" if parent_key else key
+        if isinstance(value, collections.MutableMapping):
+            items.extend(
+                _flatten_dict(
+                    value,
+                    new_key,
+                ).items()
+            )
+        else:
+            items.append((new_key, value))
+    return dict(items)
+
+
 def _create_hyperparameter_table(model):
     """Parse hyperparameter dictionary into a markdown table."""
     if model.optimizer is not None:
         optimizer_params = model.optimizer.get_config()
+        # flatten the configuration
+        optimizer_params = _flatten_dict(optimizer_params)
         optimizer_params[
             "training_precision"
         ] = tf.keras.mixed_precision.global_policy().name


Test patch: