@experimental
def autolog():
    '\n    Enables automatic logging from Keras to MLflow. Autologging captures the following information:\n\n    **Metrics** and **Parameters**\n     - Training loss; validation loss; user-specified metrics\n     - Metrics associated with the ``EarlyStopping`` callbacks: ``stopped_epoch``,\n       ``restored_epoch``, ``restore_best_weight``, ``last_epoch``, etc\n     - ``fit()`` or ``fit_generator()`` parameters; optimizer name; learning rate; epsilon\n     - ``fit()`` or ``fit_generator()`` parameters associated with ``EarlyStopping``: ``min_delta``,\n       ``patience``, ``baseline``, ``restore_best_weights``, etc\n    **Artifacts**\n     - Model summary on training start\n     - `MLflow Model <https://mlflow.org/docs/latest/models.html>`_ (Keras model) on training end\n\n    .. code-block:: python\n        :caption: Example\n\n        import mlflow\n        import mlflow.keras\n        # Build, compile, enable autologging, and train your model\n        keras_model = ...\n        keras_model.compile(optimizer="rmsprop", loss="mse", metrics=["accuracy"])\n        # autolog your metrics, parameters, and model\n        mlflow.keras.autolog()\n        results = keras_model.fit(\n            x_train, y_train, epochs=20, batch_size=128, validation_data=(x_val, y_val))\n\n    ``EarlyStopping Integration with Keras AutoLogging``\n\n    MLflow will detect if an ``EarlyStopping`` callback is used in a ``fit()`` or\n    ``fit_generator()`` call, and if the ``restore_best_weights`` parameter is set to be ``True``,\n    then MLflow will log the metrics associated with the restored model as a final, extra step.\n    The epoch of the restored model will also be logged as the metric ``restored_epoch``.\n    This allows for easy comparison between the actual metrics of the restored model and\n    the metrics of other models.\n\n    If ``restore_best_weights`` is set to be ``False``, then MLflow will not log an additional step.\n\n    Regardless of ``restore_best_weights``, MLflow will also log ``stopped_epoch``,\n    which indicates the epoch at which training stopped due to early stopping.\n\n    If training does not end due to early stopping, then ``stopped_epoch`` will be logged as ``0``.\n\n    MLflow will also log the parameters of the ``EarlyStopping`` callback,\n    excluding ``mode`` and ``verbose``.\n    '
    import keras

    class __MLflowKerasCallback(keras.callbacks.Callback):
        '\n        Callback for auto-logging metrics and parameters.\n        Records available logs after each epoch.\n        Records model structural information as params when training begins\n        '

        def on_train_begin(self, logs=None):
            try_mlflow_log(mlflow.log_param, 'num_layers', len(self.model.layers))
            try_mlflow_log(mlflow.log_param, 'optimizer_name', type(self.model.optimizer).__name__)
            if hasattr(self.model.optimizer, 'lr'):
                lr = (self.model.optimizer.lr if (type(self.model.optimizer.lr) is float) else keras.backend.eval(self.model.optimizer.lr))
                try_mlflow_log(mlflow.log_param, 'learning_rate', lr)
            if hasattr(self.model.optimizer, 'epsilon'):
                epsilon = (self.model.optimizer.epsilon if (type(self.model.optimizer.epsilon) is float) else keras.backend.eval(self.model.optimizer.epsilon))
                try_mlflow_log(mlflow.log_param, 'epsilon', epsilon)
            sum_list = []
            self.model.summary(print_fn=sum_list.append)
            summary = '\n'.join(sum_list)
            tempdir = tempfile.mkdtemp()
            try:
                summary_file = os.path.join(tempdir, 'model_summary.txt')
                with open(summary_file, 'w') as f:
                    f.write(summary)
                try_mlflow_log(mlflow.log_artifact, local_path=summary_file)
            finally:
                shutil.rmtree(tempdir)

        def on_epoch_end(self, epoch, logs=None):
            if (not logs):
                return
            try_mlflow_log(mlflow.log_metrics, logs, step=epoch)

        def on_train_end(self, logs=None):
            try_mlflow_log(log_model, self.model, artifact_path='model')

        def _implements_train_batch_hooks(self):
            return False

        def _implements_test_batch_hooks(self):
            return False

        def _implements_predict_batch_hooks(self):
            return False

    def _early_stop_check(callbacks):
        if (LooseVersion(keras.__version__) < LooseVersion('2.3.0')):
            es_callback = keras.callbacks.EarlyStopping
        else:
            es_callback = keras.callbacks.callbacks.EarlyStopping
        for callback in callbacks:
            if isinstance(callback, es_callback):
                return callback
        return None

    def _log_early_stop_callback_params(callback):
        if callback:
            try:
                earlystopping_params = {'monitor': callback.monitor, 'min_delta': callback.min_delta, 'patience': callback.patience, 'baseline': callback.baseline, 'restore_best_weights': callback.restore_best_weights}
                try_mlflow_log(mlflow.log_params, earlystopping_params)
            except Exception:
                return

    def _get_early_stop_callback_attrs(callback):
        try:
            return (callback.stopped_epoch, callback.restore_best_weights, callback.patience)
        except Exception:
            return None

    def _log_early_stop_callback_metrics(callback, history):
        if callback:
            callback_attrs = _get_early_stop_callback_attrs(callback)
            if (callback_attrs is None):
                return
            (stopped_epoch, restore_best_weights, patience) = callback_attrs
            try_mlflow_log(mlflow.log_metric, 'stopped_epoch', stopped_epoch)
            if ((stopped_epoch != 0) and restore_best_weights):
                restored_epoch = (stopped_epoch - max(1, patience))
                try_mlflow_log(mlflow.log_metric, 'restored_epoch', restored_epoch)
                restored_metrics = {key: history.history[key][restored_epoch] for key in history.history.keys()}
                metric_key = next(iter(history.history), None)
                if (metric_key is not None):
                    last_epoch = len(history.history[metric_key])
                    try_mlflow_log(mlflow.log_metrics, restored_metrics, step=last_epoch)

    def _run_and_log_function(self, original, args, kwargs, unlogged_params, callback_arg_index):
        if (not mlflow.active_run()):
            try_mlflow_log(mlflow.start_run)
            auto_end_run = True
        else:
            auto_end_run = False
        log_fn_args_as_params(original, args, kwargs, unlogged_params)
        early_stop_callback = None
        if (len(args) > callback_arg_index):
            tmp_list = list(args)
            early_stop_callback = _early_stop_check(tmp_list[callback_arg_index])
            tmp_list[callback_arg_index] += [__MLflowKerasCallback()]
            args = tuple(tmp_list)
        elif ('callbacks' in kwargs):
            early_stop_callback = _early_stop_check(kwargs['callbacks'])
            kwargs['callbacks'] += [__MLflowKerasCallback()]
        else:
            kwargs['callbacks'] = [__MLflowKerasCallback()]
        _log_early_stop_callback_params(early_stop_callback)
        history = original(self, *args, **kwargs)
        _log_early_stop_callback_metrics(early_stop_callback, history)
        if auto_end_run:
            try_mlflow_log(mlflow.end_run)
        return history

    @gorilla.patch(keras.Model)
    def fit(self, *args, **kwargs):
        original = gorilla.get_original_attribute(keras.Model, 'fit')
        unlogged_params = ['self', 'x', 'y', 'callbacks', 'validation_data', 'verbose']
        return _run_and_log_function(self, original, args, kwargs, unlogged_params, 5)

    @gorilla.patch(keras.Model)
    def fit_generator(self, *args, **kwargs):
        original = gorilla.get_original_attribute(keras.Model, 'fit_generator')
        unlogged_params = ['self', 'generator', 'callbacks', 'validation_data', 'verbose']
        return _run_and_log_function(self, original, args, kwargs, unlogged_params, 4)
    settings = gorilla.Settings(allow_hit=True, store_hit=True)
    gorilla.apply(gorilla.Patch(keras.Model, 'fit', fit, settings=settings))
    gorilla.apply(gorilla.Patch(keras.Model, 'fit_generator', fit_generator, settings=settings))
