K-Veritas
Tutorial · PyTorch Lightning

PyTorch Lightning: MNIST

The MNIST task written as a LightningModule. A Lightning callback forwards the training loss, and the test metric is printed after Trainer.test.

Install

The framework, plus the K-Veritas CLI (see the Overview for install).

pip install torch torchvision pytorch-lightning

train.py

The complete script. The KVERITAS_ lines are the only additions to an ordinary training script; everything else is standard PyTorch Lightning.

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
import pytorch_lightning as pl

SEED = 42
pl.seed_everything(SEED, verbose=False)
print(f"KVERITAS_INPUT src=seed:{SEED}", flush=True)

print("KVERITAS_PHASE name=data", flush=True)
tfm = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])
train_ds = datasets.MNIST("./data", train=True, download=True, transform=tfm)
test_ds = datasets.MNIST("./data", train=False, download=True, transform=tfm)
train_loader = DataLoader(train_ds, batch_size=128, shuffle=True)
test_loader = DataLoader(test_ds, batch_size=512)


class LitMLP(pl.LightningModule):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(nn.Flatten(), nn.Linear(784, 256), nn.ReLU(), nn.Linear(256, 10))

    def forward(self, x):
        return self.net(x)

    def training_step(self, batch, _):
        x, y = batch
        loss = F.cross_entropy(self(x), y)
        self.log("train_loss", loss, on_epoch=True, prog_bar=False)
        return loss

    def test_step(self, batch, _):
        x, y = batch
        acc = (self(x).argmax(1) == y).float().mean()
        self.log("test_accuracy", acc)

    def configure_optimizers(self):
        return torch.optim.Adam(self.parameters(), lr=1e-3)


model = LitMLP()
n_params = sum(p.numel() for p in model.parameters())
print(f"KVERITAS_MODEL params={n_params} arch=mlp precision=fp32", flush=True)
print(f"KVERITAS_WORKLOAD dataset_size={len(train_ds)} epochs=2 batch_size=128", flush=True)


class KVeritas(pl.Callback):
    def on_train_epoch_end(self, trainer, module):
        loss = trainer.callback_metrics.get("train_loss_epoch") or trainer.callback_metrics.get("loss")
        if loss is not None:
            print(f"KVERITAS_METRIC name=train_loss value={float(loss):.4f} step={trainer.current_epoch}", flush=True)


print("KVERITAS_PHASE name=train", flush=True)
trainer = pl.Trainer(max_epochs=2, accelerator="auto", devices=1, logger=False,
                     enable_checkpointing=False, enable_progress_bar=False, callbacks=[KVeritas()])
trainer.fit(model, train_loader)

print("KVERITAS_PHASE name=evaluate", flush=True)
result = trainer.test(model, test_loader, verbose=False)
acc = float(result[0]["test_accuracy"])
print(f"KVERITAS_METRIC name=test_accuracy value={acc:.4f}", flush=True)
print(f"KVERITAS_CLAIM metric=test_accuracy value={acc:.4f}", flush=True)
print(f"final test_accuracy={acc:.4f}", flush=True)

Run and seal

Wrap the script, seal a signed report, and verify it offline.

kveritas init
kveritas run -- python train.py
kveritas seal --output report.pdf
kveritas verify report.pdf

What it produced here

2 epochs reached about 97.3% test accuracy. The callback logged the training loss each epoch plus the final test accuracy.

All tutorials · The directives explained