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 +5 -6
- detect.py +3 -2
- export.py +65 -45
- hubconf.py +7 -6
- models/common.py +24 -13
- models/experimental.py +2 -2
- models/tf.py +47 -20
- models/yolo.py +2 -2
- setup.cfg +14 -0
- train.py +82 -65
- utils/activations.py +0 -2
- utils/augmentations.py +11 -4
- utils/benchmarks.py +3 -2
- utils/callbacks.py +1 -6
- utils/datasets.py +74 -38
- utils/downloads.py +10 -7
- utils/general.py +40 -34
- utils/loggers/__init__.py +16 -5
- utils/loggers/wandb/wandb_utils.py +63 -49
- utils/loss.py +10 -4
- utils/metrics.py +9 -2
- utils/plots.py +22 -8
- utils/torch_utils.py +0 -1
- val.py +17 -8
@@ -36,12 +36,11 @@ repos:
|
|
36 |
- id: isort
|
37 |
name: Sort imports
|
38 |
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
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
|
@@ -47,7 +47,8 @@ from utils.torch_utils import select_device, time_sync
|
|
47 |
|
48 |
|
49 |
@torch.no_grad()
|
50 |
-
def run(
|
|
|
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)
|
@@ -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 |
-
['
|
81 |
-
['
|
82 |
-
['
|
83 |
-
['
|
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(
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
145 |
-
|
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,
|
250 |
-
|
251 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
283 |
-
|
284 |
-
|
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
|
356 |
-
|
357 |
-
|
358 |
-
|
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(
|
|
|
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(),
|
499 |
-
|
500 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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',
|
|
|
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()
|
@@ -132,12 +132,13 @@ if __name__ == '__main__':
|
|
132 |
|
133 |
from utils.general import cv2
|
134 |
|
135 |
-
imgs = [
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
|
|
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()
|
@@ -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(
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
|
|
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 = {
|
391 |
-
|
392 |
-
|
|
|
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
|
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],
|
565 |
-
self.
|
|
|
|
|
|
|
|
|
|
|
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({
|
607 |
-
|
|
|
|
|
|
|
|
|
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
|
@@ -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 |
-
|
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 |
|
@@ -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,
|
|
|
|
|
|
|
|
|
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(
|
102 |
-
|
103 |
-
|
104 |
-
|
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,
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
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),
|
|
|
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 |
-
|
|
|
|
|
|
|
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",
|
|
|
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",
|
|
|
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",
|
|
|
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(
|
|
|
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)
|
@@ -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
|
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)
|
@@ -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
|
@@ -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,
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
235 |
-
|
236 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
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 = {
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
397 |
-
|
|
|
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(
|
432 |
-
|
433 |
-
|
434 |
-
|
435 |
-
|
436 |
-
|
437 |
-
|
438 |
-
|
439 |
-
|
440 |
-
|
441 |
-
|
442 |
-
|
443 |
-
|
|
|
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 = {
|
550 |
-
|
551 |
-
|
552 |
-
|
553 |
-
|
554 |
-
|
555 |
-
|
556 |
-
|
557 |
-
|
558 |
-
|
559 |
-
|
560 |
-
|
561 |
-
|
562 |
-
|
563 |
-
|
564 |
-
|
565 |
-
|
566 |
-
|
567 |
-
|
568 |
-
|
569 |
-
|
570 |
-
|
571 |
-
|
572 |
-
|
573 |
-
|
574 |
-
|
575 |
-
|
576 |
-
|
577 |
-
|
|
|
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
|
@@ -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)
|
@@ -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 |
-
|
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 |
-
|
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,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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]
|
@@ -45,13 +45,14 @@ from utils.general import LOGGER, print_args
|
|
45 |
from utils.torch_utils import select_device
|
46 |
|
47 |
|
48 |
-
def run(
|
|
|
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)
|
@@ -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):
|
@@ -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 = {
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
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,
|
96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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(
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
|
|
|
|
|
|
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,
|
384 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
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,
|
|
|
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,
|
|
|
|
|
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,
|
|
|
|
|
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',
|
799 |
-
|
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] = {
|
1000 |
-
|
1001 |
-
|
1002 |
-
|
1003 |
-
|
|
|
|
|
|
|
|
|
|
|
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'
|
@@ -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 = [
|
67 |
-
|
|
|
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(
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
|
|
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 |
#
|
@@ -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 = {
|
540 |
-
|
541 |
-
|
542 |
-
|
543 |
-
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
-
|
548 |
-
|
549 |
-
|
550 |
-
|
551 |
-
|
552 |
-
|
553 |
-
|
554 |
-
|
555 |
-
|
556 |
-
|
557 |
-
|
|
|
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 = [
|
595 |
-
|
596 |
-
|
|
|
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,
|
705 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
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/
|
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'#
|
844 |
-
|
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 |
-
|
852 |
-
|
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
|
@@ -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 = [
|
47 |
-
|
48 |
-
|
49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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),
|
|
|
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()
|
@@ -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 =
|
50 |
-
|
51 |
-
is_valset_wandb_artifact =
|
52 |
-
|
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(
|
234 |
-
|
235 |
-
self.val_artifact_path, self.val_artifact = self.download_dataset_artifact(
|
236 |
-
|
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',
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
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 |
-
|
|
|
349 |
if data.get('train'):
|
350 |
data['train'] = WANDB_ARTIFACT_PREFIX + str(Path(project) / 'train')
|
351 |
|
352 |
-
self.val_artifact = self.create_dataset_table(
|
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 |
-
|
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({
|
424 |
-
|
425 |
-
|
|
|
|
|
|
|
|
|
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 |
-
|
451 |
-
|
452 |
-
|
453 |
-
|
454 |
-
|
|
|
|
|
|
|
|
|
|
|
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 = [{
|
489 |
-
|
490 |
-
|
491 |
-
|
492 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
531 |
-
|
|
|
|
|
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"]
|
@@ -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(
|
187 |
-
|
188 |
-
|
189 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
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]
|
@@ -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,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
@@ -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(
|
93 |
-
|
94 |
-
|
95 |
-
|
|
|
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,
|
108 |
-
|
|
|
|
|
|
|
|
|
|
|
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],
|
|
|
|
|
|
|
|
|
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.-',
|
|
|
|
|
|
|
|
|
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))
|
@@ -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
|
@@ -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({
|
66 |
-
|
67 |
-
|
68 |
-
|
|
|
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(
|
|
|
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],
|
168 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|