code: def on_train_batch_end(self, batch_number: int, logs: Dict):
      # Just in case we want to support second-order derivatives
      with torch.no_grad():
          layer_stats = {}
          for name, param in self.model.network.named_parameters():
              if self._keep_layer(param, name):
                  grad = param.grad
                  grad_abs_values = grad.abs()
  
                  stats = {}
                  stats["mean"] = grad_abs_values.mean().item()
                  stats["var"] = grad_abs_values.var().item()
                  stats["min"] = grad.min().item()
                  stats["abs_min"] = grad_abs_values.min().item()
                  stats["max"] = grad.max().item()
                  stats["abs_max"] = grad_abs_values.max().item()
                  for norm in self.norm_type:
                      stats[f"l{norm}"] = grad_abs_values.norm(
                          norm
                      ).item()
  
                  layer_stats[name] = stats
      self.log_stats(self.epoch, batch_number, logs, layer_stats)
  
imports: import csv
  import os
  from typing import Dict, List, Optional, TextIO, Union
  import torch
  from poutyne.framework.callbacks._utils import atomic_lambda_save
  from poutyne.framework.callbacks.callbacks import Callback
label_desc: Use image node_id 4 for calling the GradientLoggerBase obj's underlying member method code with example usage: obj.on_train_batch_end(batch_number, logs) without return types