Skip to content

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,

my_logger = MyLogger(searcher)
searcher.step()

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.

import matplotlib
import matplotlib.pyplot as plt
matplotlib.use('TkAgg')
plt.ion()

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.

        # 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}')

        ...

We will also want to keep track of all iterations and status values seen so far by the logger.

        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.

    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

        # 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)

Putting all of this together we have

from evotorch.logging import Logger
import matplotlib
import matplotlib.pyplot as plt
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.))

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,

mean_eval_logger = LivePlotter(searcher, 'mean_eval')
searcher.run(200)

should create a plot that is updated in real-time!

animated