pythongenetic-algorithmdeap

DEAP: make mutation probability depend on generation number


I am using a genetic algorithm implemented with the DEAP library for Python. In order to avoid premature convergence, and to force exploration of the feature space, I would like the mutation probability to be high during the first generations. But to prevent drifting away from extremum once they are identified, I would like the mutation probability to be lower in the last generations. How do I get the mutation probability to decrease over the generations? Is there any built-in function in DEAP to get this done?

When I register a mutation function, for instance

toolbox.register('mutate', tools.mutPolynomialBounded, eta=.6, low=[0,0], up=[1,1], indpb=0.1)

the indpb parameter is a float. How can I make it a function of something else?


Solution

  • Sounds like a job for Callbackproxy which evaluates function arguments each time they are called. I added a simple example where I modified the official DEAP n-queen example such that the mutation rate is set to 2/N_GENS (arbitrary choice just to make the point).

    Notice that Callbackproxy receives a lambda, so you have to pass the mutation rate argument as a function (either using a fully blown function or just a lambda). The result anyway is that each time the indpb parameter is evaluated this lambda will be called, and if the lambda contains reference to a global variable generation counter, you got what you want.

    #    This file is part of DEAP.
    #
    #    DEAP is free software: you can redistribute it and/or modify
    #    it under the terms of the GNU Lesser General Public License as
    #    published by the Free Software Foundation, either version 3 of
    #    the License, or (at your option) any later version.
    #
    #    DEAP is distributed in the hope that it will be useful,
    #    but WITHOUT ANY WARRANTY; without even the implied warranty of
    #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    #    GNU Lesser General Public License for more details.
    #
    #    You should have received a copy of the GNU Lesser General Public
    #    License along with DEAP. If not, see <http://www.gnu.org/licenses/>.
    
    import random
    from objproxies import CallbackProxy
    import numpy
    
    from deap import algorithms
    from deap import base
    from deap import creator
    from deap import tools
    
    # Problem parameter
    NB_QUEENS = 20
    N_EVALS = 0
    N_GENS = 1
    
    def evalNQueens(individual):
        global N_EVALS, N_GENS
        """Evaluation function for the n-queens problem.
        The problem is to determine a configuration of n queens
        on a nxn chessboard such that no queen can be taken by
        one another. In this version, each queens is assigned
        to one column, and only one queen can be on each line.
        The evaluation function therefore only counts the number
        of conflicts along the diagonals.
        """
        size = len(individual)
        # Count the number of conflicts with other queens.
        # The conflicts can only be diagonal, count on each diagonal line
        left_diagonal = [0] * (2 * size - 1)
        right_diagonal = [0] * (2 * size - 1)
    
        # Sum the number of queens on each diagonal:
        for i in range(size):
            left_diagonal[i + individual[i]] += 1
            right_diagonal[size - 1 - i + individual[i]] += 1
    
        # Count the number of conflicts on each diagonal
        sum_ = 0
        for i in range(2 * size - 1):
            if left_diagonal[i] > 1:
                sum_ += left_diagonal[i] - 1
            if right_diagonal[i] > 1:
                sum_ += right_diagonal[i] - 1
    
        N_EVALS += 1
        if N_EVALS % 300 == 0:
            N_GENS += 1
        return sum_,
    
    
    creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
    creator.create("Individual", list, fitness=creator.FitnessMin)
    
    # Since there is only one queen per line,
    # individual are represented by a permutation
    toolbox = base.Toolbox()
    toolbox.register("permutation", random.sample, range(NB_QUEENS), NB_QUEENS)
    
    # Structure initializers
    # An individual is a list that represents the position of each queen.
    # Only the line is stored, the column is the index of the number in the list.
    toolbox.register("individual", tools.initIterate, creator.Individual, toolbox.permutation)
    toolbox.register("population", tools.initRepeat, list, toolbox.individual)
    
    toolbox.register("evaluate", evalNQueens)
    toolbox.register("mate", tools.cxPartialyMatched)
    toolbox.register("mutate", tools.mutShuffleIndexes, indpb=CallbackProxy(lambda: 2.0 / N_GENS))
    toolbox.register("select", tools.selTournament, tournsize=3)
    
    
    
    
    
    def main(seed=0):
        random.seed(seed)
    
        pop = toolbox.population(n=300)
        hof = tools.HallOfFame(1)
        stats = tools.Statistics(lambda ind: ind.fitness.values)
        stats.register("Avg", numpy.mean)
        stats.register("Std", numpy.std)
        stats.register("Min", numpy.min)
        stats.register("Max", numpy.max)
    
        algorithms.eaSimple(pop, toolbox, cxpb=0.5, mutpb=1, ngen=100, stats=stats,
                            halloffame=hof, verbose=True)
    
        return pop, stats, hof
    
    
    if __name__ == "__main__":
        main()