Como se dijo anteriormente, Jenetics es una biblioteca desarrollada en java que implementa algoritmos genéticos. Para esto, desarrolla en forma de clases o interfaces de java los conceptos relativos a estos algoritmos: genes, cromosomas, genotipos, fenotipos, población y función de aptitud. Para ejecutar un algoritmo se deben instanciar, extender e implementar estas clases e interfaces.
Algunas de las principales ventajas de Jenetics son: -Soporte para procesamiento en paralelo. -Preparada para java 8.
-Libre de dependencias con otras bibliotecas.
-Soporta minimización o maximización de la función de aptitud.
En cada ejecución del algoritmo se genera un stream de evolución que está ligado a un motor de evolución que se encarga de realizar todos los pasos de la evolución para cada generación del stream. Un motor se puede utilizar para el desarrollo de múltiples evolution streams que pueden ser usados de manera segura en distintos threads. El motor de evolución se puede describir como una función sin estados y no determinística que devuelve una población de salida a partir de una población inicial.
Para entender la estructura de Jenetics, se describen las clases de dominio que son las que sirven para constituir la población del algoritmo y las clases de operación que son los selectores y alteradores que utiliza el motor de evolución para generar las poblaciones.
En la figura 4.5 se muestra el diagrama de clases de las clases de dominio. Los genes son el primer bloque del dominio, en el caso de este trabajo cada gen corresponde a un candidato a la conformación del grupo.
Los cromosomas son una interfaz intermedia que puede tener más de un gen. Sobre esta estructura se realiza el crossover, si es que existe. En la aplicación presentada, cada cromosoma representa a un grupo de personas y está compuesto por tantos genes como miembros tenga el comité. Para incorporar los candidatos al algoritmo, se usa una función proveída por Jenetics que genera cromosomas de enteros en un rango determinado. Estos enteros son índices del vector que contiene la totalidad de los candidatos y el rango irá de 0 a la cantidad total de candidatos, la invocación para generar cromosomas queda constituida de la siguiente manera: IntegerChromosome.of(0, candidatos.size(),n), donde n es la cantidad de miembros del grupo o comité buscado. En la figura 4.6 se puede ver un ejemplo de la representación utilizada para crear los cromosomas utilizando los índices del vector de candidatos.
La siguiente clase es genotype,un genotipo que representa a un individuo, es decir una solución al problema. Los objetos genotype pueden contener distintos cromosomas con distintas características. En la implementación presentada cada genotipo contiene un solo cromosoma ya que una solución esta dada por un solo comité. El genotipo es evaluado por la función de aptitud y en base a ese valor es mantenido, descartado o combinado con otros. El genotipo junto con la función de aptitud conforman el fenotipo que es la siguiente clase del dominio. Esta última clase solo consiste en un entorno para el genotipo, no cambia la estructura ni el contenido de este.
Finalmente la población es la clase que al instanciarse estará conformada por el resto de los objetos del dominio. Cada invocación al motor por parte del stream
tiene como parámetro un objeto de tipo población y devuelve otro objeto de tipo población.
Para introducir la variación genética, hay que definir en la creación del motor los alteradores y selectores que se van a utilizar. Jenetics provee un conjunto de múltiples operadores genéticos.
Los selectores son los que se encargan de elegir un grupo de individuos dentro de la población dada, devuelven un subconjunto de la población que reciben como parámetro. Pueden usarse tanto para elegir los supervivientes o la descendencia. Los tipos de selectores que brinda Jenetics son:
Tournament selector El mejor individuo de una muestra aleatoria de s
individuos es elegido de la población. Este individuo, el ganador del torneo, es el que tenga el mejor valor de aptitud de la muestra. Bajo esta configuración, el peor individuo nunca sobrevive, y el mejor individuo siempre sobrevive. La presión de la selección puede variar cambiando el tamaño de los torneos. Mientras más grande sea el valor de s, menos posibilidad de sobrevivir tienen los individuos débiles.
Truncation selector Los individuos son ordenados de acuerdo a su aptitud. Solo los n mejores individuos son seleccionados. Es un algoritmo de selección básico que tiene su fuerza en elegir rápidamente individuos en grandes poblaciones, pero no es muy usado en la práctica.
Monte Carlo selector Selecciona aleatoriamente los individuos de una población dada. Este selector puede ser usado para medir la performance de otros selectores. En general, la performance de un selector debería ser mejor que la del selector Monte Carlo.
Probability selectors Estos selectores eligen individuos de una población en base a su probabilidad de selección.
[Figura 4.7] Funcionamiento de selectores probabilísticos
La figura 4.7 muestra el funcionamiento del selector probabilístico: P(i) es la probabilidad del individuo i de ser elegido, y F la suma de estas probabilidades. Se crea un espacio de probabilidades que va de 0 a F que contiene las probabilidades ordenadas en base a i.Luego se genera un número aleatorio r/0≤r<F y según su valor en el rango de probabilidades se elige el individuo. Por ejemplo, si se tienen 3 individuos (0,1,2) para seleccionar con P(0)=0.3, P(1)=0.5 y P(2)=0.2, entonces se crea un espacio de probabilidades de la siguiente manera:
-Si 0 ≤r< 0.3 se elige el individuo 0. -Si 0 ≤.3 r < 0.8 se elige el individuo 1. -Si 0 ≤.8 r < 1 se elige el individuo 2.
A continuación se describen una serie de selectores probabilísticos.
Roulette-wheel selector También conocido como fitness proportional selector. El valor de aptitud es usado para calcular la probabilidad P( i) de selección del individuo i.
(i)
P
=
fi∑n
j=1fj
Donde fi es el valor de fitness del individuo i y n es la cantidad de individuos en la población. Seleccionar n individuos de una población dada es equivalente a jugar n veces en la ruleta. La población no tiene que ser ordenada antes de elegir los individuos.
Linear-rank selector Primero los individuos son ordenados de acuerdo a su valor de aptitud. El valor N se asigna al mejor individuo y el valor 1 es asignado al peor. La probabilidad de selección P(i) es asignada linearmente a los individuos de acuerdo a su valor.
(i)
(n
n
n
)
)
P
=
N1 −+ (
+−
− Ni−1−1es la probabilidad de que el peor individuo sea seleccionado y es la N
n−
nN+
probabilidad de que el mejor individuo sea seleccionado. Notar que todos los individuos obtienen diferente valor y, por lo tanto, diferente probabilidad, incluso si tienen el mismo valor de aptitud
Exponential-rank selector Se propone como una alternativa más fuerte al Linear-rank selector. Asigna probabilidades de supervivencia a los individuos ordenados usando una función exponencial:
(i)
c
)
P
= ( − 1
ccNi−1−1Donde 0 ≤c< 1. Un valor pequeño de c incrementa la probabilidad de que los mejores individuos sean seleccionados. Si ces cero, la probabilidad de selección del mejor individuo es uno. La probabilidad de selección de todos los demás individuos es cero. Un valor cercano a uno iguala las probabilidades de selección. Este selector ordena la población en orden descendente antes de calcular las probabilidades de selección.
Boltzmann selector La probabilidad de selección es definida como
(i)
Donde b es un parámetro que controla la intensidad de selección y Z es definido como
Z
= ∑
ni=1
e
fies el fitness del individuo i y n es el tamaño de la población. Valores
fi
positivos de b incrementan la probabilidad de selección de individuos con valores de aptitud altos. Mientras que valores negativos de b la decrementan. Si b es cero, la probabilidad de selección de todos los individuos es .1
N
Además de los selectores el otro tipo de operadores provistos por Jenetics son los alteradores, estos se encargan de introducir la variación genética modificando y recombinando los individuos de las poblaciones. Existen dos tipo de alteradores: los alteradores de mutación y los alteradores de recombinación.
En los alteradores de mutación, la probabilidad de mutación P( m) es el parámetro que debe ser optimizado. El valor óptimo para la tasa de mutación depende del rol que cumpla la mutación en la implementación del algoritmo. Si la mutación es la única fuente de exploración (si no hay entrecruzamiento) , la tasa de mutación debe tener un valor que asegure que una buena cantidad de soluciones sea explorada. La probabilidad de mutación es definida como la probabilidad de que un gen específico, sobre toda la población, sea mutado.
El mutador selecciona el gen que va a ser mutado siguiendo tres pasos: 1. Seleccionar un genotipo G[i] de la población con probabilidad
(m)
PG
2. Seleccionar un cromosoma C[j] del genotipo seleccionado G[i] con
probabilidad PC(m) .
3. Seleccionar un gen g[k] del cromosoma seleccionado C[j] con probabilidad Pg(m) .
Las probabilidades para las subselecciones mencionadas son
(m)
(m)
(m)
P
G=
P
C=
P
g=
√
3P(m)
Single-point crossover este operador cambia dos cromosomas hijos tomando dos cromosomas y cortandolos en algún punto aleatorio.
[Figura 4.8] Cruzamiento por Single-point crossover
En la figura 4.8 se muestra el funcionamiento de este operador para la implementación presentada en este trabajo. En el ejemplo, se busca un grupo de tres personas, donde punto de corte es 0 (se corta en la primer posición del genotipo). A partir de los dos grupos de personas iniciales se forma un nuevo grupo usando la primera persona del primer grupo y el resto del segundo grupo.
Si se crea un hijo y su complemento, se conserva el número total de genes en la población, previniendo cualquier desviación genética. Este tipo de entrecruzamiento es muy lento si se compara con multi-point crossover.
Multi-point crossover Si el Multi-PointCrossover es creado con un solo punto de crossover, se comporta como single-point crossover.
[Figura 4.9] Cruzamiento por Multi-point crossover
La figura 4.9 muestra un 2-point crossover, es decir una instancia de Multi-point crossover con 2 puntos de corte. En este caso los índices son 0 y 2. También para este tipo de crossover se forma el vector complementario al generado.
Partially-matched crossover Este operador es un 2-point crossover que además controla en cada combinación que los cromosomas resultantes no tengan genes repetidos. Si existe algún gen repetido, modifica el gen del cromosoma.
Ahora que ya se describieron los componentes más importantes de Jenetics para desarrollar el algoritmo genético se puede mostrar de qué manera es usada esta biblioteca en el código para crear el motor y el stream.
//Crea un motor con genes enteros y aptitud double
final Engine<IntegerGene, Double> engine = Engine
//invoca al constructor pasándole la función de aptitud previamente
definida y define cada individuo(genotipo) como n enteros, donde c/entero
representa a un candidato.
.builder(fitnessfunction, Genotype.of(IntegerChromosome.of(0, candidatos.size()-1),n))
//se define el tamaño de la población como p
.populationSize(p)
//se elige como selector de supervivencia un TournamentSelector con muestra de tamaño x
.survivorsSelector(new TournamentSelector<>(x))
//se elige como selector de descendencia un RouletteWheelSelector
.offspringSelector(new RouletteWheelSelector<>())
//se agregan dos alteradores, un Mutator con probabilidad de mutación xm y
un SinglePointCrossover con probabilidad xc
.alterers(
new Mutator<>(xm),
new SinglePointCrossover<>(xc)) .build();
Después de haber creado el motor se debe generar el stream que encontrara el mejor individuo.
//se crea el fenotipo que va a contener la solución final
final Phenotype<IntegerGene, Double> best = engine.stream()
//se va a truncar el stream después de n generaciones estables
.limit(bySteadyFitness(n))
//se define un límite de x generaciones como máximo
.limit(x)
//se actualizan las estadísticas en cada generación
.peek(statistics)
// Se reduce el stream de evolución a su mejor fenotipo
En esta invocación se ve que se debe crear previamente el objeto statics que es el que guarda las estadísticas del algoritmo. Este objeto es una instancia de la clase EvolutionStatistics proveída por Jenetics y contiene información estadística acerca de la aptitud, fenotipos inválidos y eliminados, e información en tiempo de ejecución acerca de los diferentes pasos de la evolución.