Jirka Borovec pre-commit-ci[bot] glenn-jocher commited on
Commit
c3d5ac1
·
unverified ·
1 Parent(s): df9008e

precommit: yapf (#5494)

Browse files

* precommit: yapf

* align isort

* fix

# Conflicts:
# utils/plots.py

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

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

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

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

* Update setup.cfg

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

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

* Update setup.cfg

* Update setup.cfg

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

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

* Update wandb_utils.py

* Update augmentations.py

* Update setup.cfg

* Update yolo.py

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

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

* Update val.py

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

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

* simplify colorstr

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

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

* val run fix

* export.py last comma

* Update export.py

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

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

* Update hubconf.py

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

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

* PyTorch Hub tuple fix

* PyTorch Hub tuple fix2

* PyTorch Hub tuple fix3

* Update setup

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Glenn Jocher <glenn.jocher@ultralytics.com>

.pre-commit-config.yaml CHANGED
@@ -36,12 +36,11 @@ repos:
36
  - id: isort
37
  name: Sort imports
38
 
39
- # TODO
40
- #- repo: https://github.com/pre-commit/mirrors-yapf
41
- # rev: v0.31.0
42
- # hooks:
43
- # - id: yapf
44
- # name: formatting
45
 
46
  # TODO
47
  #- repo: https://github.com/executablebooks/mdformat
 
36
  - id: isort
37
  name: Sort imports
38
 
39
+ - repo: https://github.com/pre-commit/mirrors-yapf
40
+ rev: v0.31.0
41
+ hooks:
42
+ - id: yapf
43
+ name: formatting
 
44
 
45
  # TODO
46
  #- repo: https://github.com/executablebooks/mdformat
detect.py CHANGED
@@ -47,7 +47,8 @@ from utils.torch_utils import select_device, time_sync
47
 
48
 
49
  @torch.no_grad()
50
- def run(weights=ROOT / 'yolov5s.pt', # model.pt path(s)
 
51
  source=ROOT / 'data/images', # file/dir/URL/glob, 0 for webcam
52
  data=ROOT / 'data/coco128.yaml', # dataset.yaml path
53
  imgsz=(640, 640), # inference size (height, width)
@@ -73,7 +74,7 @@ def run(weights=ROOT / 'yolov5s.pt', # model.pt path(s)
73
  hide_conf=False, # hide confidences
74
  half=False, # use FP16 half-precision inference
75
  dnn=False, # use OpenCV DNN for ONNX inference
76
- ):
77
  source = str(source)
78
  save_img = not nosave and not source.endswith('.txt') # save inference images
79
  is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
 
47
 
48
 
49
  @torch.no_grad()
50
+ def run(
51
+ weights=ROOT / 'yolov5s.pt', # model.pt path(s)
52
  source=ROOT / 'data/images', # file/dir/URL/glob, 0 for webcam
53
  data=ROOT / 'data/coco128.yaml', # dataset.yaml path
54
  imgsz=(640, 640), # inference size (height, width)
 
74
  hide_conf=False, # hide confidences
75
  half=False, # use FP16 half-precision inference
76
  dnn=False, # use OpenCV DNN for ONNX inference
77
+ ):
78
  source = str(source)
79
  save_img = not nosave and not source.endswith('.txt') # save inference images
80
  is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
export.py CHANGED
@@ -76,16 +76,11 @@ from utils.torch_utils import select_device
76
 
77
  def export_formats():
78
  # YOLOv5 export formats
79
- x = [['PyTorch', '-', '.pt', True],
80
- ['TorchScript', 'torchscript', '.torchscript', True],
81
- ['ONNX', 'onnx', '.onnx', True],
82
- ['OpenVINO', 'openvino', '_openvino_model', False],
83
- ['TensorRT', 'engine', '.engine', True],
84
- ['CoreML', 'coreml', '.mlmodel', False],
85
- ['TensorFlow SavedModel', 'saved_model', '_saved_model', True],
86
- ['TensorFlow GraphDef', 'pb', '.pb', True],
87
- ['TensorFlow Lite', 'tflite', '.tflite', False],
88
- ['TensorFlow Edge TPU', 'edgetpu', '_edgetpu.tflite', False],
89
  ['TensorFlow.js', 'tfjs', '_web_model', False]]
90
  return pd.DataFrame(x, columns=['Format', 'Argument', 'Suffix', 'GPU'])
91
 
@@ -119,14 +114,25 @@ def export_onnx(model, im, file, opset, train, dynamic, simplify, prefix=colorst
119
  LOGGER.info(f'\n{prefix} starting export with onnx {onnx.__version__}...')
120
  f = file.with_suffix('.onnx')
121
 
122
- torch.onnx.export(model, im, f, verbose=False, opset_version=opset,
123
- training=torch.onnx.TrainingMode.TRAINING if train else torch.onnx.TrainingMode.EVAL,
124
- do_constant_folding=not train,
125
- input_names=['images'],
126
- output_names=['output'],
127
- dynamic_axes={'images': {0: 'batch', 2: 'height', 3: 'width'}, # shape(1,3,640,640)
128
- 'output': {0: 'batch', 1: 'anchors'} # shape(1,25200,85)
129
- } if dynamic else None)
 
 
 
 
 
 
 
 
 
 
 
130
 
131
  # Checks
132
  model_onnx = onnx.load(f) # load onnx model
@@ -140,10 +146,9 @@ def export_onnx(model, im, file, opset, train, dynamic, simplify, prefix=colorst
140
  import onnxsim
141
 
142
  LOGGER.info(f'{prefix} simplifying with onnx-simplifier {onnxsim.__version__}...')
143
- model_onnx, check = onnxsim.simplify(
144
- model_onnx,
145
- dynamic_input_shape=dynamic,
146
- input_shapes={'images': list(im.shape)} if dynamic else None)
147
  assert check, 'assert check failed'
148
  onnx.save(model_onnx, f)
149
  except Exception as e:
@@ -246,9 +251,18 @@ def export_engine(model, im, file, train, half, simplify, workspace=4, verbose=F
246
  LOGGER.info(f'\n{prefix} export failure: {e}')
247
 
248
 
249
- def export_saved_model(model, im, file, dynamic,
250
- tf_nms=False, agnostic_nms=False, topk_per_class=100, topk_all=100, iou_thres=0.45,
251
- conf_thres=0.25, keras=False, prefix=colorstr('TensorFlow SavedModel:')):
 
 
 
 
 
 
 
 
 
252
  # YOLOv5 TensorFlow SavedModel export
253
  try:
254
  import tensorflow as tf
@@ -278,11 +292,10 @@ def export_saved_model(model, im, file, dynamic,
278
  tfm = tf.Module()
279
  tfm.__call__ = tf.function(lambda x: frozen_func(x)[0], [spec])
280
  tfm.__call__(im)
281
- tf.saved_model.save(
282
- tfm,
283
- f,
284
- options=tf.saved_model.SaveOptions(experimental_custom_gradients=False) if
285
- check_version(tf.__version__, '2.6') else tf.saved_model.SaveOptions())
286
  LOGGER.info(f'{prefix} export success, saved as {f} ({file_size(f):.1f} MB)')
287
  return keras_model, f
288
  except Exception as e:
@@ -352,10 +365,10 @@ def export_edgetpu(keras_model, im, file, prefix=colorstr('Edge TPU:')):
352
  if subprocess.run(cmd + ' >/dev/null', shell=True).returncode != 0:
353
  LOGGER.info(f'\n{prefix} export requires Edge TPU compiler. Attempting install from {help_url}')
354
  sudo = subprocess.run('sudo --version >/dev/null', shell=True).returncode == 0 # sudo installed on system
355
- for c in ['curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -',
356
- 'echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" | sudo tee /etc/apt/sources.list.d/coral-edgetpu.list',
357
- 'sudo apt-get update',
358
- 'sudo apt-get install edgetpu-compiler']:
359
  subprocess.run(c if sudo else c.replace('sudo ', ''), shell=True, check=True)
360
  ver = subprocess.run(cmd, shell=True, capture_output=True, check=True).stdout.decode().split()[-1]
361
 
@@ -395,12 +408,10 @@ def export_tfjs(keras_model, im, file, prefix=colorstr('TensorFlow.js:')):
395
  r'{"outputs": {"Identity.?.?": {"name": "Identity.?.?"}, '
396
  r'"Identity.?.?": {"name": "Identity.?.?"}, '
397
  r'"Identity.?.?": {"name": "Identity.?.?"}, '
398
- r'"Identity.?.?": {"name": "Identity.?.?"}}}',
399
- r'{"outputs": {"Identity": {"name": "Identity"}, '
400
  r'"Identity_1": {"name": "Identity_1"}, '
401
  r'"Identity_2": {"name": "Identity_2"}, '
402
- r'"Identity_3": {"name": "Identity_3"}}}',
403
- json)
404
  j.write(subst)
405
 
406
  LOGGER.info(f'{prefix} export success, saved as {f} ({file_size(f):.1f} MB)')
@@ -410,7 +421,8 @@ def export_tfjs(keras_model, im, file, prefix=colorstr('TensorFlow.js:')):
410
 
411
 
412
  @torch.no_grad()
413
- def run(data=ROOT / 'data/coco128.yaml', # 'dataset.yaml path'
 
414
  weights=ROOT / 'yolov5s.pt', # weights path
415
  imgsz=(640, 640), # image (height, width)
416
  batch_size=1, # batch size
@@ -431,8 +443,8 @@ def run(data=ROOT / 'data/coco128.yaml', # 'dataset.yaml path'
431
  topk_per_class=100, # TF.js NMS: topk per class to keep
432
  topk_all=100, # TF.js NMS: topk for all classes to keep
433
  iou_thres=0.45, # TF.js NMS: IoU threshold
434
- conf_thres=0.25 # TF.js NMS: confidence threshold
435
- ):
436
  t = time.time()
437
  include = [x.lower() for x in include] # to lowercase
438
  formats = tuple(export_formats()['Argument'][1:]) # --include arguments
@@ -495,9 +507,16 @@ def run(data=ROOT / 'data/coco128.yaml', # 'dataset.yaml path'
495
  if int8 or edgetpu: # TFLite --int8 bug https://github.com/ultralytics/yolov5/issues/5707
496
  check_requirements(('flatbuffers==1.12',)) # required before `import tensorflow`
497
  assert not (tflite and tfjs), 'TFLite and TF.js models must be exported separately, please pass only one type.'
498
- model, f[5] = export_saved_model(model.cpu(), im, file, dynamic, tf_nms=nms or agnostic_nms or tfjs,
499
- agnostic_nms=agnostic_nms or tfjs, topk_per_class=topk_per_class,
500
- topk_all=topk_all, conf_thres=conf_thres, iou_thres=iou_thres) # keras model
 
 
 
 
 
 
 
501
  if pb or tfjs: # pb prerequisite to tfjs
502
  f[6] = export_pb(model, im, file)
503
  if tflite or edgetpu:
@@ -542,7 +561,8 @@ def parse_opt():
542
  parser.add_argument('--topk-all', type=int, default=100, help='TF.js NMS: topk for all classes to keep')
543
  parser.add_argument('--iou-thres', type=float, default=0.45, help='TF.js NMS: IoU threshold')
544
  parser.add_argument('--conf-thres', type=float, default=0.25, help='TF.js NMS: confidence threshold')
545
- parser.add_argument('--include', nargs='+',
 
546
  default=['torchscript', 'onnx'],
547
  help='torchscript, onnx, openvino, engine, coreml, saved_model, pb, tflite, edgetpu, tfjs')
548
  opt = parser.parse_args()
 
76
 
77
  def export_formats():
78
  # YOLOv5 export formats
79
+ x = [['PyTorch', '-', '.pt', True], ['TorchScript', 'torchscript', '.torchscript', True],
80
+ ['ONNX', 'onnx', '.onnx', True], ['OpenVINO', 'openvino', '_openvino_model', False],
81
+ ['TensorRT', 'engine', '.engine', True], ['CoreML', 'coreml', '.mlmodel', False],
82
+ ['TensorFlow SavedModel', 'saved_model', '_saved_model', True], ['TensorFlow GraphDef', 'pb', '.pb', True],
83
+ ['TensorFlow Lite', 'tflite', '.tflite', False], ['TensorFlow Edge TPU', 'edgetpu', '_edgetpu.tflite', False],
 
 
 
 
 
84
  ['TensorFlow.js', 'tfjs', '_web_model', False]]
85
  return pd.DataFrame(x, columns=['Format', 'Argument', 'Suffix', 'GPU'])
86
 
 
114
  LOGGER.info(f'\n{prefix} starting export with onnx {onnx.__version__}...')
115
  f = file.with_suffix('.onnx')
116
 
117
+ torch.onnx.export(
118
+ model,
119
+ im,
120
+ f,
121
+ verbose=False,
122
+ opset_version=opset,
123
+ training=torch.onnx.TrainingMode.TRAINING if train else torch.onnx.TrainingMode.EVAL,
124
+ do_constant_folding=not train,
125
+ input_names=['images'],
126
+ output_names=['output'],
127
+ dynamic_axes={
128
+ 'images': {
129
+ 0: 'batch',
130
+ 2: 'height',
131
+ 3: 'width'}, # shape(1,3,640,640)
132
+ 'output': {
133
+ 0: 'batch',
134
+ 1: 'anchors'} # shape(1,25200,85)
135
+ } if dynamic else None)
136
 
137
  # Checks
138
  model_onnx = onnx.load(f) # load onnx model
 
146
  import onnxsim
147
 
148
  LOGGER.info(f'{prefix} simplifying with onnx-simplifier {onnxsim.__version__}...')
149
+ model_onnx, check = onnxsim.simplify(model_onnx,
150
+ dynamic_input_shape=dynamic,
151
+ input_shapes={'images': list(im.shape)} if dynamic else None)
 
152
  assert check, 'assert check failed'
153
  onnx.save(model_onnx, f)
154
  except Exception as e:
 
251
  LOGGER.info(f'\n{prefix} export failure: {e}')
252
 
253
 
254
+ def export_saved_model(model,
255
+ im,
256
+ file,
257
+ dynamic,
258
+ tf_nms=False,
259
+ agnostic_nms=False,
260
+ topk_per_class=100,
261
+ topk_all=100,
262
+ iou_thres=0.45,
263
+ conf_thres=0.25,
264
+ keras=False,
265
+ prefix=colorstr('TensorFlow SavedModel:')):
266
  # YOLOv5 TensorFlow SavedModel export
267
  try:
268
  import tensorflow as tf
 
292
  tfm = tf.Module()
293
  tfm.__call__ = tf.function(lambda x: frozen_func(x)[0], [spec])
294
  tfm.__call__(im)
295
+ tf.saved_model.save(tfm,
296
+ f,
297
+ options=tf.saved_model.SaveOptions(experimental_custom_gradients=False)
298
+ if check_version(tf.__version__, '2.6') else tf.saved_model.SaveOptions())
 
299
  LOGGER.info(f'{prefix} export success, saved as {f} ({file_size(f):.1f} MB)')
300
  return keras_model, f
301
  except Exception as e:
 
365
  if subprocess.run(cmd + ' >/dev/null', shell=True).returncode != 0:
366
  LOGGER.info(f'\n{prefix} export requires Edge TPU compiler. Attempting install from {help_url}')
367
  sudo = subprocess.run('sudo --version >/dev/null', shell=True).returncode == 0 # sudo installed on system
368
+ for c in (
369
+ 'curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -',
370
+ 'echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" | sudo tee /etc/apt/sources.list.d/coral-edgetpu.list',
371
+ 'sudo apt-get update', 'sudo apt-get install edgetpu-compiler'):
372
  subprocess.run(c if sudo else c.replace('sudo ', ''), shell=True, check=True)
373
  ver = subprocess.run(cmd, shell=True, capture_output=True, check=True).stdout.decode().split()[-1]
374
 
 
408
  r'{"outputs": {"Identity.?.?": {"name": "Identity.?.?"}, '
409
  r'"Identity.?.?": {"name": "Identity.?.?"}, '
410
  r'"Identity.?.?": {"name": "Identity.?.?"}, '
411
+ r'"Identity.?.?": {"name": "Identity.?.?"}}}', r'{"outputs": {"Identity": {"name": "Identity"}, '
 
412
  r'"Identity_1": {"name": "Identity_1"}, '
413
  r'"Identity_2": {"name": "Identity_2"}, '
414
+ r'"Identity_3": {"name": "Identity_3"}}}', json)
 
415
  j.write(subst)
416
 
417
  LOGGER.info(f'{prefix} export success, saved as {f} ({file_size(f):.1f} MB)')
 
421
 
422
 
423
  @torch.no_grad()
424
+ def run(
425
+ data=ROOT / 'data/coco128.yaml', # 'dataset.yaml path'
426
  weights=ROOT / 'yolov5s.pt', # weights path
427
  imgsz=(640, 640), # image (height, width)
428
  batch_size=1, # batch size
 
443
  topk_per_class=100, # TF.js NMS: topk per class to keep
444
  topk_all=100, # TF.js NMS: topk for all classes to keep
445
  iou_thres=0.45, # TF.js NMS: IoU threshold
446
+ conf_thres=0.25, # TF.js NMS: confidence threshold
447
+ ):
448
  t = time.time()
449
  include = [x.lower() for x in include] # to lowercase
450
  formats = tuple(export_formats()['Argument'][1:]) # --include arguments
 
507
  if int8 or edgetpu: # TFLite --int8 bug https://github.com/ultralytics/yolov5/issues/5707
508
  check_requirements(('flatbuffers==1.12',)) # required before `import tensorflow`
509
  assert not (tflite and tfjs), 'TFLite and TF.js models must be exported separately, please pass only one type.'
510
+ model, f[5] = export_saved_model(model.cpu(),
511
+ im,
512
+ file,
513
+ dynamic,
514
+ tf_nms=nms or agnostic_nms or tfjs,
515
+ agnostic_nms=agnostic_nms or tfjs,
516
+ topk_per_class=topk_per_class,
517
+ topk_all=topk_all,
518
+ conf_thres=conf_thres,
519
+ iou_thres=iou_thres) # keras model
520
  if pb or tfjs: # pb prerequisite to tfjs
521
  f[6] = export_pb(model, im, file)
522
  if tflite or edgetpu:
 
561
  parser.add_argument('--topk-all', type=int, default=100, help='TF.js NMS: topk for all classes to keep')
562
  parser.add_argument('--iou-thres', type=float, default=0.45, help='TF.js NMS: IoU threshold')
563
  parser.add_argument('--conf-thres', type=float, default=0.25, help='TF.js NMS: confidence threshold')
564
+ parser.add_argument('--include',
565
+ nargs='+',
566
  default=['torchscript', 'onnx'],
567
  help='torchscript, onnx, openvino, engine, coreml, saved_model, pb, tflite, edgetpu, tfjs')
568
  opt = parser.parse_args()
hubconf.py CHANGED
@@ -132,12 +132,13 @@ if __name__ == '__main__':
132
 
133
  from utils.general import cv2
134
 
135
- imgs = ['data/images/zidane.jpg', # filename
136
- Path('data/images/zidane.jpg'), # Path
137
- 'https://ultralytics.com/images/zidane.jpg', # URI
138
- cv2.imread('data/images/bus.jpg')[:, :, ::-1], # OpenCV
139
- Image.open('data/images/bus.jpg'), # PIL
140
- np.zeros((320, 640, 3))] # numpy
 
141
 
142
  results = model(imgs, size=320) # batched inference
143
  results.print()
 
132
 
133
  from utils.general import cv2
134
 
135
+ imgs = [
136
+ 'data/images/zidane.jpg', # filename
137
+ Path('data/images/zidane.jpg'), # Path
138
+ 'https://ultralytics.com/images/zidane.jpg', # URI
139
+ cv2.imread('data/images/bus.jpg')[:, :, ::-1], # OpenCV
140
+ Image.open('data/images/bus.jpg'), # PIL
141
+ np.zeros((320, 640, 3))] # numpy
142
 
143
  results = model(imgs, size=320) # batched inference
144
  results.print()
models/common.py CHANGED
@@ -227,11 +227,12 @@ class GhostBottleneck(nn.Module):
227
  def __init__(self, c1, c2, k=3, s=1): # ch_in, ch_out, kernel, stride
228
  super().__init__()
229
  c_ = c2 // 2
230
- self.conv = nn.Sequential(GhostConv(c1, c_, 1, 1), # pw
231
- DWConv(c_, c_, k, s, act=False) if s == 2 else nn.Identity(), # dw
232
- GhostConv(c_, c2, 1, 1, act=False)) # pw-linear
233
- self.shortcut = nn.Sequential(DWConv(c1, c1, k, s, act=False),
234
- Conv(c1, c2, 1, 1, act=False)) if s == 2 else nn.Identity()
 
235
 
236
  def forward(self, x):
237
  return self.conv(x) + self.shortcut(x)
@@ -387,9 +388,10 @@ class DetectMultiBackend(nn.Module):
387
  Interpreter, load_delegate = tf.lite.Interpreter, tf.lite.experimental.load_delegate,
388
  if edgetpu: # Edge TPU https://coral.ai/software/#edgetpu-runtime
389
  LOGGER.info(f'Loading {w} for TensorFlow Lite Edge TPU inference...')
390
- delegate = {'Linux': 'libedgetpu.so.1',
391
- 'Darwin': 'libedgetpu.1.dylib',
392
- 'Windows': 'edgetpu.dll'}[platform.system()]
 
393
  interpreter = Interpreter(model_path=w, experimental_delegates=[load_delegate(delegate)])
394
  else: # Lite
395
  LOGGER.info(f'Loading {w} for TensorFlow Lite inference...')
@@ -531,7 +533,7 @@ class AutoShape(nn.Module):
531
  return self.model(imgs.to(p.device).type_as(p), augment, profile) # inference
532
 
533
  # Pre-process
534
- n, imgs = (len(imgs), imgs) if isinstance(imgs, list) else (1, [imgs]) # number of images, list of images
535
  shape0, shape1, files = [], [], [] # image and inference shapes, filenames
536
  for i, im in enumerate(imgs):
537
  f = f'image{i}' # filename
@@ -561,8 +563,13 @@ class AutoShape(nn.Module):
561
  t.append(time_sync())
562
 
563
  # Post-process
564
- y = non_max_suppression(y if self.dmb else y[0], self.conf, self.iou, self.classes, self.agnostic,
565
- self.multi_label, max_det=self.max_det) # NMS
 
 
 
 
 
566
  for i in range(n):
567
  scale_coords(shape1, y[i][:, :4], shape0[i])
568
 
@@ -603,8 +610,12 @@ class Detections:
603
  label = f'{self.names[int(cls)]} {conf:.2f}'
604
  if crop:
605
  file = save_dir / 'crops' / self.names[int(cls)] / self.files[i] if save else None
606
- crops.append({'box': box, 'conf': conf, 'cls': cls, 'label': label,
607
- 'im': save_one_box(box, im, file=file, save=save)})
 
 
 
 
608
  else: # all others
609
  annotator.box_label(box, label if labels else '', color=colors(cls))
610
  im = annotator.im
 
227
  def __init__(self, c1, c2, k=3, s=1): # ch_in, ch_out, kernel, stride
228
  super().__init__()
229
  c_ = c2 // 2
230
+ self.conv = nn.Sequential(
231
+ GhostConv(c1, c_, 1, 1), # pw
232
+ DWConv(c_, c_, k, s, act=False) if s == 2 else nn.Identity(), # dw
233
+ GhostConv(c_, c2, 1, 1, act=False)) # pw-linear
234
+ self.shortcut = nn.Sequential(DWConv(c1, c1, k, s, act=False), Conv(c1, c2, 1, 1,
235
+ act=False)) if s == 2 else nn.Identity()
236
 
237
  def forward(self, x):
238
  return self.conv(x) + self.shortcut(x)
 
388
  Interpreter, load_delegate = tf.lite.Interpreter, tf.lite.experimental.load_delegate,
389
  if edgetpu: # Edge TPU https://coral.ai/software/#edgetpu-runtime
390
  LOGGER.info(f'Loading {w} for TensorFlow Lite Edge TPU inference...')
391
+ delegate = {
392
+ 'Linux': 'libedgetpu.so.1',
393
+ 'Darwin': 'libedgetpu.1.dylib',
394
+ 'Windows': 'edgetpu.dll'}[platform.system()]
395
  interpreter = Interpreter(model_path=w, experimental_delegates=[load_delegate(delegate)])
396
  else: # Lite
397
  LOGGER.info(f'Loading {w} for TensorFlow Lite inference...')
 
533
  return self.model(imgs.to(p.device).type_as(p), augment, profile) # inference
534
 
535
  # Pre-process
536
+ n, imgs = (len(imgs), list(imgs)) if isinstance(imgs, (list, tuple)) else (1, [imgs]) # number, list of images
537
  shape0, shape1, files = [], [], [] # image and inference shapes, filenames
538
  for i, im in enumerate(imgs):
539
  f = f'image{i}' # filename
 
563
  t.append(time_sync())
564
 
565
  # Post-process
566
+ y = non_max_suppression(y if self.dmb else y[0],
567
+ self.conf,
568
+ self.iou,
569
+ self.classes,
570
+ self.agnostic,
571
+ self.multi_label,
572
+ max_det=self.max_det) # NMS
573
  for i in range(n):
574
  scale_coords(shape1, y[i][:, :4], shape0[i])
575
 
 
610
  label = f'{self.names[int(cls)]} {conf:.2f}'
611
  if crop:
612
  file = save_dir / 'crops' / self.names[int(cls)] / self.files[i] if save else None
613
+ crops.append({
614
+ 'box': box,
615
+ 'conf': conf,
616
+ 'cls': cls,
617
+ 'label': label,
618
+ 'im': save_one_box(box, im, file=file, save=save)})
619
  else: # all others
620
  annotator.box_label(box, label if labels else '', color=colors(cls))
621
  im = annotator.im
models/experimental.py CHANGED
@@ -63,8 +63,8 @@ class MixConv2d(nn.Module):
63
  a[0] = 1
64
  c_ = np.linalg.lstsq(a, b, rcond=None)[0].round() # solve for equal weight indices, ax = b
65
 
66
- self.m = nn.ModuleList(
67
- [nn.Conv2d(c1, int(c_), k, s, k // 2, groups=math.gcd(c1, int(c_)), bias=False) for k, c_ in zip(k, c_)])
68
  self.bn = nn.BatchNorm2d(c2)
69
  self.act = nn.SiLU()
70
 
 
63
  a[0] = 1
64
  c_ = np.linalg.lstsq(a, b, rcond=None)[0].round() # solve for equal weight indices, ax = b
65
 
66
+ self.m = nn.ModuleList([
67
+ nn.Conv2d(c1, int(c_), k, s, k // 2, groups=math.gcd(c1, int(c_)), bias=False) for k, c_ in zip(k, c_)])
68
  self.bn = nn.BatchNorm2d(c2)
69
  self.act = nn.SiLU()
70
 
models/tf.py CHANGED
@@ -69,7 +69,11 @@ class TFConv(keras.layers.Layer):
69
  # see https://stackoverflow.com/questions/52975843/comparing-conv2d-with-padding-between-tensorflow-and-pytorch
70
 
71
  conv = keras.layers.Conv2D(
72
- c2, k, s, 'SAME' if s == 1 else 'VALID', use_bias=False if hasattr(w, 'bn') else True,
 
 
 
 
73
  kernel_initializer=keras.initializers.Constant(w.conv.weight.permute(2, 3, 1, 0).numpy()),
74
  bias_initializer='zeros' if hasattr(w, 'bn') else keras.initializers.Constant(w.conv.bias.numpy()))
75
  self.conv = conv if s == 1 else keras.Sequential([TFPad(autopad(k, p)), conv])
@@ -98,10 +102,10 @@ class TFFocus(keras.layers.Layer):
98
 
99
  def call(self, inputs): # x(b,w,h,c) -> y(b,w/2,h/2,4c)
100
  # inputs = inputs / 255 # normalize 0-255 to 0-1
101
- return self.conv(tf.concat([inputs[:, ::2, ::2, :],
102
- inputs[:, 1::2, ::2, :],
103
- inputs[:, ::2, 1::2, :],
104
- inputs[:, 1::2, 1::2, :]], 3))
105
 
106
 
107
  class TFBottleneck(keras.layers.Layer):
@@ -123,9 +127,14 @@ class TFConv2d(keras.layers.Layer):
123
  super().__init__()
124
  assert g == 1, "TF v2.2 Conv2D does not support 'groups' argument"
125
  self.conv = keras.layers.Conv2D(
126
- c2, k, s, 'VALID', use_bias=bias,
 
 
 
 
127
  kernel_initializer=keras.initializers.Constant(w.weight.permute(2, 3, 1, 0).numpy()),
128
- bias_initializer=keras.initializers.Constant(w.bias.numpy()) if bias else None, )
 
129
 
130
  def call(self, inputs):
131
  return self.conv(inputs)
@@ -206,8 +215,7 @@ class TFDetect(keras.layers.Layer):
206
  self.na = len(anchors[0]) // 2 # number of anchors
207
  self.grid = [tf.zeros(1)] * self.nl # init grid
208
  self.anchors = tf.convert_to_tensor(w.anchors.numpy(), dtype=tf.float32)
209
- self.anchor_grid = tf.reshape(self.anchors * tf.reshape(self.stride, [self.nl, 1, 1]),
210
- [self.nl, 1, -1, 1, 2])
211
  self.m = [TFConv2d(x, self.no * self.na, 1, w=w.m[i]) for i, x in enumerate(ch)]
212
  self.training = False # set to False after building model
213
  self.imgsz = imgsz
@@ -339,7 +347,13 @@ class TFModel:
339
  self.yaml['nc'] = nc # override yaml value
340
  self.model, self.savelist = parse_model(deepcopy(self.yaml), ch=[ch], model=model, imgsz=imgsz)
341
 
342
- def predict(self, inputs, tf_nms=False, agnostic_nms=False, topk_per_class=100, topk_all=100, iou_thres=0.45,
 
 
 
 
 
 
343
  conf_thres=0.25):
344
  y = [] # outputs
345
  x = inputs
@@ -361,8 +375,13 @@ class TFModel:
361
  return nms, x[1]
362
  else:
363
  boxes = tf.expand_dims(boxes, 2)
364
- nms = tf.image.combined_non_max_suppression(
365
- boxes, scores, topk_per_class, topk_all, iou_thres, conf_thres, clip_boxes=False)
 
 
 
 
 
366
  return nms, x[1]
367
 
368
  return x[0] # output only first tensor [1,6300,85] = [xywh, conf, class0, class1, ...]
@@ -383,7 +402,8 @@ class AgnosticNMS(keras.layers.Layer):
383
  # TF Agnostic NMS
384
  def call(self, input, topk_all, iou_thres, conf_thres):
385
  # wrap map_fn to avoid TypeSpec related error https://stackoverflow.com/a/65809989/3036450
386
- return tf.map_fn(lambda x: self._nms(x, topk_all, iou_thres, conf_thres), input,
 
387
  fn_output_signature=(tf.float32, tf.float32, tf.float32, tf.int32),
388
  name='agnostic_nms')
389
 
@@ -392,20 +412,26 @@ class AgnosticNMS(keras.layers.Layer):
392
  boxes, classes, scores = x
393
  class_inds = tf.cast(tf.argmax(classes, axis=-1), tf.float32)
394
  scores_inp = tf.reduce_max(scores, -1)
395
- selected_inds = tf.image.non_max_suppression(
396
- boxes, scores_inp, max_output_size=topk_all, iou_threshold=iou_thres, score_threshold=conf_thres)
 
 
 
397
  selected_boxes = tf.gather(boxes, selected_inds)
398
  padded_boxes = tf.pad(selected_boxes,
399
  paddings=[[0, topk_all - tf.shape(selected_boxes)[0]], [0, 0]],
400
- mode="CONSTANT", constant_values=0.0)
 
401
  selected_scores = tf.gather(scores_inp, selected_inds)
402
  padded_scores = tf.pad(selected_scores,
403
  paddings=[[0, topk_all - tf.shape(selected_boxes)[0]]],
404
- mode="CONSTANT", constant_values=-1.0)
 
405
  selected_classes = tf.gather(class_inds, selected_inds)
406
  padded_classes = tf.pad(selected_classes,
407
  paddings=[[0, topk_all - tf.shape(selected_boxes)[0]]],
408
- mode="CONSTANT", constant_values=-1.0)
 
409
  valid_detections = tf.shape(selected_inds)[0]
410
  return padded_boxes, padded_scores, padded_classes, valid_detections
411
 
@@ -421,11 +447,12 @@ def representative_dataset_gen(dataset, ncalib=100):
421
  break
422
 
423
 
424
- def run(weights=ROOT / 'yolov5s.pt', # weights path
 
425
  imgsz=(640, 640), # inference size h,w
426
  batch_size=1, # batch size
427
  dynamic=False, # dynamic batch size
428
- ):
429
  # PyTorch model
430
  im = torch.zeros((batch_size, 3, *imgsz)) # BCHW image
431
  model = attempt_load(weights, map_location=torch.device('cpu'), inplace=True, fuse=False)
 
69
  # see https://stackoverflow.com/questions/52975843/comparing-conv2d-with-padding-between-tensorflow-and-pytorch
70
 
71
  conv = keras.layers.Conv2D(
72
+ c2,
73
+ k,
74
+ s,
75
+ 'SAME' if s == 1 else 'VALID',
76
+ use_bias=False if hasattr(w, 'bn') else True,
77
  kernel_initializer=keras.initializers.Constant(w.conv.weight.permute(2, 3, 1, 0).numpy()),
78
  bias_initializer='zeros' if hasattr(w, 'bn') else keras.initializers.Constant(w.conv.bias.numpy()))
79
  self.conv = conv if s == 1 else keras.Sequential([TFPad(autopad(k, p)), conv])
 
102
 
103
  def call(self, inputs): # x(b,w,h,c) -> y(b,w/2,h/2,4c)
104
  # inputs = inputs / 255 # normalize 0-255 to 0-1
105
+ return self.conv(
106
+ tf.concat(
107
+ [inputs[:, ::2, ::2, :], inputs[:, 1::2, ::2, :], inputs[:, ::2, 1::2, :], inputs[:, 1::2, 1::2, :]],
108
+ 3))
109
 
110
 
111
  class TFBottleneck(keras.layers.Layer):
 
127
  super().__init__()
128
  assert g == 1, "TF v2.2 Conv2D does not support 'groups' argument"
129
  self.conv = keras.layers.Conv2D(
130
+ c2,
131
+ k,
132
+ s,
133
+ 'VALID',
134
+ use_bias=bias,
135
  kernel_initializer=keras.initializers.Constant(w.weight.permute(2, 3, 1, 0).numpy()),
136
+ bias_initializer=keras.initializers.Constant(w.bias.numpy()) if bias else None,
137
+ )
138
 
139
  def call(self, inputs):
140
  return self.conv(inputs)
 
215
  self.na = len(anchors[0]) // 2 # number of anchors
216
  self.grid = [tf.zeros(1)] * self.nl # init grid
217
  self.anchors = tf.convert_to_tensor(w.anchors.numpy(), dtype=tf.float32)
218
+ self.anchor_grid = tf.reshape(self.anchors * tf.reshape(self.stride, [self.nl, 1, 1]), [self.nl, 1, -1, 1, 2])
 
219
  self.m = [TFConv2d(x, self.no * self.na, 1, w=w.m[i]) for i, x in enumerate(ch)]
220
  self.training = False # set to False after building model
221
  self.imgsz = imgsz
 
347
  self.yaml['nc'] = nc # override yaml value
348
  self.model, self.savelist = parse_model(deepcopy(self.yaml), ch=[ch], model=model, imgsz=imgsz)
349
 
350
+ def predict(self,
351
+ inputs,
352
+ tf_nms=False,
353
+ agnostic_nms=False,
354
+ topk_per_class=100,
355
+ topk_all=100,
356
+ iou_thres=0.45,
357
  conf_thres=0.25):
358
  y = [] # outputs
359
  x = inputs
 
375
  return nms, x[1]
376
  else:
377
  boxes = tf.expand_dims(boxes, 2)
378
+ nms = tf.image.combined_non_max_suppression(boxes,
379
+ scores,
380
+ topk_per_class,
381
+ topk_all,
382
+ iou_thres,
383
+ conf_thres,
384
+ clip_boxes=False)
385
  return nms, x[1]
386
 
387
  return x[0] # output only first tensor [1,6300,85] = [xywh, conf, class0, class1, ...]
 
402
  # TF Agnostic NMS
403
  def call(self, input, topk_all, iou_thres, conf_thres):
404
  # wrap map_fn to avoid TypeSpec related error https://stackoverflow.com/a/65809989/3036450
405
+ return tf.map_fn(lambda x: self._nms(x, topk_all, iou_thres, conf_thres),
406
+ input,
407
  fn_output_signature=(tf.float32, tf.float32, tf.float32, tf.int32),
408
  name='agnostic_nms')
409
 
 
412
  boxes, classes, scores = x
413
  class_inds = tf.cast(tf.argmax(classes, axis=-1), tf.float32)
414
  scores_inp = tf.reduce_max(scores, -1)
415
+ selected_inds = tf.image.non_max_suppression(boxes,
416
+ scores_inp,
417
+ max_output_size=topk_all,
418
+ iou_threshold=iou_thres,
419
+ score_threshold=conf_thres)
420
  selected_boxes = tf.gather(boxes, selected_inds)
421
  padded_boxes = tf.pad(selected_boxes,
422
  paddings=[[0, topk_all - tf.shape(selected_boxes)[0]], [0, 0]],
423
+ mode="CONSTANT",
424
+ constant_values=0.0)
425
  selected_scores = tf.gather(scores_inp, selected_inds)
426
  padded_scores = tf.pad(selected_scores,
427
  paddings=[[0, topk_all - tf.shape(selected_boxes)[0]]],
428
+ mode="CONSTANT",
429
+ constant_values=-1.0)
430
  selected_classes = tf.gather(class_inds, selected_inds)
431
  padded_classes = tf.pad(selected_classes,
432
  paddings=[[0, topk_all - tf.shape(selected_boxes)[0]]],
433
+ mode="CONSTANT",
434
+ constant_values=-1.0)
435
  valid_detections = tf.shape(selected_inds)[0]
436
  return padded_boxes, padded_scores, padded_classes, valid_detections
437
 
 
447
  break
448
 
449
 
450
+ def run(
451
+ weights=ROOT / 'yolov5s.pt', # weights path
452
  imgsz=(640, 640), # inference size h,w
453
  batch_size=1, # batch size
454
  dynamic=False, # dynamic batch size
455
+ ):
456
  # PyTorch model
457
  im = torch.zeros((batch_size, 3, *imgsz)) # BCHW image
458
  model = attempt_load(weights, map_location=torch.device('cpu'), inplace=True, fuse=False)
models/yolo.py CHANGED
@@ -260,8 +260,8 @@ def parse_model(d, ch): # model_dict, input_channels(3)
260
  pass
261
 
262
  n = n_ = max(round(n * gd), 1) if n > 1 else n # depth gain
263
- if m in [Conv, GhostConv, Bottleneck, GhostBottleneck, SPP, SPPF, DWConv, MixConv2d, Focus, CrossConv,
264
- BottleneckCSP, C3, C3TR, C3SPP, C3Ghost]:
265
  c1, c2 = ch[f], args[0]
266
  if c2 != no: # if not output
267
  c2 = make_divisible(c2 * gw, 8)
 
260
  pass
261
 
262
  n = n_ = max(round(n * gd), 1) if n > 1 else n # depth gain
263
+ if m in (Conv, GhostConv, Bottleneck, GhostBottleneck, SPP, SPPF, DWConv, MixConv2d, Focus, CrossConv,
264
+ BottleneckCSP, C3, C3TR, C3SPP, C3Ghost):
265
  c1, c2 = ch[f], args[0]
266
  if c2 != no: # if not output
267
  c2 = make_divisible(c2 * gw, 8)
setup.cfg CHANGED
@@ -1,5 +1,6 @@
1
  # Project-wide configuration file, can be used for package metadata and other toll configurations
2
  # Example usage: global configuration for PEP8 (via flake8) setting or default pytest arguments
 
3
 
4
  [metadata]
5
  license_file = LICENSE
@@ -42,4 +43,17 @@ ignore =
42
  [isort]
43
  # https://pycqa.github.io/isort/docs/configuration/options.html
44
  line_length = 120
 
45
  multi_line_output = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # Project-wide configuration file, can be used for package metadata and other toll configurations
2
  # Example usage: global configuration for PEP8 (via flake8) setting or default pytest arguments
3
+ # Local usage: pip install pre-commit, pre-commit run --all-files
4
 
5
  [metadata]
6
  license_file = LICENSE
 
43
  [isort]
44
  # https://pycqa.github.io/isort/docs/configuration/options.html
45
  line_length = 120
46
+ # see: https://pycqa.github.io/isort/docs/configuration/multi_line_output_modes.html
47
  multi_line_output = 0
48
+
49
+
50
+ [yapf]
51
+ based_on_style = pep8
52
+ spaces_before_comment = 2
53
+ COLUMN_LIMIT = 120
54
+ COALESCE_BRACKETS = True
55
+ SPACES_AROUND_POWER_OPERATOR = True
56
+ SPACE_BETWEEN_ENDING_COMMA_AND_CLOSING_BRACKET = False
57
+ SPLIT_BEFORE_CLOSING_BRACKET = False
58
+ SPLIT_BEFORE_FIRST_ARGUMENT = False
59
+ # EACH_DICT_ENTRY_ON_SEPARATE_LINE = False
train.py CHANGED
@@ -62,11 +62,7 @@ RANK = int(os.getenv('RANK', -1))
62
  WORLD_SIZE = int(os.getenv('WORLD_SIZE', 1))
63
 
64
 
65
- def train(hyp, # path/to/hyp.yaml or hyp dictionary
66
- opt,
67
- device,
68
- callbacks
69
- ):
70
  save_dir, epochs, batch_size, weights, single_cls, evolve, data, cfg, resume, noval, nosave, workers, freeze = \
71
  Path(opt.save_dir), opt.epochs, opt.batch_size, opt.weights, opt.single_cls, opt.evolve, opt.data, opt.cfg, \
72
  opt.resume, opt.noval, opt.nosave, opt.workers, opt.freeze
@@ -220,20 +216,38 @@ def train(hyp, # path/to/hyp.yaml or hyp dictionary
220
  LOGGER.info('Using SyncBatchNorm()')
221
 
222
  # Trainloader
223
- train_loader, dataset = create_dataloader(train_path, imgsz, batch_size // WORLD_SIZE, gs, single_cls,
224
- hyp=hyp, augment=True, cache=None if opt.cache == 'val' else opt.cache,
225
- rect=opt.rect, rank=LOCAL_RANK, workers=workers,
226
- image_weights=opt.image_weights, quad=opt.quad,
227
- prefix=colorstr('train: '), shuffle=True)
 
 
 
 
 
 
 
 
 
 
228
  mlc = int(np.concatenate(dataset.labels, 0)[:, 0].max()) # max label class
229
  nb = len(train_loader) # number of batches
230
  assert mlc < nc, f'Label class {mlc} exceeds nc={nc} in {data}. Possible class labels are 0-{nc - 1}'
231
 
232
  # Process 0
233
  if RANK in [-1, 0]:
234
- val_loader = create_dataloader(val_path, imgsz, batch_size // WORLD_SIZE * 2, gs, single_cls,
235
- hyp=hyp, cache=None if noval else opt.cache,
236
- rect=True, rank=-1, workers=workers * 2, pad=0.5,
 
 
 
 
 
 
 
 
237
  prefix=colorstr('val: '))[0]
238
 
239
  if not resume:
@@ -350,8 +364,8 @@ def train(hyp, # path/to/hyp.yaml or hyp dictionary
350
  if RANK in [-1, 0]:
351
  mloss = (mloss * i + loss_items) / (i + 1) # update mean losses
352
  mem = f'{torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0:.3g}G' # (GB)
353
- pbar.set_description(('%10s' * 2 + '%10.4g' * 5) % (
354
- f'{epoch}/{epochs - 1}', mem, *mloss, targets.shape[0], imgs.shape[-1]))
355
  callbacks.run('on_train_batch_end', ni, model, imgs, targets, paths, plots, opt.sync_bn)
356
  if callbacks.stop_training:
357
  return
@@ -387,14 +401,15 @@ def train(hyp, # path/to/hyp.yaml or hyp dictionary
387
 
388
  # Save model
389
  if (not nosave) or (final_epoch and not evolve): # if save
390
- ckpt = {'epoch': epoch,
391
- 'best_fitness': best_fitness,
392
- 'model': deepcopy(de_parallel(model)).half(),
393
- 'ema': deepcopy(ema.ema).half(),
394
- 'updates': ema.updates,
395
- 'optimizer': optimizer.state_dict(),
396
- 'wandb_id': loggers.wandb.wandb_run.id if loggers.wandb else None,
397
- 'date': datetime.now().isoformat()}
 
398
 
399
  # Save last, best and delete
400
  torch.save(ckpt, last)
@@ -428,19 +443,20 @@ def train(hyp, # path/to/hyp.yaml or hyp dictionary
428
  strip_optimizer(f) # strip optimizers
429
  if f is best:
430
  LOGGER.info(f'\nValidating {f}...')
431
- results, _, _ = val.run(data_dict,
432
- batch_size=batch_size // WORLD_SIZE * 2,
433
- imgsz=imgsz,
434
- model=attempt_load(f, device).half(),
435
- iou_thres=0.65 if is_coco else 0.60, # best pycocotools results at 0.65
436
- single_cls=single_cls,
437
- dataloader=val_loader,
438
- save_dir=save_dir,
439
- save_json=is_coco,
440
- verbose=True,
441
- plots=True,
442
- callbacks=callbacks,
443
- compute_loss=compute_loss) # val best model with plots
 
444
  if is_coco:
445
  callbacks.run('on_fit_epoch_end', list(mloss) + list(results) + lr, epoch, best_fitness, fi)
446
 
@@ -546,35 +562,36 @@ def main(opt, callbacks=Callbacks()):
546
  # Evolve hyperparameters (optional)
547
  else:
548
  # Hyperparameter evolution metadata (mutation scale 0-1, lower_limit, upper_limit)
549
- meta = {'lr0': (1, 1e-5, 1e-1), # initial learning rate (SGD=1E-2, Adam=1E-3)
550
- 'lrf': (1, 0.01, 1.0), # final OneCycleLR learning rate (lr0 * lrf)
551
- 'momentum': (0.3, 0.6, 0.98), # SGD momentum/Adam beta1
552
- 'weight_decay': (1, 0.0, 0.001), # optimizer weight decay
553
- 'warmup_epochs': (1, 0.0, 5.0), # warmup epochs (fractions ok)
554
- 'warmup_momentum': (1, 0.0, 0.95), # warmup initial momentum
555
- 'warmup_bias_lr': (1, 0.0, 0.2), # warmup initial bias lr
556
- 'box': (1, 0.02, 0.2), # box loss gain
557
- 'cls': (1, 0.2, 4.0), # cls loss gain
558
- 'cls_pw': (1, 0.5, 2.0), # cls BCELoss positive_weight
559
- 'obj': (1, 0.2, 4.0), # obj loss gain (scale with pixels)
560
- 'obj_pw': (1, 0.5, 2.0), # obj BCELoss positive_weight
561
- 'iou_t': (0, 0.1, 0.7), # IoU training threshold
562
- 'anchor_t': (1, 2.0, 8.0), # anchor-multiple threshold
563
- 'anchors': (2, 2.0, 10.0), # anchors per output grid (0 to ignore)
564
- 'fl_gamma': (0, 0.0, 2.0), # focal loss gamma (efficientDet default gamma=1.5)
565
- 'hsv_h': (1, 0.0, 0.1), # image HSV-Hue augmentation (fraction)
566
- 'hsv_s': (1, 0.0, 0.9), # image HSV-Saturation augmentation (fraction)
567
- 'hsv_v': (1, 0.0, 0.9), # image HSV-Value augmentation (fraction)
568
- 'degrees': (1, 0.0, 45.0), # image rotation (+/- deg)
569
- 'translate': (1, 0.0, 0.9), # image translation (+/- fraction)
570
- 'scale': (1, 0.0, 0.9), # image scale (+/- gain)
571
- 'shear': (1, 0.0, 10.0), # image shear (+/- deg)
572
- 'perspective': (0, 0.0, 0.001), # image perspective (+/- fraction), range 0-0.001
573
- 'flipud': (1, 0.0, 1.0), # image flip up-down (probability)
574
- 'fliplr': (0, 0.0, 1.0), # image flip left-right (probability)
575
- 'mosaic': (1, 0.0, 1.0), # image mixup (probability)
576
- 'mixup': (1, 0.0, 1.0), # image mixup (probability)
577
- 'copy_paste': (1, 0.0, 1.0)} # segment copy-paste (probability)
 
578
 
579
  with open(opt.hyp, errors='ignore') as f:
580
  hyp = yaml.safe_load(f) # load hyps dict
 
62
  WORLD_SIZE = int(os.getenv('WORLD_SIZE', 1))
63
 
64
 
65
+ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictionary
 
 
 
 
66
  save_dir, epochs, batch_size, weights, single_cls, evolve, data, cfg, resume, noval, nosave, workers, freeze = \
67
  Path(opt.save_dir), opt.epochs, opt.batch_size, opt.weights, opt.single_cls, opt.evolve, opt.data, opt.cfg, \
68
  opt.resume, opt.noval, opt.nosave, opt.workers, opt.freeze
 
216
  LOGGER.info('Using SyncBatchNorm()')
217
 
218
  # Trainloader
219
+ train_loader, dataset = create_dataloader(train_path,
220
+ imgsz,
221
+ batch_size // WORLD_SIZE,
222
+ gs,
223
+ single_cls,
224
+ hyp=hyp,
225
+ augment=True,
226
+ cache=None if opt.cache == 'val' else opt.cache,
227
+ rect=opt.rect,
228
+ rank=LOCAL_RANK,
229
+ workers=workers,
230
+ image_weights=opt.image_weights,
231
+ quad=opt.quad,
232
+ prefix=colorstr('train: '),
233
+ shuffle=True)
234
  mlc = int(np.concatenate(dataset.labels, 0)[:, 0].max()) # max label class
235
  nb = len(train_loader) # number of batches
236
  assert mlc < nc, f'Label class {mlc} exceeds nc={nc} in {data}. Possible class labels are 0-{nc - 1}'
237
 
238
  # Process 0
239
  if RANK in [-1, 0]:
240
+ val_loader = create_dataloader(val_path,
241
+ imgsz,
242
+ batch_size // WORLD_SIZE * 2,
243
+ gs,
244
+ single_cls,
245
+ hyp=hyp,
246
+ cache=None if noval else opt.cache,
247
+ rect=True,
248
+ rank=-1,
249
+ workers=workers * 2,
250
+ pad=0.5,
251
  prefix=colorstr('val: '))[0]
252
 
253
  if not resume:
 
364
  if RANK in [-1, 0]:
365
  mloss = (mloss * i + loss_items) / (i + 1) # update mean losses
366
  mem = f'{torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0:.3g}G' # (GB)
367
+ pbar.set_description(('%10s' * 2 + '%10.4g' * 5) %
368
+ (f'{epoch}/{epochs - 1}', mem, *mloss, targets.shape[0], imgs.shape[-1]))
369
  callbacks.run('on_train_batch_end', ni, model, imgs, targets, paths, plots, opt.sync_bn)
370
  if callbacks.stop_training:
371
  return
 
401
 
402
  # Save model
403
  if (not nosave) or (final_epoch and not evolve): # if save
404
+ ckpt = {
405
+ 'epoch': epoch,
406
+ 'best_fitness': best_fitness,
407
+ 'model': deepcopy(de_parallel(model)).half(),
408
+ 'ema': deepcopy(ema.ema).half(),
409
+ 'updates': ema.updates,
410
+ 'optimizer': optimizer.state_dict(),
411
+ 'wandb_id': loggers.wandb.wandb_run.id if loggers.wandb else None,
412
+ 'date': datetime.now().isoformat()}
413
 
414
  # Save last, best and delete
415
  torch.save(ckpt, last)
 
443
  strip_optimizer(f) # strip optimizers
444
  if f is best:
445
  LOGGER.info(f'\nValidating {f}...')
446
+ results, _, _ = val.run(
447
+ data_dict,
448
+ batch_size=batch_size // WORLD_SIZE * 2,
449
+ imgsz=imgsz,
450
+ model=attempt_load(f, device).half(),
451
+ iou_thres=0.65 if is_coco else 0.60, # best pycocotools results at 0.65
452
+ single_cls=single_cls,
453
+ dataloader=val_loader,
454
+ save_dir=save_dir,
455
+ save_json=is_coco,
456
+ verbose=True,
457
+ plots=True,
458
+ callbacks=callbacks,
459
+ compute_loss=compute_loss) # val best model with plots
460
  if is_coco:
461
  callbacks.run('on_fit_epoch_end', list(mloss) + list(results) + lr, epoch, best_fitness, fi)
462
 
 
562
  # Evolve hyperparameters (optional)
563
  else:
564
  # Hyperparameter evolution metadata (mutation scale 0-1, lower_limit, upper_limit)
565
+ meta = {
566
+ 'lr0': (1, 1e-5, 1e-1), # initial learning rate (SGD=1E-2, Adam=1E-3)
567
+ 'lrf': (1, 0.01, 1.0), # final OneCycleLR learning rate (lr0 * lrf)
568
+ 'momentum': (0.3, 0.6, 0.98), # SGD momentum/Adam beta1
569
+ 'weight_decay': (1, 0.0, 0.001), # optimizer weight decay
570
+ 'warmup_epochs': (1, 0.0, 5.0), # warmup epochs (fractions ok)
571
+ 'warmup_momentum': (1, 0.0, 0.95), # warmup initial momentum
572
+ 'warmup_bias_lr': (1, 0.0, 0.2), # warmup initial bias lr
573
+ 'box': (1, 0.02, 0.2), # box loss gain
574
+ 'cls': (1, 0.2, 4.0), # cls loss gain
575
+ 'cls_pw': (1, 0.5, 2.0), # cls BCELoss positive_weight
576
+ 'obj': (1, 0.2, 4.0), # obj loss gain (scale with pixels)
577
+ 'obj_pw': (1, 0.5, 2.0), # obj BCELoss positive_weight
578
+ 'iou_t': (0, 0.1, 0.7), # IoU training threshold
579
+ 'anchor_t': (1, 2.0, 8.0), # anchor-multiple threshold
580
+ 'anchors': (2, 2.0, 10.0), # anchors per output grid (0 to ignore)
581
+ 'fl_gamma': (0, 0.0, 2.0), # focal loss gamma (efficientDet default gamma=1.5)
582
+ 'hsv_h': (1, 0.0, 0.1), # image HSV-Hue augmentation (fraction)
583
+ 'hsv_s': (1, 0.0, 0.9), # image HSV-Saturation augmentation (fraction)
584
+ 'hsv_v': (1, 0.0, 0.9), # image HSV-Value augmentation (fraction)
585
+ 'degrees': (1, 0.0, 45.0), # image rotation (+/- deg)
586
+ 'translate': (1, 0.0, 0.9), # image translation (+/- fraction)
587
+ 'scale': (1, 0.0, 0.9), # image scale (+/- gain)
588
+ 'shear': (1, 0.0, 10.0), # image shear (+/- deg)
589
+ 'perspective': (0, 0.0, 0.001), # image perspective (+/- fraction), range 0-0.001
590
+ 'flipud': (1, 0.0, 1.0), # image flip up-down (probability)
591
+ 'fliplr': (0, 0.0, 1.0), # image flip left-right (probability)
592
+ 'mosaic': (1, 0.0, 1.0), # image mixup (probability)
593
+ 'mixup': (1, 0.0, 1.0), # image mixup (probability)
594
+ 'copy_paste': (1, 0.0, 1.0)} # segment copy-paste (probability)
595
 
596
  with open(opt.hyp, errors='ignore') as f:
597
  hyp = yaml.safe_load(f) # load hyps dict
utils/activations.py CHANGED
@@ -64,7 +64,6 @@ class AconC(nn.Module):
64
  AconC: (p1*x-p2*x) * sigmoid(beta*(p1*x-p2*x)) + p2*x, beta is a learnable parameter
65
  according to "Activate or Not: Learning Customized Activation" <https://arxiv.org/pdf/2009.04759.pdf>.
66
  """
67
-
68
  def __init__(self, c1):
69
  super().__init__()
70
  self.p1 = nn.Parameter(torch.randn(1, c1, 1, 1))
@@ -81,7 +80,6 @@ class MetaAconC(nn.Module):
81
  MetaAconC: (p1*x-p2*x) * sigmoid(beta*(p1*x-p2*x)) + p2*x, beta is generated by a small network
82
  according to "Activate or Not: Learning Customized Activation" <https://arxiv.org/pdf/2009.04759.pdf>.
83
  """
84
-
85
  def __init__(self, c1, k=1, s=1, r=16): # ch_in, kernel, stride, r
86
  super().__init__()
87
  c2 = max(r, c1 // r)
 
64
  AconC: (p1*x-p2*x) * sigmoid(beta*(p1*x-p2*x)) + p2*x, beta is a learnable parameter
65
  according to "Activate or Not: Learning Customized Activation" <https://arxiv.org/pdf/2009.04759.pdf>.
66
  """
 
67
  def __init__(self, c1):
68
  super().__init__()
69
  self.p1 = nn.Parameter(torch.randn(1, c1, 1, 1))
 
80
  MetaAconC: (p1*x-p2*x) * sigmoid(beta*(p1*x-p2*x)) + p2*x, beta is generated by a small network
81
  according to "Activate or Not: Learning Customized Activation" <https://arxiv.org/pdf/2009.04759.pdf>.
82
  """
 
83
  def __init__(self, c1, k=1, s=1, r=16): # ch_in, kernel, stride, r
84
  super().__init__()
85
  c2 = max(r, c1 // r)
utils/augmentations.py CHANGED
@@ -21,15 +21,15 @@ class Albumentations:
21
  import albumentations as A
22
  check_version(A.__version__, '1.0.3', hard=True) # version requirement
23
 
24
- self.transform = A.Compose([
25
  A.Blur(p=0.01),
26
  A.MedianBlur(p=0.01),
27
  A.ToGray(p=0.01),
28
  A.CLAHE(p=0.01),
29
  A.RandomBrightnessContrast(p=0.0),
30
  A.RandomGamma(p=0.0),
31
- A.ImageCompression(quality_lower=75, p=0.0)],
32
- bbox_params=A.BboxParams(format='yolo', label_fields=['class_labels']))
33
 
34
  LOGGER.info(colorstr('albumentations: ') + ', '.join(f'{x}' for x in self.transform.transforms if x.p))
35
  except ImportError: # package not installed, skip
@@ -121,7 +121,14 @@ def letterbox(im, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleF
121
  return im, ratio, (dw, dh)
122
 
123
 
124
- def random_perspective(im, targets=(), segments=(), degrees=10, translate=.1, scale=.1, shear=10, perspective=0.0,
 
 
 
 
 
 
 
125
  border=(0, 0)):
126
  # torchvision.transforms.RandomAffine(degrees=(-10, 10), translate=(0.1, 0.1), scale=(0.9, 1.1), shear=(-10, 10))
127
  # targets = [cls, xyxy]
 
21
  import albumentations as A
22
  check_version(A.__version__, '1.0.3', hard=True) # version requirement
23
 
24
+ T = [
25
  A.Blur(p=0.01),
26
  A.MedianBlur(p=0.01),
27
  A.ToGray(p=0.01),
28
  A.CLAHE(p=0.01),
29
  A.RandomBrightnessContrast(p=0.0),
30
  A.RandomGamma(p=0.0),
31
+ A.ImageCompression(quality_lower=75, p=0.0)] # transforms
32
+ self.transform = A.Compose(T, bbox_params=A.BboxParams(format='yolo', label_fields=['class_labels']))
33
 
34
  LOGGER.info(colorstr('albumentations: ') + ', '.join(f'{x}' for x in self.transform.transforms if x.p))
35
  except ImportError: # package not installed, skip
 
121
  return im, ratio, (dw, dh)
122
 
123
 
124
+ def random_perspective(im,
125
+ targets=(),
126
+ segments=(),
127
+ degrees=10,
128
+ translate=.1,
129
+ scale=.1,
130
+ shear=10,
131
+ perspective=0.0,
132
  border=(0, 0)):
133
  # torchvision.transforms.RandomAffine(degrees=(-10, 10), translate=(0.1, 0.1), scale=(0.9, 1.1), shear=(-10, 10))
134
  # targets = [cls, xyxy]
utils/benchmarks.py CHANGED
@@ -45,13 +45,14 @@ from utils.general import LOGGER, print_args
45
  from utils.torch_utils import select_device
46
 
47
 
48
- def run(weights=ROOT / 'yolov5s.pt', # weights path
 
49
  imgsz=640, # inference size (pixels)
50
  batch_size=1, # batch size
51
  data=ROOT / 'data/coco128.yaml', # dataset.yaml path
52
  device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu
53
  half=False, # use FP16 half-precision inference
54
- ):
55
  y, t = [], time.time()
56
  formats = export.export_formats()
57
  device = select_device(device)
 
45
  from utils.torch_utils import select_device
46
 
47
 
48
+ def run(
49
+ weights=ROOT / 'yolov5s.pt', # weights path
50
  imgsz=640, # inference size (pixels)
51
  batch_size=1, # batch size
52
  data=ROOT / 'data/coco128.yaml', # dataset.yaml path
53
  device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu
54
  half=False, # use FP16 half-precision inference
55
+ ):
56
  y, t = [], time.time()
57
  formats = export.export_formats()
58
  device = select_device(device)
utils/callbacks.py CHANGED
@@ -8,13 +8,11 @@ class Callbacks:
8
  """"
9
  Handles all registered callbacks for YOLOv5 Hooks
10
  """
11
-
12
  def __init__(self):
13
  # Define the available callbacks
14
  self._callbacks = {
15
  'on_pretrain_routine_start': [],
16
  'on_pretrain_routine_end': [],
17
-
18
  'on_train_start': [],
19
  'on_train_epoch_start': [],
20
  'on_train_batch_start': [],
@@ -22,19 +20,16 @@ class Callbacks:
22
  'on_before_zero_grad': [],
23
  'on_train_batch_end': [],
24
  'on_train_epoch_end': [],
25
-
26
  'on_val_start': [],
27
  'on_val_batch_start': [],
28
  'on_val_image_end': [],
29
  'on_val_batch_end': [],
30
  'on_val_end': [],
31
-
32
  'on_fit_epoch_end': [], # fit = train + val
33
  'on_model_save': [],
34
  'on_train_end': [],
35
  'on_params_update': [],
36
- 'teardown': [],
37
- }
38
  self.stop_training = False # set True to interrupt training
39
 
40
  def register_action(self, hook, name='', callback=None):
 
8
  """"
9
  Handles all registered callbacks for YOLOv5 Hooks
10
  """
 
11
  def __init__(self):
12
  # Define the available callbacks
13
  self._callbacks = {
14
  'on_pretrain_routine_start': [],
15
  'on_pretrain_routine_end': [],
 
16
  'on_train_start': [],
17
  'on_train_epoch_start': [],
18
  'on_train_batch_start': [],
 
20
  'on_before_zero_grad': [],
21
  'on_train_batch_end': [],
22
  'on_train_epoch_end': [],
 
23
  'on_val_start': [],
24
  'on_val_batch_start': [],
25
  'on_val_image_end': [],
26
  'on_val_batch_end': [],
27
  'on_val_end': [],
 
28
  'on_fit_epoch_end': [], # fit = train + val
29
  'on_model_save': [],
30
  'on_train_end': [],
31
  'on_params_update': [],
32
+ 'teardown': [],}
 
33
  self.stop_training = False # set True to interrupt training
34
 
35
  def register_action(self, hook, name='', callback=None):
utils/datasets.py CHANGED
@@ -77,14 +77,14 @@ def exif_transpose(image):
77
  exif = image.getexif()
78
  orientation = exif.get(0x0112, 1) # default 1
79
  if orientation > 1:
80
- method = {2: Image.FLIP_LEFT_RIGHT,
81
- 3: Image.ROTATE_180,
82
- 4: Image.FLIP_TOP_BOTTOM,
83
- 5: Image.TRANSPOSE,
84
- 6: Image.ROTATE_270,
85
- 7: Image.TRANSVERSE,
86
- 8: Image.ROTATE_90,
87
- }.get(orientation)
88
  if method is not None:
89
  image = image.transpose(method)
90
  del exif[0x0112]
@@ -92,22 +92,39 @@ def exif_transpose(image):
92
  return image
93
 
94
 
95
- def create_dataloader(path, imgsz, batch_size, stride, single_cls=False, hyp=None, augment=False, cache=False, pad=0.0,
96
- rect=False, rank=-1, workers=8, image_weights=False, quad=False, prefix='', shuffle=False):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  if rect and shuffle:
98
  LOGGER.warning('WARNING: --rect is incompatible with DataLoader shuffle, setting shuffle=False')
99
  shuffle = False
100
  with torch_distributed_zero_first(rank): # init dataset *.cache only once if DDP
101
- dataset = LoadImagesAndLabels(path, imgsz, batch_size,
102
- augment=augment, # augmentation
103
- hyp=hyp, # hyperparameters
104
- rect=rect, # rectangular batches
105
- cache_images=cache,
106
- single_cls=single_cls,
107
- stride=int(stride),
108
- pad=pad,
109
- image_weights=image_weights,
110
- prefix=prefix)
 
 
 
111
 
112
  batch_size = min(batch_size, len(dataset))
113
  nd = torch.cuda.device_count() # number of CUDA devices
@@ -128,7 +145,6 @@ class InfiniteDataLoader(dataloader.DataLoader):
128
 
129
  Uses same syntax as vanilla DataLoader
130
  """
131
-
132
  def __init__(self, *args, **kwargs):
133
  super().__init__(*args, **kwargs)
134
  object.__setattr__(self, 'batch_sampler', _RepeatSampler(self.batch_sampler))
@@ -148,7 +164,6 @@ class _RepeatSampler:
148
  Args:
149
  sampler (Sampler)
150
  """
151
-
152
  def __init__(self, sampler):
153
  self.sampler = sampler
154
 
@@ -380,8 +395,19 @@ class LoadImagesAndLabels(Dataset):
380
  # YOLOv5 train_loader/val_loader, loads images and labels for training and validation
381
  cache_version = 0.6 # dataset labels *.cache version
382
 
383
- def __init__(self, path, img_size=640, batch_size=16, augment=False, hyp=None, rect=False, image_weights=False,
384
- cache_images=False, single_cls=False, stride=32, pad=0.0, prefix=''):
 
 
 
 
 
 
 
 
 
 
 
385
  self.img_size = img_size
386
  self.augment = augment
387
  self.hyp = hyp
@@ -510,7 +536,9 @@ class LoadImagesAndLabels(Dataset):
510
  desc = f"{prefix}Scanning '{path.parent / path.stem}' images and labels..."
511
  with Pool(NUM_THREADS) as pool:
512
  pbar = tqdm(pool.imap(verify_image_label, zip(self.im_files, self.label_files, repeat(prefix))),
513
- desc=desc, total=len(self.im_files), bar_format=BAR_FORMAT)
 
 
514
  for im_file, lb, shape, segments, nm_f, nf_f, ne_f, nc_f, msg in pbar:
515
  nm += nm_f
516
  nf += nf_f
@@ -576,7 +604,8 @@ class LoadImagesAndLabels(Dataset):
576
  labels[:, 1:] = xywhn2xyxy(labels[:, 1:], ratio[0] * w, ratio[1] * h, padw=pad[0], padh=pad[1])
577
 
578
  if self.augment:
579
- img, labels = random_perspective(img, labels,
 
580
  degrees=hyp['degrees'],
581
  translate=hyp['translate'],
582
  scale=hyp['scale'],
@@ -633,8 +662,7 @@ class LoadImagesAndLabels(Dataset):
633
  h0, w0 = im.shape[:2] # orig hw
634
  r = self.img_size / max(h0, w0) # ratio
635
  if r != 1: # if sizes are not equal
636
- im = cv2.resize(im,
637
- (int(w0 * r), int(h0 * r)),
638
  interpolation=cv2.INTER_LINEAR if (self.augment or r > 1) else cv2.INTER_AREA)
639
  return im, (h0, w0), im.shape[:2] # im, hw_original, hw_resized
640
  else:
@@ -692,7 +720,9 @@ class LoadImagesAndLabels(Dataset):
692
 
693
  # Augment
694
  img4, labels4, segments4 = copy_paste(img4, labels4, segments4, p=self.hyp['copy_paste'])
695
- img4, labels4 = random_perspective(img4, labels4, segments4,
 
 
696
  degrees=self.hyp['degrees'],
697
  translate=self.hyp['translate'],
698
  scale=self.hyp['scale'],
@@ -766,7 +796,9 @@ class LoadImagesAndLabels(Dataset):
766
  # img9, labels9 = replicate(img9, labels9) # replicate
767
 
768
  # Augment
769
- img9, labels9 = random_perspective(img9, labels9, segments9,
 
 
770
  degrees=self.hyp['degrees'],
771
  translate=self.hyp['translate'],
772
  scale=self.hyp['scale'],
@@ -795,8 +827,8 @@ class LoadImagesAndLabels(Dataset):
795
  for i in range(n): # zidane torch.zeros(16,3,720,1280) # BCHW
796
  i *= 4
797
  if random.random() < 0.5:
798
- im = F.interpolate(img[i].unsqueeze(0).float(), scale_factor=2.0, mode='bilinear', align_corners=False)[
799
- 0].type(img[i].type())
800
  lb = label[i]
801
  else:
802
  im = torch.cat((torch.cat((img[i], img[i + 1]), 1), torch.cat((img[i + 2], img[i + 3]), 1)), 2)
@@ -946,7 +978,6 @@ def dataset_stats(path='coco128.yaml', autodownload=False, verbose=False, profil
946
  autodownload: Attempt to download dataset if not found locally
947
  verbose: Print stats dictionary
948
  """
949
-
950
  def round_labels(labels):
951
  # Update labels to integer class and 6 decimal place floats
952
  return [[int(c), *(round(x, 4) for x in points)] for c, *points in labels]
@@ -996,11 +1027,16 @@ def dataset_stats(path='coco128.yaml', autodownload=False, verbose=False, profil
996
  for label in tqdm(dataset.labels, total=dataset.n, desc='Statistics'):
997
  x.append(np.bincount(label[:, 0].astype(int), minlength=data['nc']))
998
  x = np.array(x) # shape(128x80)
999
- stats[split] = {'instance_stats': {'total': int(x.sum()), 'per_class': x.sum(0).tolist()},
1000
- 'image_stats': {'total': dataset.n, 'unlabelled': int(np.all(x == 0, 1).sum()),
1001
- 'per_class': (x > 0).sum(0).tolist()},
1002
- 'labels': [{str(Path(k).name): round_labels(v.tolist())} for k, v in
1003
- zip(dataset.im_files, dataset.labels)]}
 
 
 
 
 
1004
 
1005
  if hub:
1006
  im_dir = hub_dir / 'images'
 
77
  exif = image.getexif()
78
  orientation = exif.get(0x0112, 1) # default 1
79
  if orientation > 1:
80
+ method = {
81
+ 2: Image.FLIP_LEFT_RIGHT,
82
+ 3: Image.ROTATE_180,
83
+ 4: Image.FLIP_TOP_BOTTOM,
84
+ 5: Image.TRANSPOSE,
85
+ 6: Image.ROTATE_270,
86
+ 7: Image.TRANSVERSE,
87
+ 8: Image.ROTATE_90,}.get(orientation)
88
  if method is not None:
89
  image = image.transpose(method)
90
  del exif[0x0112]
 
92
  return image
93
 
94
 
95
+ def create_dataloader(path,
96
+ imgsz,
97
+ batch_size,
98
+ stride,
99
+ single_cls=False,
100
+ hyp=None,
101
+ augment=False,
102
+ cache=False,
103
+ pad=0.0,
104
+ rect=False,
105
+ rank=-1,
106
+ workers=8,
107
+ image_weights=False,
108
+ quad=False,
109
+ prefix='',
110
+ shuffle=False):
111
  if rect and shuffle:
112
  LOGGER.warning('WARNING: --rect is incompatible with DataLoader shuffle, setting shuffle=False')
113
  shuffle = False
114
  with torch_distributed_zero_first(rank): # init dataset *.cache only once if DDP
115
+ dataset = LoadImagesAndLabels(
116
+ path,
117
+ imgsz,
118
+ batch_size,
119
+ augment=augment, # augmentation
120
+ hyp=hyp, # hyperparameters
121
+ rect=rect, # rectangular batches
122
+ cache_images=cache,
123
+ single_cls=single_cls,
124
+ stride=int(stride),
125
+ pad=pad,
126
+ image_weights=image_weights,
127
+ prefix=prefix)
128
 
129
  batch_size = min(batch_size, len(dataset))
130
  nd = torch.cuda.device_count() # number of CUDA devices
 
145
 
146
  Uses same syntax as vanilla DataLoader
147
  """
 
148
  def __init__(self, *args, **kwargs):
149
  super().__init__(*args, **kwargs)
150
  object.__setattr__(self, 'batch_sampler', _RepeatSampler(self.batch_sampler))
 
164
  Args:
165
  sampler (Sampler)
166
  """
 
167
  def __init__(self, sampler):
168
  self.sampler = sampler
169
 
 
395
  # YOLOv5 train_loader/val_loader, loads images and labels for training and validation
396
  cache_version = 0.6 # dataset labels *.cache version
397
 
398
+ def __init__(self,
399
+ path,
400
+ img_size=640,
401
+ batch_size=16,
402
+ augment=False,
403
+ hyp=None,
404
+ rect=False,
405
+ image_weights=False,
406
+ cache_images=False,
407
+ single_cls=False,
408
+ stride=32,
409
+ pad=0.0,
410
+ prefix=''):
411
  self.img_size = img_size
412
  self.augment = augment
413
  self.hyp = hyp
 
536
  desc = f"{prefix}Scanning '{path.parent / path.stem}' images and labels..."
537
  with Pool(NUM_THREADS) as pool:
538
  pbar = tqdm(pool.imap(verify_image_label, zip(self.im_files, self.label_files, repeat(prefix))),
539
+ desc=desc,
540
+ total=len(self.im_files),
541
+ bar_format=BAR_FORMAT)
542
  for im_file, lb, shape, segments, nm_f, nf_f, ne_f, nc_f, msg in pbar:
543
  nm += nm_f
544
  nf += nf_f
 
604
  labels[:, 1:] = xywhn2xyxy(labels[:, 1:], ratio[0] * w, ratio[1] * h, padw=pad[0], padh=pad[1])
605
 
606
  if self.augment:
607
+ img, labels = random_perspective(img,
608
+ labels,
609
  degrees=hyp['degrees'],
610
  translate=hyp['translate'],
611
  scale=hyp['scale'],
 
662
  h0, w0 = im.shape[:2] # orig hw
663
  r = self.img_size / max(h0, w0) # ratio
664
  if r != 1: # if sizes are not equal
665
+ im = cv2.resize(im, (int(w0 * r), int(h0 * r)),
 
666
  interpolation=cv2.INTER_LINEAR if (self.augment or r > 1) else cv2.INTER_AREA)
667
  return im, (h0, w0), im.shape[:2] # im, hw_original, hw_resized
668
  else:
 
720
 
721
  # Augment
722
  img4, labels4, segments4 = copy_paste(img4, labels4, segments4, p=self.hyp['copy_paste'])
723
+ img4, labels4 = random_perspective(img4,
724
+ labels4,
725
+ segments4,
726
  degrees=self.hyp['degrees'],
727
  translate=self.hyp['translate'],
728
  scale=self.hyp['scale'],
 
796
  # img9, labels9 = replicate(img9, labels9) # replicate
797
 
798
  # Augment
799
+ img9, labels9 = random_perspective(img9,
800
+ labels9,
801
+ segments9,
802
  degrees=self.hyp['degrees'],
803
  translate=self.hyp['translate'],
804
  scale=self.hyp['scale'],
 
827
  for i in range(n): # zidane torch.zeros(16,3,720,1280) # BCHW
828
  i *= 4
829
  if random.random() < 0.5:
830
+ im = F.interpolate(img[i].unsqueeze(0).float(), scale_factor=2.0, mode='bilinear',
831
+ align_corners=False)[0].type(img[i].type())
832
  lb = label[i]
833
  else:
834
  im = torch.cat((torch.cat((img[i], img[i + 1]), 1), torch.cat((img[i + 2], img[i + 3]), 1)), 2)
 
978
  autodownload: Attempt to download dataset if not found locally
979
  verbose: Print stats dictionary
980
  """
 
981
  def round_labels(labels):
982
  # Update labels to integer class and 6 decimal place floats
983
  return [[int(c), *(round(x, 4) for x in points)] for c, *points in labels]
 
1027
  for label in tqdm(dataset.labels, total=dataset.n, desc='Statistics'):
1028
  x.append(np.bincount(label[:, 0].astype(int), minlength=data['nc']))
1029
  x = np.array(x) # shape(128x80)
1030
+ stats[split] = {
1031
+ 'instance_stats': {
1032
+ 'total': int(x.sum()),
1033
+ 'per_class': x.sum(0).tolist()},
1034
+ 'image_stats': {
1035
+ 'total': dataset.n,
1036
+ 'unlabelled': int(np.all(x == 0, 1).sum()),
1037
+ 'per_class': (x > 0).sum(0).tolist()},
1038
+ 'labels': [{
1039
+ str(Path(k).name): round_labels(v.tolist())} for k, v in zip(dataset.im_files, dataset.labels)]}
1040
 
1041
  if hub:
1042
  im_dir = hub_dir / 'images'
utils/downloads.py CHANGED
@@ -63,19 +63,21 @@ def attempt_download(file, repo='ultralytics/yolov5'): # from utils.downloads i
63
  assets = [x['name'] for x in response['assets']] # release assets, i.e. ['yolov5s.pt', 'yolov5m.pt', ...]
64
  tag = response['tag_name'] # i.e. 'v1.0'
65
  except Exception: # fallback plan
66
- assets = ['yolov5n.pt', 'yolov5s.pt', 'yolov5m.pt', 'yolov5l.pt', 'yolov5x.pt',
67
- 'yolov5n6.pt', 'yolov5s6.pt', 'yolov5m6.pt', 'yolov5l6.pt', 'yolov5x6.pt']
 
68
  try:
69
  tag = subprocess.check_output('git tag', shell=True, stderr=subprocess.STDOUT).decode().split()[-1]
70
  except Exception:
71
  tag = 'v6.0' # current release
72
 
73
  if name in assets:
74
- safe_download(file,
75
- url=f'https://github.com/{repo}/releases/download/{tag}/{name}',
76
- # url2=f'https://storage.googleapis.com/{repo}/ckpt/{name}', # backup url (optional)
77
- min_bytes=1E5,
78
- error_msg=f'{file} missing, try downloading from https://github.com/{repo}/releases/')
 
79
 
80
  return str(file)
81
 
@@ -122,6 +124,7 @@ def get_token(cookie="./cookie"):
122
  return line.split()[-1]
123
  return ""
124
 
 
125
  # Google utils: https://cloud.google.com/storage/docs/reference/libraries ----------------------------------------------
126
  #
127
  #
 
63
  assets = [x['name'] for x in response['assets']] # release assets, i.e. ['yolov5s.pt', 'yolov5m.pt', ...]
64
  tag = response['tag_name'] # i.e. 'v1.0'
65
  except Exception: # fallback plan
66
+ assets = [
67
+ 'yolov5n.pt', 'yolov5s.pt', 'yolov5m.pt', 'yolov5l.pt', 'yolov5x.pt', 'yolov5n6.pt', 'yolov5s6.pt',
68
+ 'yolov5m6.pt', 'yolov5l6.pt', 'yolov5x6.pt']
69
  try:
70
  tag = subprocess.check_output('git tag', shell=True, stderr=subprocess.STDOUT).decode().split()[-1]
71
  except Exception:
72
  tag = 'v6.0' # current release
73
 
74
  if name in assets:
75
+ safe_download(
76
+ file,
77
+ url=f'https://github.com/{repo}/releases/download/{tag}/{name}',
78
+ # url2=f'https://storage.googleapis.com/{repo}/ckpt/{name}', # backup url (optional)
79
+ min_bytes=1E5,
80
+ error_msg=f'{file} missing, try downloading from https://github.com/{repo}/releases/')
81
 
82
  return str(file)
83
 
 
124
  return line.split()[-1]
125
  return ""
126
 
127
+
128
  # Google utils: https://cloud.google.com/storage/docs/reference/libraries ----------------------------------------------
129
  #
130
  #
utils/general.py CHANGED
@@ -536,25 +536,26 @@ def one_cycle(y1=0.0, y2=1.0, steps=100):
536
  def colorstr(*input):
537
  # Colors a string https://en.wikipedia.org/wiki/ANSI_escape_code, i.e. colorstr('blue', 'hello world')
538
  *args, string = input if len(input) > 1 else ('blue', 'bold', input[0]) # color arguments, string
539
- colors = {'black': '\033[30m', # basic colors
540
- 'red': '\033[31m',
541
- 'green': '\033[32m',
542
- 'yellow': '\033[33m',
543
- 'blue': '\033[34m',
544
- 'magenta': '\033[35m',
545
- 'cyan': '\033[36m',
546
- 'white': '\033[37m',
547
- 'bright_black': '\033[90m', # bright colors
548
- 'bright_red': '\033[91m',
549
- 'bright_green': '\033[92m',
550
- 'bright_yellow': '\033[93m',
551
- 'bright_blue': '\033[94m',
552
- 'bright_magenta': '\033[95m',
553
- 'bright_cyan': '\033[96m',
554
- 'bright_white': '\033[97m',
555
- 'end': '\033[0m', # misc
556
- 'bold': '\033[1m',
557
- 'underline': '\033[4m'}
 
558
  return ''.join(colors[x] for x in args) + f'{string}' + colors['end']
559
 
560
 
@@ -591,9 +592,10 @@ def coco80_to_coco91_class(): # converts 80-index (val2014) to 91-index (paper)
591
  # b = np.loadtxt('data/coco_paper.names', dtype='str', delimiter='\n')
592
  # x1 = [list(a[i] == b).index(True) + 1 for i in range(80)] # darknet to coco
593
  # x2 = [list(b[i] == a).index(True) if any(b[i] == a) else None for i in range(91)] # coco to darknet
594
- x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 27, 28, 31, 32, 33, 34,
595
- 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
596
- 64, 65, 67, 70, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 89, 90]
 
597
  return x
598
 
599
 
@@ -701,8 +703,14 @@ def clip_coords(boxes, shape):
701
  boxes[:, [1, 3]] = boxes[:, [1, 3]].clip(0, shape[0]) # y1, y2
702
 
703
 
704
- def non_max_suppression(prediction, conf_thres=0.25, iou_thres=0.45, classes=None, agnostic=False, multi_label=False,
705
- labels=(), max_det=300):
 
 
 
 
 
 
706
  """Non-Maximum Suppression (NMS) on inference results to reject overlapping bounding boxes
707
 
708
  Returns:
@@ -816,8 +824,8 @@ def strip_optimizer(f='best.pt', s=''): # from utils.general import *; strip_op
816
  def print_mutation(results, hyp, save_dir, bucket, prefix=colorstr('evolve: ')):
817
  evolve_csv = save_dir / 'evolve.csv'
818
  evolve_yaml = save_dir / 'hyp_evolve.yaml'
819
- keys = ('metrics/precision', 'metrics/recall', 'metrics/mAP_0.5', 'metrics/mAP_0.5:0.95',
820
- 'val/box_loss', 'val/obj_loss', 'val/cls_loss') + tuple(hyp.keys()) # [results + hyps]
821
  keys = tuple(x.strip() for x in keys)
822
  vals = results + tuple(hyp.values())
823
  n = len(keys)
@@ -839,17 +847,15 @@ def print_mutation(results, hyp, save_dir, bucket, prefix=colorstr('evolve: ')):
839
  data = data.rename(columns=lambda x: x.strip()) # strip keys
840
  i = np.argmax(fitness(data.values[:, :4])) #
841
  generations = len(data)
842
- f.write('# YOLOv5 Hyperparameter Evolution Results\n' +
843
- f'# Best generation: {i}\n' +
844
- f'# Last generation: {generations - 1}\n' +
845
- '# ' + ', '.join(f'{x.strip():>20s}' for x in keys[:7]) + '\n' +
846
- '# ' + ', '.join(f'{x:>20.5g}' for x in data.values[i, :7]) + '\n\n')
847
  yaml.safe_dump(data.loc[i][7:].to_dict(), f, sort_keys=False)
848
 
849
  # Print to screen
850
- LOGGER.info(prefix + f'{generations} generations finished, current result:\n' +
851
- prefix + ', '.join(f'{x.strip():>20s}' for x in keys) + '\n' +
852
- prefix + ', '.join(f'{x:20.5g}' for x in vals) + '\n\n')
853
 
854
  if bucket:
855
  os.system(f'gsutil cp {evolve_csv} {evolve_yaml} gs://{bucket}') # upload
 
536
  def colorstr(*input):
537
  # Colors a string https://en.wikipedia.org/wiki/ANSI_escape_code, i.e. colorstr('blue', 'hello world')
538
  *args, string = input if len(input) > 1 else ('blue', 'bold', input[0]) # color arguments, string
539
+ colors = {
540
+ 'black': '\033[30m', # basic colors
541
+ 'red': '\033[31m',
542
+ 'green': '\033[32m',
543
+ 'yellow': '\033[33m',
544
+ 'blue': '\033[34m',
545
+ 'magenta': '\033[35m',
546
+ 'cyan': '\033[36m',
547
+ 'white': '\033[37m',
548
+ 'bright_black': '\033[90m', # bright colors
549
+ 'bright_red': '\033[91m',
550
+ 'bright_green': '\033[92m',
551
+ 'bright_yellow': '\033[93m',
552
+ 'bright_blue': '\033[94m',
553
+ 'bright_magenta': '\033[95m',
554
+ 'bright_cyan': '\033[96m',
555
+ 'bright_white': '\033[97m',
556
+ 'end': '\033[0m', # misc
557
+ 'bold': '\033[1m',
558
+ 'underline': '\033[4m'}
559
  return ''.join(colors[x] for x in args) + f'{string}' + colors['end']
560
 
561
 
 
592
  # b = np.loadtxt('data/coco_paper.names', dtype='str', delimiter='\n')
593
  # x1 = [list(a[i] == b).index(True) + 1 for i in range(80)] # darknet to coco
594
  # x2 = [list(b[i] == a).index(True) if any(b[i] == a) else None for i in range(91)] # coco to darknet
595
+ x = [
596
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 27, 28, 31, 32, 33, 34,
597
+ 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
598
+ 64, 65, 67, 70, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 89, 90]
599
  return x
600
 
601
 
 
703
  boxes[:, [1, 3]] = boxes[:, [1, 3]].clip(0, shape[0]) # y1, y2
704
 
705
 
706
+ def non_max_suppression(prediction,
707
+ conf_thres=0.25,
708
+ iou_thres=0.45,
709
+ classes=None,
710
+ agnostic=False,
711
+ multi_label=False,
712
+ labels=(),
713
+ max_det=300):
714
  """Non-Maximum Suppression (NMS) on inference results to reject overlapping bounding boxes
715
 
716
  Returns:
 
824
  def print_mutation(results, hyp, save_dir, bucket, prefix=colorstr('evolve: ')):
825
  evolve_csv = save_dir / 'evolve.csv'
826
  evolve_yaml = save_dir / 'hyp_evolve.yaml'
827
+ keys = ('metrics/precision', 'metrics/recall', 'metrics/mAP_0.5', 'metrics/mAP_0.5:0.95', 'val/box_loss',
828
+ 'val/obj_loss', 'val/cls_loss') + tuple(hyp.keys()) # [results + hyps]
829
  keys = tuple(x.strip() for x in keys)
830
  vals = results + tuple(hyp.values())
831
  n = len(keys)
 
847
  data = data.rename(columns=lambda x: x.strip()) # strip keys
848
  i = np.argmax(fitness(data.values[:, :4])) #
849
  generations = len(data)
850
+ f.write('# YOLOv5 Hyperparameter Evolution Results\n' + f'# Best generation: {i}\n' +
851
+ f'# Last generation: {generations - 1}\n' + '# ' + ', '.join(f'{x.strip():>20s}' for x in keys[:7]) +
852
+ '\n' + '# ' + ', '.join(f'{x:>20.5g}' for x in data.values[i, :7]) + '\n\n')
 
 
853
  yaml.safe_dump(data.loc[i][7:].to_dict(), f, sort_keys=False)
854
 
855
  # Print to screen
856
+ LOGGER.info(prefix + f'{generations} generations finished, current result:\n' + prefix +
857
+ ', '.join(f'{x.strip():>20s}' for x in keys) + '\n' + prefix + ', '.join(f'{x:20.5g}'
858
+ for x in vals) + '\n\n')
859
 
860
  if bucket:
861
  os.system(f'gsutil cp {evolve_csv} {evolve_yaml} gs://{bucket}') # upload
utils/loggers/__init__.py CHANGED
@@ -43,10 +43,20 @@ class Loggers():
43
  self.hyp = hyp
44
  self.logger = logger # for printing results to console
45
  self.include = include
46
- self.keys = ['train/box_loss', 'train/obj_loss', 'train/cls_loss', # train loss
47
- 'metrics/precision', 'metrics/recall', 'metrics/mAP_0.5', 'metrics/mAP_0.5:0.95', # metrics
48
- 'val/box_loss', 'val/obj_loss', 'val/cls_loss', # val loss
49
- 'x/lr0', 'x/lr1', 'x/lr2'] # params
 
 
 
 
 
 
 
 
 
 
50
  self.best_keys = ['best/epoch', 'best/precision', 'best/recall', 'best/mAP_0.5', 'best/mAP_0.5:0.95']
51
  for k in LOGGERS:
52
  setattr(self, k, None) # init empty logger dictionary
@@ -155,7 +165,8 @@ class Loggers():
155
  self.wandb.log({"Results": [wandb.Image(str(f), caption=f.name) for f in files]})
156
  # Calling wandb.log. TODO: Refactor this into WandbLogger.log_model
157
  if not self.opt.evolve:
158
- wandb.log_artifact(str(best if best.exists() else last), type='model',
 
159
  name='run_' + self.wandb.wandb_run.id + '_model',
160
  aliases=['latest', 'best', 'stripped'])
161
  self.wandb.finish_run()
 
43
  self.hyp = hyp
44
  self.logger = logger # for printing results to console
45
  self.include = include
46
+ self.keys = [
47
+ 'train/box_loss',
48
+ 'train/obj_loss',
49
+ 'train/cls_loss', # train loss
50
+ 'metrics/precision',
51
+ 'metrics/recall',
52
+ 'metrics/mAP_0.5',
53
+ 'metrics/mAP_0.5:0.95', # metrics
54
+ 'val/box_loss',
55
+ 'val/obj_loss',
56
+ 'val/cls_loss', # val loss
57
+ 'x/lr0',
58
+ 'x/lr1',
59
+ 'x/lr2'] # params
60
  self.best_keys = ['best/epoch', 'best/precision', 'best/recall', 'best/mAP_0.5', 'best/mAP_0.5:0.95']
61
  for k in LOGGERS:
62
  setattr(self, k, None) # init empty logger dictionary
 
165
  self.wandb.log({"Results": [wandb.Image(str(f), caption=f.name) for f in files]})
166
  # Calling wandb.log. TODO: Refactor this into WandbLogger.log_model
167
  if not self.opt.evolve:
168
+ wandb.log_artifact(str(best if best.exists() else last),
169
+ type='model',
170
  name='run_' + self.wandb.wandb_run.id + '_model',
171
  aliases=['latest', 'best', 'stripped'])
172
  self.wandb.finish_run()
utils/loggers/wandb/wandb_utils.py CHANGED
@@ -46,10 +46,10 @@ def check_wandb_dataset(data_file):
46
  if check_file(data_file) and data_file.endswith('.yaml'):
47
  with open(data_file, errors='ignore') as f:
48
  data_dict = yaml.safe_load(f)
49
- is_trainset_wandb_artifact = (isinstance(data_dict['train'], str) and
50
- data_dict['train'].startswith(WANDB_ARTIFACT_PREFIX))
51
- is_valset_wandb_artifact = (isinstance(data_dict['val'], str) and
52
- data_dict['val'].startswith(WANDB_ARTIFACT_PREFIX))
53
  if is_trainset_wandb_artifact or is_valset_wandb_artifact:
54
  return data_dict
55
  else:
@@ -116,7 +116,6 @@ class WandbLogger():
116
  For more on how this logger is used, see the Weights & Biases documentation:
117
  https://docs.wandb.com/guides/integrations/yolov5
118
  """
119
-
120
  def __init__(self, opt, run_id=None, job_type='Training'):
121
  """
122
  - Initialize WandbLogger instance
@@ -181,8 +180,7 @@ class WandbLogger():
181
  self.wandb_artifact_data_dict = self.wandb_artifact_data_dict or self.data_dict
182
 
183
  # write data_dict to config. useful for resuming from artifacts. Do this only when not resuming.
184
- self.wandb_run.config.update({'data_dict': self.wandb_artifact_data_dict},
185
- allow_val_change=True)
186
  self.setup_training(opt)
187
 
188
  if self.job_type == 'Dataset Creation':
@@ -200,8 +198,7 @@ class WandbLogger():
200
  Updated dataset info dictionary where local dataset paths are replaced by WAND_ARFACT_PREFIX links.
201
  """
202
  assert wandb, 'Install wandb to upload dataset'
203
- config_path = self.log_dataset_artifact(opt.data,
204
- opt.single_cls,
205
  'YOLOv5' if opt.project == 'runs/train' else Path(opt.project).stem)
206
  with open(config_path, errors='ignore') as f:
207
  wandb_data_dict = yaml.safe_load(f)
@@ -230,10 +227,10 @@ class WandbLogger():
230
  config.hyp, config.imgsz
231
  data_dict = self.data_dict
232
  if self.val_artifact is None: # If --upload_dataset is set, use the existing artifact, don't download
233
- self.train_artifact_path, self.train_artifact = self.download_dataset_artifact(data_dict.get('train'),
234
- opt.artifact_alias)
235
- self.val_artifact_path, self.val_artifact = self.download_dataset_artifact(data_dict.get('val'),
236
- opt.artifact_alias)
237
 
238
  if self.train_artifact_path is not None:
239
  train_path = Path(self.train_artifact_path) / 'data/images/'
@@ -308,14 +305,15 @@ class WandbLogger():
308
  fitness_score (float) -- fitness score for current epoch
309
  best_model (boolean) -- Boolean representing if the current checkpoint is the best yet.
310
  """
311
- model_artifact = wandb.Artifact('run_' + wandb.run.id + '_model', type='model', metadata={
312
- 'original_url': str(path),
313
- 'epochs_trained': epoch + 1,
314
- 'save period': opt.save_period,
315
- 'project': opt.project,
316
- 'total_epochs': opt.epochs,
317
- 'fitness_score': fitness_score
318
- })
 
319
  model_artifact.add_file(str(path / 'last.pt'), name='last.pt')
320
  wandb.log_artifact(model_artifact,
321
  aliases=['latest', 'last', 'epoch ' + str(self.current_epoch), 'best' if best_model else ''])
@@ -344,13 +342,14 @@ class WandbLogger():
344
 
345
  # log train set
346
  if not log_val_only:
347
- self.train_artifact = self.create_dataset_table(LoadImagesAndLabels(
348
- data['train'], rect=True, batch_size=1), names, name='train') if data.get('train') else None
 
349
  if data.get('train'):
350
  data['train'] = WANDB_ARTIFACT_PREFIX + str(Path(project) / 'train')
351
 
352
- self.val_artifact = self.create_dataset_table(LoadImagesAndLabels(
353
- data['val'], rect=True, batch_size=1), names, name='val') if data.get('val') else None
354
  if data.get('val'):
355
  data['val'] = WANDB_ARTIFACT_PREFIX + str(Path(project) / 'val')
356
 
@@ -412,17 +411,21 @@ class WandbLogger():
412
  else:
413
  artifact.add_file(img_file, name='data/images/' + Path(img_file).name)
414
  label_file = Path(img2label_paths([img_file])[0])
415
- artifact.add_file(str(label_file),
416
- name='data/labels/' + label_file.name) if label_file.exists() else None
417
  table = wandb.Table(columns=["id", "train_image", "Classes", "name"])
418
  class_set = wandb.Classes([{'id': id, 'name': name} for id, name in class_to_id.items()])
419
  for si, (img, labels, paths, shapes) in enumerate(tqdm(dataset)):
420
  box_data, img_classes = [], {}
421
  for cls, *xywh in labels[:, 1:].tolist():
422
  cls = int(cls)
423
- box_data.append({"position": {"middle": [xywh[0], xywh[1]], "width": xywh[2], "height": xywh[3]},
424
- "class_id": cls,
425
- "box_caption": "%s" % (class_to_id[cls])})
 
 
 
 
426
  img_classes[cls] = class_to_id[cls]
427
  boxes = {"ground_truth": {"box_data": box_data, "class_labels": class_to_id}} # inference-space
428
  table.add_data(si, wandb.Image(paths, classes=class_set, boxes=boxes), list(img_classes.values()),
@@ -446,12 +449,17 @@ class WandbLogger():
446
  for *xyxy, conf, cls in predn.tolist():
447
  if conf >= 0.25:
448
  cls = int(cls)
449
- box_data.append(
450
- {"position": {"minX": xyxy[0], "minY": xyxy[1], "maxX": xyxy[2], "maxY": xyxy[3]},
451
- "class_id": cls,
452
- "box_caption": f"{names[cls]} {conf:.3f}",
453
- "scores": {"class_score": conf},
454
- "domain": "pixel"})
 
 
 
 
 
455
  avg_conf_per_class[cls] += conf
456
 
457
  if cls in pred_class_count:
@@ -464,12 +472,9 @@ class WandbLogger():
464
 
465
  boxes = {"predictions": {"box_data": box_data, "class_labels": names}} # inference-space
466
  id = self.val_table_path_map[Path(path).name]
467
- self.result_table.add_data(self.current_epoch,
468
- id,
469
- self.val_table.data[id][1],
470
  wandb.Image(self.val_table.data[id][1], boxes=boxes, classes=class_set),
471
- *avg_conf_per_class
472
- )
473
 
474
  def val_one_image(self, pred, predn, path, names, im):
475
  """
@@ -485,11 +490,17 @@ class WandbLogger():
485
 
486
  if len(self.bbox_media_panel_images) < self.max_imgs_to_log and self.current_epoch > 0:
487
  if self.current_epoch % self.bbox_interval == 0:
488
- box_data = [{"position": {"minX": xyxy[0], "minY": xyxy[1], "maxX": xyxy[2], "maxY": xyxy[3]},
489
- "class_id": int(cls),
490
- "box_caption": f"{names[int(cls)]} {conf:.3f}",
491
- "scores": {"class_score": conf},
492
- "domain": "pixel"} for *xyxy, conf, cls in pred.tolist()]
 
 
 
 
 
 
493
  boxes = {"predictions": {"box_data": box_data, "class_labels": names}} # inference-space
494
  self.bbox_media_panel_images.append(wandb.Image(im, boxes=boxes, caption=path.name))
495
 
@@ -519,7 +530,8 @@ class WandbLogger():
519
  wandb.log(self.log_dict)
520
  except BaseException as e:
521
  LOGGER.info(
522
- f"An error occurred in wandb logger. The training will proceed without interruption. More info\n{e}")
 
523
  self.wandb_run.finish()
524
  self.wandb_run = None
525
 
@@ -527,8 +539,10 @@ class WandbLogger():
527
  self.bbox_media_panel_images = []
528
  if self.result_artifact:
529
  self.result_artifact.add(self.result_table, 'result')
530
- wandb.log_artifact(self.result_artifact, aliases=['latest', 'last', 'epoch ' + str(self.current_epoch),
531
- ('best' if best_result else '')])
 
 
532
 
533
  wandb.log({"evaluation": self.result_table})
534
  columns = ["epoch", "id", "ground truth", "prediction"]
 
46
  if check_file(data_file) and data_file.endswith('.yaml'):
47
  with open(data_file, errors='ignore') as f:
48
  data_dict = yaml.safe_load(f)
49
+ is_trainset_wandb_artifact = isinstance(data_dict['train'],
50
+ str) and data_dict['train'].startswith(WANDB_ARTIFACT_PREFIX)
51
+ is_valset_wandb_artifact = isinstance(data_dict['val'],
52
+ str) and data_dict['val'].startswith(WANDB_ARTIFACT_PREFIX)
53
  if is_trainset_wandb_artifact or is_valset_wandb_artifact:
54
  return data_dict
55
  else:
 
116
  For more on how this logger is used, see the Weights & Biases documentation:
117
  https://docs.wandb.com/guides/integrations/yolov5
118
  """
 
119
  def __init__(self, opt, run_id=None, job_type='Training'):
120
  """
121
  - Initialize WandbLogger instance
 
180
  self.wandb_artifact_data_dict = self.wandb_artifact_data_dict or self.data_dict
181
 
182
  # write data_dict to config. useful for resuming from artifacts. Do this only when not resuming.
183
+ self.wandb_run.config.update({'data_dict': self.wandb_artifact_data_dict}, allow_val_change=True)
 
184
  self.setup_training(opt)
185
 
186
  if self.job_type == 'Dataset Creation':
 
198
  Updated dataset info dictionary where local dataset paths are replaced by WAND_ARFACT_PREFIX links.
199
  """
200
  assert wandb, 'Install wandb to upload dataset'
201
+ config_path = self.log_dataset_artifact(opt.data, opt.single_cls,
 
202
  'YOLOv5' if opt.project == 'runs/train' else Path(opt.project).stem)
203
  with open(config_path, errors='ignore') as f:
204
  wandb_data_dict = yaml.safe_load(f)
 
227
  config.hyp, config.imgsz
228
  data_dict = self.data_dict
229
  if self.val_artifact is None: # If --upload_dataset is set, use the existing artifact, don't download
230
+ self.train_artifact_path, self.train_artifact = self.download_dataset_artifact(
231
+ data_dict.get('train'), opt.artifact_alias)
232
+ self.val_artifact_path, self.val_artifact = self.download_dataset_artifact(
233
+ data_dict.get('val'), opt.artifact_alias)
234
 
235
  if self.train_artifact_path is not None:
236
  train_path = Path(self.train_artifact_path) / 'data/images/'
 
305
  fitness_score (float) -- fitness score for current epoch
306
  best_model (boolean) -- Boolean representing if the current checkpoint is the best yet.
307
  """
308
+ model_artifact = wandb.Artifact('run_' + wandb.run.id + '_model',
309
+ type='model',
310
+ metadata={
311
+ 'original_url': str(path),
312
+ 'epochs_trained': epoch + 1,
313
+ 'save period': opt.save_period,
314
+ 'project': opt.project,
315
+ 'total_epochs': opt.epochs,
316
+ 'fitness_score': fitness_score})
317
  model_artifact.add_file(str(path / 'last.pt'), name='last.pt')
318
  wandb.log_artifact(model_artifact,
319
  aliases=['latest', 'last', 'epoch ' + str(self.current_epoch), 'best' if best_model else ''])
 
342
 
343
  # log train set
344
  if not log_val_only:
345
+ self.train_artifact = self.create_dataset_table(LoadImagesAndLabels(data['train'], rect=True, batch_size=1),
346
+ names,
347
+ name='train') if data.get('train') else None
348
  if data.get('train'):
349
  data['train'] = WANDB_ARTIFACT_PREFIX + str(Path(project) / 'train')
350
 
351
+ self.val_artifact = self.create_dataset_table(
352
+ LoadImagesAndLabels(data['val'], rect=True, batch_size=1), names, name='val') if data.get('val') else None
353
  if data.get('val'):
354
  data['val'] = WANDB_ARTIFACT_PREFIX + str(Path(project) / 'val')
355
 
 
411
  else:
412
  artifact.add_file(img_file, name='data/images/' + Path(img_file).name)
413
  label_file = Path(img2label_paths([img_file])[0])
414
+ artifact.add_file(str(label_file), name='data/labels/' +
415
+ label_file.name) if label_file.exists() else None
416
  table = wandb.Table(columns=["id", "train_image", "Classes", "name"])
417
  class_set = wandb.Classes([{'id': id, 'name': name} for id, name in class_to_id.items()])
418
  for si, (img, labels, paths, shapes) in enumerate(tqdm(dataset)):
419
  box_data, img_classes = [], {}
420
  for cls, *xywh in labels[:, 1:].tolist():
421
  cls = int(cls)
422
+ box_data.append({
423
+ "position": {
424
+ "middle": [xywh[0], xywh[1]],
425
+ "width": xywh[2],
426
+ "height": xywh[3]},
427
+ "class_id": cls,
428
+ "box_caption": "%s" % (class_to_id[cls])})
429
  img_classes[cls] = class_to_id[cls]
430
  boxes = {"ground_truth": {"box_data": box_data, "class_labels": class_to_id}} # inference-space
431
  table.add_data(si, wandb.Image(paths, classes=class_set, boxes=boxes), list(img_classes.values()),
 
449
  for *xyxy, conf, cls in predn.tolist():
450
  if conf >= 0.25:
451
  cls = int(cls)
452
+ box_data.append({
453
+ "position": {
454
+ "minX": xyxy[0],
455
+ "minY": xyxy[1],
456
+ "maxX": xyxy[2],
457
+ "maxY": xyxy[3]},
458
+ "class_id": cls,
459
+ "box_caption": f"{names[cls]} {conf:.3f}",
460
+ "scores": {
461
+ "class_score": conf},
462
+ "domain": "pixel"})
463
  avg_conf_per_class[cls] += conf
464
 
465
  if cls in pred_class_count:
 
472
 
473
  boxes = {"predictions": {"box_data": box_data, "class_labels": names}} # inference-space
474
  id = self.val_table_path_map[Path(path).name]
475
+ self.result_table.add_data(self.current_epoch, id, self.val_table.data[id][1],
 
 
476
  wandb.Image(self.val_table.data[id][1], boxes=boxes, classes=class_set),
477
+ *avg_conf_per_class)
 
478
 
479
  def val_one_image(self, pred, predn, path, names, im):
480
  """
 
490
 
491
  if len(self.bbox_media_panel_images) < self.max_imgs_to_log and self.current_epoch > 0:
492
  if self.current_epoch % self.bbox_interval == 0:
493
+ box_data = [{
494
+ "position": {
495
+ "minX": xyxy[0],
496
+ "minY": xyxy[1],
497
+ "maxX": xyxy[2],
498
+ "maxY": xyxy[3]},
499
+ "class_id": int(cls),
500
+ "box_caption": f"{names[int(cls)]} {conf:.3f}",
501
+ "scores": {
502
+ "class_score": conf},
503
+ "domain": "pixel"} for *xyxy, conf, cls in pred.tolist()]
504
  boxes = {"predictions": {"box_data": box_data, "class_labels": names}} # inference-space
505
  self.bbox_media_panel_images.append(wandb.Image(im, boxes=boxes, caption=path.name))
506
 
 
530
  wandb.log(self.log_dict)
531
  except BaseException as e:
532
  LOGGER.info(
533
+ f"An error occurred in wandb logger. The training will proceed without interruption. More info\n{e}"
534
+ )
535
  self.wandb_run.finish()
536
  self.wandb_run = None
537
 
 
539
  self.bbox_media_panel_images = []
540
  if self.result_artifact:
541
  self.result_artifact.add(self.result_table, 'result')
542
+ wandb.log_artifact(self.result_artifact,
543
+ aliases=[
544
+ 'latest', 'last', 'epoch ' + str(self.current_epoch),
545
+ ('best' if best_result else '')])
546
 
547
  wandb.log({"evaluation": self.result_table})
548
  columns = ["epoch", "id", "ground truth", "prediction"]
utils/loss.py CHANGED
@@ -183,10 +183,16 @@ class ComputeLoss:
183
  targets = torch.cat((targets.repeat(na, 1, 1), ai[:, :, None]), 2) # append anchor indices
184
 
185
  g = 0.5 # bias
186
- off = torch.tensor([[0, 0],
187
- [1, 0], [0, 1], [-1, 0], [0, -1], # j,k,l,m
188
- # [1, 1], [1, -1], [-1, 1], [-1, -1], # jk,jm,lk,lm
189
- ], device=self.device).float() * g # offsets
 
 
 
 
 
 
190
 
191
  for i in range(self.nl):
192
  anchors = self.anchors[i]
 
183
  targets = torch.cat((targets.repeat(na, 1, 1), ai[:, :, None]), 2) # append anchor indices
184
 
185
  g = 0.5 # bias
186
+ off = torch.tensor(
187
+ [
188
+ [0, 0],
189
+ [1, 0],
190
+ [0, 1],
191
+ [-1, 0],
192
+ [0, -1], # j,k,l,m
193
+ # [1, 1], [1, -1], [-1, 1], [-1, -1], # jk,jm,lk,lm
194
+ ],
195
+ device=self.device).float() * g # offsets
196
 
197
  for i in range(self.nl):
198
  anchors = self.anchors[i]
utils/metrics.py CHANGED
@@ -184,7 +184,14 @@ class ConfusionMatrix:
184
  labels = (0 < nn < 99) and (nn == nc) # apply names to ticklabels
185
  with warnings.catch_warnings():
186
  warnings.simplefilter('ignore') # suppress empty matrix RuntimeWarning: All-NaN slice encountered
187
- sn.heatmap(array, annot=nc < 30, annot_kws={"size": 8}, cmap='Blues', fmt='.2f', square=True, vmin=0.0,
 
 
 
 
 
 
 
188
  xticklabels=names + ['background FP'] if labels else "auto",
189
  yticklabels=names + ['background FN'] if labels else "auto").set_facecolor((1, 1, 1))
190
  fig.axes[0].set_xlabel('True')
@@ -253,7 +260,6 @@ def box_iou(box1, box2):
253
  iou (Tensor[N, M]): the NxM matrix containing the pairwise
254
  IoU values for every element in boxes1 and boxes2
255
  """
256
-
257
  def box_area(box):
258
  # box = 4xn
259
  return (box[2] - box[0]) * (box[3] - box[1])
@@ -300,6 +306,7 @@ def wh_iou(wh1, wh2):
300
 
301
  # Plots ----------------------------------------------------------------------------------------------------------------
302
 
 
303
  def plot_pr_curve(px, py, ap, save_dir='pr_curve.png', names=()):
304
  # Precision-recall curve
305
  fig, ax = plt.subplots(1, 1, figsize=(9, 6), tight_layout=True)
 
184
  labels = (0 < nn < 99) and (nn == nc) # apply names to ticklabels
185
  with warnings.catch_warnings():
186
  warnings.simplefilter('ignore') # suppress empty matrix RuntimeWarning: All-NaN slice encountered
187
+ sn.heatmap(array,
188
+ annot=nc < 30,
189
+ annot_kws={
190
+ "size": 8},
191
+ cmap='Blues',
192
+ fmt='.2f',
193
+ square=True,
194
+ vmin=0.0,
195
  xticklabels=names + ['background FP'] if labels else "auto",
196
  yticklabels=names + ['background FN'] if labels else "auto").set_facecolor((1, 1, 1))
197
  fig.axes[0].set_xlabel('True')
 
260
  iou (Tensor[N, M]): the NxM matrix containing the pairwise
261
  IoU values for every element in boxes1 and boxes2
262
  """
 
263
  def box_area(box):
264
  # box = 4xn
265
  return (box[2] - box[0]) * (box[3] - box[1])
 
306
 
307
  # Plots ----------------------------------------------------------------------------------------------------------------
308
 
309
+
310
  def plot_pr_curve(px, py, ap, save_dir='pr_curve.png', names=()):
311
  # Precision-recall curve
312
  fig, ax = plt.subplots(1, 1, figsize=(9, 6), tight_layout=True)
utils/plots.py CHANGED
@@ -89,10 +89,11 @@ class Annotator:
89
  if label:
90
  w, h = self.font.getsize(label) # text width, height
91
  outside = box[1] - h >= 0 # label fits outside box
92
- self.draw.rectangle((box[0],
93
- box[1] - h if outside else box[1],
94
- box[0] + w + 1,
95
- box[1] + 1 if outside else box[1] + h + 1), fill=color)
 
96
  # self.draw.text((box[0], box[1]), label, fill=txt_color, font=self.font, anchor='ls') # for PIL>8.0
97
  self.draw.text((box[0], box[1] - h if outside else box[1]), label, fill=txt_color, font=self.font)
98
  else: # cv2
@@ -104,8 +105,13 @@ class Annotator:
104
  outside = p1[1] - h - 3 >= 0 # label fits outside box
105
  p2 = p1[0] + w, p1[1] - h - 3 if outside else p1[1] + h + 3
106
  cv2.rectangle(self.im, p1, p2, color, -1, cv2.LINE_AA) # filled
107
- cv2.putText(self.im, label, (p1[0], p1[1] - 2 if outside else p1[1] + h + 2), 0, self.lw / 3, txt_color,
108
- thickness=tf, lineType=cv2.LINE_AA)
 
 
 
 
 
109
 
110
  def rectangle(self, xy, fill=None, outline=None, width=1):
111
  # Add rectangle to image (PIL-only)
@@ -307,11 +313,19 @@ def plot_val_study(file='', dir='', x=None): # from utils.plots import *; plot_
307
  ax[i].set_title(s[i])
308
 
309
  j = y[3].argmax() + 1
310
- ax2.plot(y[5, 1:j], y[3, 1:j] * 1E2, '.-', linewidth=2, markersize=8,
 
 
 
 
311
  label=f.stem.replace('study_coco_', '').replace('yolo', 'YOLO'))
312
 
313
  ax2.plot(1E3 / np.array([209, 140, 97, 58, 35, 18]), [34.6, 40.5, 43.0, 47.5, 49.7, 51.5],
314
- 'k.-', linewidth=2, markersize=8, alpha=.25, label='EfficientDet')
 
 
 
 
315
 
316
  ax2.grid(alpha=0.2)
317
  ax2.set_yticks(np.arange(20, 60, 5))
 
89
  if label:
90
  w, h = self.font.getsize(label) # text width, height
91
  outside = box[1] - h >= 0 # label fits outside box
92
+ self.draw.rectangle(
93
+ (box[0], box[1] - h if outside else box[1], box[0] + w + 1,
94
+ box[1] + 1 if outside else box[1] + h + 1),
95
+ fill=color,
96
+ )
97
  # self.draw.text((box[0], box[1]), label, fill=txt_color, font=self.font, anchor='ls') # for PIL>8.0
98
  self.draw.text((box[0], box[1] - h if outside else box[1]), label, fill=txt_color, font=self.font)
99
  else: # cv2
 
105
  outside = p1[1] - h - 3 >= 0 # label fits outside box
106
  p2 = p1[0] + w, p1[1] - h - 3 if outside else p1[1] + h + 3
107
  cv2.rectangle(self.im, p1, p2, color, -1, cv2.LINE_AA) # filled
108
+ cv2.putText(self.im,
109
+ label, (p1[0], p1[1] - 2 if outside else p1[1] + h + 2),
110
+ 0,
111
+ self.lw / 3,
112
+ txt_color,
113
+ thickness=tf,
114
+ lineType=cv2.LINE_AA)
115
 
116
  def rectangle(self, xy, fill=None, outline=None, width=1):
117
  # Add rectangle to image (PIL-only)
 
313
  ax[i].set_title(s[i])
314
 
315
  j = y[3].argmax() + 1
316
+ ax2.plot(y[5, 1:j],
317
+ y[3, 1:j] * 1E2,
318
+ '.-',
319
+ linewidth=2,
320
+ markersize=8,
321
  label=f.stem.replace('study_coco_', '').replace('yolo', 'YOLO'))
322
 
323
  ax2.plot(1E3 / np.array([209, 140, 97, 58, 35, 18]), [34.6, 40.5, 43.0, 47.5, 49.7, 51.5],
324
+ 'k.-',
325
+ linewidth=2,
326
+ markersize=8,
327
+ alpha=.25,
328
+ label='EfficientDet')
329
 
330
  ax2.grid(alpha=0.2)
331
  ax2.set_yticks(np.arange(20, 60, 5))
utils/torch_utils.py CHANGED
@@ -284,7 +284,6 @@ class ModelEMA:
284
  Keeps a moving average of everything in the model state_dict (parameters and buffers)
285
  For EMA details see https://www.tensorflow.org/api_docs/python/tf/train/ExponentialMovingAverage
286
  """
287
-
288
  def __init__(self, model, decay=0.9999, tau=2000, updates=0):
289
  # Create EMA
290
  self.ema = deepcopy(de_parallel(model)).eval() # FP32 EMA
 
284
  Keeps a moving average of everything in the model state_dict (parameters and buffers)
285
  For EMA details see https://www.tensorflow.org/api_docs/python/tf/train/ExponentialMovingAverage
286
  """
 
287
  def __init__(self, model, decay=0.9999, tau=2000, updates=0):
288
  # Create EMA
289
  self.ema = deepcopy(de_parallel(model)).eval() # FP32 EMA
val.py CHANGED
@@ -62,10 +62,11 @@ def save_one_json(predn, jdict, path, class_map):
62
  box = xyxy2xywh(predn[:, :4]) # xywh
63
  box[:, :2] -= box[:, 2:] / 2 # xy center to top-left corner
64
  for p, b in zip(predn.tolist(), box.tolist()):
65
- jdict.append({'image_id': image_id,
66
- 'category_id': class_map[int(p[5])],
67
- 'bbox': [round(x, 3) for x in b],
68
- 'score': round(p[4], 5)})
 
69
 
70
 
71
  def process_batch(detections, labels, iouv):
@@ -93,7 +94,8 @@ def process_batch(detections, labels, iouv):
93
 
94
 
95
  @torch.no_grad()
96
- def run(data,
 
97
  weights=None, # model.pt path(s)
98
  batch_size=32, # batch size
99
  imgsz=640, # inference size (pixels)
@@ -120,7 +122,7 @@ def run(data,
120
  plots=True,
121
  callbacks=Callbacks(),
122
  compute_loss=None,
123
- ):
124
  # Initialize/load model and set device
125
  training = model is not None
126
  if training: # called by train.py
@@ -164,8 +166,15 @@ def run(data,
164
  pad = 0.0 if task in ('speed', 'benchmark') else 0.5
165
  rect = False if task == 'benchmark' else pt # square inference for benchmarks
166
  task = task if task in ('train', 'val', 'test') else 'val' # path to train/val/test images
167
- dataloader = create_dataloader(data[task], imgsz, batch_size, stride, single_cls, pad=pad, rect=rect,
168
- workers=workers, prefix=colorstr(f'{task}: '))[0]
 
 
 
 
 
 
 
169
 
170
  seen = 0
171
  confusion_matrix = ConfusionMatrix(nc=nc)
 
62
  box = xyxy2xywh(predn[:, :4]) # xywh
63
  box[:, :2] -= box[:, 2:] / 2 # xy center to top-left corner
64
  for p, b in zip(predn.tolist(), box.tolist()):
65
+ jdict.append({
66
+ 'image_id': image_id,
67
+ 'category_id': class_map[int(p[5])],
68
+ 'bbox': [round(x, 3) for x in b],
69
+ 'score': round(p[4], 5)})
70
 
71
 
72
  def process_batch(detections, labels, iouv):
 
94
 
95
 
96
  @torch.no_grad()
97
+ def run(
98
+ data,
99
  weights=None, # model.pt path(s)
100
  batch_size=32, # batch size
101
  imgsz=640, # inference size (pixels)
 
122
  plots=True,
123
  callbacks=Callbacks(),
124
  compute_loss=None,
125
+ ):
126
  # Initialize/load model and set device
127
  training = model is not None
128
  if training: # called by train.py
 
166
  pad = 0.0 if task in ('speed', 'benchmark') else 0.5
167
  rect = False if task == 'benchmark' else pt # square inference for benchmarks
168
  task = task if task in ('train', 'val', 'test') else 'val' # path to train/val/test images
169
+ dataloader = create_dataloader(data[task],
170
+ imgsz,
171
+ batch_size,
172
+ stride,
173
+ single_cls,
174
+ pad=pad,
175
+ rect=rect,
176
+ workers=workers,
177
+ prefix=colorstr(f'{task}: '))[0]
178
 
179
  seen = 0
180
  confusion_matrix = ConfusionMatrix(nc=nc)