Skip to content

Commit

Permalink
Add ndjson logging for training (#10970)
Browse files Browse the repository at this point in the history
* Add ndjson logging for training

This adds support for NDJSON (newline-delimited JSON) metrics logging,
for both console (stdout) output and a file (like the current CSV file).

NDJSON can be easily grepped from the output and/or parsed with e.g. `jq`.

The feature is enabled with the `--ndjson-console` and `--ndjson-file`
switches to `train.py`.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
akx and pre-commit-ci[bot] authored Jan 3, 2024
1 parent 43c43d8 commit 52db52b
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 3 deletions.
21 changes: 19 additions & 2 deletions train.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
get_latest_run, increment_path, init_seeds, intersect_dicts, labels_to_class_weights,
labels_to_image_weights, methods, one_cycle, print_args, print_mutation, strip_optimizer,
yaml_save)
from utils.loggers import Loggers
from utils.loggers import LOGGERS, Loggers
from utils.loggers.comet.comet_utils import check_comet_resume
from utils.loss import ComputeLoss
from utils.metrics import fitness
Expand Down Expand Up @@ -98,7 +98,20 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio
# Loggers
data_dict = None
if RANK in {-1, 0}:
loggers = Loggers(save_dir, weights, opt, hyp, LOGGER) # loggers instance
include_loggers = list(LOGGERS)
if getattr(opt, 'ndjson_console', False):
include_loggers.append('ndjson_console')
if getattr(opt, 'ndjson_file', False):
include_loggers.append('ndjson_file')

loggers = Loggers(
save_dir=save_dir,
weights=weights,
opt=opt,
hyp=hyp,
logger=LOGGER,
include=tuple(include_loggers),
)

# Register actions
for k in methods(loggers):
Expand Down Expand Up @@ -482,6 +495,10 @@ def parse_opt(known=False):
parser.add_argument('--bbox_interval', type=int, default=-1, help='Set bounding-box image logging interval')
parser.add_argument('--artifact_alias', type=str, default='latest', help='Version of dataset artifact to use')

# NDJSON logging
parser.add_argument('--ndjson-console', action='store_true', help='Log ndjson to console')
parser.add_argument('--ndjson-file', action='store_true', help='Log ndjson to file')

return parser.parse_known_args()[0] if known else parser.parse_args()


Expand Down
24 changes: 23 additions & 1 deletion utils/loggers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"""
Logging utils
"""

import json
import os
import warnings
from pathlib import Path
Expand Down Expand Up @@ -58,6 +58,18 @@
comet_ml = None


def _json_default(value):
"""Format `value` for JSON serialization (e.g. unwrap tensors). Fall back to strings."""
if isinstance(value, torch.Tensor):
try:
value = value.item()
except ValueError: # "only one element tensors can be converted to Python scalars"
pass
if isinstance(value, float):
return value
return str(value)


class Loggers():
# YOLOv5 Loggers class
def __init__(self, save_dir=None, weights=None, opt=None, hyp=None, logger=None, include=LOGGERS):
Expand Down Expand Up @@ -86,6 +98,8 @@ def __init__(self, save_dir=None, weights=None, opt=None, hyp=None, logger=None,
for k in LOGGERS:
setattr(self, k, None) # init empty logger dictionary
self.csv = True # always log to csv
self.ndjson_console = ('ndjson_console' in self.include) # log ndjson to console
self.ndjson_file = ('ndjson_file' in self.include) # log ndjson to file

# Messages
if not comet_ml:
Expand Down Expand Up @@ -228,6 +242,14 @@ def on_fit_epoch_end(self, vals, epoch, best_fitness, fi):
s = '' if file.exists() else (('%20s,' * n % tuple(['epoch'] + self.keys)).rstrip(',') + '\n') # add header
with open(file, 'a') as f:
f.write(s + ('%20.5g,' * n % tuple([epoch] + vals)).rstrip(',') + '\n')
if self.ndjson_console or self.ndjson_file:
json_data = json.dumps(dict(epoch=epoch, **x), default=_json_default)
if self.ndjson_console:
print(json_data)
if self.ndjson_file:
file = self.save_dir / 'results.ndjson'
with open(file, 'a') as f:
print(json_data, file=f)

if self.tb:
for k, v in x.items():
Expand Down

0 comments on commit 52db52b

Please sign in to comment.