Lightweight fixed-width metric table printer for ML training loops
Project description
epoch-printer
epoch-printer is a lightweight console table utility for ML training loops. It makes live metrics readable at a glance — without adopting a full experiment tracking platform or rewriting print formatting for every experiment.
Ep LR TrainLoss ValLoss T_MAE V_MAE T_RMSE V_RMSE V_Corr V_ExpVar Time
-----------------------------------------------------------------------------------------------------------------
1 1.0000e-03 1.8421e-01 2.1077e-01 1.920e-02 2.150e-02 4.510e-02 4.910e-02 0.781 0.612 12.47
2 7.5000e-04 1.2376e-01 1.5542e-01 1.480e-02 1.740e-02 3.730e-02 4.110e-02 0.829 0.691 11.93
3 5.0000e-04 9.1040e-02 1.2010e-01 1.210e-02 1.480e-02 3.140e-02 3.720e-02 0.863 0.744 11.64
Why use this?
Training loop output usually falls into one of three patterns:
print(f"epoch {epoch}, loss {loss:.4f}")— fine for 2 metrics, messy for 10.- Full experiment trackers (W&B, MLflow, TensorBoard) — great for dashboards, but setting one up for a quick experiment adds overhead and context-switching.
- Custom formatting code — rewritten per project, fragile when the metric set changes.
epoch-printer gives you a fixed-width table that is easy to scan while training is running, defined once per project, and portable across scripts, notebooks, and IDE consoles. It handles missing values, nan/inf, and column alignment automatically.
This is not a replacement for experiment trackers. Use it alongside W&B or MLflow for readable live stdout while your dashboard updates in the background.
Installation
pip install epoch-printer
With optional pandas support for DataFrame summaries:
pip install "epoch-printer[pandas]"
30-second example
Paste into an existing training loop:
import time
from epoch_printer import EpochPrinter, make_epoch_columns
printer = EpochPrinter(make_epoch_columns())
printer.print_header()
for epoch in range(1, num_epochs + 1):
t0 = time.time()
train_metrics = train_one_epoch(model, loader, optimizer)
val_metrics = evaluate(model, val_loader)
scheduler.step()
printer.print_row({
"epoch": epoch,
"lr": scheduler.get_last_lr()[0],
"train_loss": train_metrics["loss"],
"val_loss": val_metrics["loss"],
"val_mae": val_metrics["mae"],
"time": time.time() - t0,
})
make_epoch_columns() provides a default schema (epoch, LR, loss, MAE, RMSE, correlation, explained variance, time). Keys absent from the row dict, or keys whose value is explicitly None, both render as --. There is no distinction between a missing key and an explicit None.
Common workflows
PyTorch / JAX-style training loop
See examples/pytorch_training_loop.py for a self-contained example shaped like a real PyTorch training run (no PyTorch required — model and loader are mocked).
To append project-specific columns alongside the defaults:
from epoch_printer import EpochPrinter, MetricCol, make_epoch_columns
printer = EpochPrinter(
make_epoch_columns([
MetricCol("val_acc", "V_Acc", width=8, fmt=".2%"),
MetricCol("grad_norm", "GradNorm", width=9, fmt=".3f"),
])
)
printer.print_header()
Custom research metrics
When the default schema does not match your experiment, build your own column list:
from epoch_printer import EpochTablePrinter, MetricCol, ensure_time_last
columns = ensure_time_last([
MetricCol("epoch", "Ep", width=4, fmt="d"),
MetricCol("lr", "LR", width=10, fmt=".2e"),
MetricCol("recon_loss", "Recon", width=12, fmt=".4e"),
MetricCol("kl_div", "KL", width=10, fmt=".4e"),
MetricCol("grad_norm", "GradNorm", width=9, fmt=".3f"),
MetricCol("throughput", "Samp/s", width=8, fmt=".0f"),
])
printer = EpochTablePrinter(columns)
printer.print_header()
Apply a transform to convert raw values before display (e.g. MiB → GiB):
MetricCol("gpu_mem_mb", "GPU_GiB", width=8, fmt=".2f", transform=lambda x: x / 1024)
Transforms are applied at render time during print_row(). If a transform raises an exception, it propagates — handle errors inside the transform if needed. Transforms are not called when the column value is None or missing (those render as -- instead).
See examples/custom_research_metrics.py for more patterns including VAE training and table formatting options.
Notebook summaries
The live table gives you per-epoch feedback while training is running. At the end of the cell, convert collected rows to a styled DataFrame for easy post-run review:
from epoch_printer import (
EpochPrinter, make_epoch_columns,
results_to_dataframe, style_results_dataframe,
)
results = []
printer = EpochPrinter(make_epoch_columns())
printer.print_header()
for epoch in range(1, num_epochs + 1):
row = {"epoch": epoch, "lr": ..., "train_loss": ..., "val_loss": ..., "time": ...}
printer.print_row(row) # live output during training
results.append(row)
# At end of notebook cell — renders as styled HTML table in Jupyter:
style_results_dataframe(results_to_dataframe(results))
See examples/notebook_summary.py for a runnable version.
Alongside W&B / TensorBoard / MLflow
epoch-printer only writes to stdout. Use the same underlying training metrics for both console output and experiment tracking — tracker calls may need light filtering for fields like "epoch" (which maps to step in most trackers):
import wandb
import mlflow
from epoch_printer import EpochPrinter, make_epoch_columns
wandb.init(project="my-experiment")
printer = EpochPrinter(make_epoch_columns())
printer.print_header()
for epoch in range(1, num_epochs + 1):
metrics = {
"epoch": epoch,
"lr": scheduler.get_last_lr()[0],
"train_loss": train_metrics["loss"],
"val_loss": val_metrics["loss"],
"time": time.time() - t0,
}
printer.print_row(metrics) # live stdout
# Pass the same underlying data to your tracker, filtered as needed:
wandb.log(metrics, step=epoch)
mlflow.log_metrics(
{k: v for k, v in metrics.items() if k != "epoch"},
step=epoch,
)
See examples/logger_integration.py for a version that also mocks TensorBoard and MLflow log calls.
API overview
MetricCol
Frozen dataclass defining one column.
| Field | Default | Description |
|---|---|---|
key |
— | Key expected in each row dict |
title |
None |
Header label; defaults to key if omitted |
width |
None |
Fixed display width; defaults to len(title) if omitted |
fmt |
".4f" |
Python format specifier (e.g. "d", ".2e", ".2%", "s") |
align |
">" |
Alignment: ">" (right), "<" (left), or "^" (center) |
transform |
None |
Optional callable applied to the raw value before formatting. Not called when the value is None. Exceptions propagate to the caller. |
EpochTablePrinter / EpochPrinter
EpochPrinter is an alias for EpochTablePrinter.
printer = EpochTablePrinter(columns, sep=" ", missing="--")
printer.print_header() # print header + rule to stdout
printer.print_row(row) # print one formatted row
# Non-printing string forms:
printer.format_header() # returns header string
printer.format_row(row) # returns formatted row string
| Parameter | Default | Description |
|---|---|---|
columns |
— | Ordered sequence of MetricCol definitions |
sep |
" " |
Separator between columns |
missing |
"--" |
Placeholder rendered for absent keys or explicit None values; no distinction is made between the two |
header_rule |
True |
Dashed rule beneath the header |
vertical_lines |
False |
`" |
row_lines |
False |
Dashed rule after every data row |
Column helpers
| Function | Description |
|---|---|
make_time_column() |
Returns the default "time" column (width 8, .2f) |
ensure_time_last(cols) |
Moves or appends the time column to the end |
build_default_epoch_columns() |
Full default schema: epoch, LR, loss, MAE, RMSE, corr, explained var, time |
make_epoch_columns(extra_cols) |
Default schema plus your extra columns, time last |
DataFrame helpers (requires pandas)
| Function | Description |
|---|---|
results_to_dataframe(rows) |
Converts a list of row dicts to a DataFrame |
style_results_dataframe(df) |
Returns a pandas Styler with standard metric formatting |
Install with pip install "epoch-printer[pandas]".
Design choices
- No required dependencies. Core functionality uses only the standard library.
- Explicit schema. Every displayed metric is declared in a column definition; nothing is inferred silently.
- Stable column order. Positions never shift from row to row, making it easy to scan vertically during a long run.
- Row dicts, not keyword arguments. Use the same underlying training metrics for both console output and experiment tracking, while keeping display formatting separate.
- Portable. Plain-text output works in terminals, notebooks, and IDE consoles without configuration.
- Small. A focused utility, not a platform.
When not to use this
- You need persistent experiment storage, run comparison, or metric visualization → use W&B, MLflow, or TensorBoard.
- You have hundreds of metrics per row that need filtering, aggregation, or search → use a proper logging framework.
- You need structured output (JSON, CSV) for downstream processing → write to a file directly.
- Your runs take under a minute with two or three metrics →
print()is fine.
License
MIT · v0.1.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file epoch_printer-0.2.0.tar.gz.
File metadata
- Download URL: epoch_printer-0.2.0.tar.gz
- Upload date:
- Size: 15.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a766cccb3e0a1939ba728662d17667ab05ce3f565b4433f605eefe6d6436c6b8
|
|
| MD5 |
68890a7b8fdf1f98afdbf07a541ffa9d
|
|
| BLAKE2b-256 |
842bc3432d24af6ec078fc03418534d6c8880a2cdadd6eb6e487321a5c4f04f0
|
File details
Details for the file epoch_printer-0.2.0-py3-none-any.whl.
File metadata
- Download URL: epoch_printer-0.2.0-py3-none-any.whl
- Upload date:
- Size: 10.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
97c44593791538f32b0f21578d5acbdb4bcc5d7e86f2c0713302cf22a80cb186
|
|
| MD5 |
caae46adc2bceb103d03aec67eafe97c
|
|
| BLAKE2b-256 |
f1ba318aa6ea34324f47298208df2bfaff0f4b624eb511dab23437a462e03e6a
|