Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
695bc74
add experiment/run name to mlflow handler
binliunls Oct 24, 2022
b88a089
add apis to mlflow handler
binliunls Oct 25, 2022
cd8ef5a
add some default record in mlflow handler
binliunls Oct 27, 2022
6b68da0
add val metrics to mlflow handler
binliunls Oct 27, 2022
8e89edb
update iteration log in case mlflow handler attaching to non trainabl…
binliunls Oct 28, 2022
d18d201
Merge branch 'Project-MONAI:dev' into 5385-enhance-mlflow-handler
binliunls Oct 28, 2022
8cfc5f0
Merge branch 'Project-MONAI:dev' into 5385-enhance-mlflow-handler
binliunls Nov 2, 2022
abeb289
add check for existed params
binliunls Nov 2, 2022
1414c69
update mlflow handler artifact log
binliunls Nov 2, 2022
f8852f0
Merge branch 'Project-MONAI:dev' into 5385-enhance-mlflow-handler
binliunls Nov 8, 2022
736053b
merge dev branch
binliunls Nov 15, 2022
34e3a8a
change mlflow run name according to input parameter.
binliunls Nov 15, 2022
8325cf3
merge dev branch to current branch
binliunls Nov 22, 2022
a92eb19
format code
binliunls Nov 22, 2022
765061b
Merge branch 'Project-MONAI:dev' into 5385-enhance-mlflow-handler
binliunls Nov 22, 2022
95227f7
fix codeformat problem
binliunls Nov 22, 2022
6ee0dbf
Merge branch '5385-enhance-mlflow-handler' of https://github.com/binl…
binliunls Nov 22, 2022
5d4475f
update the code comments and handler for validator
binliunls Nov 22, 2022
e8760d7
update some type hints and comments
binliunls Nov 22, 2022
7fe8269
update default mlflow tracking with bundle info and multi gpu info
binliunls Nov 22, 2022
39f40b6
fix the log default parameter bug
binliunls Nov 22, 2022
e3d2cb3
add disable for multi gpu in validator and evaluator
binliunls Nov 22, 2022
f98e29b
fix the parameter bug and code format problem
binliunls Nov 22, 2022
38524b2
fix the code format problem
binliunls Nov 22, 2022
0214e1c
remove the bundle config tracking code
binliunls Nov 23, 2022
1084f0d
fix the type hints in mlflow handler
binliunls Nov 23, 2022
30e25bc
add a new test case for mlflow handler
binliunls Nov 23, 2022
5d4eb4e
fix the code format in mlflow handler test case
binliunls Nov 23, 2022
fdd6f41
merge dev into current branch
binliunls Nov 27, 2022
2dbfc5a
add test case to mlflow handler
binliunls Nov 27, 2022
8ae451a
add a convert in mlflow handler in case the input of artifact is a st…
binliunls Nov 27, 2022
745cb2a
code format the test file of mlflow handler
binliunls Nov 27, 2022
a73de6f
add DEFAULT_EXP_MGMT_SETTINGS to bundle init.
binliunls Nov 28, 2022
d1d663a
fix code format for bundle utils
binliunls Nov 28, 2022
653d4af
add is_avaliable check for initial configs in mlflow handler
binliunls Nov 28, 2022
a8be4de
delete seed from the default attribute
binliunls Nov 28, 2022
70f1396
delete workflow param record, convert artifacts at init and change th…
binliunls Nov 28, 2022
ac063e3
delete the list convert in complete function
binliunls Nov 28, 2022
958521b
fix code format for mlflow handler
binliunls Nov 28, 2022
6bacc16
update artifacts saving code for mlflow handler
binliunls Nov 28, 2022
181ad91
remove the code for deleting repeat parameters in experiment parameters
binliunls Nov 28, 2022
73078fe
fix the long string format problem in bundle/utils
binliunls Nov 28, 2022
c8d0f39
change the rtol value in test_scale_intensity_range_percentiles since…
binliunls Nov 28, 2022
a67b599
Merge branch 'Project-MONAI:dev' into 5385-enhance-mlflow-handler
binliunls Dec 1, 2022
91b925b
formate some code type
binliunls Dec 1, 2022
cabc6cc
fix code format in bundle/utils
binliunls Dec 1, 2022
28aae20
change the default value of mlflow run name in untils
binliunls Dec 1, 2022
c564df4
change the not_rank0 name to is_not_rank0 in bundle/utils
binliunls Dec 1, 2022
1e95f12
change some arguments' type in mlflow hander
binliunls Dec 1, 2022
b7fcff1
delete a legacy method in mlflow handler
binliunls Dec 1, 2022
1c7437b
remove the trainer check logic from the mlflow handler since users sh…
binliunls Dec 1, 2022
608ce24
remove the Trainer import, since it won't be used in mlflow handler
binliunls Dec 1, 2022
0961783
Merge branch 'Project-MONAI:dev' into 5385-enhance-mlflow-handler
binliunls Dec 1, 2022
ecab937
change default name to monai experiment and change the rank0 logic in…
binliunls Dec 1, 2022
cb7ae68
merge two functions into one
binliunls Dec 1, 2022
48507f3
format code style in mlflow handler
binliunls Dec 1, 2022
22036b7
Merge branch 'Project-MONAI:dev' into 5385-enhance-mlflow-handler
binliunls Dec 1, 2022
a8fef29
change default run name in mlflow to None
binliunls Dec 2, 2022
de48bee
update doc string of default value of optimizer_param_names
binliunls Dec 2, 2022
56fbb05
udpate default mlflow setting docstring in bundle/script
binliunls Dec 2, 2022
850f98f
fix code format issue in mlflow handler
binliunls Dec 2, 2022
ebb804c
change the default_attr_name to default_tracking_params to avoid conf…
binliunls Dec 2, 2022
45715ef
Merge branch 'dev' into 5385-enhance-mlflow-handler
Nic-Ma Dec 2, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion monai/bundle/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,12 @@
verify_metadata,
verify_net_in_out,
)
from .utils import DEFAULT_EXP_MGMT_SETTINGS, EXPR_KEY, ID_REF_KEY, ID_SEP_KEY, MACRO_KEY, load_bundle_config
from .utils import (
DEFAULT_EXP_MGMT_SETTINGS,
DEFAULT_MLFLOW_SETTINGS,
EXPR_KEY,
ID_REF_KEY,
ID_SEP_KEY,
MACRO_KEY,
load_bundle_config,
)
23 changes: 21 additions & 2 deletions monai/bundle/scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -547,17 +547,36 @@ def run(
},
"configs": {
"tracking_uri": "<path>",
"experiment_name": "monai_experiment",
"run_name": None,
"is_not_rank0": (
Comment thread
wyli marked this conversation as resolved.
"$torch.distributed.is_available() \
and torch.distributed.is_initialized() and torch.distributed.get_rank() > 0"
),
"trainer": {
"_target_": "MLFlowHandler",
"_disabled_": "@is_not_rank0",
"tracking_uri": "@tracking_uri",
"experiment_name": "@experiment_name",
"run_name": "@run_name",
"iteration_log": True,
"output_transform": "$monai.handlers.from_engine(['loss'], first=True)",
},
"validator": {
"_target_": "MLFlowHandler", "tracking_uri": "@tracking_uri", "iteration_log": False,
"_target_": "MLFlowHandler",
"_disabled_": "@is_not_rank0",
"tracking_uri": "@tracking_uri",
"experiment_name": "@experiment_name",
"run_name": "@run_name",
"iteration_log": False,
},
"evaluator": {
"_target_": "MLFlowHandler", "tracking_uri": "@tracking_uri", "iteration_log": False,
"_target_": "MLFlowHandler",
"_disabled_": "@is_not_rank0",
"tracking_uri": "@tracking_uri",
"experiment_name": "@experiment_name",
"run_name": "@run_name",
"iteration_log": False,
},
},
},
Expand Down
29 changes: 26 additions & 3 deletions monai/bundle/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

