train.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924
  1. import argparse
  2. import logging
  3. import math
  4. import os
  5. import random
  6. import time
  7. from pathlib import Path
  8. from threading import Thread
  9. from warnings import warn
  10. import numpy as np
  11. import torch.distributed as dist
  12. import torch.nn as nn
  13. import torch.nn.functional as F
  14. import torch.optim as optim
  15. import torch.optim.lr_scheduler as lr_scheduler
  16. import torch.utils.data
  17. import yaml
  18. from torch.cuda import amp
  19. from torch.nn.parallel import DistributedDataParallel as DDP
  20. from torch.utils.tensorboard import SummaryWriter
  21. from tqdm import tqdm
  22. import test # import test.py to get mAP after each epoch
  23. from models.experimental import attempt_load
  24. from models.yolo import Model
  25. from utils.autoanchor import check_anchors
  26. from utils.face_datasets import create_dataloader
  27. from utils.general import (
  28. labels_to_class_weights,
  29. increment_path,
  30. labels_to_image_weights,
  31. init_seeds,
  32. fitness,
  33. strip_optimizer,
  34. get_latest_run,
  35. check_dataset,
  36. check_file,
  37. check_img_size,
  38. print_mutation,
  39. set_logging,
  40. )
  41. from utils.google_utils import attempt_download
  42. from utils.loss import compute_loss
  43. from utils.plots import plot_images, plot_labels, plot_results, plot_evolution
  44. from utils.torch_utils import (
  45. ModelEMA,
  46. select_device,
  47. intersect_dicts,
  48. torch_distributed_zero_first,
  49. )
  50. logger = logging.getLogger(__name__)
  51. begin_save = 1
  52. try:
  53. import wandb
  54. except ImportError:
  55. wandb = None
  56. logger.info(
  57. "Install Weights & Biases for experiment logging via 'pip install wandb' (recommended)"
  58. )
  59. def train(hyp, opt, device, tb_writer=None, wandb=None):
  60. logger.info(f"Hyperparameters {hyp}")
  61. save_dir, epochs, batch_size, total_batch_size, weights, rank = (
  62. Path(opt.save_dir),
  63. opt.epochs,
  64. opt.batch_size,
  65. opt.total_batch_size,
  66. opt.weights,
  67. opt.global_rank,
  68. )
  69. # Directories
  70. wdir = save_dir / "weights"
  71. wdir.mkdir(parents=True, exist_ok=True) # make dir
  72. last = wdir / "last.pt"
  73. best = wdir / "best.pt"
  74. results_file = save_dir / "results.txt"
  75. # Save run settings
  76. with open(save_dir / "hyp.yaml", "w") as f:
  77. yaml.dump(hyp, f, sort_keys=False)
  78. with open(save_dir / "opt.yaml", "w") as f:
  79. yaml.dump(vars(opt), f, sort_keys=False)
  80. # Configure
  81. plots = not opt.evolve # create plots
  82. cuda = device.type != "cpu"
  83. init_seeds(2 + rank)
  84. with open(opt.data) as f:
  85. data_dict = yaml.load(f, Loader=yaml.FullLoader) # data dict
  86. with torch_distributed_zero_first(rank):
  87. check_dataset(data_dict) # check
  88. train_path = data_dict["train"]
  89. test_path = data_dict["val"]
  90. nc = 1 if opt.single_cls else int(data_dict["nc"]) # number of classes
  91. names = (
  92. ["item"]
  93. if opt.single_cls and len(data_dict["names"]) != 1
  94. else data_dict["names"]
  95. ) # class names
  96. assert len(names) == nc, "%g names found for nc=%g dataset in %s" % (
  97. len(names),
  98. nc,
  99. opt.data,
  100. ) # check
  101. # Model
  102. pretrained = weights.endswith(".pt")
  103. if pretrained:
  104. with torch_distributed_zero_first(rank):
  105. attempt_download(weights) # download if not found locally
  106. ckpt = torch.load(weights, map_location=device) # load checkpoint
  107. if hyp.get("anchors"):
  108. ckpt["model"].yaml["anchors"] = round(hyp["anchors"]) # force autoanchor
  109. model = Model(opt.cfg or ckpt["model"].yaml, ch=3, nc=nc).to(device) # create
  110. exclude = ["anchor"] if opt.cfg or hyp.get("anchors") else [] # exclude keys
  111. state_dict = ckpt["model"].float().state_dict() # to FP32
  112. state_dict = intersect_dicts(
  113. state_dict, model.state_dict(), exclude=exclude
  114. ) # intersect
  115. model.load_state_dict(state_dict, strict=False) # load
  116. logger.info(
  117. "Transferred %g/%g items from %s"
  118. % (len(state_dict), len(model.state_dict()), weights)
  119. ) # report
  120. else:
  121. model = Model(opt.cfg, ch=3, nc=nc).to(device) # create
  122. # Freeze
  123. freeze = [] # parameter names to freeze (full or partial)
  124. for k, v in model.named_parameters():
  125. v.requires_grad = True # train all layers
  126. if any(x in k for x in freeze):
  127. print("freezing %s" % k)
  128. v.requires_grad = False
  129. # Optimizer
  130. nbs = 64 # nominal batch size
  131. accumulate = max(
  132. round(nbs / total_batch_size), 1
  133. ) # accumulate loss before optimizing
  134. hyp["weight_decay"] *= total_batch_size * accumulate / nbs # scale weight_decay
  135. pg0, pg1, pg2 = [], [], [] # optimizer parameter groups
  136. for k, v in model.named_modules():
  137. if hasattr(v, "bias") and isinstance(v.bias, nn.Parameter):
  138. pg2.append(v.bias) # biases
  139. if isinstance(v, nn.BatchNorm2d):
  140. pg0.append(v.weight) # no decay
  141. elif hasattr(v, "weight") and isinstance(v.weight, nn.Parameter):
  142. pg1.append(v.weight) # apply decay
  143. if opt.adam:
  144. optimizer = optim.Adam(
  145. pg0, lr=hyp["lr0"], betas=(hyp["momentum"], 0.999)
  146. ) # adjust beta1 to momentum
  147. else:
  148. optimizer = optim.SGD(
  149. pg0, lr=hyp["lr0"], momentum=hyp["momentum"], nesterov=True
  150. )
  151. optimizer.add_param_group(
  152. {"params": pg1, "weight_decay": hyp["weight_decay"]}
  153. ) # add pg1 with weight_decay
  154. optimizer.add_param_group({"params": pg2}) # add pg2 (biases)
  155. logger.info(
  156. "Optimizer groups: %g .bias, %g conv.weight, %g other"
  157. % (len(pg2), len(pg1), len(pg0))
  158. )
  159. del pg0, pg1, pg2
  160. # Scheduler https://arxiv.org/pdf/1812.01187.pdf
  161. # https://pytorch.org/docs/stable/_modules/torch/optim/lr_scheduler.html#OneCycleLR
  162. lf = (
  163. lambda x: ((1 + math.cos(x * math.pi / epochs)) / 2) * (1 - hyp["lrf"])
  164. + hyp["lrf"]
  165. ) # cosine
  166. scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf)
  167. # plot_lr_scheduler(optimizer, scheduler, epochs)
  168. # Logging
  169. if wandb and wandb.run is None:
  170. opt.hyp = hyp # add hyperparameters
  171. wandb_run = wandb.init(
  172. config=opt,
  173. resume="allow",
  174. project="YOLOv5" if opt.project == "runs/train" else Path(opt.project).stem,
  175. name=save_dir.stem,
  176. id=ckpt.get("wandb_id") if "ckpt" in locals() else None,
  177. )
  178. loggers = {"wandb": wandb} # loggers dict
  179. # Resume
  180. start_epoch, best_fitness = 0, 0.0
  181. if pretrained:
  182. # Optimizer
  183. if ckpt["optimizer"] is not None:
  184. optimizer.load_state_dict(ckpt["optimizer"])
  185. best_fitness = 0
  186. # Results
  187. if ckpt.get("training_results") is not None:
  188. with open(results_file, "w") as file:
  189. file.write(ckpt["training_results"]) # write results.txt
  190. # Epochs
  191. start_epoch = ckpt["epoch"] + 1
  192. if opt.resume:
  193. assert start_epoch > 0, (
  194. "%s training to %g epochs is finished, nothing to resume."
  195. % (weights, epochs)
  196. )
  197. if epochs < start_epoch:
  198. logger.info(
  199. "%s has been trained for %g epochs. Fine-tuning for %g additional epochs."
  200. % (weights, ckpt["epoch"], epochs)
  201. )
  202. epochs += ckpt["epoch"] # finetune additional epochs
  203. del ckpt, state_dict
  204. # Image sizes
  205. gs = int(max(model.stride)) # grid size (max stride)
  206. imgsz, imgsz_test = [
  207. check_img_size(x, gs) for x in opt.img_size
  208. ] # verify imgsz are gs-multiples
  209. # DP mode
  210. if cuda and rank == -1 and torch.cuda.device_count() > 1:
  211. model = torch.nn.DataParallel(model)
  212. # SyncBatchNorm
  213. if opt.sync_bn and cuda and rank != -1:
  214. model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model).to(device)
  215. logger.info("Using SyncBatchNorm()")
  216. # EMA
  217. ema = ModelEMA(model) if rank in [-1, 0] else None
  218. # DDP mode
  219. if cuda and rank != -1:
  220. model = DDP(model, device_ids=[opt.local_rank], output_device=opt.local_rank)
  221. # Trainloader
  222. dataloader, dataset = create_dataloader(
  223. train_path,
  224. imgsz,
  225. batch_size,
  226. gs,
  227. opt,
  228. hyp=hyp,
  229. augment=True,
  230. cache=opt.cache_images,
  231. rect=opt.rect,
  232. rank=rank,
  233. world_size=opt.world_size,
  234. workers=opt.workers,
  235. image_weights=opt.image_weights,
  236. )
  237. mlc = np.concatenate(dataset.labels, 0)[:, 0].max() # max label class
  238. nb = len(dataloader) # number of batches
  239. assert mlc < nc, (
  240. "Label class %g exceeds nc=%g in %s. Possible class labels are 0-%g"
  241. % (mlc, nc, opt.data, nc - 1)
  242. )
  243. # Process 0
  244. if rank in [-1, 0]:
  245. ema.updates = start_epoch * nb // accumulate # set EMA updates
  246. testloader = create_dataloader(
  247. test_path,
  248. imgsz_test,
  249. total_batch_size,
  250. gs,
  251. opt, # testloader
  252. hyp=hyp,
  253. cache=opt.cache_images and not opt.notest,
  254. rect=True,
  255. rank=-1,
  256. world_size=opt.world_size,
  257. workers=opt.workers,
  258. pad=0.5,
  259. )[0]
  260. if not opt.resume:
  261. labels = np.concatenate(dataset.labels, 0)
  262. c = torch.tensor(labels[:, 0]) # classes
  263. # cf = torch.bincount(c.long(), minlength=nc) + 1. # frequency
  264. # model._initialize_biases(cf.to(device))
  265. if plots:
  266. plot_labels(labels, save_dir, loggers)
  267. if tb_writer:
  268. tb_writer.add_histogram("classes", c, 0)
  269. # Anchors
  270. if not opt.noautoanchor:
  271. check_anchors(dataset, model=model, thr=hyp["anchor_t"], imgsz=imgsz)
  272. # Model parameters
  273. hyp["cls"] *= nc / 80.0 # scale coco-tuned hyp['cls'] to current dataset
  274. model.nc = nc # attach number of classes to model
  275. model.hyp = hyp # attach hyperparameters to model
  276. model.gr = 1.0 # iou loss ratio (obj_loss = 1.0 or iou)
  277. model.class_weights = (
  278. labels_to_class_weights(dataset.labels, nc).to(device) * nc
  279. ) # attach class weights
  280. model.names = names
  281. # Start training
  282. t0 = time.time()
  283. nw = max(
  284. round(hyp["warmup_epochs"] * nb), 1000
  285. ) # number of warmup iterations, max(3 epochs, 1k iterations)
  286. # nw = min(nw, (epochs - start_epoch) / 2 * nb) # limit warmup to < 1/2 of training
  287. maps = np.zeros(nc) # mAP per class
  288. results = (0, 0, 0, 0, 0, 0, 0) # P, R, mAP@.5, mAP@.5-.95, val_loss(box, obj, cls)
  289. scheduler.last_epoch = start_epoch - 1 # do not move
  290. scaler = amp.GradScaler(enabled=cuda)
  291. logger.info(
  292. "Image sizes %g train, %g test\n"
  293. "Using %g dataloader workers\nLogging results to %s\n"
  294. "Starting training for %g epochs..."
  295. % (imgsz, imgsz_test, dataloader.num_workers, save_dir, epochs)
  296. )
  297. for epoch in range(
  298. start_epoch, epochs
  299. ): # epoch ------------------------------------------------------------------
  300. model.train()
  301. # Update image weights (optional)
  302. if opt.image_weights:
  303. # Generate indices
  304. if rank in [-1, 0]:
  305. cw = (
  306. model.class_weights.cpu().numpy() * (1 - maps) ** 2 / nc
  307. ) # class weights
  308. iw = labels_to_image_weights(
  309. dataset.labels, nc=nc, class_weights=cw
  310. ) # image weights
  311. dataset.indices = random.choices(
  312. range(dataset.n), weights=iw, k=dataset.n
  313. ) # rand weighted idx
  314. # Broadcast if DDP
  315. if rank != -1:
  316. indices = (
  317. torch.tensor(dataset.indices)
  318. if rank == 0
  319. else torch.zeros(dataset.n)
  320. ).int()
  321. dist.broadcast(indices, 0)
  322. if rank != 0:
  323. dataset.indices = indices.cpu().numpy()
  324. # Update mosaic border
  325. # b = int(random.uniform(0.25 * imgsz, 0.75 * imgsz + gs) // gs * gs)
  326. # dataset.mosaic_border = [b - imgsz, -b] # height, width borders
  327. mloss = torch.zeros(5, device=device) # mean losses
  328. if rank != -1:
  329. dataloader.sampler.set_epoch(epoch)
  330. pbar = enumerate(dataloader)
  331. logger.info(
  332. ("\n" + "%10s" * 9)
  333. % (
  334. "Epoch",
  335. "gpu_mem",
  336. "box",
  337. "obj",
  338. "cls",
  339. "landmark",
  340. "total",
  341. "targets",
  342. "img_size",
  343. )
  344. )
  345. if rank in [-1, 0]:
  346. pbar = tqdm(pbar, total=nb) # progress bar
  347. optimizer.zero_grad()
  348. for (
  349. i,
  350. (imgs, targets, paths, _),
  351. ) in (
  352. pbar
  353. ): # batch -------------------------------------------------------------
  354. ni = i + nb * epoch # number integrated batches (since train start)
  355. imgs = (
  356. imgs.to(device, non_blocking=True).float() / 255.0
  357. ) # uint8 to float32, 0-255 to 0.0-1.0
  358. # Warmup
  359. if ni <= nw:
  360. xi = [0, nw] # x interp
  361. # model.gr = np.interp(ni, xi, [0.0, 1.0]) # iou loss ratio (obj_loss = 1.0 or iou)
  362. accumulate = max(
  363. 1, np.interp(ni, xi, [1, nbs / total_batch_size]).round()
  364. )
  365. for j, x in enumerate(optimizer.param_groups):
  366. # bias lr falls from 0.1 to lr0, all other lrs rise from 0.0 to lr0
  367. x["lr"] = np.interp(
  368. ni,
  369. xi,
  370. [
  371. hyp["warmup_bias_lr"] if j == 2 else 0.0,
  372. x["initial_lr"] * lf(epoch),
  373. ],
  374. )
  375. if "momentum" in x:
  376. x["momentum"] = np.interp(
  377. ni, xi, [hyp["warmup_momentum"], hyp["momentum"]]
  378. )
  379. # Multi-scale
  380. if opt.multi_scale:
  381. sz = random.randrange(imgsz * 0.5, imgsz * 1.5 + gs) // gs * gs # size
  382. sf = sz / max(imgs.shape[2:]) # scale factor
  383. if sf != 1:
  384. ns = [
  385. math.ceil(x * sf / gs) * gs for x in imgs.shape[2:]
  386. ] # new shape (stretched to gs-multiple)
  387. imgs = F.interpolate(
  388. imgs, size=ns, mode="bilinear", align_corners=False
  389. )
  390. # Forward
  391. with amp.autocast(enabled=cuda):
  392. pred = model(imgs) # forward
  393. loss, loss_items = compute_loss(
  394. pred, targets.to(device), model
  395. ) # loss scaled by batch_size
  396. if rank != -1:
  397. loss *= (
  398. opt.world_size
  399. ) # gradient averaged between devices in DDP mode
  400. # Backward
  401. scaler.scale(loss).backward()
  402. # Optimize
  403. if ni % accumulate == 0:
  404. scaler.step(optimizer) # optimizer.step
  405. scaler.update()
  406. optimizer.zero_grad()
  407. if ema:
  408. ema.update(model)
  409. # Print
  410. if rank in [-1, 0]:
  411. mloss = (mloss * i + loss_items) / (i + 1) # update mean losses
  412. mem = "%.3gG" % (
  413. torch.cuda.memory_reserved() / 1e9
  414. if torch.cuda.is_available()
  415. else 0
  416. ) # (GB)
  417. s = ("%10s" * 2 + "%10.4g" * 7) % (
  418. "%g/%g" % (epoch, epochs - 1),
  419. mem,
  420. *mloss,
  421. targets.shape[0],
  422. imgs.shape[-1],
  423. )
  424. pbar.set_description(s)
  425. # Plot
  426. if plots and ni < 3:
  427. f = save_dir / f"train_batch{ni}.jpg" # filename
  428. Thread(
  429. target=plot_images, args=(imgs, targets, paths, f), daemon=True
  430. ).start()
  431. # if tb_writer:
  432. # tb_writer.add_image(f, result, dataformats='HWC', global_step=epoch)
  433. # tb_writer.add_graph(model, imgs) # add model to tensorboard
  434. elif plots and ni == 3 and wandb:
  435. wandb.log(
  436. {
  437. "Mosaics": [
  438. wandb.Image(str(x), caption=x.name)
  439. for x in save_dir.glob("train*.jpg")
  440. ]
  441. }
  442. )
  443. # end batch ------------------------------------------------------------------------------------------------
  444. # end epoch ----------------------------------------------------------------------------------------------------
  445. # Scheduler
  446. lr = [x["lr"] for x in optimizer.param_groups] # for tensorboard
  447. scheduler.step()
  448. # DDP process 0 or single-GPU
  449. if rank in [-1, 0] and epoch > begin_save:
  450. # mAP
  451. if ema:
  452. ema.update_attr(
  453. model,
  454. include=[
  455. "yaml",
  456. "nc",
  457. "hyp",
  458. "gr",
  459. "names",
  460. "stride",
  461. "class_weights",
  462. ],
  463. )
  464. final_epoch = epoch + 1 == epochs
  465. if not opt.notest or final_epoch: # Calculate mAP
  466. results, maps, times = test.test(
  467. opt.data,
  468. batch_size=total_batch_size,
  469. imgsz=imgsz_test,
  470. model=ema.ema,
  471. single_cls=opt.single_cls,
  472. dataloader=testloader,
  473. save_dir=save_dir,
  474. plots=False,
  475. log_imgs=opt.log_imgs if wandb else 0,
  476. )
  477. # Write
  478. with open(results_file, "a") as f:
  479. f.write(
  480. s + "%10.4g" * 7 % results + "\n"
  481. ) # P, R, mAP@.5, mAP@.5-.95, val_loss(box, obj, cls)
  482. if len(opt.name) and opt.bucket:
  483. os.system(
  484. "gsutil cp %s gs://%s/results/results%s.txt"
  485. % (results_file, opt.bucket, opt.name)
  486. )
  487. # Log
  488. tags = [
  489. "train/box_loss",
  490. "train/obj_loss",
  491. "train/cls_loss", # train loss
  492. "metrics/precision",
  493. "metrics/recall",
  494. "metrics/mAP_0.5",
  495. "metrics/mAP_0.5:0.95",
  496. "val/box_loss",
  497. "val/obj_loss",
  498. "val/cls_loss", # val loss
  499. "x/lr0",
  500. "x/lr1",
  501. "x/lr2",
  502. ] # params
  503. for x, tag in zip(list(mloss[:-1]) + list(results) + lr, tags):
  504. if tb_writer:
  505. tb_writer.add_scalar(tag, x, epoch) # tensorboard
  506. if wandb:
  507. wandb.log({tag: x}) # W&B
  508. # Update best mAP
  509. fi = fitness(
  510. np.array(results).reshape(1, -1)
  511. ) # weighted combination of [P, R, mAP@.5, mAP@.5-.95]
  512. if fi > best_fitness:
  513. best_fitness = fi
  514. # Save model
  515. save = (not opt.nosave) or (final_epoch and not opt.evolve)
  516. if save:
  517. with open(results_file, "r") as f: # create checkpoint
  518. ckpt = {
  519. "epoch": epoch,
  520. "best_fitness": best_fitness,
  521. "training_results": f.read(),
  522. "model": ema.ema,
  523. "optimizer": None if final_epoch else optimizer.state_dict(),
  524. "wandb_id": wandb_run.id if wandb else None,
  525. }
  526. # Save last, best and delete
  527. torch.save(ckpt, last)
  528. if best_fitness == fi:
  529. ckpt_best = {
  530. "epoch": epoch,
  531. "best_fitness": best_fitness,
  532. # 'training_results': f.read(),
  533. "model": ema.ema,
  534. # 'optimizer': None if final_epoch else optimizer.state_dict(),
  535. # 'wandb_id': wandb_run.id if wandb else None
  536. }
  537. torch.save(ckpt_best, best)
  538. del ckpt
  539. # end epoch ----------------------------------------------------------------------------------------------------
  540. # end training
  541. if rank in [-1, 0]:
  542. # Strip optimizers
  543. final = best if best.exists() else last # final model
  544. for f in [last, best]:
  545. if f.exists():
  546. strip_optimizer(f) # strip optimizers
  547. if opt.bucket:
  548. os.system(f"gsutil cp {final} gs://{opt.bucket}/weights") # upload
  549. # Plots
  550. if plots:
  551. plot_results(save_dir=save_dir) # save as results.png
  552. if wandb:
  553. files = [
  554. "results.png",
  555. "precision_recall_curve.png",
  556. "confusion_matrix.png",
  557. ]
  558. wandb.log(
  559. {
  560. "Results": [
  561. wandb.Image(str(save_dir / f), caption=f)
  562. for f in files
  563. if (save_dir / f).exists()
  564. ]
  565. }
  566. )
  567. if opt.log_artifacts:
  568. wandb.log_artifact(
  569. artifact_or_path=str(final), type="model", name=save_dir.stem
  570. )
  571. # Test best.pt
  572. logger.info(
  573. "%g epochs completed in %.3f hours.\n"
  574. % (epoch - start_epoch + 1, (time.time() - t0) / 3600)
  575. )
  576. if opt.data.endswith("coco.yaml") and nc == 80: # if COCO
  577. for conf, iou, save_json in (
  578. [0.25, 0.45, False],
  579. [0.001, 0.65, True],
  580. ): # speed, mAP tests
  581. results, _, _ = test.test(
  582. opt.data,
  583. batch_size=total_batch_size,
  584. imgsz=imgsz_test,
  585. conf_thres=conf,
  586. iou_thres=iou,
  587. model=attempt_load(final, device).half(),
  588. single_cls=opt.single_cls,
  589. dataloader=testloader,
  590. save_dir=save_dir,
  591. save_json=save_json,
  592. plots=False,
  593. )
  594. else:
  595. dist.destroy_process_group()
  596. wandb.run.finish() if wandb and wandb.run else None
  597. torch.cuda.empty_cache()
  598. return results
  599. if __name__ == "__main__":
  600. parser = argparse.ArgumentParser()
  601. parser.add_argument(
  602. "--weights",
  603. type=str,
  604. default="weights/plate_detect.pt",
  605. help="initial weights path",
  606. )
  607. parser.add_argument(
  608. "--cfg", type=str, default="models/yolov5n-0.5.yaml", help="model.yaml path"
  609. )
  610. parser.add_argument(
  611. "--data", type=str, default="data/widerface.yaml", help="data.yaml path"
  612. )
  613. parser.add_argument(
  614. "--hyp", type=str, default="data/hyp.scratch.yaml", help="hyperparameters path"
  615. )
  616. parser.add_argument("--epochs", type=int, default=120)
  617. parser.add_argument(
  618. "--batch-size", type=int, default=32, help="total batch size for all GPUs"
  619. )
  620. parser.add_argument(
  621. "--img-size",
  622. nargs="+",
  623. type=int,
  624. default=[640, 640],
  625. help="[train, test] image sizes",
  626. )
  627. parser.add_argument("--rect", action="store_true", help="rectangular training")
  628. parser.add_argument(
  629. "--resume",
  630. nargs="?",
  631. const=True,
  632. default=False,
  633. help="resume most recent training",
  634. )
  635. parser.add_argument(
  636. "--nosave", action="store_true", help="only save final checkpoint"
  637. )
  638. parser.add_argument("--notest", action="store_true", help="only test final epoch")
  639. parser.add_argument(
  640. "--noautoanchor", action="store_true", help="disable autoanchor check"
  641. )
  642. parser.add_argument("--evolve", action="store_true", help="evolve hyperparameters")
  643. parser.add_argument("--bucket", type=str, default="", help="gsutil bucket")
  644. parser.add_argument(
  645. "--cache-images", action="store_true", help="cache images for faster training"
  646. )
  647. parser.add_argument(
  648. "--image-weights",
  649. action="store_true",
  650. help="use weighted image selection for training",
  651. )
  652. parser.add_argument(
  653. "--device", default="", help="cuda device, i.e. 0 or 0,1,2,3 or cpu"
  654. )
  655. parser.add_argument(
  656. "--multi-scale",
  657. action="store_true",
  658. default=True,
  659. help="vary img-size +/- 50%%",
  660. )
  661. parser.add_argument(
  662. "--single-cls",
  663. action="store_true",
  664. help="train multi-class data as single-class",
  665. )
  666. parser.add_argument(
  667. "--adam", action="store_true", help="use torch.optim.Adam() optimizer"
  668. )
  669. parser.add_argument(
  670. "--sync-bn",
  671. action="store_true",
  672. help="use SyncBatchNorm, only available in DDP mode",
  673. )
  674. parser.add_argument(
  675. "--local_rank", type=int, default=-1, help="DDP parameter, do not modify"
  676. )
  677. parser.add_argument(
  678. "--log-imgs",
  679. type=int,
  680. default=16,
  681. help="number of images for W&B logging, max 100",
  682. )
  683. parser.add_argument(
  684. "--log-artifacts",
  685. action="store_true",
  686. help="log artifacts, i.e. final trained model",
  687. )
  688. parser.add_argument(
  689. "--workers", type=int, default=4, help="maximum number of dataloader workers"
  690. )
  691. parser.add_argument("--project", default="runs/train", help="save to project/name")
  692. parser.add_argument("--name", default="exp", help="save to project/name")
  693. parser.add_argument(
  694. "--exist-ok",
  695. action="store_true",
  696. help="existing project/name ok, do not increment",
  697. )
  698. opt = parser.parse_args()
  699. # Set DDP variables
  700. opt.total_batch_size = opt.batch_size
  701. opt.world_size = int(os.environ["WORLD_SIZE"]) if "WORLD_SIZE" in os.environ else 1
  702. opt.global_rank = int(os.environ["RANK"]) if "RANK" in os.environ else -1
  703. set_logging(opt.global_rank)
  704. # if opt.global_rank in [-1, 0]:
  705. # check_git_status()
  706. # Resume
  707. if opt.resume:
  708. ckpt = (
  709. opt.resume if isinstance(opt.resume, str) else get_latest_run()
  710. )
  711. assert os.path.isfile(ckpt), "ERROR: --resume checkpoint does not exist"
  712. with open(Path(ckpt).parent.parent / "opt.yaml") as f:
  713. opt = argparse.Namespace(**yaml.load(f, Loader=yaml.FullLoader)) # replace
  714. opt.cfg, opt.weights, opt.resume = "", ckpt, True
  715. logger.info("Resuming training from %s" % ckpt)
  716. else:
  717. # opt.hyp = opt.hyp or ('hyp.finetune.yaml' if opt.weights else 'hyp.scratch.yaml')
  718. opt.data, opt.cfg, opt.hyp = (
  719. check_file(opt.data),
  720. check_file(opt.cfg),
  721. check_file(opt.hyp),
  722. ) # check files
  723. assert len(opt.cfg) or len(opt.weights), (
  724. "either --cfg or --weights must be specified"
  725. )
  726. opt.img_size.extend(
  727. [opt.img_size[-1]] * (2 - len(opt.img_size))
  728. ) # extend to 2 sizes (train, test)
  729. opt.name = "evolve" if opt.evolve else opt.name
  730. opt.save_dir = increment_path(
  731. Path(opt.project) / opt.name, exist_ok=opt.exist_ok | opt.evolve
  732. ) # increment run
  733. # DDP mode
  734. device = select_device(opt.device, batch_size=opt.batch_size)
  735. if opt.local_rank != -1:
  736. assert torch.cuda.device_count() > opt.local_rank
  737. torch.cuda.set_device(opt.local_rank)
  738. device = torch.device("cuda", opt.local_rank)
  739. dist.init_process_group(
  740. backend="nccl", init_method="env://"
  741. ) # distributed backend
  742. assert opt.batch_size % opt.world_size == 0, (
  743. "--batch-size must be multiple of CUDA device count"
  744. )
  745. opt.batch_size = opt.total_batch_size // opt.world_size
  746. # Hyperparameters
  747. with open(opt.hyp) as f:
  748. hyp = yaml.load(f, Loader=yaml.FullLoader) # load hyps
  749. if "box" not in hyp:
  750. warn(
  751. 'Compatibility: %s missing "box" which was renamed from "giou" in %s'
  752. % (opt.hyp, "https://github.com/ultralytics/yolov5/pull/1120")
  753. )
  754. hyp["box"] = hyp.pop("giou")
  755. # Train
  756. logger.info(opt)
  757. if not opt.evolve:
  758. tb_writer = None # init loggers
  759. if opt.global_rank in [-1, 0]:
  760. logger.info(
  761. f'Start Tensorboard with "tensorboard --logdir {opt.project}", view at http://localhost:6006/'
  762. )
  763. tb_writer = SummaryWriter(opt.save_dir) # Tensorboard
  764. train(hyp, opt, device, tb_writer, wandb)
  765. # Evolve hyperparameters (optional)
  766. else:
  767. # Hyperparameter evolution metadata (mutation scale 0-1, lower_limit, upper_limit)
  768. meta = {
  769. "lr0": (1, 1e-5, 1e-1), # initial learning rate (SGD=1E-2, Adam=1E-3)
  770. "lrf": (1, 0.01, 1.0), # final OneCycleLR learning rate (lr0 * lrf)
  771. "momentum": (0.3, 0.6, 0.98), # SGD momentum/Adam beta1
  772. "weight_decay": (1, 0.0, 0.001), # optimizer weight decay
  773. "warmup_epochs": (1, 0.0, 5.0), # warmup epochs (fractions ok)
  774. "warmup_momentum": (1, 0.0, 0.95), # warmup initial momentum
  775. "warmup_bias_lr": (1, 0.0, 0.2), # warmup initial bias lr
  776. "box": (1, 0.02, 0.2), # box loss gain
  777. "cls": (1, 0.2, 4.0), # cls loss gain
  778. "cls_pw": (1, 0.5, 2.0), # cls BCELoss positive_weight
  779. "obj": (1, 0.2, 4.0), # obj loss gain (scale with pixels)
  780. "obj_pw": (1, 0.5, 2.0), # obj BCELoss positive_weight
  781. "iou_t": (0, 0.1, 0.7), # IoU training threshold
  782. "anchor_t": (1, 2.0, 8.0), # anchor-multiple threshold
  783. "anchors": (2, 2.0, 10.0), # anchors per output grid (0 to ignore)
  784. "fl_gamma": (
  785. 0,
  786. 0.0,
  787. 2.0,
  788. ), # focal loss gamma (efficientDet default gamma=1.5)
  789. "hsv_h": (1, 0.0, 0.1), # image HSV-Hue augmentation (fraction)
  790. "hsv_s": (1, 0.0, 0.9), # image HSV-Saturation augmentation (fraction)
  791. "hsv_v": (1, 0.0, 0.9), # image HSV-Value augmentation (fraction)
  792. "degrees": (1, 0.0, 45.0), # image rotation (+/- deg)
  793. "translate": (1, 0.0, 0.9), # image translation (+/- fraction)
  794. "scale": (1, 0.0, 0.9), # image scale (+/- gain)
  795. "shear": (1, 0.0, 10.0), # image shear (+/- deg)
  796. "perspective": (
  797. 0,
  798. 0.0,
  799. 0.001,
  800. ), # image perspective (+/- fraction), range 0-0.001
  801. "flipud": (1, 0.0, 1.0), # image flip up-down (probability)
  802. "fliplr": (0, 0.0, 1.0), # image flip left-right (probability)
  803. "mosaic": (1, 0.0, 1.0), # image mixup (probability)
  804. "mixup": (1, 0.0, 1.0),
  805. } # image mixup (probability)
  806. assert opt.local_rank == -1, "DDP mode not implemented for --evolve"
  807. opt.notest, opt.nosave = True, True # only test/save final epoch
  808. # ei = [isinstance(x, (int, float)) for x in hyp.values()] # evolvable indices
  809. yaml_file = Path(opt.save_dir) / "hyp_evolved.yaml" # save best result here
  810. if opt.bucket:
  811. os.system(
  812. "gsutil cp gs://%s/evolve.txt ." % opt.bucket
  813. ) # download evolve.txt if exists
  814. for _ in range(300): # generations to evolve
  815. if Path(
  816. "evolve.txt"
  817. ).exists(): # if evolve.txt exists: select best hyps and mutate
  818. # Select parent(s)
  819. parent = "single" # parent selection method: 'single' or 'weighted'
  820. x = np.loadtxt("evolve.txt", ndmin=2)
  821. n = min(5, len(x)) # number of previous results to consider
  822. x = x[np.argsort(-fitness(x))][:n] # top n mutations
  823. w = fitness(x) - fitness(x).min() # weights
  824. if parent == "single" or len(x) == 1:
  825. # x = x[random.randint(0, n - 1)] # random selection
  826. x = x[random.choices(range(n), weights=w)[0]] # weighted selection
  827. elif parent == "weighted":
  828. x = (x * w.reshape(n, 1)).sum(0) / w.sum() # weighted combination
  829. # Mutate
  830. mp, s = 0.8, 0.2 # mutation probability, sigma
  831. npr = np.random
  832. npr.seed(int(time.time()))
  833. g = np.array([x[0] for x in meta.values()]) # gains 0-1
  834. ng = len(meta)
  835. v = np.ones(ng)
  836. while all(v == 1): # mutate until a change occurs (prevent duplicates)
  837. v = (
  838. g * (npr.random(ng) < mp) * npr.randn(ng) * npr.random() * s + 1
  839. ).clip(0.3, 3.0)
  840. for i, k in enumerate(hyp.keys()): # plt.hist(v.ravel(), 300)
  841. hyp[k] = float(x[i + 7] * v[i]) # mutate
  842. # Constrain to limits
  843. for k, v in meta.items():
  844. hyp[k] = max(hyp[k], v[1]) # lower limit
  845. hyp[k] = min(hyp[k], v[2]) # upper limit
  846. hyp[k] = round(hyp[k], 5) # significant digits
  847. # Train mutation
  848. results = train(hyp.copy(), opt, device, wandb=wandb)
  849. # Write mutation results
  850. print_mutation(hyp.copy(), results, yaml_file, opt.bucket)
  851. # Plot results
  852. plot_evolution(yaml_file)
  853. print(f'Hyperparameter evolution complete. Best results saved as: {yaml_file}\n'
  854. f'Command to train a new model with these hyperparameters: $ python train.py --hyp {yaml_file}')