Fitter#
- class braintools.fit.Fitter(simulate, objective, space, *, init=None, optimizer=None, backend='grad', transient=None)#
Fit model parameters to data by minimising an objective.
Functional mode (this constructor) fits a pure function; module mode (
from_module()) fits the trainable parameters of abrainstate.nn.Module.- Parameters:
simulate (
Callable[[Dict[str,Any]],Any]) –simulate(params) -> prediction, a pure JAX function of a dict{name: value}(values carry the units ofspace). It must be traceable byjax.jit/jax.vmap/jax.grad('grad'needs gradients; the other backends only needvmap).objective (
Callable[[Any,Any],Any]) –objective(prediction, target) -> scalar, e.g. anObjectiveor a loss frombraintools.fit.losses.space (
Dict[str,Any]) –{name: (low, high)}box bounds (scalars, arrays or Quantities;loworhighmay beNonefor a one-sided bound) or{name: None}for an unbounded parameter whose value comes frominit. The shape of a parameter is the broadcast shape of its bounds and initial value.init (
Dict[str,Any] |None) – Initial values{name: value}, used for unbounded parameters (required there), for one-sided bounds (required there), and as the single start whenn_starts=1and nokeyis given (box parameters default to the midpoint of their box).optimizer (
Any) – Backend options. A string names the method ('lbfgs'/'adam'/'gd'for'grad', ascipy.optimize.minimize()method for'scipy', a Nevergrad optimiser such as'DE'or'CMA'for'nevergrad'). A dict holds'method'plus options:'tol'and'learning_rate'for'grad';'tol'plusscipy.optimize.minimize()options for'scipy';'n_sample'(population per generation, defaultn_starts) plusNevergradOptimizerkeyword arguments for'nevergrad'. For'grad'anoptaxtransformation or abraintools.optimoptax optimiser (itstx) is used as the update rule. Defaults: L-BFGS,'L-BFGS-B','DE'.backend (
str) – Optimiser backend.transient (
int|None) – Number of leading samples (axis 0 of every prediction leaf) dropped before the objective is evaluated.fit()may override it.
- Raises:
ValueError – If
backendis unknown, orspace/initare inconsistent.TypeError – If
simulateorobjectiveis not callable, oroptimizerhas the wrong type for the backend.
See also
FitResultThe result of
fit().braintools.optim.multistart_minimizeThe
'grad'backend.sweepScan parameters on a grid instead.
hessian_ciConfidence intervals at the optimum.
Notes
Parameter spaces. The
'grad'and'scipy'backends optimise in an unconstrained space: a bounded parameter is mapped throughbrainstate.nn.SigmoidT(SoftplusTfor one-sided bounds), a module parameter through its own transform.'nevergrad'searches the constrained box directly, so every parameter needs finite bounds.Starts. With
n_starts=1and nokey, the single start is the initial value (init, the box midpoint, or the module’s current value). Otherwisekeyis required and every bounded parameter is drawn uniformly from its box (unbounded ones use their initial value).x0infit()gives the starts explicitly. For'grad'and'scipy', a start within1e-3of the box width of a bound is moved that far inside, so a start on a bound (including a module parameter sitting at its transform’s bound) maps to a finite unconstrained value where the transform is not flat. The'grad'convergence test then acts on the unconstrained gradient (seemultistart_minimize()for the test itself).Randomness. All random draws derive from
key;'nevergrad'is seeded from it and does not touch NumPy’s global random generator.Purity. Functional mode is pure. In module mode, the model is not touched while fitting: the parameter values are fed into a functionalised copy of
predict(seefrom_module()), and the best values are written into the parameters only afterfit()finishes.Transformations.
fit()drives the search from the host (it compiles its own programs: onejit-ed,vmap-ped multi-start loop for'grad', ajit-ed loss and gradient for'scipy', ajit-ed,vmap-ped batch loss for'nevergrad'), so it cannot itself be called insidejax.jitorjax.vmap.Examples
>>> import jax >>> import jax.numpy as jnp >>> from braintools.fit import Fitter >>> t = jnp.linspace(0., 1., 50) >>> simulate = lambda p: p['a'] * jnp.exp(-t / p['tau']) >>> target = simulate({'a': 2.0, 'tau': 0.3}) >>> mse = lambda pred, y: jnp.mean((pred - y) ** 2) >>> fitter = Fitter(simulate, mse, {'a': (0., 5.), 'tau': (0.05, 1.)}) >>> res = fitter.fit(target, n_starts=4, key=jax.random.PRNGKey(0)) >>> print(round(float(res.best_params['a']), 3), round(float(res.best_params['tau']), 3)) 2.0 0.3
- fit(target, *, n_starts=1, key=None, n_steps=100, transient=None, x0=None)[source]#
Run the optimisation.
- Parameters:
target (
Any) – Data compared with the prediction by the objective.n_starts (
int) – Number of startsS: parallel local searches for'grad', sequential SciPy runs for'scipy', the population per generation for'nevergrad'(unless'n_sample'is set in the options).key (
Any) – Random key for drawing starts (required whenn_starts > 1andx0is not given, and always for'nevergrad').n_steps (
int) – Maximum iterations per start ('grad','scipy') or number of generations ('nevergrad').transient (
int|None) – Overrides the constructor’stransient.x0 (
Dict[str,Any] |None) – Explicit starts{name: values}in the constrained space, each with a leading axisS(which then setsn_starts). Not used by'nevergrad'.
- Returns:
Best and per-start parameters and losses.
- Return type:
- Raises:
ValueError – If
n_startsorn_stepsis not a positive integer, a key is needed but missing,x0is malformed, or'nevergrad'is used with a parameter that has no finite box.
- classmethod from_module(model, predict, objective, *, optimizer=None, backend='grad', transient=None, search_space=None)[source]#
Fit the trainable
brainstate.nn.Paramparameters of a module.- Parameters:
model (
Any) – The model. Everybrainstate.nn.Paramwithfit=Trueis fitted, named by its dotted attribute path (e.g.'w_ee','layer.tau').predict (
Callable[[Any],Any]) –predict(model) -> prediction; runs the model and reads its output. It is traced once perfit()call.objective (
Callable[[Any,Any],Any]) –objective(prediction, target) -> scalar.backend (
str) – Optimiser backend.transient (
int|None) – Leading prediction samples dropped before the objective.search_space (
Dict[str,Any] |None) –{name: (low, high)}constrained bounds overriding the box implied by a parameter’s transform (SigmoidT,TanhT,SoftsignT,ScaledSigmoidT,ClipT). Parameters with a box have their starts drawn from it;'nevergrad'needs a box for every parameter.
- Returns:
A fitter in module mode.
- Return type:
- Raises:
ValueError – If the model has no
fit=Trueparameter, orsearch_spacenames an unknown parameter.
Notes
predict(model)(plusmodel.reg_loss()when the model defines it, as brainmass does) is turned into a pure function of the parameter values withbrainstate.transform.StatefulFunction: the unconstrained values are substituted for the parameters’ParamStatevalues in the traced program, so starts and candidates arevmap-ped and the model’s states are never written during the search. The parameter’s own transform maps the unconstrained value to the constrained one. Afterfit()returns, the model holds the best parameters.Examples
>>> import brainstate >>> import jax >>> import jax.numpy as jnp >>> from braintools.fit import Fitter >>> class Decay(brainstate.nn.Module): ... def __init__(self): ... super().__init__() ... self.tau = brainstate.nn.Param(0.5, t=brainstate.nn.SigmoidT(0.05, 2.)) ... def __call__(self, t): ... return jnp.exp(-t / self.tau.value()) >>> t = jnp.linspace(0., 1., 40) >>> model = Decay() >>> mse = lambda p, y: jnp.mean((p - y) ** 2) >>> fitter = Fitter.from_module(model, lambda m: m(t), mse) >>> res = fitter.fit(jnp.exp(-t / 0.2)) >>> print(round(float(res.best_params['tau']), 3), round(float(model.tau.value()), 3)) 0.2 0.2