code: def learn_from_batch(self, batch):
      # batch contains a list of episodes to learn from
      network_keys = self.ap.network_wrappers[
          "main"
      ].input_embedders_parameters.keys()
  
      total_returns = batch.n_step_discounted_rewards()
      for i in reversed(range(batch.size)):
          if (
              self.policy_gradient_rescaler
              == PolicyGradientRescaler.TOTAL_RETURN
          ):
              total_returns[i] = total_returns[0]
          elif (
              self.policy_gradient_rescaler
              == PolicyGradientRescaler.FUTURE_RETURN
          ):
              # just take the total return as it is
              pass
          elif (
              self.policy_gradient_rescaler
              == PolicyGradientRescaler.FUTURE_RETURN_NORMALIZED_BY_EPISODE
          ):
              # we can get a single transition episode while playing Doom Basic, causing the std to be 0
              if self.std_discounted_return != 0:
                  total_returns[i] = (
                      total_returns[i] - self.mean_discounted_return
                  ) / self.std_discounted_return
              else:
                  total_returns[i] = 0
          elif (
              self.policy_gradient_rescaler
              == PolicyGradientRescaler.FUTURE_RETURN_NORMALIZED_BY_TIMESTEP
          ):
              total_returns[
                  i
              ] -= self.mean_return_over_multiple_episodes[i]
          else:
              screen.warning(
                  "WARNING: The requested policy gradient rescaler is not available"
              )
  
      targets = total_returns
      actions = batch.actions()
      if (
          type(self.spaces.action) != DiscreteActionSpace
          and len(actions.shape) < 2
      ):
          actions = np.expand_dims(actions, -1)
  
      self.returns_mean.add_sample(np.mean(total_returns))
      self.returns_variance.add_sample(np.std(total_returns))
  
      result = self.networks[
          "main"
      ].online_network.accumulate_gradients(
          {**batch.states(network_keys), "output_0_0": actions}, targets
      )
      total_loss, losses, unclipped_grads = result[:3]
  
      return total_loss, losses, unclipped_grads
  
imports: from typing import Union
  import numpy
  from rl_coach.agents.policy_optimization_agent import PolicyOptimizationAgent, PolicyGradientRescaler
  from rl_coach.architectures.embedder_parameters import InputEmbedderParameters
  from rl_coach.architectures.head_parameters import PolicyHeadParameters
  from rl_coach.architectures.middleware_parameters import FCMiddlewareParameters
  from rl_coach.base_parameters import NetworkParameters, AlgorithmParameters, AgentParameters
  from rl_coach.exploration_policies.additive_noise import AdditiveNoiseParameters
  from rl_coach.exploration_policies.categorical import CategoricalParameters
  from rl_coach.logger import screen
  from rl_coach.memories.episodic.single_episode_buffer import SingleEpisodeBufferParameters
  from rl_coach.spaces import DiscreteActionSpace, BoxActionSpace
label_desc: Use image node_id 3 for calling the PolicyGradientsAgent obj's underlying member method code with example usage: obj.learn_from_batch(batch) and returns: total_loss,  losses,  unclipped_grads