rnn_sequencing.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. """RNN utils for RLlib.
  2. The main trick here is that we add the time dimension at the last moment.
  3. The non-LSTM layers of the model see their inputs as one flat batch. Before
  4. the LSTM cell, we reshape the input to add the expected time dimension. During
  5. postprocessing, we dynamically pad the experience batches so that this
  6. reshaping is possible.
  7. Note that this padding strategy only works out if we assume zero inputs don't
  8. meaningfully affect the loss function. This happens to be true for all the
  9. current algorithms: https://github.com/ray-project/ray/issues/2992
  10. """
  11. import functools
  12. import logging
  13. from typing import List, Optional
  14. import numpy as np
  15. import tree # pip install dm_tree
  16. from ray.rllib.policy.sample_batch import SampleBatch
  17. from ray.rllib.utils.annotations import OldAPIStack
  18. from ray.rllib.utils.debug import summarize
  19. from ray.rllib.utils.framework import try_import_tf, try_import_torch
  20. from ray.rllib.utils.typing import SampleBatchType, TensorType, ViewRequirementsDict
  21. from ray.util import log_once
  22. tf1, tf, tfv = try_import_tf()
  23. torch, _ = try_import_torch()
  24. logger = logging.getLogger(__name__)
  25. @OldAPIStack
  26. def pad_batch_to_sequences_of_same_size(
  27. batch: SampleBatch,
  28. max_seq_len: int,
  29. shuffle: bool = False,
  30. batch_divisibility_req: int = 1,
  31. feature_keys: Optional[List[str]] = None,
  32. view_requirements: Optional[ViewRequirementsDict] = None,
  33. _enable_new_api_stack: bool = False,
  34. padding: str = "zero",
  35. ):
  36. """Applies padding to `batch` so it's choppable into same-size sequences.
  37. Shuffles `batch` (if desired), makes sure divisibility requirement is met,
  38. then pads the batch ([B, ...]) into same-size chunks ([B, ...]) w/o
  39. adding a time dimension (yet).
  40. Padding depends on episodes found in batch and `max_seq_len`.
  41. Args:
  42. batch: The SampleBatch object. All values in here have
  43. the shape [B, ...].
  44. max_seq_len: The max. sequence length to use for chopping.
  45. shuffle: Whether to shuffle batch sequences. Shuffle may
  46. be done in-place. This only makes sense if you're further
  47. applying minibatch SGD after getting the outputs.
  48. batch_divisibility_req: The int by which the batch dimension
  49. must be dividable.
  50. feature_keys: An optional list of keys to apply sequence-chopping
  51. to. If None, use all keys in batch that are not
  52. "state_in/out_"-type keys.
  53. view_requirements: An optional Policy ViewRequirements dict to
  54. be able to infer whether e.g. dynamic max'ing should be
  55. applied over the seq_lens.
  56. _enable_new_api_stack: This is a temporary flag to enable the new RLModule API.
  57. After a complete rollout of the new API, this flag will be removed.
  58. padding: Padding type to use. Either "zero" or "last". Zero padding
  59. will pad with zeros, last padding will pad with the last value.
  60. """
  61. # If already zero-padded, skip.
  62. if batch.zero_padded:
  63. return
  64. batch.zero_padded = True
  65. if batch_divisibility_req > 1:
  66. meets_divisibility_reqs = (
  67. len(batch[SampleBatch.CUR_OBS]) % batch_divisibility_req == 0
  68. # not multiagent
  69. and max(batch[SampleBatch.AGENT_INDEX]) == 0
  70. )
  71. else:
  72. meets_divisibility_reqs = True
  73. states_already_reduced_to_init = False
  74. # RNN/attention net case. Figure out whether we should apply dynamic
  75. # max'ing over the list of sequence lengths.
  76. if _enable_new_api_stack and ("state_in" in batch or "state_out" in batch):
  77. # TODO (Kourosh): This is a temporary fix to enable the new RLModule API.
  78. # We should think of a more elegant solution once we have confirmed that other
  79. # parts of the API are stable and user-friendly.
  80. seq_lens = batch.get(SampleBatch.SEQ_LENS)
  81. # state_in is a nested dict of tensors of states. We need to retreive the
  82. # length of the inner most tensor (which should be already the same as the
  83. # length of other tensors) and compare it to len(seq_lens).
  84. state_ins = tree.flatten(batch["state_in"])
  85. if state_ins:
  86. assert all(
  87. len(state_in) == len(state_ins[0]) for state_in in state_ins
  88. ), "All state_in tensors should have the same batch_dim size."
  89. # if the batch dim of states is the same as the number of sequences
  90. if len(state_ins[0]) == len(seq_lens):
  91. states_already_reduced_to_init = True
  92. # TODO (Kourosh): What is the use-case of DynamicMax functionality?
  93. dynamic_max = True
  94. else:
  95. dynamic_max = False
  96. elif not _enable_new_api_stack and (
  97. "state_in_0" in batch or "state_out_0" in batch
  98. ):
  99. # Check, whether the state inputs have already been reduced to their
  100. # init values at the beginning of each max_seq_len chunk.
  101. if batch.get(SampleBatch.SEQ_LENS) is not None and len(
  102. batch["state_in_0"]
  103. ) == len(batch[SampleBatch.SEQ_LENS]):
  104. states_already_reduced_to_init = True
  105. # RNN (or single timestep state-in): Set the max dynamically.
  106. if view_requirements and view_requirements["state_in_0"].shift_from is None:
  107. dynamic_max = True
  108. # Attention Nets (state inputs are over some range): No dynamic maxing
  109. # possible.
  110. else:
  111. dynamic_max = False
  112. # Multi-agent case.
  113. elif not meets_divisibility_reqs:
  114. max_seq_len = batch_divisibility_req
  115. dynamic_max = False
  116. batch.max_seq_len = max_seq_len
  117. # Simple case: No RNN/attention net, nor do we need to pad.
  118. else:
  119. if shuffle:
  120. batch.shuffle()
  121. return
  122. # RNN, attention net, or multi-agent case.
  123. state_keys = []
  124. feature_keys_ = feature_keys or []
  125. for k, v in batch.items():
  126. if k.startswith("state_in"):
  127. state_keys.append(k)
  128. elif (
  129. not feature_keys
  130. and (not k.startswith("state_out") if not _enable_new_api_stack else True)
  131. and k not in [SampleBatch.SEQ_LENS]
  132. ):
  133. feature_keys_.append(k)
  134. feature_sequences, initial_states, seq_lens = chop_into_sequences(
  135. feature_columns=[batch[k] for k in feature_keys_],
  136. state_columns=[batch[k] for k in state_keys],
  137. episode_ids=batch.get(SampleBatch.EPS_ID),
  138. unroll_ids=batch.get(SampleBatch.UNROLL_ID),
  139. agent_indices=batch.get(SampleBatch.AGENT_INDEX),
  140. seq_lens=batch.get(SampleBatch.SEQ_LENS),
  141. max_seq_len=max_seq_len,
  142. dynamic_max=dynamic_max,
  143. states_already_reduced_to_init=states_already_reduced_to_init,
  144. shuffle=shuffle,
  145. handle_nested_data=True,
  146. padding=padding,
  147. pad_infos_with_empty_dicts=_enable_new_api_stack,
  148. )
  149. for i, k in enumerate(feature_keys_):
  150. batch[k] = tree.unflatten_as(batch[k], feature_sequences[i])
  151. for i, k in enumerate(state_keys):
  152. batch[k] = initial_states[i]
  153. batch[SampleBatch.SEQ_LENS] = np.array(seq_lens)
  154. if dynamic_max:
  155. batch.max_seq_len = max(seq_lens)
  156. if log_once("rnn_ma_feed_dict"):
  157. logger.info(
  158. "Padded input for RNN/Attn.Nets/MA:\n\n{}\n".format(
  159. summarize(
  160. {
  161. "features": feature_sequences,
  162. "initial_states": initial_states,
  163. "seq_lens": seq_lens,
  164. "max_seq_len": max_seq_len,
  165. }
  166. )
  167. )
  168. )
  169. @OldAPIStack
  170. def add_time_dimension(
  171. padded_inputs: TensorType,
  172. *,
  173. seq_lens: TensorType,
  174. framework: str = "tf",
  175. time_major: bool = False,
  176. ):
  177. """Adds a time dimension to padded inputs.
  178. Args:
  179. padded_inputs: a padded batch of sequences. That is,
  180. for seq_lens=[1, 2, 2], then inputs=[A, *, B, B, C, C], where
  181. A, B, C are sequence elements and * denotes padding.
  182. seq_lens: A 1D tensor of sequence lengths, denoting the non-padded length
  183. in timesteps of each rollout in the batch.
  184. framework: The framework string ("tf2", "tf", "torch").
  185. time_major: Whether data should be returned in time-major (TxB)
  186. format or not (BxT).
  187. Returns:
  188. TensorType: Reshaped tensor of shape [B, T, ...] or [T, B, ...].
  189. """
  190. # Sequence lengths have to be specified for LSTM batch inputs. The
  191. # input batch must be padded to the max seq length given here. That is,
  192. # batch_size == len(seq_lens) * max(seq_lens)
  193. if framework in ["tf2", "tf"]:
  194. assert time_major is False, "time-major not supported yet for tf!"
  195. padded_inputs = tf.convert_to_tensor(padded_inputs)
  196. padded_batch_size = tf.shape(padded_inputs)[0]
  197. # Dynamically reshape the padded batch to introduce a time dimension.
  198. new_batch_size = tf.shape(seq_lens)[0]
  199. time_size = padded_batch_size // new_batch_size
  200. new_shape = tf.concat(
  201. [
  202. tf.expand_dims(new_batch_size, axis=0),
  203. tf.expand_dims(time_size, axis=0),
  204. tf.shape(padded_inputs)[1:],
  205. ],
  206. axis=0,
  207. )
  208. return tf.reshape(padded_inputs, new_shape)
  209. elif framework == "torch":
  210. padded_inputs = torch.as_tensor(padded_inputs)
  211. padded_batch_size = padded_inputs.shape[0]
  212. # Dynamically reshape the padded batch to introduce a time dimension.
  213. new_batch_size = seq_lens.shape[0]
  214. time_size = padded_batch_size // new_batch_size
  215. batch_major_shape = (new_batch_size, time_size) + padded_inputs.shape[1:]
  216. padded_outputs = padded_inputs.view(batch_major_shape)
  217. if time_major:
  218. # Swap the batch and time dimensions
  219. padded_outputs = padded_outputs.transpose(0, 1)
  220. return padded_outputs
  221. else:
  222. assert framework == "np", "Unknown framework: {}".format(framework)
  223. padded_inputs = np.asarray(padded_inputs)
  224. padded_batch_size = padded_inputs.shape[0]
  225. # Dynamically reshape the padded batch to introduce a time dimension.
  226. new_batch_size = seq_lens.shape[0]
  227. time_size = padded_batch_size // new_batch_size
  228. batch_major_shape = (new_batch_size, time_size) + padded_inputs.shape[1:]
  229. padded_outputs = padded_inputs.reshape(batch_major_shape)
  230. if time_major:
  231. # Swap the batch and time dimensions
  232. padded_outputs = padded_outputs.transpose(0, 1)
  233. return padded_outputs
  234. @OldAPIStack
  235. def chop_into_sequences(
  236. *,
  237. feature_columns,
  238. state_columns,
  239. max_seq_len,
  240. episode_ids=None,
  241. unroll_ids=None,
  242. agent_indices=None,
  243. dynamic_max=True,
  244. shuffle=False,
  245. seq_lens=None,
  246. states_already_reduced_to_init=False,
  247. handle_nested_data=False,
  248. _extra_padding=0,
  249. padding: str = "zero",
  250. pad_infos_with_empty_dicts: bool = False,
  251. ):
  252. """Truncate and pad experiences into fixed-length sequences.
  253. Args:
  254. feature_columns: List of arrays containing features.
  255. state_columns: List of arrays containing LSTM state values.
  256. max_seq_len: Max length of sequences. Sequences longer than max_seq_len
  257. will be split into subsequences that span the batch dimension
  258. and sum to max_seq_len.
  259. episode_ids (List[EpisodeID]): List of episode ids for each step.
  260. unroll_ids (List[UnrollID]): List of identifiers for the sample batch.
  261. This is used to make sure sequences are cut between sample batches.
  262. agent_indices (List[AgentID]): List of agent ids for each step. Note
  263. that this has to be combined with episode_ids for uniqueness.
  264. dynamic_max: Whether to dynamically shrink the max seq len.
  265. For example, if max len is 20 and the actual max seq len in the
  266. data is 7, it will be shrunk to 7.
  267. shuffle: Whether to shuffle the sequence outputs.
  268. handle_nested_data: If True, assume that the data in
  269. `feature_columns` could be nested structures (of data).
  270. If False, assumes that all items in `feature_columns` are
  271. only np.ndarrays (no nested structured of np.ndarrays).
  272. _extra_padding: Add extra padding to the end of sequences.
  273. padding: Padding type to use. Either "zero" or "last". Zero padding
  274. will pad with zeros, last padding will pad with the last value.
  275. pad_infos_with_empty_dicts: If True, will zero-pad INFOs with empty
  276. dicts (instead of None). Used by the new API stack in the meantime,
  277. however, as soon as the new ConnectorV2 API will be activated (as
  278. part of the new API stack), we will no longer use this utility function
  279. anyway.
  280. Returns:
  281. f_pad: Padded feature columns. These will be of shape
  282. [NUM_SEQUENCES * MAX_SEQ_LEN, ...].
  283. s_init: Initial states for each sequence, of shape
  284. [NUM_SEQUENCES, ...].
  285. seq_lens: List of sequence lengths, of shape [NUM_SEQUENCES].
  286. .. testcode::
  287. :skipif: True
  288. from ray.rllib.policy.rnn_sequencing import chop_into_sequences
  289. f_pad, s_init, seq_lens = chop_into_sequences(
  290. episode_ids=[1, 1, 5, 5, 5, 5],
  291. unroll_ids=[4, 4, 4, 4, 4, 4],
  292. agent_indices=[0, 0, 0, 0, 0, 0],
  293. feature_columns=[[4, 4, 8, 8, 8, 8],
  294. [1, 1, 0, 1, 1, 0]],
  295. state_columns=[[4, 5, 4, 5, 5, 5]],
  296. max_seq_len=3)
  297. print(f_pad)
  298. print(s_init)
  299. print(seq_lens)
  300. .. testoutput::
  301. [[4, 4, 0, 8, 8, 8, 8, 0, 0],
  302. [1, 1, 0, 0, 1, 1, 0, 0, 0]]
  303. [[4, 4, 5]]
  304. [2, 3, 1]
  305. """
  306. if seq_lens is None or len(seq_lens) == 0:
  307. prev_id = None
  308. seq_lens = []
  309. seq_len = 0
  310. unique_ids = np.add(
  311. np.add(episode_ids, agent_indices),
  312. np.array(unroll_ids, dtype=np.int64) << 32,
  313. )
  314. for uid in unique_ids:
  315. if (prev_id is not None and uid != prev_id) or seq_len >= max_seq_len:
  316. seq_lens.append(seq_len)
  317. seq_len = 0
  318. seq_len += 1
  319. prev_id = uid
  320. if seq_len:
  321. seq_lens.append(seq_len)
  322. seq_lens = np.array(seq_lens, dtype=np.int32)
  323. # Dynamically shrink max len as needed to optimize memory usage
  324. if dynamic_max:
  325. max_seq_len = max(seq_lens) + _extra_padding
  326. length = len(seq_lens) * max_seq_len
  327. feature_sequences = []
  328. for col in feature_columns:
  329. if isinstance(col, list):
  330. col = np.array(col)
  331. feature_sequences.append([])
  332. for f in tree.flatten(col):
  333. # Save unnecessary copy.
  334. if not isinstance(f, np.ndarray):
  335. f = np.array(f)
  336. # New stack behavior (temporarily until we move to ConnectorV2 API, where
  337. # this (admitedly convoluted) function will no longer be used at all).
  338. if (
  339. f.dtype == object
  340. and pad_infos_with_empty_dicts
  341. and isinstance(f[0], dict)
  342. ):
  343. f_pad = [{} for _ in range(length)]
  344. # Old stack behavior: Pad INFOs with None.
  345. elif f.dtype == object or f.dtype.type is np.str_:
  346. f_pad = [None] * length
  347. # Pad everything else with zeros.
  348. else:
  349. # Make sure type doesn't change.
  350. f_pad = np.zeros((length,) + np.shape(f)[1:], dtype=f.dtype)
  351. seq_base = 0
  352. i = 0
  353. for len_ in seq_lens:
  354. for seq_offset in range(len_):
  355. f_pad[seq_base + seq_offset] = f[i]
  356. i += 1
  357. if padding == "last":
  358. for seq_offset in range(len_, max_seq_len):
  359. f_pad[seq_base + seq_offset] = f[i - 1]
  360. seq_base += max_seq_len
  361. assert i == len(f), f
  362. feature_sequences[-1].append(f_pad)
  363. if states_already_reduced_to_init:
  364. initial_states = state_columns
  365. else:
  366. initial_states = []
  367. for state_column in state_columns:
  368. if isinstance(state_column, list):
  369. state_column = np.array(state_column)
  370. initial_state_flat = []
  371. # state_column may have a nested structure (e.g. LSTM state).
  372. for s in tree.flatten(state_column):
  373. # Skip unnecessary copy.
  374. if not isinstance(s, np.ndarray):
  375. s = np.array(s)
  376. s_init = []
  377. i = 0
  378. for len_ in seq_lens:
  379. s_init.append(s[i])
  380. i += len_
  381. initial_state_flat.append(np.array(s_init))
  382. initial_states.append(tree.unflatten_as(state_column, initial_state_flat))
  383. if shuffle:
  384. permutation = np.random.permutation(len(seq_lens))
  385. for i, f in enumerate(tree.flatten(feature_sequences)):
  386. orig_shape = f.shape
  387. f = np.reshape(f, (len(seq_lens), -1) + f.shape[1:])
  388. f = f[permutation]
  389. f = np.reshape(f, orig_shape)
  390. feature_sequences[i] = f
  391. for i, s in enumerate(initial_states):
  392. s = s[permutation]
  393. initial_states[i] = s
  394. seq_lens = seq_lens[permutation]
  395. # Classic behavior: Don't assume data in feature_columns are nested
  396. # structs. Don't return them as flattened lists, but as is (index 0).
  397. if not handle_nested_data:
  398. feature_sequences = [f[0] for f in feature_sequences]
  399. return feature_sequences, initial_states, seq_lens
  400. @OldAPIStack
  401. def timeslice_along_seq_lens_with_overlap(
  402. sample_batch: SampleBatchType,
  403. seq_lens: Optional[List[int]] = None,
  404. zero_pad_max_seq_len: int = 0,
  405. pre_overlap: int = 0,
  406. zero_init_states: bool = True,
  407. ) -> List["SampleBatch"]:
  408. """Slices batch along `seq_lens` (each seq-len item produces one batch).
  409. Args:
  410. sample_batch: The SampleBatch to timeslice.
  411. seq_lens (Optional[List[int]]): An optional list of seq_lens to slice
  412. at. If None, use `sample_batch[SampleBatch.SEQ_LENS]`.
  413. zero_pad_max_seq_len: If >0, already zero-pad the resulting
  414. slices up to this length. NOTE: This max-len will include the
  415. additional timesteps gained via setting pre_overlap (see Example).
  416. pre_overlap: If >0, will overlap each two consecutive slices by
  417. this many timesteps (toward the left side). This will cause
  418. zero-padding at the very beginning of the batch.
  419. zero_init_states: Whether initial states should always be
  420. zero'd. If False, will use the state_outs of the batch to
  421. populate state_in values.
  422. Returns:
  423. List[SampleBatch]: The list of (new) SampleBatches.
  424. Examples:
  425. assert seq_lens == [5, 5, 2]
  426. assert sample_batch.count == 12
  427. # self = 0 1 2 3 4 | 5 6 7 8 9 | 10 11 <- timesteps
  428. slices = timeslice_along_seq_lens_with_overlap(
  429. sample_batch=sample_batch.
  430. zero_pad_max_seq_len=10,
  431. pre_overlap=3)
  432. # Z = zero padding (at beginning or end).
  433. # |pre (3)| seq | max-seq-len (up to 10)
  434. # slices[0] = | Z Z Z | 0 1 2 3 4 | Z Z
  435. # slices[1] = | 2 3 4 | 5 6 7 8 9 | Z Z
  436. # slices[2] = | 7 8 9 | 10 11 Z Z Z | Z Z
  437. # Note that `zero_pad_max_seq_len=10` includes the 3 pre-overlaps
  438. # count (makes sure each slice has exactly length 10).
  439. """
  440. if seq_lens is None:
  441. seq_lens = sample_batch.get(SampleBatch.SEQ_LENS)
  442. else:
  443. if sample_batch.get(SampleBatch.SEQ_LENS) is not None and log_once(
  444. "overriding_sequencing_information"
  445. ):
  446. logger.warning(
  447. "Found sequencing information in a batch that will be "
  448. "ignored when slicing. Ignore this warning if you know "
  449. "what you are doing."
  450. )
  451. if seq_lens is None:
  452. max_seq_len = zero_pad_max_seq_len - pre_overlap
  453. if log_once("no_sequence_lengths_available_for_time_slicing"):
  454. logger.warning(
  455. "Trying to slice a batch along sequences without "
  456. "sequence lengths being provided in the batch. Batch will "
  457. "be sliced into slices of size "
  458. "{} = {} - {} = zero_pad_max_seq_len - pre_overlap.".format(
  459. max_seq_len, zero_pad_max_seq_len, pre_overlap
  460. )
  461. )
  462. num_seq_lens, last_seq_len = divmod(len(sample_batch), max_seq_len)
  463. seq_lens = [zero_pad_max_seq_len] * num_seq_lens + (
  464. [last_seq_len] if last_seq_len else []
  465. )
  466. assert (
  467. seq_lens is not None and len(seq_lens) > 0
  468. ), "Cannot timeslice along `seq_lens` when `seq_lens` is empty or None!"
  469. # Generate n slices based on seq_lens.
  470. start = 0
  471. slices = []
  472. for seq_len in seq_lens:
  473. pre_begin = start - pre_overlap
  474. slice_begin = start
  475. end = start + seq_len
  476. slices.append((pre_begin, slice_begin, end))
  477. start += seq_len
  478. timeslices = []
  479. for begin, slice_begin, end in slices:
  480. zero_length = None
  481. data_begin = 0
  482. zero_init_states_ = zero_init_states
  483. if begin < 0:
  484. zero_length = pre_overlap
  485. data_begin = slice_begin
  486. zero_init_states_ = True
  487. else:
  488. eps_ids = sample_batch[SampleBatch.EPS_ID][begin if begin >= 0 else 0 : end]
  489. is_last_episode_ids = eps_ids == eps_ids[-1]
  490. if not is_last_episode_ids[0]:
  491. zero_length = int(sum(1.0 - is_last_episode_ids))
  492. data_begin = begin + zero_length
  493. zero_init_states_ = True
  494. if zero_length is not None:
  495. data = {
  496. k: np.concatenate(
  497. [
  498. np.zeros(shape=(zero_length,) + v.shape[1:], dtype=v.dtype),
  499. v[data_begin:end],
  500. ]
  501. )
  502. for k, v in sample_batch.items()
  503. if k != SampleBatch.SEQ_LENS
  504. }
  505. else:
  506. data = {
  507. k: v[begin:end]
  508. for k, v in sample_batch.items()
  509. if k != SampleBatch.SEQ_LENS
  510. }
  511. if zero_init_states_:
  512. i = 0
  513. key = "state_in_{}".format(i)
  514. while key in data:
  515. data[key] = np.zeros_like(sample_batch[key][0:1])
  516. # Del state_out_n from data if exists.
  517. data.pop("state_out_{}".format(i), None)
  518. i += 1
  519. key = "state_in_{}".format(i)
  520. # TODO: This will not work with attention nets as their state_outs are
  521. # not compatible with state_ins.
  522. else:
  523. i = 0
  524. key = "state_in_{}".format(i)
  525. while key in data:
  526. data[key] = sample_batch["state_out_{}".format(i)][begin - 1 : begin]
  527. del data["state_out_{}".format(i)]
  528. i += 1
  529. key = "state_in_{}".format(i)
  530. timeslices.append(SampleBatch(data, seq_lens=[end - begin]))
  531. # Zero-pad each slice if necessary.
  532. if zero_pad_max_seq_len > 0:
  533. for ts in timeslices:
  534. ts.right_zero_pad(max_seq_len=zero_pad_max_seq_len, exclude_states=True)
  535. return timeslices
  536. @OldAPIStack
  537. def get_fold_unfold_fns(b_dim: int, t_dim: int, framework: str):
  538. """Produces two functions to fold/unfold any Tensors in a struct.
  539. Args:
  540. b_dim: The batch dimension to use for folding.
  541. t_dim: The time dimension to use for folding.
  542. framework: The framework to use for folding. One of "tf2" or "torch".
  543. Returns:
  544. fold: A function that takes a struct of torch.Tensors and reshapes
  545. them to have a first dimension of `b_dim * t_dim`.
  546. unfold: A function that takes a struct of torch.Tensors and reshapes
  547. them to have a first dimension of `b_dim` and a second dimension
  548. of `t_dim`.
  549. """
  550. if framework in "tf2":
  551. # TensorFlow traced eager complains if we don't convert these to tensors here
  552. b_dim = tf.convert_to_tensor(b_dim)
  553. t_dim = tf.convert_to_tensor(t_dim)
  554. def fold_mapping(item):
  555. if item is None:
  556. # Torch has no representation for `None`, so we return None
  557. return item
  558. item = tf.convert_to_tensor(item)
  559. shape = tf.shape(item)
  560. other_dims = shape[2:]
  561. return tf.reshape(item, tf.concat([[b_dim * t_dim], other_dims], axis=0))
  562. def unfold_mapping(item):
  563. if item is None:
  564. return item
  565. item = tf.convert_to_tensor(item)
  566. shape = item.shape
  567. other_dims = shape[1:]
  568. return tf.reshape(item, tf.concat([[b_dim], [t_dim], other_dims], axis=0))
  569. elif framework == "torch":
  570. def fold_mapping(item):
  571. if item is None:
  572. # Torch has no representation for `None`, so we return None
  573. return item
  574. item = torch.as_tensor(item)
  575. size = list(item.size())
  576. current_b_dim, current_t_dim = list(size[:2])
  577. assert (b_dim, t_dim) == (current_b_dim, current_t_dim), (
  578. "All tensors in the struct must have the same batch and time "
  579. "dimensions. Got {} and {}.".format(
  580. (b_dim, t_dim), (current_b_dim, current_t_dim)
  581. )
  582. )
  583. other_dims = size[2:]
  584. return item.reshape([b_dim * t_dim] + other_dims)
  585. def unfold_mapping(item):
  586. if item is None:
  587. return item
  588. item = torch.as_tensor(item)
  589. size = list(item.size())
  590. current_b_dim = size[0]
  591. other_dims = size[1:]
  592. assert current_b_dim == b_dim * t_dim, (
  593. "The first dimension of the tensor must be equal to the product of "
  594. "the desired batch and time dimensions. Got {} and {}.".format(
  595. current_b_dim, b_dim * t_dim
  596. )
  597. )
  598. return item.reshape([b_dim, t_dim] + other_dims)
  599. else:
  600. raise ValueError(f"framework {framework} not implemented!")
  601. return functools.partial(tree.map_structure, fold_mapping), functools.partial(
  602. tree.map_structure, unfold_mapping
  603. )