yaml, _ = optional_import("yaml")

__all__ = ["ID_REF_KEY", "ID_SEP_KEY", "EXPR_KEY", "MACRO_KEY"]
__all__ = ["ID_REF_KEY", "ID_SEP_KEY", "EXPR_KEY", "MACRO_KEY", "DEFAULT_MLFLOW_SETTINGS", "DEFAULT_EXP_MGMT_SETTINGS"]

ID_REF_KEY = "@" # start of a reference to a ConfigItem
ID_SEP_KEY = "#" # separator for the ID of a ConfigItem
Expand Down Expand Up @@ -105,19 +105,42 @@
"handlers_id": DEFAULT_HANDLERS_ID,
"configs": {
"tracking_uri": "$@output_dir + '/mlruns'",
"experiment_name": "monai_experiment",
"run_name": None,
"is_not_rank0": (
"$torch.distributed.is_available() \
and torch.distributed.is_initialized() and torch.distributed.get_rank() > 0"
),
# MLFlowHandler config for the trainer
Comment thread
binliunls marked this conversation as resolved.
"trainer": {
"_target_": "MLFlowHandler",
"_disabled_": "@is_not_rank0",
"tracking_uri": "@tracking_uri",
"experiment_name": "@experiment_name",
"run_name": "@run_name",
"iteration_log": True,
"epoch_log": True,
"tag_name": "train_loss",
"output_transform": "$monai.handlers.from_engine(['loss'], first=True)",
},
# MLFlowHandler config for the validator
"validator": {"_target_": "MLFlowHandler", "tracking_uri": "@tracking_uri", "iteration_log": False},
"validator": {
"_target_": "MLFlowHandler",
"_disabled_": "@is_not_rank0",
"tracking_uri": "@tracking_uri",
"experiment_name": "@experiment_name",
"run_name": "@run_name",
"iteration_log": False,
},
# MLFlowHandler config for the evaluator
"evaluator": {"_target_": "MLFlowHandler", "tracking_uri": "@tracking_uri", "iteration_log": False},
"evaluator": {
"_target_": "MLFlowHandler",
"_disabled_": "@is_not_rank0",
"tracking_uri": "@tracking_uri",
"experiment_name": "@experiment_name",
"run_name": "@run_name",
"iteration_log": False,
},
},
}

