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.

tensor.py 12 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  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. """Tensor utils."""
  16. import numpy as np
  17. from mindinsight.utils.exceptions import ParamValueError
  18. from mindinsight.utils.exceptions import ParamTypeError
  19. from mindinsight.utils.log import utils_logger as logger
  20. F32_MIN, F32_MAX = np.finfo(np.float32).min, np.finfo(np.float32).max
  21. MAX_DIMENSIONS_FOR_TENSOR = 2
  22. class Statistics:
  23. """Statistics data class.
  24. Args:
  25. max_value (float): max value of tensor data.
  26. min_value (float): min value of tensor data.
  27. avg_value (float): avg value of tensor data.
  28. count (int): total count of tensor data.
  29. nan_count (int): count of NAN.
  30. neg_inf_count (int): count of negative INF.
  31. pos_inf_count (int): count of positive INF.
  32. """
  33. def __init__(self, max_value=0, min_value=0, avg_value=0,
  34. count=0, nan_count=0, neg_inf_count=0, pos_inf_count=0):
  35. self._max = max_value
  36. self._min = min_value
  37. self._avg = avg_value
  38. self._count = count
  39. self._nan_count = nan_count
  40. self._neg_inf_count = neg_inf_count
  41. self._pos_inf_count = pos_inf_count
  42. @property
  43. def max(self):
  44. """Get max value of tensor."""
  45. return self._max
  46. @property
  47. def min(self):
  48. """Get min value of tensor."""
  49. return self._min
  50. @property
  51. def avg(self):
  52. """Get avg value of tensor."""
  53. return self._avg
  54. @property
  55. def count(self):
  56. """Get total count of tensor."""
  57. return self._count
  58. @property
  59. def nan_count(self):
  60. """Get count of NAN."""
  61. return self._nan_count
  62. @property
  63. def neg_inf_count(self):
  64. """Get count of negative INF."""
  65. return self._neg_inf_count
  66. @property
  67. def pos_inf_count(self):
  68. """Get count of positive INF."""
  69. return self._pos_inf_count
  70. class TensorComparison:
  71. """TensorComparison class.
  72. Args:
  73. tolerance (float): tolerance for calculating tensor diff.
  74. stats (float): statistics of tensor diff.
  75. value (numpy.ndarray): tensor diff.
  76. """
  77. def __init__(self, tolerance=0, stats=None, value=None):
  78. self._tolerance = tolerance
  79. self._stats = stats
  80. self._value = value
  81. @property
  82. def tolerance(self):
  83. """Get tolerance of TensorComparison."""
  84. return self._tolerance
  85. @property
  86. def stats(self):
  87. """Get stats of tensor diff."""
  88. return self._stats
  89. def update(self, tolerance, value):
  90. """update tensor comparisons."""
  91. self._tolerance = tolerance
  92. self._value = value
  93. @property
  94. def value(self):
  95. """Get value of tensor diff."""
  96. return self._value
  97. def str_to_slice_or_int(input_str):
  98. """
  99. Translate param from string to slice or int.
  100. Args:
  101. input_str (str): The string to be translated.
  102. Returns:
  103. Union[int, slice], the transformed param.
  104. """
  105. try:
  106. if ':' in input_str:
  107. ret = slice(*map(lambda x: int(x.strip()) if x.strip() else None, input_str.split(':')))
  108. else:
  109. ret = int(input_str)
  110. except ValueError:
  111. raise ParamValueError("Invalid shape. Convert int from str failed. input_str: {}".format(input_str))
  112. return ret
  113. class TensorUtils:
  114. """Tensor Utils class."""
  115. @staticmethod
  116. def parse_shape(shape, limit=0):
  117. """
  118. Parse shape from str.
  119. Args:
  120. shape (str): Specify shape of tensor.
  121. limit (int): The max dimensions specified. Default value is 0 which means that there is no limitation.
  122. Returns:
  123. Union[None, tuple], a string like this: "[0, 0, 1:10, :]" will convert to this value:
  124. (0, 0, slice(1, 10, None), slice(None, None, None)].
  125. Raises:
  126. ParamValueError, If type of shape is not str or format is not correct or exceed specified dimensions.
  127. """
  128. if shape is None:
  129. return shape
  130. if not (isinstance(shape, str) and shape.strip().startswith('[') and shape.strip().endswith(']')):
  131. raise ParamValueError("Invalid shape. The type of shape should be str and start with `[` and "
  132. "end with `]`. Received: {}.".format(shape))
  133. shape = shape.strip()[1:-1]
  134. dimension_size = sum(1 for dim in shape.split(',') if dim.count(':'))
  135. if limit and dimension_size > limit:
  136. raise ParamValueError("Invalid shape. At most {} dimensions are specified. Received: {}"
  137. .format(limit, shape))
  138. parsed_shape = tuple(
  139. str_to_slice_or_int(dim.strip()) for dim in shape.split(',')) if shape else tuple()
  140. return parsed_shape
  141. @staticmethod
  142. def get_specific_dims_data(ndarray, dims):
  143. """
  144. Get specific dims data.
  145. Args:
  146. ndarray (numpy.ndarray): An ndarray of numpy.
  147. dims (tuple): A tuple of specific dims.
  148. Returns:
  149. numpy.ndarray, an ndarray of specific dims tensor data.
  150. Raises:
  151. ParamValueError, If the length of param dims is not equal to the length of tensor dims.
  152. IndexError, If the param dims and tensor shape is unmatched.
  153. """
  154. if len(ndarray.shape) != len(dims):
  155. raise ParamValueError("Invalid dims. The length of param dims and tensor shape should be the same.")
  156. try:
  157. result = ndarray[dims]
  158. except IndexError:
  159. raise ParamValueError("Invalid shape. Shape unmatched. Received: {}, tensor shape: {}"
  160. .format(dims, ndarray.shape))
  161. # Make sure the return type is numpy.ndarray.
  162. if not isinstance(result, np.ndarray):
  163. result = np.array(result)
  164. return result
  165. @staticmethod
  166. def get_statistics_from_tensor(tensors):
  167. """
  168. Calculates statistics data of tensor.
  169. Args:
  170. tensors (numpy.ndarray): An numpy.ndarray of tensor data.
  171. Returns:
  172. an instance of Statistics.
  173. """
  174. ma_value = np.ma.masked_invalid(tensors)
  175. total, valid = tensors.size, ma_value.count()
  176. invalids = []
  177. for isfn in np.isnan, np.isposinf, np.isneginf:
  178. if total - valid > sum(invalids):
  179. count = np.count_nonzero(isfn(tensors))
  180. invalids.append(count)
  181. else:
  182. invalids.append(0)
  183. nan_count, pos_inf_count, neg_inf_count = invalids
  184. if not valid:
  185. logger.warning('There are no valid values in the tensors(size=%d, shape=%s)', total, tensors.shape)
  186. statistics = Statistics(max_value=0,
  187. min_value=0,
  188. avg_value=0,
  189. count=total,
  190. nan_count=nan_count,
  191. neg_inf_count=neg_inf_count,
  192. pos_inf_count=pos_inf_count)
  193. return statistics
  194. # BUG: max of a masked array with dtype np.float16 returns inf
  195. # See numpy issue#15077
  196. if issubclass(tensors.dtype.type, np.floating):
  197. tensor_min = ma_value.min(fill_value=np.PINF)
  198. tensor_max = ma_value.max(fill_value=np.NINF)
  199. if tensor_min < F32_MIN or tensor_max > F32_MAX:
  200. logger.warning('Values(%f, %f) are too large, you may encounter some undefined '
  201. 'behaviours hereafter.', tensor_min, tensor_max)
  202. else:
  203. tensor_min = ma_value.min()
  204. tensor_max = ma_value.max()
  205. tensor_sum = ma_value.sum(dtype=np.float64)
  206. statistics = Statistics(max_value=tensor_max,
  207. min_value=tensor_min,
  208. avg_value=tensor_sum / valid,
  209. count=total,
  210. nan_count=nan_count,
  211. neg_inf_count=neg_inf_count,
  212. pos_inf_count=pos_inf_count)
  213. return statistics
  214. @staticmethod
  215. def get_statistics_dict(stats, overall_stats):
  216. """
  217. Get statistics dict according to statistics value.
  218. Args:
  219. stats (Statistics): An instance of Statistics for sliced tensor.
  220. overall_stats (Statistics): An instance of Statistics for whole tensor.
  221. Returns:
  222. dict, a dict including 'max', 'min', 'avg', 'count',
  223. 'nan_count', 'neg_inf_count', 'pos_inf_count', 'overall_max', 'overall_min'.
  224. """
  225. statistics = {
  226. "max": float(stats.max),
  227. "min": float(stats.min),
  228. "avg": float(stats.avg),
  229. "count": stats.count,
  230. "nan_count": stats.nan_count,
  231. "neg_inf_count": stats.neg_inf_count,
  232. "pos_inf_count": stats.pos_inf_count,
  233. "overall_max": float(overall_stats.max),
  234. "overall_min": float(overall_stats.min)
  235. }
  236. return statistics
  237. @staticmethod
  238. def calc_diff_between_two_tensor(first_tensor, second_tensor, tolerance):
  239. """
  240. Calculate the difference between the first tensor and the second tensor.
  241. Args:
  242. first_tensor (numpy.ndarray): Specify the first tensor.
  243. second_tensor (numpy.ndarray): Specify the second tensor.
  244. tolerance (float): The tolerance of difference between the first tensor and the second tensor.
  245. Its is a percentage. The boundary value is equal to max(abs(min),abs(max)) * tolerance.
  246. The function of min and max is being used to calculate the min value and max value of
  247. the result of the first tensor subtract the second tensor. If the absolute value of
  248. result is less than or equal to boundary value, the result will set to be zero.
  249. Returns:
  250. tuple[numpy.ndarray, OverallDiffMetric], numpy.ndarray indicates the value of the first tensor
  251. subtract the second tensor and set the value to be zero when its less than or equal to tolerance.
  252. Raises:
  253. ParamTypeError: If the type of these two tensors is not the numpy.ndarray.
  254. ParamValueError: If the shape or dtype is not the same of these two tensors or
  255. the tolerance should be between 0 and 1.
  256. """
  257. if not isinstance(first_tensor, np.ndarray):
  258. raise ParamTypeError('first_tensor', np.ndarray)
  259. if not isinstance(second_tensor, np.ndarray):
  260. raise ParamTypeError('second_tensor', np.ndarray)
  261. if first_tensor.shape != second_tensor.shape:
  262. raise ParamValueError("the shape: {} of first tensor is not equal to shape: {} of second tensor."
  263. .format(first_tensor.shape, second_tensor.shape))
  264. if first_tensor.dtype != second_tensor.dtype:
  265. raise ParamValueError("the dtype: {} of first tensor is not equal to dtype: {} of second tensor."
  266. .format(first_tensor.dtype, second_tensor.dtype))
  267. # Make sure tolerance is between 0 and 1.
  268. if tolerance < 0 or tolerance > 1:
  269. raise ParamValueError("the tolerance should be between 0 and 1, but got {}".format(tolerance))
  270. diff_tensor = np.subtract(first_tensor, second_tensor)
  271. stats = TensorUtils.get_statistics_from_tensor(diff_tensor)
  272. boundary_value = max(abs(stats.max), abs(stats.min)) * tolerance
  273. is_close = np.isclose(first_tensor, second_tensor, atol=boundary_value, rtol=0)
  274. result = np.multiply(diff_tensor, ~is_close)
  275. return result