pints
pints copied to clipboard
Change set_hyperparameters to accept a dictionary and named arguments
At the moment we've got a class TunableMethod that samplers and optimisers and anything else can implement, that provides two methods:
n_hyper_parameters -> int
set_hyper_parameters(x)
where x
is an array of scalars. This is really useful as it means you can treat the hyper parameters as a 1d vector of parameters to optimise, and @martinjrobins has been doing this in performance testing.
For functional testing, @fcooper8472 has proposed something like this:
def test_haario_bardenet_acmc_on_two_dim_gaussian():
problem = RunMcmcMethodOnTwoDimGaussian(
method=pints.HaarioBardenetACMC,
n_chains=3,
n_iterations=4000,
n_warmup=1000
)
return {
'kld': problem.estimate_kld(),
'mean-ess': problem.estimate_mean_ess()
}
Here, it'd be really good if we could pass in an extra argument after n_warmup
that would be e.g. an array of hyperparameters.
The internal code would then (1) figure out if it's a single or multi-chain method, (2) call set_hyperparameters on each.
Solved!
But it does mean that
- you need to remember the order the hyperparameters are in, when writing the test; and
- you need to set each hyperparameter, even the ones that are always some magic number (e.g. 7 in bfgs)
So I was thinking it'd be good to have a set_hyperparameters
that takes a dictionary, or named arguments, as input. That way the code above could be e.g.
problem = RunMcmcMethodOnTwoDimGaussian(
method=pints.HaarioBardenetACMC,
n_chains=3,
n_iterations=4000,
n_warmup=1000,
hyper_parameters={'mu': 1, 'sigma': 2}
)
or
problem = RunMcmcMethodOnTwoDimGaussian(
method=pints.HaarioBardenetACMC,
n_chains=3,
n_iterations=4000,
n_warmup=1000,
hyper_parameters={'mu': 1, 'sigma': 2, 'special_number_thats_usually_six': 12}
)
for a particularly nasty problem.
Seems to me this will be much easier to use? An additional bonus would be that we solve @ben18785 's (justified) annoyance with setting hyperparameters on a controller.
Instead of
for x in controller.samplers():
x.set_barry(5)
we could do
controller.set_hyper_parameters(barry=5)
and
params = {'barry': 5}
controller.set_hyper_parameters(**params)
(which would work via arbitrary keyword args: def set_hyper_parameters(**kwargs)
gets kwargs as a dictionary)
Proposal 1
We update tunable method to:
def set_hyper_parameter_array(self, x):
"""
Sets the hyper-parameters for the method with the given vector of
values (see :class:`TunableMethod`).
Parameters
----------
x
An array of length ``n_hyper_parameters`` used to set the
hyper-parameters.
"""
pass
def set_hyper_parameters(self, **kwargs):
"""
Sets the hyper-parameters for the method using keyword
arguments (see :class:`TunableMethod`).
Examples::
tunable_method.set_hyper_parameters(x=3, y=4)
parameters = {'x': 3}
tunable_method.set_hyper_parameters(**parameters)
The hyper-parameters that can be set depend on the class implementing
:class:`TunableMethod`. Setting hyper-parameters that do not exist will
result in a ``ValueError`` being raised.
"""
pass
Proposal 2
Slightly more confusing, but perhaps better
def set_hyper_parameter(self, _array=None, **kwargs):
"""
Sets the hyper-parameters for the method with a given vector of
values or keyword arguments (see :class:`TunableMethod`).
Parameters
----------
_array
An optional array of length ``n_hyper_parameters`` used to set the
hyper-parameters.
**
Optional keyword arguments to set specific hyper-parameters. (If both
a vector an keyword arguments are given, the array arguments will be
set first, and then overwritten by the keyword arguments).
Examples::
tunable_method.set_hyper_parameters([3, 4])
tunable_method.set_hyper_parameters(x=3, y=4)
parameters = {'x': 3}
tunable_method.set_hyper_parameters(**parameters)
The hyper-parameters that can be set depend on the class implementing
:class:`TunableMethod`. Setting hyper-parameters that do not exist will
result in a ``ValueError`` being raised.
"""
pass
Further changes
The controller would get some extra method set_hyper_parameters(**kwargs)
that gets kwargs
as a dict, it can then just pass this on to the appropriate sampler or samplers via sampler.set_hyper_parameters(**kwargs)
(where the **
unpacks the dict into keyword arguments again).
Similarly, the functional tests would get a constructor argument hyper_parameters
that they could then pass to the controller using controller.set_hyper_parameters(**hyper_parameters)
@ben18785 @martinjrobins @fcooper8472 and any others, please have a look and see if this would work for you?
I think anyone can implement it, after that :D
Thanks @chonlei -- I really like this! I think this makes setting hyperparameters much neater.
My preference would be to go for proposal 1 since it's safer.
I agree @chonlei , you should implement this immediately!
Haha. I have no idea why I wrote @chonlei there! Sorry @MichaelClerx (I think I must have written "@C" then chose "h" rather than "l).
I was hoping Chon would be too busy to notice and just start coding