当前位置:   article > 正文

基于depgraph的模型压缩——使用coco128+yolov5验证_depgraph: towards any structural pruning

depgraph: towards any structural pruning

参考论文是23年发表在cvpr的论文,DepGraph: Towards Any Structural Pruning,是一种结构化的模型压缩算法。凭记忆写的,可能会有些小问题...

一、torch_pruning环境搭建

1. 创建conda虚拟环境

conda create -n ModelPruning python=3.9

2. 到官网下载代码解压,cd到对应文件夹里安装环境

pip install torch-pruning 

3. 测试一下

  1. # 下载预训练模型
  2. wget https://github.com/VainF/Torch-Pruning/releases/download/v1.1.4/cifar10_resnet56.pth
  3. # 按提示装包
  4. # 把模型放到相应文件夹里,运行depgraph算法
  5. python benchmarks/main.py --mode prune --model resnet56 --batch-size 128 --restore models/trained/cifar10_resnet56.pth --dataset cifar10 --method group_sl --speed-up 2.11 --global-pruning

二、模型与数据集

仍然使用上面的conda环境

1. 下载yolov5的代码(官网),安装依赖

pip install -U -r requirements.txt

我的这条命令会报错找不到对应版本的包,通过关掉vpn解决了。

2. 下载预训练模型(yolov5s)与coco128数据集,放到yolov5的文件夹中

3. 各种命令

  1. # 训练
  2. python train.py --img 640 --batch 16 --epochs 5 --data ./data/coco128.yaml --cfg ./models/yolov5s.yaml --weights ./yolov5s.pt
  3. # 预测
  4. python detect.py --source inference/images/ --weights ./yolov5s.pt
  5. # 测试
  6. python val.py --weights yolov5s.pt --data coco128.yaml --img 640

