sambo.plot
The module contains functions for plotting convergence, regret, partial dependence, sequence of evaluations …
Example>>> import matplotlib.pyplot as plt
>>> from scipy.optimize import rosen
>>> from sambo import minimize
>>> result = minimize(rosen, bounds=[(-2, 2), (-2, 2)],
... constraints=lambda x: sum(x) <= len(x))
>>> plot_convergence(result)
>>> plot_regret(result)
>>> plot_objective(result)
>>> plot_evaluations(result)
>>> plt.show()
def plot_convergence(*results: sambo._util.OptimizeResult | tuple[str, sambo._util.OptimizeResult],
true_minimum: float | None = None,
xscale: Literal['linear', 'log'] = 'linear',
yscale: Literal['linear', 'log'] = 'linear') ‑> matplotlib.figure.Figure
def plot_convergence(
*results: OptimizeResult | tuple[str, OptimizeResult],
true_minimum: Optional[float] = None,
xscale: Literal['linear', 'log'] = 'linear',
yscale: Literal['linear', 'log'] = 'linear',
) -> Figure:
"""
Plot one or several convergence traces,
showing how an error estimate evolved during the optimization process.
Parameters
----------
*results : OptimizeResult or tuple[str, OptimizeResult]
The result(s) for which to plot the convergence trace.
In tuple format, the string is used as the legend label
for that result.
true_minimum : float, optional
The true minimum *value* of the objective function, if known.
xscale, yscale : {'linear', 'log'}, optional, default='linear'
The scales for the axes.
Returns
-------
fig : matplotlib.figure.Figure
The matplotlib figure.
Example
-------
.. image:: /convergence.svg
"""
assert results, results
fig = plt.figure()
_watermark(fig)
ax = plt.gca()
ax.set_title("Convergence")
ax.set_xlabel("Number of function evaluations $n$")
ax.set_ylabel(r"$\min\ f(x)$ after $n$ evaluations")
ax.grid()
_set_xscale_yscale(ax, xscale, yscale)
fig.set_layout_engine('tight')
MARKER = cycle(_MARKER_SEQUENCE)
for i, result in enumerate(results, 1):
name = f'#{i}' if len(results) > 1 else None
if isinstance(result, tuple):
name, result = result
result = _check_result(result)
nfev = _check_nfev(result)
mins = np.minimum.accumulate(result.funv)
ax.plot(range(1, nfev + 1), mins,
label=name, marker=next(MARKER), markevery=(.05 + .05 * i, .2),
linestyle='--', alpha=.7, markersize=6, lw=2)
if true_minimum is not None:
ax.axhline(true_minimum, color="k", linestyle='--', lw=1, label="True minimum")
if true_minimum is not None or name is not None:
ax.legend(loc="upper right")
return fig
Plot one or several convergence traces, showing how an error estimate evolved during the optimization process.
Parameters*results
: OptimizeResult
or tuple[str, OptimizeResult]
true_minimum
: float
, optional
xscale
, yscale
: {'linear', 'log'}
, optional, default='linear'
fig
: matplotlib.figure.Figure
def plot_evaluations(result: sambo._util.OptimizeResult,
*,
bins: int = 10,
names: list[str] | None = None,
plot_dims: list[int] | None = None,
jitter: float = 0.02,
size: int = 1.7,
cmap: str = 'summer') ‑> matplotlib.figure.Figure
def plot_evaluations(
result: OptimizeResult,
*,
bins: int = 10,
names: Optional[list[str]] = None,
plot_dims: Optional[list[int]] = None,
jitter: float = .02,
size: int = _DEFAULT_SUBPLOT_SIZE,
cmap: str = 'summer',
) -> Figure:
"""Visualize the order in which points were evaluated during optimization.
This creates a 2D matrix plot where the diagonal plots are histograms
that show distribution of samples for each variable.
Plots below the diagonal are scatter-plots of the sample points,
with the color indicating the order in which the samples were evaluated.
A red star shows the best found parameters.
Parameters
----------
result : `OptimizeResult`
The optimization result.
bins : int, default=10
Number of bins to use for histograms on the diagonal. This value is
used for real dimensions, whereas categorical and integer dimensions
use number of bins equal to their distinct values.
names : list of str, default=None
Labels of the dimension variables. Defaults to `['x0', 'x1', ...]`.
plot_dims : list of int, default=None
List of dimension indices to be included in the plot.
Default uses all non-constant dimensions of
the search-space.
jitter : float, default=.02
Ratio of jitter to add to scatter plots.
Default looks clear for categories of up to about 8 items.
size : float, default=2
Height (in inches) of each subplot/facet.
cmap: str or Colormap, default='summer'
Color map for the sequence of scatter points.
.. todo::
Figure out how to lay out multiple Figure objects side-by-side.
Alternatively, figure out how to take parameter `ax=` to plot onto.
Then we can show a plot of evaluations for each of the built-in methods
(`TestDocs.test_make_doc_plots()`).
Returns
-------
fig : matplotlib.figure.Figure
A 2D matrix of subplots.
Example
-------
.. image:: /evaluations.svg
"""
result = _check_result(result)
space = _check_space(result)
plot_dims = _check_plot_dims(plot_dims, space._bounds)
n_dims = len(plot_dims)
bounds = dict(zip(plot_dims, space._bounds[plot_dims]))
assert names is None or isinstance(names, Iterable) and len(names) == n_dims, \
(names, n_dims, plot_dims)
x_min = space.transform(np.atleast_2d(result.x))[0]
samples = space.transform(result.xv)
color = np.arange(len(samples))
fig, axs = _subplots_grid(n_dims, size, "Sequence & distribution of function evaluations")
for _i, i in enumerate(plot_dims):
for _j, j in enumerate(plot_dims[:_i + 1]):
ax = axs[_i, _j]
# diagonal histogram
if i == j:
# if dim.prior == 'log-uniform':
# bins_ = np.logspace(*np.log10(bounds[i]), bins)
ax.hist(
samples[:, i],
bins=(int(bounds[i][1] + 1) if space._is_cat(i) else
min(bins, int(bounds[i][1] - bounds[i][0] + 1)) if space._is_int(i) else
bins),
range=None if space._is_cat(i) else bounds[i]
)
# lower triangle scatter plot
elif i > j:
x, y = samples[:, j], samples[:, i]
if jitter:
x, y = _maybe_jitter(jitter, (j, x), (i, y), space=space)
ax.scatter(x, y, c=color, s=40, cmap=cmap, lw=.5, edgecolor='k')
ax.scatter(x_min[j], x_min[i], c='#d009', s=400, marker='*', lw=.5, edgecolor='k')
_format_scatter_plot_axes(fig, axs, space, plot_dims=plot_dims, dim_labels=names, size=size)
return fig
Visualize the order in which points were evaluated during optimization.
This creates a 2D matrix plot where the diagonal plots are histograms that show distribution of samples for each variable.
Plots below the diagonal are scatter-plots of the sample points, with the color indicating the order in which the samples were evaluated.
A red star shows the best found parameters.
Parametersresult
: OptimizeResult
bins
: int
, default=10
names
: list
of str
, default=None
['x0', 'x1', ...]
.
plot_dims
: list
of int
, default=None
jitter
: float
, default=.02
size
: float
, default=2
cmap
: str
or Colormap
, default='summer'
TODO
Figure out how to lay out multiple Figure objects side-by-side. Alternatively, figure out how to take parameter ax=
to plot onto. Then we can show a plot of evaluations for each of the built-in methods (TestDocs.test_make_doc_plots()
).
fig
: matplotlib.figure.Figure
def plot_objective(result: sambo._util.OptimizeResult,
*,
levels: int = 10,
resolution: int = 16,
n_samples: int = 250,
estimator: str | sambo._util._SklearnLikeRegressor | None = None,
size: float = 1.7,
zscale: Literal['linear', 'log'] = 'linear',
names: list[str] | None = None,
true_minimum: list[float] | list[list[float]] | None = None,
plot_dims: list[int] | None = None,
plot_max_points: int = 200,
jitter: float = 0.02,
cmap: str = 'viridis_r') ‑> matplotlib.figure.Figure
def plot_objective(
result: OptimizeResult,
*,
levels: int = 10,
resolution: int = 16,
n_samples: int = 250,
estimator: Optional[str | _SklearnLikeRegressor] = None,
size: float = _DEFAULT_SUBPLOT_SIZE,
zscale: Literal['linear', 'log'] = 'linear',
names: Optional[list[str]] = None,
true_minimum: Optional[list[float] | list[list[float]]] = None,
plot_dims: Optional[list[int]] = None,
plot_max_points: int = 200,
jitter: float = .02,
cmap: str = 'viridis_r',
) -> Figure:
"""Plot a 2D matrix of partial dependence plots that show the
individual influence of each variable on the objective function.
The diagonal plots show the effect of a single dimension on the
objective function, while the plots below the diagonal show
the effect on the objective function when varying two dimensions.
Partial dependence plot shows how the values of any two variables
influence `estimator` predictions after "averaging out"
the influence of all other variables.
Partial dependence is calculated by averaging the objective value
for a number of random samples in the search-space,
while keeping one or two dimensions fixed at regular intervals. This
averages out the effect of varying the other dimensions and shows
the influence of just one or two dimensions on the objective function.
Black dots indicate the points evaluated during optimization.
A red star indicates the best found minimum (or `true_minimum`,
if provided).
.. note::
Partial dependence plot is only an estimation of the surrogate
model which in turn is only an estimation of the true objective
function that has been optimized. This means the plots show
an "estimate of an estimate" and may therefore be quite imprecise,
especially if relatively few samples have been collected during the
optimization, and especially in regions of the search-space
that have been sparsely sampled (e.g. regions far away from the
found optimum).
Parameters
----------
result : OptimizeResult
The optimization result.
levels : int, default=10
Number of levels to draw on the contour plot, passed directly
to `plt.contourf()`.
resolution : int, default=16
Number of points at which to evaluate the partial dependence
along each dimension.
n_samples : int, default=250
Number of samples to use for averaging the model function
at each of the `n_points`.
estimator
Last fitted model for estimating the objective function.
size : float, default=2
Height (in inches) of each subplot/facet.
zscale : {'linear', 'log'}, default='linear'
Scale to use for the z axis of the contour plots.
names : list of str, default=None
Labels of the dimension variables. Defaults to `['x0', 'x1', ...]`.
plot_dims : list of int, default=None
List of dimension indices to be included in the plot.
Default uses all non-constant dimensions of
the search-space.
true_minimum : list of floats, default=None
Value(s) of the red point(s) in the plots.
Default uses best found X parameters from the result.
plot_max_points: int, default=200
Plot at most this many randomly-chosen evaluated points
overlaying the contour plots.
jitter : float, default=.02
Amount of jitter to add to categorical and integer dimensions.
Default looks clear for categories of up to about 8 items.
cmap: str or Colormap, default='viridis_r'
Color map for contour plots, passed directly to
`plt.contourf()`.
Returns
-------
fig : matplotlib.figure.Figure
A 2D matrix of partial dependence sub-plots.
Example
-------
.. image:: /objective.svg
"""
result = _check_result(result)
space = _check_space(result)
plot_dims = _check_plot_dims(plot_dims, space._bounds)
n_dims = len(plot_dims)
bounds = dict(zip(plot_dims, space._bounds[plot_dims]))
assert names is None or isinstance(names, Iterable) and len(names) == n_dims, (n_dims, plot_dims, names)
if true_minimum is None:
true_minimum = result.x
true_minimum = np.atleast_2d(true_minimum)
assert true_minimum.shape[1] == len(result.x), (true_minimum, result)
true_minimum = space.transform(true_minimum)
assert isinstance(plot_max_points, Integral) and plot_max_points >= 0, plot_max_points
rng = np.random.default_rng(0)
# Sample points to plot, but don't include points exactly at res.x
inds = np.setdiff1d(
np.arange(len(result.xv)),
np.where(np.all(result.xv == result.x, axis=1))[0],
assume_unique=True)
plot_max_points = min(len(inds), plot_max_points)
inds = np.sort(rng.choice(inds, plot_max_points, replace=False))
x_samples = space.transform(result.xv[inds])
samples = space.sample(n_samples)
assert zscale in ('log', 'linear', None), zscale
locator = LogLocator() if zscale == 'log' else None
fig, axs = _subplots_grid(n_dims, size, "Partial dependence")
result_estimator = getattr(result, 'model', [None])[-1]
from sambo._estimators import _estimator_factory
if estimator is None and result_estimator is not None:
estimator = result_estimator
else:
_estimator_arg = estimator
estimator = _estimator_factory(estimator, bounds, rng=0)
if result_estimator is None and _estimator_arg is None:
warnings.warn(
'The optimization result process does not appear to have been '
'driven by a model. You can still still observe partial dependence '
f'of the variables as modeled by estimator={estimator!r}',
UserWarning, stacklevel=2)
estimator.fit(space.transform(result.xv), result.funv)
assert isinstance(estimator, _SklearnLikeRegressor), estimator
for _i, i in enumerate(plot_dims):
for _j, j in enumerate(plot_dims[:_i + 1]):
ax = axs[_i, _j]
# diagonal line plot
if i == j:
xi, yi = _partial_dependence(
space, bounds, estimator, i, j=None, sample_points=samples, resolution=resolution)
ax.plot(xi, yi)
for m in true_minimum:
ax.axvline(m[i], linestyle="--", color="r", lw=1)
# lower triangle contour field
elif i > j:
xi, yi, zi = _partial_dependence(
space, bounds, estimator, i, j, sample_points=samples, resolution=resolution)
ax.contourf(xi, yi, zi, levels, locator=locator, cmap=cmap,
alpha=(1 - .2 * int(bool(plot_max_points))))
for m in true_minimum:
ax.scatter(m[j], m[i], c='#d00', s=200, lw=.5, marker='*')
if plot_max_points:
x, y = x_samples[:, j], x_samples[:, i]
if jitter:
x, y = _maybe_jitter(jitter, (j, x), (i, y), space=space)
ax.scatter(x, y, c='k', s=12, lw=0, alpha=.5)
_format_scatter_plot_axes(fig, axs, space, plot_dims=plot_dims, dim_labels=names, size=size)
return fig
Plot a 2D matrix of partial dependence plots that show the individual influence of each variable on the objective function.
The diagonal plots show the effect of a single dimension on the objective function, while the plots below the diagonal show the effect on the objective function when varying two dimensions.
Partial dependence plot shows how the values of any two variables influence estimator
predictions after "averaging out" the influence of all other variables.
Partial dependence is calculated by averaging the objective value for a number of random samples in the search-space, while keeping one or two dimensions fixed at regular intervals. This averages out the effect of varying the other dimensions and shows the influence of just one or two dimensions on the objective function.
Black dots indicate the points evaluated during optimization.
A red star indicates the best found minimum (or true_minimum
, if provided).
Note
Partial dependence plot is only an estimation of the surrogate model which in turn is only an estimation of the true objective function that has been optimized. This means the plots show an "estimate of an estimate" and may therefore be quite imprecise, especially if relatively few samples have been collected during the optimization, and especially in regions of the search-space that have been sparsely sampled (e.g. regions far away from the found optimum).
Parametersresult
: OptimizeResult
levels
: int
, default=10
plt.contourf()
.
resolution
: int
, default=16
n_samples
: int
, default=250
n_points
.
estimator
size
: float
, default=2
zscale
: {'linear', 'log'}
, default='linear'
names
: list
of str
, default=None
['x0', 'x1', ...]
.
plot_dims
: list
of int
, default=None
true_minimum
: list
of floats
, default=None
plot_max_points
: int
, default=200
jitter
: float
, default=.02
cmap
: str
or Colormap
, default='viridis_r'
plt.contourf()
.
fig
: matplotlib.figure.Figure
def plot_regret(*results: sambo._util.OptimizeResult | tuple[str, sambo._util.OptimizeResult],
true_minimum: float | None = None,
xscale: Literal['linear', 'log'] = 'linear',
yscale: Literal['linear', 'log'] = 'linear') ‑> matplotlib.figure.Figure
def plot_regret(
*results: OptimizeResult | tuple[str, OptimizeResult],
true_minimum: Optional[float] = None,
xscale: Literal['linear', 'log'] = 'linear',
yscale: Literal['linear', 'log'] = 'linear',
) -> Figure:
"""
Plot one or several cumulative [regret] traces.
Regret is the difference between achieved objective and its optimum.
[regret]: https://en.wikipedia.org/wiki/Regret_(decision_theory)
Parameters
----------
*results : OptimizeResult or tuple[str, OptimizeResult]
The result(s) for which to plot the convergence trace.
In tuple format, the string is used as the legend label
for that result.
true_minimum : float, optional
The true minimum *value* of the objective function, if known.
If unspecified, minimum is assumed to be the minimum of the
values found in `results`.
xscale, yscale : {'linear', 'log'}, optional, default='linear'
The scales for the axes.
Returns
-------
fig : matplotlib.figure.Figure
The matplotlib figure.
Example
-------
.. image:: /regret.svg
"""
assert results, results
fig = plt.figure()
_watermark(fig)
ax = fig.gca()
ax.set_title("Cumulative regret")
ax.set_xlabel("Number of function evaluations $n$")
ax.set_ylabel(r"Cumulative regret after $n$ evaluations: "
r"$\ \sum_t^n{\,\left[\,f\,\left(x_t\right) - f_{\mathrm{opt}}\,\right]}$")
ax.grid()
_set_xscale_yscale(ax, xscale, yscale)
ax.yaxis.set_major_formatter(FormatStrFormatter('$%.3g$'))
fig.set_layout_engine('tight')
MARKER = cycle(_MARKER_SEQUENCE)
if true_minimum is None:
true_minimum = np.min([
np.min((r[1] if isinstance(r, tuple) else r).funv) # TODO ensure funv???
for r in results
])
for i, result in enumerate(results, 1):
name = f'#{i}' if len(results) > 1 else None
if isinstance(result, tuple):
name, result = result
result = _check_result(result)
nfev = _check_nfev(result)
regrets = [np.sum(result.funv[:i] - true_minimum)
for i in range(1, nfev + 1)]
ax.plot(range(1, nfev + 1), regrets,
label=name, marker=next(MARKER), markevery=(.05 + .05 * i, .2),
linestyle='--', alpha=.7, markersize=6, lw=2)
if name is not None:
ax.legend(loc="lower right")
return fig
Plot one or several cumulative regret traces. Regret is the difference between achieved objective and its optimum.
Parameters*results
: OptimizeResult
or tuple[str, OptimizeResult]
true_minimum
: float
, optional
results
.
xscale
, yscale
: {'linear', 'log'}
, optional, default='linear'
fig
: matplotlib.figure.Figure
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4