You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

querier.py 19 kB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. # Copyright 2020 Huawei Technologies Co., Ltd
  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. # ============================================================================
  15. """This file is used to define lineage info querier."""
  16. import enum
  17. import functools
  18. import operator
  19. import os
  20. from mindinsight.lineagemgr.common.exceptions.exceptions import \
  21. LineageParamTypeError, LineageSummaryAnalyzeException, \
  22. LineageEventNotExistException, LineageQuerierParamException, \
  23. LineageSummaryParseException, LineageEventFieldNotExistException
  24. from mindinsight.lineagemgr.common.log import logger
  25. from mindinsight.lineagemgr.common.utils import enum_to_list
  26. from mindinsight.lineagemgr.querier.query_model import LineageObj, FIELD_MAPPING
  27. from mindinsight.lineagemgr.summary.lineage_summary_analyzer import \
  28. LineageSummaryAnalyzer
  29. @enum.unique
  30. class ConditionParam(enum.Enum):
  31. """
  32. Filtering and sorting field names.
  33. `LIMIT` represents the number of lineage info per page. `OFFSET` represents
  34. page number. `SORTED_NAME` means to sort by this field. `SORTED_TYPE` means
  35. ascending or descending.
  36. """
  37. LIMIT = 'limit'
  38. OFFSET = 'offset'
  39. SORTED_NAME = 'sorted_name'
  40. SORTED_TYPE = 'sorted_type'
  41. LINEAGE_TYPE = 'lineage_type'
  42. @classmethod
  43. def is_condition_type(cls, value):
  44. """
  45. Judge that the input param is one of field names in the class.
  46. Args:
  47. value (str): The input field name.
  48. Returns:
  49. bool, `True` if the input field name in the class, else `False`.
  50. """
  51. return value in cls._value2member_map_
  52. @enum.unique
  53. class ExpressionType(enum.Enum):
  54. """
  55. Filter condition name definition.
  56. `EQ` means `==`. `LT` means `<`. `GT` means `>`. `LE` means `<=`. `GE` means
  57. `>=`. `IN` means filter value in the specified list.
  58. """
  59. EQ = 'eq'
  60. LT = 'lt'
  61. GT = 'gt'
  62. LE = 'le'
  63. GE = 'ge'
  64. IN = 'in'
  65. @classmethod
  66. def is_valid_exp(cls, key):
  67. """
  68. Judge that the input param is one of filter condition names in the class.
  69. Args:
  70. key (str): The input filter condition name.
  71. Returns:
  72. bool, `True` if the input filter condition name in the class,
  73. else `False`.
  74. """
  75. return key in cls._value2member_map_
  76. @classmethod
  77. def is_match(cls, except_key, except_value, actual_value):
  78. """
  79. Determine whether the value meets the expected requirement.
  80. Args:
  81. except_key (str): The expression key.
  82. except_value (Union[str, int, float, list, tuple]): The expected
  83. value.
  84. actual_value (Union[str, int, float]): The actual value.
  85. Returns:
  86. bool, `True` if the actual value meets the expected requirement,
  87. else `False`.
  88. """
  89. if actual_value is None and except_key in [cls.LT.value, cls.GT.value,
  90. cls.LE.value, cls.GE.value]:
  91. return False
  92. try:
  93. if except_key == cls.IN.value:
  94. state = operator.contains(except_value, actual_value)
  95. else:
  96. state = getattr(operator, except_key)(actual_value, except_value)
  97. except TypeError:
  98. # actual_value can not compare with except_value
  99. return False
  100. return state
  101. @enum.unique
  102. class LineageFilterKey(enum.Enum):
  103. """Summary lineage information filter key."""
  104. METRIC = 'metric'
  105. HYPER_PARAM = 'hyper_parameters'
  106. ALGORITHM = 'algorithm'
  107. TRAIN_DATASET = 'train_dataset'
  108. VALID_DATASET = 'valid_dataset'
  109. MODEL = 'model'
  110. DATASET_GRAPH = 'dataset_graph'
  111. @classmethod
  112. def is_valid_filter_key(cls, key):
  113. """
  114. Judge that the input param is one of field names in the class.
  115. Args:
  116. key (str): The input field name.
  117. Returns:
  118. bool, `True` if the input field name in the class, else `False`.
  119. """
  120. return key in cls._value2member_map_
  121. @classmethod
  122. def get_key_list(cls):
  123. """
  124. Get the filter key name list.
  125. Returns:
  126. list[str], the filter key name list.
  127. """
  128. return [member.value for member in cls]
  129. @enum.unique
  130. class LineageType(enum.Enum):
  131. """Lineage search type."""
  132. DATASET = 'dataset'
  133. MODEL = 'model'
  134. class Querier:
  135. """
  136. The querier of model lineage information.
  137. The class provides model lineage information query function. The information
  138. includes hyper parameters, train dataset, algorithm, model information,
  139. metric, valid dataset, etc.
  140. The class also provides search and sorting capabilities about model lineage
  141. information. You can search and sort by the specified condition.
  142. The condition explain in `ConditionParam` and `ExpressionType` class.
  143. See the method `filter_summary_lineage` for supported fields.
  144. Args:
  145. summary_path (Union[str, list[str]]): The single summary log path or
  146. a list of summary log path.
  147. Raises:
  148. LineageParamTypeError: If the input parameter type is invalid.
  149. LineageQuerierParamException: If the input parameter value is invalid.
  150. LineageSummaryParseException: If all summary logs parsing failed.
  151. """
  152. def __init__(self, summary_path):
  153. self._lineage_objects = []
  154. self._index_map = {}
  155. self._parse_failed_paths = []
  156. self._parse_summary_logs(summary_path)
  157. self._size = len(self._lineage_objects)
  158. def get_summary_lineage(self, summary_dir=None, filter_keys=None):
  159. """
  160. Get summary lineage information.
  161. If a summary dir is specified, the special summary lineage information
  162. will be found. If the summary dir is `None`, all summary lineage
  163. information will be found.
  164. Returns the content corresponding to the specified field in the filter
  165. key. The contents of the filter key include `metric`, `hyper_parameters`,
  166. `algorithm`, `train_dataset`, `valid_dataset` and `model`. You can
  167. specify multiple filter keys in the `filter_keys`. If the parameter is
  168. `None`, complete information will be returned.
  169. Args:
  170. summary_dir (Union[str, None]): Summary log dir. Default: None.
  171. filter_keys (Union[list[str], None]): Filter keys. Default: None.
  172. Returns:
  173. list[dict], summary lineage information.
  174. """
  175. self._parse_fail_summary_logs()
  176. if filter_keys is None:
  177. filter_keys = LineageFilterKey.get_key_list()
  178. else:
  179. for key in filter_keys:
  180. if not LineageFilterKey.is_valid_filter_key(key):
  181. raise LineageQuerierParamException(
  182. filter_keys, 'The filter key {} is invalid.'.format(key)
  183. )
  184. if summary_dir is None:
  185. result = [
  186. item.get_summary_info(filter_keys) for item in self._lineage_objects
  187. ]
  188. else:
  189. index = self._index_map.get(summary_dir)
  190. if index is None:
  191. raise LineageQuerierParamException(
  192. 'summary_dir',
  193. 'Summary dir {} does not exist.'.format(summary_dir)
  194. )
  195. lineage_obj = self._lineage_objects[index]
  196. result = [lineage_obj.get_summary_info(filter_keys)]
  197. return result
  198. def filter_summary_lineage(self, condition=None):
  199. """
  200. Filter and sort lineage information based on the specified condition.
  201. See `ConditionType` and `ExpressionType` class for the rule of filtering
  202. and sorting. The filtering and sorting fields are defined in
  203. `FIELD_MAPPING` or prefixed with `metric/` or 'user_defined/'.
  204. If the condition is `None`, all model lineage information will be
  205. returned.
  206. Args:
  207. condition (Union[dict, None]): Filter and sort condition.
  208. Default: None.
  209. Returns:
  210. dict, filtered and sorted model lineage information.
  211. """
  212. def _filter(lineage_obj: LineageObj):
  213. for condition_key, condition_value in condition.items():
  214. if ConditionParam.is_condition_type(condition_key):
  215. continue
  216. if self._is_valid_field(condition_key):
  217. raise LineageQuerierParamException(
  218. 'condition',
  219. 'The field {} not supported'.format(condition_key)
  220. )
  221. value = lineage_obj.get_value_by_key(condition_key)
  222. for exp_key, exp_value in condition_value.items():
  223. if not ExpressionType.is_valid_exp(exp_key):
  224. raise LineageQuerierParamException(
  225. 'condition',
  226. 'The expression {} not supported.'.format(exp_key)
  227. )
  228. if not ExpressionType.is_match(exp_key, exp_value, value):
  229. return False
  230. return True
  231. def _cmp(obj1: LineageObj, obj2: LineageObj):
  232. value1 = obj1.get_value_by_key(sorted_name)
  233. value2 = obj2.get_value_by_key(sorted_name)
  234. if value1 is None and value2 is None:
  235. cmp_result = 0
  236. elif value1 is None:
  237. cmp_result = -1
  238. elif value2 is None:
  239. cmp_result = 1
  240. else:
  241. try:
  242. cmp_result = (value1 > value2) - (value1 < value2)
  243. except TypeError:
  244. type1 = type(value1).__name__
  245. type2 = type(value2).__name__
  246. cmp_result = (type1 > type2) - (type1 < type2)
  247. return cmp_result
  248. self._parse_fail_summary_logs()
  249. if condition is None:
  250. condition = {}
  251. results = list(filter(_filter, self._lineage_objects))
  252. if ConditionParam.SORTED_NAME.value in condition:
  253. sorted_name = condition.get(ConditionParam.SORTED_NAME.value)
  254. if self._is_valid_field(sorted_name):
  255. raise LineageQuerierParamException(
  256. 'condition',
  257. 'The sorted name {} not supported.'.format(sorted_name)
  258. )
  259. sorted_type = condition.get(ConditionParam.SORTED_TYPE.value)
  260. reverse = sorted_type == 'descending'
  261. results = sorted(
  262. results, key=functools.cmp_to_key(_cmp), reverse=reverse
  263. )
  264. offset_results = self._handle_limit_and_offset(condition, results)
  265. customized = self._organize_customized(offset_results)
  266. lineage_types = condition.get(ConditionParam.LINEAGE_TYPE.value)
  267. lineage_types = self._get_lineage_types(lineage_types)
  268. object_items = []
  269. for item in offset_results:
  270. lineage_object = dict()
  271. if LineageType.MODEL.value in lineage_types:
  272. lineage_object.update(item.to_model_lineage_dict())
  273. if LineageType.DATASET.value in lineage_types:
  274. lineage_object.update(item.to_dataset_lineage_dict())
  275. object_items.append(lineage_object)
  276. lineage_info = {
  277. 'customized': customized,
  278. 'object': object_items,
  279. 'count': len(results)
  280. }
  281. return lineage_info
  282. def _organize_customized(self, offset_results):
  283. """Organize customized."""
  284. customized = dict()
  285. for offset_result in offset_results:
  286. for obj_name in ["metric", "user_defined"]:
  287. self._organize_customized_item(customized, offset_result, obj_name)
  288. # If types contain numbers and string, it will be "mixed".
  289. # If types contain "int" and "float", it will be "float".
  290. for key, value in customized.items():
  291. types = value["type"]
  292. if len(types) == 1:
  293. customized[key]["type"] = list(types)[0]
  294. elif types.issubset(["int", "float"]):
  295. customized[key]["type"] = "float"
  296. else:
  297. customized[key]["type"] = "mixed"
  298. return customized
  299. def _organize_customized_item(self, customized, offset_result, obj_name):
  300. """Organize customized item."""
  301. obj = getattr(offset_result, obj_name)
  302. require = bool(obj_name == "metric")
  303. if obj and isinstance(obj, dict):
  304. for key, value in obj.items():
  305. label = f'{obj_name}/{key}'
  306. current_type = type(value).__name__
  307. if customized.get(label) is None:
  308. customized[label] = dict()
  309. customized[label]["label"] = label
  310. # user defined info is not displayed by default
  311. customized[label]["required"] = require
  312. customized[label]["type"] = set()
  313. customized[label]["type"].add(current_type)
  314. def _get_lineage_types(self, lineage_type_param):
  315. """
  316. Get lineage types.
  317. Args:
  318. lineage_type_param (dict): A dict contains "in" or "eq".
  319. Returns:
  320. list, lineage type.
  321. """
  322. # lineage_type_param is None or an empty dict
  323. if not lineage_type_param:
  324. return enum_to_list(LineageType)
  325. if lineage_type_param.get("in") is not None:
  326. return lineage_type_param.get("in")
  327. return [lineage_type_param.get("eq")]
  328. def _is_valid_field(self, field_name):
  329. """
  330. Check if field name is valid.
  331. Args:
  332. field_name (str): Field name.
  333. Returns:
  334. bool, `True` if the field name is valid, else `False`.
  335. """
  336. return field_name not in FIELD_MAPPING and \
  337. not field_name.startswith(('metric/', 'user_defined/'))
  338. def _handle_limit_and_offset(self, condition, result):
  339. """
  340. Handling the condition of `limit` and `offset`.
  341. Args:
  342. condition (dict): Filter and sort condition.
  343. result (list[LineageObj]): Filtered and sorted result.
  344. Returns:
  345. list[LineageObj], paginated result.
  346. """
  347. offset = 0
  348. limit = 10
  349. if ConditionParam.OFFSET.value in condition:
  350. offset = condition.get(ConditionParam.OFFSET.value)
  351. if ConditionParam.LIMIT.value in condition:
  352. limit = condition.get(ConditionParam.LIMIT.value)
  353. if ConditionParam.OFFSET.value not in condition \
  354. and ConditionParam.LIMIT.value not in condition:
  355. offset_result = result
  356. else:
  357. offset_result = result[offset * limit: limit * (offset + 1)]
  358. return offset_result
  359. def _parse_summary_logs(self, summary_path):
  360. """
  361. Parse summary logs.
  362. Args:
  363. summary_path (Union[str, list[str]]): The single summary log path or
  364. a list of summary log path.
  365. """
  366. if not summary_path:
  367. raise LineageQuerierParamException(
  368. 'summary_path', 'The summary path is empty.'
  369. )
  370. if isinstance(summary_path, str):
  371. self._parse_summary_log(summary_path, 0)
  372. elif isinstance(summary_path, list):
  373. index = 0
  374. for path in summary_path:
  375. parse_result = self._parse_summary_log(path, index)
  376. if parse_result:
  377. index += 1
  378. else:
  379. raise LineageParamTypeError('Summary path is not str or list.')
  380. if self._parse_failed_paths:
  381. logger.info('Parse failed paths: %s', str(self._parse_failed_paths))
  382. if not self._lineage_objects:
  383. raise LineageSummaryParseException()
  384. def _parse_summary_log(self, log_path, index: int, is_save_fail_path=True):
  385. """
  386. Parse the single summary log.
  387. Args:
  388. log_path (str): The single summary log path.
  389. index (int): TrainInfo instance index in the train info list.
  390. is_save_fail_path (bool): Set whether to save the failed summary
  391. path. Default: True.
  392. Returns:
  393. bool, `True` if parse summary log success, else `False`.
  394. """
  395. log_dir = os.path.dirname(log_path)
  396. try:
  397. lineage_info = LineageSummaryAnalyzer.get_summary_infos(log_path)
  398. user_defined_info = LineageSummaryAnalyzer.get_user_defined_info(log_path)
  399. lineage_obj = LineageObj(
  400. log_dir,
  401. train_lineage=lineage_info.train_lineage,
  402. evaluation_lineage=lineage_info.eval_lineage,
  403. dataset_graph=lineage_info.dataset_graph,
  404. user_defined_info=user_defined_info
  405. )
  406. self._lineage_objects.append(lineage_obj)
  407. self._add_dataset_mark()
  408. self._index_map[log_dir] = index
  409. return True
  410. except (LineageSummaryAnalyzeException,
  411. LineageEventNotExistException,
  412. LineageEventFieldNotExistException):
  413. if is_save_fail_path:
  414. self._parse_failed_paths.append(log_path)
  415. return False
  416. def _parse_fail_summary_logs(self):
  417. """Parse fail summary logs."""
  418. if self._parse_failed_paths:
  419. failed_paths = []
  420. for path in self._parse_failed_paths:
  421. parse_result = self._parse_summary_log(path, self._size, False)
  422. if parse_result:
  423. self._size += 1
  424. else:
  425. failed_paths.append(path)
  426. self._parse_failed_paths = failed_paths
  427. def _add_dataset_mark(self):
  428. """Add dataset mark into LineageObj."""
  429. # give a dataset mark for each dataset graph in lineage information
  430. marked_dataset_group = {'1': None}
  431. for lineage in self._lineage_objects:
  432. dataset_mark = '0'
  433. for dataset_graph_mark, marked_dataset_graph in marked_dataset_group.items():
  434. if marked_dataset_graph == lineage.dataset_graph:
  435. dataset_mark = dataset_graph_mark
  436. break
  437. # if no matched, add the new dataset graph into group
  438. if dataset_mark == '0':
  439. dataset_mark = str(int(max(marked_dataset_group.keys())) + 1)
  440. marked_dataset_group.update({
  441. dataset_mark:
  442. lineage.dataset_graph
  443. })
  444. lineage.dataset_mark = dataset_mark

MindInsight为MindSpore提供了简单易用的调优调试能力。在训练过程中,可以将标量、张量、图像、计算图、模型超参、训练耗时等数据记录到文件中,通过MindInsight可视化页面进行查看及分析。