glenn-jocher commited on
Commit
dc5e183
·
unverified ·
2 Parent(s): abb024d 16f6834

Merge branch 'master' into advanced_logging

Browse files
Files changed (8) hide show
  1. detect.py +3 -4
  2. models/experimental.py +21 -1
  3. models/export.py +2 -1
  4. requirements.txt +1 -1
  5. test.py +12 -15
  6. train.py +10 -12
  7. utils/datasets.py +3 -3
  8. utils/utils.py +1 -1
detect.py CHANGED
@@ -2,7 +2,7 @@ import argparse
2
 
3
  import torch.backends.cudnn as cudnn
4
 
5
- from utils import google_utils
6
  from utils.datasets import *
7
  from utils.utils import *
8
 
@@ -20,8 +20,7 @@ def detect(save_img=False):
20
  half = device.type != 'cpu' # half precision only supported on CUDA
21
 
22
  # Load model
23
- google_utils.attempt_download(weights)
24
- model = torch.load(weights, map_location=device)['model'].float().eval() # load FP32 model
25
  imgsz = check_img_size(imgsz, s=model.stride.max()) # check img_size
26
  if half:
27
  model.half() # to FP16
@@ -137,7 +136,7 @@ def detect(save_img=False):
137
 
138
  if __name__ == '__main__':
139
  parser = argparse.ArgumentParser()
140
- parser.add_argument('--weights', type=str, default='weights/yolov5s.pt', help='model.pt path')
141
  parser.add_argument('--source', type=str, default='inference/images', help='source') # file/folder, 0 for webcam
142
  parser.add_argument('--output', type=str, default='inference/output', help='output folder') # output folder
143
  parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)')
 
2
 
3
  import torch.backends.cudnn as cudnn
4
 
5
+ from models.experimental import *
6
  from utils.datasets import *
7
  from utils.utils import *
8
 
 
20
  half = device.type != 'cpu' # half precision only supported on CUDA
21
 
22
  # Load model
23
+ model = attempt_load(weights, map_location=device) # load FP32 model
 
24
  imgsz = check_img_size(imgsz, s=model.stride.max()) # check img_size
25
  if half:
26
  model.half() # to FP16
 
136
 
137
  if __name__ == '__main__':
138
  parser = argparse.ArgumentParser()
139
+ parser.add_argument('--weights', nargs='+', type=str, default='yolov5s.pt', help='model.pt path(s)')
140
  parser.add_argument('--source', type=str, default='inference/images', help='source') # file/folder, 0 for webcam
141
  parser.add_argument('--output', type=str, default='inference/output', help='output folder') # output folder
142
  parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)')
models/experimental.py CHANGED
@@ -1,6 +1,7 @@
1
  # This file contains experimental modules
2
 
3
  from models.common import *
 
4
 
5
 
6
  class CrossConv(nn.Module):
@@ -118,4 +119,23 @@ class Ensemble(nn.ModuleList):
118
  y = []
119
  for module in self:
120
  y.append(module(x, augment)[0])
121
- return torch.cat(y, 1), None # ensembled inference output, train output
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # This file contains experimental modules
2
 
3
  from models.common import *
4
+ from utils import google_utils
5
 
6
 
7
  class CrossConv(nn.Module):
 
119
  y = []
120
  for module in self:
121
  y.append(module(x, augment)[0])
122
+ # y = torch.stack(y).max(0)[0] # max ensemble
123
+ # y = torch.cat(y, 1) # nms ensemble
124
+ y = torch.stack(y).mean(0) # mean ensemble
125
+ return y, None # inference, train output
126
+
127
+
128
+ def attempt_load(weights, map_location=None):
129
+ # Loads an ensemble of models weights=[a,b,c] or a single model weights=[a] or weights=a
130
+ model = Ensemble()
131
+ for w in weights if isinstance(weights, list) else [weights]:
132
+ google_utils.attempt_download(w)
133
+ model.append(torch.load(w, map_location=map_location)['model'].float().fuse().eval()) # load FP32 model
134
+
135
+ if len(model) == 1:
136
+ return model[-1] # return model
137
+ else:
138
+ print('Ensemble created with %s\n' % weights)
139
+ for k in ['names', 'stride']:
140
+ setattr(model, k, getattr(model[-1], k))
141
+ return model # return ensemble
models/export.py CHANGED
@@ -61,7 +61,8 @@ if __name__ == '__main__':
61
  import coremltools as ct
