Skip to content

Commit 0e5aee1

Browse files
authored
TensorFlow autolog automatic run ending (mlflow#2094)
* added keras logic and tests * refactored keras autolog tests * minor edits to keras tests * minor edit to documentation * added autolog ending for tf.Keras, added patching for tf.keras.model.fit_generator * refactored and added tests for tf.keras * removed fit_generator hook for another PR * renamed create_model to create_keras_model * refactored and added tests for auto ending tf.keras autolog runs * made test names more descriptive * lint * added run starting in keras * refactored run starting into yield fixture * added logic for run autoending in tf.estimator * added logic and tests for tf estimator run ending * added _autolog_run_id, actually patched train * fixed wrapper tries, refactored tests * forgot a couple of parentheses * updated docs * updated docs more, removed extra line in tests * changed wording for docs * added tests for tf1 * added space in docs * moved space in docs * removed code in docs * grammar in docs * added param checking test * fixed malformed table * removing _AUTO_END_RUN * added check for ending auto runs * replaced manual tempdir with tmpdir fixture * removing export_savedmodel from docs Co-Authored-By: Siddharth Murching <smurching@gmail.com> * Update docs wording Co-Authored-By: Siddharth Murching <smurching@gmail.com> * Added defensive check to run ending Co-Authored-By: Siddharth Murching <smurching@gmail.com> * created contextmanager * fixed defensive check * lint * edited docs
1 parent 9ce7f66 commit 0e5aee1

4 files changed

Lines changed: 257 additions & 163 deletions

File tree

docs/source/tracking.rst

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -217,20 +217,26 @@ log statements. See example usages with `Keras <https://github.com/mlflow/mlflow
217217

218218
Autologging captures the following information:
219219

220-
+------------------+--------------------------------------------------------+----------------------------------------------------------+---------------+-------------------------------------------------------------------------------------------------------------------------------+
221-
| Framework | Metrics | Parameters | Tags | Artifacts |
222-
+------------------+--------------------------------------------------------+----------------------------------------------------------+---------------+-------------------------------------------------------------------------------------------------------------------------------+
223-
| Keras | Training loss; validation loss; user-specified metrics | Number of layers; optimizer name; learning rate; epsilon | Model summary | `MLflow Model <https://mlflow.org/docs/latest/models.html>`_ (Keras model); on training end |
224-
+------------------+--------------------------------------------------------+----------------------------------------------------------+---------------+-------------------------------------------------------------------------------------------------------------------------------+
225-
| ``tf.keras`` | Training loss; validation loss; user-specified metrics | Number of layers; optimizer name; learning rate; epsilon | Model summary | `MLflow Model <https://mlflow.org/docs/latest/models.html>`_ (Keras model), TensorBoard logs; on training end |
226-
+------------------+--------------------------------------------------------+----------------------------------------------------------+---------------+-------------------------------------------------------------------------------------------------------------------------------+
227-
| ``tf.estimator`` | TensorBoard metrics | -- | -- | `MLflow Model <https://mlflow.org/docs/latest/models.html>`_ (TF saved model); on call to ``tf.estimator.export_saved_model`` |
228-
+------------------+--------------------------------------------------------+----------------------------------------------------------+---------------+-------------------------------------------------------------------------------------------------------------------------------+
229-
| TensorFlow Core | All ``tf.summary.scalar`` calls | -- | -- | -- |
230-
+------------------+--------------------------------------------------------+----------------------------------------------------------+---------------+-------------------------------------------------------------------------------------------------------------------------------+
220+
+------------------+--------------------------------------------------------+----------------------------------------------------------+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------+
221+
| Framework | Metrics | Parameters | Tags | Artifacts |
222+
+------------------+--------------------------------------------------------+----------------------------------------------------------+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------+
223+
| Keras | Training loss; validation loss; user-specified metrics | Number of layers; optimizer name; learning rate; epsilon | Model summary | Model summary on training start; `MLflow Model <https://mlflow.org/docs/latest/models.html>`_ (Keras model) on training end |
224+
+------------------+--------------------------------------------------------+----------------------------------------------------------+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------+
225+
| ``tf.keras`` | Training loss; validation loss; user-specified metrics | Number of layers; optimizer name; learning rate; epsilon | Model summary | Model summary on training start; `MLflow Model <https://mlflow.org/docs/latest/models.html>`_ (Keras model), TensorBoard logs on training end |
226+
+------------------+--------------------------------------------------------+----------------------------------------------------------+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------+
227+
| ``tf.estimator`` | TensorBoard metrics | steps, max_steps | -- | `MLflow Model <https://mlflow.org/docs/latest/models.html>`_ (TF saved model) on call to ``tf.estimator.export_saved_model`` |
228+
+------------------+--------------------------------------------------------+----------------------------------------------------------+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------+
229+
| TensorFlow Core | All ``tf.summary.scalar`` calls | -- | -- | -- |
230+
+------------------+--------------------------------------------------------+----------------------------------------------------------+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------+
231231

232232
Note that autologging for ``tf.keras`` is handled by :py:func:`mlflow.tensorflow.autolog`, not :py:func:`mlflow.keras.autolog`.
233233

234+
If no active run exists when ``autolog()`` captures data, MLflow will automatically create a run to log information to.
235+
Once training ends via calls to ``tf.estimator.train()``, ``tf.keras.fit()``, ``tf.keras.fit_generator()``, ``keras.fit()`` or ``keras.fit_generator()``,
236+
or once ``tf.estimator`` models are exported via ``tf.estimator.export_saved_model()``, MLflow will automatically end that run.
237+
238+
If a run exists when ``autolog()`` captures data, MLflow will log to that run and not automatically end that run after training.
239+
234240
**Note**: this feature is experimental - the API and format of the logged data are subject to change.
235241

236242

mlflow/tensorflow.py

Lines changed: 93 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import tensorflow
2828
import mlflow.keras
2929
from distutils.version import LooseVersion
30+
from contextlib import contextmanager
3031
from tensorflow.keras.callbacks import Callback, TensorBoard # pylint: disable=import-error
3132
from mlflow import pyfunc
3233
from mlflow.exceptions import MlflowException
@@ -54,7 +55,7 @@
5455
_thread_pool = concurrent.futures.ThreadPoolExecutor(max_workers=1)
5556

5657
# For tracking if the run was started by autologging.
57-
_AUTO_END_RUN = False
58+
_AUTOLOG_RUN_ID = None
5859

5960

6061
def get_default_conda_env():
@@ -584,8 +585,8 @@ def _log_event(event):
584585
"""
585586
if not mlflow.active_run():
586587
try_mlflow_log(mlflow.start_run)
587-
global _AUTO_END_RUN
588-
_AUTO_END_RUN = True
588+
global _AUTOLOG_RUN_ID
589+
_AUTOLOG_RUN_ID = mlflow.active_run().info.run_id
589590
if event.WhichOneof('what') == 'summary':
590591
summary = event.summary
591592
for v in summary.value:
@@ -657,85 +658,123 @@ def autolog(every_n_iter=100):
657658
"1.12 <= v <= 2.0.0 are supported.")
658659
return
659660

661+
@contextmanager
662+
def _manage_active_run():
663+
if not mlflow.active_run():
664+
try_mlflow_log(mlflow.start_run)
665+
global _AUTOLOG_RUN_ID
666+
if mlflow.active_run() is not None: # defensive check in case `mlflow.start_run` fails
667+
_AUTOLOG_RUN_ID = mlflow.active_run().info.run_id
668+
yield mlflow.active_run()
669+
if mlflow.active_run() is not None and mlflow.active_run().info.run_id == _AUTOLOG_RUN_ID:
670+
try_mlflow_log(mlflow.end_run)
671+
672+
@gorilla.patch(tensorflow.estimator.Estimator)
673+
def train(self, *args, **kwargs):
674+
with _manage_active_run():
675+
original = gorilla.get_original_attribute(tensorflow.estimator.Estimator, 'train')
676+
677+
# Checking step and max_step parameters for logging
678+
if len(args) >= 3:
679+
try_mlflow_log(mlflow.log_param, 'steps', args[2])
680+
if len(args) >= 4:
681+
try_mlflow_log(mlflow.log_param, 'max_steps', args[3])
682+
if 'steps' in kwargs:
683+
try_mlflow_log(mlflow.log_param, 'steps', kwargs['steps'])
684+
if 'max_steps' in kwargs:
685+
try_mlflow_log(mlflow.log_param, 'max_steps', kwargs['max_steps'])
686+
687+
result = original(self, *args, **kwargs)
688+
689+
return result
690+
660691
@gorilla.patch(tensorflow.estimator.Estimator)
661692
def export_saved_model(self, *args, **kwargs):
693+
auto_end = False
694+
if not mlflow.active_run():
695+
global _AUTOLOG_RUN_ID
696+
if _AUTOLOG_RUN_ID:
697+
try_mlflow_log(mlflow.start_run, _AUTOLOG_RUN_ID)
698+
else:
699+
try_mlflow_log(mlflow.start_run)
700+
auto_end = True
701+
662702
original = gorilla.get_original_attribute(tensorflow.estimator.Estimator,
663703
'export_saved_model')
664704
serialized = original(self, *args, **kwargs)
665705
try_mlflow_log(log_model, tf_saved_model_dir=serialized.decode('utf-8'),
666706
tf_meta_graph_tags=[tag_constants.SERVING],
667707
tf_signature_def_key='predict',
668708
artifact_path='model')
709+
if (mlflow.active_run() is not None and mlflow.active_run().info.run_id == _AUTOLOG_RUN_ID)\
710+
or auto_end:
711+
try_mlflow_log(mlflow.end_run)
669712
return serialized
670713

671714
@gorilla.patch(tensorflow.estimator.Estimator)
672715
def export_savedmodel(self, *args, **kwargs):
716+
auto_end = False
717+
global _AUTOLOG_RUN_ID
718+
if not mlflow.active_run():
719+
if _AUTOLOG_RUN_ID:
720+
try_mlflow_log(mlflow.start_run, _AUTOLOG_RUN_ID)
721+
else:
722+
try_mlflow_log(mlflow.start_run)
723+
auto_end = True
724+
673725
original = gorilla.get_original_attribute(tensorflow.estimator.Estimator,
674726
'export_savedmodel')
675727
serialized = original(self, *args, **kwargs)
676728
try_mlflow_log(log_model, tf_saved_model_dir=serialized.decode('utf-8'),
677729
tf_meta_graph_tags=[tag_constants.SERVING],
678730
tf_signature_def_key='predict',
679731
artifact_path='model')
732+
if (mlflow.active_run() is not None and mlflow.active_run().info.run_id == _AUTOLOG_RUN_ID)\
733+
or auto_end:
734+
try_mlflow_log(mlflow.end_run)
680735
return serialized
681736

682737
@gorilla.patch(tensorflow.keras.Model)
683738
def fit(self, *args, **kwargs):
684-
global _AUTO_END_RUN
685-
if not mlflow.active_run():
686-
try_mlflow_log(mlflow.start_run)
687-
_AUTO_END_RUN = True
688-
689-
original = gorilla.get_original_attribute(tensorflow.keras.Model, 'fit')
690-
691-
# Checking if the 'callback' argument of fit() is set
692-
if len(args) >= 6:
693-
tmp_list = list(args)
694-
tmp_list[5], log_dir = _setup_callbacks(tmp_list[5])
695-
args = tuple(tmp_list)
696-
elif 'callbacks' in kwargs:
697-
kwargs['callbacks'], log_dir = _setup_callbacks(kwargs['callbacks'])
698-
else:
699-
kwargs['callbacks'], log_dir = _setup_callbacks([])
700-
result = original(self, *args, **kwargs)
701-
_flush_queue()
702-
_log_artifacts_with_warning(local_dir=log_dir, artifact_path='tensorboard_logs')
703-
shutil.rmtree(log_dir)
704-
705-
if _AUTO_END_RUN:
706-
try_mlflow_log(mlflow.end_run)
707-
_AUTO_END_RUN = False
739+
with _manage_active_run():
740+
original = gorilla.get_original_attribute(tensorflow.keras.Model, 'fit')
741+
742+
# Checking if the 'callback' argument of fit() is set
743+
if len(args) >= 6:
744+
tmp_list = list(args)
745+
tmp_list[5], log_dir = _setup_callbacks(tmp_list[5])
746+
args = tuple(tmp_list)
747+
elif 'callbacks' in kwargs:
748+
kwargs['callbacks'], log_dir = _setup_callbacks(kwargs['callbacks'])
749+
else:
750+
kwargs['callbacks'], log_dir = _setup_callbacks([])
751+
result = original(self, *args, **kwargs)
752+
_flush_queue()
753+
_log_artifacts_with_warning(local_dir=log_dir, artifact_path='tensorboard_logs')
754+
shutil.rmtree(log_dir)
708755

709-
return result
756+
return result
710757

711758
@gorilla.patch(tensorflow.keras.Model)
712759
def fit_generator(self, *args, **kwargs):
713-
global _AUTO_END_RUN
714-
if not mlflow.active_run():
715-
try_mlflow_log(mlflow.start_run)
716-
_AUTO_END_RUN = True
717-
718-
original = gorilla.get_original_attribute(tensorflow.keras.Model, 'fit_generator')
719-
720-
# Checking if the 'callback' argument of fit() is set
721-
if len(args) >= 5:
722-
tmp_list = list(args)
723-
tmp_list[4], log_dir = _setup_callbacks(tmp_list[4])
724-
args = tuple(tmp_list)
725-
elif 'callbacks' in kwargs:
726-
kwargs['callbacks'], log_dir = _setup_callbacks(kwargs['callbacks'])
727-
else:
728-
kwargs['callbacks'], log_dir = _setup_callbacks([])
729-
result = original(self, *args, **kwargs)
730-
_flush_queue()
731-
_log_artifacts_with_warning(local_dir=log_dir, artifact_path='tensorboard_logs')
732-
shutil.rmtree(log_dir)
733-
734-
if _AUTO_END_RUN:
735-
try_mlflow_log(mlflow.end_run)
736-
_AUTO_END_RUN = False
760+
with _manage_active_run():
761+
original = gorilla.get_original_attribute(tensorflow.keras.Model, 'fit_generator')
762+
763+
# Checking if the 'callback' argument of fit() is set
764+
if len(args) >= 5:
765+
tmp_list = list(args)
766+
tmp_list[4], log_dir = _setup_callbacks(tmp_list[4])
767+
args = tuple(tmp_list)
768+
elif 'callbacks' in kwargs:
769+
kwargs['callbacks'], log_dir = _setup_callbacks(kwargs['callbacks'])
770+
else:
771+
kwargs['callbacks'], log_dir = _setup_callbacks([])
772+
result = original(self, *args, **kwargs)
773+
_flush_queue()
774+
_log_artifacts_with_warning(local_dir=log_dir, artifact_path='tensorboard_logs')
775+
shutil.rmtree(log_dir)
737776

738-
return result
777+
return result
739778

740779
@gorilla.patch(EventFileWriter)
741780
def add_event(self, event):
@@ -754,6 +793,7 @@ def add_summary(self, *args, **kwargs):
754793
patches = [
755794
gorilla.Patch(EventFileWriter, 'add_event', add_event, settings=settings),
756795
gorilla.Patch(EventFileWriterV2, 'add_event', add_event, settings=settings),
796+
gorilla.Patch(tensorflow.estimator.Estimator, 'train', train, settings=settings),
757797
gorilla.Patch(tensorflow.keras.Model, 'fit', fit, settings=settings),
758798
gorilla.Patch(tensorflow.keras.Model, 'fit_generator', fit_generator, settings=settings),
759799
gorilla.Patch(tensorflow.estimator.Estimator, 'export_saved_model',

0 commit comments

Comments
 (0)