class_docstr: Callback that streams epoch results to a CSV file.
  
  Supports all values that can be represented as a string,
  including 1D iterables such as `np.ndarray`.
  
  Args:
      filename: Filename of the CSV file, e.g. `'run/log.csv'`.
      separator: String used to separate elements in the CSV file.
      append: Boolean. True: append if file exists (useful for continuing
          training). False: overwrite existing file.
  
  Example:
  
  ```python
  csv_logger = CSVLogger('training.log')
  model.fit(X_train, Y_train, callbacks=[csv_logger])
  ```
class_docstr_tok: ["Callback","that","streams","epoch","results","to","a","CSV","file",".","Supports","all","values","that","can","be","represented","as","a","string",",","including","1D","iterables","such","as","`","np.ndarray","`",".","Args",":","filename",":","Filename","of","the","CSV","file",",","e.g",".","`","'run\/log.csv","'","`",".","separator",":","String","used","to","separate","elements","in","the","CSV","file",".","append",":","Boolean",".","True",":","append","if","file","exists","(","useful","for","continuing","training",")",".","False",":","overwrite","existing","file",".","Example",":","``","`","python","csv_logger","=","CSVLogger","(","'training.log","'",")","model.fit","(","X_train",",","Y_train",",","callbacks=","[","csv_logger","]",")","``","`"]
code: def on_epoch_end(self, epoch, logs=None):
      logs = logs or {}
  
      def handle_value(k):
          is_zero_dim_ndarray = (
              isinstance(k, np.ndarray) and k.ndim == 0
          )
          if isinstance(k, str):
              return k
          elif (
              isinstance(k, collections.abc.Iterable)
              and not is_zero_dim_ndarray
          ):
              return f"\"[{', '.join(map(str, k))}]\""
          else:
              return k
  
      if self.keys is None:
          self.keys = sorted(logs.keys())
          # When validation_freq > 1, `val_` keys are not in first epoch logs
          # Add the `val_` keys so that its part of the fieldnames of writer.
          val_keys_found = False
          for key in self.keys:
              if key.startswith("val_"):
                  val_keys_found = True
                  break
          if not val_keys_found:
              self.keys.extend(["val_" + k for k in self.keys])
  
      if not self.writer:
  
          class CustomDialect(csv.excel):
              delimiter = self.sep
  
          fieldnames = ["epoch"] + self.keys
  
          self.writer = csv.DictWriter(
              self.csv_file,
              fieldnames=fieldnames,
              dialect=CustomDialect,
          )
          if self.append_header:
              self.writer.writeheader()
  
      row_dict = collections.OrderedDict({"epoch": epoch})
      row_dict.update(
          (key, handle_value(logs.get(key, "NA"))) for key in self.keys
      )
      self.writer.writerow(row_dict)
      self.csv_file.flush()
  
file_path: keras/keras/callbacks/csv_logger.py
imports: import collections
  import csv
  import numpy
  from keras.api_export import keras_export
  from keras.callbacks.callback import Callback
  from keras.utils import file_utils
label_desc: Use image node_id 3 for calling the CSVLogger obj's underlying member method code with example usage: obj.on_epoch_end(epoch, logs) and returns: k, str, str, str, k