62
 
63
  print('\nStarting CoreML export with coremltools %s...' % ct.__version__)
64
- model = ct.convert(ts, inputs=[ct.ImageType(name='images', shape=img.shape)]) # convert
 
65
  f = opt.weights.replace('.pt', '.mlmodel') # filename
66
  model.save(f)
67
  print('CoreML export success, saved as %s' % f)
 
61
  import coremltools as ct
62
 
63
  print('\nStarting CoreML export with coremltools %s...' % ct.__version__)
64
+ # convert model from torchscript and apply pixel scaling as per detect.py
65
+ model = ct.convert(ts, inputs=[ct.ImageType(name='images', shape=img.shape, scale=1/255.0, bias=[0, 0, 0])])
66
  f = opt.weights.replace('.pt', '.mlmodel') # filename
67
  model.save(f)
68
  print('CoreML export success, saved as %s' % f)
requirements.txt CHANGED
@@ -2,7 +2,7 @@
2
  Cython
3
  numpy==1.17
4
  opencv-python
5
- torch>=1.4
6
  matplotlib
7
  pillow
8
  tensorboard
 
2
  Cython
3
  numpy==1.17
4
  opencv-python
5
+ torch>=1.5.1
6
  matplotlib
7
  pillow
8
  tensorboard
test.py CHANGED
@@ -1,9 +1,8 @@
1
  import argparse
2
  import json
3
 
4
- from utils import google_utils
5
  from utils.datasets import *
6
- from utils.utils import *
7
 
8
 
