Skip to content

Commit 8d35435

Browse files
Added tests and additional PSO variants
1 parent a2762ec commit 8d35435

File tree

2 files changed

+536
-10
lines changed

2 files changed

+536
-10
lines changed

src/qinfer/hyper_heuristic_optimisers.py

Lines changed: 292 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,43 @@
1-
from __future__ import division, absolute_import, print_function
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
##
4+
# test_models.py: Simple models for testing inference engines.
5+
##
6+
# © 2017 Alan Robertson (arob8086@uni.sydney.edu.au)
7+
#
8+
# This file is a part of the Qinfer project.
9+
# Licensed under the AGPL version 3.
10+
##
11+
# This program is free software: you can redistribute it and/or modify
12+
# it under the terms of the GNU Affero General Public License as published by
13+
# the Free Software Foundation, either version 3 of the License, or
14+
# (at your option) any later version.
15+
#
16+
# This program is distributed in the hope that it will be useful,
17+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
18+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19+
# GNU Affero General Public License for more details.
20+
#
21+
# You should have received a copy of the GNU Affero General Public License
22+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
23+
##
24+
25+
## FEATURES ##################################################################
26+
27+
from __future__ import division
28+
from __future__ import absolute_import
29+
from __future__ import print_function
30+
31+
## IMPORTS ###################################################################
232

333
import random
434
import numpy as np
535
from functools import partial
636
from qinfer.perf_testing import perf_test_multiple, apply_serial
737
from qinfer import distributions
838

39+
## CLASSES ####################################################################
40+
941
class Optimiser(object):
1042
'''
1143
A generic optimiser class that is inherited by the other optimisation functions.
@@ -52,8 +84,11 @@ class ParticleSwarmOptimiser(Optimiser):
5284
A particle swarm optimisation based hyperheuristic
5385
:param integer n_pso_iterations:
5486
:param integer n_pso_particles:
55-
:param
56-
:param
87+
:param QInferDistribution initial_velocity_distribution:
88+
:param QInferDistribution initial_velocity_distribution:
89+
:param double
90+
:param double
91+
:param function
5792
'''
5893