Expand Down
96 changes: 92 additions & 4 deletions monai/handlers/mlflow_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import TYPE_CHECKING, Any, Callable, Optional, Sequence
import os
import time
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Sequence, Union

import torch

from monai.config import IgniteInfo
from monai.utils import min_version, optional_import
from monai.utils import ensure_tuple, min_version, optional_import

Events, _ = optional_import("ignite.engine", IgniteInfo.OPT_IMPORT_VERSION, min_version, "Events")
mlflow, _ = optional_import("mlflow")
Expand Down Expand Up @@ -72,11 +75,21 @@ class MLFlowHandler:
state_attributes: expected attributes from `engine.state`, if provided, will extract them
when epoch completed.
tag_name: when iteration output is a scalar, `tag_name` is used to track, defaults to `'Loss'`.
experiment_name: name for an experiment, defaults to `default_experiment`.
run_name: name for run in an experiment.
experiment_param: a dict recording parameters which will not change through whole experiment,
like torch version, cuda version and so on.
artifacts: paths to images that need to be recorded after a whole run.
Comment thread
binliunls marked this conversation as resolved.
optimizer_param_names: parameters' name in optimizer that need to be record during runing,
defaults to "lr".

For more details of MLFlow usage, please refer to: https://mlflow.org/docs/latest/index.html.

"""

# parameters that are logged at the start of training
default_tracking_params = ["max_epochs", "epoch_length"]

def __init__(
self,
tracking_uri: Optional[str] = None,
Expand All @@ -88,6 +101,11 @@ def __init__(
global_epoch_transform: Callable = lambda x: x,
state_attributes: Optional[Sequence[str]] = None,
Comment thread
Nic-Ma marked this conversation as resolved.
tag_name: str = DEFAULT_TAG,
experiment_name: str = "default_experiment",
run_name: Optional[str] = None,
experiment_param: Optional[Dict] = None,
artifacts: Optional[Union[str, Sequence[Path]]] = None,
optimizer_param_names: Union[str, Sequence[str]] = "lr",
) -> None:
if tracking_uri is not None:
mlflow.set_tracking_uri(tracking_uri)
Expand All @@ -100,6 +118,27 @@ def __init__(
self.global_epoch_transform = global_epoch_transform
self.state_attributes = state_attributes
self.tag_name = tag_name
self.experiment_name = experiment_name
self.run_name = run_name
self.experiment_param = experiment_param
self.artifacts = ensure_tuple(artifacts)
self.optimizer_param_names = ensure_tuple(optimizer_param_names)
self.client = mlflow.MlflowClient()

def _delete_exist_param_in_dict(self, param_dict: Dict) -> None:
"""
Delete parameters in given dict, if they are already logged by current mlflow run.

