A PyTorch training loop is the code that turns mini-batches into model parameter updates. Keeping the loop explicit helps when a project needs custom data handling, loss calculations, logging, or optimizer timing instead of a higher-level training framework.
A minimal loop needs a model, a DataLoader, a loss function, and an optimizer. For each batch, the loop resets stale gradients, runs the forward pass, computes loss, calls backward(), and lets the optimizer update the parameters.
The smoke script uses a tiny regression dataset on CPU so the training signal is easy to inspect. Lower epoch loss, weight_changed=True, and a prediction tensor shape confirm that the loop executed the training pass and the trained model still returns outputs.
Related: How to use a DataLoader in PyTorch
Related: How to set the Adam optimizer in PyTorch
import torch from torch import nn from torch.utils.data import DataLoader, TensorDataset torch.manual_seed(23) features = torch.tensor( [ [-1.0, 0.0, 0.5], [-0.5, 0.25, 1.0], [0.0, -0.5, 0.25], [0.5, 0.75, -0.25], [1.0, -0.25, -0.5], [1.5, 0.5, 0.0], [2.0, -0.75, 0.75], [2.5, 1.0, -1.0], ], dtype=torch.float32, ) targets = features @ torch.tensor([[0.8], [-0.4], [0.3]]) + 0.2 train_data = TensorDataset(features, targets) train_loader = DataLoader(train_data, batch_size=4, shuffle=False) model = nn.Sequential(nn.Linear(3, 1)) loss_fn = nn.MSELoss() optimizer = torch.optim.SGD(model.parameters(), lr=0.08) first_weight = model[0].weight weight_before = first_weight.detach().clone() model.train() epoch_losses = [] for epoch in range(1, 7): loss_total = 0.0 sample_count = 0 for batch_features, batch_targets in train_loader: optimizer.zero_grad(set_to_none=True) predictions = model(batch_features) loss = loss_fn(predictions, batch_targets) loss.backward() optimizer.step() loss_total += loss.item() * batch_features.size(0) sample_count += batch_features.size(0) average_loss = loss_total / sample_count epoch_losses.append(average_loss) print(f"epoch={epoch} loss={average_loss:.6f}") with torch.inference_mode(): sample_prediction = model(features[:2]) weight_changed = not torch.equal(weight_before, first_weight.detach()) loss_decreased = epoch_losses[-1] < epoch_losses[0] print(f"weight_changed={weight_changed}") print(f"loss_decreased={loss_decreased}") print(f"prediction_shape={tuple(sample_prediction.shape)}")
shuffle=False keeps the smoke output repeatable. Use shuffle=True for ordinary training data when batch order should vary between epochs.
$ python training_loop_demo.py epoch=1 loss=1.753747 epoch=2 loss=0.223522 epoch=3 loss=0.080783 epoch=4 loss=0.053622 epoch=5 loss=0.040088 epoch=6 loss=0.030757 weight_changed=True loss_decreased=True prediction_shape=(2, 1)
loss_decreased=True means the final epoch loss is lower than the first epoch loss. weight_changed=True confirms that optimizer.step() updated the linear layer, and prediction_shape=(2, 1) confirms that the trained model still returns two one-value predictions.
def train_one_epoch(model, train_loader, loss_fn, optimizer, device): model.train() loss_total = 0.0 sample_count = 0 for batch_features, batch_targets in train_loader: batch_features = batch_features.to(device) batch_targets = batch_targets.to(device) optimizer.zero_grad(set_to_none=True) predictions = model(batch_features) loss = loss_fn(predictions, batch_targets) loss.backward() optimizer.step() loss_total += loss.item() * batch_features.size(0) sample_count += batch_features.size(0) return loss_total / sample_count
optimizer.zero_grad(set_to_none=True) clears stale gradients before each backward pass. Move every batch tensor to the same device as the model before the forward pass.
Related: How to zero gradients in PyTorch
Related: How to select a device in PyTorch
for epoch in range(1, epochs + 1): train_loss = train_one_epoch( model, train_loader, loss_fn, optimizer, device, ) print(f"epoch={epoch} train_loss={train_loss:.6f}")
Add evaluation, checkpointing, or scheduler calls outside the batch loop unless the project needs per-batch behavior.
Related: How to run evaluation in PyTorch
Related: How to save and restore a PyTorch training checkpoint
Related: How to set a learning rate scheduler in PyTorch
$ rm training_loop_demo.py