train_sparsity.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. # YOLOv5 🚀 by Ultralytics, GPL-3.0 license
  2. """
  3. Train a YOLOv5 model on a custom dataset.
  4. Models and datasets download automatically from the latest YOLOv5 release.
  5. Models: https://github.com/ultralytics/yolov5/tree/master/models
  6. Datasets: https://github.com/ultralytics/yolov5/tree/master/data
  7. Tutorial: https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data
  8. Usage:
  9. $ python path/to/train.py --data coco128.yaml --weights yolov5s.pt --img 640 # from pretrained (RECOMMENDED)
  10. $ python path/to/train.py --data coco128.yaml --weights '' --cfg yolov5s.yaml --img 640 # from scratch
  11. """
  12. import argparse
  13. import math
  14. import os
  15. import random
  16. import sys
  17. import time
  18. from copy import deepcopy
  19. from datetime import datetime
  20. from pathlib import Path
  21. import numpy as np
  22. import torch
  23. import torch.distributed as dist
  24. import torch.nn as nn
  25. import yaml
  26. from torch.cuda import amp
  27. from torch.nn.parallel import DistributedDataParallel as DDP
  28. from torch.optim import SGD, Adam, AdamW, lr_scheduler
  29. from tqdm import tqdm
  30. FILE = Path(__file__).resolve()
  31. ROOT = FILE.parents[0] # YOLOv5 root directory
  32. if str(ROOT) not in sys.path:
  33. sys.path.append(str(ROOT)) # add ROOT to PATH
  34. ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative
  35. import val # for end-of-epoch mAP
  36. from models.experimental import attempt_load
  37. from models.yolo import Model
  38. from utils.autoanchor import check_anchors
  39. from utils.autobatch import check_train_batch_size
  40. from utils.callbacks import Callbacks
  41. from utils.datasets import create_dataloader
  42. from utils.downloads import attempt_download
  43. from utils.general import (LOGGER, check_dataset, check_file, check_git_status, check_img_size, check_requirements,
  44. check_suffix, check_yaml, colorstr, get_latest_run, increment_path, init_seeds,
  45. intersect_dicts, labels_to_class_weights, labels_to_image_weights, methods, one_cycle,
  46. print_args, print_mutation, strip_optimizer)
  47. from utils.loggers import Loggers
  48. from utils.loggers.wandb.wandb_utils import check_wandb_resume
  49. from utils.loss import ComputeLoss
  50. from utils.metrics import fitness
  51. from utils.plots import plot_evolve, plot_labels
  52. from utils.torch_utils import EarlyStopping, ModelEMA, de_parallel, select_device, torch_distributed_zero_first
  53. from models.common import Bottleneck
  54. LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html
  55. RANK = int(os.getenv('RANK', -1))
  56. WORLD_SIZE = int(os.getenv('WORLD_SIZE', 1))
  57. def train(hyp, # path/to/hyp.yaml or hyp dictionary
  58. opt,
  59. device,
  60. callbacks
  61. ):
  62. save_dir, epochs, batch_size, weights, single_cls, evolve, data, cfg, resume, noval, nosave, workers, freeze = \
  63. Path(opt.save_dir), opt.epochs, opt.batch_size, opt.weights, opt.single_cls, opt.evolve, opt.data, opt.cfg, \
  64. opt.resume, opt.noval, opt.nosave, opt.workers, opt.freeze
  65. # Directories
  66. w = save_dir / 'weights' # weights dir
  67. (w.parent if evolve else w).mkdir(parents=True, exist_ok=True) # make dir
  68. last, best = w / 'last.pt', w / 'best.pt'
  69. # Hyperparameters
  70. if isinstance(hyp, str):
  71. with open(hyp, errors='ignore') as f:
  72. hyp = yaml.safe_load(f) # load hyps dict
  73. LOGGER.info(colorstr('hyperparameters: ') + ', '.join(f'{k}={v}' for k, v in hyp.items()))
  74. # Save run settings
  75. if not evolve:
  76. with open(save_dir / 'hyp.yaml', 'w') as f:
  77. yaml.safe_dump(hyp, f, sort_keys=False)
  78. with open(save_dir / 'opt.yaml', 'w') as f:
  79. yaml.safe_dump(vars(opt), f, sort_keys=False)
  80. # Loggers
  81. data_dict = None
  82. if RANK in [-1, 0]:
  83. loggers = Loggers(save_dir, weights, opt, hyp, LOGGER) # loggers instance
  84. if loggers.wandb:
  85. data_dict = loggers.wandb.data_dict
  86. if resume:
  87. weights, epochs, hyp, batch_size = opt.weights, opt.epochs, opt.hyp, opt.batch_size
  88. # Register actions
  89. for k in methods(loggers):
  90. callbacks.register_action(k, callback=getattr(loggers, k))
  91. # Config
  92. plots = not evolve # create plots
  93. cuda = device.type != 'cpu'
  94. init_seeds(1 + RANK)
  95. with torch_distributed_zero_first(LOCAL_RANK):
  96. data_dict = data_dict or check_dataset(data) # check if None
  97. train_path, val_path = data_dict['train'], data_dict['val']
  98. nc = 1 if single_cls else int(data_dict['nc']) # number of classes
  99. names = ['item'] if single_cls and len(data_dict['names']) != 1 else data_dict['names'] # class names
  100. assert len(names) == nc, f'{len(names)} names found for nc={nc} dataset in {data}' # check
  101. is_coco = isinstance(val_path, str) and val_path.endswith('coco/val2017.txt') # COCO dataset
  102. # Model
  103. check_suffix(weights, '.pt') # check weights
  104. pretrained = weights.endswith('.pt')
  105. if pretrained:
  106. with torch_distributed_zero_first(LOCAL_RANK):
  107. weights = attempt_download(weights) # download if not found locally
  108. ckpt = torch.load(weights, map_location='cpu') # load checkpoint to CPU to avoid CUDA memory leak
  109. model = Model(cfg or ckpt['model'].yaml, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device) # create
  110. exclude = ['anchor'] if (cfg or hyp.get('anchors')) and not resume else [] # exclude keys
  111. csd = ckpt['model'].float().state_dict() # checkpoint state_dict as FP32
  112. csd = intersect_dicts(csd, model.state_dict(), exclude=exclude) # intersect
  113. model.load_state_dict(csd, strict=False) # load
  114. LOGGER.info(f'Transferred {len(csd)}/{len(model.state_dict())} items from {weights}') # report
  115. else:
  116. model = Model(cfg, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device) # create
  117. # Freeze
  118. freeze = [f'model.{x}.' for x in (freeze if len(freeze) > 1 else range(freeze[0]))] # layers to freeze
  119. for k, v in model.named_parameters():
  120. v.requires_grad = True # train all layers
  121. if any(x in k for x in freeze):
  122. LOGGER.info(f'freezing {k}')
  123. v.requires_grad = False
  124. # Image size
  125. gs = max(int(model.stride.max()), 32) # grid size (max stride)
  126. imgsz = check_img_size(opt.imgsz, gs, floor=gs * 2) # verify imgsz is gs-multiple
  127. # Batch size
  128. if RANK == -1 and batch_size == -1: # single-GPU only, estimate best batch size
  129. batch_size = check_train_batch_size(model, imgsz)
  130. loggers.on_params_update({"batch_size": batch_size})
  131. # Optimizer
  132. nbs = 64 # nominal batch size
  133. accumulate = max(round(nbs / batch_size), 1) # accumulate loss before optimizing
  134. hyp['weight_decay'] *= batch_size * accumulate / nbs # scale weight_decay
  135. LOGGER.info(f"Scaled weight_decay = {hyp['weight_decay']}")
  136. g0, g1, g2 = [], [], [] # optimizer parameter groups
  137. for v in model.modules():
  138. if hasattr(v, 'bias') and isinstance(v.bias, nn.Parameter): # bias
  139. g2.append(v.bias)
  140. if isinstance(v, nn.BatchNorm2d): # weight (no decay)
  141. g0.append(v.weight)
  142. elif hasattr(v, 'weight') and isinstance(v.weight, nn.Parameter): # weight (with decay)
  143. g1.append(v.weight)
  144. if opt.optimizer == 'Adam':
  145. optimizer = Adam(g0, lr=hyp['lr0'], betas=(hyp['momentum'], 0.999)) # adjust beta1 to momentum
  146. elif opt.optimizer == 'AdamW':
  147. optimizer = AdamW(g0, lr=hyp['lr0'], betas=(hyp['momentum'], 0.999)) # adjust beta1 to momentum
  148. else:
  149. optimizer = SGD(g0, lr=hyp['lr0'], momentum=hyp['momentum'], nesterov=True)
  150. optimizer.add_param_group({'params': g1, 'weight_decay': hyp['weight_decay']}) # add g1 with weight_decay
  151. optimizer.add_param_group({'params': g2}) # add g2 (biases)
  152. LOGGER.info(f"{colorstr('optimizer:')} {type(optimizer).__name__} with parameter groups "
  153. f"{len(g0)} weight (no decay), {len(g1)} weight, {len(g2)} bias")
  154. del g0, g1, g2
  155. # Scheduler
  156. if opt.cos_lr:
  157. lf = one_cycle(1, hyp['lrf'], epochs) # cosine 1->hyp['lrf']
  158. else:
  159. lf = lambda x: (1 - x / epochs) * (1.0 - hyp['lrf']) + hyp['lrf'] # linear
  160. scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf) # plot_lr_scheduler(optimizer, scheduler, epochs)
  161. # EMA
  162. ema = ModelEMA(model) if RANK in [-1, 0] else None
  163. # Resume
  164. start_epoch, best_fitness = 0, 0.0
  165. if pretrained:
  166. # Optimizer
  167. if ckpt['optimizer'] is not None:
  168. optimizer.load_state_dict(ckpt['optimizer'])
  169. best_fitness = ckpt['best_fitness']
  170. # EMA
  171. if ema and ckpt.get('ema'):
  172. ema.ema.load_state_dict(ckpt['ema'].float().state_dict())
  173. ema.updates = ckpt['updates']
  174. # Epochs
  175. start_epoch = ckpt['epoch'] + 1
  176. if resume:
  177. assert start_epoch > 0, f'{weights} training to {epochs} epochs is finished, nothing to resume.'
  178. if epochs < start_epoch:
  179. LOGGER.info(f"{weights} has been trained for {ckpt['epoch']} epochs. Fine-tuning for {epochs} more epochs.")
  180. epochs += ckpt['epoch'] # finetune additional epochs
  181. del ckpt, csd
  182. # DP mode
  183. if cuda and RANK == -1 and torch.cuda.device_count() > 1:
  184. LOGGER.warning('WARNING: DP not recommended, use torch.distributed.run for best DDP Multi-GPU results.\n'
  185. 'See Multi-GPU Tutorial at https://github.com/ultralytics/yolov5/issues/475 to get started.')
  186. model = torch.nn.DataParallel(model)
  187. # SyncBatchNorm
  188. if opt.sync_bn and cuda and RANK != -1:
  189. model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model).to(device)
  190. LOGGER.info('Using SyncBatchNorm()')
  191. print(model)
  192. # Trainloader
  193. train_loader, dataset = create_dataloader(train_path, imgsz, batch_size // WORLD_SIZE, gs, single_cls,
  194. hyp=hyp, augment=True, cache=None if opt.cache == 'val' else opt.cache,
  195. rect=opt.rect, rank=LOCAL_RANK, workers=workers,
  196. image_weights=opt.image_weights, quad=opt.quad,
  197. prefix=colorstr('train: '), shuffle=True)
  198. mlc = int(np.concatenate(dataset.labels, 0)[:, 0].max()) # max label class
  199. nb = len(train_loader) # number of batches
  200. assert mlc < nc, f'Label class {mlc} exceeds nc={nc} in {data}. Possible class labels are 0-{nc - 1}'
  201. # Process 0
  202. if RANK in [-1, 0]:
  203. val_loader = create_dataloader(val_path, imgsz, batch_size // WORLD_SIZE * 2, gs, single_cls,
  204. hyp=hyp, cache=None if noval else opt.cache,
  205. rect=True, rank=-1, workers=workers * 2, pad=0.5,
  206. prefix=colorstr('val: '))[0]
  207. if not resume:
  208. labels = np.concatenate(dataset.labels, 0)
  209. # c = torch.tensor(labels[:, 0]) # classes
  210. # cf = torch.bincount(c.long(), minlength=nc) + 1. # frequency
  211. # model._initialize_biases(cf.to(device))
  212. if plots:
  213. plot_labels(labels, names, save_dir)
  214. # Anchors
  215. if not opt.noautoanchor:
  216. check_anchors(dataset, model=model, thr=hyp['anchor_t'], imgsz=imgsz)
  217. model.half().float() # pre-reduce anchor precision
  218. callbacks.run('on_pretrain_routine_end')
  219. # DDP mode
  220. if cuda and RANK != -1:
  221. model = DDP(model, device_ids=[LOCAL_RANK], output_device=LOCAL_RANK)
  222. # Model attributes
  223. nl = de_parallel(model).model[-1].nl # number of detection layers (to scale hyps)
  224. hyp['box'] *= 3 / nl # scale to layers
  225. hyp['cls'] *= nc / 80 * 3 / nl # scale to classes and layers
  226. hyp['obj'] *= (imgsz / 640) ** 2 * 3 / nl # scale to image size and layers
  227. hyp['label_smoothing'] = opt.label_smoothing
  228. model.nc = nc # attach number of classes to model
  229. model.hyp = hyp # attach hyperparameters to model
  230. model.class_weights = labels_to_class_weights(dataset.labels, nc).to(device) * nc # attach class weights
  231. model.names = names
  232. # Start training
  233. t0 = time.time()
  234. nw = max(round(hyp['warmup_epochs'] * nb), 1000) # number of warmup iterations, max(3 epochs, 1k iterations)
  235. # nw = min(nw, (epochs - start_epoch) / 2 * nb) # limit warmup to < 1/2 of training
  236. last_opt_step = -1
  237. maps = np.zeros(nc) # mAP per class
  238. results = (0, 0, 0, 0, 0, 0, 0) # P, R, mAP@.5, mAP@.5-.95, val_loss(box, obj, cls)
  239. scheduler.last_epoch = start_epoch - 1 # do not move
  240. scaler = amp.GradScaler(enabled=cuda)
  241. stopper = EarlyStopping(patience=opt.patience)
  242. compute_loss = ComputeLoss(model) # init loss class
  243. LOGGER.info(f'Image sizes {imgsz} train, {imgsz} val\n'
  244. f'Using {train_loader.num_workers * WORLD_SIZE} dataloader workers\n'
  245. f"Logging results to {colorstr('bold', save_dir)}\n"
  246. f'Starting training for {epochs} epochs...')
  247. for epoch in range(start_epoch, epochs): # epoch ------------------------------------------------------------------
  248. model.train()
  249. # Update image weights (optional, single-GPU only)
  250. if opt.image_weights:
  251. cw = model.class_weights.cpu().numpy() * (1 - maps) ** 2 / nc # class weights
  252. iw = labels_to_image_weights(dataset.labels, nc=nc, class_weights=cw) # image weights
  253. dataset.indices = random.choices(range(dataset.n), weights=iw, k=dataset.n) # rand weighted idx
  254. # Update mosaic border (optional)
  255. # b = int(random.uniform(0.25 * imgsz, 0.75 * imgsz + gs) // gs * gs)
  256. # dataset.mosaic_border = [b - imgsz, -b] # height, width borders
  257. mloss = torch.zeros(3, device=device) # mean losses
  258. if RANK != -1:
  259. train_loader.sampler.set_epoch(epoch)
  260. pbar = enumerate(train_loader)
  261. LOGGER.info(('\n' + '%10s' * 7) % ('Epoch', 'gpu_mem', 'box', 'obj', 'cls', 'labels', 'img_size'))
  262. if RANK in [-1, 0]:
  263. pbar = tqdm(pbar, total=nb, bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}') # progress bar
  264. optimizer.zero_grad()
  265. for i, (imgs, targets, paths, _) in pbar: # batch -------------------------------------------------------------
  266. ni = i + nb * epoch # number integrated batches (since train start)
  267. imgs = imgs.to(device, non_blocking=True).float() / 255 # uint8 to float32, 0-255 to 0.0-1.0
  268. # Warmup
  269. if ni <= nw:
  270. xi = [0, nw] # x interp
  271. # compute_loss.gr = np.interp(ni, xi, [0.0, 1.0]) # iou loss ratio (obj_loss = 1.0 or iou)
  272. accumulate = max(1, np.interp(ni, xi, [1, nbs / batch_size]).round())
  273. for j, x in enumerate(optimizer.param_groups):
  274. # bias lr falls from 0.1 to lr0, all other lrs rise from 0.0 to lr0
  275. x['lr'] = np.interp(ni, xi, [hyp['warmup_bias_lr'] if j == 2 else 0.0, x['initial_lr'] * lf(epoch)])
  276. if 'momentum' in x:
  277. x['momentum'] = np.interp(ni, xi, [hyp['warmup_momentum'], hyp['momentum']])
  278. # Multi-scale
  279. if opt.multi_scale:
  280. sz = random.randrange(imgsz * 0.5, imgsz * 1.5 + gs) // gs * gs # size
  281. sf = sz / max(imgs.shape[2:]) # scale factor
  282. if sf != 1:
  283. ns = [math.ceil(x * sf / gs) * gs for x in imgs.shape[2:]] # new shape (stretched to gs-multiple)
  284. imgs = nn.functional.interpolate(imgs, size=ns, mode='bilinear', align_corners=False)
  285. # Forward
  286. with amp.autocast(enabled=cuda):
  287. pred = model(imgs) # forward
  288. loss, loss_items = compute_loss(pred, targets.to(device)) # loss scaled by batch_size
  289. if RANK != -1:
  290. loss *= WORLD_SIZE # gradient averaged between devices in DDP mode
  291. if opt.quad:
  292. loss *= 4.
  293. # Backward
  294. # scaler.scale(loss).backward()
  295. loss.backward()
  296. # # ============================= sparsity training ========================== #
  297. srtmp = opt.sr*(1 - 0.9*epoch/epochs)
  298. if opt.st:
  299. ignore_bn_list = []
  300. for k, m in model.named_modules():
  301. if isinstance(m, Bottleneck):
  302. if m.add:
  303. ignore_bn_list.append(k.rsplit(".", 2)[0] + ".cv1.bn")
  304. ignore_bn_list.append(k + '.cv1.bn')
  305. ignore_bn_list.append(k + '.cv2.bn')
  306. if isinstance(m, nn.BatchNorm2d) and (k not in ignore_bn_list):
  307. m.weight.grad.data.add_(srtmp * torch.sign(m.weight.data)) # L1
  308. m.bias.grad.data.add_(opt.sr*10 * torch.sign(m.bias.data)) # L1
  309. # # ============================= sparsity training ========================== #
  310. # Optimize
  311. # if ni - last_opt_step >= accumulate:
  312. optimizer.step()
  313. # scaler.step(optimizer) # optimizer.step
  314. # scaler.update()
  315. optimizer.zero_grad()
  316. if ema:
  317. ema.update(model)
  318. # last_opt_step = ni
  319. # Log
  320. if RANK in [-1, 0]:
  321. mloss = (mloss * i + loss_items) / (i + 1) # update mean losses
  322. mem = f'{torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0:.3g}G' # (GB)
  323. pbar.set_description(('%10s' * 2 + '%10.4g' * 5) % (
  324. f'{epoch}/{epochs - 1}', mem, *mloss, targets.shape[0], imgs.shape[-1]))
  325. callbacks.run('on_train_batch_end', ni, model, imgs, targets, paths, plots, opt.sync_bn)
  326. if callbacks.stop_training:
  327. return
  328. # end batch ------------------------------------------------------------------------------------------------
  329. # Scheduler
  330. lr = [x['lr'] for x in optimizer.param_groups] # for loggers
  331. scheduler.step()
  332. # =============== show bn weights ===================== #
  333. module_list = []
  334. for i, layer in model.named_modules():
  335. if isinstance(layer, nn.BatchNorm2d) and i not in ignore_bn_list:
  336. bnw = layer.state_dict()['weight']
  337. bnb = layer.state_dict()['bias']
  338. module_list.append(bnw)
  339. size_list = [idx.data.shape[0] for idx in module_list]
  340. bn_weights = torch.zeros(sum(size_list))
  341. bnb_weights = torch.zeros(sum(size_list))
  342. index = 0
  343. for idx, size in enumerate(size_list):
  344. bn_weights[index:(index + size)] = module_list[idx].data.abs().clone()
  345. index += size
  346. if RANK in [-1, 0]:
  347. # mAP
  348. callbacks.run('on_train_epoch_end', epoch=epoch)
  349. ema.update_attr(model, include=['yaml', 'nc', 'hyp', 'names', 'stride', 'class_weights'])
  350. final_epoch = (epoch + 1 == epochs) or stopper.possible_stop
  351. if not noval or final_epoch: # Calculate mAP
  352. results, maps, _ = val.run(data_dict,
  353. batch_size=batch_size // WORLD_SIZE * 2,
  354. imgsz=imgsz,
  355. model=ema.ema,
  356. single_cls=single_cls,
  357. dataloader=val_loader,
  358. save_dir=save_dir,
  359. plots=False,
  360. callbacks=callbacks,
  361. compute_loss=compute_loss)
  362. # Update best mAP
  363. fi = fitness(np.array(results).reshape(1, -1)) # weighted combination of [P, R, mAP@.5, mAP@.5-.95]
  364. if fi > best_fitness:
  365. best_fitness = fi
  366. #log_vals = list(mloss) + list(results) + lr + [srtmp]
  367. log_vals = list(mloss) + list(results) + lr
  368. callbacks.run('on_fit_epoch_end', log_vals, epoch, best_fitness, fi)
  369. callbacks.run('on_fit_epoch_end_prune', bn_weights.numpy(), epoch)
  370. # Save model
  371. if (not nosave) or (final_epoch and not evolve): # if save
  372. ckpt = {'epoch': epoch,
  373. 'best_fitness': best_fitness,
  374. 'model': deepcopy(de_parallel(model)).half(),
  375. 'ema': deepcopy(ema.ema).half(),
  376. 'updates': ema.updates,
  377. 'optimizer': optimizer.state_dict(),
  378. 'wandb_id': loggers.wandb.wandb_run.id if loggers.wandb else None,
  379. 'date': datetime.now().isoformat()}
  380. # Save last, best and delete
  381. torch.save(ckpt, last)
  382. if best_fitness == fi:
  383. torch.save(ckpt, best)
  384. if (epoch > 0) and (opt.save_period > 0) and (epoch % opt.save_period == 0):
  385. torch.save(ckpt, w / f'epoch{epoch}.pt')
  386. del ckpt
  387. callbacks.run('on_model_save', last, epoch, final_epoch, best_fitness, fi)
  388. # Stop Single-GPU
  389. if RANK == -1 and stopper(epoch=epoch, fitness=fi):
  390. break
  391. # Stop DDP TODO: known issues shttps://github.com/ultralytics/yolov5/pull/4576
  392. # stop = stopper(epoch=epoch, fitness=fi)
  393. # if RANK == 0:
  394. # dist.broadcast_object_list([stop], 0) # broadcast 'stop' to all ranks
  395. # Stop DPP
  396. # with torch_distributed_zero_first(RANK):
  397. # if stop:
  398. # break # must break all DDP ranks
  399. # end epoch ----------------------------------------------------------------------------------------------------
  400. # end training -----------------------------------------------------------------------------------------------------
  401. if RANK in [-1, 0]:
  402. LOGGER.info(f'\n{epoch - start_epoch + 1} epochs completed in {(time.time() - t0) / 3600:.3f} hours.')
  403. for f in last, best:
  404. if f.exists():
  405. strip_optimizer(f) # strip optimizers
  406. if f is best:
  407. LOGGER.info(f'\nValidating {f}...')
  408. results, _, _ = val.run(data_dict,
  409. batch_size=batch_size // WORLD_SIZE * 2,
  410. imgsz=imgsz,
  411. model=attempt_load(f, device).half(),
  412. iou_thres=0.65 if is_coco else 0.60, # best pycocotools results at 0.65
  413. single_cls=single_cls,
  414. dataloader=val_loader,
  415. save_dir=save_dir,
  416. save_json=is_coco,
  417. verbose=True,
  418. plots=True,
  419. callbacks=callbacks,
  420. compute_loss=compute_loss) # val best model with plots
  421. if is_coco:
  422. callbacks.run('on_fit_epoch_end', list(mloss) + list(results) + lr, epoch, best_fitness, fi)
  423. callbacks.run('on_train_end', last, best, plots, epoch, results)
  424. LOGGER.info(f"Results saved to {colorstr('bold', save_dir)}")
  425. torch.cuda.empty_cache()
  426. return results
  427. def parse_opt(known=False):
  428. parser = argparse.ArgumentParser()
  429. parser.add_argument('--st', action='store_true',default=True, help='train with L1 sparsity normalization')
  430. parser.add_argument('--sr', type=float, default=0.0001, help='L1 normal sparse rate')
  431. parser.add_argument('--weights', type=str, default=ROOT / 'VOC2007_wm/train/exp5/weights/last.pt', help='initial weights path')
  432. parser.add_argument('--cfg', type=str, default='models/yolov5s.yaml', help='model.yaml path')
  433. parser.add_argument('--data', type=str, default=ROOT / 'data/VOC.yaml', help='dataset.yaml path')
  434. parser.add_argument('--hyp', type=str, default=ROOT / 'data/hyps/hyp.scratch-low.yaml', help='hyperparameters path')
  435. parser.add_argument('--epochs', type=int, default=300)
  436. parser.add_argument('--batch-size', type=int, default=16, help='total batch size for all GPUs, -1 for autobatch')
  437. parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=640, help='train, val image size (pixels)')
  438. parser.add_argument('--rect', action='store_true', help='rectangular training')
  439. parser.add_argument('--resume', nargs='?', const=True, default=False, help='resume most recent training')
  440. parser.add_argument('--nosave', action='store_true', help='only save final checkpoint')
  441. parser.add_argument('--noval', action='store_true', help='only validate final epoch')
  442. parser.add_argument('--noautoanchor', action='store_true', help='disable AutoAnchor')
  443. parser.add_argument('--evolve', type=int, nargs='?', const=300, help='evolve hyperparameters for x generations')
  444. parser.add_argument('--bucket', type=str, default='', help='gsutil bucket')
  445. parser.add_argument('--cache', type=str, nargs='?', const='ram', help='--cache images in "ram" (default) or "disk"')
  446. parser.add_argument('--image-weights', action='store_true', help='use weighted image selection for training')
  447. parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
  448. parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%%')
  449. parser.add_argument('--single-cls', action='store_true', help='train multi-class data as single-class')
  450. parser.add_argument('--optimizer', type=str, choices=['SGD', 'Adam', 'AdamW'], default='SGD', help='optimizer')
  451. parser.add_argument('--sync-bn', action='store_true', help='use SyncBatchNorm, only available in DDP mode')
  452. parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)')
  453. parser.add_argument('--project', default=ROOT / 'VOC2007_wm/train_sparity', help='save to project/name')
  454. parser.add_argument('--name', default='exp', help='save to project/name')
  455. parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
  456. parser.add_argument('--quad', action='store_true', help='quad dataloader')
  457. parser.add_argument('--cos-lr', action='store_true', help='cosine LR scheduler')
  458. parser.add_argument('--label-smoothing', type=float, default=0.0, help='Label smoothing epsilon')
  459. parser.add_argument('--patience', type=int, default=100, help='EarlyStopping patience (epochs without improvement)')
  460. parser.add_argument('--freeze', nargs='+', type=int, default=[0], help='Freeze layers: backbone=10, first3=0 1 2')
  461. parser.add_argument('--save-period', type=int, default=-1, help='Save checkpoint every x epochs (disabled if < 1)')
  462. parser.add_argument('--local_rank', type=int, default=-1, help='DDP parameter, do not modify')
  463. # Weights & Biases arguments
  464. parser.add_argument('--entity', default=None, help='W&B: Entity')
  465. parser.add_argument('--upload_dataset', nargs='?', const=True, default=False, help='W&B: Upload data, "val" option')
  466. parser.add_argument('--bbox_interval', type=int, default=-1, help='W&B: Set bounding-box image logging interval')
  467. parser.add_argument('--artifact_alias', type=str, default='latest', help='W&B: Version of dataset artifact to use')
  468. opt = parser.parse_known_args()[0] if known else parser.parse_args()
  469. return opt
  470. def main(opt, callbacks=Callbacks()):
  471. # Checks
  472. if RANK in [-1, 0]:
  473. print_args(FILE.stem, opt)
  474. check_git_status()
  475. check_requirements(exclude=['thop'])
  476. # Resume
  477. if opt.resume and not check_wandb_resume(opt) and not opt.evolve: # resume an interrupted run
  478. ckpt = opt.resume if isinstance(opt.resume, str) else get_latest_run() # specified or most recent path
  479. assert os.path.isfile(ckpt), 'ERROR: --resume checkpoint does not exist'
  480. with open(Path(ckpt).parent.parent / 'opt.yaml', errors='ignore') as f:
  481. opt = argparse.Namespace(**yaml.safe_load(f)) # replace
  482. opt.cfg, opt.weights, opt.resume = '', ckpt, True # reinstate
  483. LOGGER.info(f'Resuming training from {ckpt}')
  484. else:
  485. opt.data, opt.cfg, opt.hyp, opt.weights, opt.project = \
  486. check_file(opt.data), check_yaml(opt.cfg), check_yaml(opt.hyp), str(opt.weights), str(opt.project) # checks
  487. assert len(opt.cfg) or len(opt.weights), 'either --cfg or --weights must be specified'
  488. if opt.evolve:
  489. if opt.project == str(ROOT / 'runs/train'): # if default project name, rename to runs/evolve
  490. opt.project = str(ROOT / 'runs/evolve')
  491. opt.exist_ok, opt.resume = opt.resume, False # pass resume to exist_ok and disable resume
  492. opt.save_dir = str(increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok))
  493. # DDP mode
  494. device = select_device(opt.device, batch_size=opt.batch_size)
  495. if LOCAL_RANK != -1:
  496. msg = 'is not compatible with YOLOv5 Multi-GPU DDP training'
  497. assert not opt.image_weights, f'--image-weights {msg}'
  498. assert not opt.evolve, f'--evolve {msg}'
  499. assert opt.batch_size != -1, f'AutoBatch with --batch-size -1 {msg}, please pass a valid --batch-size'
  500. assert opt.batch_size % WORLD_SIZE == 0, f'--batch-size {opt.batch_size} must be multiple of WORLD_SIZE'
  501. assert torch.cuda.device_count() > LOCAL_RANK, 'insufficient CUDA devices for DDP command'
  502. torch.cuda.set_device(LOCAL_RANK)
  503. device = torch.device('cuda', LOCAL_RANK)
  504. dist.init_process_group(backend="nccl" if dist.is_nccl_available() else "gloo")
  505. # Train
  506. if not opt.evolve:
  507. train(opt.hyp, opt, device, callbacks)
  508. if WORLD_SIZE > 1 and RANK == 0:
  509. LOGGER.info('Destroying process group... ')
  510. dist.destroy_process_group()
  511. # Evolve hyperparameters (optional)
  512. else:
  513. # Hyperparameter evolution metadata (mutation scale 0-1, lower_limit, upper_limit)
  514. meta = {'lr0': (1, 1e-5, 1e-1), # initial learning rate (SGD=1E-2, Adam=1E-3)
  515. 'lrf': (1, 0.01, 1.0), # final OneCycleLR learning rate (lr0 * lrf)
  516. 'momentum': (0.3, 0.6, 0.98), # SGD momentum/Adam beta1
  517. 'weight_decay': (1, 0.0, 0.001), # optimizer weight decay
  518. 'warmup_epochs': (1, 0.0, 5.0), # warmup epochs (fractions ok)
  519. 'warmup_momentum': (1, 0.0, 0.95), # warmup initial momentum
  520. 'warmup_bias_lr': (1, 0.0, 0.2), # warmup initial bias lr
  521. 'box': (1, 0.02, 0.2), # box loss gain
  522. 'cls': (1, 0.2, 4.0), # cls loss gain
  523. 'cls_pw': (1, 0.5, 2.0), # cls BCELoss positive_weight
  524. 'obj': (1, 0.2, 4.0), # obj loss gain (scale with pixels)
  525. 'obj_pw': (1, 0.5, 2.0), # obj BCELoss positive_weight
  526. 'iou_t': (0, 0.1, 0.7), # IoU training threshold
  527. 'anchor_t': (1, 2.0, 8.0), # anchor-multiple threshold
  528. 'anchors': (2, 2.0, 10.0), # anchors per output grid (0 to ignore)
  529. 'fl_gamma': (0, 0.0, 2.0), # focal loss gamma (efficientDet default gamma=1.5)
  530. 'hsv_h': (1, 0.0, 0.1), # image HSV-Hue augmentation (fraction)
  531. 'hsv_s': (1, 0.0, 0.9), # image HSV-Saturation augmentation (fraction)
  532. 'hsv_v': (1, 0.0, 0.9), # image HSV-Value augmentation (fraction)
  533. 'degrees': (1, 0.0, 45.0), # image rotation (+/- deg)
  534. 'translate': (1, 0.0, 0.9), # image translation (+/- fraction)
  535. 'scale': (1, 0.0, 0.9), # image scale (+/- gain)
  536. 'shear': (1, 0.0, 10.0), # image shear (+/- deg)
  537. 'perspective': (0, 0.0, 0.001), # image perspective (+/- fraction), range 0-0.001
  538. 'flipud': (1, 0.0, 1.0), # image flip up-down (probability)
  539. 'fliplr': (0, 0.0, 1.0), # image flip left-right (probability)
  540. 'mosaic': (1, 0.0, 1.0), # image mixup (probability)
  541. 'mixup': (1, 0.0, 1.0), # image mixup (probability)
  542. 'copy_paste': (1, 0.0, 1.0)} # segment copy-paste (probability)
  543. with open(opt.hyp, errors='ignore') as f:
  544. hyp = yaml.safe_load(f) # load hyps dict
  545. if 'anchors' not in hyp: # anchors commented in hyp.yaml
  546. hyp['anchors'] = 3
  547. opt.noval, opt.nosave, save_dir = True, True, Path(opt.save_dir) # only val/save final epoch
  548. # ei = [isinstance(x, (int, float)) for x in hyp.values()] # evolvable indices
  549. evolve_yaml, evolve_csv = save_dir / 'hyp_evolve.yaml', save_dir / 'evolve.csv'
  550. if opt.bucket:
  551. os.system(f'gsutil cp gs://{opt.bucket}/evolve.csv {evolve_csv}') # download evolve.csv if exists
  552. for _ in range(opt.evolve): # generations to evolve
  553. if evolve_csv.exists(): # if evolve.csv exists: select best hyps and mutate
  554. # Select parent(s)
  555. parent = 'single' # parent selection method: 'single' or 'weighted'
  556. x = np.loadtxt(evolve_csv, ndmin=2, delimiter=',', skiprows=1)
  557. n = min(5, len(x)) # number of previous results to consider
  558. x = x[np.argsort(-fitness(x))][:n] # top n mutations
  559. w = fitness(x) - fitness(x).min() + 1E-6 # weights (sum > 0)
  560. if parent == 'single' or len(x) == 1:
  561. # x = x[random.randint(0, n - 1)] # random selection
  562. x = x[random.choices(range(n), weights=w)[0]] # weighted selection
  563. elif parent == 'weighted':
  564. x = (x * w.reshape(n, 1)).sum(0) / w.sum() # weighted combination
  565. # Mutate
  566. mp, s = 0.8, 0.2 # mutation probability, sigma
  567. npr = np.random
  568. npr.seed(int(time.time()))
  569. g = np.array([meta[k][0] for k in hyp.keys()]) # gains 0-1
  570. ng = len(meta)
  571. v = np.ones(ng)
  572. while all(v == 1): # mutate until a change occurs (prevent duplicates)
  573. v = (g * (npr.random(ng) < mp) * npr.randn(ng) * npr.random() * s + 1).clip(0.3, 3.0)
  574. for i, k in enumerate(hyp.keys()): # plt.hist(v.ravel(), 300)
  575. hyp[k] = float(x[i + 7] * v[i]) # mutate
  576. # Constrain to limits
  577. for k, v in meta.items():
  578. hyp[k] = max(hyp[k], v[1]) # lower limit
  579. hyp[k] = min(hyp[k], v[2]) # upper limit
  580. hyp[k] = round(hyp[k], 5) # significant digits
  581. # Train mutation
  582. results = train(hyp.copy(), opt, device, callbacks)
  583. callbacks = Callbacks()
  584. # Write mutation results
  585. print_mutation(results, hyp.copy(), save_dir, opt.bucket)
  586. # Plot results
  587. plot_evolve(evolve_csv)
  588. LOGGER.info(f'Hyperparameter evolution finished {opt.evolve} generations\n'
  589. f"Results saved to {colorstr('bold', save_dir)}\n"
  590. f'Usage example: $ python train.py --hyp {evolve_yaml}')
  591. def run(**kwargs):
  592. # Usage: import train; train.run(data='coco128.yaml', imgsz=320, weights='yolov5m.pt')
  593. opt = parse_opt(True)
  594. for k, v in kwargs.items():
  595. setattr(opt, k, v)
  596. main(opt)
  597. return opt
  598. if __name__ == "__main__":
  599. opt = parse_opt()
  600. main(opt)