Enables automatic logging from Keras to MLflow. Autologging captures the following information:

**Metrics** and **Parameters**
 - Training loss; validation loss; user-specified metrics
 - Metrics associated with the ``EarlyStopping`` callbacks: ``stopped_epoch``,
   ``restored_epoch``, ``restore_best_weight``, ``last_epoch``, etc
 - ``fit()`` or ``fit_generator()`` parameters; optimizer name; learning rate; epsilon
 - ``fit()`` or ``fit_generator()`` parameters associated with ``EarlyStopping``: ``min_delta``,
   ``patience``, ``baseline``, ``restore_best_weights``, etc
**Artifacts**
 - Model summary on training start
 - `MLflow Model <https://mlflow.org/docs/latest/models.html>`_ (Keras model) on training end

.. code-block:: python
    :caption: Example

    import mlflow
    import mlflow.keras
    # Build, compile, enable autologging, and train your model
    keras_model = ...
    keras_model.compile(optimizer="rmsprop", loss="mse", metrics=["accuracy"])
    # autolog your metrics, parameters, and model
    mlflow.keras.autolog()
    results = keras_model.fit(
        x_train, y_train, epochs=20, batch_size=128, validation_data=(x_val, y_val))

``EarlyStopping Integration with Keras AutoLogging``

