Last active
January 21, 2020 10:36
-
-
Save DMTSource/f6496998bc1030cad5d8f6c2a709b817 to your computer and use it in GitHub Desktop.
GA example of an array of constrained value items with crossover and mutation, from question https://groups.google.com/forum/#!topic/deap-users/bu3v9Ozez8E
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# GA example of an array of constrained value items with crossover and mutation, | |
# from question https://groups.google.com/forum/#!topic/deap-users/bu3v9Ozez8E | |
# Derek M Tishler - 2019 | |
# Started with example and modified to keep as simple as possible: | |
# https://raw.githubusercontent.com/DEAP/deap/454b4f65a9c944ea2c90b38a75d384cddf524220/examples/ga/onemax_np.py | |
import random | |
import numpy as np | |
from deap import algorithms | |
from deap import base | |
from deap import creator | |
from deap import tools | |
# from example linked above, operator for np array | |
def cxTwoPointCopy(ind1, ind2): | |
size = len(ind1) | |
cxpoint1 = random.randint(1, size) | |
cxpoint2 = random.randint(1, size - 1) | |
if cxpoint2 >= cxpoint1: | |
cxpoint2 += 1 | |
else: # Swap the two cx points | |
cxpoint1, cxpoint2 = cxpoint2, cxpoint1 | |
ind1[cxpoint1:cxpoint2], ind2[cxpoint1:cxpoint2] \ | |
= ind2[cxpoint1:cxpoint2].copy(), ind1[cxpoint1:cxpoint2].copy() | |
return ind1, ind2 | |
# | |
# Using the previously used mutFlipBit as a template, apply the random choice from init but for replacement | |
def mutRandChoice(individual, indpb, possible_values, p=None): | |
for i in xrange(len(individual)): | |
if random.random() < indpb: | |
# copy and create list without current value, otherwise we risk replacing with current value and wasting time | |
unique_vals = possible_values[np.where(possible_values != individual[i])] | |
unique_val_ps = p | |
if unique_val_ps is not None: | |
unique_val_ps = p[np.where(possible_values != individual[i])] | |
unique_val_ps /= np.sum(unique_val_ps) # have to renorm or else choise throws error, ValueError: probabilities do not sum to 1 | |
individual[i] = np.random.choice(unique_vals, 1, p=unique_val_ps)[0] | |
return individual, | |
# evaluate the individual once per generation | |
def evaluate(individual): | |
''' | |
#keys = sample(range(1093+1), 390) # select a subset of parameters to assign values to | |
# # if there are too many parameters for commandline | |
# # (390 is safe for 1 decimal point accuracy e.g. 0.1) | |
keys = list(individual) | |
params = {} | |
param_string = "" | |
for p in keys: | |
if p==116: | |
value = 0.80 # keep this as is | |
elif p==117: | |
value = 0.80 # keep this as is | |
else: | |
value = round(random(),1) | |
params[p] = value | |
param_string += "--parameter {},{} ".format(p,value) | |
out_textpath = out_dir+out_basename+"_"+str(n).zfill(3)+".txt" | |
out_soundpath = out_dir+out_basename+"_"+str(n).zfill(3)+".wav" | |
# save parameter file for later use | |
f = open(out_textpath,"w") | |
f.write(str(params)) | |
f.close() | |
# create soundfile for comparison/fitness testing | |
cmd = '{} -s 96000 -p "{}" {} -m {} -o {}'.format(command_path,plugin_path,param_string,midi_path,out_soundpath) | |
os.system(cmd) | |
# normalize soundfile for better comparison | |
original_sound = AudioSegment.from_file(out_soundpath, "wav") | |
normalized_sound = effects.normalize(original_sound) | |
normalized_sound.export(out_soundpath, format="wav") | |
# comparison/fitness code here | |
''' | |
# creating fake fitness values so we can illustrate storing in the object and returning a fitness value | |
fit_1 = np.prod(individual) | |
fit_2 = np.sum(individual) | |
fit_3 = np.std(individual) | |
# can store to the object for use later if you dont have multi objective or just wnat to store anything inside the ind object | |
individual.fit_1 = fit_1 | |
individual.fit_2 = fit_2 | |
individual.fit_3 = fit_3 | |
combined_fitness = fit_1 + fit_2 + fit_3 | |
return (combined_fitness,) | |
#return (fit_1,fit_2,fit_3) # can also consider a multi objective opt...can be tricky | |
possible_values = np.array([0.0, 0.1 ,0.5 ,0.9 ,1.0]) | |
p = None #np.array([0.05, 0.25, 0.4, 0.25, 0.05]) # you can set the probabilities per value if you need/like/can. must sum to 1. | |
creator.create("FitnessMax", base.Fitness, weights=(1.0,)) | |
creator.create("Individual", np.ndarray, fitness=creator.FitnessMax) | |
toolbox = base.Toolbox() | |
# 1k long list of floats from list of values(can add probabilities see docs) | |
# https://docs.scipy.org/doc/numpy-1.15.0/reference/generated/numpy.random.choice.html | |
toolbox.register("expr", np.random.choice, possible_values, 1000) | |
toolbox.register("individual", tools.initIterate, creator.Individual, toolbox.expr) | |
toolbox.register("population", tools.initRepeat, list, toolbox.individual) | |
toolbox.register("evaluate", evaluate) | |
toolbox.register("mate", cxTwoPointCopy) | |
toolbox.register("mutate", mutRandChoice, indpb=0.05, possible_values=possible_values, p=p) | |
toolbox.register("select", tools.selTournament, tournsize=3) | |
def main(): | |
random.seed(64) | |
pop = toolbox.population(n=100) | |
hof = tools.HallOfFame(1, similar=np.array_equal) | |
stats = tools.Statistics(lambda ind: ind.fitness.values) | |
stats.register("avg", np.mean) | |
stats.register("min", np.min) | |
stats.register("max", np.max) | |
stats.register("std", np.std) | |
algorithms.eaSimple(pop, toolbox, cxpb=0.5, mutpb=0.2, ngen=50, stats=stats, | |
halloffame=hof) | |
print("Best Individual:\n%s"%str(hof[0])) | |
return pop, stats, hof | |
if __name__ == "__main__": | |
main() |
@d-vyd Updated with your example code to help show where to run each individual's evaluation process.
If you can make it run your program using the supplied floats(see below url for fix on windows) and return a fit value then this will help you optimize right away.
Updated with mutRandChoice, I had forgotten the make the mutation operator relevant to the problem.
Since the possible value list is small, mutation also now ensures that it is not replacing value with same value via: unique_vals
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Derek, this is very helpful. Thank you.