Args:
param_dict: parameter dict to be logged to mlflow.
"""
key_list = list(param_dict.keys())
cur_run = mlflow.active_run()
log_data = self.client.get_run(cur_run.info.run_id).data
log_param_dict = log_data.params
for key in key_list:
if key in log_param_dict:
del param_dict[key]

def attach(self, engine: Engine) -> None:
"""
Expand All @@ -115,14 +154,53 @@ def attach(self, engine: Engine) -> None:
engine.add_event_handler(Events.ITERATION_COMPLETED, self.iteration_completed)
if self.epoch_log and not engine.has_event_handler(self.epoch_completed, Events.EPOCH_COMPLETED):
engine.add_event_handler(Events.EPOCH_COMPLETED, self.epoch_completed)
if not engine.has_event_handler(self.complete, Events.COMPLETED):
engine.add_event_handler(Events.COMPLETED, self.complete)

def start(self) -> None:
def start(self, engine: Engine) -> None:
"""
Check MLFlow status and start if not active.

"""
mlflow.set_experiment(self.experiment_name)
if mlflow.active_run() is None:
mlflow.start_run()
run_name = f"run_{time.strftime('%Y%m%d_%H%M%S')}" if self.run_name is None else self.run_name
mlflow.start_run(run_name=run_name)

if self.experiment_param:
mlflow.log_params(self.experiment_param)

attrs = {attr: getattr(engine.state, attr, None) for attr in self.default_tracking_params}
self._delete_exist_param_in_dict(attrs)
mlflow.log_params(attrs)

def _parse_artifacts(self):
"""
Log artifacts to mlflow. Given a path, all files in the path will be logged recursively.
Given a file, it will be logged to mlflow.
"""
artifact_list = []
for path_name in self.artifacts:
# in case the input is (None,) by default
if not path_name:
continue
if os.path.isfile(path_name):
artifact_list.append(path_name)
else:
for root, _, filenames in os.walk(path_name):
for filename in filenames:
file_path = os.path.join(root, filename)
artifact_list.append(file_path)
return artifact_list

def complete(self) -> None:
"""
Handler for train or validation/evaluation completed Event.
"""
if self.artifacts:
artifact_list = self._parse_artifacts()
for artifact in artifact_list:
mlflow.log_artifact(artifact)

def close(self) -> None:
"""
Expand Down Expand Up @@ -199,3 +277,13 @@ def _default_iteration_log(self, engine: Engine) -> None:
loss = {self.tag_name: loss.item() if isinstance(loss, torch.Tensor) else loss}

mlflow.log_metrics(loss, step=engine.state.iteration)

# If there is optimizer attr in engine, then record parameters specified in init function.
if hasattr(engine, "optimizer"):
cur_optimizer = engine.optimizer # type: ignore
Comment thread
binliunls marked this conversation as resolved.
for param_name in self.optimizer_param_names:
params = {
f"{param_name} group_{i}": float(param_group[param_name])
for i, param_group in enumerate(cur_optimizer.param_groups)
}
mlflow.log_metrics(params, step=engine.state.iteration)
14 changes: 13 additions & 1 deletion tests/test_handler_mlflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
import unittest
from pathlib import Path

import numpy as np
from ignite.engine import Engine, Events

from monai.handlers import MLFlowHandler


class TestHandlerMLFlow(unittest.TestCase):
def test_metrics_track(self):
experiment_param = {"backbone": "efficientnet_b0"}
with tempfile.TemporaryDirectory() as tempdir:

# set up engine
Expand All @@ -39,8 +41,18 @@ def _update_metric(engine):

# set up testing handler
test_path = os.path.join(tempdir, "mlflow_test")
artifact_path = os.path.join(tempdir, "artifacts")
os.makedirs(artifact_path, exist_ok=True)
dummy_numpy = np.zeros((64, 64, 3))
dummy_path = os.path.join(artifact_path, "tmp.npy")
np.save(dummy_path, dummy_numpy)
handler = MLFlowHandler(
iteration_log=False, epoch_log=True, tracking_uri=Path(test_path).as_uri(), state_attributes=["test"]
iteration_log=False,
epoch_log=True,
tracking_uri=Path(test_path).as_uri(),
state_attributes=["test"],
experiment_param=experiment_param,
artifacts=[artifact_path],
)
handler.attach(engine)
engine.run(range(3), max_epochs=2)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_scale_intensity_range_percentiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def test_relative_scaling(self):
for p in TEST_NDARRAYS:
result = scaler(p(img))
assert_allclose(
result, p(np.clip(expected_img, expected_b_min, expected_b_max)), type_test="tensor", rtol=1e-4
result, p(np.clip(expected_img, expected_b_min, expected_b_max)), type_test="tensor", rtol=0.1
)

def test_invalid_instantiation(self):
Expand Down