From d18adf8df9f5b884cee60a3c5c0eefd1aa52e10a Mon Sep 17 00:00:00 2001 From: ChristopherSTAN <497592613@qq.com> Date: Wed, 24 Jun 2020 15:53:34 -0400 Subject: [PATCH 01/71] Add files via upload --- data/VOC2007.sh | 42 +++++++++++++++++++++++++++++++ data/VOC2012.sh | 38 ++++++++++++++++++++++++++++ data/VOC_split.py | 60 ++++++++++++++++++++++++++++++++++++++++++++ data/organize_VOC.py | 47 ++++++++++++++++++++++++++++++++++ data/voc.yml | 10 ++++++++ 5 files changed, 197 insertions(+) create mode 100644 data/VOC2007.sh create mode 100644 data/VOC2012.sh create mode 100644 data/VOC_split.py create mode 100644 data/organize_VOC.py create mode 100644 data/voc.yml diff --git a/data/VOC2007.sh b/data/VOC2007.sh new file mode 100644 index 0000000..69bad20 --- /dev/null +++ b/data/VOC2007.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# Ellis Brown + +start=`date +%s` + +# handle optional download dir +if [ -z "$1" ] + then + # navigate to ~/data + echo "navigating to ../data/ ..." + mkdir -p ./data + cd ./data/ + else + # check if is valid directory + if [ ! -d $1 ]; then + echo $1 "is not a valid directory" + exit 0 + fi + echo "navigating to" $1 "..." + cd $1 +fi + +echo "Downloading VOC2007 trainval ..." +# Download the data. +curl -LO http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtrainval_06-Nov-2007.tar +echo "Downloading VOC2007 test data ..." +curl -LO http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtest_06-Nov-2007.tar +echo "Done downloading." + +# Extract data +echo "Extracting trainval ..." +tar -xvf VOCtrainval_06-Nov-2007.tar +echo "Extracting test ..." +tar -xvf VOCtest_06-Nov-2007.tar +echo "removing tars ..." +rm VOCtrainval_06-Nov-2007.tar +rm VOCtest_06-Nov-2007.tar + +end=`date +%s` +runtime=$((end-start)) + +echo "Completed in" $runtime "seconds" \ No newline at end of file diff --git a/data/VOC2012.sh b/data/VOC2012.sh new file mode 100644 index 0000000..945f8d7 --- /dev/null +++ b/data/VOC2012.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# Ellis Brown + +start=`date +%s` + +# handle optional download dir +if [ -z "$1" ] + then + # navigate to ~/data + echo "navigating to ~/data/ ..." + mkdir -p ./data + cd ./data/ + else + # check if is valid directory + if [ ! -d $1 ]; then + echo $1 "is not a valid directory" + exit 0 + fi + echo "navigating to" $1 "..." + cd $1 +fi + +echo "Downloading VOC2012 trainval ..." +# Download the data. +curl -LO http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar +echo "Done downloading." + + +# Extract data +echo "Extracting trainval ..." +tar -xvf VOCtrainval_11-May-2012.tar +echo "removing tar ..." +rm VOCtrainval_11-May-2012.tar + +end=`date +%s` +runtime=$((end-start)) + +echo "Completed in" $runtime "seconds" \ No newline at end of file diff --git a/data/VOC_split.py b/data/VOC_split.py new file mode 100644 index 0000000..69405a3 --- /dev/null +++ b/data/VOC_split.py @@ -0,0 +1,60 @@ + +# Run this file with folder VOCdevkit. +import xml.etree.ElementTree as ET +import pickle +import os +from os import listdir, getcwd +from os.path import join + +sets=[('2012', 'train'), ('2012', 'val'), ('2007', 'train'), ('2007', 'val'), ('2007', 'test')] + +classes = ["aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"] + + +def convert(size, box): + dw = 1./(size[0]) + dh = 1./(size[1]) + x = (box[0] + box[1])/2.0 - 1 + y = (box[2] + box[3])/2.0 - 1 + w = box[1] - box[0] + h = box[3] - box[2] + x = x*dw + w = w*dw + y = y*dh + h = h*dh + return (x,y,w,h) + +def convert_annotation(year, image_id): + in_file = open('VOCdevkit/VOC%s/Annotations/%s.xml'%(year, image_id)) + out_file = open('VOCdevkit/VOC%s/labels/%s.txt'%(year, image_id), 'w') + tree=ET.parse(in_file) + root = tree.getroot() + size = root.find('size') + w = int(size.find('width').text) + h = int(size.find('height').text) + + for obj in root.iter('object'): + difficult = obj.find('difficult').text + cls = obj.find('name').text + if cls not in classes or int(difficult)==1: + continue + cls_id = classes.index(cls) + xmlbox = obj.find('bndbox') + b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text), float(xmlbox.find('ymax').text)) + bb = convert((w,h), b) + out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n') + +wd = getcwd() + +for year, image_set in sets: + if not os.path.exists('VOCdevkit/VOC%s/labels/'%(year)): + os.makedirs('VOCdevkit/VOC%s/labels/'%(year)) + image_ids = open('VOCdevkit/VOC%s/ImageSets/Main/%s.txt'%(year, image_set)).read().strip().split() + list_file = open('%s_%s.txt'%(year, image_set), 'w') + for image_id in image_ids: + list_file.write('%s/VOCdevkit/VOC%s/JPEGImages/%s.jpg\n'%(wd, year, image_id)) + convert_annotation(year, image_id) + list_file.close() + +os.system("cat 2007_train.txt 2007_val.txt 2012_train.txt 2012_val.txt > train.txt") +os.system("cat 2007_train.txt 2007_val.txt 2007_test.txt 2012_train.txt 2012_val.txt > train.all.txt") \ No newline at end of file diff --git a/data/organize_VOC.py b/data/organize_VOC.py new file mode 100644 index 0000000..d4ccf3a --- /dev/null +++ b/data/organize_VOC.py @@ -0,0 +1,47 @@ +print(os.path.exists('../data/train.txt')) +f = open('../data/train.txt', 'r') +lines = f.readlines() + +for line in lines: + #print(line.split('/')[-1][:-1]) + line = "/".join(line.split('/')[2:]) + + if (os.path.exists(line[:-1])): + os.system("cp "+ line[:-1] + " VOC/images/train") + +print(os.path.exists('../data/train.txt')) +f = open('../data/train.txt', 'r') +lines = f.readlines() + +for line in lines: + #print(line.split('/')[-1][:-1]) + line = "/".join(line.split('/')[2:]) + line = line.replace('JPEGImages', 'labels') + line = line.replace('jpg', 'txt') + #print(line) + if (os.path.exists(line[:-1])): + os.system("cp "+ line[:-1] + " VOC/labels/train") + +print(os.path.exists('../data/2007_test.txt')) +f = open('../data/2007_test.txt', 'r') +lines = f.readlines() + +for line in lines: + #print(line.split('/')[-1][:-1]) + line = "/".join(line.split('/')[2:]) + + if (os.path.exists(line[:-1])): + os.system("cp "+ line[:-1] + " VOC/images/val") + +print(os.path.exists('../data/2007_test.txt')) +f = open('../data/2007_test.txt', 'r') +lines = f.readlines() + +for line in lines: + #print(line.split('/')[-1][:-1]) + line = "/".join(line.split('/')[2:]) + line = line.replace('JPEGImages', 'labels') + line = line.replace('jpg', 'txt') + #print(line) + if (os.path.exists(line[:-1])): + os.system("cp "+ line[:-1] + " VOC/labels/val") \ No newline at end of file diff --git a/data/voc.yml b/data/voc.yml new file mode 100644 index 0000000..d531fa8 --- /dev/null +++ b/data/voc.yml @@ -0,0 +1,10 @@ + +# train and val datasets (image directory or *.txt file with image paths) +train: ../VOC/images/train/ +val: ../VOC/images/val// + +# number of classes +nc: 20 + +# class names +names: ['aeroplane', 'bicycle','bird','boat','bottle','bus','car','cat','chair','cow','diningtable','dog','horse','motorbike','person','pottedplant','sheep','sofa','train','tvmonitor'] \ No newline at end of file From 93a6765806bf7e0b36f22a836422f1ad6bd5486c Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 27 Jun 2020 13:02:01 -0700 Subject: [PATCH 02/71] update mosaic border --- utils/datasets.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/utils/datasets.py b/utils/datasets.py index aee891c..69bed47 100755 --- a/utils/datasets.py +++ b/utils/datasets.py @@ -62,7 +62,7 @@ def create_dataloader(path, imgsz, batch_size, stride, opt, hyp=None, augment=Fa class LoadImages: # for inference - def __init__(self, path, img_size=416): + def __init__(self, path, img_size=640): path = str(Path(path)) # os-agnostic files = [] if os.path.isdir(path): @@ -139,7 +139,7 @@ class LoadImages: # for inference class LoadWebcam: # for inference - def __init__(self, pipe=0, img_size=416): + def __init__(self, pipe=0, img_size=640): self.img_size = img_size if pipe == '0': @@ -204,7 +204,7 @@ class LoadWebcam: # for inference class LoadStreams: # multiple IP or RTSP cameras - def __init__(self, sources='streams.txt', img_size=416): + def __init__(self, sources='streams.txt', img_size=640): self.mode = 'images' self.img_size = img_size @@ -277,7 +277,7 @@ class LoadStreams: # multiple IP or RTSP cameras class LoadImagesAndLabels(Dataset): # for training/testing - def __init__(self, path, img_size=416, batch_size=16, augment=False, hyp=None, rect=False, image_weights=False, + def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, rect=False, image_weights=False, cache_images=False, single_cls=False, stride=32, pad=0.0): try: path = str(Path(path)) # os-agnostic @@ -307,6 +307,9 @@ class LoadImagesAndLabels(Dataset): # for training/testing self.image_weights = image_weights self.rect = False if image_weights else rect self.mosaic = self.augment and not self.rect # load 4 images at a time into a mosaic (only during training) + self.mosaic_border = None + self.stride = stride + # Define labels self.label_files = [x.replace('images', 'labels').replace(os.path.splitext(x)[-1], '.txt') @@ -585,7 +588,8 @@ def load_mosaic(self, index): labels4 = [] s = self.img_size - xc, yc = [int(random.uniform(s * 0.5, s * 1.5)) for _ in range(2)] # mosaic center x, y + border = [-s // 2, -s // 2] # self.mosaic_border + yc, xc = [int(random.uniform(-x, 2 * s + x)) for x in border] # mosaic center x, y indices = [index] + [random.randint(0, len(self.labels) - 1) for _ in range(3)] # 3 additional image indices for i, index in enumerate(indices): # Load image @@ -633,12 +637,12 @@ def load_mosaic(self, index): translate=self.hyp['translate'], scale=self.hyp['scale'], shear=self.hyp['shear'], - border=-s // 2) # border to remove + border=border) # border to remove return img4, labels4 -def letterbox(img, new_shape=(416, 416), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True): +def letterbox(img, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True): # Resize image to a 32-pixel-multiple rectangle https://github.com/ultralytics/yolov3/issues/232 shape = img.shape[:2] # current shape [height, width] if isinstance(new_shape, int): @@ -671,13 +675,13 @@ def letterbox(img, new_shape=(416, 416), color=(114, 114, 114), auto=True, scale return img, ratio, (dw, dh) -def random_affine(img, targets=(), degrees=10, translate=.1, scale=.1, shear=10, border=0): +def random_affine(img, targets=(), degrees=10, translate=.1, scale=.1, shear=10, border=(0, 0)): # torchvision.transforms.RandomAffine(degrees=(-10, 10), translate=(.1, .1), scale=(.9, 1.1), shear=(-10, 10)) # https://medium.com/uruvideo/dataset-augmentation-with-random-homographies-a8f4b44830d4 # targets = [cls, xyxy] - height = img.shape[0] + border * 2 - width = img.shape[1] + border * 2 + height = img.shape[0] + border[0] * 2 # shape(h,w,c) + width = img.shape[1] + border[1] * 2 # Rotation and Scale R = np.eye(3) @@ -689,8 +693,8 @@ def random_affine(img, targets=(), degrees=10, translate=.1, scale=.1, shear=10, # Translation T = np.eye(3) - T[0, 2] = random.uniform(-translate, translate) * img.shape[0] + border # x translation (pixels) - T[1, 2] = random.uniform(-translate, translate) * img.shape[1] + border # y translation (pixels) + T[0, 2] = random.uniform(-translate, translate) * img.shape[1] + border[1] # x translation (pixels) + T[1, 2] = random.uniform(-translate, translate) * img.shape[0] + border[0] # y translation (pixels) # Shear S = np.eye(3) @@ -699,7 +703,7 @@ def random_affine(img, targets=(), degrees=10, translate=.1, scale=.1, shear=10, # Combined rotation matrix M = S @ T @ R # ORDER IS IMPORTANT HERE!! - if (border != 0) or (M != np.eye(3)).any(): # image changed + if (border[0] != 0) or (border[1] != 0) or (M != np.eye(3)).any(): # image changed img = cv2.warpAffine(img, M[:2], dsize=(width, height), flags=cv2.INTER_LINEAR, borderValue=(114, 114, 114)) # Transform label coordinates From 37e13f88467d29bfb5461ae041fa88e211fbc7ca Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 27 Jun 2020 13:50:15 -0700 Subject: [PATCH 03/71] update mosaic border --- train.py | 4 ++++ utils/datasets.py | 7 +++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/train.py b/train.py index 4238713..a572a37 100644 --- a/train.py +++ b/train.py @@ -207,6 +207,10 @@ def train(hyp): image_weights = labels_to_image_weights(dataset.labels, nc=nc, class_weights=w) dataset.indices = random.choices(range(dataset.n), weights=image_weights, k=dataset.n) # rand weighted idx + # Update mosaic border + # b = int(random.uniform(0.25 * imgsz, 0.75 * imgsz + gs) // gs * gs) + # dataset.mosaic_border = [b - imgsz, -b] # height, width borders + mloss = torch.zeros(4, device=device) # mean losses print(('\n' + '%10s' * 8) % ('Epoch', 'gpu_mem', 'GIoU', 'obj', 'cls', 'total', 'targets', 'img_size')) pbar = tqdm(enumerate(dataloader), total=nb) # progress bar diff --git a/utils/datasets.py b/utils/datasets.py index 69bed47..1d423e4 100755 --- a/utils/datasets.py +++ b/utils/datasets.py @@ -307,7 +307,7 @@ class LoadImagesAndLabels(Dataset): # for training/testing self.image_weights = image_weights self.rect = False if image_weights else rect self.mosaic = self.augment and not self.rect # load 4 images at a time into a mosaic (only during training) - self.mosaic_border = None + self.mosaic_border = [-img_size // 2, -img_size // 2] self.stride = stride @@ -588,8 +588,7 @@ def load_mosaic(self, index): labels4 = [] s = self.img_size - border = [-s // 2, -s // 2] # self.mosaic_border - yc, xc = [int(random.uniform(-x, 2 * s + x)) for x in border] # mosaic center x, y + yc, xc = [int(random.uniform(-x, 2 * s + x)) for x in self.mosaic_border] # mosaic center x, y indices = [index] + [random.randint(0, len(self.labels) - 1) for _ in range(3)] # 3 additional image indices for i, index in enumerate(indices): # Load image @@ -637,7 +636,7 @@ def load_mosaic(self, index): translate=self.hyp['translate'], scale=self.hyp['scale'], shear=self.hyp['shear'], - border=border) # border to remove + border=self.mosaic_border) # border to remove return img4, labels4 From 685091a3fc41ebfef7020f27e2b290cf26e3ef4f Mon Sep 17 00:00:00 2001 From: Jake Poznanski Date: Sun, 28 Jun 2020 13:17:05 -0700 Subject: [PATCH 04/71] Adding torchscript export --- models/torchscript_export.py | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 models/torchscript_export.py diff --git a/models/torchscript_export.py b/models/torchscript_export.py new file mode 100644 index 0000000..1b9d6c5 --- /dev/null +++ b/models/torchscript_export.py @@ -0,0 +1,38 @@ +"""Exports a pytorch *.pt model to *.torchscript format + +Usage: + $ export PYTHONPATH="$PWD" && python models/torchscript_export.py --weights ./weights/yolov5s.pt --img 640 --batch 1 +""" + +import argparse + + +from models.common import * +from utils import google_utils + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--weights', type=str, default='./yolov5s.pt', help='weights path') + parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='image size') + parser.add_argument('--batch-size', type=int, default=1, help='batch size') + opt = parser.parse_args() + print(opt) + + # Parameters + f = opt.weights.replace('.pt', '.torchscript') # onnx filename + img = torch.zeros((opt.batch_size, 3, *opt.img_size)) # image size, (1, 3, 320, 192) iDetection + + # Load pytorch model + google_utils.attempt_download(opt.weights) + model = torch.load(opt.weights, map_location=torch.device('cpu'))['model'].float() + model.eval() + + # Don't fuse layers, it won't work with torchscript exports + #model.fuse() + + # Export to jit/torchscript + model.model[-1].export = True # set Detect() layer export=True + _ = model(img) # dry run + + traced_script_module = torch.jit.trace(model, img) + traced_script_module.save(f) \ No newline at end of file From ec8554a39b778594e79b43ff578b9b2ee31c120a Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 28 Jun 2020 13:26:31 -0700 Subject: [PATCH 05/71] FROM nvcr.io/nvidia/pytorch:20.06-py3 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index bda78c0..01551a0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Start FROM Nvidia PyTorch image https://ngc.nvidia.com/catalog/containers/nvidia:pytorch -FROM nvcr.io/nvidia/pytorch:20.03-py3 +FROM nvcr.io/nvidia/pytorch:20.06-py3 RUN pip install -U gsutil # Create working directory From 0bc80e1e57d1a993e4980e73f7c89820fc2801c5 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 28 Jun 2020 17:07:03 -0700 Subject: [PATCH 06/71] onnx_export.py bug fix #224 --- models/onnx_export.py | 1 + 1 file changed, 1 insertion(+) diff --git a/models/onnx_export.py b/models/onnx_export.py index fe0287e..ca6bd50 100644 --- a/models/onnx_export.py +++ b/models/onnx_export.py @@ -17,6 +17,7 @@ if __name__ == '__main__': parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='image size') parser.add_argument('--batch-size', type=int, default=1, help='batch size') opt = parser.parse_args() + opt.img_size *= 2 if len(opt.img_size) == 1 else 1 print(opt) # Parameters From 0d5065d3b9e50507578ff338cd74896e466a2542 Mon Sep 17 00:00:00 2001 From: Russ Ferriday Date: Mon, 29 Jun 2020 12:56:40 +0100 Subject: [PATCH 07/71] Remove non-ascii that breaks pdb single-stepping --- detect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/detect.py b/detect.py index 69a37de..988a002 100644 --- a/detect.py +++ b/detect.py @@ -82,7 +82,7 @@ def detect(save_img=False): save_path = str(Path(out) / Path(p).name) txt_path = str(Path(out) / Path(p).stem) + ('_%g' % dataset.frame if dataset.mode == 'video' else '') s += '%gx%g ' % img.shape[2:] # print string - gn = torch.tensor(im0.shape)[[1, 0, 1, 0]] #  normalization gain whwh + gn = torch.tensor(im0.shape)[[1, 0, 1, 0]] # normalization gain whwh if det is not None and len(det): # Rescale boxes from img_size to im0 size det[:, :4] = scale_coords(img.shape[2:], det[:, :4], im0.shape).round() From b203c9b7ffe89ae1b9182dc9516f627153fdeeac Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 29 Jun 2020 12:45:25 -0700 Subject: [PATCH 08/71] update train.py incompatible model message fix #222 --- train.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/train.py b/train.py index a572a37..39dd0e5 100644 --- a/train.py +++ b/train.py @@ -118,8 +118,9 @@ def train(hyp): if model.state_dict()[k].shape == v.shape} # to FP32, filter model.load_state_dict(ckpt['model'], strict=False) except KeyError as e: - s = "%s is not compatible with %s. Specify --weights '' or specify a --cfg compatible with %s." \ - % (opt.weights, opt.cfg, opt.weights) + s = "%s is not compatible with %s. This may be due to model differences or %s may be out of date. " \ + "Please delete or update %s and try again, or use --weights '' to train from scatch." \ + % (opt.weights, opt.cfg, opt.weights, opt.weights) raise KeyError(s) from e # load optimizer From 615d6d0cfa44f1720df177101bf350b737887354 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 29 Jun 2020 14:00:13 -0700 Subject: [PATCH 09/71] FROM nvcr.io/nvidia/pytorch:20.06-py3 --- models/export.py | 55 ++++++++++++++++++++++++++++++++++++ models/onnx_export.py | 43 ---------------------------- models/torchscript_export.py | 38 ------------------------- 3 files changed, 55 insertions(+), 81 deletions(-) create mode 100644 models/export.py delete mode 100644 models/onnx_export.py delete mode 100644 models/torchscript_export.py diff --git a/models/export.py b/models/export.py new file mode 100644 index 0000000..2aa6ce4 --- /dev/null +++ b/models/export.py @@ -0,0 +1,55 @@ +"""Exports a YOLOv5 *.pt model to *.onnx and *.torchscript formats + +Usage: + $ export PYTHONPATH="$PWD" && python models/export.py --weights ./weights/yolov5s.pt --img 640 --batch 1 +""" + +import argparse + +import onnx + +from models.common import * +from utils import google_utils + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--weights', type=str, default='./yolov5s.pt', help='weights path') + parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='image size') + parser.add_argument('--batch-size', type=int, default=1, help='batch size') + opt = parser.parse_args() + opt.img_size *= 2 if len(opt.img_size) == 1 else 1 # expand + print(opt) + + # Input + img = torch.zeros((opt.batch_size, 3, *opt.img_size)) # image size, (1, 3, 320, 192) iDetection + + # Load PyTorch model + google_utils.attempt_download(opt.weights) + model = torch.load(opt.weights, map_location=torch.device('cpu'))['model'].float() + model.eval() + model.model[-1].export = True # set Detect() layer export=True + _ = model(img) # dry run + + # Export to torchscript + try: + f = opt.weights.replace('.pt', '.torchscript') # filename + ts = torch.jit.trace(model, img) + ts.save(f) + print('Torchscript export success, saved as %s' % f) + except: + print('Torchscript export failed.') + + # Export to ONNX + try: + f = opt.weights.replace('.pt', '.onnx') # filename + model.fuse() # only for ONNX + torch.onnx.export(model, img, f, verbose=False, opset_version=11, input_names=['images'], + output_names=['output']) # output_names=['classes', 'boxes'] + + # Checks + onnx_model = onnx.load(f) # load onnx model + onnx.checker.check_model(onnx_model) # check onnx model + print(onnx.helper.printable_graph(onnx_model.graph)) # print a human readable representation of the graph + print('ONNX export success, saved as %s\nView with https://github.com/lutzroeder/netron' % f) + except: + print('ONNX export failed.') diff --git a/models/onnx_export.py b/models/onnx_export.py deleted file mode 100644 index ca6bd50..0000000 --- a/models/onnx_export.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Exports a pytorch *.pt model to *.onnx format - -Usage: - $ export PYTHONPATH="$PWD" && python models/onnx_export.py --weights ./weights/yolov5s.pt --img 640 --batch 1 -""" - -import argparse - -import onnx - -from models.common import * -from utils import google_utils - -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument('--weights', type=str, default='./yolov5s.pt', help='weights path') - parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='image size') - parser.add_argument('--batch-size', type=int, default=1, help='batch size') - opt = parser.parse_args() - opt.img_size *= 2 if len(opt.img_size) == 1 else 1 - print(opt) - - # Parameters - f = opt.weights.replace('.pt', '.onnx') # onnx filename - img = torch.zeros((opt.batch_size, 3, *opt.img_size)) # image size, (1, 3, 320, 192) iDetection - - # Load pytorch model - google_utils.attempt_download(opt.weights) - model = torch.load(opt.weights, map_location=torch.device('cpu'))['model'].float() - model.eval() - model.fuse() - - # Export to onnx - model.model[-1].export = True # set Detect() layer export=True - _ = model(img) # dry run - torch.onnx.export(model, img, f, verbose=False, opset_version=11, input_names=['images'], - output_names=['output']) # output_names=['classes', 'boxes'] - - # Check onnx model - model = onnx.load(f) # load onnx model - onnx.checker.check_model(model) # check onnx model - print(onnx.helper.printable_graph(model.graph)) # print a human readable representation of the graph - print('Export complete. ONNX model saved to %s\nView with https://github.com/lutzroeder/netron' % f) diff --git a/models/torchscript_export.py b/models/torchscript_export.py deleted file mode 100644 index 1b9d6c5..0000000 --- a/models/torchscript_export.py +++ /dev/null @@ -1,38 +0,0 @@ -"""Exports a pytorch *.pt model to *.torchscript format - -Usage: - $ export PYTHONPATH="$PWD" && python models/torchscript_export.py --weights ./weights/yolov5s.pt --img 640 --batch 1 -""" - -import argparse - - -from models.common import * -from utils import google_utils - -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument('--weights', type=str, default='./yolov5s.pt', help='weights path') - parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='image size') - parser.add_argument('--batch-size', type=int, default=1, help='batch size') - opt = parser.parse_args() - print(opt) - - # Parameters - f = opt.weights.replace('.pt', '.torchscript') # onnx filename - img = torch.zeros((opt.batch_size, 3, *opt.img_size)) # image size, (1, 3, 320, 192) iDetection - - # Load pytorch model - google_utils.attempt_download(opt.weights) - model = torch.load(opt.weights, map_location=torch.device('cpu'))['model'].float() - model.eval() - - # Don't fuse layers, it won't work with torchscript exports - #model.fuse() - - # Export to jit/torchscript - model.model[-1].export = True # set Detect() layer export=True - _ = model(img) # dry run - - traced_script_module = torch.jit.trace(model, img) - traced_script_module.save(f) \ No newline at end of file From 9a9333d245ed22cc57dc9be71f445b98422d636b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 29 Jun 2020 17:10:33 -0700 Subject: [PATCH 10/71] add replicate() to datasets.py --- utils/datasets.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/utils/datasets.py b/utils/datasets.py index 1d423e4..1ebd709 100755 --- a/utils/datasets.py +++ b/utils/datasets.py @@ -310,7 +310,6 @@ class LoadImagesAndLabels(Dataset): # for training/testing self.mosaic_border = [-img_size // 2, -img_size // 2] self.stride = stride - # Define labels self.label_files = [x.replace('images', 'labels').replace(os.path.splitext(x)[-1], '.txt') for x in self.img_files] @@ -629,6 +628,9 @@ def load_mosaic(self, index): # np.clip(labels4[:, 1:] - s / 2, 0, s, out=labels4[:, 1:]) # use with center crop np.clip(labels4[:, 1:], 0, 2 * s, out=labels4[:, 1:]) # use with random_affine + # Replicate + # img4, labels4 = replicate(img4, labels4) + # Augment # img4 = img4[s // 2: int(s * 1.5), s // 2:int(s * 1.5)] # center crop (WARNING, requires box pruning) img4, labels4 = random_affine(img4, labels4, @@ -641,6 +643,23 @@ def load_mosaic(self, index): return img4, labels4 +def replicate(img, labels): + # Replicate labels + h, w = img.shape[:2] + boxes = labels[:, 1:].astype(int) + x1, y1, x2, y2 = boxes.T + s = ((x2 - x1) + (y2 - y1)) / 2 # side length (pixels) + for i in s.argsort()[:round(s.size * 0.5)]: # smallest indices + x1b, y1b, x2b, y2b = boxes[i] + bh, bw = y2b - y1b, x2b - x1b + yc, xc = int(random.uniform(0, h - bh)), int(random.uniform(0, w - bw)) # offset x, y + x1a, y1a, x2a, y2a = [xc, yc, xc + bw, yc + bh] + img[y1a:y2a, x1a:x2a] = img[y1b:y2b, x1b:x2b] # img4[ymin:ymax, xmin:xmax] + labels = np.append(labels, [[labels[i, 0], x1a, y1a, x2a, y2a]], axis=0) + + return img, labels + + def letterbox(img, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True): # Resize image to a 32-pixel-multiple rectangle https://github.com/ultralytics/yolov3/issues/232 shape = img.shape[:2] # current shape [height, width] @@ -765,6 +784,7 @@ def cutout(image, labels): box2_area = (b2_x2 - b2_x1) * (b2_y2 - b2_y1) + 1e-16 # Intersection over box2 area + return inter_area / box2_area # create random masks From cdb9bde181641917504717e162952826fca61a41 Mon Sep 17 00:00:00 2001 From: yxNONG <62932917+yxNONG@users.noreply.github.com> Date: Tue, 30 Jun 2020 19:06:28 +0800 Subject: [PATCH 11/71] Unify the check point of single and multi GPU save the model.hyp etc to checkpoint when use multi GPU training --- train.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/train.py b/train.py index 39dd0e5..d933a5d 100644 --- a/train.py +++ b/train.py @@ -79,7 +79,7 @@ def train(hyp): # Create model model = Model(opt.cfg).to(device) assert model.md['nc'] == nc, '%s nc=%g classes but %s nc=%g classes' % (opt.data, nc, opt.cfg, model.md['nc']) - model.names = data_dict['names'] + # Image sizes gs = int(max(model.stride)) # grid size (max stride) @@ -172,6 +172,7 @@ def train(hyp): model.hyp = hyp # attach hyperparameters to model model.gr = 1.0 # giou loss ratio (obj_loss = 1.0 or giou) model.class_weights = labels_to_class_weights(dataset.labels, nc).to(device) # attach class weights + model.names = data_dict['names'] # Class frequency labels = np.concatenate(dataset.labels, 0) @@ -314,6 +315,14 @@ def train(hyp): # Save model save = (not opt.nosave) or (final_epoch and not opt.evolve) if save: + if hasattr(model, 'module'): + # Duplicate Model parameters for Multi-GPU save + ema.ema.module.nc = model.nc # attach number of classes to model + ema.ema.module.hyp = model.hyp # attach hyperparameters to model + ema.ema.module.gr = model.gr = 1.0 # giou loss ratio (obj_loss = 1.0 or giou) + ema.ema.module.class_weights = model.class_weights # attach class weights + ema.ema.module.names = data_dict['names'] + with open(results_file, 'r') as f: # create checkpoint ckpt = {'epoch': epoch, 'best_fitness': best_fitness, From 3b16c865f0d0764ab4585230fb403dcda92f200d Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 30 Jun 2020 14:08:08 -0700 Subject: [PATCH 12/71] assert --epochs --- train.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/train.py b/train.py index 39dd0e5..3df5239 100644 --- a/train.py +++ b/train.py @@ -134,6 +134,8 @@ def train(hyp): file.write(ckpt['training_results']) # write results.txt start_epoch = ckpt['epoch'] + 1 + assert opt.epochs > start_epoch, '%s has already trained %g epochs. --epochs must be greater than %g' % \ + (opt.weights, ckpt['epoch'], ckpt['epoch']) del ckpt # Mixed precision training https://github.com/NVIDIA/apex From a9dc0c2c29c45e1b4a7bb764c9d6f6230750b01a Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 30 Jun 2020 15:25:38 -0700 Subject: [PATCH 13/71] update check_img_size() for model strides --- detect.py | 2 +- test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/detect.py b/detect.py index 988a002..bb84a0d 100644 --- a/detect.py +++ b/detect.py @@ -25,6 +25,7 @@ def detect(save_img=False): # torch.save(torch.load(weights, map_location=device), weights) # update model if SourceChangeWarning # model.fuse() model.to(device).eval() + imgsz = check_img_size(imgsz, s=model.model[-1].stride.max()) # check img_size if half: model.half() # to FP16 @@ -152,7 +153,6 @@ if __name__ == '__main__': parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS') parser.add_argument('--augment', action='store_true', help='augmented inference') opt = parser.parse_args() - opt.img_size = check_img_size(opt.img_size) print(opt) with torch.no_grad(): diff --git a/test.py b/test.py index c0bda5f..259d444 100644 --- a/test.py +++ b/test.py @@ -34,6 +34,7 @@ def test(data, torch_utils.model_info(model) model.fuse() model.to(device) + imgsz = check_img_size(imgsz, s=model.model[-1].stride.max()) # check img_size # Multi-GPU disabled, incompatible with .half() https://github.com/ultralytics/yolov5/issues/99 # if device.type != 'cpu' and torch.cuda.device_count() > 1: @@ -242,7 +243,6 @@ if __name__ == '__main__': parser.add_argument('--merge', action='store_true', help='use Merge NMS') parser.add_argument('--verbose', action='store_true', help='report mAP by class') opt = parser.parse_args() - opt.img_size = check_img_size(opt.img_size) opt.save_json = opt.save_json or opt.data.endswith('coco.yaml') opt.data = check_file(opt.data) # check file print(opt) From ad4c22cbfeb1acbf804e3d6411c263ccc4069a2e Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 30 Jun 2020 16:16:29 -0700 Subject: [PATCH 14/71] --resume bug fix #187 --- train.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/train.py b/train.py index 3df5239..27a8771 100644 --- a/train.py +++ b/train.py @@ -133,9 +133,13 @@ def train(hyp): with open(results_file, 'w') as file: file.write(ckpt['training_results']) # write results.txt + # epochs start_epoch = ckpt['epoch'] + 1 - assert opt.epochs > start_epoch, '%s has already trained %g epochs. --epochs must be greater than %g' % \ - (opt.weights, ckpt['epoch'], ckpt['epoch']) + if epochs < start_epoch: + print('%s has been trained for %g epochs. Fine-tuning for %g additional epochs.' % + (opt.weights, ckpt['epoch'], epochs)) + epochs += ckpt['epoch'] # finetune additional epochs + del ckpt # Mixed precision training https://github.com/NVIDIA/apex @@ -166,7 +170,7 @@ def train(hyp): # Testloader testloader = create_dataloader(test_path, imgsz_test, batch_size, gs, opt, - hyp=hyp, augment=False, cache=opt.cache_images, rect=True)[0] + hyp=hyp, augment=False, cache=opt.cache_images, rect=True)[0] # Model parameters hyp['cls'] *= nc / 80. # scale coco-tuned hyp['cls'] to current dataset From 4e62eb90be664dcd6c19b4cc70a8bc4e5bd394fb Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 30 Jun 2020 16:41:26 -0700 Subject: [PATCH 15/71] check_img_size() int() update --- utils/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/utils.py b/utils/utils.py index c33f41f..305486a 100755 --- a/utils/utils.py +++ b/utils/utils.py @@ -47,7 +47,7 @@ def check_git_status(): def check_img_size(img_size, s=32): # Verify img_size is a multiple of stride s - new_size = make_divisible(img_size, s) # ceil gs-multiple + new_size = make_divisible(img_size, int(s)) # ceil gs-multiple if new_size != img_size: print('WARNING: --img-size %g must be multiple of max stride %g, updating to %g' % (img_size, s, new_size)) return new_size From 86784cfdbf123df3945ff5664a3ffe46a2304aa8 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 30 Jun 2020 21:43:53 -0700 Subject: [PATCH 16/71] --resume bug fix #252 --- train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/train.py b/train.py index 27a8771..fa672d1 100644 --- a/train.py +++ b/train.py @@ -378,7 +378,7 @@ if __name__ == '__main__': parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%') parser.add_argument('--single-cls', action='store_true', help='train as single-class dataset') opt = parser.parse_args() - opt.weights = last if opt.resume else opt.weights + opt.weights = last if opt.resume and not opt.weights else opt.weights opt.cfg = check_file(opt.cfg) # check file opt.data = check_file(opt.data) # check file print(opt) From 1c86c2cb6600f4d019fcd3ba67bebceeaf982f5a Mon Sep 17 00:00:00 2001 From: edurenye Date: Wed, 1 Jul 2020 10:04:26 +0200 Subject: [PATCH 17/71] Add torchscript files to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5a95798..07993ab 100755 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,7 @@ gcp_test*.sh *.pt *.onnx *.mlmodel +*.torchscript darknet53.conv.74 yolov3-tiny.conv.15 From b5659d1195907472d7db5f23bab15d7d3b101891 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 1 Jul 2020 11:44:49 -0700 Subject: [PATCH 18/71] module updates --- models/common.py | 17 ++++++++++------- models/experimental.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/models/common.py b/models/common.py index 3c4a0d7..6a59723 100644 --- a/models/common.py +++ b/models/common.py @@ -1,9 +1,13 @@ # This file contains modules common to various models - from utils.utils import * +def autopad(k): + # Pad to 'same' + return k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad + + def DWConv(c1, c2, k=1, s=1, act=True): # Depthwise convolution return Conv(c1, c2, k, s, g=math.gcd(c1, c2), act=act) @@ -11,10 +15,9 @@ def DWConv(c1, c2, k=1, s=1, act=True): class Conv(nn.Module): # Standard convolution - def __init__(self, c1, c2, k=1, s=1, g=1, act=True): # ch_in, ch_out, kernel, stride, groups + def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups super(Conv, self).__init__() - p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # padding - self.conv = nn.Conv2d(c1, c2, k, s, p, groups=g, bias=False) + self.conv = nn.Conv2d(c1, c2, k, s, p or autopad(k), groups=g, bias=False) self.bn = nn.BatchNorm2d(c2) self.act = nn.LeakyReLU(0.1, inplace=True) if act else nn.Identity() @@ -46,7 +49,7 @@ class BottleneckCSP(nn.Module): self.cv1 = Conv(c1, c_, 1, 1) self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False) self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False) - self.cv4 = Conv(c2, c2, 1, 1) + self.cv4 = Conv(2 * c_, c2, 1, 1) self.bn = nn.BatchNorm2d(2 * c_) # applied to cat(cv2, cv3) self.act = nn.LeakyReLU(0.1, inplace=True) self.m = nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)]) @@ -79,9 +82,9 @@ class Flatten(nn.Module): class Focus(nn.Module): # Focus wh information into c-space - def __init__(self, c1, c2, k=1): + def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups super(Focus, self).__init__() - self.conv = Conv(c1 * 4, c2, k, 1) + self.conv = Conv(c1 * 4, c2, k, s, p, g, act) def forward(self, x): # x(b,c,w,h) -> y(b,4c,w/2,h/2) return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1)) diff --git a/models/experimental.py b/models/experimental.py index 60cb7aa..cff9d14 100644 --- a/models/experimental.py +++ b/models/experimental.py @@ -1,6 +1,40 @@ +# This file contains experimental modules + from models.common import * +class CrossConv(nn.Module): + # Cross Convolution + def __init__(self, c1, c2, shortcut=True, g=1, e=0.5): # ch_in, ch_out, shortcut, groups, expansion + super(CrossConv, self).__init__() + c_ = int(c2 * e) # hidden channels + self.cv1 = Conv(c1, c_, (1, 3), 1) + self.cv2 = Conv(c_, c2, (3, 1), 1, g=g) + self.add = shortcut and c1 == c2 + + def forward(self, x): + return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x)) + + +class C3(nn.Module): + # Cross Convolution CSP + def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion + super(C3, self).__init__() + c_ = int(c2 * e) # hidden channels + self.cv1 = Conv(c1, c_, 1, 1) + self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False) + self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False) + self.cv4 = Conv(2 * c_, c2, 1, 1) + self.bn = nn.BatchNorm2d(2 * c_) # applied to cat(cv2, cv3) + self.act = nn.LeakyReLU(0.1, inplace=True) + self.m = nn.Sequential(*[CrossConv(c_, c_, shortcut, g, e=1.0) for _ in range(n)]) + + def forward(self, x): + y1 = self.cv3(self.m(self.cv1(x))) + y2 = self.cv2(x) + return self.cv4(self.act(self.bn(torch.cat((y1, y2), dim=1)))) + + class Sum(nn.Module): # Weighted sum of 2 or more layers https://arxiv.org/abs/1911.09070 def __init__(self, n, weight=False): # n: number of inputs From f1d67f4110a29292b372aec2b94243ea82a9f7a2 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 1 Jul 2020 15:46:15 -0700 Subject: [PATCH 19/71] update export.py --- models/export.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/models/export.py b/models/export.py index 2aa6ce4..1c78d3a 100644 --- a/models/export.py +++ b/models/export.py @@ -1,4 +1,4 @@ -"""Exports a YOLOv5 *.pt model to *.onnx and *.torchscript formats +"""Exports a YOLOv5 *.pt model to ONNX and TorchScript formats Usage: $ export PYTHONPATH="$PWD" && python models/export.py --weights ./weights/yolov5s.pt --img 640 --batch 1 @@ -30,20 +30,20 @@ if __name__ == '__main__': model.model[-1].export = True # set Detect() layer export=True _ = model(img) # dry run - # Export to torchscript + # Export to TorchScript try: f = opt.weights.replace('.pt', '.torchscript') # filename ts = torch.jit.trace(model, img) ts.save(f) - print('Torchscript export success, saved as %s' % f) - except: - print('Torchscript export failed.') + print('TorchScript export success, saved as %s' % f) + except Exception as e: + print('TorchScript export failed: %s' % e) # Export to ONNX try: f = opt.weights.replace('.pt', '.onnx') # filename model.fuse() # only for ONNX - torch.onnx.export(model, img, f, verbose=False, opset_version=11, input_names=['images'], + torch.onnx.export(model, img, f, verbose=False, opset_version=12, input_names=['images'], output_names=['output']) # output_names=['classes', 'boxes'] # Checks @@ -51,5 +51,5 @@ if __name__ == '__main__': onnx.checker.check_model(onnx_model) # check onnx model print(onnx.helper.printable_graph(onnx_model.graph)) # print a human readable representation of the graph print('ONNX export success, saved as %s\nView with https://github.com/lutzroeder/netron' % f) - except: - print('ONNX export failed.') + except Exception as e: + print('ONNX export failed: %s' % e) From a62a1c2c679cc6de730debf1529f073d10180452 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 1 Jul 2020 16:14:49 -0700 Subject: [PATCH 20/71] export.py update --- detect.py | 8 ++++---- models/export.py | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/detect.py b/detect.py index bb84a0d..93faf6d 100644 --- a/detect.py +++ b/detect.py @@ -156,9 +156,9 @@ if __name__ == '__main__': print(opt) with torch.no_grad(): - detect() + # detect() # Update all models - # for opt.weights in ['yolov5s.pt', 'yolov5m.pt', 'yolov5l.pt', 'yolov5x.pt', 'yolov3-spp.pt']: - # detect() - # create_pretrained(opt.weights, opt.weights) + for opt.weights in ['yolov5s.pt', 'yolov5m.pt', 'yolov5l.pt', 'yolov5x.pt', 'yolov3-spp.pt']: + detect() + create_pretrained(opt.weights, opt.weights) diff --git a/models/export.py b/models/export.py index 1c78d3a..bb310f3 100644 --- a/models/export.py +++ b/models/export.py @@ -6,8 +6,6 @@ Usage: import argparse -import onnx - from models.common import * from utils import google_utils @@ -21,7 +19,7 @@ if __name__ == '__main__': print(opt) # Input - img = torch.zeros((opt.batch_size, 3, *opt.img_size)) # image size, (1, 3, 320, 192) iDetection + img = torch.zeros((opt.batch_size, 3, *opt.img_size)) # image size(1,3,320,192) iDetection # Load PyTorch model google_utils.attempt_download(opt.weights) @@ -30,7 +28,7 @@ if __name__ == '__main__': model.model[-1].export = True # set Detect() layer export=True _ = model(img) # dry run - # Export to TorchScript + # TorchScript export try: f = opt.weights.replace('.pt', '.torchscript') # filename ts = torch.jit.trace(model, img) @@ -39,8 +37,10 @@ if __name__ == '__main__': except Exception as e: print('TorchScript export failed: %s' % e) - # Export to ONNX + # ONNX export try: + import onnx + f = opt.weights.replace('.pt', '.onnx') # filename model.fuse() # only for ONNX torch.onnx.export(model, img, f, verbose=False, opset_version=12, input_names=['images'], From 5323ad224d90680da9cdb7fd8b82089750b7252e Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 1 Jul 2020 16:15:25 -0700 Subject: [PATCH 21/71] export.py update --- detect.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/detect.py b/detect.py index 93faf6d..2650c20 100644 --- a/detect.py +++ b/detect.py @@ -156,9 +156,9 @@ if __name__ == '__main__': print(opt) with torch.no_grad(): - # detect() + detect() - # Update all models - for opt.weights in ['yolov5s.pt', 'yolov5m.pt', 'yolov5l.pt', 'yolov5x.pt', 'yolov3-spp.pt']: - detect() - create_pretrained(opt.weights, opt.weights) + # # Update all models + # for opt.weights in ['yolov5s.pt', 'yolov5m.pt', 'yolov5l.pt', 'yolov5x.pt', 'yolov3-spp.pt']: + # detect() + # create_pretrained(opt.weights, opt.weights) From 1fca7a7f2461f6e178833ebc7d938fea86a6bf84 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Wed, 1 Jul 2020 19:15:59 -0700 Subject: [PATCH 22/71] autopad() update in common.py --- models/common.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/models/common.py b/models/common.py index 6a59723..2c2d600 100644 --- a/models/common.py +++ b/models/common.py @@ -3,9 +3,11 @@ from utils.utils import * -def autopad(k): +def autopad(k, p=None): # kernel, padding # Pad to 'same' - return k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad + if p is None: + p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad + return p def DWConv(c1, c2, k=1, s=1, act=True): @@ -17,7 +19,7 @@ class Conv(nn.Module): # Standard convolution def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups super(Conv, self).__init__() - self.conv = nn.Conv2d(c1, c2, k, s, p or autopad(k), groups=g, bias=False) + self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False) self.bn = nn.BatchNorm2d(c2) self.act = nn.LeakyReLU(0.1, inplace=True) if act else nn.Identity() From 53cdaf6bf5fcdf28b140d3898d59876ceee5fac0 Mon Sep 17 00:00:00 2001 From: yxNONG <62932917+yxNONG@users.noreply.github.com> Date: Thu, 2 Jul 2020 13:48:19 +0800 Subject: [PATCH 23/71] Update utils.py --- utils/utils.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/utils/utils.py b/utils/utils.py index c33f41f..2205999 100755 --- a/utils/utils.py +++ b/utils/utils.py @@ -421,7 +421,9 @@ def compute_loss(p, targets, model): # predictions, targets, model ft = torch.cuda.FloatTensor if p[0].is_cuda else torch.Tensor lcls, lbox, lobj = ft([0]), ft([0]), ft([0]) tcls, tbox, indices, anchors = build_targets(p, targets, model) # targets - h = model.hyp # hyperparameters + h = model.module.hyp if hasattr(model, 'module') else model.hyp # hyperparameters + nc = model.module.nc if hasattr(model, 'module') else model.nc + gr = model.module.gr if hasattr(model, 'module') else model.gr red = 'mean' # Loss reduction (sum or mean) # Define criteria @@ -455,10 +457,10 @@ def compute_loss(p, targets, model): # predictions, targets, model lbox += (1.0 - giou).sum() if red == 'sum' else (1.0 - giou).mean() # giou loss # Obj - tobj[b, a, gj, gi] = (1.0 - model.gr) + model.gr * giou.detach().clamp(0).type(tobj.dtype) # giou ratio + tobj[b, a, gj, gi] = (1.0 - gr) + gr * giou.detach().clamp(0).type(tobj.dtype) # giou ratio # Class - if model.nc > 1: # cls loss (only if multiple classes) + if nc > 1: # cls loss (only if multiple classes) t = torch.full_like(ps[:, 5:], cn) # targets t[range(nb), tcls[i]] = cp lcls += BCEcls(ps[:, 5:], t) # BCE @@ -477,7 +479,7 @@ def compute_loss(p, targets, model): # predictions, targets, model g = 3.0 # loss gain lobj *= g / bs if nt: - lcls *= g / nt / model.nc + lcls *= g / nt / nc lbox *= g / nt loss = lbox + lobj + lcls @@ -488,6 +490,8 @@ def build_targets(p, targets, model): # Build targets for compute_loss(), input targets(image,class,x,y,w,h) det = model.module.model[-1] if type(model) in (nn.parallel.DataParallel, nn.parallel.DistributedDataParallel) \ else model.model[-1] # Detect() module + hyp = model.module.hyp if hasattr(model, 'module') else model.hyp + na, nt = det.na, targets.shape[0] # number of anchors, targets tcls, tbox, indices, anch = [], [], [], [] gain = torch.ones(6, device=targets.device) # normalized to gridspace gain @@ -503,7 +507,7 @@ def build_targets(p, targets, model): a, t, offsets = [], targets * gain, 0 if nt: r = t[None, :, 4:6] / anchors[:, None] # wh ratio - j = torch.max(r, 1. / r).max(2)[0] < model.hyp['anchor_t'] # compare + j = torch.max(r, 1. / r).max(2)[0] < hyp['anchor_t'] # compare # j = wh_iou(anchors, t[:, 4:6]) > model.hyp['iou_t'] # iou(3,n) = wh_iou(anchors(3,2), gwh(n,2)) a, t = at[j], t.repeat(na, 1, 1)[j] # filter From 1aa2b679333657cc20a702dabb1b5de3315cf577 Mon Sep 17 00:00:00 2001 From: yxNONG <62932917+yxNONG@users.noreply.github.com> Date: Thu, 2 Jul 2020 13:51:52 +0800 Subject: [PATCH 24/71] Update train.py --- train.py | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/train.py b/train.py index d933a5d..3b7c9a5 100644 --- a/train.py +++ b/train.py @@ -147,15 +147,6 @@ def train(hyp): # https://discuss.pytorch.org/t/a-problem-occured-when-resuming-an-optimizer/28822 # plot_lr_scheduler(optimizer, scheduler, epochs) - # Initialize distributed training - if device.type != 'cpu' and torch.cuda.device_count() > 1 and torch.distributed.is_available(): - dist.init_process_group(backend='nccl', # distributed backend - init_method='tcp://127.0.0.1:9999', # init method - world_size=1, # number of nodes - rank=0) # node rank - model = torch.nn.parallel.DistributedDataParallel(model) - # pip install torch==1.4.0+cu100 torchvision==0.5.0+cu100 -f https://download.pytorch.org/whl/torch_stable.html - # Trainloader dataloader, dataset = create_dataloader(train_path, imgsz, batch_size, gs, opt, hyp=hyp, augment=True, cache=opt.cache_images, rect=opt.rect) @@ -173,6 +164,15 @@ def train(hyp): model.gr = 1.0 # giou loss ratio (obj_loss = 1.0 or giou) model.class_weights = labels_to_class_weights(dataset.labels, nc).to(device) # attach class weights model.names = data_dict['names'] + + # Initialize distributed training + if device.type != 'cpu' and torch.cuda.device_count() > 1 and torch.distributed.is_available(): + dist.init_process_group(backend='nccl', # distributed backend + init_method='tcp://127.0.0.1:9999', # init method + world_size=1, # number of nodes + rank=0) # node rank + model = torch.nn.parallel.DistributedDataParallel(model) + # pip install torch==1.4.0+cu100 torchvision==0.5.0+cu100 -f https://download.pytorch.org/whl/torch_stable.html # Class frequency labels = np.concatenate(dataset.labels, 0) @@ -289,7 +289,7 @@ def train(hyp): batch_size=batch_size, imgsz=imgsz_test, save_json=final_epoch and opt.data.endswith(os.sep + 'coco.yaml'), - model=ema.ema, + model=ema.ema.module if hasattr(model, 'module') else ema.ema, single_cls=opt.single_cls, dataloader=testloader) @@ -315,14 +315,6 @@ def train(hyp): # Save model save = (not opt.nosave) or (final_epoch and not opt.evolve) if save: - if hasattr(model, 'module'): - # Duplicate Model parameters for Multi-GPU save - ema.ema.module.nc = model.nc # attach number of classes to model - ema.ema.module.hyp = model.hyp # attach hyperparameters to model - ema.ema.module.gr = model.gr = 1.0 # giou loss ratio (obj_loss = 1.0 or giou) - ema.ema.module.class_weights = model.class_weights # attach class weights - ema.ema.module.names = data_dict['names'] - with open(results_file, 'r') as f: # create checkpoint ckpt = {'epoch': epoch, 'best_fitness': best_fitness, From 13f69777a69c4a6056c7cb8499c7e3910868122d Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 2 Jul 2020 09:26:03 -0700 Subject: [PATCH 25/71] typo fix --- train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/train.py b/train.py index fa672d1..0cc3f31 100644 --- a/train.py +++ b/train.py @@ -119,7 +119,7 @@ def train(hyp): model.load_state_dict(ckpt['model'], strict=False) except KeyError as e: s = "%s is not compatible with %s. This may be due to model differences or %s may be out of date. " \ - "Please delete or update %s and try again, or use --weights '' to train from scatch." \ + "Please delete or update %s and try again, or use --weights '' to train from scratch." \ % (opt.weights, opt.cfg, opt.weights, opt.weights) raise KeyError(s) from e From a8d17ce7124e81907020d5d8e7737a22a46657f0 Mon Sep 17 00:00:00 2001 From: ChristopherSTAN <497592613@qq.com> Date: Thu, 2 Jul 2020 14:25:28 -0400 Subject: [PATCH 26/71] Update train.py --- train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/train.py b/train.py index 2dfe781..700cddd 100644 --- a/train.py +++ b/train.py @@ -37,7 +37,7 @@ hyp = {'lr0': 0.01, # initial learning rate (SGD=1E-2, Adam=1E-3) 'obj_pw': 1.0, # obj BCELoss positive_weight 'iou_t': 0.20, # iou training threshold 'anchor_t': 4.0, # anchor-multiple threshold - 'fl_gamma': 0.0, # focal loss gamma (efficientDet default is gamma=1.5) + 'fl_gamma': 1.5, # focal loss gamma (efficientDet default is gamma=1.5) 'hsv_h': 0.014, # image HSV-Hue augmentation (fraction) 'hsv_s': 0.68, # image HSV-Saturation augmentation (fraction) 'hsv_v': 0.36, # image HSV-Value augmentation (fraction) From 9705c15da1763c885990427139b1724758e4b8ce Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 2 Jul 2020 11:37:48 -0700 Subject: [PATCH 27/71] Update train.py --- train.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/train.py b/train.py index 700cddd..2dfe781 100644 --- a/train.py +++ b/train.py @@ -37,7 +37,7 @@ hyp = {'lr0': 0.01, # initial learning rate (SGD=1E-2, Adam=1E-3) 'obj_pw': 1.0, # obj BCELoss positive_weight 'iou_t': 0.20, # iou training threshold 'anchor_t': 4.0, # anchor-multiple threshold - 'fl_gamma': 1.5, # focal loss gamma (efficientDet default is gamma=1.5) + 'fl_gamma': 0.0, # focal loss gamma (efficientDet default is gamma=1.5) 'hsv_h': 0.014, # image HSV-Hue augmentation (fraction) 'hsv_s': 0.68, # image HSV-Saturation augmentation (fraction) 'hsv_v': 0.36, # image HSV-Value augmentation (fraction) From 597ed4ce630071cd5809c65c1322f5b961ba8c9c Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 2 Jul 2020 12:00:55 -0700 Subject: [PATCH 28/71] Update train.py --- train.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/train.py b/train.py index 3b7c9a5..dfc9ecd 100644 --- a/train.py +++ b/train.py @@ -79,7 +79,6 @@ def train(hyp): # Create model model = Model(opt.cfg).to(device) assert model.md['nc'] == nc, '%s nc=%g classes but %s nc=%g classes' % (opt.data, nc, opt.cfg, model.md['nc']) - # Image sizes gs = int(max(model.stride)) # grid size (max stride) @@ -133,7 +132,13 @@ def train(hyp): with open(results_file, 'w') as file: file.write(ckpt['training_results']) # write results.txt + # epochs start_epoch = ckpt['epoch'] + 1 + if epochs < start_epoch: + print('%s has been trained for %g epochs. Fine-tuning for %g additional epochs.' % + (opt.weights, ckpt['epoch'], epochs)) + epochs += ckpt['epoch'] # finetune additional epochs + del ckpt # Mixed precision training https://github.com/NVIDIA/apex @@ -147,6 +152,15 @@ def train(hyp): # https://discuss.pytorch.org/t/a-problem-occured-when-resuming-an-optimizer/28822 # plot_lr_scheduler(optimizer, scheduler, epochs) + # Initialize distributed training + if device.type != 'cpu' and torch.cuda.device_count() > 1 and torch.distributed.is_available(): + dist.init_process_group(backend='nccl', # distributed backend + init_method='tcp://127.0.0.1:9999', # init method + world_size=1, # number of nodes + rank=0) # node rank + model = torch.nn.parallel.DistributedDataParallel(model) + # pip install torch==1.4.0+cu100 torchvision==0.5.0+cu100 -f https://download.pytorch.org/whl/torch_stable.html + # Trainloader dataloader, dataset = create_dataloader(train_path, imgsz, batch_size, gs, opt, hyp=hyp, augment=True, cache=opt.cache_images, rect=opt.rect) @@ -155,7 +169,7 @@ def train(hyp): # Testloader testloader = create_dataloader(test_path, imgsz_test, batch_size, gs, opt, - hyp=hyp, augment=False, cache=opt.cache_images, rect=True)[0] + hyp=hyp, augment=False, cache=opt.cache_images, rect=True)[0] # Model parameters hyp['cls'] *= nc / 80. # scale coco-tuned hyp['cls'] to current dataset @@ -164,15 +178,6 @@ def train(hyp): model.gr = 1.0 # giou loss ratio (obj_loss = 1.0 or giou) model.class_weights = labels_to_class_weights(dataset.labels, nc).to(device) # attach class weights model.names = data_dict['names'] - - # Initialize distributed training - if device.type != 'cpu' and torch.cuda.device_count() > 1 and torch.distributed.is_available(): - dist.init_process_group(backend='nccl', # distributed backend - init_method='tcp://127.0.0.1:9999', # init method - world_size=1, # number of nodes - rank=0) # node rank - model = torch.nn.parallel.DistributedDataParallel(model) - # pip install torch==1.4.0+cu100 torchvision==0.5.0+cu100 -f https://download.pytorch.org/whl/torch_stable.html # Class frequency labels = np.concatenate(dataset.labels, 0) @@ -373,7 +378,7 @@ if __name__ == '__main__': parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%') parser.add_argument('--single-cls', action='store_true', help='train as single-class dataset') opt = parser.parse_args() - opt.weights = last if opt.resume else opt.weights + opt.weights = last if opt.resume and not opt.weights else opt.weights opt.cfg = check_file(opt.cfg) # check file opt.data = check_file(opt.data) # check file print(opt) From fc7c42723d8008438b217072dfa088612ac76225 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 2 Jul 2020 12:01:43 -0700 Subject: [PATCH 29/71] Update utils.py --- utils/utils.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/utils/utils.py b/utils/utils.py index 2205999..305486a 100755 --- a/utils/utils.py +++ b/utils/utils.py @@ -47,7 +47,7 @@ def check_git_status(): def check_img_size(img_size, s=32): # Verify img_size is a multiple of stride s - new_size = make_divisible(img_size, s) # ceil gs-multiple + new_size = make_divisible(img_size, int(s)) # ceil gs-multiple if new_size != img_size: print('WARNING: --img-size %g must be multiple of max stride %g, updating to %g' % (img_size, s, new_size)) return new_size @@ -421,9 +421,7 @@ def compute_loss(p, targets, model): # predictions, targets, model ft = torch.cuda.FloatTensor if p[0].is_cuda else torch.Tensor lcls, lbox, lobj = ft([0]), ft([0]), ft([0]) tcls, tbox, indices, anchors = build_targets(p, targets, model) # targets - h = model.module.hyp if hasattr(model, 'module') else model.hyp # hyperparameters - nc = model.module.nc if hasattr(model, 'module') else model.nc - gr = model.module.gr if hasattr(model, 'module') else model.gr + h = model.hyp # hyperparameters red = 'mean' # Loss reduction (sum or mean) # Define criteria @@ -457,10 +455,10 @@ def compute_loss(p, targets, model): # predictions, targets, model lbox += (1.0 - giou).sum() if red == 'sum' else (1.0 - giou).mean() # giou loss # Obj - tobj[b, a, gj, gi] = (1.0 - gr) + gr * giou.detach().clamp(0).type(tobj.dtype) # giou ratio + tobj[b, a, gj, gi] = (1.0 - model.gr) + model.gr * giou.detach().clamp(0).type(tobj.dtype) # giou ratio # Class - if nc > 1: # cls loss (only if multiple classes) + if model.nc > 1: # cls loss (only if multiple classes) t = torch.full_like(ps[:, 5:], cn) # targets t[range(nb), tcls[i]] = cp lcls += BCEcls(ps[:, 5:], t) # BCE @@ -479,7 +477,7 @@ def compute_loss(p, targets, model): # predictions, targets, model g = 3.0 # loss gain lobj *= g / bs if nt: - lcls *= g / nt / nc + lcls *= g / nt / model.nc lbox *= g / nt loss = lbox + lobj + lcls @@ -490,8 +488,6 @@ def build_targets(p, targets, model): # Build targets for compute_loss(), input targets(image,class,x,y,w,h) det = model.module.model[-1] if type(model) in (nn.parallel.DataParallel, nn.parallel.DistributedDataParallel) \ else model.model[-1] # Detect() module - hyp = model.module.hyp if hasattr(model, 'module') else model.hyp - na, nt = det.na, targets.shape[0] # number of anchors, targets tcls, tbox, indices, anch = [], [], [], [] gain = torch.ones(6, device=targets.device) # normalized to gridspace gain @@ -507,7 +503,7 @@ def build_targets(p, targets, model): a, t, offsets = [], targets * gain, 0 if nt: r = t[None, :, 4:6] / anchors[:, None] # wh ratio - j = torch.max(r, 1. / r).max(2)[0] < hyp['anchor_t'] # compare + j = torch.max(r, 1. / r).max(2)[0] < model.hyp['anchor_t'] # compare # j = wh_iou(anchors, t[:, 4:6]) > model.hyp['iou_t'] # iou(3,n) = wh_iou(anchors(3,2), gwh(n,2)) a, t = at[j], t.repeat(na, 1, 1)[j] # filter From f02481c73a4f8e3dbc0ae809b50310c0b2d700c9 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 2 Jul 2020 12:03:45 -0700 Subject: [PATCH 30/71] Update torch_utils.py --- utils/torch_utils.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/utils/torch_utils.py b/utils/torch_utils.py index e069792..a62adc9 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -54,6 +54,11 @@ def time_synchronized(): return time.time() +def is_parallel(model): + # is model is parallel with DP or DDP + return type(model) in (nn.parallel.DataParallel, nn.parallel.DistributedDataParallel) + + def initialize_weights(model): for m in model.modules(): t = type(m) @@ -111,8 +116,8 @@ def model_info(model, verbose=False): try: # FLOPS from thop import profile - macs, _ = profile(model, inputs=(torch.zeros(1, 3, 480, 640),), verbose=False) - fs = ', %.1f GFLOPS' % (macs / 1E9 * 2) + flops = profile(deepcopy(model), inputs=(torch.zeros(1, 3, 64, 64),), verbose=False)[0] / 1E9 * 2 + fs = ', %.1f GFLOPS' % (flops * 100) # 640x640 FLOPS except: fs = '' @@ -185,7 +190,7 @@ class ModelEMA: self.updates += 1 d = self.decay(self.updates) with torch.no_grad(): - if type(model) in (nn.parallel.DataParallel, nn.parallel.DistributedDataParallel): + if is_parallel(model): msd, esd = model.module.state_dict(), self.ema.module.state_dict() else: msd, esd = model.state_dict(), self.ema.state_dict() @@ -196,7 +201,8 @@ class ModelEMA: v += (1. - d) * msd[k].detach() def update_attr(self, model): - # Assign attributes (which may change during training) - for k in model.__dict__.keys(): - if not k.startswith('_'): - setattr(self.ema, k, getattr(model, k)) + # Update class attributes + ema = self.ema.module if is_parallel(model) else self.ema + for k, v in model.__dict__.items(): + if not k.startswith('_') and k != 'module': + setattr(ema, k, v) From 570dcf05a92512bf863158447631d2653466c59e Mon Sep 17 00:00:00 2001 From: ChristopherSTAN <497592613@qq.com> Date: Thu, 2 Jul 2020 17:04:54 -0400 Subject: [PATCH 31/71] Delete VOC2007.sh --- data/VOC2007.sh | 42 ------------------------------------------ 1 file changed, 42 deletions(-) delete mode 100644 data/VOC2007.sh diff --git a/data/VOC2007.sh b/data/VOC2007.sh deleted file mode 100644 index 69bad20..0000000 --- a/data/VOC2007.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash -# Ellis Brown - -start=`date +%s` - -# handle optional download dir -if [ -z "$1" ] - then - # navigate to ~/data - echo "navigating to ../data/ ..." - mkdir -p ./data - cd ./data/ - else - # check if is valid directory - if [ ! -d $1 ]; then - echo $1 "is not a valid directory" - exit 0 - fi - echo "navigating to" $1 "..." - cd $1 -fi - -echo "Downloading VOC2007 trainval ..." -# Download the data. -curl -LO http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtrainval_06-Nov-2007.tar -echo "Downloading VOC2007 test data ..." -curl -LO http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtest_06-Nov-2007.tar -echo "Done downloading." - -# Extract data -echo "Extracting trainval ..." -tar -xvf VOCtrainval_06-Nov-2007.tar -echo "Extracting test ..." -tar -xvf VOCtest_06-Nov-2007.tar -echo "removing tars ..." -rm VOCtrainval_06-Nov-2007.tar -rm VOCtest_06-Nov-2007.tar - -end=`date +%s` -runtime=$((end-start)) - -echo "Completed in" $runtime "seconds" \ No newline at end of file From 2bcf3da84835183e9e7796ed5400dd12754b1634 Mon Sep 17 00:00:00 2001 From: ChristopherSTAN <497592613@qq.com> Date: Thu, 2 Jul 2020 17:05:02 -0400 Subject: [PATCH 32/71] Delete VOC2012.sh --- data/VOC2012.sh | 38 -------------------------------------- 1 file changed, 38 deletions(-) delete mode 100644 data/VOC2012.sh diff --git a/data/VOC2012.sh b/data/VOC2012.sh deleted file mode 100644 index 945f8d7..0000000 --- a/data/VOC2012.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash -# Ellis Brown - -start=`date +%s` - -# handle optional download dir -if [ -z "$1" ] - then - # navigate to ~/data - echo "navigating to ~/data/ ..." - mkdir -p ./data - cd ./data/ - else - # check if is valid directory - if [ ! -d $1 ]; then - echo $1 "is not a valid directory" - exit 0 - fi - echo "navigating to" $1 "..." - cd $1 -fi - -echo "Downloading VOC2012 trainval ..." -# Download the data. -curl -LO http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar -echo "Done downloading." - - -# Extract data -echo "Extracting trainval ..." -tar -xvf VOCtrainval_11-May-2012.tar -echo "removing tar ..." -rm VOCtrainval_11-May-2012.tar - -end=`date +%s` -runtime=$((end-start)) - -echo "Completed in" $runtime "seconds" \ No newline at end of file From 389ed1f9d686a5638692fc0848460ace0e934ed8 Mon Sep 17 00:00:00 2001 From: ChristopherSTAN <497592613@qq.com> Date: Thu, 2 Jul 2020 17:05:09 -0400 Subject: [PATCH 33/71] Delete VOC_split.py --- data/VOC_split.py | 60 ----------------------------------------------- 1 file changed, 60 deletions(-) delete mode 100644 data/VOC_split.py diff --git a/data/VOC_split.py b/data/VOC_split.py deleted file mode 100644 index 69405a3..0000000 --- a/data/VOC_split.py +++ /dev/null @@ -1,60 +0,0 @@ - -# Run this file with folder VOCdevkit. -import xml.etree.ElementTree as ET -import pickle -import os -from os import listdir, getcwd -from os.path import join - -sets=[('2012', 'train'), ('2012', 'val'), ('2007', 'train'), ('2007', 'val'), ('2007', 'test')] - -classes = ["aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"] - - -def convert(size, box): - dw = 1./(size[0]) - dh = 1./(size[1]) - x = (box[0] + box[1])/2.0 - 1 - y = (box[2] + box[3])/2.0 - 1 - w = box[1] - box[0] - h = box[3] - box[2] - x = x*dw - w = w*dw - y = y*dh - h = h*dh - return (x,y,w,h) - -def convert_annotation(year, image_id): - in_file = open('VOCdevkit/VOC%s/Annotations/%s.xml'%(year, image_id)) - out_file = open('VOCdevkit/VOC%s/labels/%s.txt'%(year, image_id), 'w') - tree=ET.parse(in_file) - root = tree.getroot() - size = root.find('size') - w = int(size.find('width').text) - h = int(size.find('height').text) - - for obj in root.iter('object'): - difficult = obj.find('difficult').text - cls = obj.find('name').text - if cls not in classes or int(difficult)==1: - continue - cls_id = classes.index(cls) - xmlbox = obj.find('bndbox') - b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text), float(xmlbox.find('ymax').text)) - bb = convert((w,h), b) - out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n') - -wd = getcwd() - -for year, image_set in sets: - if not os.path.exists('VOCdevkit/VOC%s/labels/'%(year)): - os.makedirs('VOCdevkit/VOC%s/labels/'%(year)) - image_ids = open('VOCdevkit/VOC%s/ImageSets/Main/%s.txt'%(year, image_set)).read().strip().split() - list_file = open('%s_%s.txt'%(year, image_set), 'w') - for image_id in image_ids: - list_file.write('%s/VOCdevkit/VOC%s/JPEGImages/%s.jpg\n'%(wd, year, image_id)) - convert_annotation(year, image_id) - list_file.close() - -os.system("cat 2007_train.txt 2007_val.txt 2012_train.txt 2012_val.txt > train.txt") -os.system("cat 2007_train.txt 2007_val.txt 2007_test.txt 2012_train.txt 2012_val.txt > train.all.txt") \ No newline at end of file From 0bd619b5aaaae5102c5fde110276c700d400d31a Mon Sep 17 00:00:00 2001 From: ChristopherSTAN <497592613@qq.com> Date: Thu, 2 Jul 2020 17:05:25 -0400 Subject: [PATCH 34/71] Delete voc.yml --- data/voc.yml | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 data/voc.yml diff --git a/data/voc.yml b/data/voc.yml deleted file mode 100644 index d531fa8..0000000 --- a/data/voc.yml +++ /dev/null @@ -1,10 +0,0 @@ - -# train and val datasets (image directory or *.txt file with image paths) -train: ../VOC/images/train/ -val: ../VOC/images/val// - -# number of classes -nc: 20 - -# class names -names: ['aeroplane', 'bicycle','bird','boat','bottle','bus','car','cat','chair','cow','diningtable','dog','horse','motorbike','person','pottedplant','sheep','sofa','train','tvmonitor'] \ No newline at end of file From 4503d804490bdb1875c15807cf107fc1d3310401 Mon Sep 17 00:00:00 2001 From: ChristopherSTAN <497592613@qq.com> Date: Thu, 2 Jul 2020 17:05:33 -0400 Subject: [PATCH 35/71] Delete organize_VOC.py --- data/organize_VOC.py | 47 -------------------------------------------- 1 file changed, 47 deletions(-) delete mode 100644 data/organize_VOC.py diff --git a/data/organize_VOC.py b/data/organize_VOC.py deleted file mode 100644 index d4ccf3a..0000000 --- a/data/organize_VOC.py +++ /dev/null @@ -1,47 +0,0 @@ -print(os.path.exists('../data/train.txt')) -f = open('../data/train.txt', 'r') -lines = f.readlines() - -for line in lines: - #print(line.split('/')[-1][:-1]) - line = "/".join(line.split('/')[2:]) - - if (os.path.exists(line[:-1])): - os.system("cp "+ line[:-1] + " VOC/images/train") - -print(os.path.exists('../data/train.txt')) -f = open('../data/train.txt', 'r') -lines = f.readlines() - -for line in lines: - #print(line.split('/')[-1][:-1]) - line = "/".join(line.split('/')[2:]) - line = line.replace('JPEGImages', 'labels') - line = line.replace('jpg', 'txt') - #print(line) - if (os.path.exists(line[:-1])): - os.system("cp "+ line[:-1] + " VOC/labels/train") - -print(os.path.exists('../data/2007_test.txt')) -f = open('../data/2007_test.txt', 'r') -lines = f.readlines() - -for line in lines: - #print(line.split('/')[-1][:-1]) - line = "/".join(line.split('/')[2:]) - - if (os.path.exists(line[:-1])): - os.system("cp "+ line[:-1] + " VOC/images/val") - -print(os.path.exists('../data/2007_test.txt')) -f = open('../data/2007_test.txt', 'r') -lines = f.readlines() - -for line in lines: - #print(line.split('/')[-1][:-1]) - line = "/".join(line.split('/')[2:]) - line = line.replace('JPEGImages', 'labels') - line = line.replace('jpg', 'txt') - #print(line) - if (os.path.exists(line[:-1])): - os.system("cp "+ line[:-1] + " VOC/labels/val") \ No newline at end of file From 8c3b829214fb8cdae96c61091c0636c497be76bb Mon Sep 17 00:00:00 2001 From: ChristopherSTAN <497592613@qq.com> Date: Thu, 2 Jul 2020 17:05:57 -0400 Subject: [PATCH 36/71] Add files via upload --- data/get_voc.sh | 206 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 data/get_voc.sh diff --git a/data/get_voc.sh b/data/get_voc.sh new file mode 100644 index 0000000..f035bb4 --- /dev/null +++ b/data/get_voc.sh @@ -0,0 +1,206 @@ + +start=`date +%s` + +# handle optional download dir +if [ -z "$1" ] + then + # navigate to ~/data + echo "navigating to ../data/ ..." + mkdir -p ../data + cd ../data/ + else + # check if is valid directory + if [ ! -d $1 ]; then + echo $1 "is not a valid directory" + exit 0 + fi + echo "navigating to" $1 "..." + cd $1 +fi + +echo "Downloading VOC2007 trainval ..." +# Download the data. +curl -LO http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtrainval_06-Nov-2007.tar +echo "Downloading VOC2007 test data ..." +curl -LO http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtest_06-Nov-2007.tar +echo "Done downloading." + +# Extract data +echo "Extracting trainval ..." +tar -xvf VOCtrainval_06-Nov-2007.tar +echo "Extracting test ..." +tar -xvf VOCtest_06-Nov-2007.tar +echo "removing tars ..." +rm VOCtrainval_06-Nov-2007.tar +rm VOCtest_06-Nov-2007.tar + +end=`date +%s` +runtime=$((end-start)) + +echo "Completed in" $runtime "seconds" + +start=`date +%s` + +# handle optional download dir +if [ -z "$1" ] + then + # navigate to ~/data + echo "navigating to ../data/ ..." + mkdir -p ../data + cd ../data/ + else + # check if is valid directory + if [ ! -d $1 ]; then + echo $1 "is not a valid directory" + exit 0 + fi + echo "navigating to" $1 "..." + cd $1 +fi + +echo "Downloading VOC2012 trainval ..." +# Download the data. +curl -LO http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar +echo "Done downloading." + + +# Extract data +echo "Extracting trainval ..." +tar -xvf VOCtrainval_11-May-2012.tar +echo "removing tar ..." +rm VOCtrainval_11-May-2012.tar + +end=`date +%s` +runtime=$((end-start)) + +echo "Completed in" $runtime "seconds" + +cd ../data +echo "Spliting dataset..." +python3 - "$@" < train.txt +cat 2007_train.txt 2007_val.txt 2007_test.txt 2012_train.txt 2012_val.txt > train.all.txt + +python3 - "$@" < Date: Thu, 2 Jul 2020 17:06:59 -0400 Subject: [PATCH 37/71] Create voc.yaml --- data/voc.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 data/voc.yaml diff --git a/data/voc.yaml b/data/voc.yaml new file mode 100644 index 0000000..e2641d6 --- /dev/null +++ b/data/voc.yaml @@ -0,0 +1,10 @@ + +# train and val datasets (image directory or *.txt file with image paths) +train: ../VOC/images/train/ +val: ../VOC/images/val// + +# number of classes +nc: 20 + +# class names +names: ['aeroplane', 'bicycle','bird','boat','bottle','bus','car','cat','chair','cow','diningtable','dog','horse','motorbike','person','pottedplant','sheep','sofa','train','tvmonitor'] From 6ca3f35cd4e6834adc116e2d1ebe2defa082c7e8 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 2 Jul 2020 16:41:03 -0700 Subject: [PATCH 38/71] update .dockerignore --- .dockerignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.dockerignore b/.dockerignore index a68626d..42f241f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -14,8 +14,10 @@ data/samples/* # Neural Network weights ----------------------------------------------------------------------------------------------- **/*.weights **/*.pt +**/*.pth **/*.onnx **/*.mlmodel +**/*.torchscript # Below Copied From .gitignore ----------------------------------------------------------------------------------------- From 3bdea3f697d4fce36c8e24a0701c0f419fa8f63a Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Thu, 2 Jul 2020 21:24:26 -0700 Subject: [PATCH 39/71] strip_optimizer() bug fix #253 --- train.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/train.py b/train.py index aabf4f1..08a0fe5 100644 --- a/train.py +++ b/train.py @@ -336,17 +336,17 @@ def train(hyp): # end epoch ---------------------------------------------------------------------------------------------------- # end training - n = opt.name - if len(n): - n = '_' + n if not n.isnumeric() else n - fresults, flast, fbest = 'results%s.txt' % n, wdir + 'last%s.pt' % n, wdir + 'best%s.pt' % n - for f1, f2 in zip([wdir + 'last.pt', wdir + 'best.pt', 'results.txt'], [flast, fbest, fresults]): - if os.path.exists(f1): - os.rename(f1, f2) # rename - ispt = f2.endswith('.pt') # is *.pt - strip_optimizer(f2) if ispt else None # strip optimizer - os.system('gsutil cp %s gs://%s/weights' % (f2, opt.bucket)) if opt.bucket and ispt else None # upload - + # Strip optimizers + n = ('_' if len(opt.name) and not opt.name.isnumeric() else '') + opt.name + fresults, flast, fbest = 'results%s.txt' % n, wdir + 'last%s.pt' % n, wdir + 'best%s.pt' % n + for f1, f2 in zip([wdir + 'last.pt', wdir + 'best.pt', 'results.txt'], [flast, fbest, fresults]): + if os.path.exists(f1): + os.rename(f1, f2) # rename + ispt = f2.endswith('.pt') # is *.pt + strip_optimizer(f2) if ispt else None # strip optimizer + os.system('gsutil cp %s gs://%s/weights' % (f2, opt.bucket)) if opt.bucket and ispt else None # upload + + # Finish if not opt.evolve: plot_results() # save as results.png print('%g epochs completed in %.3f hours.\n' % (epoch - start_epoch + 1, (time.time() - t0) / 3600)) From 569fbc63c8d41ca9810ca8f3c5d9ed44cebd9323 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 3 Jul 2020 11:29:53 -0700 Subject: [PATCH 40/71] export.py update --- models/export.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/models/export.py b/models/export.py index bb310f3..42fd577 100644 --- a/models/export.py +++ b/models/export.py @@ -30,6 +30,7 @@ if __name__ == '__main__': # TorchScript export try: + print('\nStarting TorchScript export with torch %s...' % torch.__version) f = opt.weights.replace('.pt', '.torchscript') # filename ts = torch.jit.trace(model, img) ts.save(f) @@ -41,6 +42,7 @@ if __name__ == '__main__': try: import onnx + print('\nStarting ONNX export with onnx %s...' % onnx.__version__) f = opt.weights.replace('.pt', '.onnx') # filename model.fuse() # only for ONNX torch.onnx.export(model, img, f, verbose=False, opset_version=12, input_names=['images'], @@ -49,7 +51,7 @@ if __name__ == '__main__': # Checks onnx_model = onnx.load(f) # load onnx model onnx.checker.check_model(onnx_model) # check onnx model - print(onnx.helper.printable_graph(onnx_model.graph)) # print a human readable representation of the graph + print(onnx.helper.printable_graph(onnx_model.graph)) # print a human readable model print('ONNX export success, saved as %s\nView with https://github.com/lutzroeder/netron' % f) except Exception as e: print('ONNX export failed: %s' % e) From a9d20eba3e61ac5b7e77e0a7c07828fb2cf84bb5 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 3 Jul 2020 11:50:59 -0700 Subject: [PATCH 41/71] export.py update --- models/export.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/models/export.py b/models/export.py index 42fd577..1ff9526 100644 --- a/models/export.py +++ b/models/export.py @@ -30,7 +30,7 @@ if __name__ == '__main__': # TorchScript export try: - print('\nStarting TorchScript export with torch %s...' % torch.__version) + print('\nStarting TorchScript export with torch %s...' % torch.__version__) f = opt.weights.replace('.pt', '.torchscript') # filename ts = torch.jit.trace(model, img) ts.save(f) @@ -52,6 +52,9 @@ if __name__ == '__main__': onnx_model = onnx.load(f) # load onnx model onnx.checker.check_model(onnx_model) # check onnx model print(onnx.helper.printable_graph(onnx_model.graph)) # print a human readable model - print('ONNX export success, saved as %s\nView with https://github.com/lutzroeder/netron' % f) + print('ONNX export success, saved as %s' % f) except Exception as e: print('ONNX export failed: %s' % e) + + # Finish + print('\nExports complete. Visualize with https://github.com/lutzroeder/netron.') \ No newline at end of file From df224a0d8fb115c2cedc369cd03f506efc25bbdd Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 3 Jul 2020 11:56:14 -0700 Subject: [PATCH 42/71] EMA bug fix #279 --- train.py | 4 ++-- utils/torch_utils.py | 21 +++++++++------------ 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/train.py b/train.py index 08a0fe5..1c8ff7d 100644 --- a/train.py +++ b/train.py @@ -294,7 +294,7 @@ def train(hyp): batch_size=batch_size, imgsz=imgsz_test, save_json=final_epoch and opt.data.endswith(os.sep + 'coco.yaml'), - model=ema.ema.module if hasattr(model, 'module') else ema.ema, + model=ema.ema, single_cls=opt.single_cls, dataloader=testloader) @@ -324,7 +324,7 @@ def train(hyp): ckpt = {'epoch': epoch, 'best_fitness': best_fitness, 'training_results': f.read(), - 'model': ema.ema.module if hasattr(model, 'module') else ema.ema, + 'model': ema.ema, 'optimizer': None if final_epoch else optimizer.state_dict()} # Save last, best and delete diff --git a/utils/torch_utils.py b/utils/torch_utils.py index a62adc9..3cbec8b 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -175,8 +175,8 @@ class ModelEMA: """ def __init__(self, model, decay=0.9999, device=''): - # make a copy of the model for accumulating moving average of weights - self.ema = deepcopy(model) + # Create EMA + self.ema = deepcopy(model.module if is_parallel(model) else model).half() # FP16 EMA self.ema.eval() self.updates = 0 # number of EMA updates self.decay = lambda x: decay * (1 - math.exp(-x / 2000)) # decay exponential ramp (to help early epochs) @@ -187,22 +187,19 @@ class ModelEMA: p.requires_grad_(False) def update(self, model): - self.updates += 1 - d = self.decay(self.updates) + # Update EMA parameters with torch.no_grad(): - if is_parallel(model): - msd, esd = model.module.state_dict(), self.ema.module.state_dict() - else: - msd, esd = model.state_dict(), self.ema.state_dict() + self.updates += 1 + d = self.decay(self.updates) - for k, v in esd.items(): + msd = model.module.state_dict() if is_parallel(model) else model.state_dict() # model state_dict + for k, v in self.ema.state_dict().items(): if v.dtype.is_floating_point: v *= d v += (1. - d) * msd[k].detach() def update_attr(self, model): - # Update class attributes - ema = self.ema.module if is_parallel(model) else self.ema + # Update EMA attributes for k, v in model.__dict__.items(): if not k.startswith('_') and k != 'module': - setattr(ema, k, v) + setattr(self.ema, k, v) From 655895a8382fcafd0e346794f401c081f57f2dc4 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 3 Jul 2020 13:46:12 -0700 Subject: [PATCH 43/71] module updates --- models/experimental.py | 22 ++++++---------------- models/yolo.py | 2 +- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/models/experimental.py b/models/experimental.py index cff9d14..539e7f9 100644 --- a/models/experimental.py +++ b/models/experimental.py @@ -4,12 +4,13 @@ from models.common import * class CrossConv(nn.Module): - # Cross Convolution - def __init__(self, c1, c2, shortcut=True, g=1, e=0.5): # ch_in, ch_out, shortcut, groups, expansion + # Cross Convolution Downsample + def __init__(self, c1, c2, k=3, s=1, g=1, e=1.0, shortcut=False): + # ch_in, ch_out, kernel, stride, groups, expansion, shortcut super(CrossConv, self).__init__() c_ = int(c2 * e) # hidden channels - self.cv1 = Conv(c1, c_, (1, 3), 1) - self.cv2 = Conv(c_, c2, (3, 1), 1, g=g) + self.cv1 = Conv(c1, c_, (1, k), (1, s)) + self.cv2 = Conv(c_, c2, (k, 1), (s, 1), g=g) self.add = shortcut and c1 == c2 def forward(self, x): @@ -27,7 +28,7 @@ class C3(nn.Module): self.cv4 = Conv(2 * c_, c2, 1, 1) self.bn = nn.BatchNorm2d(2 * c_) # applied to cat(cv2, cv3) self.act = nn.LeakyReLU(0.1, inplace=True) - self.m = nn.Sequential(*[CrossConv(c_, c_, shortcut, g, e=1.0) for _ in range(n)]) + self.m = nn.Sequential(*[CrossConv(c_, c_, 3, 1, g, 1.0, shortcut) for _ in range(n)]) def forward(self, x): y1 = self.cv3(self.m(self.cv1(x))) @@ -84,17 +85,6 @@ class GhostBottleneck(nn.Module): return self.conv(x) + self.shortcut(x) -class ConvPlus(nn.Module): - # Plus-shaped convolution - def __init__(self, c1, c2, k=3, s=1, g=1, bias=True): # ch_in, ch_out, kernel, stride, groups - super(ConvPlus, self).__init__() - self.cv1 = nn.Conv2d(c1, c2, (k, 1), s, (k // 2, 0), groups=g, bias=bias) - self.cv2 = nn.Conv2d(c1, c2, (1, k), s, (0, k // 2), groups=g, bias=bias) - - def forward(self, x): - return self.cv1(x) + self.cv2(x) - - class MixConv2d(nn.Module): # Mixed Depthwise Conv https://arxiv.org/abs/1907.09595 def __init__(self, c1, c2, k=(1, 3), s=1, equal_ch=True): diff --git a/models/yolo.py b/models/yolo.py index c9e6c49..7cc86b2 100644 --- a/models/yolo.py +++ b/models/yolo.py @@ -161,7 +161,7 @@ def parse_model(md, ch): # model_dict, input_channels(3) pass n = max(round(n * gd), 1) if n > 1 else n # depth gain - if m in [nn.Conv2d, Conv, Bottleneck, SPP, DWConv, MixConv2d, Focus, ConvPlus, BottleneckCSP]: + if m in [nn.Conv2d, Conv, Bottleneck, SPP, DWConv, MixConv2d, Focus, BottleneckCSP, CrossConv]: c1, c2 = ch[f], args[0] # Normal From f767023c56b9a8154c8fbf5b702dc54eb6eaef7f Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 3 Jul 2020 14:07:00 -0700 Subject: [PATCH 44/71] offset and balance update --- utils/utils.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/utils/utils.py b/utils/utils.py index 305486a..d9ffaac 100755 --- a/utils/utils.py +++ b/utils/utils.py @@ -438,6 +438,7 @@ def compute_loss(p, targets, model): # predictions, targets, model # per output nt = 0 # targets + balance = [1.0, 1.0, 1.0] for i, pi in enumerate(p): # layer index, layer predictions b, a, gj, gi = indices[i] # image, anchor, gridy, gridx tobj = torch.zeros_like(pi[..., 0]) # target obj @@ -467,11 +468,12 @@ def compute_loss(p, targets, model): # predictions, targets, model # with open('targets.txt', 'a') as file: # [file.write('%11.5g ' * 4 % tuple(x) + '\n') for x in torch.cat((txy[i], twh[i]), 1)] - lobj += BCEobj(pi[..., 4], tobj) # obj loss + lobj += BCEobj(pi[..., 4], tobj) * balance[i] # obj loss - lbox *= h['giou'] - lobj *= h['obj'] - lcls *= h['cls'] + s = 3 / (i + 1) # output count scaling + lbox *= h['giou'] * s + lobj *= h['obj'] * s + lcls *= h['cls'] * s bs = tobj.shape[0] # batch size if red == 'sum': g = 3.0 # loss gain @@ -508,16 +510,15 @@ def build_targets(p, targets, model): a, t = at[j], t.repeat(na, 1, 1)[j] # filter # overlaps + g = 0.5 # offset gxy = t[:, 2:4] # grid xy z = torch.zeros_like(gxy) if style == 'rect2': - g = 0.2 # offset j, k = ((gxy % 1. < g) & (gxy > 1.)).T a, t = torch.cat((a, a[j], a[k]), 0), torch.cat((t, t[j], t[k]), 0) offsets = torch.cat((z, z[j] + off[0], z[k] + off[1]), 0) * g elif style == 'rect4': - g = 0.5 # offset j, k = ((gxy % 1. < g) & (gxy > 1.)).T l, m = ((gxy % 1. > (1 - g)) & (gxy < (gain[[2, 3]] - 1.))).T a, t = torch.cat((a, a[j], a[k], a[l], a[m]), 0), torch.cat((t, t[j], t[k], t[l], t[m]), 0) @@ -764,11 +765,11 @@ def kmean_anchors(path='./data/coco128.yaml', n=9, img_size=640, thr=4.0, gen=10 wh0 = np.concatenate([l[:, 3:5] * s for s, l in zip(shapes, dataset.labels)]) # wh # Filter - i = (wh0 < 4.0).any(1).sum() + i = (wh0 < 3.0).any(1).sum() if i: print('WARNING: Extremely small objects found. ' - '%g of %g labels are < 4 pixels in width or height.' % (i, len(wh0))) - wh = wh0[(wh0 >= 4.0).any(1)] # filter > 2 pixels + '%g of %g labels are < 3 pixels in width or height.' % (i, len(wh0))) + wh = wh0[(wh0 >= 2.0).any(1)] # filter > 2 pixels # Kmeans calculation from scipy.cluster.vq import kmeans From af41083d52992d86da7cc74734b4cf14bdb56e04 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 3 Jul 2020 16:57:08 -0700 Subject: [PATCH 45/71] EMA FP16 fix #279 --- utils/torch_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/torch_utils.py b/utils/torch_utils.py index 3cbec8b..dd2e6e7 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -176,13 +176,13 @@ class ModelEMA: def __init__(self, model, decay=0.9999, device=''): # Create EMA - self.ema = deepcopy(model.module if is_parallel(model) else model).half() # FP16 EMA + self.ema = deepcopy(model.module if is_parallel(model) else model) # FP32 EMA self.ema.eval() self.updates = 0 # number of EMA updates self.decay = lambda x: decay * (1 - math.exp(-x / 2000)) # decay exponential ramp (to help early epochs) self.device = device # perform ema on different device from model if set if device: - self.ema.to(device=device) + self.ema.to(device) for p in self.ema.parameters(): p.requires_grad_(False) From ce0c58f6781609ad11d99481999c9b9aa87cb91b Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 3 Jul 2020 18:56:07 -0700 Subject: [PATCH 46/71] update compute_loss() --- utils/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/utils.py b/utils/utils.py index d9ffaac..85253a6 100755 --- a/utils/utils.py +++ b/utils/utils.py @@ -437,7 +437,8 @@ def compute_loss(p, targets, model): # predictions, targets, model BCEcls, BCEobj = FocalLoss(BCEcls, g), FocalLoss(BCEobj, g) # per output - nt = 0 # targets + nt = 0 # number of targets + np = len(p) # number of outputs balance = [1.0, 1.0, 1.0] for i, pi in enumerate(p): # layer index, layer predictions b, a, gj, gi = indices[i] # image, anchor, gridy, gridx @@ -470,7 +471,7 @@ def compute_loss(p, targets, model): # predictions, targets, model lobj += BCEobj(pi[..., 4], tobj) * balance[i] # obj loss - s = 3 / (i + 1) # output count scaling + s = 3 / np # output count scaling lbox *= h['giou'] * s lobj *= h['obj'] * s lcls *= h['cls'] * s @@ -517,7 +518,6 @@ def build_targets(p, targets, model): j, k = ((gxy % 1. < g) & (gxy > 1.)).T a, t = torch.cat((a, a[j], a[k]), 0), torch.cat((t, t[j], t[k]), 0) offsets = torch.cat((z, z[j] + off[0], z[k] + off[1]), 0) * g - elif style == 'rect4': j, k = ((gxy % 1. < g) & (gxy > 1.)).T l, m = ((gxy % 1. > (1 - g)) & (gxy < (gain[[2, 3]] - 1.))).T From 7bd7b2c090cbeb7d5ff4e678d093c258d34368ee Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Fri, 3 Jul 2020 20:05:50 -0700 Subject: [PATCH 47/71] update export.py --- models/export.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/models/export.py b/models/export.py index 1ff9526..54d7a12 100644 --- a/models/export.py +++ b/models/export.py @@ -26,7 +26,7 @@ if __name__ == '__main__': model = torch.load(opt.weights, map_location=torch.device('cpu'))['model'].float() model.eval() model.model[-1].export = True # set Detect() layer export=True - _ = model(img) # dry run + y = model(img) # dry run # TorchScript export try: @@ -36,7 +36,7 @@ if __name__ == '__main__': ts.save(f) print('TorchScript export success, saved as %s' % f) except Exception as e: - print('TorchScript export failed: %s' % e) + print('TorchScript export failure: %s' % e) # ONNX export try: @@ -46,7 +46,7 @@ if __name__ == '__main__': f = opt.weights.replace('.pt', '.onnx') # filename model.fuse() # only for ONNX torch.onnx.export(model, img, f, verbose=False, opset_version=12, input_names=['images'], - output_names=['output']) # output_names=['classes', 'boxes'] + output_names=['classes', 'boxes'] if y is None else ['output']) # Checks onnx_model = onnx.load(f) # load onnx model @@ -54,7 +54,7 @@ if __name__ == '__main__': print(onnx.helper.printable_graph(onnx_model.graph)) # print a human readable model print('ONNX export success, saved as %s' % f) except Exception as e: - print('ONNX export failed: %s' % e) + print('ONNX export failure: %s' % e) # Finish - print('\nExports complete. Visualize with https://github.com/lutzroeder/netron.') \ No newline at end of file + print('\nExport complete. Visualize with https://github.com/lutzroeder/netron.') From f83521ab9a63b7728cd562194afa7d9caf66971f Mon Sep 17 00:00:00 2001 From: NanoCode012 Date: Sat, 4 Jul 2020 16:50:33 +0700 Subject: [PATCH 48/71] Fix save error for multi-gpu --- utils/torch_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/torch_utils.py b/utils/torch_utils.py index dd2e6e7..d697a06 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -201,5 +201,5 @@ class ModelEMA: def update_attr(self, model): # Update EMA attributes for k, v in model.__dict__.items(): - if not k.startswith('_') and k != 'module': + if not k.startswith('_') and k not in ["module", "process_group", "reducer"]: setattr(self.ema, k, v) From ccfa9373ae784d9bcda19d47973af9b9c15d7906 Mon Sep 17 00:00:00 2001 From: NanoCode012 Date: Sat, 4 Jul 2020 23:12:57 +0700 Subject: [PATCH 49/71] Update to remove "module" attribute check --- utils/torch_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/torch_utils.py b/utils/torch_utils.py index d697a06..fd00b8b 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -201,5 +201,5 @@ class ModelEMA: def update_attr(self, model): # Update EMA attributes for k, v in model.__dict__.items(): - if not k.startswith('_') and k not in ["module", "process_group", "reducer"]: + if not k.startswith('_') and k not in ["process_group", "reducer"]: setattr(self.ema, k, v) From c0c5fd812188f9561965fe2313ae06ab0246de84 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 4 Jul 2020 16:11:14 -0700 Subject: [PATCH 50/71] Update voc.yaml --- data/voc.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/data/voc.yaml b/data/voc.yaml index e2641d6..9275cf2 100644 --- a/data/voc.yaml +++ b/data/voc.yaml @@ -1,7 +1,14 @@ +# PASCAL VOC dataset http://host.robots.ox.ac.uk/pascal/VOC/ +# Download command: bash yolov5/data/get_voc.sh +# Train command: python train.py --data voc.yaml +# Dataset should be placed next to yolov5 folder: +# /parent_folder +# /VOC +# /yolov5 # train and val datasets (image directory or *.txt file with image paths) train: ../VOC/images/train/ -val: ../VOC/images/val// +val: ../VOC/images/val/ # number of classes nc: 20 From 7df4fcebb1e32d0fdb973d61bca32814fa254769 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 4 Jul 2020 16:12:17 -0700 Subject: [PATCH 51/71] Update voc.yaml --- data/voc.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/voc.yaml b/data/voc.yaml index 9275cf2..ba4eff6 100644 --- a/data/voc.yaml +++ b/data/voc.yaml @@ -14,4 +14,5 @@ val: ../VOC/images/val/ nc: 20 # class names -names: ['aeroplane', 'bicycle','bird','boat','bottle','bus','car','cat','chair','cow','diningtable','dog','horse','motorbike','person','pottedplant','sheep','sofa','train','tvmonitor'] +names: ['aeroplane', 'bicycle','bird','boat','bottle','bus','car','cat','chair','cow','diningtable','dog','horse', + 'motorbike','person','pottedplant','sheep','sofa','train','tvmonitor'] From 72a1746938710a8548c54b2c034b4cb582dcc28c Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 4 Jul 2020 16:12:52 -0700 Subject: [PATCH 52/71] Update get_voc.sh --- data/get_voc.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data/get_voc.sh b/data/get_voc.sh index f035bb4..f1ad410 100644 --- a/data/get_voc.sh +++ b/data/get_voc.sh @@ -1,4 +1,3 @@ - start=`date +%s` # handle optional download dir @@ -203,4 +202,4 @@ for line in lines: END -rm -rf ../data \ No newline at end of file +rm -rf ../data From 55e48603bf917968a72e5aa65f832517d4f06e81 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 4 Jul 2020 16:25:48 -0700 Subject: [PATCH 53/71] udpate dataset download instructions --- data/get_coco2017.sh | 10 +++++++--- data/get_voc.sh | 8 ++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/data/get_coco2017.sh b/data/get_coco2017.sh index fed5747..03b2c7e 100755 --- a/data/get_coco2017.sh +++ b/data/get_coco2017.sh @@ -1,7 +1,11 @@ #!/bin/bash -# Zip coco folder -# zip -r coco.zip coco -# tar -czvf coco.tar.gz coco +# COCO 2017 dataset http://cocodataset.org +# Download command: bash yolov5/data/get_coco2017.sh +# Train command: python train.py --data ./data/coco.yaml +# Dataset should be placed next to yolov5 folder: +# /parent_folder +# /coco +# /yolov5 # Download labels from Google Drive, accepting presented query filename="coco2017labels.zip" diff --git a/data/get_voc.sh b/data/get_voc.sh index f1ad410..61edbac 100644 --- a/data/get_voc.sh +++ b/data/get_voc.sh @@ -1,3 +1,11 @@ +# PASCAL VOC dataset http://host.robots.ox.ac.uk/pascal/VOC/ +# Download command: bash yolov5/data/get_voc.sh +# Train command: python train.py --data voc.yaml +# Dataset should be placed next to yolov5 folder: +# /parent_folder +# /VOC +# /yolov5 + start=`date +%s` # handle optional download dir From 45abf0c74f52566003169b86012837c6f386dfa7 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 4 Jul 2020 16:42:12 -0700 Subject: [PATCH 54/71] remove verbose tar unzip --- data/get_voc.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/get_voc.sh b/data/get_voc.sh index 61edbac..166370f 100644 --- a/data/get_voc.sh +++ b/data/get_voc.sh @@ -34,9 +34,9 @@ echo "Done downloading." # Extract data echo "Extracting trainval ..." -tar -xvf VOCtrainval_06-Nov-2007.tar +tar -xf VOCtrainval_06-Nov-2007.tar echo "Extracting test ..." -tar -xvf VOCtest_06-Nov-2007.tar +tar -xf VOCtest_06-Nov-2007.tar echo "removing tars ..." rm VOCtrainval_06-Nov-2007.tar rm VOCtest_06-Nov-2007.tar @@ -73,7 +73,7 @@ echo "Done downloading." # Extract data echo "Extracting trainval ..." -tar -xvf VOCtrainval_11-May-2012.tar +tar -xf VOCtrainval_11-May-2012.tar echo "removing tar ..." rm VOCtrainval_11-May-2012.tar From fd0b1cc924e0ce47db7ca51e4be229dc1e3c0220 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 4 Jul 2020 16:50:11 -0700 Subject: [PATCH 55/71] update voc.yaml --- data/voc.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/voc.yaml b/data/voc.yaml index ba4eff6..efe2230 100644 --- a/data/voc.yaml +++ b/data/voc.yaml @@ -1,5 +1,5 @@ # PASCAL VOC dataset http://host.robots.ox.ac.uk/pascal/VOC/ -# Download command: bash yolov5/data/get_voc.sh +# Download command: bash ./data/get_voc.sh # Train command: python train.py --data voc.yaml # Dataset should be placed next to yolov5 folder: # /parent_folder @@ -14,5 +14,5 @@ val: ../VOC/images/val/ nc: 20 # class names -names: ['aeroplane', 'bicycle','bird','boat','bottle','bus','car','cat','chair','cow','diningtable','dog','horse', - 'motorbike','person','pottedplant','sheep','sofa','train','tvmonitor'] +names: ['aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', 'cat', 'chair', 'cow', 'diningtable', 'dog', + 'horse', 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor'] From 659ad748c810bf0d6f5b5178e6aa1048780683ba Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 4 Jul 2020 17:13:43 -0700 Subject: [PATCH 56/71] update get_voc.sh --- Dockerfile | 2 +- data/get_voc.sh | 2 +- detect.py | 6 +++--- models/export.py | 12 ++++++++++++ 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 01551a0..bda78c0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Start FROM Nvidia PyTorch image https://ngc.nvidia.com/catalog/containers/nvidia:pytorch -FROM nvcr.io/nvidia/pytorch:20.06-py3 +FROM nvcr.io/nvidia/pytorch:20.03-py3 RUN pip install -U gsutil # Create working directory diff --git a/data/get_voc.sh b/data/get_voc.sh index 166370f..949e0cc 100644 --- a/data/get_voc.sh +++ b/data/get_voc.sh @@ -1,5 +1,5 @@ # PASCAL VOC dataset http://host.robots.ox.ac.uk/pascal/VOC/ -# Download command: bash yolov5/data/get_voc.sh +# Download command: bash ./data/get_voc.sh # Train command: python train.py --data voc.yaml # Dataset should be placed next to yolov5 folder: # /parent_folder diff --git a/detect.py b/detect.py index 2650c20..f9b12eb 100644 --- a/detect.py +++ b/detect.py @@ -102,7 +102,7 @@ def detect(save_img=False): if save_img or view_img: # Add bbox to image label = '%s %.2f' % (names[int(cls)], conf) - plot_one_box(xyxy, im0, label=label, color=colors[int(cls)], line_thickness=3) + plot_one_box(xyxy, im0, label=label, color=colors[int(cls)], line_thickness=1) # Print time (inference + NMS) print('%sDone. (%.3fs)' % (s, t2 - t1)) @@ -139,10 +139,10 @@ def detect(save_img=False): if __name__ == '__main__': parser = argparse.ArgumentParser() - parser.add_argument('--weights', type=str, default='weights/yolov5s.pt', help='model.pt path') + parser.add_argument('--weights', type=str, default='weights/yolov5m.pt', help='model.pt path') parser.add_argument('--source', type=str, default='inference/images', help='source') # file/folder, 0 for webcam parser.add_argument('--output', type=str, default='inference/output', help='output folder') # output folder - parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)') + parser.add_argument('--img-size', type=int, default=1024, help='inference size (pixels)') parser.add_argument('--conf-thres', type=float, default=0.4, help='object confidence threshold') parser.add_argument('--iou-thres', type=float, default=0.5, help='IOU threshold for NMS') parser.add_argument('--fourcc', type=str, default='mp4v', help='output video codec (verify ffmpeg support)') diff --git a/models/export.py b/models/export.py index 54d7a12..c11c0a3 100644 --- a/models/export.py +++ b/models/export.py @@ -56,5 +56,17 @@ if __name__ == '__main__': except Exception as e: print('ONNX export failure: %s' % e) + # CoreML export + try: + import coremltools as ct + + print('\nStarting CoreML export with coremltools %s...' % ct.__version__) + model = ct.convert(ts, inputs=[ct.ImageType(name='images', shape=img.shape)]) # convert + f = opt.weights.replace('.pt', '.mlmodel') # filename + model.save(f) + print('CoreML export success, saved as %s' % f) + except Exception as e: + print('CoreML export failure: %s' % e) + # Finish print('\nExport complete. Visualize with https://github.com/lutzroeder/netron.') From 11ba5294ddb9423654c36ac056673b984d9a59dc Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 4 Jul 2020 17:36:53 -0700 Subject: [PATCH 57/71] update get_voc.sh --- data/get_voc.sh | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/data/get_voc.sh b/data/get_voc.sh index 949e0cc..b7e66d0 100644 --- a/data/get_voc.sh +++ b/data/get_voc.sh @@ -11,10 +11,10 @@ start=`date +%s` # handle optional download dir if [ -z "$1" ] then - # navigate to ~/data - echo "navigating to ../data/ ..." - mkdir -p ../data - cd ../data/ + # navigate to ~/tmp + echo "navigating to ../tmp/ ..." + mkdir -p ../tmp + cd ../tmp/ else # check if is valid directory if [ ! -d $1 ]; then @@ -51,10 +51,10 @@ start=`date +%s` # handle optional download dir if [ -z "$1" ] then - # navigate to ~/data - echo "navigating to ../data/ ..." - mkdir -p ../data - cd ../data/ + # navigate to ~/tmp + echo "navigating to ../tmp/ ..." + mkdir -p ../tmp + cd ../tmp/ else # check if is valid directory if [ ! -d $1 ]; then @@ -82,7 +82,7 @@ runtime=$((end-start)) echo "Completed in" $runtime "seconds" -cd ../data +cd ../tmp echo "Spliting dataset..." python3 - "$@" < Date: Sat, 4 Jul 2020 17:44:53 -0700 Subject: [PATCH 58/71] update detect.py --- detect.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/detect.py b/detect.py index f9b12eb..2650c20 100644 --- a/detect.py +++ b/detect.py @@ -102,7 +102,7 @@ def detect(save_img=False): if save_img or view_img: # Add bbox to image label = '%s %.2f' % (names[int(cls)], conf) - plot_one_box(xyxy, im0, label=label, color=colors[int(cls)], line_thickness=1) + plot_one_box(xyxy, im0, label=label, color=colors[int(cls)], line_thickness=3) # Print time (inference + NMS) print('%sDone. (%.3fs)' % (s, t2 - t1)) @@ -139,10 +139,10 @@ def detect(save_img=False): if __name__ == '__main__': parser = argparse.ArgumentParser() - parser.add_argument('--weights', type=str, default='weights/yolov5m.pt', help='model.pt path') + parser.add_argument('--weights', type=str, default='weights/yolov5s.pt', help='model.pt path') parser.add_argument('--source', type=str, default='inference/images', help='source') # file/folder, 0 for webcam parser.add_argument('--output', type=str, default='inference/output', help='output folder') # output folder - parser.add_argument('--img-size', type=int, default=1024, help='inference size (pixels)') + parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)') parser.add_argument('--conf-thres', type=float, default=0.4, help='object confidence threshold') parser.add_argument('--iou-thres', type=float, default=0.5, help='IOU threshold for NMS') parser.add_argument('--fourcc', type=str, default='mp4v', help='output video codec (verify ffmpeg support)') From bb3c34691639b2aafb1a1f567e1dfb551a9a2563 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 4 Jul 2020 17:51:54 -0700 Subject: [PATCH 59/71] model.yaml nc inherited from dataset.yaml --- models/yolo.py | 3 ++- train.py | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/models/yolo.py b/models/yolo.py index 7cc86b2..5d96c60 100644 --- a/models/yolo.py +++ b/models/yolo.py @@ -52,7 +52,8 @@ class Model(nn.Module): self.md = yaml.load(f, Loader=yaml.FullLoader) # model dict # Define model - if nc: + if nc and nc != self.md['nc']: + print('Overriding %s nc=%g with nc=%g' % (model_cfg, self.md['nc'], nc)) self.md['nc'] = nc # override yaml value self.model, self.save = parse_model(self.md, ch=[ch]) # model, savelist, ch_out # print([x.shape for x in self.forward(torch.zeros(1, ch, 64, 64))]) diff --git a/train.py b/train.py index 1c8ff7d..e07cbb1 100644 --- a/train.py +++ b/train.py @@ -77,8 +77,7 @@ def train(hyp): os.remove(f) # Create model - model = Model(opt.cfg).to(device) - assert model.md['nc'] == nc, '%s nc=%g classes but %s nc=%g classes' % (opt.data, nc, opt.cfg, model.md['nc']) + model = Model(opt.cfg, nc=data_dict['nc']).to(device) # Image sizes gs = int(max(model.stride)) # grid size (max stride) From 4e2d24602d246231694ba1b4d3bf3bd01f027ea4 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sat, 4 Jul 2020 23:07:26 -0700 Subject: [PATCH 60/71] update yolo.py --- models/yolo.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/models/yolo.py b/models/yolo.py index 7cc86b2..66bdb7d 100644 --- a/models/yolo.py +++ b/models/yolo.py @@ -59,10 +59,14 @@ class Model(nn.Module): # Build strides, anchors m = self.model[-1] # Detect() - m.stride = torch.tensor([128 / x.shape[-2] for x in self.forward(torch.zeros(1, ch, 128, 128))]) # forward - m.anchors /= m.stride.view(-1, 1, 1) - check_anchor_order(m) - self.stride = m.stride + if isinstance(m, Detect): + s = 128 # 2x min stride + m.stride = torch.tensor([s / x.shape[-2] for x in self.forward(torch.zeros(1, ch, s, s))]) # forward + m.anchors /= m.stride.view(-1, 1, 1) + check_anchor_order(m) + self.stride = m.stride + self._initialize_biases() # only run once + # print('Strides: %s' % m.stride.tolist()) # Init weights, biases torch_utils.initialize_weights(self) @@ -146,7 +150,7 @@ class Model(nn.Module): def parse_model(md, ch): # model_dict, input_channels(3) - print('\n%3s%15s%3s%10s %-40s%-30s' % ('', 'from', 'n', 'params', 'module', 'arguments')) + print('\n%3s%18s%3s%10s %-40s%-30s' % ('', 'from', 'n', 'params', 'module', 'arguments')) anchors, nc, gd, gw = md['anchors'], md['nc'], md['depth_multiple'], md['width_multiple'] na = (len(anchors[0]) // 2) # number of anchors no = na * (nc + 5) # number of outputs = anchors * (classes + 5) @@ -161,7 +165,7 @@ def parse_model(md, ch): # model_dict, input_channels(3) pass n = max(round(n * gd), 1) if n > 1 else n # depth gain - if m in [nn.Conv2d, Conv, Bottleneck, SPP, DWConv, MixConv2d, Focus, BottleneckCSP, CrossConv]: + if m in [nn.Conv2d, Conv, Bottleneck, SPP, DWConv, MixConv2d, Focus, CrossConv, BottleneckCSP, C3]: c1, c2 = ch[f], args[0] # Normal @@ -182,7 +186,7 @@ def parse_model(md, ch): # model_dict, input_channels(3) # c2 = make_divisible(c2, 8) if c2 != no else c2 args = [c1, c2, *args[1:]] - if m is BottleneckCSP: + if m in [BottleneckCSP, C3]: args.insert(2, n) n = 1 elif m is nn.BatchNorm2d: @@ -198,7 +202,7 @@ def parse_model(md, ch): # model_dict, input_channels(3) t = str(m)[8:-2].replace('__main__.', '') # module type np = sum([x.numel() for x in m_.parameters()]) # number params m_.i, m_.f, m_.type, m_.np = i, f, t, np # attach index, 'from' index, type, number params - print('%3s%15s%3s%10.0f %-40s%-30s' % (i, f, n, np, t, args)) # print + print('%3s%18s%3s%10.0f %-40s%-30s' % (i, f, n, np, t, args)) # print save.extend(x % i for x in ([f] if isinstance(f, int) else f) if x != -1) # append to savelist layers.append(m_) ch.append(c2) From 956511dafd7451423be9c10f9c38f8e21631ba52 Mon Sep 17 00:00:00 2001 From: Laughing <61612323+Laughing-q@users.noreply.github.com> Date: Sun, 5 Jul 2020 15:08:24 +0800 Subject: [PATCH 61/71] fix LR bug --- train.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/train.py b/train.py index e07cbb1..61ed84e 100644 --- a/train.py +++ b/train.py @@ -101,6 +101,9 @@ def train(hyp): optim.SGD(pg0, lr=hyp['lr0'], momentum=hyp['momentum'], nesterov=True) optimizer.add_param_group({'params': pg1, 'weight_decay': hyp['weight_decay']}) # add pg1 with weight_decay optimizer.add_param_group({'params': pg2}) # add pg2 (biases) + # Scheduler https://arxiv.org/pdf/1812.01187.pdf + lf = lambda x: (((1 + math.cos(x * math.pi / epochs)) / 2) ** 1.0) * 0.9 + 0.1 # cosine + scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf) print('Optimizer groups: %g .bias, %g conv.weight, %g other' % (len(pg2), len(pg1), len(pg0))) del pg0, pg1, pg2 @@ -144,9 +147,7 @@ def train(hyp): if mixed_precision: model, optimizer = amp.initialize(model, optimizer, opt_level='O1', verbosity=0) - # Scheduler https://arxiv.org/pdf/1812.01187.pdf - lf = lambda x: (((1 + math.cos(x * math.pi / epochs)) / 2) ** 1.0) * 0.9 + 0.1 # cosine - scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf) + scheduler.last_epoch = start_epoch - 1 # do not move # https://discuss.pytorch.org/t/a-problem-occured-when-resuming-an-optimizer/28822 # plot_lr_scheduler(optimizer, scheduler, epochs) From 2541f77946530c6afebc5f2400701a220e2954a9 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 5 Jul 2020 11:57:48 -0700 Subject: [PATCH 62/71] update detect.py --- detect.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/detect.py b/detect.py index 2650c20..268b5df 100644 --- a/detect.py +++ b/detect.py @@ -123,10 +123,11 @@ def detect(save_img=False): if isinstance(vid_writer, cv2.VideoWriter): vid_writer.release() # release previous video writer + fourcc = 'mp4v' # output video codec fps = vid_cap.get(cv2.CAP_PROP_FPS) w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH)) h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) - vid_writer = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*opt.fourcc), fps, (w, h)) + vid_writer = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*fourcc), fps, (w, h)) vid_writer.write(im0) if save_txt or save_img: @@ -145,20 +146,20 @@ if __name__ == '__main__': parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)') parser.add_argument('--conf-thres', type=float, default=0.4, help='object confidence threshold') parser.add_argument('--iou-thres', type=float, default=0.5, help='IOU threshold for NMS') - parser.add_argument('--fourcc', type=str, default='mp4v', help='output video codec (verify ffmpeg support)') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') parser.add_argument('--view-img', action='store_true', help='display results') parser.add_argument('--save-txt', action='store_true', help='save results to *.txt') parser.add_argument('--classes', nargs='+', type=int, help='filter by class') parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS') parser.add_argument('--augment', action='store_true', help='augmented inference') + parser.add_argument('--update', action='store_true', help='update all models') opt = parser.parse_args() print(opt) with torch.no_grad(): - detect() - - # # Update all models - # for opt.weights in ['yolov5s.pt', 'yolov5m.pt', 'yolov5l.pt', 'yolov5x.pt', 'yolov3-spp.pt']: - # detect() - # create_pretrained(opt.weights, opt.weights) + if opt.update: # update all models (to fix SourceChangeWarning) + for opt.weights in ['yolov5s.pt', 'yolov5m.pt', 'yolov5l.pt', 'yolov5x.pt', 'yolov3-spp.pt']: + detect() + create_pretrained(opt.weights, opt.weights) + else: + detect() From 8c43a6906d32e96002b8d6aa6b5cfb8571fda3ce Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 5 Jul 2020 12:33:51 -0700 Subject: [PATCH 63/71] Update README.md --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1e29d18..d97be15 100755 --- a/README.md +++ b/README.md @@ -41,9 +41,13 @@ $ pip install -U -r requirements.txt ## Tutorials * [Notebook](https://github.com/ultralytics/yolov5/blob/master/tutorial.ipynb) Open In Colab +* [Kaggle](https://www.kaggle.com/ultralytics/yolov5-tutorial) * [Train Custom Data](https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data) -* [Google Cloud Quickstart Guide](https://github.com/ultralytics/yolov5/wiki/GCP-Quickstart) -* [Docker Quickstart Guide](https://github.com/ultralytics/yolov5/wiki/Docker-Quickstart) ![Docker Pulls](https://img.shields.io/docker/pulls/ultralytics/yolov5?logo=docker) +* [PyTorch Hub](https://github.com/ultralytics/yolov5/issues/36) +* [ONNX and TorchScript Export](https://github.com/ultralytics/yolov5/issues/251) +* [Test-Time Augmentation (TTA)](https://github.com/ultralytics/yolov5/issues/303) +* [Google Cloud Quickstart](https://github.com/ultralytics/yolov5/wiki/GCP-Quickstart) +* [Docker Quickstart](https://github.com/ultralytics/yolov5/wiki/Docker-Quickstart) ![Docker Pulls](https://img.shields.io/docker/pulls/ultralytics/yolov5?logo=docker) ## Inference From 997ba7b346beede15009b187b6d1d47367f94434 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 5 Jul 2020 12:50:04 -0700 Subject: [PATCH 64/71] import yaml in yolo.py --- models/yolo.py | 1 + 1 file changed, 1 insertion(+) diff --git a/models/yolo.py b/models/yolo.py index 7961fab..b2d09cc 100644 --- a/models/yolo.py +++ b/models/yolo.py @@ -48,6 +48,7 @@ class Model(nn.Module): if type(model_cfg) is dict: self.md = model_cfg # model dict else: # is *.yaml + import yaml with open(model_cfg) as f: self.md = yaml.load(f, Loader=yaml.FullLoader) # model dict From 38f5c1ad1d0f4d391544e302498eb81ff234e175 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 5 Jul 2020 13:41:21 -0700 Subject: [PATCH 65/71] pruning and sparsity initial commit --- models/yolo.py | 2 +- utils/torch_utils.py | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/models/yolo.py b/models/yolo.py index b2d09cc..9617f5b 100644 --- a/models/yolo.py +++ b/models/yolo.py @@ -48,7 +48,7 @@ class Model(nn.Module): if type(model_cfg) is dict: self.md = model_cfg # model dict else: # is *.yaml - import yaml + import yaml # for torch hub with open(model_cfg) as f: self.md = yaml.load(f, Loader=yaml.FullLoader) # model dict diff --git a/utils/torch_utils.py b/utils/torch_utils.py index fd00b8b..35ef011 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -76,6 +76,26 @@ def find_modules(model, mclass=nn.Conv2d): return [i for i, m in enumerate(model.module_list) if isinstance(m, mclass)] +def sparsity(model): + # Return global model sparsity + a, b = 0., 0. + for p in model.parameters(): + a += p.numel() + b += (p == 0).sum() + return b / a + + +def prune(model, amount=0.3): + # Prune model to requested global sparsity + import torch.nn.utils.prune as prune + print('Pruning model... ', end='') + for name, m in model.named_modules(): + if isinstance(m, torch.nn.Conv2d): + prune.l1_unstructured(m, name='weight', amount=amount) # prune + prune.remove(m, 'weight') # make permanent + print(' %.3g global sparsity' % sparsity(model)) + + def fuse_conv_and_bn(conv, bn): # https://tehnokv.com/posts/fusing-batchnorm-and-conv/ with torch.no_grad(): From 5ba1de0cdcc414c69ceb7a4c45eb1e3895eca32a Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 5 Jul 2020 15:02:56 -0700 Subject: [PATCH 66/71] update experimental.py with Ensemble() module --- models/experimental.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/models/experimental.py b/models/experimental.py index 539e7f9..146a61b 100644 --- a/models/experimental.py +++ b/models/experimental.py @@ -107,3 +107,15 @@ class MixConv2d(nn.Module): def forward(self, x): return x + self.act(self.bn(torch.cat([m(x) for m in self.m], 1))) + + +class Ensemble(nn.ModuleList): + # Ensemble of models + def __init__(self): + super(Ensemble, self).__init__() + + def forward(self, x, augment=False): + y = [] + for module in self: + y.append(module(x, augment)[0]) + return torch.cat(y, 1), None # ensembled inference output, train output From 04bdbe4104728dac15937ad06dbb9071ae3bebf9 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 5 Jul 2020 23:16:50 -0700 Subject: [PATCH 67/71] fuse update --- detect.py | 9 +++------ models/yolo.py | 4 ++-- test.py | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/detect.py b/detect.py index 268b5df..44cd64e 100644 --- a/detect.py +++ b/detect.py @@ -21,13 +21,10 @@ def detect(save_img=False): # Load model google_utils.attempt_download(weights) - model = torch.load(weights, map_location=device)['model'].float() # load to FP32 - # torch.save(torch.load(weights, map_location=device), weights) # update model if SourceChangeWarning - # model.fuse() - model.to(device).eval() - imgsz = check_img_size(imgsz, s=model.model[-1].stride.max()) # check img_size + model = torch.load(weights, map_location=device)['model'].float().eval() # load FP32 model + imgsz = check_img_size(imgsz, s=model.stride.max()) # check img_size if half: - model.half() # to FP16 + model.float() # to FP16 # Second-stage classifier classify = False diff --git a/models/yolo.py b/models/yolo.py index 9617f5b..3fd87a3 100644 --- a/models/yolo.py +++ b/models/yolo.py @@ -142,14 +142,14 @@ class Model(nn.Module): # print('%10.3g' % (m.w.detach().sigmoid() * 2)) # shortcut weights def fuse(self): # fuse model Conv2d() + BatchNorm2d() layers - print('Fusing layers...') + print('Fusing layers... ', end='') for m in self.model.modules(): if type(m) is Conv: m.conv = torch_utils.fuse_conv_and_bn(m.conv, m.bn) # update conv m.bn = None # remove batchnorm m.forward = m.fuseforward # update forward torch_utils.model_info(self) - + return self def parse_model(md, ch): # model_dict, input_channels(3) print('\n%3s%18s%3s%10s %-40s%-30s' % ('', 'from', 'n', 'params', 'module', 'arguments')) diff --git a/test.py b/test.py index 259d444..644f6b9 100644 --- a/test.py +++ b/test.py @@ -22,6 +22,7 @@ def test(data, # Initialize/load model and set device if model is None: training = False + merge = opt.merge # use Merge NMS device = torch_utils.select_device(opt.device, batch_size=batch_size) # Remove previous @@ -59,7 +60,6 @@ def test(data, # Dataloader if dataloader is None: # not training - merge = opt.merge # use Merge NMS img = torch.zeros((1, 3, imgsz, imgsz), device=device) # init img _ = model(img.half() if half else img) if device.type != 'cpu' else None # run once path = data['test'] if opt.task == 'test' else data['val'] # path to val/test images From a40f615c6f0c9b3acf3a39b3e58841c006010af2 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 5 Jul 2020 23:24:53 -0700 Subject: [PATCH 68/71] .half() bug fix --- detect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/detect.py b/detect.py index 44cd64e..d02f0a9 100644 --- a/detect.py +++ b/detect.py @@ -24,7 +24,7 @@ def detect(save_img=False): model = torch.load(weights, map_location=device)['model'].float().eval() # load FP32 model imgsz = check_img_size(imgsz, s=model.stride.max()) # check img_size if half: - model.float() # to FP16 + model.half() # to FP16 # Second-stage classifier classify = False From a62333e8084301721fa66797e8968b11b2204bbc Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 5 Jul 2020 23:27:03 -0700 Subject: [PATCH 69/71] test.py .fuse() update --- test.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test.py b/test.py index 644f6b9..2d6ab54 100644 --- a/test.py +++ b/test.py @@ -31,11 +31,8 @@ def test(data, # Load model google_utils.attempt_download(weights) - model = torch.load(weights, map_location=device)['model'].float() # load to FP32 - torch_utils.model_info(model) - model.fuse() - model.to(device) - imgsz = check_img_size(imgsz, s=model.model[-1].stride.max()) # check img_size + model = torch.load(weights, map_location=device)['model'].float().fuse() # load to FP32 + imgsz = check_img_size(imgsz, s=model.stride.max()) # check img_size # Multi-GPU disabled, incompatible with .half() https://github.com/ultralytics/yolov5/issues/99 # if device.type != 'cpu' and torch.cuda.device_count() > 1: From 6b95d6d2c06879f0f64f004e1f166246f7e90093 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Sun, 5 Jul 2020 23:37:36 -0700 Subject: [PATCH 70/71] .to(device) bug fix --- test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test.py b/test.py index 2d6ab54..1cfae95 100644 --- a/test.py +++ b/test.py @@ -31,7 +31,7 @@ def test(data, # Load model google_utils.attempt_download(weights) - model = torch.load(weights, map_location=device)['model'].float().fuse() # load to FP32 + model = torch.load(weights, map_location=device)['model'].float().fuse().to(device) # load to FP32 imgsz = check_img_size(imgsz, s=model.stride.max()) # check img_size # Multi-GPU disabled, incompatible with .half() https://github.com/ultralytics/yolov5/issues/99 From 121d90b3f2ffe085176ca3a21bbbc87260667655 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Mon, 6 Jul 2020 11:46:10 -0700 Subject: [PATCH 71/71] update fuse_conv_and_bn() --- utils/torch_utils.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/utils/torch_utils.py b/utils/torch_utils.py index 35ef011..6baa9d5 100644 --- a/utils/torch_utils.py +++ b/utils/torch_utils.py @@ -90,7 +90,7 @@ def prune(model, amount=0.3): import torch.nn.utils.prune as prune print('Pruning model... ', end='') for name, m in model.named_modules(): - if isinstance(m, torch.nn.Conv2d): + if isinstance(m, nn.Conv2d): prune.l1_unstructured(m, name='weight', amount=amount) # prune prune.remove(m, 'weight') # make permanent print(' %.3g global sparsity' % sparsity(model)) @@ -100,12 +100,12 @@ def fuse_conv_and_bn(conv, bn): # https://tehnokv.com/posts/fusing-batchnorm-and-conv/ with torch.no_grad(): # init - fusedconv = torch.nn.Conv2d(conv.in_channels, - conv.out_channels, - kernel_size=conv.kernel_size, - stride=conv.stride, - padding=conv.padding, - bias=True) + fusedconv = nn.Conv2d(conv.in_channels, + conv.out_channels, + kernel_size=conv.kernel_size, + stride=conv.stride, + padding=conv.padding, + bias=True).to(conv.weight.device) # prepare filters w_conv = conv.weight.clone().view(conv.out_channels, -1) @@ -113,10 +113,7 @@ def fuse_conv_and_bn(conv, bn): fusedconv.weight.copy_(torch.mm(w_bn, w_conv).view(fusedconv.weight.size())) # prepare spatial bias - if conv.bias is not None: - b_conv = conv.bias - else: - b_conv = torch.zeros(conv.weight.size(0), device=conv.weight.device) + b_conv = torch.zeros(conv.weight.size(0), device=conv.weight.device) if conv.bias is None else conv.bias b_bn = bn.bias - bn.weight.mul(bn.running_mean).div(torch.sqrt(bn.running_var + bn.eps)) fusedconv.bias.copy_(torch.mm(w_bn, b_conv.reshape(-1, 1)).reshape(-1) + b_bn) @@ -159,8 +156,8 @@ def load_classifier(name='resnet101', n=2): # Reshape output to n classes filters = model.fc.weight.shape[1] - model.fc.bias = torch.nn.Parameter(torch.zeros(n), requires_grad=True) - model.fc.weight = torch.nn.Parameter(torch.zeros(n, filters), requires_grad=True) + model.fc.bias = nn.Parameter(torch.zeros(n), requires_grad=True) + model.fc.weight = nn.Parameter(torch.zeros(n, filters), requires_grad=True) model.fc.out_features = n return model