MLflow will detect if an ``EarlyStopping`` callback is used in a ``fit()`` or
``fit_generator()`` call, and if the ``restore_best_weights`` parameter is set to be ``True``,
then MLflow will log the metrics associated with the restored model as a final, extra step.
The epoch of the restored model will also be logged as the metric ``restored_epoch``.
This allows for easy comparison between the actual metrics of the restored model and
the metrics of other models.

If ``restore_best_weights`` is set to be ``False``, then MLflow will not log an additional step.

Regardless of ``restore_best_weights``, MLflow will also log ``stopped_epoch``,
which indicates the epoch at which training stopped due to early stopping.

If training does not end due to early stopping, then ``stopped_epoch`` will be logged as ``0``.

MLflow will also log the parameters of the ``EarlyStopping`` callback,
excluding ``mode`` and ``verbose``.
@experimental
def autolog():
    '\n    Enables automatic logging from Keras to MLflow. Autologging captures the following information:\n\n    **Metrics** and **Parameters**\n     - Training loss; validation loss; user-specified metrics\n     - Metrics associated with the ``EarlyStopping`` callbacks: ``stopped_epoch``,\n       ``restored_epoch``, ``restore_best_weight``, ``last_epoch``, etc\n     - ``fit()`` or ``fit_generator()`` parameters; optimizer name; learning rate; epsilon\n     - ``fit()`` or ``fit_generator()`` parameters associated with ``EarlyStopping``: ``min_delta``,\n       ``patience``, ``baseline``, ``restore_best_weights``, etc\n    **Artifacts**\n     - Model summary on training start\n     - `MLflow Model <https://mlflow.org/docs/latest/models.html>`_ (Keras model) on training end\n\n    .. code-block:: python\n        :caption: Example\n\n        import mlflow\n        import mlflow.keras\n        # Build, compile, enable autologging, and train your model\n        keras_model = ...\n        keras_model.compile(optimizer="rmsprop", loss="mse", metrics=["accuracy"])\n        # autolog your metrics, parameters, and model\n        mlflow.keras.autolog()\n        results = keras_model.fit(\n            x_train, y_train, epochs=20, batch_size=128, validation_data=(x_val, y_val))\n\n    ``EarlyStopping Integration with Keras AutoLogging``\n\n    MLflow will detect if an ``EarlyStopping`` callback is used in a ``fit()`` or\n    ``fit_generator()`` call, and if the ``restore_best_weights`` parameter is set to be ``True``,\n    then MLflow will log the metrics associated with the restored model as a final, extra step.\n    The epoch of the restored model will also be logged as the metric ``restored_epoch``.\n    This allows for easy comparison between the actual metrics of the restored model and\n    the metrics of other models.\n\n    If ``restore_best_weights`` is set to be ``False``, then MLflow will not log an additional step.\n\n    Regardless of ``restore_best_weights``, MLflow will also log ``stopped_epoch``,\n    which indicates the epoch at which training stopped due to early stopping.\n\n    If training does not end due to early stopping, then ``stopped_epoch`` will be logged as ``0``.\n\n    MLflow will also log the parameters of the ``EarlyStopping`` callback,\n    excluding ``mode`` and ``verbose``.\n    '
    import keras

    class __MLflowKerasCallback(keras.callbacks.Callback):
        '\n        Callback for auto-logging metrics and parameters.\n        Records available logs after each epoch.\n        Records model structural information as params when training begins\n        '

        def on_train_begin(self, logs=None):
            try_mlflow_log(mlflow.log_param, 'num_layers', len(self.model.layers))
            try_mlflow_log(mlflow.log_param, 'optimizer_name', type(self.model.optimizer).__name__)
            if hasattr(self.model.optimizer, 'lr'):
                lr = (self.model.optimizer.lr if (type(self.model.optimizer.lr) is float) else keras.backend.eval(self.model.optimizer.lr))
                try_mlflow_log(mlflow.log_param, 'learning_rate', lr)
            if hasattr(self.model.optimizer, 'epsilon'):
                epsilon = (self.model.optimizer.epsilon if (type(self.model.optimizer.epsilon) is float) else keras.backend.eval(self.model.optimizer.epsilon))
                try_mlflow_log(mlflow.log_param, 'epsilon', epsilon)
            sum_list = []
            self.model.summary(print_fn=sum_list.append)
            summary = '\n'.join(sum_list)
            tempdir = tempfile.mkdtemp()
            try:
                summary_file = os.path.join(tempdir, 'model_summary.txt')
                with open(summary_file, 'w') as f:
                    f.write(summary)
                try_mlflow_log(mlflow.log_artifact, local_path=summary_file)
            finally:
                shutil.rmtree(tempdir)

        def on_epoch_end(self, epoch, logs=None):
            if (not logs):
                return
            try_mlflow_log(mlflow.log_metrics, logs, step=epoch)

        def on_train_end(self, logs=None):
            try_mlflow_log(log_model, self.model, artifact_path='model')

        def _implements_train_batch_hooks(self):
            return False

        def _implements_test_batch_hooks(self):
            return False

        def _implements_predict_batch_hooks(self):
            return False

    def _early_stop_check(callbacks):
        if (LooseVersion(keras.__version__) < LooseVersion('2.3.0')):
            es_callback = keras.callbacks.EarlyStopping
        else:
            es_callback = keras.callbacks.callbacks.EarlyStopping
        for callback in callbacks:
            if isinstance(callback, es_callback):
                return callback
        return None

    def _log_early_stop_callback_params(callback):
        if callback:
            try:
                earlystopping_params = {'monitor': callback.monitor, 'min_delta': callback.min_delta, 'patience': callback.patience, 'baseline': callback.baseline, 'restore_best_weights': callback.restore_best_weights}
                try_mlflow_log(mlflow.log_params, earlystopping_params)
            except Exception:
                return

    def _get_early_stop_callback_attrs(callback):
        try:
            return (callback.stopped_epoch, callback.restore_best_weights, callback.patience)
        except Exception:
            return None

    def _log_early_stop_callback_metrics(callback, history):
        if callback:
            callback_attrs = _get_early_stop_callback_attrs(callback)
            if (callback_attrs is None):
                return
            (stopped_epoch, restore_best_weights, patience) = callback_attrs
            try_mlflow_log(mlflow.log_metric, 'stopped_epoch', stopped_epoch)
            if ((stopped_epoch != 0) and restore_best_weights):
                restored_epoch = (stopped_epoch - max(1, patience))
                try_mlflow_log(mlflow.log_metric, 'restored_epoch', restored_epoch)
                restored_metrics = {key: history.history[key][restored_epoch] for key in history.history.keys()}
                metric_key = next(iter(history.history), None)
                if (metric_key is not None):
                    last_epoch = len(history.history[metric_key])
                    try_mlflow_log(mlflow.log_metrics, restored_metrics, step=last_epoch)

    def _run_and_log_function(self, original, args, kwargs, unlogged_params, callback_arg_index):
        if (not mlflow.active_run()):
            try_mlflow_log(mlflow.start_run)
            auto_end_run = True
        else:
            auto_end_run = False
        log_fn_args_as_params(original, args, kwargs, unlogged_params)
        early_stop_callback = None
        if (len(args) > callback_arg_index):
            tmp_list = list(args)
            early_stop_callback = _early_stop_check(tmp_list[callback_arg_index])
            tmp_list[callback_arg_index] += [__MLflowKerasCallback()]
            args = tuple(tmp_list)
        elif ('callbacks' in kwargs):
            early_stop_callback = _early_stop_check(kwargs['callbacks'])
            kwargs['callbacks'] += [__MLflowKerasCallback()]
        else:
            kwargs['callbacks'] = [__MLflowKerasCallback()]
        _log_early_stop_callback_params(early_stop_callback)
        history = original(self, *args, **kwargs)
        _log_early_stop_callback_metrics(early_stop_callback, history)
        if auto_end_run:
            try_mlflow_log(mlflow.end_run)
        return history

    @gorilla.patch(keras.Model)
    def fit(self, *args, **kwargs):
        original = gorilla.get_original_attribute(keras.Model, 'fit')
        unlogged_params = ['self', 'x', 'y', 'callbacks', 'validation_data', 'verbose']
        return _run_and_log_function(self, original, args, kwargs, unlogged_params, 5)

    @gorilla.patch(keras.Model)
    def fit_generator(self, *args, **kwargs):
        original = gorilla.get_original_attribute(keras.Model, 'fit_generator')
        unlogged_params = ['self', 'generator', 'callbacks', 'validation_data', 'verbose']
        return _run_and_log_function(self, original, args, kwargs, unlogged_params, 4)
    settings = gorilla.Settings(allow_hit=True, store_hit=True)
    gorilla.apply(gorilla.Patch(keras.Model, 'fit', fit, settings=settings))
    gorilla.apply(gorilla.Patch(keras.Model, 'fit_generator', fit_generator, settings=settings))