program.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. # Copyright (c) 2020 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. import yaml
  20. import time
  21. import shutil
  22. import paddle
  23. import paddle.distributed as dist
  24. from tqdm import tqdm
  25. from argparse import ArgumentParser, RawDescriptionHelpFormatter
  26. from ppocr.utils.stats import TrainingStats
  27. from ppocr.utils.save_load import save_model
  28. from ppocr.utils.utility import print_dict
  29. from ppocr.utils.logging import get_logger
  30. import numpy as np
  31. class ArgsParser(ArgumentParser):
  32. def __init__(self):
  33. super(ArgsParser, self).__init__(
  34. formatter_class=RawDescriptionHelpFormatter)
  35. self.add_argument("-c", "--config", help="configuration file to use")
  36. self.add_argument(
  37. "-o", "--opt", nargs='+', help="set configuration options")
  38. def parse_args(self, argv=None):
  39. # ArgumentParser.parse_args : 获取命令行运行时设置的参数
  40. # 传递配置文件地址
  41. # -c configs/rec/rec_mv3_none_bilstm_ctc.yml
  42. args = super(ArgsParser, self).parse_args(argv)
  43. assert args.config is not None, \
  44. "Please specify --config=configure_file_path."
  45. # 解析配置文件参数,返回参数字典
  46. args.opt = self._parse_opt(args.opt)
  47. return args
  48. # 解析配置文件参数,返回参数字典
  49. def _parse_opt(self, opts):
  50. config = {}
  51. if not opts:
  52. return config
  53. for s in opts:
  54. s = s.strip()
  55. k, v = s.split('=')
  56. config[k] = yaml.load(v, Loader=yaml.Loader)
  57. return config
  58. class AttrDict(dict):
  59. """Single level attribute dict, NOT recursive"""
  60. def __init__(self, **kwargs):
  61. super(AttrDict, self).__init__()
  62. super(AttrDict, self).update(kwargs)
  63. def __getattr__(self, key):
  64. if key in self:
  65. return self[key]
  66. raise AttributeError("object has no attribute '{}'".format(key))
  67. global_config = AttrDict()
  68. default_config = {'Global': {'debug': False, }}
  69. def load_config(file_path):
  70. """
  71. Load config from yml/yaml file.
  72. Args:
  73. file_path (str): Path of the config file to be loaded.
  74. Returns: global config
  75. """
  76. # 将默认配置合并到全局配置字典中
  77. merge_config(default_config)
  78. # 判断后缀为yaml
  79. _, ext = os.path.splitext(file_path)
  80. assert ext in ['.yml', '.yaml'], "only support yaml files for now"
  81. # 合并配置文件配置到全局配置字典中
  82. # yaml文件读取后是多层字典
  83. # {'Global': {'use_gpu': True, 'epoch_num': 72, 'log_smooth_window': 20}
  84. merge_config(yaml.load(open(file_path, 'rb'), Loader=yaml.Loader))
  85. return global_config
  86. def merge_config(config):
  87. """
  88. Merge config into global config.
  89. Args:
  90. config (dict): Config to be merged.
  91. Returns: global config
  92. """
  93. for key, value in config.items():
  94. if "." not in key:
  95. if isinstance(value, dict) and key in global_config:
  96. global_config[key].update(value)
  97. else:
  98. global_config[key] = value
  99. else:
  100. sub_keys = key.split('.')
  101. assert (
  102. sub_keys[0] in global_config
  103. ), "the sub_keys can only be one of global_config: {}, but get: {}, please check your running command".format(
  104. global_config.keys(), sub_keys[0])
  105. cur = global_config[sub_keys[0]]
  106. for idx, sub_key in enumerate(sub_keys[1:]):
  107. if idx == len(sub_keys) - 2:
  108. cur[sub_key] = value
  109. else:
  110. cur = cur[sub_key]
  111. def check_gpu(use_gpu):
  112. """
  113. Log error and exit when set use_gpu=true in paddlepaddle
  114. cpu version.
  115. """
  116. err = "Config use_gpu cannot be set as true while you are " \
  117. "using paddlepaddle cpu version ! \nPlease try: \n" \
  118. "\t1. Install paddlepaddle-gpu to run model on GPU \n" \
  119. "\t2. Set use_gpu as false in config file to run " \
  120. "model on CPU"
  121. try:
  122. if use_gpu and not paddle.is_compiled_with_cuda():
  123. print(err)
  124. sys.exit(1)
  125. except Exception as e:
  126. pass
  127. def train(config,
  128. train_dataloader,
  129. valid_dataloader,
  130. device,
  131. model,
  132. loss_class,
  133. optimizer,
  134. lr_scheduler,
  135. post_process_class,
  136. eval_class,
  137. pre_best_model_dict,
  138. logger,
  139. vdl_writer=None):
  140. cal_metric_during_train = config['Global'].get('cal_metric_during_train',
  141. False)
  142. log_smooth_window = config['Global']['log_smooth_window']
  143. epoch_num = config['Global']['epoch_num']
  144. print_batch_step = config['Global']['print_batch_step']
  145. eval_batch_step = config['Global']['eval_batch_step']
  146. global_step = 0
  147. start_eval_step = 0
  148. if type(eval_batch_step) == list and len(eval_batch_step) >= 2:
  149. start_eval_step = eval_batch_step[0]
  150. eval_batch_step = eval_batch_step[1]
  151. if len(valid_dataloader) == 0:
  152. logger.info(
  153. 'No Images in eval dataset, evaluation during training will be disabled'
  154. )
  155. start_eval_step = 1e111
  156. logger.info(
  157. "During the training process, after the {}th iteration, an evaluation is run every {} iterations".
  158. format(start_eval_step, eval_batch_step))
  159. save_epoch_step = config['Global']['save_epoch_step']
  160. save_model_dir = config['Global']['save_model_dir']
  161. if not os.path.exists(save_model_dir):
  162. os.makedirs(save_model_dir)
  163. main_indicator = eval_class.main_indicator
  164. best_model_dict = {main_indicator: 0}
  165. best_model_dict.update(pre_best_model_dict)
  166. train_stats = TrainingStats(log_smooth_window, ['lr'])
  167. model_average = False
  168. model.train()
  169. use_srn = config['Architecture']['algorithm'] == "SRN"
  170. if 'start_epoch' in best_model_dict:
  171. start_epoch = best_model_dict['start_epoch']
  172. start_epoch = 1
  173. else:
  174. start_epoch = 1
  175. global_acc = 0
  176. # 迭代训练轮数
  177. for epoch in range(start_epoch, epoch_num + 1):
  178. print("=========================================================")
  179. # print("=========================================================")
  180. # train_dataloader = build_dataloader(
  181. # config, 'Train', device, logger, seed=epoch)
  182. train_batch_cost = 0.0
  183. train_reader_cost = 0.0
  184. batch_sum = 0
  185. batch_start = time.time()
  186. for idx, batch in enumerate(train_dataloader):
  187. train_reader_cost += time.time() - batch_start
  188. if idx >= len(train_dataloader):
  189. break
  190. lr = optimizer.get_lr()
  191. images = batch[0]
  192. example_image = images[0]
  193. # 预测值为(128, 80, 6625)
  194. # 128为单个batch大小,即一个batch中有多少图片
  195. # 80为
  196. # 6625为中文字典大小,即有多少个中文字符
  197. if use_srn:
  198. others = batch[-4:]
  199. preds = model(images, others)
  200. model_average = True
  201. else:
  202. # print("==========================")
  203. # print(model)
  204. preds = model(images)
  205. loss = loss_class(preds, batch)
  206. avg_loss = loss['loss']
  207. avg_loss.backward()
  208. optimizer.step()
  209. optimizer.clear_grad()
  210. train_batch_cost += time.time() - batch_start
  211. batch_sum += len(images)
  212. if not isinstance(lr_scheduler, float):
  213. lr_scheduler.step()
  214. # logger and visualdl
  215. # 更新了loss和lr到logs字典
  216. stats = {k: v.numpy().mean() for k, v in loss.items()}
  217. stats['lr'] = lr
  218. train_stats.update(stats)
  219. # only rec and cls need
  220. # 计算Metric,其中包含accuracy,更新accuracy到logs字典
  221. if cal_metric_during_train:
  222. batch = [item.numpy() for item in batch]
  223. # 将预测值(128, 80, 6625) 传入CTC解码
  224. post_result = post_process_class(preds, batch[1])
  225. # print("predict", post_result[0])
  226. # print("real ", post_result[1])
  227. # 初始化评估对象,输入
  228. eval_class(post_result, batch)
  229. # 计算Metric
  230. metric = eval_class.get_metric()
  231. train_stats.update(metric)
  232. if vdl_writer is not None and dist.get_rank() == 0:
  233. for k, v in train_stats.get().items():
  234. vdl_writer.add_scalar('TRAIN/{}'.format(k), v, global_step)
  235. vdl_writer.add_scalar('TRAIN/lr', lr, global_step)
  236. if dist.get_rank(
  237. ) == 0 and global_step > 0 and global_step % print_batch_step == 0:
  238. logs = train_stats.log()
  239. strs = 'epoch: [{}/{}], iter: {}, {}, reader_cost: {:.5f} s, batch_cost: {:.5f} s, samples: {}, ips: {:.5f}'.format(
  240. epoch, epoch_num, global_step, logs, train_reader_cost /
  241. print_batch_step, train_batch_cost / print_batch_step,
  242. batch_sum, batch_sum / train_batch_cost)
  243. logger.info(strs)
  244. train_batch_cost = 0.0
  245. train_reader_cost = 0.0
  246. batch_sum = 0
  247. # if global_step % 500 == 0:
  248. # print("save best accuracy model!")
  249. # # save_model(
  250. # # model,
  251. # # optimizer,
  252. # # save_model_dir,
  253. # # logger,
  254. # # is_best=True,
  255. # # prefix='best_accuracy',
  256. # # best_model_dict=best_model_dict,
  257. # # epoch=epoch)
  258. # paddle.jit.save(
  259. # layer=model,
  260. # path=config['Global']['save_model_dir']+'best_accuracy'
  261. # # input_spec=[example_image]
  262. # )
  263. current_acc = train_stats.get().get('acc')
  264. if global_acc < float(current_acc):
  265. if global_step > int(config['Train']['loader']['batch_size_per_card']):
  266. print("save best accuracy model!", global_acc, "->", current_acc)
  267. paddle.jit.save(
  268. layer=model,
  269. path=config['Global']['save_model_dir']+'best_accuracy'
  270. )
  271. global_acc = current_acc
  272. if global_step % 25000 == 1:
  273. print("save iters model!")
  274. # save_model(
  275. # model,
  276. # optimizer,
  277. # save_model_dir,
  278. # logger,
  279. # is_best=False,
  280. # prefix='iter_{}'.format(global_step),
  281. # best_model_dict=best_model_dict,
  282. # epoch=epoch)
  283. paddle.jit.save(
  284. layer=model,
  285. path=config['Global']['save_model_dir']+'iter_{}'.format(global_step)
  286. # input_spec=[example_image]
  287. )
  288. # 训练过程中测试阶段
  289. # eval
  290. if global_step > start_eval_step and \
  291. (global_step - start_eval_step) % eval_batch_step == 0 and dist.get_rank() == 0:
  292. # 算法为SRN时平均
  293. if model_average:
  294. Model_Average = paddle.incubate.optimizer.ModelAverage(
  295. 0.15,
  296. parameters=model.parameters(),
  297. min_average_window=10000,
  298. max_average_window=15625)
  299. Model_Average.apply()
  300. cur_metric = eval(
  301. model,
  302. valid_dataloader,
  303. post_process_class,
  304. eval_class,
  305. use_srn=use_srn)
  306. cur_metric_str = 'cur metric, {}'.format(', '.join(
  307. ['{}: {}'.format(k, v) for k, v in cur_metric.items()]))
  308. logger.info("acc: " + str(cur_metric.get("acc")) +
  309. " norm_edit_dis: " + str(cur_metric.get("norm_edit_dis")) +
  310. " fps: " + str(cur_metric.get("fps")))
  311. # logger metric
  312. if vdl_writer is not None:
  313. for k, v in cur_metric.items():
  314. if isinstance(v, (float, int)):
  315. vdl_writer.add_scalar('EVAL/{}'.format(k),
  316. cur_metric[k], global_step)
  317. if cur_metric[main_indicator] >= best_model_dict[
  318. main_indicator]:
  319. best_model_dict.update(cur_metric)
  320. best_model_dict['best_epoch'] = epoch
  321. # save_model(
  322. # model,
  323. # optimizer,
  324. # save_model_dir,
  325. # logger,
  326. # is_best=True,
  327. # prefix='best_accuracy',
  328. # best_model_dict=best_model_dict,
  329. # epoch=epoch)
  330. paddle.jit.save(
  331. layer=model,
  332. path=config['Global']['save_model_dir']+'best_accuracy'
  333. # input_spec=[example_image]
  334. )
  335. best_str = 'best metric, {}'.format(', '.join([
  336. '{}: {}'.format(k, v) for k, v in best_model_dict.items()
  337. ]))
  338. # logger.info(best_str)
  339. # logger best metric
  340. if vdl_writer is not None:
  341. vdl_writer.add_scalar('EVAL/best_{}'.format(main_indicator),
  342. best_model_dict[main_indicator],
  343. global_step)
  344. global_step += 1
  345. optimizer.clear_grad()
  346. batch_start = time.time()
  347. if dist.get_rank() == 0 and epoch > 0 and epoch % save_epoch_step == 0:
  348. # save_model(
  349. # model,
  350. # optimizer,
  351. # save_model_dir,
  352. # logger,
  353. # is_best=False,
  354. # prefix='iter_epoch_{}'.format(epoch),
  355. # best_model_dict=best_model_dict,
  356. # epoch=epoch)
  357. paddle.jit.save(
  358. layer=model,
  359. path=config['Global']['save_model_dir']+'iter_epoch_{}'.format(epoch)
  360. # input_spec=[example_image]
  361. )
  362. # best_str = 'best metric, {}'.format(', '.join(
  363. # ['{}: {}'.format(k, v) for k, v in best_model_dict.items()]))
  364. # logger.info(best_str)
  365. if dist.get_rank() == 0 and vdl_writer is not None:
  366. vdl_writer.close()
  367. return
  368. def eval(model, valid_dataloader, post_process_class, eval_class,
  369. use_srn=False):
  370. model.eval()
  371. with paddle.no_grad():
  372. total_frame = 0.0
  373. total_time = 0.0
  374. pbar = tqdm(total=len(valid_dataloader), desc='eval model:')
  375. for idx, batch in enumerate(valid_dataloader):
  376. if idx >= len(valid_dataloader):
  377. # if idx >= 2:
  378. break
  379. images = batch[0]
  380. start = time.time()
  381. if use_srn:
  382. others = batch[-4:]
  383. preds = model(images, others)
  384. else:
  385. preds = model(images)
  386. batch = [item.numpy() for item in batch]
  387. # Obtain usable results from post-processing methods
  388. post_result = post_process_class(preds, batch[1])
  389. total_time += time.time() - start
  390. # Evaluate the results of the current batch
  391. eval_class(post_result, batch)
  392. pbar.update(1)
  393. total_frame += len(images)
  394. # Get final metric,eg. acc or hmean
  395. metric = eval_class.get_metric()
  396. pbar.close()
  397. model.train()
  398. metric['fps'] = total_frame / total_time
  399. return metric
  400. def preprocess(is_train=False):
  401. FLAGS = ArgsParser().parse_args()
  402. # 从配置文件读取参数,得到全局参数字典
  403. config = load_config(FLAGS.config)
  404. merge_config(FLAGS.opt)
  405. # check if set use_gpu=True in paddlepaddle cpu version
  406. # 检查gpu配置是否正确
  407. use_gpu = config['Global']['use_gpu']
  408. check_gpu(use_gpu)
  409. # 检查算法类型是否正确
  410. alg = config['Architecture']['algorithm']
  411. assert alg in [
  412. 'EAST', 'DB', 'SAST', 'Rosetta', 'CRNN', 'STARNet', 'RARE', 'SRN', 'CLS'
  413. ]
  414. device = 'gpu:{}'.format(dist.ParallelEnv().dev_id) if use_gpu else 'cpu'
  415. device = paddle.set_device(device)
  416. config['Global']['distributed'] = dist.get_world_size() != 1
  417. # 区分训练和评估,训练需同时保存配置文件和日志文件,评估不需要
  418. if is_train:
  419. # save_config
  420. # ./output/rec/mv3_none_bilstm_ctc/
  421. save_model_dir = config['Global']['save_model_dir']
  422. os.makedirs(save_model_dir, exist_ok=True)
  423. # 保存配置文件
  424. with open(os.path.join(save_model_dir, 'config.yml'), 'w') as f:
  425. yaml.dump(
  426. dict(config), f, default_flow_style=False, sort_keys=False)
  427. log_file = '{}/train.log'.format(save_model_dir)
  428. else:
  429. log_file = None
  430. logger = get_logger(name='root', log_file=log_file)
  431. # 保存visualDL 日志文件
  432. if config['Global']['use_visualdl']:
  433. from visualdl import LogWriter
  434. save_model_dir = config['Global']['save_model_dir']
  435. vdl_writer_path = '{}/vdl/'.format(save_model_dir)
  436. os.makedirs(vdl_writer_path, exist_ok=True)
  437. vdl_writer = LogWriter(logdir=vdl_writer_path)
  438. else:
  439. vdl_writer = None
  440. print_dict(config, logger)
  441. logger.info('train with paddle {} and device {}'.format(paddle.__version__,
  442. device))
  443. return config, device, logger, vdl_writer