Support hierarchical dict (#1152)

* Add support for hierarchical dict

* Support nested Namespace

* Add docstring

* Migrate hparam flattening to each logger

* Modify URLs in CHANGELOG

* typo

* Simplify the conditional branch about Namespace

Co-Authored-By: Jirka Borovec <Borda@users.noreply.github.com>

* Update CHANGELOG.md

Co-Authored-By: Jirka Borovec <Borda@users.noreply.github.com>

* added examples section to docstring

* renamed _dict -> input_dict

Co-authored-by: Jirka Borovec <Borda@users.noreply.github.com>
This commit is contained in:
So Uchida
2020-03-19 09:15:47 -04:00
committed by GitHub
co-authored by Jirka Borovec
parent 22a7264e9a
commit 01b8991c5a
9 changed files with 45 additions and 5 deletions
+1
View File
@@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
### Added
- Added support for hierarchical `dict` ([#1152](https://github.com/PyTorchLightning/pytorch-lightning/pull/1152))
- Added `TrainsLogger` class ([#1122](https://github.com/PyTorchLightning/pytorch-lightning/pull/1122))
- Added type hints to `pytorch_lightning.core` ([#946](https://github.com/PyTorchLightning/pytorch-lightning/pull/946))
- Added support for IterableDataset in validation and testing ([#1104](https://github.com/PyTorchLightning/pytorch-lightning/pull/1104))
+33
View File
@@ -53,6 +53,39 @@ class LightningLoggerBase(ABC):
return params
@staticmethod
def _flatten_dict(params: Dict[str, Any], delimiter: str = '/') -> Dict[str, Any]:
"""Flatten hierarchical dict e.g. {'a': {'b': 'c'}} -> {'a/b': 'c'}.
Args:
params: Dictionary contains hparams
delimiter: Delimiter to express the hierarchy. Defaults to '/'.
Returns:
Flatten dict.
Examples:
>>> LightningLoggerBase._flatten_dict({'a': {'b': 'c'}})
{'a/b': 'c'}
>>> LightningLoggerBase._flatten_dict({'a': {'b': 123}})
{'a/b': 123}
"""
def _dict_generator(input_dict, prefixes=None):
prefixes = prefixes[:] if prefixes else []
if isinstance(input_dict, dict):
for key, value in input_dict.items():
if isinstance(value, (dict, Namespace)):
value = vars(value) if isinstance(value, Namespace) else value
for d in _dict_generator(value, prefixes + [key]):
yield d
else:
yield prefixes + [key, value if value is not None else str(None)]
else:
yield prefixes + [input_dict if input_dict is None else str(input_dict)]
return {delimiter.join(keys): val for *keys, val in _dict_generator(params)}
@staticmethod
def _sanitize_params(params: Dict[str, Any]) -> Dict[str, Any]:
"""Returns params with non-primitvies converted to strings for logging
+1
View File
@@ -163,6 +163,7 @@ class CometLogger(LightningLoggerBase):
@rank_zero_only
def log_hyperparams(self, params: Union[Dict[str, Any], Namespace]) -> None:
params = self._convert_params(params)
params = self._flatten_dict(params)
self.experiment.log_parameters(params)
@rank_zero_only
+1
View File
@@ -89,6 +89,7 @@ class MLFlowLogger(LightningLoggerBase):
@rank_zero_only
def log_hyperparams(self, params: Union[Dict[str, Any], Namespace]) -> None:
params = self._convert_params(params)
params = self._flatten_dict(params)
for k, v in params.items():
self.experiment.log_param(self.run_id, k, v)
+1
View File
@@ -222,6 +222,7 @@ class NeptuneLogger(LightningLoggerBase):
@rank_zero_only
def log_hyperparams(self, params: Union[Dict[str, Any], Namespace]) -> None:
params = self._convert_params(params)
params = self._flatten_dict(params)
for key, val in params.items():
self.experiment.set_property(f'param__{key}', val)
+1
View File
@@ -99,6 +99,7 @@ class TensorBoardLogger(LightningLoggerBase):
@rank_zero_only
def log_hyperparams(self, params: Union[Dict[str, Any], Namespace]) -> None:
params = self._convert_params(params)
params = self._flatten_dict(params)
sanitized_params = self._sanitize_params(params)
if parse_version(torch.__version__) < parse_version("1.3.0"):
+1
View File
@@ -96,6 +96,7 @@ class TestTubeLogger(LightningLoggerBase):
# TODO: HACK figure out where this is being set to true
self.experiment.debug = self.debug
params = self._convert_params(params)
params = self._flatten_dict(params)
self.experiment.argparse(Namespace(**params))
@rank_zero_only
+4 -4
View File
@@ -130,10 +130,10 @@ class TrainsLogger(LightningLoggerBase):
return None
if not params:
return
if isinstance(params, dict):
self._trains.connect(params)
else:
self._trains.connect(vars(params))
params = self._convert_params(params)
params = self._flatten_dict(params)
self._trains.connect(params)
@rank_zero_only
def log_metrics(self, metrics: Dict[str, float], step: Optional[int] = None) -> None:
+2 -1
View File
@@ -108,8 +108,9 @@ def test_tensorboard_log_hyperparams(tmpdir):
"int": 1,
"string": "abc",
"bool": True,
"dict": {'a': {'b': 'c'}},
"list": [1, 2, 3],
"namespace": Namespace(foo=3),
"namespace": Namespace(foo=Namespace(bar='buzz')),
"layer": torch.nn.BatchNorm1d
}
logger.log_hyperparams(hparams)