Skip to content

Instantly share code, notes, and snippets.

@DMTSource
Last active January 21, 2020 10:36
Show Gist options
  • Save DMTSource/f6496998bc1030cad5d8f6c2a709b817 to your computer and use it in GitHub Desktop.
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
# 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
Copy link

d-vyd commented Nov 29, 2019

Derek, this is very helpful. Thank you.

@DMTSource
Copy link
Author

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

https://www.google.com/url?q=https%3A%2F%2Fstackoverflow.com%2Fquestions%2F682799%2Fwhat-to-do-with-the-input-line-is-too-long-error-message&sa=D&sntz=1&usg=AFQjCNGls9o6L3ZZkH3ENlbUfq2b_84gow

@DMTSource
Copy link
Author

Updated with mutRandChoice, I had forgotten the make the mutation operator relevant to the problem.

@DMTSource
Copy link
Author

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