Keras
machine learning
training history
deep learning
Python

How to save training history on every epoch in Keras?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Keras gives you a History object after training finishes, but that object lives in memory and only becomes fully available at the end of fit(). If you want metrics persisted after each epoch, the normal solution is a callback that writes the logs as training progresses. This matters when training is long-running, runs remotely, or might be interrupted before the final epoch completes.

Know What History Does by Default

A standard Keras training call returns a History object.

python
history = model.fit(x_train, y_train, epochs=5, validation_data=(x_val, y_val))
print(history.history.keys())

That is useful for plotting after training, but it does not by itself save the epoch logs anywhere durable. If the process crashes mid-run, the in-memory object is gone.

So if the real requirement is “persist metrics every epoch,” you need a callback.

Write a Custom Callback

A custom callback lets you capture the logs dictionary at the end of each epoch and append it to a file.

python
1import csv
2import tensorflow as tf
3
4class EpochHistoryLogger(tf.keras.callbacks.Callback):
5    def __init__(self, path):
6        super().__init__()
7        self.path = path
8        self.fieldnames = None
9
10    def on_epoch_end(self, epoch, logs=None):
11        logs = logs or {}
12        row = {"epoch": epoch + 1, **logs}
13
14        if self.fieldnames is None:
15            self.fieldnames = list(row.keys())
16
17        write_header = epoch == 0
18        with open(self.path, "a", newline="") as f:
19            writer = csv.DictWriter(f, fieldnames=self.fieldnames)
20            if write_header:
21                writer.writeheader()
22            writer.writerow(row)
23
24logger = EpochHistoryLogger("training_history.csv")
25model.fit(x_train, y_train, epochs=5, callbacks=[logger])

This gives you a persistent CSV file that grows one row per epoch. It is simple, readable, and easy to inspect with pandas or Excel later.

Use the Built-In CSVLogger When It Fits

Keras already ships a built-in callback for this common case: CSVLogger.

python
1import tensorflow as tf
2
3csv_logger = tf.keras.callbacks.CSVLogger("training.csv", append=False)
4
5model.fit(
6    x_train,
7    y_train,
8    epochs=5,
9    validation_data=(x_val, y_val),
10    callbacks=[csv_logger],
11)

If CSV is enough, this is often the best choice because it is built in and avoids writing custom callback code.

The custom callback approach becomes more useful when you need JSON, database writes, extra metadata, or integration with a larger experiment-tracking system.

Save More Than Just Loss

The logs dictionary usually contains all metrics Keras knows about for that epoch, including validation metrics if validation is enabled.

For example, if the model was compiled with accuracy, the callback may receive keys such as:

  • 'loss'
  • 'accuracy'
  • 'val_loss'
  • 'val_accuracy'

That means your callback should be written flexibly enough to handle whichever metrics are present, rather than hardcoding only loss.

JSON Lines Is Another Good Format

If you want a more structured log format than CSV, JSON lines can work well.

python
1import json
2import tensorflow as tf
3
4class JsonEpochLogger(tf.keras.callbacks.Callback):
5    def __init__(self, path):
6        super().__init__()
7        self.path = path
8
9    def on_epoch_end(self, epoch, logs=None):
10        logs = logs or {}
11        row = {"epoch": epoch + 1, **logs}
12        with open(self.path, "a", encoding="utf-8") as f:
13            f.write(json.dumps(row) + "\n")

This is convenient when the history needs to be ingested by another Python service or experiment dashboard later.

Think About Resume and Append Behavior

If you resume training, decide whether you want to overwrite old history or append new epochs to the same file. The answer depends on whether the log represents one uninterrupted run or a training process that may continue across restarts.

That is why callbacks such as CSVLogger expose append behavior. A durable history format is only useful if its lifecycle matches how you actually train models.

Common Pitfalls

  • Assuming the returned History object saves itself to disk automatically.
  • Waiting until the end of training to persist metrics when the job might terminate early.
  • Writing a custom callback that only records loss and silently drops other important metrics.
  • Forgetting to define whether resumed training should append to existing history or start a new file.
  • Storing only epoch metrics when the real requirement is richer experiment tracking with model version, dataset, or hyperparameter metadata.

Summary

  • The default Keras History object is in-memory and is only complete after fit() returns.
  • To save metrics every epoch, use a callback.
  • 'CSVLogger is the simplest built-in solution for persistent epoch logs.'
  • A custom callback is useful when you need JSON, metadata, or a different storage target.
  • Decide early whether your history file should overwrite or append when training resumes.

Course illustration
Course illustration

All Rights Reserved.