Skip to content

Commit 128d212

Browse files
committed
feat (add support for 2D)
1 parent ce6b8ef commit 128d212

1 file changed

Lines changed: 106 additions & 36 deletions

File tree

deepxde/callbacks.py

Lines changed: 106 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -571,24 +571,32 @@ def on_train_end(self):
571571
class TrainingMonitor(Callback):
572572
"""Live-plot the predicted solution and the loss history during training.
573573
574-
Every `period` epochs, this callback redraws two subplots: the current
575-
network prediction along `x_plot` (optionally against a reference
576-
solution), and the train/test loss history on a log scale. Unlike
577-
``MovieDumper``, which only writes an animation to disk in
578-
``on_train_end``, or ``dde.saveplot``, which produces a single static
579-
plot after training finishes, this callback gives feedback while the
580-
model is still training.
574+
Every `period` epochs, this callback redraws the current network
575+
prediction over `x_plot` (optionally against a reference solution), and
576+
the train/test loss history on a log scale. Unlike ``MovieDumper``,
577+
which only writes an animation to disk in ``on_train_end``, or
578+
``dde.saveplot``, which produces a single static plot after training
579+
finishes, this callback gives feedback while the model is still
580+
training.
581+
582+
Both ODEs (`x_plot` of shape (N, 1), e.g. `y = f(t)`) and 2D
583+
space-time PDEs (`x_plot` of shape (N, 2), e.g. `y = f(x, t)` as in
584+
``diffusion_1d.py``) are supported. For the 2D case, the solution is
585+
shown as a scatter plot colored by `y`, with a matching panel for
586+
`y_reference` (if given) sharing the same color scale for an
587+
at-a-glance comparison.
581588
582589
Args:
583590
period (int): Interval (number of epochs) between plot updates.
584591
component (int): Which component of the solution to plot.
585-
x_plot: Points (array of shape (N, dim)) at which the solution is
586-
evaluated and plotted.
592+
x_plot: Points at which the solution is evaluated and plotted, of
593+
shape (N, 1) for `y = f(t)`-like problems, or (N, 2) for
594+
`y = f(x, t)`-like problems.
587595
y_reference: A function `y_reference(x_plot)` returning the
588596
reference (e.g., exact) solution for comparison. If ``None``,
589597
only the predicted solution is shown.
590598
show_loss (bool): If True, also plot the training/testing loss
591-
history in a second subplot.
599+
history in an additional subplot.
592600
593601
Warning:
594602
Live plotting requires an interactive Matplotlib backend. In
@@ -611,6 +619,16 @@ def __init__(
611619
self.period = period
612620
self.component = component
613621
self.x_plot = np.asarray(x_plot, dtype=config.real(np))
622+
if self.x_plot.ndim == 1:
623+
self.x_plot = self.x_plot[:, None]
624+
if self.x_plot.shape[1] not in (1, 2):
625+
raise ValueError(
626+
"TrainingMonitor only supports 1D (e.g. y = f(t)) or 2D "
627+
"(e.g. y = f(x, t)) `x_plot`, got {} columns.".format(
628+
self.x_plot.shape[1]
629+
)
630+
)
631+
self.dim = self.x_plot.shape[1]
614632
self.y_reference = y_reference
615633
self.show_loss = show_loss
616634

@@ -619,7 +637,10 @@ def __init__(
619637
self.plt = None
620638
self.fig = None
621639
self.ax_sol = None
640+
self.ax_ref = None
622641
self.ax_loss = None
642+
self.cbar_sol = None
643+
self.cbar_ref = None
623644

624645
def on_train_begin(self):
625646
self.epochs_since_last = 0
@@ -640,11 +661,20 @@ def on_train_begin(self):
640661

641662
self.plt = plt
642663
plt.ion()
643-
if self.show_loss:
644-
self.fig, (self.ax_sol, self.ax_loss) = plt.subplots(1, 2, figsize=(10, 4))
645-
else:
646-
self.fig, self.ax_sol = plt.subplots(figsize=(5, 4))
647-
self.ax_loss = None
664+
self.ax_ref = None
665+
self.cbar_sol = None
666+
self.cbar_ref = None
667+
n_sol_axes = 1 if (self.dim == 1 or self.y_reference is None) else 2
668+
n_axes = n_sol_axes + (1 if self.show_loss else 0)
669+
self.fig, axes = plt.subplots(1, n_axes, figsize=(5 * n_axes, 4))
670+
axes = np.atleast_1d(axes)
671+
i = 0
672+
self.ax_sol = axes[i]
673+
i += 1
674+
if n_sol_axes == 2:
675+
self.ax_ref = axes[i]
676+
i += 1
677+
self.ax_loss = axes[i] if self.show_loss else None
648678
self._redraw()
649679

650680
@staticmethod
@@ -673,36 +703,76 @@ def _update_plot(self):
673703
try:
674704
y_pred = self.model.predict(self.x_plot)[:, self.component]
675705

676-
x = np.ravel(self.x_plot)
677-
self.ax_sol.cla()
678-
self.ax_sol.plot(x, y_pred, "--r", label="Predicted")
679-
if self.y_reference is not None:
680-
y_ref = np.ravel(self.y_reference(self.x_plot))
681-
self.ax_sol.plot(x, y_ref, "-k", label="Reference")
682-
self.ax_sol.set_xlabel("x")
683-
self.ax_sol.set_ylabel("y")
684-
self.ax_sol.set_title(
685-
"Epoch {}".format(self.model.train_state.iteration)
686-
)
687-
self.ax_sol.legend()
706+
if self.dim == 1:
707+
self._plot_1d(y_pred)
708+
else:
709+
self._plot_2d(y_pred)
688710

689711
if self.show_loss:
690-
loss_history = self.model.losshistory
691-
loss_train = [np.sum(loss) for loss in loss_history.loss_train]
692-
loss_test = [np.sum(loss) for loss in loss_history.loss_test]
693-
self.ax_loss.cla()
694-
self.ax_loss.semilogy(loss_history.steps, loss_train, label="Train loss")
695-
self.ax_loss.semilogy(loss_history.steps, loss_test, label="Test loss")
696-
self.ax_loss.set_xlabel("# Steps")
697-
self.ax_loss.set_ylabel("Loss")
698-
self.ax_loss.legend()
712+
self._plot_loss()
699713

700714
self._redraw()
701715
except Exception as e: # pylint: disable=broad-except
702716
# Never let a plotting error interrupt training.
703717
self.enabled = False
704718
print("TrainingMonitor: disabling live plot due to error: {}".format(e))
705719

720+
def _plot_1d(self, y_pred):
721+
x = np.ravel(self.x_plot)
722+
self.ax_sol.cla()
723+
self.ax_sol.plot(x, y_pred, "--r", label="Predicted")
724+
if self.y_reference is not None:
725+
y_ref = np.ravel(self.y_reference(self.x_plot))
726+
self.ax_sol.plot(x, y_ref, "-k", label="Reference")
727+
self.ax_sol.set_xlabel("x")
728+
self.ax_sol.set_ylabel("y")
729+
self.ax_sol.set_title("Epoch {}".format(self.model.train_state.iteration))
730+
self.ax_sol.legend()
731+
732+
def _plot_2d(self, y_pred):
733+
x0, x1 = self.x_plot[:, 0], self.x_plot[:, 1]
734+
y_ref = None
735+
if self.y_reference is not None:
736+
y_ref = np.ravel(self.y_reference(self.x_plot))
737+
vmin = min(y_pred.min(), y_ref.min())
738+
vmax = max(y_pred.max(), y_ref.max())
739+
else:
740+
vmin, vmax = y_pred.min(), y_pred.max()
741+
742+
if self.cbar_sol is not None:
743+
self.cbar_sol.remove()
744+
self.ax_sol.cla()
745+
sc = self.ax_sol.scatter(x0, x1, c=y_pred, cmap="jet", vmin=vmin, vmax=vmax, s=10)
746+
self.ax_sol.set_xlabel("x")
747+
self.ax_sol.set_ylabel("t")
748+
self.ax_sol.set_title(
749+
"Predicted, epoch {}".format(self.model.train_state.iteration)
750+
)
751+
self.cbar_sol = self.fig.colorbar(sc, ax=self.ax_sol)
752+
753+
if self.ax_ref is not None:
754+
if self.cbar_ref is not None:
755+
self.cbar_ref.remove()
756+
self.ax_ref.cla()
757+
sc_ref = self.ax_ref.scatter(
758+
x0, x1, c=y_ref, cmap="jet", vmin=vmin, vmax=vmax, s=10
759+
)
760+
self.ax_ref.set_xlabel("x")
761+
self.ax_ref.set_ylabel("t")
762+
self.ax_ref.set_title("Reference")
763+
self.cbar_ref = self.fig.colorbar(sc_ref, ax=self.ax_ref)
764+
765+
def _plot_loss(self):
766+
loss_history = self.model.losshistory
767+
loss_train = [np.sum(loss) for loss in loss_history.loss_train]
768+
loss_test = [np.sum(loss) for loss in loss_history.loss_test]
769+
self.ax_loss.cla()
770+
self.ax_loss.semilogy(loss_history.steps, loss_train, label="Train loss")
771+
self.ax_loss.semilogy(loss_history.steps, loss_test, label="Test loss")
772+
self.ax_loss.set_xlabel("# Steps")
773+
self.ax_loss.set_ylabel("Loss")
774+
self.ax_loss.legend()
775+
706776
def _redraw(self):
707777
self.fig.tight_layout()
708778
self.fig.canvas.draw()

0 commit comments

Comments
 (0)