9
  def test(data,
@@ -22,28 +21,26 @@ def test(data,
22
  merge=False):
23
 
24
  # Initialize/load model and set device
25
- if model is None:
26
- training = False
27
- merge = opt.merge # use Merge NMS
 
 
28
  device = torch_utils.select_device(opt.device, batch_size=batch_size)
 
29
 
30
  # Remove previous
31
  for f in glob.glob(str(Path(save_dir) / 'test_batch*.jpg')):
32
  os.remove(f)
33
 
34
  # Load model
35
- google_utils.attempt_download(weights)
36
- model = torch.load(weights, map_location=device)['model'].float().fuse().to(device) # load to FP32
37
  imgsz = check_img_size(imgsz, s=model.stride.max()) # check img_size
38
 
39
  # Multi-GPU disabled, incompatible with .half() https://github.com/ultralytics/yolov5/issues/99
40
  # if device.type != 'cpu' and torch.cuda.device_count() > 1:
41
  # model = nn.DataParallel(model)
42
 
43
- else: # called by train.py
44
- training = True
45
- device = next(model.parameters()).device # get model device
46
-
47
  # Half
48
  half = device.type != 'cpu' and torch.cuda.device_count() == 1 # half precision only supported on single-GPU
49
  if half:
@@ -58,11 +55,11 @@ def test(data,
58
  niou = iouv.numel()
59
 
60
  # Dataloader
61
- if dataloader is None: # not training
62
  img = torch.zeros((1, 3, imgsz, imgsz), device=device) # init img
63
  _ = model(img.half() if half else img) if device.type != 'cpu' else None # run once
64
  path = data['test'] if opt.task == 'test' else data['val'] # path to val/test images
65
- dataloader = create_dataloader(path, imgsz, batch_size, int(max(model.stride)), opt,
66
  hyp=None, augment=False, cache=False, pad=0.5, rect=True)[0]
67
 
68
  seen = 0
@@ -195,7 +192,7 @@ def test(data,
195
  if save_json and map50 and len(jdict):
196
  imgIds = [int(Path(x).stem.split('_')[-1]) for x in dataloader.dataset.img_files]
197
  f = 'detections_val2017_%s_results.json' % \
198
- (weights.split(os.sep)[-1].replace('.pt', '') if weights else '') # filename
199
  print('\nCOCO mAP with pycocotools... saving %s...' % f)
200
  with open(f, 'w') as file:
201
  json.dump(jdict, file)
@@ -228,7 +225,7 @@ def test(data,
228
 
229
  if __name__ == '__main__':
230
  parser = argparse.ArgumentParser(prog='test.py')
231
- parser.add_argument('--weights', type=str, default='weights/yolov5s.pt', help='model.pt path')
232
  parser.add_argument('--data', type=str, default='data/coco128.yaml', help='*.data path')
233
  parser.add_argument('--batch-size', type=int, default=32, help='size of each image batch')
234
  parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)')
 
1
  import argparse
2
  import json
3
 
4
+ from models.experimental import *
5
  from utils.datasets import *
 
6
 
7
 
8
  def test(data,
 
21
  merge=False):
22
 
23
  # Initialize/load model and set device
24
+ training = model is not None
25
+ if training: # called by train.py
26
+ device = next(model.parameters()).device # get model device
27
+
28
+ else: # called directly
29
  device = torch_utils.select_device(opt.device, batch_size=batch_size)
30
+ merge = opt.merge # use Merge NMS
31
 
32
  # Remove previous
33
  for f in glob.glob(str(Path(save_dir) / 'test_batch*.jpg')):
34
  os.remove(f)
35
 
36
  # Load model
37
+ model = attempt_load(weights, map_location=device) # load FP32 model
 
38
  imgsz = check_img_size(imgsz, s=model.stride.max()) # check img_size
39
 
40
  # Multi-GPU disabled, incompatible with .half() https://github.com/ultralytics/yolov5/issues/99
41
  # if device.type != 'cpu' and torch.cuda.device_count() > 1:
42
  # model = nn.DataParallel(model)
43
 
 
 
 
 
44
  # Half
45
  half = device.type != 'cpu' and torch.cuda.device_count() == 1 # half precision only supported on single-GPU
46
  if half:
 
55
  niou = iouv.numel()
56
 
57
  # Dataloader
58
+ if not training:
59
  img = torch.zeros((1, 3, imgsz, imgsz), device=device) # init img
60
  _ = model(img.half() if half else img) if device.type != 'cpu' else None # run once
61
  path = data['test'] if opt.task == 'test' else data['val'] # path to val/test images
62
+ dataloader = create_dataloader(path, imgsz, batch_size, model.stride.max(), opt,
63
  hyp=None, augment=False, cache=False, pad=0.5, rect=True)[0]
64
 
65
  seen = 0
 
192
  if save_json and map50 and len(jdict):
193
  imgIds = [int(Path(x).stem.split('_')[-1]) for x in dataloader.dataset.img_files]
194
  f = 'detections_val2017_%s_results.json' % \
195
+ (weights.split(os.sep)[-1].replace('.pt', '') if isinstance(weights, str) else '') # filename
196
  print('\nCOCO mAP with pycocotools... saving %s...' % f)
197
  with open(f, 'w') as file:
198
  json.dump(jdict, file)
 
225
 
226
  if __name__ == '__main__':
227
  parser = argparse.ArgumentParser(prog='test.py')
228
+ parser.add_argument('--weights', nargs='+', type=str, default='yolov5s.pt', help='model.pt path(s)')
229
  parser.add_argument('--data', type=str, default='data/coco128.yaml', help='*.data path')
230
  parser.add_argument('--batch-size', type=int, default=32, help='size of each image batch')
231
  parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)')
train.py CHANGED
@@ -96,11 +96,13 @@ def train(hyp):
96
 
97
  optimizer.add_param_group({'params': pg1, 'weight_decay': hyp['weight_decay']}) # add pg1 with weight_decay
98
  optimizer.add_param_group({'params': pg2}) # add pg2 (biases)
 
 
 
99
  # Scheduler https://arxiv.org/pdf/1812.01187.pdf
100
  lf = lambda x: (((1 + math.cos(x * math.pi / epochs)) / 2) ** 1.0) * 0.9 + 0.1 # cosine
101
  scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf)
102
- print('Optimizer groups: %g .bias, %g conv.weight, %g other' % (len(pg2), len(pg1), len(pg0)))
103
- del pg0, pg1, pg2
104
 
105
  # Load Model
106
  google_utils.attempt_download(weights)
@@ -142,12 +144,7 @@ def train(hyp):
142
  if mixed_precision:
143
  model, optimizer = amp.initialize(model, optimizer, opt_level='O1', verbosity=0)
144
 
145
-
146
- scheduler.last_epoch = start_epoch - 1 # do not move
147
- # https://discuss.pytorch.org/t/a-problem-occured-when-resuming-an-optimizer/28822
148
- plot_lr_scheduler(optimizer, scheduler, epochs, save_dir=log_dir)
149
-
150
- # Initialize distributed training
151
  if device.type != 'cpu' and torch.cuda.device_count() > 1 and torch.distributed.is_available():
152
  dist.init_process_group(backend='nccl', # distributed backend
153
  init_method='tcp://127.0.0.1:9999', # init method
@@ -199,9 +196,10 @@ def train(hyp):
199
  # Start training
200
  t0 = time.time()
201
  nb = len(dataloader) # number of batches
202
- n_burn = max(3 * nb, 1e3) # burn-in iterations, max(3 epochs, 1k iterations)
203
  maps = np.zeros(nc) # mAP per class
204
  results = (0, 0, 0, 0, 0, 0, 0) # 'P', 'R', 'mAP', 'F1', 'val GIoU', 'val Objectness', 'val Classification'
 
205
  print('Image sizes %g train, %g test' % (imgsz, imgsz_test))
206
  print('Using %g dataloader workers' % dataloader.num_workers)
207
  print('Starting training for %g epochs...' % epochs)
@@ -226,9 +224,9 @@ def train(hyp):
226
  ni = i + nb * epoch # number integrated batches (since train start)
227
  imgs = imgs.to(device).float() / 255.0 # uint8 to float32, 0 - 255 to 0.0 - 1.0
228
 
229
- # Burn-in
230
- if ni <= n_burn:
231
- xi = [0, n_burn] # x interp
232
  # model.gr = np.interp(ni, xi, [0.0, 1.0]) # giou loss ratio (obj_loss = 1.0 or giou)
233
  accumulate = max(1, np.interp(ni, xi, [1, nbs / batch_size]).round())
234
  for j, x in enumerate(optimizer.param_groups):
 
96
 
97
  optimizer.add_param_group({'params': pg1, 'weight_decay': hyp['weight_decay']}) # add pg1 with weight_decay
98
  optimizer.add_param_group({'params': pg2}) # add pg2 (biases)
99
+ print('Optimizer groups: %g .bias, %g conv.weight, %g other' % (len(pg2), len(pg1), len(pg0)))
100
+ del pg0, pg1, pg2
101
+
102
  # Scheduler https://arxiv.org/pdf/1812.01187.pdf
103
  lf = lambda x: (((1 + math.cos(x * math.pi / epochs)) / 2) ** 1.0) * 0.9 + 0.1 # cosine
104
  scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf)
105
+ plot_lr_scheduler(optimizer, scheduler, epochs, save_dir=log_dir)
 
106
 
107
  # Load Model
108
  google_utils.attempt_download(weights)
 
144
  if mixed_precision:
145
  model, optimizer = amp.initialize(model, optimizer, opt_level='O1', verbosity=0)
146
 
147
+ # Distributed training
 
 
 
 
 
148
  if device.type != 'cpu' and torch.cuda.device_count() > 1 and torch.distributed.is_available():
149
  dist.init_process_group(backend='nccl', # distributed backend
150
  init_method='tcp://127.0.0.1:9999', # init method
 
196
  # Start training
197
  t0 = time.time()
198
  nb = len(dataloader) # number of batches
199
+ nw = max(3 * nb, 1e3) # number of warmup iterations, max(3 epochs, 1k iterations)
200
  maps = np.zeros(nc) # mAP per class
201
  results = (0, 0, 0, 0, 0, 0, 0) # 'P', 'R', 'mAP', 'F1', 'val GIoU', 'val Objectness', 'val Classification'
202
+ scheduler.last_epoch = start_epoch - 1 # do not move
203
  print('Image sizes %g train, %g test' % (imgsz, imgsz_test))
204
  print('Using %g dataloader workers' % dataloader.num_workers)
205
  print('Starting training for %g epochs...' % epochs)
 
224
  ni = i + nb * epoch # number integrated batches (since train start)
225
  imgs = imgs.to(device).float() / 255.0 # uint8 to float32, 0 - 255 to 0.0 - 1.0
226
 
227
+ # Warmup
228
+ if ni <= nw:
229
+ xi = [0, nw] # x interp
230
  # model.gr = np.interp(ni, xi, [0.0, 1.0]) # giou loss ratio (obj_loss = 1.0 or giou)
231
  accumulate = max(1, np.interp(ni, xi, [1, nbs / batch_size]).round())
232
  for j, x in enumerate(optimizer.param_groups):
utils/datasets.py CHANGED
@@ -48,7 +48,7 @@ def create_dataloader(path, imgsz, batch_size, stride, opt, hyp=None, augment=Fa
48
  rect=rect, # rectangular training
49
  cache_images=cache,
50
  single_cls=opt.single_cls,
51
- stride=stride,
52
  pad=pad)
53
 
54
  batch_size = min(batch_size, len(dataset))
@@ -679,8 +679,8 @@ def letterbox(img, new_shape=(640, 640), color=(114, 114, 114), auto=True, scale
679
  dw, dh = np.mod(dw, 64), np.mod(dh, 64) # wh padding
680
  elif scaleFill: # stretch
681
  dw, dh = 0.0, 0.0
682
- new_unpad = new_shape
683
- ratio = new_shape[0] / shape[1], new_shape[1] / shape[0] # width, height ratios
684
 
685
  dw /= 2 # divide padding into 2 sides
686
  dh /= 2
 
48
  rect=rect, # rectangular training
49
  cache_images=cache,
50
  single_cls=opt.single_cls,
51
+ stride=int(stride),
52
  pad=pad)
53
 
54
  batch_size = min(batch_size, len(dataset))
 
679
  dw, dh = np.mod(dw, 64), np.mod(dh, 64) # wh padding
680
  elif scaleFill: # stretch
681
  dw, dh = 0.0, 0.0
682
+ new_unpad = (new_shape[1], new_shape[0])
683
+ ratio = new_shape[1] / shape[1], new_shape[0] / shape[0] # width, height ratios
684
 
685
  dw /= 2 # divide padding into 2 sides
686
  dh /= 2
utils/utils.py CHANGED
@@ -179,7 +179,7 @@ def xywh2xyxy(x):
179
  def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None):
180
  # Rescale coords (xyxy) from img1_shape to img0_shape
181
  if ratio_pad is None: # calculate from img0_shape
182
- gain = max(img1_shape) / max(img0_shape) # gain = old / new
183
  pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2 # wh padding
184
  else:
185
  gain = ratio_pad[0][0]
 
179
  def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None):
180
  # Rescale coords (xyxy) from img1_shape to img0_shape
181
  if ratio_pad is None: # calculate from img0_shape
182
+ gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1]) # gain = old / new
183
  pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2 # wh padding
184
  else:
185
  gain = ratio_pad[0][0]