Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start setting up the improved W&B integration #1948

Merged
merged 36 commits into from
Feb 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
3530aa5
Add helper functions for wandb and artifacts
AyushExel Jan 15, 2021
1ef16b1
cleanup
AyushExel Jan 15, 2021
bd37ac7
Reorganize files
AyushExel Jan 15, 2021
7cd5623
Update wandb_utils.py
glenn-jocher Jan 17, 2021
d8daa64
Update log_dataset.py
glenn-jocher Jan 17, 2021
fc7d8f9
Reorganize and update dataloader call
AyushExel Jan 18, 2021
9b8f46a
yaml.SafeLoader
glenn-jocher Jan 20, 2021
2c67ba1
PEP8 reformat
glenn-jocher Jan 20, 2021
9ffb887
remove redundant checks
glenn-jocher Jan 20, 2021
1eca722
Add helper functions for wandb and artifacts
AyushExel Jan 15, 2021
ad8408f
cleanup
AyushExel Jan 15, 2021
8b039b3
Reorganize files
AyushExel Jan 15, 2021
0b715eb
Update wandb_utils.py
glenn-jocher Jan 17, 2021
58ae23f
Update log_dataset.py
glenn-jocher Jan 17, 2021
6671347
Reorganize and update dataloader call
AyushExel Jan 18, 2021
bbc5271
yaml.SafeLoader
glenn-jocher Jan 20, 2021
9bbecc8
PEP8 reformat
glenn-jocher Jan 20, 2021
ceb63cd
remove redundant checks
glenn-jocher Jan 20, 2021
2529fb7
Update util files
AyushExel Jan 21, 2021
efdcd7e
Update wandb_utils.py
AyushExel Jan 21, 2021
0d84870
Remove word size
AyushExel Jan 21, 2021
93219b9
Change path of labels.zip
AyushExel Jan 21, 2021
ac8fad2
remove unused imports
glenn-jocher Jan 23, 2021
e40b817
remove --rect
glenn-jocher Jan 23, 2021
0e21989
log_dataset.py cleanup
glenn-jocher Jan 23, 2021
3e53865
log_dataset.py cleanup2
glenn-jocher Jan 23, 2021
4ebfb83
wandb_utils.py cleanup
glenn-jocher Jan 23, 2021
58616a6
remove redundant id_count
glenn-jocher Jan 23, 2021
a8168c3
wandb_utils.py cleanup2
glenn-jocher Jan 23, 2021
1bb261d
rename cls
glenn-jocher Jan 23, 2021
850b59d
use pathlib for zip
glenn-jocher Jan 23, 2021
94b7631
rename dataloader to dataset
glenn-jocher Jan 23, 2021
b959a02
Change import order
AyushExel Jan 27, 2021
4e9a54c
Remove redundant code
AyushExel Jan 30, 2021
28ad4ab
remove unused import
AyushExel Jan 30, 2021
5651225
remove unused imports
glenn-jocher Feb 2, 2021
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
3 changes: 2 additions & 1 deletion utils/datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,8 @@ def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, r
self.mosaic = self.augment and not self.rect # load 4 images at a time into a mosaic (only during training)
self.mosaic_border = [-img_size // 2, -img_size // 2]
self.stride = stride

self.path = path
glenn-jocher marked this conversation as resolved.
Show resolved Hide resolved

try:
f = [] # image files
for p in path if isinstance(path, list) else [path]:
Expand Down
Empty file added utils/wandb_logging/__init__.py
Empty file.
39 changes: 39 additions & 0 deletions utils/wandb_logging/log_dataset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import argparse
from pathlib import Path

import yaml

from wandb_utils import WandbLogger
from utils.datasets import LoadImagesAndLabels

glenn-jocher marked this conversation as resolved.
Show resolved Hide resolved
WANDB_ARTIFACT_PREFIX = 'wandb-artifact://'


def create_dataset_artifact(opt):
with open(opt.data) as f:
data = yaml.load(f, Loader=yaml.SafeLoader) # data dict
logger = WandbLogger(opt, '', None, data, job_type='create_dataset')
nc, names = (1, ['item']) if opt.single_cls else (int(data['nc']), data['names'])
names = {k: v for k, v in enumerate(names)} # to index dictionary
logger.log_dataset_artifact(LoadImagesAndLabels(data['train']), names, name='train') # trainset
logger.log_dataset_artifact(LoadImagesAndLabels(data['val']), names, name='val') # valset

# Update data.yaml with artifact links
data['train'] = WANDB_ARTIFACT_PREFIX + str(Path(opt.project) / 'train')
data['val'] = WANDB_ARTIFACT_PREFIX + str(Path(opt.project) / 'val')
path = opt.data if opt.overwrite_config else opt.data.replace('.', '_wandb.') # updated data.yaml path
data.pop('download', None) # download via artifact instead of predefined field 'download:'
with open(path, 'w') as f:
yaml.dump(data, f)
print("New Config file => ", path)


if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--data', type=str, default='data/coco128.yaml', help='data.yaml path')
parser.add_argument('--single-cls', action='store_true', help='train as single-class dataset')
parser.add_argument('--project', type=str, default='YOLOv5', help='name of W&B Project')
parser.add_argument('--overwrite_config', action='store_true', help='overwrite data.yaml')
opt = parser.parse_args()

create_dataset_artifact(opt)
145 changes: 145 additions & 0 deletions utils/wandb_logging/wandb_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import json
import shutil
import sys
from datetime import datetime
from pathlib import Path

import torch

sys.path.append(str(Path(__file__).parent.parent.parent)) # add utils/ to path
from utils.general import colorstr, xywh2xyxy

try:
import wandb
except ImportError:
wandb = None
print(f"{colorstr('wandb: ')}Install Weights & Biases for YOLOv5 logging with 'pip install wandb' (recommended)")

WANDB_ARTIFACT_PREFIX = 'wandb-artifact://'


def remove_prefix(from_string, prefix):
return from_string[len(prefix):]


class WandbLogger():
def __init__(self, opt, name, run_id, data_dict, job_type='Training'):
self.wandb = wandb
self.wandb_run = wandb.init(config=opt, resume="allow",
project='YOLOv5' if opt.project == 'runs/train' else Path(opt.project).stem,
name=name,
job_type=job_type,
id=run_id) if self.wandb else None

if job_type == 'Training':
self.setup_training(opt, data_dict)
if opt.bbox_interval == -1:
opt.bbox_interval = (opt.epochs // 10) if opt.epochs > 10 else opt.epochs
if opt.save_period == -1:
opt.save_period = (opt.epochs // 10) if opt.epochs > 10 else opt.epochs

def setup_training(self, opt, data_dict):
self.log_dict = {}
self.train_artifact_path, self.trainset_artifact = \
self.download_dataset_artifact(data_dict['train'], opt.artifact_alias)
self.test_artifact_path, self.testset_artifact = \
self.download_dataset_artifact(data_dict['val'], opt.artifact_alias)
self.result_artifact, self.result_table, self.weights = None, None, None
if self.train_artifact_path is not None:
train_path = Path(self.train_artifact_path) / 'data/images/'
data_dict['train'] = str(train_path)
if self.test_artifact_path is not None:
test_path = Path(self.test_artifact_path) / 'data/images/'
data_dict['val'] = str(test_path)
self.result_artifact = wandb.Artifact("run_" + wandb.run.id + "_progress", "evaluation")
self.result_table = wandb.Table(["epoch", "id", "prediction", "avg_confidence"])
if opt.resume_from_artifact:
modeldir, _ = self.download_model_artifact(opt.resume_from_artifact)
if modeldir:
self.weights = Path(modeldir) / "best.pt"
opt.weights = self.weights

def download_dataset_artifact(self, path, alias):
if path.startswith(WANDB_ARTIFACT_PREFIX):
dataset_artifact = wandb.use_artifact(remove_prefix(path, WANDB_ARTIFACT_PREFIX) + ":" + alias)
assert dataset_artifact is not None, "'Error: W&B dataset artifact doesn\'t exist'"
datadir = dataset_artifact.download()
labels_zip = Path(datadir) / "data/labels.zip"
shutil.unpack_archive(labels_zip, Path(datadir) / 'data/labels', 'zip')
print("Downloaded dataset to : ", datadir)
return datadir, dataset_artifact
return None, None

def download_model_artifact(self, name):
model_artifact = wandb.use_artifact(name + ":latest")
assert model_artifact is not None, 'Error: W&B model artifact doesn\'t exist'
modeldir = model_artifact.download()
print("Downloaded model to : ", modeldir)
return modeldir, model_artifact

def log_model(self, path, opt, epoch):
datetime_suffix = datetime.today().strftime('%Y-%m-%d-%H-%M-%S')
model_artifact = wandb.Artifact('run_' + wandb.run.id + '_model', type='model', metadata={
'original_url': str(path),
'epoch': epoch + 1,
'save period': opt.save_period,
'project': opt.project,
'datetime': datetime_suffix
})
model_artifact.add_file(str(path / 'last.pt'), name='last.pt')
model_artifact.add_file(str(path / 'best.pt'), name='best.pt')
wandb.log_artifact(model_artifact)
print("Saving model artifact on epoch ", epoch + 1)

def log_dataset_artifact(self, dataset, class_to_id, name='dataset'):
artifact = wandb.Artifact(name=name, type="dataset")
image_path = dataset.path
artifact.add_dir(image_path, name='data/images')
table = wandb.Table(columns=["id", "train_image", "Classes"])
class_set = wandb.Classes([{'id': id, 'name': name} for id, name in class_to_id.items()])
for si, (img, labels, paths, shapes) in enumerate(dataset):
height, width = shapes[0]
labels[:, 2:] = (xywh2xyxy(labels[:, 2:].view(-1, 4)))
labels[:, 2:] *= torch.Tensor([width, height, width, height])
box_data = []
img_classes = {}
for cls, *xyxy in labels[:, 1:].tolist():
cls = int(cls)
box_data.append({"position": {"minX": xyxy[0], "minY": xyxy[1], "maxX": xyxy[2], "maxY": xyxy[3]},
"class_id": cls,
"box_caption": "%s" % (class_to_id[cls]),
"scores": {"acc": 1},
"domain": "pixel"})
img_classes[cls] = class_to_id[cls]
boxes = {"ground_truth": {"box_data": box_data, "class_labels": class_to_id}} # inference-space
table.add_data(si, wandb.Image(paths, classes=class_set, boxes=boxes), json.dumps(img_classes))
artifact.add(table, name)
labels_path = 'labels'.join(image_path.rsplit('images', 1))
zip_path = Path(labels_path).parent / (name + '_labels.zip')
if not zip_path.is_file(): # make_archive won't check if file exists
shutil.make_archive(zip_path.with_suffix(''), 'zip', labels_path)
artifact.add_file(str(zip_path), name='data/labels.zip')
wandb.log_artifact(artifact)
print("Saving data to W&B...")

def log(self, log_dict):
if self.wandb_run:
for key, value in log_dict.items():
self.log_dict[key] = value

def end_epoch(self):
if self.wandb_run and self.log_dict:
wandb.log(self.log_dict)
self.log_dict = {}

def finish_run(self):
if self.wandb_run:
if self.result_artifact:
print("Add Training Progress Artifact")
self.result_artifact.add(self.result_table, 'result')
train_results = wandb.JoinedTable(self.testset_artifact.get("val"), self.result_table, "id")
self.result_artifact.add(train_results, 'joined_result')
wandb.log_artifact(self.result_artifact)
if self.log_dict:
wandb.log(self.log_dict)
wandb.run.finish()