Custom Loggers
While we provide a number of logging functionalities out-of-the-box, there are many cases where you may wish to create your own logger. This short tutorial will walk you through the steps to achieve this.
General Structure¶
A Logger object has quite a simple structure: it requires only an __init__
method that accepts a SearchAlgorithm instance, and a _log
method to handle the status that is returned from the SearchAlgorithm:
from evotorch.logging import Logger
class MyLogger(Logger):
def __init__(self, searcher):
super().__init__(searcher)
...
# any additional desired initialisation
...
def _log(self, status: dict):
...
# do some work on the status dictionary
...
From only these two methods, the Logger instance will work for any SearchAlgorithm instance, simply by attaching it at instantiation,
A Simple Example¶
Let's suppose that we wish to use matplotlib
functionality to create a simple chart that tracks the status of our algorithm in real-time. To do this, we first import matplotlib
using the 'TkAgg'
back-end and in interactive mode.
When we initialise our custom logger, LivePlotter
, we will pass it a searcher
to attach to, and a named status variable to plot target_status
.
class LivePlotter(Logger):
def __init__(self, searcher, target_status: str):
# Call the super constructor
super().__init__(searcher)
# Set up the target status
self._target_status = target_status
...
Additionally, we will set up a matplotlib
Figure
instance that we can interact with as we receive data from the searcher
.
class LivePlotter(Logger):
def __init__(self, searcher, target_status: str):
...
# Create a figure and axis
self._fig = plt.figure(figsize=(10, 4), dpi=80)
self._ax = self._fig.add_subplot(111)
# Set the labels of the x and y axes
self._ax.set_xlabel("iter")
self._ax.set_ylabel(target_status)
# Create a line with (initially) no data in it
(self._line,) = self._ax.plot([], [])
# Update the TkAgg window name to something more interesting
self._fig.canvas.manager.window.title(f"LivePlotter: {target_status}")
...
We will also want to keep track of all iterations and status values seen so far by the logger
.
class LivePlotter(Logger):
def __init__(self, searcher, target_status: str):
...
self._iter_hist = []
self._status_hist = []
When _log
gets called from the searcher, we are passed the status
dictionary. We can use this to update the histories of iterations and status values.
class LivePlotter(Logger):
def _log(self, status: dict):
# Update the histories of the status
self._iter_hist.append(status["iter"])
self._status_hist.append(status[self._target_status])
...
Then we simply need to update the data in the figure self._fig
class LivePlotter(Logger):
def _log(self, status: dict):
...
# Update the x and y data
self._line.set_xdata(self._iter_hist)
self._line.set_ydata(self._status_hist)
# Rescale the limits of the x and y axis
self._ax.set_xlim(0.99, status["iter"])
self._ax.set_ylim(min(self._status_hist) * 0.99, max(self._status_hist) * 1.01)
# Draw the figure and flush its events
self._fig.canvas.draw()
self._fig.canvas.flush_events()
Putting all of this together we have
from evotorch.logging import Logger
import matplotlib
import matplotlib.pyplot as plt
import time
matplotlib.use("TkAgg")
plt.ion()
class LivePlotter(Logger):
def __init__(self, searcher, target_status: str):
# Call the super constructor
super().__init__(searcher)
# Set up the target status
self._target_status = target_status
# Create a figure and axis
self._fig = plt.figure(figsize=(10, 4), dpi=80)
self._ax = self._fig.add_subplot(111)
# Set the labels of the x and y axis
self._ax.set_xlabel("iter")
self._ax.set_ylabel(target_status)
# Create a line with (initially) no data in it
(self._line,) = self._ax.plot([], [])
# Update the TkAgg window name to something more interesting
self._fig.canvas.manager.window.title(f"LivePlotter: {target_status}")
self._iter_hist = []
self._status_hist = []
def _log(self, status: dict):
# Update the histories of the status
self._iter_hist.append(status["iter"])
self._status_hist.append(status[self._target_status])
# Update the x and y data
self._line.set_xdata(self._iter_hist)
self._line.set_ydata(self._status_hist)
# Rescale the limits of the x and y axis
self._ax.set_xlim(0.99, status["iter"])
self._ax.set_ylim(min(self._status_hist) * 0.99, max(self._status_hist) * 1.01)
# Draw the figure and flush its events
self._fig.canvas.draw()
self._fig.canvas.flush_events()
# Sleeping here will make the updates easier to watch
time.sleep(0.05)
If we now create a simple problem and associated searcher
,
from evotorch import Problem
from evotorch.algorithms import SNES
import torch
def sphere(x: torch.Tensor) -> torch.Tensor:
return torch.sum(x.pow(2.0))
problem = Problem(
"min",
sphere,
solution_length=10,
initial_bounds=(-1, 1),
)
searcher = SNES(problem, stdev_init=5)
attaching an instance of our custom logger to plot the 'mean_eval'
status and running the searcher,
should create a plot that is updated in real-time!