program.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  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 ocr.ppocr.utils.stats import TrainingStats
  27. from ocr.ppocr.utils.save_load import save_model
  28. from ocr.ppocr.utils.utility import print_dict
  29. from ocr.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. else:
  173. start_epoch = 1
  174. # 迭代训练轮数
  175. for epoch in range(start_epoch, epoch_num + 1):
  176. # print("=========================================================")
  177. # print("=========================================================")
  178. # train_dataloader = build_dataloader(
  179. # config, 'Train', device, logger, seed=epoch)
  180. train_batch_cost = 0.0
  181. train_reader_cost = 0.0
  182. batch_sum = 0
  183. batch_start = time.time()
  184. for idx, batch in enumerate(train_dataloader):
  185. train_reader_cost += time.time() - batch_start
  186. if idx >= len(train_dataloader):
  187. break
  188. lr = optimizer.get_lr()
  189. images = batch[0]
  190. example_image = images[0]
  191. # 预测值为(128, 80, 6625)
  192. # 128为单个batch大小,即一个batch中有多少图片
  193. # 80为
  194. # 6625为中文字典大小,即有多少个中文字符
  195. if use_srn:
  196. others = batch[-4:]
  197. preds = model(images, others)
  198. model_average = True
  199. else:
  200. preds = model(images)
  201. loss = loss_class(preds, batch)
  202. avg_loss = loss['loss']
  203. avg_loss.backward()
  204. optimizer.step()
  205. optimizer.clear_grad()
  206. train_batch_cost += time.time() - batch_start
  207. batch_sum += len(images)
  208. if not isinstance(lr_scheduler, float):
  209. lr_scheduler.step()
  210. # logger and visualdl
  211. # 更新了loss和lr到logs字典
  212. stats = {k: v.numpy().mean() for k, v in loss.items()}
  213. stats['lr'] = lr
  214. train_stats.update(stats)
  215. # only rec and cls need
  216. # 计算Metric,其中包含accuracy,更新accuracy到logs字典
  217. if cal_metric_during_train:
  218. batch = [item.numpy() for item in batch]
  219. # 将预测值(128, 80, 6625) 传入CTC解码
  220. post_result = post_process_class(preds, batch[1])
  221. # print("predict", post_result[0])
  222. # print("real ", post_result[1])
  223. # 初始化评估对象,输入
  224. eval_class(post_result, batch)
  225. # 计算Metric
  226. metric = eval_class.get_metric()
  227. train_stats.update(metric)
  228. if vdl_writer is not None and dist.get_rank() == 0:
  229. for k, v in train_stats.get().items():
  230. vdl_writer.add_scalar('TRAIN/{}'.format(k), v, global_step)
  231. vdl_writer.add_scalar('TRAIN/lr', lr, global_step)
  232. if dist.get_rank(
  233. ) == 0 and global_step > 0 and global_step % print_batch_step == 0:
  234. logs = train_stats.log()
  235. strs = 'epoch: [{}/{}], iter: {}, {}, reader_cost: {:.5f} s, batch_cost: {:.5f} s, samples: {}, ips: {:.5f}'.format(
  236. epoch, epoch_num, global_step, logs, train_reader_cost /
  237. print_batch_step, train_batch_cost / print_batch_step,
  238. batch_sum, batch_sum / train_batch_cost)
  239. logger.info(strs)
  240. train_batch_cost = 0.0
  241. train_reader_cost = 0.0
  242. batch_sum = 0
  243. if global_step % 500 == 9:
  244. print("save best accuracy model!")
  245. # save_model(
  246. # model,
  247. # optimizer,
  248. # save_model_dir,
  249. # logger,
  250. # is_best=True,
  251. # prefix='best_accuracy',
  252. # best_model_dict=best_model_dict,
  253. # epoch=epoch)
  254. paddle.jit.save(
  255. layer=model,
  256. path=config['Global']['save_model_dir']+'best_accuracy'
  257. # input_spec=[example_image]
  258. )
  259. if global_step % 25000 == 1:
  260. print("save iters model!")
  261. # save_model(
  262. # model,
  263. # optimizer,
  264. # save_model_dir,
  265. # logger,
  266. # is_best=False,
  267. # prefix='iter_{}'.format(global_step),
  268. # best_model_dict=best_model_dict,
  269. # epoch=epoch)
  270. paddle.jit.save(
  271. layer=model,
  272. path=config['Global']['save_model_dir']+'iter_{}'.format(global_step)
  273. # input_spec=[example_image]
  274. )
  275. # 训练过程中测试阶段
  276. # eval
  277. if global_step > start_eval_step and \
  278. (global_step - start_eval_step) % eval_batch_step == 0 and dist.get_rank() == 0:
  279. # 算法为SRN时平均
  280. if model_average:
  281. Model_Average = paddle.incubate.optimizer.ModelAverage(
  282. 0.15,
  283. parameters=model.parameters(),
  284. min_average_window=10000,
  285. max_average_window=15625)
  286. Model_Average.apply()
  287. cur_metric = eval(
  288. model,
  289. valid_dataloader,
  290. post_process_class,
  291. eval_class,
  292. use_srn=use_srn)
  293. cur_metric_str = 'cur metric, {}'.format(', '.join(
  294. ['{}: {}'.format(k, v) for k, v in cur_metric.items()]))
  295. logger.info("acc: " + str(cur_metric.get("acc")) +
  296. " norm_edit_dis: " + str(cur_metric.get("norm_edit_dis")) +
  297. " fps: " + str(cur_metric.get("fps")))
  298. # logger metric
  299. if vdl_writer is not None:
  300. for k, v in cur_metric.items():
  301. if isinstance(v, (float, int)):
  302. vdl_writer.add_scalar('EVAL/{}'.format(k),
  303. cur_metric[k], global_step)
  304. if cur_metric[main_indicator] >= best_model_dict[
  305. main_indicator]:
  306. best_model_dict.update(cur_metric)
  307. best_model_dict['best_epoch'] = epoch
  308. # save_model(
  309. # model,
  310. # optimizer,
  311. # save_model_dir,
  312. # logger,
  313. # is_best=True,
  314. # prefix='best_accuracy',
  315. # best_model_dict=best_model_dict,
  316. # epoch=epoch)
  317. paddle.jit.save(
  318. layer=model,
  319. path=config['Global']['save_model_dir']+'best_accuracy'
  320. # input_spec=[example_image]
  321. )
  322. best_str = 'best metric, {}'.format(', '.join([
  323. '{}: {}'.format(k, v) for k, v in best_model_dict.items()
  324. ]))
  325. # logger.info(best_str)
  326. # logger best metric
  327. if vdl_writer is not None:
  328. vdl_writer.add_scalar('EVAL/best_{}'.format(main_indicator),
  329. best_model_dict[main_indicator],
  330. global_step)
  331. global_step += 1
  332. optimizer.clear_grad()
  333. batch_start = time.time()
  334. # if dist.get_rank() == 0:
  335. # save_model(
  336. # model,
  337. # optimizer,
  338. # save_model_dir,
  339. # logger,
  340. # is_best=False,
  341. # prefix='latest',
  342. # best_model_dict=best_model_dict,
  343. # epoch=epoch)
  344. if dist.get_rank() == 0 and epoch > 0 and epoch % save_epoch_step == 0:
  345. # save_model(
  346. # model,
  347. # optimizer,
  348. # save_model_dir,
  349. # logger,
  350. # is_best=False,
  351. # prefix='iter_epoch_{}'.format(epoch),
  352. # best_model_dict=best_model_dict,
  353. # epoch=epoch)
  354. paddle.jit.save(
  355. layer=model,
  356. path=config['Global']['save_model_dir']+'iter_epoch_{}'.format(epoch)
  357. # input_spec=[example_image]
  358. )
  359. # best_str = 'best metric, {}'.format(', '.join(
  360. # ['{}: {}'.format(k, v) for k, v in best_model_dict.items()]))
  361. # logger.info(best_str)
  362. if dist.get_rank() == 0 and vdl_writer is not None:
  363. vdl_writer.close()
  364. return
  365. def eval(model, valid_dataloader, post_process_class, eval_class,
  366. use_srn=False):
  367. model.eval()
  368. with paddle.no_grad():
  369. total_frame = 0.0
  370. total_time = 0.0
  371. pbar = tqdm(total=len(valid_dataloader), desc='eval model:')
  372. for idx, batch in enumerate(valid_dataloader):
  373. if idx >= len(valid_dataloader):
  374. # if idx >= 2:
  375. break
  376. images = batch[0]
  377. start = time.time()
  378. if use_srn:
  379. others = batch[-4:]
  380. preds = model(images, others)
  381. else:
  382. preds = model(images)
  383. batch = [item.numpy() for item in batch]
  384. # Obtain usable results from post-processing methods
  385. post_result = post_process_class(preds, batch[1])
  386. total_time += time.time() - start
  387. # Evaluate the results of the current batch
  388. eval_class(post_result, batch)
  389. pbar.update(1)
  390. total_frame += len(images)
  391. # Get final metric,eg. acc or hmean
  392. metric = eval_class.get_metric()
  393. pbar.close()
  394. model.train()
  395. metric['fps'] = total_frame / total_time
  396. return metric
  397. def preprocess(is_train=False):
  398. FLAGS = ArgsParser().parse_args()
  399. # 从配置文件读取参数,得到全局参数字典
  400. config = load_config(FLAGS.config)
  401. merge_config(FLAGS.opt)
  402. # check if set use_gpu=True in paddlepaddle cpu version
  403. # 检查gpu配置是否正确
  404. use_gpu = config['Global']['use_gpu']
  405. check_gpu(use_gpu)
  406. # 检查算法类型是否正确
  407. alg = config['Architecture']['algorithm']
  408. assert alg in [
  409. 'EAST', 'DB', 'SAST', 'Rosetta', 'CRNN', 'STARNet', 'RARE', 'SRN', 'CLS'
  410. ]
  411. device = 'gpu:{}'.format(dist.ParallelEnv().dev_id) if use_gpu else 'cpu'
  412. device = paddle.set_device(device)
  413. config['Global']['distributed'] = dist.get_world_size() != 1
  414. # 区分训练和评估,训练需同时保存配置文件和日志文件,评估不需要
  415. if is_train:
  416. # save_config
  417. # ./output/rec/mv3_none_bilstm_ctc/
  418. save_model_dir = config['Global']['save_model_dir']
  419. os.makedirs(save_model_dir, exist_ok=True)
  420. # 保存配置文件
  421. with open(os.path.join(save_model_dir, 'config.yml'), 'w') as f:
  422. yaml.dump(
  423. dict(config), f, default_flow_style=False, sort_keys=False)
  424. log_file = '{}/train.log'.format(save_model_dir)
  425. else:
  426. log_file = None
  427. logger = get_logger(name='root', log_file=log_file)
  428. # 保存visualDL 日志文件
  429. if config['Global']['use_visualdl']:
  430. from visualdl import LogWriter
  431. save_model_dir = config['Global']['save_model_dir']
  432. vdl_writer_path = '{}/vdl/'.format(save_model_dir)
  433. os.makedirs(vdl_writer_path, exist_ok=True)
  434. vdl_writer = LogWriter(logdir=vdl_writer_path)
  435. else:
  436. vdl_writer = None
  437. print_dict(config, logger)
  438. logger.info('train with paddle {} and device {}'.format(paddle.__version__,
  439. device))
  440. return config, device, logger, vdl_writer