def mutate(value, lower, upper, size): """ Mutate value in the domain [lower,upper] with mutation 'size' as a decimal percentage of the domain. WARNING: Doesn't work if the mutation size is too big and breaches both ends of the domain! """ # Define a random size for the mutation in the domain deltav = size*(upper - lower) vmax = value + deltav vmin = value - deltav if vmax > upper: # Percentage of the the total mutation size that fell in the domain bias = (upper - value)/deltav mutated = numpy.random.uniform(vmin, upper) # This compensated for the smaller region after 'value' while not (mutated >= value or flip(bias)): mutated = numpy.random.uniform(vmin, upper) elif vmin < lower: # Do the same as above but in the case where the mutation size breaches # the lower bound of the domain bias = (value - lower)/deltav mutated = numpy.random.uniform(lower, vmax) # This compensated for the smaller region after 'value' while not (mutated <= value or flip(bias)): mutated = numpy.random.uniform(lower, vmax) else: # If the mutation size doesn't breach the domain bounds, just make a # random mutation mutated = numpy.random.uniform(vmin, vmax) return mutated
def recomb_pop(population, pop_fitness, recomb, lower, upper): """ Recombines the whole population according to a fitness function and a recombination strategy. Parameters: population: list of elements in the population. Each element should be an Nx1 dimensional array like pop_fitness: list of the fitness of each element in the population recomb: function object of the recombination strategy lower: list with the lower bounds of the parameter space upper: list with the upper bounds of the parameter space """ assert len(lower) == len(upper) == len(population[0]), \ "upper and lower must have the same number of elements " + \ "as there are dimensions in the problem" assert len(pop_fitness) == len(population), \ "Need a fitness value for every element in the population." new_pop = [] pop_size = len(population) for i, element, fitness in zip(xrange(pop_size), population, pop_fitness): if flip(fitness): left_over_pop = population[0:i] + population[i+1:] left_over_fit = pop_fitness[0:i] + pop_fitness[i+1:] for mate, mate_fitness in zip(left_over_pop, left_over_fit): if flip(mate_fitness): child = recomb(element, fitness, mate, mate_fitness) new_pop.append(child) if len(new_pop) == pop_size: break if len(new_pop) == pop_size: break while len(new_pop) != pop_size: estimate = [] for l, u in zip(lower, upper): estimate.append(numpy.random.uniform(l, u)) new_pop.append(numpy.array(estimate)) return new_pop
def evolve(fitness, goal, dims, pop_size, lower, upper, prob_mutation=0.005, \ mutation_size=0.1, max_it=100): """ Evolve a population using a given fitness criterion. Parameters: fitness: function object that calculates the fitness of each element in the population. Input: list of elements representing the population. Each element is a list of size 'dims'. Output: List with the fitness of each element as a float value in the range [0:1] goal: function object that calculates the goal function due to an element of the population dims: number of dimensions in the problem pop_size: how many individuals in the population lower: list with the lower bounds of the parameter space upper: list with the upper bounds of the parameter space prob_mutation: the probability of a mutation occurring at each iteration mutation_size: percentage of the parameter space that will be allowed during the mutation max_it: maximum number of iterations Output: [best_elements, best_goals] best_elements: list with the optimum element at each iteration best_goals: list with the goal function value of each optimum element """ assert len(lower) == len(upper) == dims, \ "upper and lower must have the same number of elements " + \ "as there are dimensions in the problem" assert prob_mutation >= 0 and prob_mutation <= 1, \ "prob_mutation must be in the range [0:1]" # Begin with a random population population = [] for i in xrange(pop_size): estimate = [] for l, u in zip(lower, upper): estimate.append(numpy.random.uniform(l, u)) population.append(numpy.array(estimate)) pop_fitness = fitness(population) best_fit = max(pop_fitness) best = [population[pop_fitness.index(best_fit)]] best_goal = [goal(*best[-1])] # Evolve until reaching stagnation or max_it for iteration in xrange(max_it): population = recomb_pop(population, pop_fitness, wmean_recomb, \ lower, upper) # Mutate the unlucky ones for element in population: if flip(prob_mutation): p = numpy.random.randint(dims) element[p] = mutate(element[p], lower[p], upper[p], \ size=mutation_size) pop_fitness = fitness(population) best_fit = max(pop_fitness) best.append(population[pop_fitness.index(best_fit)]) best_goal.append(goal(*best[-1])) print "it %d:" % (iteration) print " best goal:", best_goal[-1] print " best:", best[-1] if abs((best_goal[-1] - best_goal[-2])/best_goal[-2]) <= 10**(-4): break return best, best_goal
def randwalk(func, dims, lower, upper, num_solutions, threshold=0.40): """ Optimize using a random walk. Parameters: func: function object to be optimized dims: number of arguments the function receives lower: array with lower bounds of the parameter space upper: array with upper bounds of the parameter space step_sizes: step size in each dimension maxit: maximum iterations threshold: chance of accepting an upward step (range [0:1]) Returns: [best_goal, best_estimate, goals, estimates]: best_goal: the smallest value of the goal function that was found best_estimate: point in the parameter space where 'best_goal' was found goals: list of the goal function through the iterations estimates: list of the points where 'goals' where found """ # Keep all the steps recorded estimates = [] goals = [] solution_i = 0 while solution_i < threshold*num_solutions: for j in xrange(num_solutions): tmp = numpy.zeros(dims) for i in xrange(dims): tmp[i] = numpy.random.uniform(lower[i], upper[i]) goal = func(*tmp) if solution_i == 0: survival_prob = 1 else: survival_prob = abs((max(goals) - goal)/(max(goals) - min(goals))) if flip(survival_prob): estimate = tmp if solution_i == 0 or goal < best_goal: best_goal = goal best_estimate = estimate estimates.append(estimate) goals.append(goal) solution_i += 1 if solution_i == num_solutions: break return best_goal, best_estimate, goals, numpy.array(estimates)
def simulated_annealing(func, dims, lower, upper, step_sizes, \ tstart, tfinal, tstep, it_per_t): """ Optimize using Simulated Annealing. Parameters: func: function object to be optimized dims: number of arguments the function receives lower: array with lower bounds of the parameter space upper: array with upper bounds of the parameter space step: list with the step sizes in each dimension maxit: maximum iterations threshold: chance of accepting an upward step (range [0:1]) Returns: [best_goal, best_estimate, goals, estimates]: best_goal: the smallest value of the goal function that was found best_estimate: point in the parameter space where 'best_goal' was found goals: list of the goal function through the iterations estimates: list of the points where 'goals' where found """ # Make random initial estimate estimate = [] for i in xrange(dims): estimate.append(numpy.random.uniform(lower[i], upper[i])) estimate = numpy.array(estimate) goal = func(*estimate) best_estimate = estimate best_goal = goal # Keep all the steps recorded estimates = [estimate] goals = [goal] step = numpy.empty_like(estimate) time_steps = numpy.arange(0, tfinal+1, 1, 'f') cooling_schedule = tstart*0.99**(time_steps) for temperature in cooling_schedule: accepted = 0 for iteration in xrange(it_per_t): # Make a random perturbation for i in xrange(dims): step[i] = numpy.random.uniform(-step_sizes[i], step_sizes[i]) # If the step takes the estimate out of the bounds, bring it # back. if estimate[i] + step[i] > upper[i]: step[i] = -step[i] if estimate[i] + step[i] < lower[i]: step[i] = -step[i] tmp = estimate + step goal = func(*tmp) delta_goal = goals[-1] - goal survival_prob = math.exp(delta_goal/temperature) if delta_goal > 0 or flip(survival_prob): estimate = tmp if goal < best_goal: best_goal = goal best_estimate = estimate estimates.append(estimate) goals.append(goal) accepted += 1 if accepted == 0: print "Exited due to frozen system: temp = %g" % (temperature) break return best_goal, best_estimate, goals, numpy.array(estimates)
def mutated_rw(func, lower, upper, num_agentes, prob_mutation=0.01, \ size_mutation=0.1, max_it=100, threshold=0.8): """ Random Walk with mutations. (1D) """ # Generate random agents agents = numpy.zeros(num_agentes) goals = numpy.zeros(num_agentes) for i in xrange(num_agentes): agents[i] = numpy.random.uniform(lower, upper) goals[i] = func(agents[i]) if i == 0 or best_goal > goals[i]: best_agent = agents[i] best_goal = goals[i] survivors = [] for iteration in xrange(max_it): survivors = [] # Kill all the unworthy! (according to a survival probability) for i in xrange(num_agentes): survival_prob = math.exp(-abs((goals[i] - min(goals))/min(goals))) if flip(survival_prob): survivors.append(agents[i]) if len(survivors) >= threshold*num_agentes: print "Enough survivors: %d out of %d" % (len(survivors), \ num_agentes) break # Mutate the survivors for i in xrange(len(survivors)): if flip(prob_mutation): agents[i] = mutate(survivors[i], lower, upper, size_mutation) else: agents[i] = survivors[i] goals[i] = func(agents[i]) if best_goal > goals[i]: best_agent = agents[i] best_goal = goals[i] for i in xrange(len(survivors), num_agentes): agents[i] = numpy.random.uniform(lower, upper) goals[i] = func(agents[i]) if best_goal > goals[i]: best_agent = agents[i] best_goal = goals[i] print "Needed %d iterations." % (iteration + 1) return best_agent, best_goal, agents, goals