5994
def __call__(self,
@@ -66,13 +101,9 @@ def __call__(self,
66101
phi_g=0.5,
67102
apply=apply_serial
68103
):
69-
self._fitness_dt = np.dtype([
70-
('params', np.float64, (self._n_free_params,)),
71-
('velocities', np.float64, (self._n_free_params,)),
72-
('fitness', np.float64)])
73-
self._fitness = np.empty([n_pso_iterations, n_pso_particles], dtype=self._fitness_dt)
74-
local_attractors = np.empty([n_pso_particles], dtype=self._fitness_dt)
75-
global_attractor = np.empty([1], dtype=self._fitness_dt)
104+
self._fitness = np.empty([n_pso_iterations, n_pso_particles], dtype=self.fitness_dt())
105+
local_attractors = np.empty([n_pso_particles], dtype=self.fitness_dt())
106+
global_attractor = np.empty([1], dtype=self.fitness_dt())
76107

77108
if initial_position_distribution is None:
78109
initial_position_distribution = distributions.UniformDistribution(np.array([[ 0, 1]] * self._n_free_params));
@@ -161,7 +192,258 @@ def update_attractors(self, particles, local_attractors, global_attractor):
161192
local_attractors[idx] = particle
162193
global_attractor = local_attractors[np.argmin(local_attractors["fitness"])]
163194
return local_attractors, global_attractor
195+
196+
def fitness_dt(self):
197+
return np.dtype([
198+
('params', np.float64, (self._n_free_params,)),
199+
('velocities', np.float64, (self._n_free_params,)),
200+
('fitness', np.float64)])
201+
202+
203+
class ParticleSwarmSimpleAnnealingOptimiser(ParticleSwarmOptimiser):
204+
205+
def __call__(self,
206+
n_pso_iterations=50,
207+
n_pso_particles=60,
208+
initial_position_distribution=None,
209+
initial_velocity_distribution=None,
210+
omega_v=0.35,
211+
phi_p=0.25,
212+
phi_g=0.5,
213+
temperature = 0.95,
214+
apply=apply_serial
215+
):
216+
self._fitness = np.empty([n_pso_iterations, n_pso_particles], dtype=self.fitness_dt())
217+
local_attractors = np.empty([n_pso_particles], dtype=self.fitness_dt())
218+
global_attractor = np.empty([1], dtype=self.fitness_dt())
219+
220+
if initial_position_distribution is None:
221+
initial_position_distribution = distributions.UniformDistribution(np.array([[ 0, 1]] * self._n_free_params));
222+
223+
if initial_velocity_distribution is None:
224+
initial_velocity_distribution = distributions.UniformDistribution(np.array([[-1, 1]] * self._n_free_params))
225+
226+
# Initial particle positions
227+
self._fitness[0]["params"] = initial_position_distribution.sample(n_pso_particles)
228+
229+
# Apply the boundary conditions if any exist
230+
if self._boundary_map is not None:
231+
self._fitness[itr]["params"] = self._boundary_map(self._fitness[itr]["params"])
232+
233+
# Calculate the initial particle fitnesses
234+
self._fitness[0]["fitness"] = self.evaluate_fitness(self._fitness[0]["params"],
235+
apply=apply)
236+
237+
# Calculate the positions of the attractors
238+
local_attractors = self._fitness[0]
239+
local_attractors, global_attractor = self.update_attractors(
240+
self._fitness[0],
241+
local_attractors,
242+
global_attractor)
243+
244+
# Initial particle velocities
245+
self._fitness[0]["velocities"] = initial_velocity_distribution.sample(n_pso_particles)
246+
self._fitness[0]["velocities"] = self.update_velocities(
247+
self._fitness[0]["params"],
248+
self._fitness[0]["velocities"],
249+
local_attractors["params"],
250+
global_attractor["params"],
251+
omega_v, phi_p, phi_g)
252+
253+
for itr in range(1, n_pso_iterations):
254+
#Update the particle positions
255+
self._fitness[itr]["params"] = self.update_positions(
256+
self._fitness[itr - 1]["params"],
257+
self._fitness[itr - 1]["velocities"])
258+
259+
# Apply the boundary conditions if any exist
260+
if self._boundary_map is not None:
261+
self._fitness[itr]["params"] = self._boundary_map(self._fitness[itr]["params"])
262+
263+
# Recalculate the fitness function
264+
self._fitness[itr]["fitness"] = self.evaluate_fitness(
265+
self._fitness[itr]["params"],
266+
apply=apply)
267+
268+
# Find the new attractors
269+
local_attractors, global_attractor = self.update_attractors(
270+
self._fitness[itr],
271+
local_attractors,
272+
global_attractor)
273+
274+
# Update the velocities
275+
self._fitness[itr]["velocities"] = self.update_velocities(
276+
self._fitness[itr]["params"],
277+
self._fitness[itr - 1]["velocities"],
278+
local_attractors["params"],
279+
global_attractor["params"],
280+
omega_v, phi_p, phi_g)
281+
282+
# Update the PSO params
283+
omega_v, phi_p, phi_g = self.update_pso_params(
284+
temperature,
285+
omega_v,
286+
phi_p,
287+
phi_g)
288+
289+
return global_attractor
290+
291+
def update_pso_params(self, temperature, omega_v, phi_p, phi_g):
292+
omega_v, phi_p, phi_g = np.multiply(temperature, [omega_v, phi_p, phi_g])
293+
return omega_v, phi_p, phi_g
294+
295+
296+
class ParticleSwarmTemperingOptimiser(ParticleSwarmOptimiser):
297+
'''
298+
A particle swarm optimisation based hyperheuristic
299+
:param integer n_pso_iterations:
300+
:param integer n_pso_particles:
301+
:param QInferDistribution initial_velocity_distribution:
302+
:param QInferDistribution initial_velocity_distribution:
303+
:param double
304+
:param double
305+
:param function
306+
'''
164307

308+
def __call__(self,
309+
n_pso_iterations=50,
310+
n_pso_particles=60,
311+
initial_position_distribution=None,
312+
initial_velocity_distribution=None,
313+
n_temper_categories = 6,
314+
temper_frequency = 10,
315+
temper_params = None,
316+
apply=apply_serial
317+
):
318+
self._fitness = np.empty([n_pso_iterations, n_pso_particles], dtype=self.fitness_dt())
319+
local_attractors = np.empty([n_pso_particles], dtype=self.fitness_dt())
320+
global_attractor = np.empty([1], dtype=self.fitness_dt())
321+
322+
if initial_position_distribution is None:
323+
initial_position_distribution = distributions.UniformDistribution(np.array([[ 0, 1]] * self._n_free_params));
324+
325+
if initial_velocity_distribution is None:
326+
initial_velocity_distribution = distributions.UniformDistribution(np.array([[-1, 1]] * self._n_free_params))
327+
328+
if temper_params is None:
329+
omega_v = np.random.random(n_temper_categories)
330+
phi_p = np.random.random(n_temper_categories)
331+
phi_g = np.random.random(n_temper_categories)
332+
temper_params = [np.array((params), dtype=self.temper_params_dt()) for params in zip(omega_v, phi_p, phi_g)]
333+
334+
# Distribute the particles into different temper categories
335+
temper_map = self.distribute_particles(n_pso_particles, n_temper_categories)
336+
337+
# Initial particle positions
338+
self._fitness[0]["params"] = initial_position_distribution.sample(n_pso_particles)
339+
340+
# Apply the boundary conditions if any exist
341+
if self._boundary_map is not None:
342+
self._fitness[itr]["params"] = self._boundary_map(self._fitness[itr]["params"])
343+
344+
# Calculate the initial particle fitnesses
345+
self._fitness[0]["fitness"] = self.evaluate_fitness(self._fitness[0]["params"],
346+
apply=apply)
347+
348+
# Calculate the positions of the attractors
349+
local_attractors = self._fitness[0]
350+
local_attractors, global_attractor = self.update_attractors(
351+
self._fitness[0],
352+
local_attractors,
353+
global_attractor)
354+
355+
# Initial particle velocities
356+
self._fitness[0]["velocities"] = initial_velocity_distribution.sample(n_pso_particles)
357+
358+
# Update the velocities using the temper map
359+
for idx, temper_category in enumerate(temper_map):
360+
self._fitness[0]["velocities"][temper_category] = self.update_velocities(
361+
self._fitness[0]["params"][temper_category],
362+
self._fitness[0]["velocities"][temper_category],
363+
local_attractors["params"][temper_category],
364+
global_attractor["params"],
365+
temper_params[idx]["omega_v"],
366+
temper_params[idx]["phi_p"],
367+
temper_params[idx]["phi_g"])
368+
369+
for itr in range(1, n_pso_iterations):
370+
# Update the particle positions
371+
self._fitness[itr]["params"] = self.update_positions(
372+
self._fitness[itr - 1]["params"],
373+
self._fitness[itr - 1]["velocities"])
374+
375+
# Apply the boundary conditions if any exist
376+
if self._boundary_map is not None:
377+
self._fitness[itr]["params"] = self._boundary_map(self._fitness[itr]["params"])
378+
379+
# Recalculate the fitness function
380+
self._fitness[itr]["fitness"] = self.evaluate_fitness(
381+
self._fitness[itr]["params"],
382+
apply=apply)
383+
384+
# Find the new attractors
385+
local_attractors, global_attractor = self.update_attractors(
386+
self._fitness[itr],
387+
local_attractors,
388+
global_attractor)
389+
390+
# Update the velocities
391+
for idx, temper_category in enumerate(temper_map):
392+
self._fitness[itr]["velocities"][temper_category] = self.update_velocities(
393+
self._fitness[itr]["params"][temper_category],
394+
self._fitness[itr - 1]["velocities"][temper_category],
395+
local_attractors["params"][temper_category],
396+
global_attractor["params"],
397+
temper_params[idx]["omega_v"],
398+
temper_params[idx]["phi_p"],
399+
temper_params[idx]["phi_g"])
400+
401+
# Redistribute the particles into different temper categories
402+
if itr % temper_frequency == 0:
403+
temper_map = self.distribute_particles(n_pso_particles, n_temper_categories)
404+
405+
return global_attractor
406+
407+
def temper_params_dt(self):
408+
return np.dtype([
409+
('omega_v', np.float64),
410+
('phi_p', np.float64),
411+
('phi_g', np.float64)])
412+
413+
414+
def distribute_particles(self, n_pso_particles, n_temper_categories):
415+
416+
# Distribute as many particles as evenly as possible across the categories,
417+
# This ensures that there are no empty categories
418+
n_evenly_distributable = (n_pso_particles // n_temper_categories) * n_temper_categories
419+
n_unevenly_distributable = n_pso_particles - n_evenly_distributable
420+
421+
# Generate the required indicies for the pso particles
422+
particle_indicies = range(0, n_pso_particles)
423+
424+
# Randomise the order
425+
np.random.shuffle(particle_indicies)
426+
427+
# Reshape to a 2D array indexed on the number of tempering categories
428+
particle_map = np.reshape(
429+
particle_indicies[:n_evenly_distributable],
430+
(n_temper_categories, n_evenly_distributable//n_temper_categories))
431+
432+
# Transfer to the map
433+
temper_map = {}
434+
for i, index_category in enumerate(particle_map):
435+
temper_map[i] = index_category
436+
437+
# Transfer any excess particles that could not be evenly distributed
438+
# This is a slow operation, so for the purposes of speed the number of
439+
# temper categories should be a factor of the number of pso particles
440+
if n_unevenly_distributable != 0:
441+
for i in range(n_evenly_distributable, n_pso_particles):
442+
temper_map[random.randrange(0, n_temper_categories)] = (
443+
np.append(temper_map[random.randrange(0, n_temper_categories)], [particle_indicies[i]]))
444+
445+
return temper_map
446+
165447
class PerfTestMultipleAbstractor(object):
166448
def __init__(self,
167449
param_names,

0 commit comments

Comments
 (0)