Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change set_hyperparameters to accept a dictionary and named arguments #1320

Open
MichaelClerx opened this issue Mar 16, 2021 · 5 comments
Open

Comments

@MichaelClerx
Copy link
Member

MichaelClerx commented Mar 16, 2021

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

  1. you need to remember the order the hyperparameters are in, when writing the test; and
  2. 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)

@MichaelClerx
Copy link
Member Author

@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

@ben18785
Copy link
Collaborator

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.

@MichaelClerx
Copy link
Member Author

I agree @chonlei , you should implement this immediately!

@ben18785
Copy link
Collaborator

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).

@MichaelClerx
Copy link
Member Author

I was hoping Chon would be too busy to notice and just start coding

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants