Creating Individuals

This tutorial introduces the fundamental concepts of the fitness, individual and population objects and how they relate with each other. We will explore the the usage of the creator and the base modules, which are used to create and initialize these objects.


Overview

Fitness

Fitness represents the suitability of the solution values of an individual for the given problem. Individuals with higher fitness have solutions which represent better solutions to the problem.

Fitness types are created with the creator module and require their weights attribute to be set in order to be functional. The weights attribute is a sequence of either positive or negative real numbers. Negative weights indicate minimizing fitness objectives while positive weights indicate maximizing fitness objectives. A Fitness type is single-objective, when the length of the weights tuple is equal to one, and multi-objective, when it’s greater than one. The weights can also be used to vary the importance of objectives against each other by varying the sizes of the numbers. The type and quantity of fitness weights should be chosen according to the nature of the problem to be solved.

The following examples illustrate the various ways of creating different types of Fitness. The create() function takes at least two arguments: a name for the subclass and a base class to inherit from. All subsequent arguments, if there are any, become the attributes of the new type.

Single-objective
 creator.create("FitnessMin", base.Fitness, weights=(-1.0,))  # Minimizing
 creator.create("FitnessMax", base.Fitness, weights=(1.0,))   # Maximizing
Multi-objective
 creator.create("FitnessMulti", base.Fitness, weights=(-1.0, 1.0))       # Min and max
 creator.create("FitnessVaried", base.Fitness, weights(0.5, 1.1, -1.7))  # Varied importance

Individuals

Individuals are collections of solution values which mutate and mate with each other in order to produce offsprings with altered solution values. Each individual has a fitness attribute, which represents the suitability of the solution values for the given problem. During evolution, the population of each generation is evaluated and those individuals with better fitness scores pass their solution values to the next generation.

Individuals are created by using the creator module and must inherit from mutable types of Collection. In addition to the standard list class, it is also possible to create individuals based on the array.array or numpy.ndarray classes:

creator.create("Individual", list, fitness=creator.FitnessMax)
creator.create("Individual", numpy.ndarray, fitness=creator.FitnessMax)
creator.create("Individual", array.array, typecode="i", fitness=creator.FitnessMax)

After an Individual subclass with a fitness attribute has been created, it must be registered into a Toolbox. In the following example, calling the toolbox.individual() method creates a single individual of type creator.Individual.

toolbox = base.Toolbox()
toolbox.register("attr_float", random.random)      # alias and func
toolbox.register("individual", tools.init_repeat,  # alias and func
    container=creator.Individual,                  # passed to init_repeat
    func=toolbox.attr_float,                       # passed to init_repeat
    size=10                                        # passed to init_repeat
)
ind = toolbox.individual()  # creates a single individual

Populations

Populations are collections of individuals, strategies or particles. Individuals are usually not created directly one-by-one, but in bulk by a population generator in the toolbox. Please refer to the Types of Populations section for more details on generating different types of populations.



Types of Individuals

A List of Random Floats

This type of an individual can be created by using the random.random() function. The following example creates a single individual, which is a list of 10 random floating-point numbers and has a fitness attribute of the single-objective maximizing type.

from deap_er import creator, base, tools

creator.create("FitnessMax", base.Fitness, weights=(1.0,))
creator.create("Individual", list, fitness=creator.FitnessMax)

toolbox = base.Toolbox()
toolbox.register("attr_float", random.random)
toolbox.register("individual", tools.init_repeat,
    container=creator.Individual,
    func=toolbox.attr_float,
    size=10
)
ind = toolbox.individual()

A List of Permutations

This type of an individual can be created by using the random.sample() function. The following example creates a single individual, which is a list of 10 permuted values of the integers 0 through 9 and has a fitness attribute of the single-objective minimizing type.

from deap_er import creator, base, tools

creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
creator.create("Individual", list, fitness=creator.FitnessMin)

toolbox = base.Toolbox()
toolbox.register("indices", random.sample,
    population=range(10), k=10
)
toolbox.register("individual", tools.init_iterate,
    container=creator.Individual,
    generator=toolbox.indices
)

A Tree of Expressions

This type of an individual is a prefix tree of mathematical expressions which is used to solve symbolic regression problems. The prefix tree needs a collection of all the available mathematical operators that the individual could use to solve the problem in the form of a PrimitiveSet. The following example creates a single individual of type PrimitiveTree, which can use add(), sub(), and mul() operators and has a fitness attribute of the single-objective minimizing type.

Note

Arity represents the number of arguments an operator takes.

from deap_er import creator, base, tools, gp
import operator

pset = gp.PrimitiveSet("MAIN", arity=1)
pset.add_primitive(operator.add, arity=2)
pset.add_primitive(operator.sub, arity=2)
pset.add_primitive(operator.mul, arity=2)

creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
creator.create("Individual", gp.PrimitiveTree,
    fitness=creator.FitnessMin, prim_set=pset
)
toolbox = base.Toolbox()
toolbox.register("expr", gp.gen_half_and_half,
    prim_set=pset, min_depth=1, max_depth=2
)
toolbox.register("individual", tools.init_iterate,
    container=creator.Individual,
    generator=toolbox.expr
)
ind = toolbox.individual()

Evolution Strategies

