|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import math
|
|
|
import numpy as np
|
|
|
import multiprocessing
|
|
|
from pathlib import Path
|
|
|
import sys
|
|
|
import random
|
|
|
from collections import defaultdict
|
|
|
from sklearn.neighbors import KDTree
|
|
|
from scipy.spatial import distance
|
|
|
|
|
|
from map_elites import common as cm
|
|
|
|
|
|
|
|
|
def add_to_archive(s, archive):
|
|
|
centroid = cm.make_hashable(s.centroid)
|
|
|
if centroid in archive:
|
|
|
if s.fitness > archive[centroid].fitness:
|
|
|
archive[centroid] = s
|
|
|
return 1
|
|
|
return 0
|
|
|
else:
|
|
|
archive[centroid] = s
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
|
|
def __evaluate(t):
|
|
|
z, f, task, centroid, _ = t
|
|
|
fit = f(z, task)
|
|
|
return cm.Species(z, task, fit, centroid)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def bandit(successes, n_niches):
|
|
|
n = 0
|
|
|
for v in successes.values():
|
|
|
n += len(v)
|
|
|
v = [1, 10, 50, 100, 500]
|
|
|
if len(successes.keys()) < len(v):
|
|
|
return random.choice(v)
|
|
|
ucb = []
|
|
|
for k in v:
|
|
|
x = [i[0] for i in successes[k]]
|
|
|
mean = sum(x) / float(len(x))
|
|
|
n_a = len(x)
|
|
|
ucb += [mean + math.sqrt(2 * math.log(n) / n_a)]
|
|
|
a = np.argmax(ucb)
|
|
|
t_size = v[a]
|
|
|
return t_size
|
|
|
|
|
|
|
|
|
def select_niche(x, z, f, centroids, tasks, t_size, params, use_distance=False):
|
|
|
to_evaluate = []
|
|
|
if not use_distance:
|
|
|
|
|
|
niche = np.random.randint(len(tasks))
|
|
|
to_evaluate += [(z, f, tasks[niche], centroids[niche, :], params)]
|
|
|
else:
|
|
|
|
|
|
|
|
|
|
|
|
niches_centroids = []
|
|
|
niches_tasks = []
|
|
|
rand = np.random.randint(centroids.shape[0], size=t_size)
|
|
|
for p in range(0, t_size):
|
|
|
n = rand[p]
|
|
|
niches_centroids += [centroids[n, :]]
|
|
|
niches_tasks += [tasks[n]]
|
|
|
cd = distance.cdist(niches_centroids, [x.centroid], 'euclidean')
|
|
|
cd_min = np.argmin(cd)
|
|
|
to_evaluate += [(z, f, niches_tasks[cd_min], niches_centroids[cd_min], params)]
|
|
|
return to_evaluate
|
|
|
|
|
|
|
|
|
def compute(dim_map=-1,
|
|
|
dim_x=-1,
|
|
|
f=None,
|
|
|
max_evals=1e5,
|
|
|
centroids=[],
|
|
|
tasks=[],
|
|
|
variation_operator=cm.variation,
|
|
|
params=cm.default_params,
|
|
|
log_file=None):
|
|
|
"""Multi-task MAP-Elites
|
|
|
- if there is no centroid : random assignation of niches
|
|
|
- if there is no task: use the centroids as tasks
|
|
|
- if there is a centroid list: use the centroids to compute distances
|
|
|
when using the distance, use the bandit to select the tournament size (cf paper):
|
|
|
|
|
|
Format of the logfile: evals archive_size max mean 5%_percentile, 95%_percentile
|
|
|
|
|
|
Reference:
|
|
|
Mouret and Maguire (2020). Quality Diversity for Multitask Optimization
|
|
|
Proceedings of ACM GECCO.
|
|
|
"""
|
|
|
print(params)
|
|
|
assert(f != None)
|
|
|
assert(dim_x != -1)
|
|
|
|
|
|
use_distance = False
|
|
|
if tasks != [] and centroids != []:
|
|
|
use_distance = True
|
|
|
elif tasks == [] and centroids != []:
|
|
|
|
|
|
tasks = centroids
|
|
|
use_distance = True
|
|
|
elif tasks != [] and centroids == []:
|
|
|
|
|
|
centroids = np.arange(0, len(tasks)).reshape(len(tasks), 1)
|
|
|
use_distance = False
|
|
|
else:
|
|
|
raise ValueError('Multi-task MAP-Elites: you need to specify a list of task, a list of centroids, or both')
|
|
|
print("Multitask-MAP-Elites:: using distance =>", use_distance)
|
|
|
|
|
|
assert(len(tasks) == len(centroids))
|
|
|
n_tasks = len(tasks)
|
|
|
|
|
|
|
|
|
archive = {}
|
|
|
|
|
|
init_count = 0
|
|
|
|
|
|
|
|
|
num_cores = multiprocessing.cpu_count()
|
|
|
pool = multiprocessing.Pool(num_cores)
|
|
|
|
|
|
|
|
|
n_evals = 0
|
|
|
b_evals = 0
|
|
|
t_size = 1
|
|
|
successes = defaultdict(list)
|
|
|
while (n_evals < max_evals):
|
|
|
to_evaluate = []
|
|
|
to_evaluate_centroid = []
|
|
|
if len(archive) <= params['random_init'] * n_tasks:
|
|
|
|
|
|
for i in range(0, params['random_init_batch']):
|
|
|
|
|
|
x = np.random.uniform(low=params['min'], high=params['max'], size=dim_x)
|
|
|
|
|
|
n = np.random.randint(0, n_tasks)
|
|
|
to_evaluate += [(x, f, tasks[n], centroids[n], params)]
|
|
|
s_list = cm.parallel_eval(__evaluate, to_evaluate, pool, params)
|
|
|
n_evals += len(to_evaluate)
|
|
|
b_evals += len(to_evaluate)
|
|
|
for i in range(0, len(list(s_list))):
|
|
|
add_to_archive(s_list[i], archive)
|
|
|
else:
|
|
|
|
|
|
keys = list(archive.keys())
|
|
|
|
|
|
rand1 = np.random.randint(len(keys), size=params['batch_size'])
|
|
|
rand2 = np.random.randint(len(keys), size=params['batch_size'])
|
|
|
for n in range(0, params['batch_size']):
|
|
|
|
|
|
x = archive[keys[rand1[n]]]
|
|
|
y = archive[keys[rand2[n]]]
|
|
|
|
|
|
z = variation_operator(x.x, y.x, params)
|
|
|
|
|
|
to_evaluate += select_niche(x, z, f, centroids, tasks, t_size, params, use_distance)
|
|
|
|
|
|
s_list = cm.parallel_eval(__evaluate, to_evaluate, pool, params)
|
|
|
n_evals += len(to_evaluate)
|
|
|
b_evals += len(to_evaluate)
|
|
|
|
|
|
suc = 0
|
|
|
for i in range(0, len(list(s_list))):
|
|
|
suc += add_to_archive(s_list[i], archive)
|
|
|
if use_distance:
|
|
|
successes[t_size] += [(suc, n_evals)]
|
|
|
if use_distance:
|
|
|
t_size = bandit(successes, n_tasks)
|
|
|
|
|
|
|
|
|
if params['dump_period'] != -1 and b_evals > params['dump_period']:
|
|
|
cm.__save_archive(archive, n_evals)
|
|
|
b_evals = 0
|
|
|
n_e = [len(v) for v in successes.values()]
|
|
|
print(n_evals, n_e)
|
|
|
np.savetxt('t_size.dat', np.array(n_e))
|
|
|
if log_file != None:
|
|
|
fit_list = np.array([x.fitness for x in archive.values()])
|
|
|
log_file.write("{} {} {} {} {} {} {}\n".format(n_evals, len(archive.keys()), fit_list.max(), np.mean(fit_list), np.median(fit_list), np.percentile(fit_list, 5), np.percentile(fit_list, 95)))
|
|
|
log_file.flush()
|
|
|
cm.__save_archive(archive, n_evals)
|
|
|
return archive
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
def rastrigin(xx):
|
|
|
x = xx * 10.0 - 5.0
|
|
|
f = 10 * x.shape[0]
|
|
|
for i in range(0, x.shape[0]):
|
|
|
f += x[i] * x[i] - 10 * math.cos(2 * math.pi * x[i])
|
|
|
return -f, np.array([xx[0], xx[1]])
|
|
|
|
|
|
my_map = compute(dim_map=2, dim_x = 10, n_niches=1500, f=rastrigin)
|
|
|
|