|
| 1 | +========= |
| 2 | +Callbacks |
| 3 | +========= |
| 4 | + |
| 5 | +.. currentmodule:: pyqit.core.callbacks |
| 6 | + |
| 7 | +A pyqit callback implements up to three hooks, ``on_fit_start``, |
| 8 | +``on_epoch_end`` and ``on_fit_end``, each taking one :class:`LoopState`. Write it |
| 9 | +once and both backends honour it. |
| 10 | + |
| 11 | +.. code-block:: python |
| 12 | +
|
| 13 | + import pyqit |
| 14 | + from pyqit.core import EarlyStopping, ModelCheckpoint |
| 15 | +
|
| 16 | + trainer = pyqit.Trainer( |
| 17 | + max_epochs=100, |
| 18 | + loss_fn="cross_entropy", |
| 19 | + callbacks=[ |
| 20 | + EarlyStopping(monitor="val_loss", patience=3), |
| 21 | + ModelCheckpoint(dirpath="ckpts", save_best=True, save_last=True), |
| 22 | + ], |
| 23 | + ) |
| 24 | + history = trainer.fit(model, dm) |
| 25 | +
|
| 26 | +.. code-block:: text |
| 27 | +
|
| 28 | + [EarlyStopping] Stopped at epoch 18 - val_loss did not improve for 3 epoch(s) |
| 29 | + [Checkpoint] Restored best weights from epoch 15 (val_loss: 0.3721) |
| 30 | +
|
| 31 | +Why Lightning callbacks are rejected |
| 32 | +==================================== |
| 33 | + |
| 34 | +They are typed against Lightning's hooks, so the PennyLane loop could only |
| 35 | +ignore them. An ignored :class:`EarlyStopping` hands back a fully trained model |
| 36 | +without saying so, and that failure is invisible. Rejecting them at the door is |
| 37 | +the louder option. |
| 38 | + |
| 39 | +On the torch backend a shim reads Lightning's ``callback_metrics`` into the same |
| 40 | +metric names and forwards ``state.stop`` onto ``trainer.should_stop``, so the |
| 41 | +same callback object works on both sides. |
| 42 | + |
| 43 | +Checkpointing |
| 44 | +============= |
| 45 | + |
| 46 | +:class:`ModelCheckpoint` owns checkpointing on both backends, and Lightning's |
| 47 | +own is switched off so a run is never written twice. Only the file format |
| 48 | +differs, ``.ckpt`` holding a ``state_dict`` on torch and ``.npz`` on pennylane. |
| 49 | +The array keys match ``model.weights`` either way. |
| 50 | + |
| 51 | +Three files can be written independently. ``save_best`` uses the stem from |
| 52 | +``filename``, ``save_last`` uses ``last``, and ``every_n_epochs`` uses |
| 53 | +``epoch<n>``, numbered from zero to match ``best_epoch``. The best file is |
| 54 | +written once after training. Set ``save_on_improve=True`` to write on every |
| 55 | +improvement instead, at the cost of extra I/O. |
| 56 | + |
| 57 | +``restore_best`` defaults to whatever ``save_best`` is, not to ``True``, so |
| 58 | +``save_best=False, save_last=True`` will not quietly hand you back the best |
| 59 | +model when you asked for the last one. |
| 60 | + |
| 61 | +Nothing here resumes a run. These files hold weights only, with no optimizer |
| 62 | +state and no epoch counter. |
| 63 | + |
| 64 | +Writing your own |
| 65 | +================ |
| 66 | + |
| 67 | +.. code-block:: python |
| 68 | +
|
| 69 | + from pyqit.core import BaseCallback |
| 70 | +
|
| 71 | + class StopWhenConverged(BaseCallback): |
| 72 | + def on_epoch_end(self, state): |
| 73 | + if state.metrics["train_loss"] < 0.01: |
| 74 | + state.stop = True |
| 75 | +
|
| 76 | +``state`` carries the model, datamodule, history, reporter, epoch index and this |
| 77 | +epoch's metrics. ``state.stop`` is the one field a callback may write. |
| 78 | + |
| 79 | +Related |
| 80 | +======= |
| 81 | + |
| 82 | +:doc:`trainer` takes the ``callbacks`` list and assembles it. The |
| 83 | +:doc:`callbacks tutorial </tutorials/callbacks>` runs both built-in callbacks |
| 84 | +together and reloads the checkpoint afterwards. |
| 85 | + |
| 86 | +.. autosummary:: |
| 87 | + :nosignatures: |
| 88 | + |
| 89 | + BaseCallback |
| 90 | + LoopState |
| 91 | + HistoryCallback |
| 92 | + EarlyStopping |
| 93 | + ModelCheckpoint |
| 94 | + |
| 95 | +.. autoclass:: BaseCallback |
| 96 | +.. autoclass:: LoopState |
| 97 | +.. autoclass:: HistoryCallback |
| 98 | +.. autoclass:: EarlyStopping |
| 99 | +.. autoclass:: ModelCheckpoint |
0 commit comments