-
Notifications
You must be signed in to change notification settings - Fork 31
Added a simple particle swarm based hyper-heuristic #125
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
base: feature-hho
Are you sure you want to change the base?
Changes from 1 commit
82fe4cd
0325109
60ce342
a2762ec
8d35435
fdafb22
0f1f598
95094e6
339fc5d
60f3874
cab23a9
d9964ff
9dd8c12
73875a3
b87a69c
58dbe05
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,202 @@ | ||
|
|
||
|
|
||
| ## FEATURES ################################################################### | ||
|
|
||
| from __future__ import absolute_import | ||
| from __future__ import division | ||
|
|
||
| ## IMPORTS #################################################################### | ||
|
|
||
| import numpy as np | ||
| import random | ||
| from functools import partial | ||
| from qinfer.perf_testing import perf_test_multiple | ||
| from qinfer import distributions | ||
|
|
||
| ## CLASSES #################################################################### | ||
|
|
||
| __all__ = [ | ||
| 'ParticleSwarmOptimiser' | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The performance objective class should also be listed under |
||
| ] | ||
|
|
||
| class HyperHeuristicOptimiser(object): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At the level of generality that this class is written, including |
||
| ''' | ||
| A generic hyper-heuristic optimiser class that is inherited by the other optimisation functions. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A short example (such as the |
||
|
|
||
| :param np.ndarray param_names: The list of parameters that are being searched over. | ||
| :param function fitness_function: The function that is being optimised over, defaults to perf test multiple | ||
| :param function boundary_map: Function to constrain points within some boundary regime | ||
| :param dict funct_args: Arguments to pass to the fitness function | ||
| :param dict funct_kwargs: Keyword arguments to pass to the fitness function | ||
| ''' | ||
|
|
||
| def __init__( | ||
| self, | ||
| param_names, | ||
| fitness_function = None, | ||
| boundary_map=None, | ||
| *funct_args, | ||
| **funct_kwargs | ||
| ): | ||
| self._param_names = param_names | ||
| self._n_free_params = len(param_names) | ||
| self._boundary_map = boundary_map | ||
| self._funct_args = funct_args | ||
| self._funct_kwargs = funct_kwargs | ||
|
|
||
| if fitness_function is None: # Default to calling perf test multiple | ||
| self._optimisable = PerfTestMultipleAbstractor( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be |
||
| self._param_names, | ||
| *self._funct_args, | ||
| **self._funct_kwargs | ||
| ) | ||
| else: | ||
| self._fitness_function = partial(fitness_function, *self._funct_args, **self._funct_kwargs) | ||
|
|
||
| # Member function needed for parralelisation | ||
| def fitness_function(self, params): | ||
| return self._fitness_function(params) | ||
|
|
||
| def parrallel(self): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there's only one "r" in parallel? |
||
| raise NotImplementedError("This optimiser does not have parrallel support. To resolve this issue, level an appropriate criticism at the developer.") | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's probably OK to leave just the first sentence of the exception. |
||
|
|
||
| class ParticleSwarmOptimiser(HyperHeuristicOptimiser): | ||
| ''' | ||
| A particle swarm optimisation based hyperheuristic | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good start to the docstring, but we should make sure it describes all of the kwargs. |
||
| :param integer n_pso_iterations: | ||
| :param integer n_pso_particles: | ||
| :param | ||
| :param | ||
| ''' | ||
|
|
||
| def __call__(self, | ||
| n_pso_iterations=50, | ||
| n_pso_particles=60, | ||
| initial_position_distribution=None, | ||
| initial_velocity_distribution=None, | ||
| omega_v=0.35, | ||
| phi_p=0.25, | ||
| phi_g=0.5, | ||
| serial_map=map | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The name |
||
| ): | ||
| self._fitness_dt = np.dtype([ | ||
| ('params', np.float64, (self._n_free_params,)), | ||
| ('velocities', np.float64, (self._n_free_params,)), | ||
| ('fitness', np.float64)]) | ||
| self._fitness = np.empty([n_pso_iterations, n_pso_particles], dtype=self._fitness_dt) | ||
| local_attractors = np.empty([n_pso_particles], dtype=self._fitness_dt) | ||
| global_attractor = np.empty([1], dtype=self._fitness_dt) | ||
|
|
||
| if initial_position_distribution is None: | ||
| initial_position_distribution = distributions.UniformDistribution(np.array([[ 0, 1]] * self._n_free_params)); | ||
|
|
||
| if initial_velocity_distribution is None: | ||
| initial_velocity_distribution = distributions.UniformDistribution(np.array([[-1, 1]] * self._n_free_params)) | ||
|
|
||
| # Initial particle positions | ||
| self._fitness[0]["params"] = initial_position_distribution.sample(n_pso_particles) | ||
|
|
||
| # Apply the boundary conditions if any exist | ||
| if self._boundary_map is not None: | ||
| self._fitness[itr]["params"] = self._boundary_map(self._fitness[itr]["params"]) | ||
|
|
||
| # Calculate the initial particle fitnesses | ||
| self._fitness[0]["fitness"] = self.evaluate_fitness(self._fitness[0]["params"], | ||
| serial_map=serial_map) | ||
|
|
||
| # Calculate the positions of the attractors | ||
| local_attractors = self._fitness[0] | ||
| local_attractors, global_attractor = self.update_attractors( | ||
| self._fitness[0], | ||
| local_attractors, | ||
| global_attractor) | ||
|
|
||
| # Initial particle velocities | ||
| self._fitness[0]["velocities"] = initial_velocity_distribution.sample(n_pso_particles) | ||
| self._fitness[0]["velocities"] = self.update_velocities( | ||
| self._fitness[0]["params"], | ||
| self._fitness[0]["velocities"], | ||
| local_attractors["params"], | ||
| global_attractor["params"], | ||
| omega_v, phi_p, phi_g) | ||
|
|
||
| for itr in range(1, n_pso_iterations): | ||
| #Update the particle positions | ||
| self._fitness[itr]["params"] = self.update_positions( | ||
| self._fitness[itr - 1]["params"], | ||
| self._fitness[itr - 1]["velocities"]) | ||
|
|
||
| # Apply the boundary conditions if any exist | ||
| if self._boundary_map is not None: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The behavior of |
||
| self._fitness[itr]["params"] = self._boundary_map(self._fitness[itr]["params"]) | ||
|
|
||
| # Recalculate the fitness function | ||
| self._fitness[itr]["fitness"] = self.evaluate_fitness( | ||
| self._fitness[itr]["params"], | ||
| serial_map=serial_map) | ||
|
|
||
| # Find the new attractors | ||
| local_attractors, global_attractor = self.update_attractors( | ||
| self._fitness[itr], | ||
| local_attractors, | ||
| global_attractor) | ||
|
|
||
| # Update the velocities | ||
| self._fitness[itr]["velocities"] = self.update_velocities( | ||
| self._fitness[itr]["params"], | ||
| self._fitness[itr - 1]["velocities"], | ||
| local_attractors["params"], | ||
| global_attractor["params"], | ||
| omega_v, phi_p, phi_g) | ||
|
|
||
| return global_attractor | ||
|
|
||
| def evaluate_fitness(self, particles, serial_map): | ||
| fitness_function = partial(self.fitness_function) | ||
| fitness = np.empty([len(particles)], dtype=np.float64) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line has no effect, since |
||
| fitness = serial_map(self.fitness_function, particles) | ||
| return fitness | ||
|
|
||
| def update_positions(self, positions, velocities): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These methods are public and should thus have at least some brief docstrings. They probably don't need to be very detailed, but there should be something. |
||
| updated = positions + velocities | ||
| return updated | ||
|
|
||
| def update_velocities(self, positions, velocities, local_attractors, global_attractor, omega_v, phi_p, phi_g): | ||
| random_p = np.random.random_sample(positions.shape) | ||
| random_g = np.random.random_sample(positions.shape) | ||
| updated = omega_v * velocities + phi_p * random_p * (local_attractors - positions) + phi_g * random_g * (global_attractor - positions) | ||
| return updated | ||
|
|
||
| def update_attractors(self, particles, local_attractors, global_attractor): | ||
| for idx, particle in enumerate(particles): | ||
| if particle["fitness"] < local_attractors[idx]["fitness"]: | ||
| local_attractors[idx] = particle | ||
| global_attractor = local_attractors[np.argmin(local_attractors["fitness"])] | ||
| return local_attractors, global_attractor | ||
|
|
||
| class PerfTestMultipleAbstractor: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the term |
||
| def __init__(self, | ||
| param_names, | ||
| evaluation_function = None, | ||
| *args, | ||
| **kwargs): | ||
| self._heuristic = kwargs['heuristic_class'] | ||
| del kwargs['heuristic_class'] | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can cause an exception if |
||
| self._args = args | ||
| self._kwargs = kwargs | ||
| self._param_names = param_names | ||
| if evaluation_function is None: | ||
| self._evaluation_function = lambda performance: performance['loss'][:,-1].mean(axis=0) | ||
| else: | ||
| self._evaluation_function = evaluation_function | ||
|
|
||
| def __call__(self, params): | ||
| performance = perf_test_multiple( | ||
| *self._args, | ||
| heuristic_class = self._heuristic(**{ | ||
| name: param | ||
| for name, param in zip(self._param_names, params) | ||
| }), | ||
| **self._kwargs | ||
| ) | ||
| return self._evaluation_function(performance) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should make sure the copyright header and
-*- codinglines are kept on this file as well.