Individuals with evolution strategies are slightly different as they generally contain two lists: one for the fitness values and the other for the mutation parameters. Since there are no builtin helper functions which generate two different iterables at the same time, we must define this function ourselves. The following example creates a single individual, which has an evolution strategy and a fitness attribute of the single-objective minimizing type.

from deap_er import creator, base, tools
import random

creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
creator.create("Individual", list, fitness=creator.FitnessMin, strategy=None)
creator.create("Strategy", list)

def init_evo_strat(individual, strategy, i_size, i_min, i_max, s_min, s_max):
    ind = individual(random.uniform(i_min, i_max) for _ in range(i_size))
    ind.strategy = strategy(random.uniform(s_min, s_max) for _ in range(i_size))
    return ind

toolbox = base.Toolbox()
toolbox.register("individual", init_evo_strat,
    individual=creator.Individual,
    strategy=creator.Strategy,
    i_size=10,
    i_min=-5, i_max=5,
    s_min=-1, s_max=1
)
ind = toolbox.individual()

Moving Particles

A particle is an individual with a speed vector and can remember its best position. Particles are used for solving particle swarm optimization problems. Since there are no builtin helper functions to generate particles, we must define this function ourselves. The following example creates a single individual, which has a speed vector and a fitness attribute of the multi-objective maximizing type.

from deap_er import creator, base, tools
import random

creator.create("FitnessMax", base.Fitness, weights=(1.0, 1.0))
creator.create("Particle", list, fitness=creator.FitnessMax,
    speed=None, s_min=None, s_max=None, best=None
)
def init_particle(cr_cls, size, pos_min, pos_max, spd_min, spd_max):
    particle = cr_cls(random.uniform(pos_min, pos_max) for _ in range(size))
    particle.speed = [random.uniform(spd_min, spd_max) for _ in range(size)]
    particle.spd_min = spd_min
    particle.spd_max = spd_max
    return particle

toolbox = base.Toolbox()
toolbox.register("particle", init_particle, cr_cls=creator.Particle,
    size=2, pos_min=-6, pos_max=6, spd_min=-3, spd_max=3
)
ind = toolbox.particle()

Custom Types

If your evolution problem can’t be solved using the previously described individuals, it’s also possible to create individuals with custom behaviors depending on your requirements. The following example creates a single individual, which is a list of alternating integer and floating point numbers [int, float, int, float, …] and has a fitness attribute of the multi-objective maximizing type.

from deap_er import creator, base, tools
import random

creator.create("FitnessMax", base.Fitness, weights=(1.0, 1.0))
creator.create("Individual", list, fitness=creator.FitnessMax)

INT_MIN, INT_MAX = 5, 10
FLT_MIN, FLT_MAX = -0.2, 0.8
N_CYCLES = 4

toolbox = base.Toolbox()
toolbox.register("attr_int", random.randint, INT_MIN, INT_MAX)
toolbox.register("attr_float", random.uniform, FLT_MIN, FLT_MAX)
toolbox.register("individual", tools.init_cycle,
    container=creator.Individual,
    funcs=(toolbox.attr_int, toolbox.attr_float),
    size=N_CYCLES
)
ind = toolbox.individual()


Types of Populations

Bags

A bag is the most commonly used type of a population. It has no particular ordering and is usually implemented as a list. The population is initialized using the init_repeat() function and created by calling toolbox.population(size=num), where num is the quantity of individuals in the population.

toolbox.register("population", tools.init_repeat,  # alias and func
    container=list, func=toolbox.individual        # passed to init_repeat
)
pop = toolbox.population(size=100)  # creates a population of 100 individuals

Grids

A grid is a special case of a structured population where neighbouring individuals are associated with each other. The individuals are distributed in a grid pattern, where each cell contains a single individual. It is usually implemented as a list of rows, where each row is a list of individuals. The length of the row determines the number of columns in the grid. The individuals are accessible using two consecutive subscript operators pop[i][j] (row, column).

NUM_COLUMNS, NUM_ROWS = 50, 100

toolbox.register("row", tools.init_repeat,  # a row of columns
    container=list,
    func=toolbox.individual,
    size=NUM_COLUMNS
)
toolbox.register("population", tools.init_repeat,  # a population of rows
    container=list,
    func=toolbox.row,
    size=NUM_ROWS
)
pop = toolbox.population()  # size was already implicitly passed

Swarms

Swarm-type populations are used for solving particle swarm optimization problems. Please refer to the Moving Particles section on how to create particles for swarm-type populations. A particle swarm contains a communication network between the particles. The simplest network is a completely connected one, where each particle knows the best position that has ever been visited by any particle. This can be implemented by recording the best position and the best fitness as population attributes, as given in the following example:

creator.create("Swarm", list, g_best_pos=None, g_best_fit=creator.FitnessMax)
toolbox.register("swarm", tools.init_repeat, creator.Swarm, toolbox.particle)
pop = toolbox.swarm(size=100)    # creates a swarm of 100 particles

Demes

A deme is a sub-population that is contained inside a population. In the following example, a population of 3 demes is created, where each deme has a different number of individuals:

DEME_SIZES = [10, 50, 100]
toolbox.register("deme", tools.init_repeat, list, toolbox.individual)
population = [toolbox.deme(size=size) for size in DEME_SIZES]