三、使用depgraph算法压缩yolov5模型

  1. import argparse
  2. import copy
  3. import math
  4. import os
  5. import random
  6. import subprocess
  7. import sys
  8. import time
  9. from copy import deepcopy
  10. from datetime import datetime
  11. from pathlib import Path
  12. from functools import partial
  13. try:
  14. import comet_ml # must be imported before torch (if installed)
  15. except ImportError:
  16. comet_ml = None
  17. import numpy as np
  18. import torch
  19. import torch.distributed as dist
  20. import torch.nn as nn
  21. import yaml
  22. from torch.optim import lr_scheduler
  23. from tqdm import tqdm
  24. FILE = Path(__file__).resolve()
  25. ROOT = FILE.parents[0].parents[0] # YOLOv5 root directory
  26. if str(ROOT) not in sys.path:
  27. sys.path.append(str(ROOT)) # add ROOT to PATH
  28. ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative
  29. import val as validate # for end-of-epoch mAP
  30. from models.experimental import attempt_load
  31. from models.yolo import Model
  32. from utils.autoanchor import check_anchors
  33. from utils.autobatch import check_train_batch_size
  34. from utils.callbacks import Callbacks
  35. from utils.dataloaders import create_dataloader
  36. from utils.downloads import attempt_download, is_url
  37. from utils.general import (LOGGER, TQDM_BAR_FORMAT, check_amp, check_dataset, check_file, check_git_info,
  38. check_git_status, check_img_size, check_requirements, check_suffix, check_yaml, colorstr,
  39. get_latest_run, increment_path, init_seeds, intersect_dicts, labels_to_class_weights,
  40. labels_to_image_weights, methods, one_cycle, print_args, print_mutation, strip_optimizer,
  41. yaml_save)
  42. from utils.loggers import Loggers
  43. from utils.loggers.comet.comet_utils import check_comet_resume
  44. from utils.loss import ComputeLoss
  45. from utils.metrics import fitness
  46. from utils.plots import plot_evolve
  47. from utils.torch_utils import (EarlyStopping, ModelEMA, de_parallel, select_device, smart_DDP, smart_optimizer,
  48. smart_resume, torch_distributed_zero_first)
  49. LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html
  50. RANK = int(os.getenv('RANK', -1))
  51. WORLD_SIZE = int(os.getenv('WORLD_SIZE', 1))
  52. GIT_INFO = check_git_info()
  53. ## global variables ##
  54. nc = 1 # number of classes
  55. w = '' # path of pretrained model
  56. last = '' # path of last.pt
  57. best = '' # path of best.pt
  58. plots = True # figure
  59. cuda =True # if using gpu
  60. names = {} # class name
  61. is_coco = False # if val with coco dataset
  62. amp = True # if training/valuating model with amp
  63. batch_size = 64
  64. loggers = None # logs
  65. train_path = ''
  66. val_path = ''
  67. pretrained = True # if load pretained model
  68. imgsz = 320
  69. ckpt = None # the loaded model
  70. csd = None # checkpoint state_dict as FP32
  71. labels = None # torch tensor
  72. gs = 0 # image size
  73. data_dict = ''
  74. compute_loss = None
  75. # 运行命令
  76. # python pruning/MagnitudePrunner_train_after_pruning.py --weights yolov5s.pt --data data/coco128.yaml --target-prune-rate 0.1 --epochs 50
  77. def preparing(hyp, opt, device, callbacks):
  78. global loggers, nc, last, best, plots, cuda, names, is_coco, train_path, val_path, ckpt, data_dict, w
  79. save_dir, epochs, batch_size, weights, single_cls, evolve, data, cfg, resume, noval, nosave, workers, freeze = \
  80. Path(opt.save_dir), opt.epochs, opt.batch_size, opt.weights, opt.single_cls, opt.evolve, opt.data, opt.cfg, \
  81. opt.resume, opt.noval, opt.nosave, opt.workers, opt.freeze
  82. callbacks.run('on_pretrain_routine_start')
  83. # Directories
  84. w = save_dir / 'weights' # weights dir
  85. (w.parent if evolve else w).mkdir(parents=True, exist_ok=True) # make dir
  86. last, best = w / 'last.pt', w / 'best.pt'
  87. # Hyperparameters
  88. if isinstance(hyp, str):
  89. with open(hyp, errors='ignore') as f:
  90. hyp = yaml.safe_load(f) # load hyps dict
  91. LOGGER.info(colorstr('hyperparameters: ') + ', '.join(f'{k}={v}' for k, v in hyp.items()))
  92. opt.hyp = hyp.copy() # for saving hyps to checkpoints
  93. # Save run settings
  94. if not evolve:
  95. yaml_save(save_dir / 'hyp.yaml', hyp)
  96. yaml_save(save_dir / 'opt.yaml', vars(opt))
  97. # Loggers
  98. data_dict = None
  99. if RANK in {-1, 0}:
  100. loggers = Loggers(save_dir, weights, opt, hyp, LOGGER) # loggers instance
  101. # Register actions
  102. for k in methods(loggers):
  103. callbacks.register_action(k, callback=getattr(loggers, k))
  104. # Process custom dataset artifact link
  105. data_dict = loggers.remote_dataset
  106. if resume: # If resuming runs from remote artifact
  107. weights, epochs, hyp, batch_size = opt.weights, opt.epochs, opt.hyp, opt.batch_size
  108. # Config
  109. plots = not evolve and not opt.noplots # create plots
  110. cuda = device.type != 'cpu'
  111. init_seeds(opt.seed + 1 + RANK, deterministic=True)
  112. with torch_distributed_zero_first(LOCAL_RANK):
  113. data_dict = data_dict or check_dataset(data) # check if None
  114. train_path, val_path = data_dict['train'], data_dict['val']
  115. nc = 1 if single_cls else int(data_dict['nc']) # number of classes
  116. names = {0: 'item'} if single_cls and len(data_dict['names']) != 1 else data_dict['names'] # class names
  117. is_coco = isinstance(val_path, str) and val_path.endswith('coco/val2017.txt') # COCO dataset
  118. def load_model(hyp, opt, device):
  119. weights, cfg, resume, freeze, single_cls = \
  120. opt.weights, opt.cfg, opt.resume, opt.freeze, opt.single_cls
  121. global pretrained, ckpt, csd
  122. # Model
  123. check_suffix(weights, '.pt') # check weights
  124. pretrained = weights.endswith('.pt')
  125. if pretrained:
  126. with torch_distributed_zero_first(LOCAL_RANK):
  127. weights = attempt_download(weights) # download if not found locally
  128. ckpt = torch.load(weights, map_location='cpu') # load checkpoint to CPU to avoid CUDA memory leak
  129. model = Model(cfg or ckpt['model'].yaml, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device) # create
  130. exclude = ['anchor'] if (cfg or hyp.get('anchors')) and not resume else [] # exclude keys
  131. csd = ckpt['model'].float().state_dict() # checkpoint state_dict as FP32
  132. csd = intersect_dicts(csd, model.state_dict(), exclude=exclude) # intersect
  133. model.load_state_dict(csd, strict=False) # load
  134. LOGGER.info(f'Transferred {len(csd)}/{len(model.state_dict())} items from {weights}') # report
  135. else:
  136. model = Model(cfg, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device) # create
  137. global amp
  138. amp = check_amp(model) # check AMP
  139. # Freeze
  140. freeze = [f'model.{x}.' for x in (freeze if len(freeze) > 1 else range(freeze[0]))] # layers to freeze
  141. for k, v in model.named_parameters():
  142. v.requires_grad = True # train all layers
  143. # v.register_hook(lambda x: torch.nan_to_num(x)) # NaN to 0 (commented for erratic training results)
  144. if any(x in k for x in freeze):
  145. LOGGER.info(f'freezing {k}')
  146. v.requires_grad = False
  147. # DP mode
  148. if cuda and RANK == -1 and torch.cuda.device_count() > 1:
  149. LOGGER.warning(
  150. 'WARNING ⚠️ DP not recommended, use torch.distributed.run for best DDP Multi-GPU results.\n'
  151. 'See Multi-GPU Tutorial at https://docs.ultralytics.com/yolov5/tutorials/multi_gpu_training to get started.'
  152. )
  153. model = torch.nn.DataParallel(model)
  154. # SyncBatchNorm
  155. if opt.sync_bn and cuda and RANK != -1:
  156. model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model).to(device)
  157. LOGGER.info('Using SyncBatchNorm()')
  158. return model
  159. def load_dataset(hyp, opt, model):
  160. global batch_size, imgsz, labels, gs
  161. single_cls, workers, data, noval = opt.single_cls, opt.workers, opt.data, opt.noval
  162. # Image size
  163. gs = max(int(model.stride.max()), 32) # grid size (max stride)
  164. imgsz = check_img_size(opt.imgsz, gs, floor=gs * 2) # verify imgsz is gs-multiple
  165. # Batch size
  166. if RANK == -1 and batch_size == -1: # single-GPU only, estimate best batch size
  167. batch_size = check_train_batch_size(model, imgsz, amp)
  168. loggers.on_params_update({'batch_size': batch_size})
  169. # Trainloader
  170. train_loader, dataset = create_dataloader(train_path,
  171. imgsz,
  172. batch_size // WORLD_SIZE,
  173. gs,
  174. single_cls,
  175. hyp=hyp,
  176. augment=True,
  177. cache=None if opt.cache == 'val' else opt.cache,
  178. rect=opt.rect,
  179. rank=LOCAL_RANK,
  180. workers=workers,
  181. image_weights=opt.image_weights,
  182. quad=opt.quad,
  183. prefix=colorstr('train: '),
  184. shuffle=True,
  185. seed=opt.seed)
  186. labels = np.concatenate(dataset.labels, 0)
  187. mlc = int(labels[:, 0].max()) # max label class
  188. assert mlc < nc, f'Label class {mlc} exceeds nc={nc} in {data}. Possible class labels are 0-{nc - 1}'
  189. # Process 0
  190. val_loader = None
  191. if RANK in {-1, 0}:
  192. val_loader = create_dataloader(val_path,
  193. imgsz,
  194. batch_size // WORLD_SIZE * 2,
  195. gs,
  196. single_cls,
  197. hyp=hyp,
  198. cache=None if noval else opt.cache,
  199. rect=True,
  200. rank=-1,
  201. workers=workers * 2,
  202. pad=0.5,
  203. prefix=colorstr('val: '))[0]
  204. return dataset, train_loader, val_loader
  205. def get_pruner(opt, model, device):
  206. import torch_pruning as tp
  207. example_inputs = torch.randn(1, 3, imgsz, imgsz).to(device)
  208. ignored_layers = []
  209. from models.yolo import Detect
  210. for m in model.modules():
  211. if isinstance(m, (Detect)):
  212. ignored_layers.append(m.m)
  213. opt.sparsity_learning = True
  214. imp = tp.importance.GroupNormImportance(p=2, normalizer='max') # normalized by the maximum score for CIFAR
  215. pruner_entry = partial(tp.pruner.GroupNormPruner, reg=opt.reg, global_pruning=opt.global_pruning)
  216. pruning_ratio_dict = {}
  217. unwrapped_parameters = []
  218. pruner = pruner_entry(
  219. model,
  220. example_inputs,
  221. importance=imp,
  222. iterative_steps=20,
  223. pruning_ratio=1.0,
  224. pruning_ratio_dict=pruning_ratio_dict,
  225. max_pruning_ratio=0.5,
  226. ignored_layers=ignored_layers,
  227. unwrapped_parameters=unwrapped_parameters,
  228. )
  229. return pruner
  230. def pruning(opt, model, device, pruner):
  231. import torch_pruning as tp
  232. model.eval()
  233. # print(model)
  234. example_inputs = torch.randn(1, 3, imgsz, imgsz).to(device)
  235. base_macs, base_nparams = tp.utils.count_ops_and_params(model, example_inputs)
  236. # progress
  237. current_speed_up = 1
  238. while current_speed_up < opt.speed_up:
  239. pruner.step()
  240. pruned_ops, _ = tp.utils.count_ops_and_params(model, example_inputs=example_inputs)
  241. current_speed_up = float(base_macs) / pruned_ops
  242. if pruner.current_step == pruner.iterative_steps:
  243. break
  244. pruned_macs, pruned_nparams = tp.utils.count_ops_and_params(model, example_inputs)
  245. # print(model)
  246. print("Before Pruning: MACs=%f G, #Params=%f G" % (base_macs / 1e9, base_nparams / 1e9))
  247. print("After Pruning: MACs=%f G, #Params=%f G" % (pruned_macs / 1e9, pruned_nparams / 1e9))
  248. return model
  249. def eval(model, val_loader, op, callbacks, save_dir):
  250. single_cls = op.single_cls
  251. save_dir.mkdir(parents=True, exist_ok=True)
  252. LOGGER.info("eval model...")
  253. validate.run(
  254. data_dict,
  255. batch_size=batch_size // WORLD_SIZE * 2,
  256. imgsz=imgsz,
  257. model=model,
  258. iou_thres=0.65 if is_coco else 0.60, # best pycocotools at iou 0.65
  259. single_cls=single_cls,
  260. dataloader=val_loader,
  261. save_dir=save_dir,
  262. save_json=is_coco,
  263. verbose=True,
  264. plots=plots,
  265. callbacks=callbacks,
  266. compute_loss=compute_loss,
  267. )
  268. from enum import Enum
  269. class Mode(Enum):
  270. FINE_TUNING = False
  271. SPARSE_LEARNING = True
  272. def train(hyp, opt, device,
  273. model, dataset, train_loader, val_loader,
  274. callbacks,
  275. mode=Mode.FINE_TUNING,
  276. pruner=None): # hyp is path/to/hyp.yaml or hyp dictionary
  277. weights, epochs, resume, save_dir, single_cls, noval, nosave, evolve = (
  278. opt.weights, opt.epochs, opt.resume, Path(opt.save_dir), opt.single_cls, opt.noval, opt.nosave, opt.evolve)
  279. if mode == Mode.SPARSE_LEARNING:
  280. epochs = opt.sl_epochs
  281. # Optimizer
  282. nbs = 64 # nominal batch size
  283. accumulate = max(round(nbs / batch_size), 1) # accumulate loss before optimizing
  284. hyp['weight_decay'] *= batch_size * accumulate / nbs # scale weight_decay
  285. optimizer = smart_optimizer(model, opt.optimizer, hyp['lr0'], hyp['momentum'], hyp['weight_decay'])
  286. # Scheduler
  287. if opt.cos_lr:
  288. lf = one_cycle(1, hyp['lrf'], epochs) # cosine 1->hyp['lrf']
  289. else:
  290. lf = lambda x: (1 - x / epochs) * (1.0 - hyp['lrf']) + hyp['lrf'] # linear
  291. scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf) # plot_lr_scheduler(optimizer, scheduler, epochs)
  292. # EMA
  293. ema = ModelEMA(model) if RANK in {-1, 0} else None
  294. # Resume
  295. best_fitness, start_epoch = 0.0, 0
  296. global ckpt, csd
  297. if pretrained:
  298. if resume:
  299. best_fitness, start_epoch, epochs = smart_resume(ckpt, optimizer, ema, weights, epochs, resume)
  300. try:
  301. del ckpt, csd
  302. except NameError:
  303. print("ckpt, csd have been deleted!")
  304. if RANK in {-1, 0}:
  305. if not resume:
  306. if not opt.noautoanchor:
  307. check_anchors(dataset, model=model, thr=hyp['anchor_t'], imgsz=imgsz) # run AutoAnchor
  308. model.half().float() # pre-reduce anchor precision
  309. callbacks.run('on_pretrain_routine_end', labels, names)
  310. # DDP mode
  311. if cuda and RANK != -1:
  312. model = smart_DDP(model)
  313. # Model attributes
  314. nl = de_parallel(model).model[-1].nl # number of detection layers (to scale hyps)
  315. hyp['box'] *= 3 / nl # scale to layers
  316. hyp['cls'] *= nc / 80 * 3 / nl # scale to classes and layers
  317. hyp['obj'] *= (imgsz / 640) ** 2 * 3 / nl # scale to image size and layers
  318. hyp['label_smoothing'] = opt.label_smoothing
  319. model.nc = nc # attach number of classes to model
  320. model.hyp = hyp # attach hyperparameters to model
  321. model.class_weights = labels_to_class_weights(dataset.labels, nc).to(device) * nc # attach class weights
  322. model.names = names
  323. # Start training
  324. t0 = time.time()
  325. nb = len(train_loader) # number of batches
  326. nw = max(round(hyp['warmup_epochs'] * nb), 100) # number of warmup iterations, max(3 epochs, 100 iterations)
  327. # nw = min(nw, (epochs - start_epoch) / 2 * nb) # limit warmup to < 1/2 of training
  328. last_opt_step = -1
  329. maps = np.zeros(nc) # mAP per class
  330. results = (0, 0, 0, 0, 0, 0, 0) # P, R, mAP@.5, mAP@.5-.95, val_loss(box, obj, cls)
  331. scheduler.last_epoch = start_epoch - 1 # do not move
  332. scaler = torch.cuda.amp.GradScaler(enabled=amp)
  333. stopper, stop = EarlyStopping(patience=opt.patience), False
  334. global compute_loss
  335. compute_loss = ComputeLoss(model) # init loss class
  336. callbacks.run('on_train_start')
  337. LOGGER.info(f'Image sizes {imgsz} train, {imgsz} val\n'
  338. f'Using {train_loader.num_workers * WORLD_SIZE} dataloader workers\n'
  339. f"Logging results to {colorstr('bold', save_dir)}\n"
  340. f'Starting training for {epochs} epochs...')
  341. for epoch in range(start_epoch, epochs): # epoch ------------------------------------------------------------------
  342. callbacks.run('on_train_epoch_start')
  343. model.train()
  344. # Update image weights (optional, single-GPU only)
  345. if opt.image_weights:
  346. cw = model.class_weights.cpu().numpy() * (1 - maps) ** 2 / nc # class weights
  347. iw = labels_to_image_weights(dataset.labels, nc=nc, class_weights=cw) # image weights
  348. dataset.indices = random.choices(range(dataset.n), weights=iw, k=dataset.n) # rand weighted idx
  349. # Update mosaic border (optional)
  350. # b = int(random.uniform(0.25 * imgsz, 0.75 * imgsz + gs) // gs * gs)
  351. # dataset.mosaic_border = [b - imgsz, -b] # height, width borders
  352. mloss = torch.zeros(3, device=device) # mean losses
  353. if RANK != -1:
  354. train_loader.sampler.set_epoch(epoch)
  355. pbar = enumerate(train_loader)
  356. LOGGER.info(('\n' + '%11s' * 7) % ('Epoch', 'GPU_mem', 'box_loss', 'obj_loss', 'cls_loss', 'Instances', 'Size'))
  357. if RANK in {-1, 0}:
  358. pbar = tqdm(pbar, total=nb, bar_format=TQDM_BAR_FORMAT) # progress bar
  359. optimizer.zero_grad()
  360. for i, (imgs, targets, paths, _) in pbar: # batch -------------------------------------------------------------
  361. callbacks.run('on_train_batch_start')
  362. ni = i + nb * epoch # number integrated batches (since train start)
  363. imgs = imgs.to(device, non_blocking=True).float() / 255 # uint8 to float32, 0-255 to 0.0-1.0
  364. # Warmup
  365. if ni <= nw:
  366. xi = [0, nw] # x interp
  367. # compute_loss.gr = np.interp(ni, xi, [0.0, 1.0]) # iou loss ratio (obj_loss = 1.0 or iou)
  368. accumulate = max(1, np.interp(ni, xi, [1, nbs / batch_size]).round())
  369. for j, x in enumerate(optimizer.param_groups):
  370. # bias lr falls from 0.1 to lr0, all other lrs rise from 0.0 to lr0
  371. x['lr'] = np.interp(ni, xi, [hyp['warmup_bias_lr'] if j == 0 else 0.0, x['initial_lr'] * lf(epoch)])
  372. if 'momentum' in x:
  373. x['momentum'] = np.interp(ni, xi, [hyp['warmup_momentum'], hyp['momentum']])
  374. # Multi-scale
  375. if opt.multi_scale:
  376. sz = random.randrange(int(imgsz * 0.5), int(imgsz * 1.5) + gs) // gs * gs # size
  377. sf = sz / max(imgs.shape[2:]) # scale factor
  378. if sf != 1:
  379. ns = [math.ceil(x * sf / gs) * gs for x in imgs.shape[2:]] # new shape (stretched to gs-multiple)
  380. imgs = nn.functional.interpolate(imgs, size=ns, mode='bilinear', align_corners=False)
  381. # Forward
  382. with torch.cuda.amp.autocast(amp):
  383. pred = model(imgs) # forward
  384. loss, loss_items = compute_loss(pred, targets.to(device)) # loss scaled by batch_size
  385. if RANK != -1:
  386. loss *= WORLD_SIZE # gradient averaged between devices in DDP mode
  387. if opt.quad:
  388. loss *= 4.
  389. # Backward
  390. scaler.scale(loss).backward()
  391. # sparse learning
  392. if mode == Mode.SPARSE_LEARNING:
  393. pruner.regularize(model)
  394. # Optimize - https://pytorch.org/docs/master/notes/amp_examples.html
  395. if ni - last_opt_step >= accumulate:
  396. scaler.unscale_(optimizer) # unscale gradients
  397. torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=10.0) # clip gradients
  398. scaler.step(optimizer) # optimizer.step
  399. scaler.update()
  400. optimizer.zero_grad()
  401. if ema:
  402. ema.update(model)
  403. last_opt_step = ni
  404. # Log
  405. if RANK in {-1, 0}:
  406. mloss = (mloss * i + loss_items) / (i + 1) # update mean losses
  407. mem = f'{torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0:.3g}G' # (GB)
  408. pbar.set_description(('%11s' * 2 + '%11.4g' * 5) %
  409. (f'{epoch}/{epochs - 1}', mem, *mloss, targets.shape[0], imgs.shape[-1]))
  410. callbacks.run('on_train_batch_end', model, ni, imgs, targets, paths, list(mloss))
  411. if callbacks.stop_training:
  412. return
  413. # end batch ------------------------------------------------------------------------------------------------
  414. # Scheduler
  415. lr = [x['lr'] for x in optimizer.param_groups] # for loggers
  416. scheduler.step()
  417. if RANK in {-1, 0}:
  418. # mAP
  419. callbacks.run('on_train_epoch_end', epoch=epoch)
  420. ema.update_attr(model, include=['yaml', 'nc', 'hyp', 'names', 'stride', 'class_weights'])
  421. final_epoch = (epoch + 1 == epochs) or stopper.possible_stop
  422. if not noval or final_epoch: # Calculate mAP
  423. results, maps, _ = validate.run(data_dict,
  424. batch_size=batch_size // WORLD_SIZE * 2,
  425. imgsz=imgsz,
  426. half=amp,
  427. model=ema.ema,
  428. single_cls=single_cls,
  429. dataloader=val_loader,
  430. save_dir=save_dir,
  431. plots=False,
  432. callbacks=callbacks,
  433. compute_loss=compute_loss)
  434. # Update best mAP
  435. fi = fitness(np.array(results).reshape(1, -1)) # weighted combination of [P, R, mAP@.5, mAP@.5-.95]
  436. stop = stopper(epoch=epoch, fitness=fi) # early stop check
  437. if fi > best_fitness:
  438. best_fitness = fi
  439. log_vals = list(mloss) + list(results) + lr
  440. callbacks.run('on_fit_epoch_end', log_vals, epoch, best_fitness, fi)
  441. # Save model
  442. if (not nosave) or (final_epoch and not evolve): # if save
  443. ckpt = {
  444. 'epoch': epoch,
  445. 'best_fitness': best_fitness,
  446. 'model': deepcopy(de_parallel(model)).half(),
  447. 'ema': deepcopy(ema.ema).half(),
  448. 'updates': ema.updates,
  449. 'optimizer': optimizer.state_dict(),
  450. 'opt': vars(opt),
  451. 'git': GIT_INFO, # {remote, branch, commit} if a git repo
  452. 'date': datetime.now().isoformat()}
  453. # Save last, best and delete
  454. torch.save(ckpt, last)
  455. if best_fitness == fi:
  456. torch.save(ckpt, best)
  457. if opt.save_period > 0 and epoch % opt.save_period == 0:
  458. torch.save(ckpt, w / f'epoch{epoch}.pt')
  459. del ckpt
  460. callbacks.run('on_model_save', last, epoch, final_epoch, best_fitness, fi)
  461. # EarlyStopping
  462. if RANK != -1: # if DDP training
  463. broadcast_list = [stop if RANK == 0 else None]
  464. dist.broadcast_object_list(broadcast_list, 0) # broadcast 'stop' to all ranks
  465. if RANK != 0:
  466. stop = broadcast_list[0]
  467. if stop:
  468. break # must break all DDP ranks
  469. # end epoch ----------------------------------------------------------------------------------------------------
  470. # end training -----------------------------------------------------------------------------------------------------
  471. if RANK in {-1, 0}:
  472. LOGGER.info(f'\n{epoch - start_epoch + 1} epochs completed in {(time.time() - t0) / 3600:.3f} hours.')
  473. for f in last, best:
  474. if f.exists():
  475. strip_optimizer(f) # strip optimizers
  476. if f is best:
  477. LOGGER.info(f'\nValidating {f}...')
  478. results, _, _ = validate.run(
  479. data_dict,
  480. batch_size=batch_size // WORLD_SIZE * 2,
  481. imgsz=imgsz,
  482. model=attempt_load(f, device).half(),
  483. iou_thres=0.65 if is_coco else 0.60, # best pycocotools at iou 0.65
  484. single_cls=single_cls,
  485. dataloader=val_loader,
  486. save_dir=save_dir,
  487. save_json=is_coco,
  488. verbose=True,
  489. plots=plots,
  490. callbacks=callbacks,
  491. compute_loss=compute_loss) # val best model with plots
  492. if is_coco:
  493. callbacks.run('on_fit_epoch_end', list(mloss) + list(results) + lr, epoch, best_fitness, fi)
  494. callbacks.run('on_train_end', last, best, epoch, results)
  495. torch.cuda.empty_cache()
  496. return results
  497. def parse_opt(known=False):
  498. parser = argparse.ArgumentParser()
  499. parser.add_argument('--weights', type=str, default=ROOT / 'yolov5s.pt', help='initial weights path')
  500. parser.add_argument('--cfg', type=str, default='', help='model.yaml path')
  501. parser.add_argument('--data', type=str, default=ROOT / 'data/coco128.yaml', help='dataset.yaml path')
  502. parser.add_argument('--hyp', type=str, default=ROOT / 'data/hyps/hyp.scratch-low.yaml', help='hyperparameters path')
  503. parser.add_argument('--epochs', type=int, default=5, help='total training epochs')
  504. parser.add_argument('--batch-size', type=int, default=16, help='total batch size for all GPUs, -1 for autobatch')
  505. parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=640, help='train, val image size (pixels)')
  506. parser.add_argument('--rect', action='store_true', help='rectangular training')
  507. parser.add_argument('--resume', nargs='?', const=True, default=False, help='resume most recent training')
  508. parser.add_argument('--nosave', action='store_true', help='only save final checkpoint')
  509. parser.add_argument('--noval', action='store_true', help='only validate final epoch')
  510. parser.add_argument('--noautoanchor', action='store_true', help='disable AutoAnchor')
  511. parser.add_argument('--noplots', action='store_true', help='save no plot files')
  512. parser.add_argument('--evolve', type=int, nargs='?', const=300, help='evolve hyperparameters for x generations')
  513. parser.add_argument('--bucket', type=str, default='', help='gsutil bucket')
  514. parser.add_argument('--cache', type=str, nargs='?', const='ram', help='image --cache ram/disk')
  515. parser.add_argument('--image-weights', action='store_true', help='use weighted image selection for training')
  516. parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
  517. parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%%')
  518. parser.add_argument('--single-cls', action='store_true', help='train multi-class data as single-class')
  519. parser.add_argument('--optimizer', type=str, choices=['SGD', 'Adam', 'AdamW'], default='SGD', help='optimizer')
  520. parser.add_argument('--sync-bn', action='store_true', help='use SyncBatchNorm, only available in DDP mode')
  521. parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)')
  522. parser.add_argument('--project', default=ROOT / 'runs/train', help='save to project/name')
  523. parser.add_argument('--name', default='exp', help='save to project/name')
  524. parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
  525. parser.add_argument('--quad', action='store_true', help='quad dataloader')
  526. parser.add_argument('--cos-lr', action='store_true', help='cosine LR scheduler')
  527. parser.add_argument('--label-smoothing', type=float, default=0.0, help='Label smoothing epsilon')
  528. parser.add_argument('--patience', type=int, default=100, help='EarlyStopping patience (epochs without improvement)')
  529. parser.add_argument('--freeze', nargs='+', type=int, default=[0], help='Freeze layers: backbone=10, first3=0 1 2')
  530. parser.add_argument('--save-period', type=int, default=-1, help='Save checkpoint every x epochs (disabled if < 1)')
  531. parser.add_argument('--seed', type=int, default=0, help='Global training seed')
  532. parser.add_argument('--local_rank', type=int, default=-1, help='Automatic DDP Multi-GPU argument, do not modify')
  533. # Logger arguments
  534. parser.add_argument('--entity', default=None, help='Entity')
  535. parser.add_argument('--upload_dataset', nargs='?', const=True, default=False, help='Upload data, "val" option')
  536. parser.add_argument('--bbox_interval', type=int, default=-1, help='Set bounding-box image logging interval')
  537. parser.add_argument('--artifact_alias', type=str, default='latest', help='Version of dataset artifact to use')
  538. ## for pruning
  539. parser.add_argument('--target-prune-rate', default=0.5, type=float, help='Target pruning rate')
  540. parser.add_argument("--speed-up", type=float, default=2)
  541. parser.add_argument("--global-pruning", action="store_true", default=False)
  542. parser.add_argument("--iterative-steps", default=20, type=int)
  543. parser.add_argument("--max-pruning-ratio", type=float, default=1.0)
  544. parser.add_argument("--soft-keeping-ratio", type=float, default=0.0)
  545. parser.add_argument("--finetune", action="store_true", default=False, help='whether finetune or not')
  546. # parser.add_argument("--total-epochs", type=int, default=100)
  547. parser.add_argument("--lr-decay-milestones", default="60,80", type=str, help="milestones for learning rate decay")
  548. parser.add_argument("--lr-decay-gamma", default=0.1, type=float)
  549. parser.add_argument("--lr", default=0.01, type=float, help="learning rate")
  550. parser.add_argument("--weight-decay", type=float, default=5e-4) # optimizer的参数,限制模型权重
  551. parser.add_argument("--sparse-learning", action="store_true", default=False, help='whether sparse learning or not')
  552. parser.add_argument("--sl-epochs", type=int, default=5, help="epochs for sparsity learning")
  553. parser.add_argument("--sl-lr", default=0.01, type=float, help="learning rate for sparsity learning")
  554. parser.add_argument("--sl-lr-decay-milestones", default="60,80", type=str, help="milestones for sparsity learning")
  555. parser.add_argument("--sl-restore", type=str, default=None)
  556. parser.add_argument("--reg", type=float, default=5e-4)
  557. return parser.parse_known_args()[0] if known else parser.parse_args()
  558. def main(opt, callbacks=Callbacks()):
  559. # Checks
  560. if RANK in {-1, 0}:
  561. print_args(vars(opt))
  562. check_git_status()
  563. check_requirements(ROOT / 'requirements.txt')
  564. # Resume (from specified or most recent last.pt)
  565. if opt.resume and not check_comet_resume(opt) and not opt.evolve:
  566. global last
  567. last = Path(check_file(opt.resume) if isinstance(opt.resume, str) else get_latest_run())
  568. opt_yaml = last.parent.parent / 'opt.yaml' # train options yaml
  569. opt_data = opt.data # original dataset
  570. if opt_yaml.is_file():
  571. with open(opt_yaml, errors='ignore') as f:
  572. d = yaml.safe_load(f)
  573. else:
  574. d = torch.load(last, map_location='cpu')['opt']
  575. opt = argparse.Namespace(**d) # replace
  576. opt.cfg, opt.weights, opt.resume = '', str(last), True # reinstate
  577. if is_url(opt_data):
  578. opt.data = check_file(opt_data) # avoid HUB resume auth timeout
  579. else:
  580. opt.data, opt.cfg, opt.hyp, opt.weights, opt.project = \
  581. check_file(opt.data), check_yaml(opt.cfg), check_yaml(opt.hyp), str(opt.weights), str(opt.project) # checks
  582. assert len(opt.cfg) or len(opt.weights), 'either --cfg or --weights must be specified'
  583. if opt.evolve:
  584. if opt.project == str(ROOT / 'runs/train'): # if default project name, rename to runs/evolve
  585. opt.project = str(ROOT / 'runs/evolve')
  586. opt.exist_ok, opt.resume = opt.resume, False # pass resume to exist_ok and disable resume
  587. if opt.name == 'cfg':
  588. opt.name = Path(opt.cfg).stem # use model.yaml as name
  589. opt.save_dir = str(increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok))
  590. # DDP mode
  591. device = select_device(opt.device, batch_size=opt.batch_size)
  592. if LOCAL_RANK != -1:
  593. msg = 'is not compatible with YOLOv5 Multi-GPU DDP training'
  594. assert not opt.image_weights, f'--image-weights {msg}'
  595. assert not opt.evolve, f'--evolve {msg}'
  596. assert opt.batch_size != -1, f'AutoBatch with --batch-size -1 {msg}, please pass a valid --batch-size'
  597. assert opt.batch_size % WORLD_SIZE == 0, f'--batch-size {opt.batch_size} must be multiple of WORLD_SIZE'
  598. assert torch.cuda.device_count() > LOCAL_RANK, 'insufficient CUDA devices for DDP command'
  599. torch.cuda.set_device(LOCAL_RANK)
  600. device = torch.device('cuda', LOCAL_RANK)
  601. dist.init_process_group(backend='nccl' if dist.is_nccl_available() else 'gloo')
  602. # Train
  603. if not opt.evolve:
  604. # 设置参数
  605. preparing(opt.hyp, opt, device, callbacks)
  606. # 加载预训练模型
  607. model = copy.deepcopy(load_model(opt.hyp, opt, device))
  608. # 加载数据集
  609. dataset, train_loader, val_loader = load_dataset(opt.hyp, opt, model)
  610. # 测试原模型
  611. eval(model, val_loader, opt, callbacks, save_dir=Path(opt.save_dir) / 'original_eval')
  612. ## 用DepGraph算法剪枝
  613. # 稀疏训练
  614. # model.train()
  615. pruner = get_pruner(opt, model, device)
  616. if opt.sparse_learning:
  617. train(opt.hyp, opt, device, model, dataset, train_loader, val_loader, callbacks, Mode.SPARSE_LEARNING, pruner)
  618. # 剪枝
  619. pruning(opt, model, device, pruner)
  620. # 模型微调
  621. if opt.finetune:
  622. train(opt.hyp, opt, device, model, dataset, train_loader, val_loader, callbacks)
  623. # 测试新模型
  624. # eval(model, val_loader, opt, callbacks, save_dir=Path(opt.save_dir) / 'pruned_eval')
  625. # Evolve hyperparameters (optional)
  626. else:
  627. # Hyperparameter evolution metadata (mutation scale 0-1, lower_limit, upper_limit)
  628. meta = {
  629. 'lr0': (1, 1e-5, 1e-1), # initial learning rate (SGD=1E-2, Adam=1E-3)
  630. 'lrf': (1, 0.01, 1.0), # final OneCycleLR learning rate (lr0 * lrf)
  631. 'momentum': (0.3, 0.6, 0.98), # SGD momentum/Adam beta1
  632. 'weight_decay': (1, 0.0, 0.001), # optimizer weight decay
  633. 'warmup_epochs': (1, 0.0, 5.0), # warmup epochs (fractions ok)
  634. 'warmup_momentum': (1, 0.0, 0.95), # warmup initial momentum
  635. 'warmup_bias_lr': (1, 0.0, 0.2), # warmup initial bias lr
  636. 'box': (1, 0.02, 0.2), # box loss gain
  637. 'cls': (1, 0.2, 4.0), # cls loss gain
  638. 'cls_pw': (1, 0.5, 2.0), # cls BCELoss positive_weight
  639. 'obj': (1, 0.2, 4.0), # obj loss gain (scale with pixels)
  640. 'obj_pw': (1, 0.5, 2.0), # obj BCELoss positive_weight
  641. 'iou_t': (0, 0.1, 0.7), # IoU training threshold
  642. 'anchor_t': (1, 2.0, 8.0), # anchor-multiple threshold
  643. 'anchors': (2, 2.0, 10.0), # anchors per output grid (0 to ignore)
  644. 'fl_gamma': (0, 0.0, 2.0), # focal loss gamma (efficientDet default gamma=1.5)
  645. 'hsv_h': (1, 0.0, 0.1), # image HSV-Hue augmentation (fraction)
  646. 'hsv_s': (1, 0.0, 0.9), # image HSV-Saturation augmentation (fraction)
  647. 'hsv_v': (1, 0.0, 0.9), # image HSV-Value augmentation (fraction)
  648. 'degrees': (1, 0.0, 45.0), # image rotation (+/- deg)
  649. 'translate': (1, 0.0, 0.9), # image translation (+/- fraction)
  650. 'scale': (1, 0.0, 0.9), # image scale (+/- gain)
  651. 'shear': (1, 0.0, 10.0), # image shear (+/- deg)
  652. 'perspective': (0, 0.0, 0.001), # image perspective (+/- fraction), range 0-0.001
  653. 'flipud': (1, 0.0, 1.0), # image flip up-down (probability)
  654. 'fliplr': (0, 0.0, 1.0), # image flip left-right (probability)
  655. 'mosaic': (1, 0.0, 1.0), # image mixup (probability)
  656. 'mixup': (1, 0.0, 1.0), # image mixup (probability)
  657. 'copy_paste': (1, 0.0, 1.0)} # segment copy-paste (probability)
  658. with open(opt.hyp, errors='ignore') as f:
  659. hyp = yaml.safe_load(f) # load hyps dict
  660. if 'anchors' not in hyp: # anchors commented in hyp.yaml
  661. hyp['anchors'] = 3
  662. if opt.noautoanchor:
  663. del hyp['anchors'], meta['anchors']
  664. opt.noval, opt.nosave, save_dir = True, True, Path(opt.save_dir) # only val/save final epoch
  665. # ei = [isinstance(x, (int, float)) for x in hyp.values()] # evolvable indices
  666. evolve_yaml, evolve_csv = save_dir / 'hyp_evolve.yaml', save_dir / 'evolve.csv'
  667. if opt.bucket:
  668. # download evolve.csv if exists
  669. subprocess.run([
  670. 'gsutil',
  671. 'cp',
  672. f'gs://{opt.bucket}/evolve.csv',
  673. str(evolve_csv), ])
  674. for _ in range(opt.evolve): # generations to evolve
  675. if evolve_csv.exists(): # if evolve.csv exists: select best hyps and mutate
  676. # Select parent(s)
  677. parent = 'single' # parent selection method: 'single' or 'weighted'
  678. x = np.loadtxt(evolve_csv, ndmin=2, delimiter=',', skiprows=1)
  679. n = min(5, len(x)) # number of previous results to consider
  680. x = x[np.argsort(-fitness(x))][:n] # top n mutations
  681. w = fitness(x) - fitness(x).min() + 1E-6 # weights (sum > 0)
  682. if parent == 'single' or len(x) == 1:
  683. # x = x[random.randint(0, n - 1)] # random selection
  684. x = x[random.choices(range(n), weights=w)[0]] # weighted selection
  685. elif parent == 'weighted':
  686. x = (x * w.reshape(n, 1)).sum(0) / w.sum() # weighted combination
  687. # Mutate
  688. mp, s = 0.8, 0.2 # mutation probability, sigma
  689. npr = np.random
  690. npr.seed(int(time.time()))
  691. g = np.array([meta[k][0] for k in hyp.keys()]) # gains 0-1
  692. ng = len(meta)
  693. v = np.ones(ng)
  694. while all(v == 1): # mutate until a change occurs (prevent duplicates)
  695. v = (g * (npr.random(ng) < mp) * npr.randn(ng) * npr.random() * s + 1).clip(0.3, 3.0)
  696. for i, k in enumerate(hyp.keys()): # plt.hist(v.ravel(), 300)
  697. hyp[k] = float(x[i + 7] * v[i]) # mutate
  698. # Constrain to limits
  699. for k, v in meta.items():
  700. hyp[k] = max(hyp[k], v[1]) # lower limit
  701. hyp[k] = min(hyp[k], v[2]) # upper limit
  702. hyp[k] = round(hyp[k], 5) # significant digits
  703. # Train mutation
  704. results = train(hyp.copy(), opt, device, callbacks)
  705. callbacks = Callbacks()
  706. # Write mutation results
  707. keys = ('metrics/precision', 'metrics/recall', 'metrics/mAP_0.5', 'metrics/mAP_0.5:0.95', 'val/box_loss',
  708. 'val/obj_loss', 'val/cls_loss')
  709. print_mutation(keys, results, hyp.copy(), save_dir, opt.bucket)
  710. # Plot results
  711. plot_evolve(evolve_csv)
  712. LOGGER.info(f'Hyperparameter evolution finished {opt.evolve} generations\n'
  713. f"Results saved to {colorstr('bold', save_dir)}\n"
  714. f'Usage example: $ python train.py --hyp {evolve_yaml}')
  715. def run(**kwargs):
  716. # Usage: import train; train.run(data='coco128.yaml', imgsz=320, weights='yolov5m.pt')
  717. opt = parse_opt(True)
  718. for k, v in kwargs.items():
  719. setattr(opt, k, v)
  720. main(opt)
  721. return opt
  722. if __name__ == '__main__':
  723. opt = parse_opt()
  724. main(opt)

目前没有区别Sparse Learning和Fine-tuing的optimizer和scheduler,需要的话,可以增加if语句为两种情况创建不同的优化器。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/在线问答5/article/detail/745355
推荐阅读
相关标签
  

闽ICP备14008679号