class_docstr: Parent class for data loaders. The role of a Loader is to iterate over a
  Dataset and yield batches of graphs to feed your Keras Models.
  
  This is achieved by having a generator object that produces lists of Graphs,
  which are then collated together and returned as Tensors.
  
  The core of a Loader is the `collate(batch)` method.
  This takes as input a list of `Graph` objects and returns a list of Tensors,
  np.arrays, or SparseTensors.
  
  For instance, if all graphs have the same number of nodes and size of the
  attributes, a simple collation function can be:
  
  ```python
  def collate(self, batch):
      x = np.array([g.x for g in batch])
      a = np.array([g.a for g in batch)]
      return x, a
  ```
  
  The `load()` method of a Loader returns an object that can be passed to a Keras
  model when using the `fit`, `predict` and `evaluate` functions.
  You can use it as follows:
  
  ```python
  model.fit(loader.load(), steps_per_epoch=loader.steps_per_epoch)
  ```
  
  The `steps_per_epoch` property represents the number of batches that are in
  an epoch, and is a required keyword when calling `fit`, `predict` or `evaluate`
  with a Loader.
  
  If you are using a custom training function, you can specify the input signature
  of your batches with the tf.TypeSpec system to avoid unnecessary re-tracings.
  The signature is computed automatically by calling `loader.tf_signature()`.
  
  For example, a simple training step can be written as:
  
  ```python
  @tf.function(input_signature=loader.tf_signature())  # Specify signature here
  def train_step(inputs, target):
      with tf.GradientTape() as tape:
          predictions = model(inputs, training=True)
          loss = loss_fn(target, predictions)
      gradients = tape.gradient(loss, model.trainable_variables)
      optimizer.apply_gradients(zip(gradients, model.trainable_variables))
  ```
  
  We can then train our model in a loop as follows:
  
  ```python
  for batch in loader:
      train_step(*batch)
  ```
  
  **Arguments**
  
  - `dataset`: a `spektral.data.Dataset` object;
  - `batch_size`: int, size of the mini-batches;
  - `epochs`: int, number of epochs to iterate over the dataset. By default (`None`)
  iterates indefinitely;
  - `shuffle`: bool, whether to shuffle the dataset at the start of each epoch.
code: def __iter__(self):
      return self
  
imports: import numpy
  import tensorflow
  from spektral.data.utils import batch_generator, collate_labels_batch, collate_labels_disjoint, get_spec, prepend_none, sp_matrices_to_sp_tensors, to_batch, to_disjoint, to_mixed, to_tf_signature
label_desc: Use image node_id 2 for calling the Loader obj's underlying member method code with example usage: obj.__iter__() and returns: self