Closed NishanthARao closed 4 years ago
Right now a tree can can return just a single value. A work around would be to create a bag of two trees and optimize the output of the bag.
Right. Like you said, I have created a list of populations, and evolved it based on a single cost function. This way, just by returning a single cost (thus, a single maximization problem), all the population members in the list is evolved. Thank you, @fmder
Here is the code snippet that I have used, if it can be of any help to others!
In the following problem, I have 10 input variables, and 4 output variables. Based on a cost function, all the 4 outputs must be maximized. I have rewritten the eaSimple(.)
function for this purpose. Please change it according to your requirements.
#Define the primitives, and the required operators. I have re-written the division operator (as seen in symbreg.py example)
pset = gp.PrimitiveSet("MAIN", 10) #There are 10 inputs. Further, if each of these 10 inputs are of different types, you can use the gp.PrimitiveSetTyped(.) function
pset.addPrimitive(operator.add, 2)
pset.addPrimitive(operator.sub, 2)
pset.addPrimitive(operator.mul, 2)
pset.addPrimitive(Div, 2)
pset.addPrimitive(operator.neg, 1)
pset.addPrimitive(math.cos, 1)
pset.addPrimitive(math.sin, 1)
pset.addPrimitive(math.tanh, 1)
pset.addEphemeralConstant("rand01", lambda: random.randint(0,1))
#Create only a single maximization problem, based on which each individual is assessed.
creator.create("FitnessMax", base.Fitness, weights=(1.0,))
creator.create("Individual", gp.PrimitiveTree, fitness=creator.FitnessMax)
toolbox = base.Toolbox()
toolbox.register("expr", gp.genHalfAndHalf, pset=pset, min_=5, max_=10)
#Create 4 individuals (as there are 4 outputs), and define their populations.
toolbox.register("individual1", tools.initIterate, creator.Individual, toolbox.expr)
toolbox.register("individual2", tools.initIterate, creator.Individual, toolbox.expr)
toolbox.register("individual3", tools.initIterate, creator.Individual, toolbox.expr)
toolbox.register("individual4", tools.initIterate, creator.Individual, toolbox.expr)
toolbox.register("population1", tools.initRepeat, list, toolbox.individual1)
toolbox.register("population2", tools.initRepeat, list, toolbox.individual2)
toolbox.register("population3", tools.initRepeat, list, toolbox.individual3)
toolbox.register("population4", tools.initRepeat, list, toolbox.individual4)
toolbox.register("compile", gp.compile, pset=pset)
#Define your cost function here
def cost_function(individual):
func1 = toolbox.compile(expr=individual[0]) #f1(x)
func2 = toolbox.compile(expr=individual[1]) #f2(x)
func3 = toolbox.compile(expr=individual[2]) #f3(x)
func4 = toolbox.compile(expr=individual[3]) #f4(x)
#where x is a list containing the inputs. In the present example, this list would have 10 elements, as there are 10 inputs.
#write what you want to do with these functions, and hence calculate the required cost.
#For eg., cost = math.fsum((func1(a)* for a in x) + (func2(a)* for a in x) + (func3(a)* for a in x) + (func4(a)* for a in x))
return cost,
#Re-write the eaSimple() function to evolve the 4 individuals w.r.t to the cost returned by the python function: cost_function
def Evolve(pop, toolbox, cxpb, mutpb, ngen, stats=None, halloffame=None, verbose=__debug__):
pop1 = pop[0]
pop2 = pop[1]
pop3 = pop[2]
pop4 = pop[3]
hof1 = halloffame[0]
hof2 = halloffame[1]
hof3 = halloffame[2]
hof4 = halloffame[3]
logbook1 = tools.Logbook()
logbook2 = tools.Logbook()
logbook3 = tools.Logbook()
logbook4 = tools.Logbook()
logbook1.header = ['gen', 'nevals'] + (stats.fields if stats else [])
logbook2.header = ['gen', 'nevals'] + (stats.fields if stats else [])
logbook3.header = ['gen', 'nevals'] + (stats.fields if stats else [])
logbook4.header = ['gen', 'nevals'] + (stats.fields if stats else [])
invalid_ind1 = [ind for ind in pop1 if not ind.fitness.valid]
invalid_ind2 = [ind for ind in pop2 if not ind.fitness.valid]
invalid_ind3 = [ind for ind in pop3 if not ind.fitness.valid]
invalid_ind4 = [ind for ind in pop4 if not ind.fitness.valid]
#Here, the function cost_function takes in 4 inputs. Thus, we need to zip the 4 individuals and pass it to the cost_function, that is represented here as "toolbox.evaluate". The returned list of cost is then evaluated for each of the individuals.
fitnesses1 = toolbox.map(toolbox.evaluate, zip(invalid_ind1, invalid_ind2, invalid_ind3, invalid_ind4))
for ind, fit in zip(invalid_ind1, fitnesses1):
ind.fitness.values = fit
fitnesses2 = toolbox.map(toolbox.evaluate, zip(invalid_ind1, invalid_ind2, invalid_ind3, invalid_ind4))
for ind, fit in zip(invalid_ind2, fitnesses2):
ind.fitness.values = fit
fitnesses3 = toolbox.map(toolbox.evaluate, zip(invalid_ind1, invalid_ind2, invalid_ind3, invalid_ind4))
for ind, fit in zip(invalid_ind3, fitnesses3):
ind.fitness.values = fit
fitnesses4 = toolbox.map(toolbox.evaluate, zip(invalid_ind1, invalid_ind2, invalid_ind3, invalid_ind4))
for ind, fit in zip(invalid_ind4, fitnesses4):
ind.fitness.values = fit
hof1.update(pop1)
hof2.update(pop2)
hof3.update(pop3)
hof4.update(pop4)
record1 = stats.compile(pop1) if stats else {}
record2 = stats.compile(pop2) if stats else {}
record3 = stats.compile(pop3) if stats else {}
record4 = stats.compile(pop4) if stats else {}
logbook1.record(gen=0, nevals=len(invalid_ind1), **record1)
logbook2.record(gen=0, nevals=len(invalid_ind2), **record2)
logbook3.record(gen=0, nevals=len(invalid_ind3), **record3)
logbook4.record(gen=0, nevals=len(invalid_ind4), **record4)
if verbose:
print(logbook1.stream, logbook2.stream, logbook3.stream, logbook4.stream)
for gen in range(1, ngen + 1):
offspring1 = toolbox.select(pop1, len(pop1))
offspring2 = toolbox.select(pop2, len(pop2))
offspring3 = toolbox.select(pop3, len(pop3))
offspring4 = toolbox.select(pop4, len(pop4))
offspring1 = algorithms.varAnd(offspring1, toolbox, cxpb, mutpb)
offspring2 = algorithms.varAnd(offspring2, toolbox, cxpb, mutpb)
offspring3 = algorithms.varAnd(offspring3, toolbox, cxpb, mutpb)
offspring4 = algorithms.varAnd(offspring4, toolbox, cxpb, mutpb)
invalid_ind1 = [ind for ind in offspring1 if not ind.fitness.valid]
invalid_ind2 = [ind for ind in offspring2 if not ind.fitness.valid]
invalid_ind3 = [ind for ind in offspring3 if not ind.fitness.valid]
invalid_ind4 = [ind for ind in offspring4 if not ind.fitness.valid]
fitness1 = toolbox.map(toolbox.evaluate, zip(invalid_ind1,invalid_ind2, invalid_ind3, invalid_ind4))
for ind, fit in zip(invalid_ind1, fitness1):
ind.fitness.values = fit
fitness2 = toolbox.map(toolbox.evaluate, zip(invalid_ind1,invalid_ind2, invalid_ind3, invalid_ind4))
for ind, fit in zip(invalid_ind2, fitness2):
ind.fitness.values = fit
fitness3 = toolbox.map(toolbox.evaluate, zip(invalid_ind1,invalid_ind2, invalid_ind3, invalid_ind4))
for ind, fit in zip(invalid_ind3, fitness3):
ind.fitness.values = fit
fitness4 = toolbox.map(toolbox.evaluate, zip(invalid_ind1,invalid_ind2, invalid_ind3, invalid_ind4))
for ind, fit in zip(invalid_ind4, fitness4):
ind.fitness.values = fit
hof1.update(offspring1)
hof2.update(offspring2)
hof3.update(offspring3)
hof4.update(offspring4)
pop1[:] = offspring1
pop2[:] = offspring2
pop3[:] = offspring3
pop4[:] = offspring4
record1 = stats.compile(pop1) if stats else {}
record2 = stats.compile(pop2) if stats else {}
record3 = stats.compile(pop3) if stats else {}
record4 = stats.compile(pop4) if stats else {}
logbook1.record(gen=gen, nevals=len(invalid_ind1), **record1)
logbook2.record(gen=gen, nevals=len(invalid_ind2), **record2)
logbook3.record(gen=gen, nevals=len(invalid_ind3), **record3)
logbook4.record(gen=gen, nevals=len(invalid_ind4), **record4)
if verbose:
print(logbook1.stream, logbook2.stream, logbook3.stream, logbook4.stream)
pop = [pop1, pop2, pop3, pop4]
log = [logbook1, logbook2, logbook3, logbook4]
return pop, log
#Define the parameters for the genetic programming.
toolbox.register("evaluate", cost_function) #Thus, the function cost_function is now represented by "toolbox.evaluate"
toolbox.register("select", tools.selTournament, tournsize=7)
toolbox.register("mate", gp.cxOnePoint)
toolbox.register("expr_mut", gp.genFull, min_=3, max_=5)
toolbox.register("mutate", gp.mutUniform, expr=toolbox.expr_mut, pset=pset)
toolbox.decorate("mate", gp.staticLimit(key=operator.attrgetter("height"), max_value=17))
toolbox.decorate("mutate", gp.staticLimit(key=operator.attrgetter("height"), max_value=17))
pop1 = toolbox.population1(n=300)
pop2 = toolbox.population2(n=300)
pop3 = toolbox.population1(n=300)
pop4 = toolbox.population2(n=300)
hof1 = tools.HallOfFame(1)
hof2 = tools.HallOfFame(1)
hof3 = tools.HallOfFame(1)
hof4 = tools.HallOfFame(1)
#Simply call the Evolve() function as follows
pop, log = Evolve([pop1, pop2, pop3, pop4], toolbox, 0.5, 0.4, generations, halloffame=[hof1, hof2, hof3, hof4], verbose=True)
#Note that pop is now a list, containing the 4 populations i.e., pop = [pop1, pop2, pop3, pop4]. Same is the case with log.
Hello there,
I actually have a similar need, since my cost function is dependent on 4 different functions (each of them with a different output), that I need to evolve.
Now, I actually worked out a very similar solution as yours, but I do not really get why you do evaluate the fitness 4 time in each cycle:
fitness1 = toolbox.map(toolbox.evaluate, zip(invalid_ind1,invalid_ind2, invalid_ind3, invalid_ind4))
for ind, fit in zip(invalid_ind1, fitness1):
ind.fitness.values = fit
fitness2 = toolbox.map(toolbox.evaluate, zip(invalid_ind1,invalid_ind2, invalid_ind3, invalid_ind4))
for ind, fit in zip(invalid_ind2, fitness2):
ind.fitness.values = fit
fitness3 = toolbox.map(toolbox.evaluate, zip(invalid_ind1,invalid_ind2, invalid_ind3, invalid_ind4))
for ind, fit in zip(invalid_ind3, fitness3):
ind.fitness.values = fit
fitness4 = toolbox.map(toolbox.evaluate, zip(invalid_ind1,invalid_ind2, invalid_ind3, invalid_ind4))
for ind, fit in zip(invalid_ind4, fitness4):
ind.fitness.values = fit
Isn't it sufficient to evaluate just once and then assign the fitness of the given combination to the 4 individuals being evaluated? Something like:
fitness = toolbox.map(toolbox.evaluate, zip(invalid_ind1,invalid_ind2, invalid_ind3, invalid_ind4))
for ind, fit in zip(invalid_ind1, fitness):
ind.fitness.values = fit
for ind, fit in zip(invalid_ind2, fitness):
ind.fitness.values = fit
for ind, fit in zip(invalid_ind3, fitness):
ind.fitness.values = fit
for ind, fit in zip(invalid_ind4, fitness):
ind.fitness.values = fit
Many thanks for your feedback!
Implementation of multiple-output genetic programming tree with Modi nodes [Zhang, Yun & Zhang, Mengjie. (2005). A multiple-output program tree structure in genetic programming. 6-10.]: https://github.com/DEAP/deap/pull/678
Example with 2 inputs, 2 outputs:
import numpy
import random
from deap import gp, creator
num_outputs = 2
pset = gp.PrimitiveSet("MAIN", 1)
pset.addPrimitive(numpy.add, 2, name="vadd")
pset.addPrimitive(numpy.multiply, 2, name="vmul")
pset.addEphemeralConstant("rand101", lambda: random.randint(-1,1))
for i in range(num_outputs):
modi_i = gp.Modi(i)
pset.addPrimitive(modi_i, 1, name=str(modi_i))
creator.create("ModiTree", gp.MultiOutputTree, num_outputs=2)
tree = creator.ModiTree(gp.genHalfAndHalf(pset=pset, min_=3, max_=3))
print(f"tree: {tree}")
print(f"eval: {gp.compile(tree, pset=pset)(1)}")
Output:
tree: [modi0(modi1(ARG0)),modi1(ARG0)+modi1(modi0(modi1(ARG0)))]
eval: [1, 2]
I have a 6-input 2-output problem. Basically, I need to find a function that would map the 6-inputs to the 2-outputs, hence minimizing the error. (Just like symbolic regression, but with multiple output). Here are the following changes that I could do so far:
Now, I need to use the
gp.compile
tool to create a functionfunc
that would return two values of the cost function.Could you please guide me on how to do this? I saw several examples on multi-objectives, but none of them had the symbolic function involved.
Thank you in advance.