data_feeder.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. # Copyright (c) 2018 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. import struct
  15. import numpy as np
  16. from paddle import pir
  17. from ..pir import Value
  18. from ..pir.core import _PADDLE_PIR_DTYPE_2_NUMPY_DTYPE, ParameterMeta
  19. from . import core
  20. from .framework import (
  21. Variable,
  22. _cpu_num,
  23. _cuda_ids,
  24. default_main_program,
  25. in_dygraph_mode,
  26. in_pir_mode,
  27. )
  28. __all__ = []
  29. _PADDLE_DTYPE_2_NUMPY_DTYPE = {
  30. core.VarDesc.VarType.BOOL: 'bool',
  31. core.VarDesc.VarType.FP16: 'float16',
  32. core.VarDesc.VarType.BF16: 'uint16',
  33. core.VarDesc.VarType.FP32: 'float32',
  34. core.VarDesc.VarType.FP64: 'float64',
  35. core.VarDesc.VarType.INT8: 'int8',
  36. core.VarDesc.VarType.INT16: 'int16',
  37. core.VarDesc.VarType.INT32: 'int32',
  38. core.VarDesc.VarType.INT64: 'int64',
  39. core.VarDesc.VarType.UINT8: 'uint8',
  40. core.VarDesc.VarType.COMPLEX64: 'complex64',
  41. core.VarDesc.VarType.COMPLEX128: 'complex128',
  42. }
  43. _NUMPY_DTYPE_2_PADDLE_DTYPE = {
  44. 'bool': core.VarDesc.VarType.BOOL,
  45. 'float16': core.VarDesc.VarType.FP16,
  46. 'uint16': core.VarDesc.VarType.BF16,
  47. 'float32': core.VarDesc.VarType.FP32,
  48. 'float64': core.VarDesc.VarType.FP64,
  49. 'int8': core.VarDesc.VarType.INT8,
  50. 'int16': core.VarDesc.VarType.INT16,
  51. 'int32': core.VarDesc.VarType.INT32,
  52. 'int64': core.VarDesc.VarType.INT64,
  53. 'uint8': core.VarDesc.VarType.UINT8,
  54. 'complex64': core.VarDesc.VarType.COMPLEX64,
  55. 'complex128': core.VarDesc.VarType.COMPLEX128,
  56. }
  57. def convert_float_to_uint16(data, data_format="NCHW"):
  58. if data.size == 0:
  59. return data.view(np.uint16)
  60. if data_format == "NHWC":
  61. data = np.transpose(data, [0, 3, 1, 2])
  62. new_data = np.vectorize(
  63. lambda x: struct.unpack('<I', struct.pack('<f', x))[0] >> 16,
  64. otypes=[np.uint16],
  65. )(data.flat)
  66. new_data = np.reshape(new_data, data.shape)
  67. if data_format == "NHWC":
  68. new_data = np.transpose(new_data, [0, 2, 3, 1])
  69. return new_data
  70. def convert_uint16_to_float(data):
  71. new_data = np.vectorize(
  72. lambda x: struct.unpack('<f', struct.pack('<I', x << 16))[0],
  73. otypes=[np.float32],
  74. )(data.flat)
  75. return np.reshape(new_data, data.shape)
  76. def convert_dtype(dtype):
  77. if isinstance(dtype, core.VarDesc.VarType):
  78. if dtype in _PADDLE_DTYPE_2_NUMPY_DTYPE:
  79. return _PADDLE_DTYPE_2_NUMPY_DTYPE[dtype]
  80. if isinstance(dtype, core.DataType):
  81. if dtype in _PADDLE_PIR_DTYPE_2_NUMPY_DTYPE:
  82. return _PADDLE_PIR_DTYPE_2_NUMPY_DTYPE[dtype]
  83. elif isinstance(dtype, type):
  84. # This branch is for NumPy scalar types
  85. if dtype in [
  86. bool,
  87. np.float16,
  88. np.uint16,
  89. np.float32,
  90. np.float64,
  91. np.int8,
  92. np.int16,
  93. np.int32,
  94. np.int64,
  95. np.uint8,
  96. np.complex64,
  97. np.complex128,
  98. ]:
  99. return dtype.__name__
  100. else:
  101. # This branch is for np.dtype and str
  102. if dtype in [
  103. 'bool',
  104. 'float16',
  105. 'uint16',
  106. 'float32',
  107. 'float64',
  108. 'int8',
  109. 'int16',
  110. 'int32',
  111. 'int64',
  112. 'uint8',
  113. 'complex64',
  114. 'complex128',
  115. ]:
  116. # NOTE(SigureMo): Since the np.dtype object is not an instance of
  117. # type, so it will not be handled by the previous branch. We need
  118. # to convert it to str here.
  119. return str(dtype)
  120. # NOTE(zhangbo): Now numpy does not support bfloat, so use numpy.uint16 to represent paddle.bfloat16, there binaries are consistent.
  121. # If cast ndarray to uint16 and trans to tensor, should not ndarray.astype('uint16') directly
  122. # should use function 'convert_float_to_uint16' above, otherwise bits is wrong
  123. if dtype in ['bfloat16']:
  124. return 'uint16'
  125. raise TypeError(
  126. "dtype must be any of [bool, float16, uint16, float32, float64, int8, int16, "
  127. "int32, int64, uint8, complex64, complex128, bfloat16], but received %s"
  128. % dtype
  129. )
  130. def check_variable_and_dtype(
  131. input, input_name, expected_dtype, op_name, extra_message=''
  132. ):
  133. if in_pir_mode():
  134. check_type(
  135. input, input_name, (Value, ParameterMeta), op_name, extra_message
  136. )
  137. else:
  138. check_type(input, input_name, Variable, op_name, extra_message)
  139. check_dtype(input.dtype, input_name, expected_dtype, op_name, extra_message)
  140. def check_type(input, input_name, expected_type, op_name, extra_message=''):
  141. # NOTE [ Why skip dynamic graph check ]:
  142. # 1. If the input type / dtype of a layer is wrong, it will be reported
  143. # directly on that line. User can easily print the relevant information
  144. # on which line. It is easier to debug, so there is no need to check
  145. # in dynamic graph mode.
  146. # 2. Performance considerations. Because these checks are executed at
  147. # each step in dynamic graph mode, it will bring a heavy performance burden.
  148. if in_dygraph_mode():
  149. return
  150. # NOTE: `in_to_static_mode` is used to determined whether this op is called under
  151. # @to_static in transformation from dygraph to static layer. We add Tensor in
  152. # expected_type to skip checking because Tensor may be created and used in unusual way.
  153. from .dygraph.base import in_to_static_mode
  154. # Need a better design to be fix this.
  155. if in_to_static_mode():
  156. if not isinstance(expected_type, tuple):
  157. expected_type = (expected_type,)
  158. expected_type += (core.eager.Tensor,)
  159. elif isinstance(input, core.eager.Tensor):
  160. raise TypeError(
  161. "Please use `with base.dygraph.guard()` as context or `base.enable_dygraph()` to switch to imperative mode firstly. "
  162. f"Because received '{input_name}' in {op_name} is a imperative Variable."
  163. )
  164. if not isinstance(input, expected_type):
  165. raise TypeError(
  166. f"The type of '{input_name}' in {op_name} must be {expected_type}, but received {type(input)}. {extra_message}"
  167. )
  168. def check_dtype(
  169. input_dtype, input_name, expected_dtype, op_name, extra_message=''
  170. ):
  171. # See NOTE [ Why skip dynamic graph check ]
  172. if in_dygraph_mode():
  173. return
  174. if convert_dtype(input_dtype) not in expected_dtype:
  175. raise TypeError(
  176. f"The data type of '{input_name}' in {op_name} must be {expected_dtype}, but received {convert_dtype(input_dtype)}. {extra_message}"
  177. )
  178. def check_shape(
  179. shape,
  180. op_name,
  181. expected_shape_type=(list, tuple, Variable, Value),
  182. expected_element_type=(int, Variable, Value),
  183. expected_tensor_dtype=('int32', 'int64'),
  184. ):
  185. # See NOTE [ Why skip dynamic graph check ]
  186. if in_dygraph_mode():
  187. return
  188. check_type(shape, 'shape', expected_shape_type, op_name)
  189. if expected_element_type is not None and not isinstance(
  190. shape, (Variable, Value)
  191. ):
  192. for item in shape:
  193. check_type(item, 'element of shape', expected_element_type, op_name)
  194. if expected_tensor_dtype is not None and isinstance(
  195. item, (Variable, Value)
  196. ):
  197. check_dtype(
  198. item.dtype,
  199. 'element of shape',
  200. expected_tensor_dtype,
  201. op_name,
  202. 'If element of shape is Tensor, its data type should be {}'.format(
  203. ', '.join(expected_tensor_dtype)
  204. ),
  205. )
  206. if expected_tensor_dtype is not None and isinstance(
  207. shape, (Variable, Value)
  208. ):
  209. check_dtype(shape.dtype, 'shape', expected_tensor_dtype, op_name)
  210. class DataToLoDTensorConverter:
  211. def __init__(self, place, lod_level, shape, dtype):
  212. self.place = place
  213. self.lod_level = lod_level
  214. self.shape = shape
  215. negative_count = 0
  216. for s in self.shape:
  217. if s < 0:
  218. negative_count += 1
  219. if negative_count > 1:
  220. self.shape = None
  221. break
  222. self.dtype = convert_dtype(dtype)
  223. self._reset()
  224. def _reset(self):
  225. self.data = []
  226. self.lod = [[] for _ in range(self.lod_level)]
  227. def feed(self, data):
  228. self._feed_impl_(data, self.lod, self.lod_level)
  229. def _feed_impl_(self, data, lod, lod_level):
  230. if lod_level == 0:
  231. self.data.append(data)
  232. else:
  233. lod[0].append(len(data))
  234. for each_data in data:
  235. self._feed_impl_(each_data, lod[1:], lod_level - 1)
  236. def _check_shape(self, shape):
  237. for s1, s2 in zip(self.shape, shape):
  238. if s1 != s2 and s1 >= 0 and s2 >= 0:
  239. raise ValueError(
  240. f"Shape not match. What is defined in data layer is {self.shape}, but receive {shape}"
  241. )
  242. def done(self):
  243. arr = np.array(self.data, dtype=self.dtype)
  244. if self.shape:
  245. if len(arr.shape) != len(self.shape):
  246. try:
  247. arr = arr.reshape(self.shape)
  248. except ValueError:
  249. raise ValueError(
  250. f"Reshape error. What is defined in data layer is {self.shape}, but receive {arr.shape}"
  251. )
  252. t = core.LoDTensor()
  253. t.set(arr, self.place)
  254. if self.lod_level > 0:
  255. t.set_recursive_sequence_lengths(self.lod)
  256. self._reset()
  257. return t
  258. class BatchedTensorProvider:
  259. def __init__(self, feed_list, place, batch_size, generator, drop_last):
  260. self.place = place
  261. self.batch_size = batch_size
  262. self.generator = generator
  263. self.converters = []
  264. self.drop_last = drop_last
  265. for var in feed_list:
  266. assert var.lod_level == 0, "lod_level must be 0"
  267. self.converters.append(
  268. DataToLoDTensorConverter(
  269. place=self.place,
  270. lod_level=0,
  271. shape=var.shape,
  272. dtype=var.dtype,
  273. )
  274. )
  275. def _done(self):
  276. return [c.done() for c in self.converters]
  277. def __call__(self):
  278. idx = 0
  279. for each_sample in self.generator():
  280. for each_slot, each_converter in zip(each_sample, self.converters):
  281. each_converter.data.append(each_slot)
  282. idx += 1
  283. if idx == self.batch_size:
  284. idx = 0
  285. yield self._done()
  286. if not self.drop_last and idx > 0:
  287. yield self._done()
  288. else:
  289. [c._reset() for c in self.converters]
  290. class DataFeeder:
  291. """
  292. :api_attr: Static Graph
  293. DataFeeder converts the data that returned by a reader into a data
  294. structure that can feed into Executor. The reader is usually a
  295. python generator that returns a list of mini-batch data entries.
  296. Parameters:
  297. feed_list (list): Variables or names of Variables that need
  298. to feed.
  299. place (:ref:`api_paddle_CPUPlace` | :ref:`api_paddle_CUDAPlace` ):
  300. place indicates the device (CPU | GPU) the data will be fed into, if
  301. you want to feed data into GPU, please using :code:`base.CUDAPlace(i)`
  302. (:code:`i` represents the GPU id), or if you want to feed data into CPU,
  303. please using :code:`base.CPUPlace()`.
  304. program (:ref:`api_paddle_static_Program` , optional): The Program that will
  305. feed data into, if program is None, it will use default_main_program().
  306. Default None.
  307. Raises:
  308. :code:`ValueError` - If some Variables are not in this Program.
  309. Example:
  310. .. code-block:: python
  311. >>> import numpy as np
  312. >>> import paddle
  313. >>> from paddle import base
  314. >>> paddle.enable_static()
  315. >>> place = paddle.CPUPlace()
  316. >>> def reader():
  317. ... for _ in range(4):
  318. ... yield np.random.random([4]).astype('float32'), np.random.random([3]).astype('float32'),
  319. ...
  320. >>> main_program = paddle.static.Program()
  321. >>> startup_program = paddle.static.Program()
  322. >>> with paddle.static.program_guard(main_program, startup_program):
  323. ... data_1 = paddle.static.data(name='data_1', shape=[None, 2, 2], dtype='float32')
  324. ... data_2 = paddle.static.data(name='data_2', shape=[None, 1, 3], dtype='float32')
  325. ... out = paddle.static.nn.fc(x=[data_1, data_2], size=2)
  326. ... # ...
  327. >>> feeder = base.DataFeeder([data_1, data_2], place)
  328. >>> exe = paddle.static.Executor(place)
  329. >>> exe.run(startup_program)
  330. >>> feed_data = feeder.feed(reader())
  331. >>> # print feed_data to view feed results
  332. >>> # print(feed_data['data_1'])
  333. >>> # print(feed_data['data_2'])
  334. >>> outs = exe.run(
  335. ... program=main_program,
  336. ... feed=feed_data,
  337. ... fetch_list=[out]
  338. ... )
  339. >>> print(outs)
  340. """
  341. def __init__(self, feed_list, place, program=None):
  342. self.feed_dtypes = []
  343. self.feed_names = []
  344. self.feed_shapes = []
  345. self.feed_lod_level = []
  346. self.place = place
  347. if in_pir_mode():
  348. if program is None:
  349. program = pir.core.default_main_program()
  350. for each_var in feed_list:
  351. if isinstance(each_var, str):
  352. raise ValueError(
  353. "In PIR Mode, Not supported string input yet"
  354. )
  355. if not isinstance(each_var, Value):
  356. raise TypeError("Feed list should contain a list of Value")
  357. self.feed_dtypes.append(each_var.dtype)
  358. self.feed_names.append(each_var.name)
  359. self.feed_lod_level.append(0)
  360. self.feed_shapes.append(each_var.shape)
  361. else:
  362. if program is None:
  363. program = default_main_program()
  364. for each_var in feed_list:
  365. if isinstance(each_var, str):
  366. each_var = program.block(0).var(each_var)
  367. if not isinstance(each_var, Variable):
  368. raise TypeError(
  369. "Feed list should contain a list of variable"
  370. )
  371. self.feed_dtypes.append(each_var.dtype)
  372. self.feed_names.append(each_var.name)
  373. self.feed_lod_level.append(each_var.lod_level)
  374. self.feed_shapes.append(each_var.shape)
  375. def feed(self, iterable):
  376. """
  377. According to :code:`feed_list` of :code:`DataFeeder` and :code:`iterable` , converts
  378. the input into a data structure that can feed into Executor.
  379. Parameters:
  380. iterable (generator): user defined python generator to read the raw input data
  381. Returns:
  382. :code:`dict`: a :code:`dict` that contains (variable name - converted tensor) pairs
  383. Example:
  384. .. code-block:: python
  385. >>> # In this example, reader - generator will return a list of ndarray of 3 elements
  386. >>> # feed API will convert each ndarray input into a tensor
  387. >>> # the return result is a dict with keys: data_1, data_2, data_3
  388. >>> # result['data_1'] a LoD-Tensor with shape of [5, 2, 1, 3]. 5 is batch size, and [2, 1, 3] is the real shape of data_1.
  389. >>> # result['data_2'], result['data_3'] are similar.
  390. >>> import numpy as np
  391. >>> import paddle
  392. >>> from paddle import base
  393. >>> paddle.enable_static()
  394. >>> def reader(limit=5):
  395. ... for i in range(1, limit + 1):
  396. ... yield np.ones([6]).astype('float32') * i , np.ones([1]).astype('int64') * i, np.random.random([9]).astype('float32')
  397. ...
  398. >>> data_1 = paddle.static.data(name='data_1', shape=[None, 2, 1, 3])
  399. >>> data_2 = paddle.static.data(name='data_2', shape=[None, 1], dtype='int64')
  400. >>> data_3 = paddle.static.data(name='data_3', shape=[None, 3, 3], dtype='float32')
  401. >>> feeder = base.DataFeeder(['data_1','data_2', 'data_3'], paddle.CPUPlace())
  402. >>> result = feeder.feed(reader())
  403. >>> print(result['data_1'])
  404. >>> print(result['data_2'])
  405. >>> print(result['data_3'])
  406. """
  407. converter = []
  408. for lod_level, shape, dtype in zip(
  409. self.feed_lod_level, self.feed_shapes, self.feed_dtypes
  410. ):
  411. converter.append(
  412. DataToLoDTensorConverter(
  413. place=self.place,
  414. lod_level=lod_level,
  415. shape=shape,
  416. dtype=dtype,
  417. )
  418. )
  419. for each_sample in iterable:
  420. assert len(each_sample) == len(converter), (
  421. "The number of fields in data (%d) does not match "
  422. + "len(feed_list) (%d)"
  423. ) % (len(each_sample), len(converter))
  424. for each_converter, each_slot in zip(converter, each_sample):
  425. each_converter.feed(each_slot)
  426. ret_dict = {}
  427. for each_name, each_converter in zip(self.feed_names, converter):
  428. ret_dict[each_name] = each_converter.done()
  429. return ret_dict
  430. def _get_number_of_places_(self, num_places):
  431. if num_places is not None:
  432. return int(num_places)
  433. elif isinstance(self.place, core.CUDAPlace):
  434. return len(_cuda_ids())
  435. else:
  436. return _cpu_num()