Record metrics at a specific step. E.g.

        my_history.log(34, loss=2.3, accuracy=0.2)

        Okay to call multiple times for the same step. New values overwrite
        older ones if they have the same metric name.

        step: An integer or tuple of integers. If a tuple, then the first
            value is considered to be the epoch and the second is the step
            within the epoch.

def log(self, step, **kwargs):
        """Record metrics at a specific step. E.g.

        my_history.log(34, loss=2.3, accuracy=0.2)

        Okay to call multiple times for the same step. New values overwrite
        older ones if they have the same metric name.

        step: An integer or tuple of integers. If a tuple, then the first
            value is considered to be the epoch and the second is the step
            within the epoch.
        """
        assert isinstance(step, (int, tuple)), "Step must be an int or a tuple of two ints"
        self.step = step
        # Any new metrics we haven't seen before?
        self.metrics |= set(kwargs.keys())
        # Insert (or update) record of the step
        if step not in self.history:
            self.history[step] = {}
        self.history[step].update({k:utils.to_data(v) for k, v in kwargs.items()})
        # Update step timestamp
        self.history[step]["__timestamp__"] = time.time()