Training pipeline with callbacks#
VQCClassifiertrained withEarlyStoppingandModelCheckpoint.Both run on both backends, unlike a Lightning callback.
Inspect what each callback recorded, resume the run from its last checkpoint, then reload the fitted preprocessing for new rows.
Pennylane backend throughout.
1. Data#
make_moons, 200 samples.normalize="minmax", fit on train only.AngleEmbeddingprescaling maps it to [0, pi] insetup().
[1]:
from sklearn.datasets import make_moons
import pyqit
from pyqit import DataModule, Trainer
from pyqit.ansatzes import SELAnsatz
from pyqit.core import AngleEmbedding, EarlyStopping, ModelCheckpoint
from pyqit.models import VQCClassifier
pyqit.set_seed(42)
X, y = make_moons(n_samples=200, noise=0.1, random_state=0)
dm = DataModule(X, y, normalize="minmax", batch_size=16, seed=42)
2. Trainer with both callbacks#
EarlyStoppingonval_loss, patience 5. Stops once it stops improving.ModelCheckpointsaves the best and last epoch, restores best weights after training.Kept as named variables, not inlined, to read their state after
fit.loss_fn="cross_entropy".
[2]:
model = VQCClassifier(n_qubits=4, n_layers=3, ansatz=SELAnsatz, encoder=AngleEmbedding)
early_stop = EarlyStopping(monitor="val_loss", patience=5)
checkpoint = ModelCheckpoint(dirpath="ckpts", save_best=True, save_last=True)
trainer = Trainer(
max_epochs=60,
loss_fn="cross_entropy",
callbacks=[early_stop, checkpoint],
verbose=1,
)
history = trainer.fit(model, dm)
[Trainer] Starting pennylane backend | 60 epochs | lr=0.01
[EarlyStopping] Stopped at epoch 39 -- val_loss did not improve for 5 epoch(s) (best val_loss: 0.3256)
[Checkpoint] Last epoch -> ckpts/last.npz
[Checkpoint] Restored best weights from epoch 34 (val_loss: 0.3256)
[Trainer] Training complete.
3. What each callback recorded#
early_stop.stopped_epochandearly_stop.stopping_reason, set once training stops early.checkpoint.best_epoch,checkpoint.best_path,checkpoint.last_path, the epoch and filesModelCheckpointwrote.history.best_epoch,history.best_score,history.best_metric, the same best epoch from the run’s own record.
[3]:
print("stopped at:", early_stop.stopped_epoch, "|", early_stop.stopping_reason)
print("best checkpoint:", checkpoint.best_epoch, "->", checkpoint.best_path)
print("last checkpoint:", checkpoint.last_path)
print("history best:", history.best_epoch, history.best_score, history.best_metric)
stopped at: 39 | val_loss did not improve for 5 epoch(s) (best val_loss: 0.3256)
best checkpoint: 34 -> ckpts/best.npz
last checkpoint: ckpts/last.npz
history best: 34 0.32560367617711666 val_loss
4. Loss curve#
[4]:
import matplotlib.pyplot as plt
plt.plot(history.train_loss, label="train_loss")
plt.plot(history.val_loss, label="val_loss")
plt.xlabel("epoch")
plt.ylabel("loss")
plt.legend()
plt.show()
5. Resuming from a checkpoint#
A checkpoint holds the weights, the optimizer state and the history to that epoch,
.npzon pennylane and.ckpton torch.ModelCheckpoint(resume_from=...)loads all three before the first epoch, so a fresh model continues wherelast.npzstopped.max_epochscounts from zero: the resumed run trains the epochs the file does not already hold.
[5]:
n_done = len(history.train_loss)
fresh_model = VQCClassifier(
n_qubits=4, n_layers=3, ansatz=SELAnsatz, encoder=AngleEmbedding
)
resume = ModelCheckpoint(
dirpath="ckpts", save_best=False, resume_from=checkpoint.last_path
)
resumed = Trainer(
max_epochs=n_done + 5, loss_fn="cross_entropy", callbacks=[resume], verbose=1
).fit(fresh_model, dm)
assert resumed.train_loss[:n_done] == history.train_loss
print(f"{n_done} epochs from the file, {len(resumed.train_loss) - n_done} trained now")
[Trainer] Starting pennylane backend | 45 epochs | lr=0.01
[Checkpoint] Resumed from ckpts/last.npz at epoch 40
[Trainer] Training complete.
40 epochs from the file, 5 trained now
6. Saving the DataModule#
The checkpoint holds the model. The fitted normalizer is the DataModule’s, saved on request with
dm.save.DataModule.load(path, X_new)attaches new rows to the saved settings, likedm.for_prediction, so predicting needs neither the training data nor a refit.
[6]:
X_new, _ = make_moons(n_samples=5, noise=0.1, random_state=1)
dm.save("ckpts/datamodule.pkl")
dm_new = DataModule.load("ckpts/datamodule.pkl", X_new)
print(trainer.predict(fresh_model, dm_new))
[0 0 1 1 1]