train_nas.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. # Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from __future__ import absolute_import
  15. from __future__ import division
  16. from __future__ import print_function
  17. import os
  18. import sys
  19. # add python path of PadleDetection to sys.path
  20. parent_path = os.path.abspath(os.path.join(__file__, *(['..'] * 3)))
  21. if parent_path not in sys.path:
  22. sys.path.append(parent_path)
  23. import time
  24. import numpy as np
  25. import datetime
  26. from collections import deque
  27. from paddle import fluid
  28. import logging
  29. FORMAT = '%(asctime)s-%(levelname)s: %(message)s'
  30. logging.basicConfig(level=logging.INFO, format=FORMAT)
  31. logger = logging.getLogger(__name__)
  32. try:
  33. from ppdet.experimental import mixed_precision_context
  34. from ppdet.core.workspace import load_config, merge_config, create, register
  35. from ppdet.data.reader import create_reader
  36. from ppdet.utils import dist_utils
  37. from ppdet.utils.eval_utils import parse_fetches, eval_run
  38. from ppdet.utils.stats import TrainingStats
  39. from ppdet.utils.cli import ArgsParser
  40. from ppdet.utils.check import check_gpu, check_version, check_config, enable_static_mode
  41. import ppdet.utils.checkpoint as checkpoint
  42. except ImportError as e:
  43. if sys.argv[0].find('static') >= 0:
  44. logger.error("Importing ppdet failed when running static model "
  45. "with error: {}\n"
  46. "please try:\n"
  47. "\t1. run static model under PaddleDetection/static "
  48. "directory\n"
  49. "\t2. run 'pip uninstall ppdet' to uninstall ppdet "
  50. "dynamic version firstly.".format(e))
  51. sys.exit(-1)
  52. else:
  53. raise e
  54. from paddleslim.analysis import flops, TableLatencyEvaluator
  55. from paddleslim.nas import SANAS
  56. ### register search space to paddleslim
  57. import search_space
  58. @register
  59. class Constraint(object):
  60. """
  61. Constraint for nas
  62. """
  63. def __init__(self,
  64. ctype,
  65. max_constraint=None,
  66. min_constraint=None,
  67. table_file=None):
  68. super(Constraint, self).__init__()
  69. self.ctype = ctype
  70. self.max_constraint = max_constraint
  71. self.min_constraint = min_constraint
  72. self.table_file = table_file
  73. def compute_constraint(self, program):
  74. if self.ctype == 'flops':
  75. model_status = flops(program)
  76. elif self.ctype == 'latency':
  77. assert os.path.exists(
  78. self.table_file
  79. ), "latency constraint must have latency table, please check whether table file exist!"
  80. model_latency = TableLatencyEvaluator(self.table_file)
  81. model_status = model_latency.latency(program, only_conv=True)
  82. else:
  83. raise NotImplementedError(
  84. "{} constraint is NOT support!!! Now PaddleSlim support flops constraint and latency constraint".
  85. format(self.ctype))
  86. return model_status
  87. def get_bboxes_scores(result):
  88. bboxes = result['bbox'][0]
  89. gt_bbox = result['gt_bbox'][0]
  90. bbox_lengths = result['bbox'][1][0]
  91. gt_lengths = result['gt_bbox'][1][0]
  92. bbox_list = []
  93. gt_box_list = []
  94. for i in range(len(bbox_lengths)):
  95. num = bbox_lengths[i]
  96. for j in range(num):
  97. dt = bboxes[j]
  98. clsid, score, xmin, ymin, xmax, ymax = dt.tolist()
  99. im_shape = result['im_shape'][0][i].tolist()
  100. im_height, im_width = int(im_shape[0]), int(im_shape[1])
  101. xmin *= im_width
  102. ymin *= im_height
  103. xmax *= im_width
  104. ymax *= im_height
  105. bbox_list.append([xmin, ymin, xmax, ymax, score])
  106. faces_num_gt = 0
  107. for i in range(len(gt_lengths)):
  108. num = gt_lengths[i]
  109. for j in range(num):
  110. gt = gt_bbox[j]
  111. xmin, ymin, xmax, ymax = gt.tolist()
  112. im_shape = result['im_shape'][0][i].tolist()
  113. im_height, im_width = int(im_shape[0]), int(im_shape[1])
  114. xmin *= im_width
  115. ymin *= im_height
  116. xmax *= im_width
  117. ymax *= im_height
  118. gt_box_list.append([xmin, ymin, xmax, ymax])
  119. faces_num_gt += 1
  120. return gt_box_list, bbox_list, faces_num_gt
  121. def calculate_ap_py(results):
  122. def cal_iou(rect1, rect2):
  123. lt_x = max(rect1[0], rect2[0])
  124. lt_y = max(rect1[1], rect2[1])
  125. rb_x = min(rect1[2], rect2[2])
  126. rb_y = min(rect1[3], rect2[3])
  127. if (rb_x > lt_x) and (rb_y > lt_y):
  128. intersection = (rb_x - lt_x) * (rb_y - lt_y)
  129. else:
  130. return 0
  131. area1 = (rect1[2] - rect1[0]) * (rect1[3] - rect1[1])
  132. area2 = (rect2[2] - rect2[0]) * (rect2[3] - rect2[1])
  133. intersection = min(intersection, area1, area2)
  134. union = area1 + area2 - intersection
  135. return float(intersection) / union
  136. def is_same_face(face_gt, face_pred):
  137. iou = cal_iou(face_gt, face_pred)
  138. return iou >= 0.5
  139. def eval_single_image(faces_gt, faces_pred):
  140. pred_is_true = [False] * len(faces_pred)
  141. gt_been_pred = [False] * len(faces_gt)
  142. for i in range(len(faces_pred)):
  143. isface = False
  144. for j in range(len(faces_gt)):
  145. if gt_been_pred[j] == 0:
  146. isface = is_same_face(faces_gt[j], faces_pred[i])
  147. if isface == 1:
  148. gt_been_pred[j] = True
  149. break
  150. pred_is_true[i] = isface
  151. return pred_is_true
  152. score_res_pair = {}
  153. faces_num_gt = 0
  154. for t in results:
  155. gt_box_list, bbox_list, face_num_gt = get_bboxes_scores(t)
  156. faces_num_gt += face_num_gt
  157. pred_is_true = eval_single_image(gt_box_list, bbox_list)
  158. for i in range(0, len(pred_is_true)):
  159. now_score = bbox_list[i][-1]
  160. if now_score in score_res_pair:
  161. score_res_pair[now_score].append(int(pred_is_true[i]))
  162. else:
  163. score_res_pair[now_score] = [int(pred_is_true[i])]
  164. keys = score_res_pair.keys()
  165. keys = sorted(keys, reverse=True)
  166. tp_num = 0
  167. predict_num = 0
  168. precision_list = []
  169. recall_list = []
  170. for i in range(len(keys)):
  171. k = keys[i]
  172. v = score_res_pair[k]
  173. predict_num += len(v)
  174. tp_num += sum(v)
  175. recall = float(tp_num) / faces_num_gt
  176. precision_list.append(float(tp_num) / predict_num)
  177. recall_list.append(recall)
  178. ap = precision_list[0] * recall_list[0]
  179. for i in range(1, len(precision_list)):
  180. ap += precision_list[i] * (recall_list[i] - recall_list[i - 1])
  181. return ap
  182. def main():
  183. env = os.environ
  184. FLAGS.dist = 'PADDLE_TRAINER_ID' in env and 'PADDLE_TRAINERS_NUM' in env
  185. if FLAGS.dist:
  186. trainer_id = int(env['PADDLE_TRAINER_ID'])
  187. import random
  188. local_seed = (99 + trainer_id)
  189. random.seed(local_seed)
  190. np.random.seed(local_seed)
  191. cfg = load_config(FLAGS.config)
  192. merge_config(FLAGS.opt)
  193. check_config(cfg)
  194. # check if set use_gpu=True in paddlepaddle cpu version
  195. check_gpu(cfg.use_gpu)
  196. # check if paddlepaddle version is satisfied
  197. check_version()
  198. main_arch = cfg.architecture
  199. if cfg.use_gpu:
  200. devices_num = fluid.core.get_cuda_device_count()
  201. else:
  202. devices_num = int(os.environ.get('CPU_NUM', 1))
  203. if 'FLAGS_selected_gpus' in env:
  204. device_id = int(env['FLAGS_selected_gpus'])
  205. else:
  206. device_id = 0
  207. place = fluid.CUDAPlace(device_id) if cfg.use_gpu else fluid.CPUPlace()
  208. exe = fluid.Executor(place)
  209. lr_builder = create('LearningRate')
  210. optim_builder = create('OptimizerBuilder')
  211. # add NAS
  212. config = ([(cfg.search_space)])
  213. server_address = (cfg.server_ip, cfg.server_port)
  214. load_checkpoint = FLAGS.resume_checkpoint if FLAGS.resume_checkpoint else None
  215. sa_nas = SANAS(
  216. config,
  217. server_addr=server_address,
  218. init_temperature=cfg.init_temperature,
  219. reduce_rate=cfg.reduce_rate,
  220. search_steps=cfg.search_steps,
  221. save_checkpoint=cfg.save_dir,
  222. load_checkpoint=load_checkpoint,
  223. is_server=cfg.is_server)
  224. start_iter = 0
  225. train_reader = create_reader(cfg.TrainReader, (cfg.max_iters - start_iter) *
  226. devices_num, cfg)
  227. eval_reader = create_reader(cfg.EvalReader)
  228. constraint = create('Constraint')
  229. for step in range(cfg.search_steps):
  230. logger.info('----->>> search step: {} <<<------'.format(step))
  231. archs = sa_nas.next_archs()[0]
  232. # build program
  233. startup_prog = fluid.Program()
  234. train_prog = fluid.Program()
  235. with fluid.program_guard(train_prog, startup_prog):
  236. with fluid.unique_name.guard():
  237. model = create(main_arch)
  238. if FLAGS.fp16:
  239. assert (getattr(model.backbone, 'norm_type', None)
  240. != 'affine_channel'), \
  241. '--fp16 currently does not support affine channel, ' \
  242. ' please modify backbone settings to use batch norm'
  243. with mixed_precision_context(FLAGS.loss_scale,
  244. FLAGS.fp16) as ctx:
  245. inputs_def = cfg['TrainReader']['inputs_def']
  246. feed_vars, train_loader = model.build_inputs(**inputs_def)
  247. train_fetches = archs(feed_vars, 'train', cfg)
  248. loss = train_fetches['loss']
  249. if FLAGS.fp16:
  250. loss *= ctx.get_loss_scale_var()
  251. lr = lr_builder()
  252. optimizer = optim_builder(lr)
  253. optimizer.minimize(loss)
  254. if FLAGS.fp16:
  255. loss /= ctx.get_loss_scale_var()
  256. current_constraint = constraint.compute_constraint(train_prog)
  257. logger.info('current steps: {}, constraint {}'.format(
  258. step, current_constraint))
  259. if (constraint.max_constraint != None and
  260. current_constraint > constraint.max_constraint) or (
  261. constraint.min_constraint != None and
  262. current_constraint < constraint.min_constraint):
  263. continue
  264. # parse train fetches
  265. train_keys, train_values, _ = parse_fetches(train_fetches)
  266. train_values.append(lr)
  267. if FLAGS.eval:
  268. eval_prog = fluid.Program()
  269. with fluid.program_guard(eval_prog, startup_prog):
  270. with fluid.unique_name.guard():
  271. model = create(main_arch)
  272. inputs_def = cfg['EvalReader']['inputs_def']
  273. feed_vars, eval_loader = model.build_inputs(**inputs_def)
  274. fetches = archs(feed_vars, 'eval', cfg)
  275. eval_prog = eval_prog.clone(True)
  276. # When iterable mode, set set_sample_list_generator(eval_reader, place)
  277. eval_loader.set_sample_list_generator(eval_reader)
  278. extra_keys = ['im_id', 'im_shape', 'gt_bbox']
  279. eval_keys, eval_values, eval_cls = parse_fetches(fetches, eval_prog,
  280. extra_keys)
  281. # compile program for multi-devices
  282. build_strategy = fluid.BuildStrategy()
  283. build_strategy.fuse_all_optimizer_ops = False
  284. build_strategy.fuse_elewise_add_act_ops = True
  285. exec_strategy = fluid.ExecutionStrategy()
  286. # iteration number when CompiledProgram tries to drop local execution scopes.
  287. # Set it to be 1 to save memory usages, so that unused variables in
  288. # local execution scopes can be deleted after each iteration.
  289. exec_strategy.num_iteration_per_drop_scope = 1
  290. if FLAGS.dist:
  291. dist_utils.prepare_for_multi_process(exe, build_strategy,
  292. startup_prog, train_prog)
  293. exec_strategy.num_threads = 1
  294. exe.run(startup_prog)
  295. compiled_train_prog = fluid.CompiledProgram(
  296. train_prog).with_data_parallel(
  297. loss_name=loss.name,
  298. build_strategy=build_strategy,
  299. exec_strategy=exec_strategy)
  300. if FLAGS.eval:
  301. compiled_eval_prog = fluid.CompiledProgram(eval_prog)
  302. # When iterable mode, set set_sample_list_generator(train_reader, place)
  303. train_loader.set_sample_list_generator(train_reader)
  304. train_stats = TrainingStats(cfg.log_iter, train_keys)
  305. train_loader.start()
  306. end_time = time.time()
  307. cfg_name = os.path.basename(FLAGS.config).split('.')[0]
  308. save_dir = os.path.join(cfg.save_dir, cfg_name)
  309. time_stat = deque(maxlen=cfg.log_iter)
  310. ap = 0
  311. for it in range(start_iter, cfg.max_iters):
  312. start_time = end_time
  313. end_time = time.time()
  314. time_stat.append(end_time - start_time)
  315. time_cost = np.mean(time_stat)
  316. eta_sec = (cfg.max_iters - it) * time_cost
  317. eta = str(datetime.timedelta(seconds=int(eta_sec)))
  318. outs = exe.run(compiled_train_prog, fetch_list=train_values)
  319. stats = {
  320. k: np.array(v).mean()
  321. for k, v in zip(train_keys, outs[:-1])
  322. }
  323. train_stats.update(stats)
  324. logs = train_stats.log()
  325. if it % cfg.log_iter == 0 and (not FLAGS.dist or trainer_id == 0):
  326. strs = 'iter: {}, lr: {:.6f}, {}, time: {:.3f}, eta: {}'.format(
  327. it, np.mean(outs[-1]), logs, time_cost, eta)
  328. logger.info(strs)
  329. if (it > 0 and it == cfg.max_iters - 1) and (not FLAGS.dist or
  330. trainer_id == 0):
  331. save_name = str(
  332. it) if it != cfg.max_iters - 1 else "model_final"
  333. checkpoint.save(exe, train_prog,
  334. os.path.join(save_dir, save_name))
  335. if FLAGS.eval:
  336. # evaluation
  337. results = eval_run(exe, compiled_eval_prog, eval_loader,
  338. eval_keys, eval_values, eval_cls)
  339. ap = calculate_ap_py(results)
  340. train_loader.reset()
  341. eval_loader.reset()
  342. logger.info('rewards: ap is {}'.format(ap))
  343. sa_nas.reward(float(ap))
  344. current_best_tokens = sa_nas.current_info()['best_tokens']
  345. logger.info("All steps end, the best BlazeFace-NAS structure is: ")
  346. sa_nas.tokens2arch(current_best_tokens)
  347. if __name__ == '__main__':
  348. enable_static_mode()
  349. parser = ArgsParser()
  350. parser.add_argument(
  351. "-r",
  352. "--resume_checkpoint",
  353. default=None,
  354. type=str,
  355. help="Checkpoint path for resuming training.")
  356. parser.add_argument(
  357. "--fp16",
  358. action='store_true',
  359. default=False,
  360. help="Enable mixed precision training.")
  361. parser.add_argument(
  362. "--loss_scale",
  363. default=8.,
  364. type=float,
  365. help="Mixed precision training loss scale.")
  366. parser.add_argument(
  367. "--eval",
  368. action='store_true',
  369. default=True,
  370. help="Whether to perform evaluation in train")
  371. FLAGS = parser.parse_args()
  372. main()