181 lines
7.2 KiB
Python
181 lines
7.2 KiB
Python
# YOLOv5 🚀 by Ultralytics, GPL-3.0 license
|
|
"""
|
|
Logging utils
|
|
"""
|
|
|
|
import os
|
|
import warnings
|
|
from pathlib import Path
|
|
|
|
import pkg_resources as pkg
|
|
import torch
|
|
from torch.utils.tensorboard import SummaryWriter
|
|
|
|
from utils.general import LOGGER, colorstr, cv2
|
|
from utils.plots import plot_images, plot_labels, plot_results
|
|
from utils.torch_utils import de_parallel
|
|
|
|
LOGGERS = ('csv', 'tb') # *.csv, TensorBoard, Weights & Biases, ClearML
|
|
RANK = int(os.getenv('RANK', -1))
|
|
|
|
|
|
class Loggers():
|
|
# YOLOv5 Loggers class
|
|
def __init__(self, save_dir=None, weights=None, opt=None, hyp=None, logger=None, include=LOGGERS):
|
|
self.save_dir = save_dir
|
|
self.weights = weights
|
|
self.opt = opt
|
|
self.hyp = hyp
|
|
self.plots = not opt.noplots # plot results
|
|
self.logger = logger # for printing results to console
|
|
self.include = include
|
|
self.keys = [
|
|
'train/box_loss',
|
|
'train/obj_loss',
|
|
'train/cls_loss', # train loss
|
|
'metrics/precision',
|
|
'metrics/recall',
|
|
'metrics/mAP_0.5',
|
|
'metrics/mAP_0.5:0.95', # metrics
|
|
'val/box_loss',
|
|
'val/obj_loss',
|
|
'val/cls_loss', # val loss
|
|
'x/lr0',
|
|
'x/lr1',
|
|
'x/lr2'] # params
|
|
self.best_keys = ['best/epoch', 'best/precision', 'best/recall', 'best/mAP_0.5', 'best/mAP_0.5:0.95']
|
|
for k in LOGGERS:
|
|
setattr(self, k, None) # init empty logger dictionary
|
|
self.csv = True # always log to csv
|
|
|
|
# TensorBoard
|
|
s = self.save_dir
|
|
if 'tb' in self.include and not self.opt.evolve:
|
|
prefix = colorstr('TensorBoard: ')
|
|
self.logger.info(f"{prefix}Start with 'tensorboard --logdir {s.parent}', view at http://localhost:6006/")
|
|
self.tb = SummaryWriter(str(s))
|
|
|
|
@property
|
|
def remote_dataset(self):
|
|
# Get data_dict if custom dataset artifact link is provided
|
|
|
|
return None
|
|
|
|
def on_pretrain_routine_end(self, labels, names):
|
|
# Callback runs on pre-train routine end
|
|
if self.plots:
|
|
plot_labels(labels, names, self.save_dir)
|
|
paths = self.save_dir.glob('*labels*.jpg') # training labels
|
|
|
|
def on_train_batch_end(self, model, ni, imgs, targets, paths, vals):
|
|
log_dict = dict(zip(self.keys[:3], vals))
|
|
# Callback runs on train batch end
|
|
# ni: number integrated batches (since train start)
|
|
if self.plots:
|
|
if ni < 3:
|
|
f = self.save_dir / f'train_batch{ni}.jpg' # filename
|
|
plot_images(imgs, targets, paths, f)
|
|
if ni == 0 and self.tb and not self.opt.sync_bn:
|
|
log_tensorboard_graph(self.tb, model, imgsz=(self.opt.imgsz, self.opt.imgsz))
|
|
|
|
def on_fit_epoch_end(self, vals, epoch, best_fitness, fi):
|
|
# Callback runs at the end of each fit (train+val) epoch
|
|
x = dict(zip(self.keys, vals))
|
|
if self.csv:
|
|
file = self.save_dir / 'results.csv'
|
|
n = len(x) + 1 # number of cols
|
|
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.tb:
|
|
for k, v in x.items():
|
|
self.tb.add_scalar(k, v, epoch)
|
|
|
|
def on_train_end(self, last, best, epoch, results):
|
|
# Callback runs on training end, i.e. saving best model
|
|
if self.plots:
|
|
plot_results(file=self.save_dir / 'results.csv') # save results.png
|
|
files = ['results.png', 'confusion_matrix.png', *(f'{x}_curve.png' for x in ('F1', 'PR', 'P', 'R'))]
|
|
files = [(self.save_dir / f) for f in files if (self.save_dir / f).exists()] # filter
|
|
self.logger.info(f"Results saved to {colorstr('bold', self.save_dir)}")
|
|
|
|
if self.tb: # These images are already captured by ClearML by now, we don't want doubles
|
|
for f in files:
|
|
self.tb.add_image(f.stem, cv2.imread(str(f))[..., ::-1], epoch, dataformats='HWC')
|
|
|
|
class GenericLogger:
|
|
"""
|
|
YOLOv5 General purpose logger for non-task specific logging
|
|
Usage: from utils.loggers import GenericLogger; logger = GenericLogger(...)
|
|
Arguments
|
|
opt: Run arguments
|
|
console_logger: Console logger
|
|
include: loggers to include
|
|
"""
|
|
|
|
def __init__(self, opt, console_logger, include=('tb', 'wandb')):
|
|
# init default loggers
|
|
self.save_dir = Path(opt.save_dir)
|
|
self.include = include
|
|
self.console_logger = console_logger
|
|
self.csv = self.save_dir / 'results.csv' # CSV logger
|
|
if 'tb' in self.include:
|
|
prefix = colorstr('TensorBoard: ')
|
|
self.console_logger.info(
|
|
f"{prefix}Start with 'tensorboard --logdir {self.save_dir.parent}', view at http://localhost:6006/")
|
|
self.tb = SummaryWriter(str(self.save_dir))
|
|
|
|
def log_metrics(self, metrics, epoch):
|
|
# Log metrics dictionary to all loggers
|
|
if self.csv:
|
|
keys, vals = list(metrics.keys()), list(metrics.values())
|
|
n = len(metrics) + 1 # number of cols
|
|
s = '' if self.csv.exists() else (('%23s,' * n % tuple(['epoch'] + keys)).rstrip(',') + '\n') # header
|
|
with open(self.csv, 'a') as f:
|
|
f.write(s + ('%23.5g,' * n % tuple([epoch] + vals)).rstrip(',') + '\n')
|
|
|
|
if self.tb:
|
|
for k, v in metrics.items():
|
|
self.tb.add_scalar(k, v, epoch)
|
|
|
|
def log_images(self, files, name='Images', epoch=0):
|
|
# Log images to all loggers
|
|
files = [Path(f) for f in (files if isinstance(files, (tuple, list)) else [files])] # to Path
|
|
files = [f for f in files if f.exists()] # filter by exists
|
|
|
|
if self.tb:
|
|
for f in files:
|
|
self.tb.add_image(f.stem, cv2.imread(str(f))[..., ::-1], epoch, dataformats='HWC')
|
|
|
|
def log_graph(self, model, imgsz=(640, 640)):
|
|
# Log model graph to all loggers
|
|
if self.tb:
|
|
log_tensorboard_graph(self.tb, model, imgsz)
|
|
|
|
def log_model(self, model_path, epoch=0, metadata={}):
|
|
# a placeholder
|
|
|
|
return None
|
|
|
|
|
|
def log_tensorboard_graph(tb, model, imgsz=(640, 640)):
|
|
# Log model graph to TensorBoard
|
|
try:
|
|
p = next(model.parameters()) # for device, type
|
|
imgsz = (imgsz, imgsz) if isinstance(imgsz, int) else imgsz # expand
|
|
im = torch.zeros((1, 3, *imgsz)).to(p.device).type_as(p) # input image (WARNING: must be zeros, not empty)
|
|
with warnings.catch_warnings():
|
|
warnings.simplefilter('ignore') # suppress jit trace warning
|
|
tb.add_graph(torch.jit.trace(de_parallel(model), im, strict=False), [])
|
|
except Exception as e:
|
|
LOGGER.warning(f'WARNING ⚠️ TensorBoard graph visualization failure {e}')
|
|
|
|
|
|
def web_project_name(project):
|
|
# Convert local project name to web project name
|
|
if not project.startswith('runs/train'):
|
|
return project
|
|
suffix = '-Classify' if project.endswith('-cls') else '-Segment' if project.endswith('-seg') else ''
|
|
return f'YOLOv5{suffix}'
|