class_docstr: Write me
      
code: def backward(self, reward, terminal):
      # Store most recent experience in memory.
      if self.step % self.memory_interval == 0:
          self.memory.append(
              self.recent_observation,
              self.recent_action,
              reward,
              terminal,
              training=self.training,
          )
  
      metrics = [np.nan for _ in self.metrics_names]
      if not self.training:
          # We're done here. No need to update the experience memory since we only use the working
          # memory to obtain the state over the most recent observations.
          return metrics
  
      # Train the network on a single stochastic batch.
      if (
          self.step > self.nb_steps_warmup
          and self.step % self.train_interval == 0
      ):
          experiences = self.memory.sample(self.batch_size)
          assert len(experiences) == self.batch_size
  
          # Start by extracting the necessary parameters (we use a vectorized implementation).
          state0_batch = []
          reward_batch = []
          action_batch = []
          terminal1_batch = []
          state1_batch = []
          for e in experiences:
              state0_batch.append(e.state0)
              state1_batch.append(e.state1)
              reward_batch.append(e.reward)
              action_batch.append(e.action)
              terminal1_batch.append(0.0 if e.terminal1 else 1.0)
  
          # Prepare and validate parameters.
          state0_batch = self.process_state_batch(state0_batch)
          state1_batch = self.process_state_batch(state1_batch)
          terminal1_batch = np.array(terminal1_batch)
          reward_batch = np.array(reward_batch)
          action_batch = np.array(action_batch)
          assert reward_batch.shape == (self.batch_size,)
          assert terminal1_batch.shape == reward_batch.shape
          assert action_batch.shape == (
              self.batch_size,
              self.nb_actions,
          )
  
          # Compute Q values for mini-batch update.
          q_batch = self.target_V_model.predict_on_batch(
              state1_batch
          ).flatten()
          assert q_batch.shape == (self.batch_size,)
  
          # Compute discounted reward.
          discounted_reward_batch = self.gamma * q_batch
          # Set discounted reward to zero for all states that were terminal.
          discounted_reward_batch *= terminal1_batch
          assert discounted_reward_batch.shape == reward_batch.shape
          Rs = reward_batch + discounted_reward_batch
          assert Rs.shape == (self.batch_size,)
  
          # Finally, perform a single update on the entire batch.
          if len(self.combined_model.input) == 2:
              metrics = self.combined_model.train_on_batch(
                  [action_batch, state0_batch], Rs
              )
          else:
              metrics = self.combined_model.train_on_batch(
                  [action_batch] + state0_batch, Rs
              )
          if self.processor is not None:
              metrics += self.processor.metrics
  
      if (
          self.target_model_update >= 1
          and self.step % self.target_model_update == 0
      ):
          self.update_target_model_hard()
  
      return metrics
  
imports: from __future__ import division
  import warnings
  import keras.backend
  from keras.models import Model
  from keras.layers import Lambda, Input, Layer, Dense
  from rl.core import Agent
  from rl.policy import EpsGreedyQPolicy, GreedyQPolicy
  from rl.util import *
label_desc: Use image node_id 9 for calling the NAFAgent obj's underlying member method code with example usage: obj.backward(reward, terminal) and returns: metrics, metrics