Source code for latents.tracking

"""Base classes for fit tracking infrastructure."""

from __future__ import annotations

from dataclasses import dataclass

import matplotlib.pyplot as plt
import numpy as np

from latents.base import ArrayContainer


[docs] class FitTracker(ArrayContainer): """Base class for quantities tracked during a model fit. Parameters ---------- lb : ndarray of float, shape (num_iter,) or None, default None Variational lower bound at each iteration. iter_time : ndarray of float, shape (num_iter,) or None, default None Runtime on each iteration. lb_base : float or None, default None Baseline lower bound for convergence checking. Set during initial iterations of a fresh fit and preserved during resume. Attributes ---------- lb : ndarray of float, shape (num_iter,) or None Variational lower bound at each iteration. iter_time : ndarray of float, shape (num_iter,) or None Runtime on each iteration. lb_base : float or None Baseline lower bound for convergence checking. Examples -------- Access via a fitted model: >>> model.fit(Y) >>> model.tracker.plot_lb() # Plot convergence >>> model.tracker.plot_runtime() # Plot cumulative runtime """ def __init__( self, lb: np.ndarray | None = None, iter_time: np.ndarray | None = None, lb_base: float | None = None, ): self.lb = lb self.iter_time = iter_time self.lb_base = lb_base
[docs] def plot_lb(self) -> None: """Plot the variational lower bound each iteration.""" if self.lb is not None: # create figure fig, ax_lb = plt.subplots(nrows=1, ncols=1, figsize=(3.5, 3)) # Plot the lower bound. It should be monotonically increasing. ax_lb.plot(self.lb, color="black", linestyle="solid", linewidth=1.0) ax_lb.set_xlabel("Iteration") ax_lb.set_ylabel("Lower bound") ax_lb.spines["top"].set_visible(False) ax_lb.spines["right"].set_visible(False) fig.tight_layout() plt.show() else: print("No lower bound to plot.")
[docs] def plot_runtime(self) -> None: """Plot the runtime at each iteration.""" if self.iter_time is not None: # create figure fig, ax_rt = plt.subplots(nrows=1, ncols=1, figsize=(3.5, 3)) # Plot cumulative runtime. ax_rt.plot( np.cumsum(self.iter_time), color="black", linestyle="solid", linewidth=1.0, ) ax_rt.set_xlabel("Iteration") ax_rt.set_ylabel("Cumulative runtime (s)") ax_rt.spines["top"].set_visible(False) ax_rt.spines["right"].set_visible(False) fig.tight_layout() plt.show() else: print("No runtime to plot.")
[docs] @dataclass class FitFlags: """Status flags from a model fit. Parameters ---------- converged : bool, default False True if the lower bound converged before reaching max_iter. decreasing_lb : bool, default False True if lower bound decreased during fitting. private_var_floor : bool, default False True if the private variance floor was used on any values of phi. Examples -------- Access via a fitted model: >>> model.fit(Y) >>> model.flags.display() # Print status summary """ converged: bool = False decreasing_lb: bool = False private_var_floor: bool = False def display(self) -> None: """Print the fit flags.""" print(f"Converged: {self.converged}") print(f"Decreasing lower bound: {self.decreasing_lb}") print(f"Private variance floor: {self.private_var_floor}")