Training loops are easier to compare when loss and accuracy are written as event data instead of disappearing into terminal output. PyTorch sends scalar metrics to TensorBoard through SummaryWriter, so a run directory can show both the metric names and the values recorded at each training step.
SummaryWriter writes TensorBoard event files under its log_dir and flushes entries asynchronously while training continues. Giving each run its own directory, such as runs/tensorboard-demo or runs/experiment-name, keeps the Scalars dashboard organized and prevents unrelated runs from sharing one event file.
Close or flush the writer before inspecting a short smoke run so pending metric points reach disk. The same logging calls can then move into the real training loop, where global_step should come from the epoch, batch index, or optimizer step that the metric represents.
Related: How to run a training loop in PyTorch
Related: How to run the PyTorch profiler
$ python -m pip install tensorboard
Run this inside the same virtual environment or Conda environment that imports torch.
Related: How to install PyTorch with pip
from pathlib import Path import shutil import torch from torch import nn from torch.utils.tensorboard import SummaryWriter from tensorboard.backend.event_processing import event_accumulator torch.manual_seed(19) log_dir = Path("runs/tensorboard-demo") if log_dir.exists(): shutil.rmtree(log_dir) features = torch.tensor( [ [-1.0, 0.2, 0.1], [-0.5, 0.5, 0.4], [0.0, -0.3, 0.9], [0.5, 0.7, -0.4], [1.0, -0.6, -0.1], [1.5, 0.4, 0.2], ], dtype=torch.float32, ) targets = torch.tensor([0, 0, 1, 1, 1, 0]) model = nn.Sequential( nn.Linear(3, 6), nn.ReLU(), nn.Linear(6, 2), ) loss_fn = nn.CrossEntropyLoss() optimizer = torch.optim.SGD(model.parameters(), lr=0.15) writer = SummaryWriter(log_dir=log_dir) for epoch in range(5): optimizer.zero_grad(set_to_none=True) logits = model(features) loss = loss_fn(logits, targets) loss.backward() optimizer.step() with torch.no_grad(): predictions = model(features).argmax(dim=1) accuracy = (predictions == targets).float().mean().item() writer.add_scalar("Loss/train", loss.item(), epoch) writer.add_scalar("Accuracy/train", accuracy, epoch) print(f"epoch={epoch} loss={loss.item():.4f} accuracy={accuracy:.4f}") writer.flush() writer.close() events = sorted(log_dir.glob("events.out.tfevents.*")) reader = event_accumulator.EventAccumulator(str(log_dir)) reader.Reload() scalar_tags = sorted(reader.Tags()["scalars"]) loss_points = reader.Scalars("Loss/train") accuracy_points = reader.Scalars("Accuracy/train") print(f"torch_version={torch.__version__}") print(f"log_dir={log_dir}") print(f"event_files={len(events)}") for path in events: print(f"event_file={path.name}") print(f"scalar_tags={','.join(scalar_tags)}") print(f"loss_points={len(loss_points)}") print(f"accuracy_points={len(accuracy_points)}") print(f"last_loss={loss_points[-1].value:.4f}") print(f"last_accuracy={accuracy_points[-1].value:.4f}")
EventAccumulator is used only for the terminal readback. Training code only needs SummaryWriter and the add_scalar() calls.
$ python tensorboard_train_metrics.py epoch=0 loss=0.7052 accuracy=0.5000 epoch=1 loss=0.6980 accuracy=0.5000 epoch=2 loss=0.6919 accuracy=0.5000 epoch=3 loss=0.6870 accuracy=0.5000 epoch=4 loss=0.6823 accuracy=0.5000 torch_version=2.12.1+cpu log_dir=runs/tensorboard-demo event_files=1 event_file=events.out.tfevents.1783379917.training-host.2508.0 scalar_tags=Accuracy/train,Loss/train loss_points=5 accuracy_points=5 last_loss=0.6823 last_accuracy=0.5000
event_files=1 confirms that SummaryWriter created an event file. scalar_tags confirms that both metric names were written.
$ tensorboard --inspect --logdir runs/tensorboard-demo TensorFlow installation not found - running with reduced feature set. ====================================================================== Processing event files... (this can take a few minutes) ====================================================================== Found event files in: runs/tensorboard-demo These tags are in runs/tensorboard-demo: audio - histograms - images - scalars Accuracy/train Loss/train tensor - ====================================================================== Event statistics for runs/tensorboard-demo: audio - graph - histograms - images - scalars first_step 0 last_step 4 max_step 4 min_step 0 num_steps 5 outoforder_steps [] sessionlog:checkpoint - sessionlog:start - sessionlog:stop - tensor - ======================================================================
The reduced feature message is normal when TensorBoard is installed without TensorFlow. The scalars section and num_steps count are the event readback signals.
writer = SummaryWriter(log_dir=f"runs/{run_name}") for global_step, (inputs, targets) in enumerate(train_loader): loss, accuracy = train_one_batch(model, inputs, targets) writer.add_scalar("Loss/train", loss, global_step) writer.add_scalar("Accuracy/train", accuracy, global_step) writer.close()
Log Python floats or scalar tensors after each value is computed. Keep tag names stable across runs so TensorBoard can overlay comparable curves.
$ tensorboard --logdir runs
Open the local URL printed by TensorBoard and choose the tensorboard-demo run in the Scalars dashboard. Point --logdir at the parent directory when several run subdirectories should be compared.
$ rm -r tensorboard_train_metrics.py runs/tensorboard-demo