################################################################################
# CÓDIGO OPTIMIZACIÓN CON ALGORITMO GENÉTICO #
# #
# This work by Joaquín Amat Rodrigo is licensed under a Creative Commons #
# Attribution 4.0 International License. #
################################################################################
# coding=utf-8
################################################################################
# CLASE POBLACIÓN #
################################################################################
[docs]class Poblacion:
"""
Esta clase crea una población de n individuos.
Parameters
----------
n_individuos :`int`
número de individuos de la población.
n_variables : `int`
número de variables que definen a cada individuo.
limites_inf : `list` or `numpy.ndarray`
límite inferior de cada variable. Si solo se quiere predefinir límites
de alguna variable, emplear ``None``. Los ``None`` serán remplazados por
el valor (-10**3).
limites_sup : `list` or `numpy.ndarray`
límite superior de cada variable. Si solo se quiere predefinir límites
de alguna variable, emplear ``None``. Los``None`` serán remplazados por
el valor (+10**3).
verbose : `bool`, optional
mostrar información del proceso por pantalla. (default ``False``)
Attributes
----------
individuos : `list`
lista con todos los individuos de la población en su estado actual.
n_individuos :`int`
número de individuos de la población.
n_variables : `int`
número de variables que definen a cada individuo.
limites_inf : `list` or `numpy.ndarray`
límite inferior de cada variable.
limites_sup : `list` or `numpy.ndarray`
límite superior de cada variable.
mejor_individuo : `object individuo`
mejor individuo de la población en su estado actual.
mejor_fitness : `float`
fitness del mejor individuo de la población en su estado actual.
mejor_valor_funcion : `float`
valor de la función objetivo del mejor individuo de la población en su
estado actual.
mejor_individuo_variables : `numpy.ndarray`
valor de las variables del mejor individuo de la población en su estado
actual.
historico_individuos : `list`
lista con la información de todos los individuos en cada una de las
generaciones que ha tenido la población.
historico_mejor_individuo_variables : `list`
lista con valor de las variables del mejor individuo en cada una de las
generaciones que ha tenido la población.
historico_mejor_fitness : `list`
lista con el mejor fitness en cada una de las generaciones que ha tenido
la población.
historico_mejor_valor_funcion : `list`
lista con valor de la función objetivo del mejor individuo en cada una
de las generaciones que ha tenido la población.
diferencia_abs : `list`
diferencia absoluta entre el mejor fitness de generaciones consecutivas.
resultados_df : `pandas.core.frame.DataFrame`
dataframe con la información del mejor fitness y valor de las variables
encontrado en cada generación, así como la diferencia respecto a la
generación anterior.
fitness_optimo : `float`
mejor fitness encontrado tras el proceso de optimización.
valor_funcion_optimo : `float`
valor de la función objetivo encontrado tras el proceso de optimización.
valor_variables_optimo : `numpy.narray`
valor de las variables con el que se ha conseguido el mejor fitness tras
el proceso de optimización.
optimizado : `bool`
si la población ha sido optimizada.
iter_optimizacion : `int`
número de iteraciones de optimización (generaciones).
Examples
--------
Ejemplo crear población
>>> poblacion = Poblacion(
n_individuos = 3,
n_variables = 3,
limites_inf = [-5,-5,-5],
limites_sup = [5,5,5],
verbose = True
)
"""
def __init__(self, n_individuos, n_variables, limites_inf=None,
limites_sup=None, verbose=False):
# Número de individuos de la población
self.n_individuos = n_individuos
# Número de variables de cada individuo
self.n_variables = n_variables
# Límite inferior de cada variable
self.limites_inf = limites_inf
# Límite superior de cada variable
self.limites_sup = limites_sup
# Lista de los individuos de la población
self.individuos = []
# Etiqueta para saber si la población ha sido optimizada
self.optimizado = False
# Número de iteraciones de optimización llevadas a cabo
self.iter_optimizacion = None
# Mejor individuo de la población
self.mejor_individuo = None
# Fitness del mejor individuo de la población (el de mayor fitness)
self.mejor_fitness = None
# Valor de la función objetivo del mejor individuo de la población
self.mejor_valor_funcion = None
# Valor de las variables del mejor individuo de la población
self.mejor_valor_variables = None
# Información de todas los individuos de la población en cada generación
self.historico_individuos = []
# Valor de las variables del mejor individuo en cada generación
self.historico_mejor_valor_variables = []
# Fitness del mejor individuo en cada generación
self.historico_mejor_fitness = []
# Valor de la función objetivo del mejor individuo en cada generación
self.historico_mejor_valor_funcion = []
# Diferencia absoluta entre el mejor fitness de generaciones consecutivas
self.diferencia_abs = []
# data.frame con la información del mejor fitness y valor de variables
# encontrado en cada generación, así como la diferencia respecto a la
# generación anterior.
self.resultados_df = None
# Fitness del mejor individuo de todas las generaciones
self.fitness_optimo = None
# Valor de las variables del mejor individuo de todas las generaciones
self.valor_variables_optimo = None
# Valor de función objetivo del mejor individuo de todas las generaciones
self.valor_funcion_optimo = None
# CONVERSIONES DE TIPO INICIALES
# ----------------------------------------------------------------------
# Si limites_inf o limites_sup no son un array numpy, se convierten en
# ello.
if self.limites_inf is not None \
and not isinstance(self.limites_inf,np.ndarray):
self.limites_inf = np.array(self.limites_inf)
if self.limites_sup is not None \
and not isinstance(self.limites_sup,np.ndarray):
self.limites_sup = np.array(self.limites_sup)
# SE CREAN LOS INDIVIDUOS DE LA POBLACIÓN Y SE ALMACENAN
# ----------------------------------------------------------------------
for i in np.arange(n_individuos):
individuo_i = Individuo(
n_variables = self.n_variables,
limites_inf = self.limites_inf,
limites_sup = self.limites_sup,
verbose = verbose
)
self.individuos.append(individuo_i)
# INFORMACIÓN DEL PROCESO (VERBOSE)
# ----------------------------------------------------------------------
if verbose:
print("----------------")
print("Población creada")
print("----------------")
print("Número de individuos: " + str(self.n_individuos))
print("Límites inferiores de cada variable: " \
+ np.array2string(self.limites_inf))
print("Límites superiores de cada variable: " \
+ np.array2string(self.limites_sup))
print("")
def __repr__(self):
"""
Información que se muestra cuando se imprime un objeto población.
"""
texto = "============================" \
+ "\n" \
+ " Población" \
+ "\n" \
+ "============================" \
+ "\n" \
+ "Número de individuos: " + str(self.n_individuos) \
+ "\n" \
+ "Límites inferiores de cada variable: " + str(self.limites_inf) \
+ "\n" \
+ "Límites superiores de cada variable: " + str(self.limites_sup) \
+ "\n" \
+ "Optimizado: " + str(self.optimizado) \
+ "\n" \
+ "Iteraciones optimización (generaciones): " \
+ str(self.iter_optimizacion) \
+ "\n" \
+ "\n" \
+ "Información del mejor individuo:" \
+ "\n" \
+ "----------------------------" \
+ "\n" \
+ "Valor variables: " + str(self.mejor_valor_variables) \
+ "\n" \
+ "Fitness: " + str(self.mejor_fitness) \
+ "\n" \
+ "\n" \
+ "Resultados tras optimizar:" \
+ "\n" \
+ "--------------------------" \
+ "\n" \
+ "Valor óptimo de variables: " + str(self.valor_variables_optimo) \
+ "\n" \
+ "Valor óptimo función objetivo: " + str(self.valor_funcion_optimo) \
+ "\n" \
+ "Fitness óptimo: " + str(self.fitness_optimo)
return(texto)
[docs] def mostrar_individuos(self, n=None):
"""
Este método muestra la información de cada uno de los n primeros
individuos de la población.
Parameters
----------
n : `int`
número de individuos que se muestran. Si no se indica el valor
(por defecto ``None``), se muestran todas. Si el valor es mayor
que `self.n_individuos` se muestran todas.
Examples
--------
>>> poblacion = Poblacion(
n_individuos = 5,
n_variables = 3,
limites_inf = [-5,-5,-5],
limites_sup = [5,5,5],
verbose = True
)
>>> poblacion.mostrar_individuos(n = 1)
"""
if n is None:
n = self.n_individuos
elif n > self.n_individuos:
n = self.n_individuos
for i in np.arange(n):
print(self.individuos[i])
return(None)
[docs] def evaluar_poblacion(self, funcion_objetivo, optimizacion, verbose=False):
"""
Este método calcula el fitness de todos los individuos de la población,
actualiza sus valores e identifica el mejor.
Parameters
----------
funcion_objetivo : `function`
función que se quiere optimizar.
optimizacion : {"maximizar" o "minimizar"}
si se desea maximizar o minimizar la función.
verbose : `bool`, optional
mostrar información del proceso por pantalla. (default ``False``)
Examples
--------
Ejemplo evaluar población
>>> poblacion = Poblacion(
n_individuos = 5,
n_variables = 3,
limites_inf = [-5,-5,-5],
limites_sup = [5,5,5],
verbose = True
)
>>> def funcion_objetivo(x_0, x_1, x_2):
f= x_0**2 + x_1**2 + x_2**2
return(f)
>>> poblacion.evaluar_poblacion(
funcion_objetivo = funcion_objetivo,
optimizacion = "minimizar",
verbose = True
)
"""
# SE EVALÚA CADA INDIVIDUO DE LA POBLACIÓN
# ----------------------------------------------------------------------
for i in np.arange(self.n_individuos):
self.individuos[i].calcular_fitness(
funcion_objetivo = funcion_objetivo,
optimizacion = optimizacion,
verbose = verbose
)
# MEJOR INDIVIDUO DE LA POBLACIÓN
# ----------------------------------------------------------------------
# Se identifica el mejor individuo de toda el población, el de mayor
# fitness.
# Se selecciona inicialmente como mejor individuo el primero.
self.mejor_individuo = copy.deepcopy(self.individuos[0])
# Se comparan todas los individuos de la población.
for i in np.arange(self.n_individuos):
if self.individuos[i].fitness > self.mejor_individuo.fitness:
self.mejor_individuo = copy.deepcopy(self.individuos[i])
# Se extrae la información del mejor individuo de la población.
self.mejor_fitness = self.mejor_individuo.fitness
self.mejor_valor_variables = self.mejor_individuo.valor_variables
self.mejor_valor_funcion = self.mejor_individuo.valor_funcion
# INFORMACIÓN DEL PROCESO (VERBOSE)
# ----------------------------------------------------------------------
if verbose:
print("------------------")
print("Población evaluada")
print("------------------")
print("Mejor fitness encontrado : " + str(self.mejor_fitness))
print("Valor de la función objetivo: " \
+ str(self.mejor_valor_funcion))
print("Mejor valor de variables encontrado : "
+ str(self.mejor_valor_variables))
print("")
[docs] def cruzar_individuos(self, parental_1, parental_2, verbose=False):
"""
Este método genera un nuevo individuo a partir de dos individuos
parentales empleando el método de cruzamiento uniforme.
Parameters
----------
parental_1 : `int`
índice del individuo de la población que se quiere emplear como
parental 1 para el cruzamiento.
parental_1 : `int`
índice del individuo de la población que se quiere emplear como
parental 1 para el cruzamiento.
verbose : `bool`, optional
mostrar información del proceso por pantalla. (default ``False``)
Raises
------
raise Exception
si los índices parental_1 o parental_2 no son índices válidos.
Returns
------
descendencia : `Individuo`
Nuevo individuo generado por cruzamiento de dos parentales.
Examples
--------
>>> poblacion = Poblacion(
n_individuos = 5,
n_variables = 3,
limites_inf = [-5,-5,-5],
limites_sup = [5,5,5]
)
>>> descendencia = poblacion.cruzar_individuos(
parental_1 = 0,
parental_2 = 1,
verbose = True
)
Notes
-----
El objetivo del cruzamiento es generar, a partir de individuos ya
existentes (parentales), nuevos individuos (descendencia) que combinen
las características de los anteriores. Este es otro de los puntos del
algoritmo en los que se puede seguir varias estrategias. Tres de las más
empleadas son:
Cruzamiento a partir de uno solo punto: se selecciona aleatoriamente
una posición que actúa como punto de corte. Cada individuo parental se
divide en dos partes y se intercambian las mitades. Como resultado de
este proceso, por cada cruce, se generan dos nuevos individuos.
Cruzamiento a partir múltiples puntos: se seleccionan aleatoriamente
varias posiciones que actúan como puntos de corte. Cada individuo
parental se divide por los puntos de corte y se intercambian las partes.
Como resultado de este proceso, por cada cruce, se generan dos nuevos
individuos.
Cruzamiento uniforme: el valor que toma cada posición del nuevo
individuo se obtiene de uno de los dos parentales. Por lo general,
la probabilidad de que el valor proceda de cada parental es la misma,
aunque podría, por ejemplo, estar condicionada al fitness de cada uno.
A diferencia de las anteriores estrategias, con esta, de cada cruce se
genera un único descendiente.
"""
# COMPROBACIONES INICIALES: EXCEPTIONS Y WARNINGS
# ----------------------------------------------------------------------
if parental_1 not in np.arange(self.n_individuos):
raise Exception(
"El el índice del parental_1 debe de ser un valor entre 0 y " +
"el número de individuos de la población."
)
if parental_2 not in np.arange(self.n_individuos):
raise Exception(
"El el índice del parental_2 debe de ser un valor entre 0 y " +
"el número de individuos de la población."
)
# CREACIÓN DE LA DESCENDENCIA
# ----------------------------------------------------------------------
# Se extraen los parentales acorde a los índices indicados.
parental_1 = self.individuos[parental_1]
parental_2 = self.individuos[parental_2]
# Se clona uno de los parentales para utilizarlo como plantilla del nuevo
# individuo.
descendencia = copy.deepcopy(parental_1)
descendencia.valor_variables = np.repeat(None, descendencia.n_variables)
descendencia.fitness = None
# Se seleccionan aleatoriamente las posiciones que se heredan del
# parental_1 y del parental 2.
herencia_parent_1 = np.random.choice(
a = [True, False],
size = descendencia.n_variables,
p = [0.5, 0.5],
replace = True
)
herencia_parent_2 = np.logical_not(herencia_parent_1)
# Se transfieren los valores al nuevo individuo.
descendencia.valor_variables[herencia_parent_1] \
= parental_1.valor_variables[herencia_parent_1]
descendencia.valor_variables[herencia_parent_2] \
= parental_2.valor_variables[herencia_parent_2]
# Se crea un deepcopy para que el nuevo individuo sea independiente de
# los parentales. Esto evita problemas si posteriormente se muta.
descendencia = copy.deepcopy(descendencia)
# INFORMACIÓN DEL PROCESO (VERBOSE)
# ----------------------------------------------------------------------
if verbose:
print("---------------")
print("Cruce realizado: descendencia creada")
print("---------------")
print("Valor variables: " + str(descendencia.valor_variables))
print("")
return(descendencia)
[docs] def seleccionar_individuo(self, n, return_indices=True,
metodo_seleccion="tournament", verbose=False):
"""
Este método selecciona los índices de n individuos de una población,
donde la probabilidad de selección está relacionada con el fitness de
cada individuo. Si el argumento `return_indices=False` en lugar de los
índices se devuelve una copia de los individuos seleccionados.
Parameters
----------
n : `int`
número de individuos de la población seleccionados.
return_indices : `bool`, optional
cuando es True, se devuelve el indice que ocupan los individuos
seleccionados, cuando es False se devuelve una lista que contiene una
copia de los individuos. (default ``True``)
metodo_seleccion : {"ruleta", "rank", "tournament"}
método de selección de selección, ver notas para más información.
(default `tournament`)
verbose : `bool`, optional
mostrar información del proceso por pantalla. (default ``False``)
Raises
------
raise Exception
si el argumento `metodo_seleccion` no es 'ruleta', 'rank' o
'tournament'.
Returns
-------
indices : `numpy.ndarray`
índice de los individuos seleccionados (si `return_indices=True`).
individuos : `list`
lista con los individuos seleccionados (si `return_indices=False`).
Examples
--------
>>> poblacion = Poblacion(
n_individuos = 5,
n_variables = 3,
limites_inf = [-5,-5,-5],
limites_sup = [5,5,5],
verbose = True
)
>>> def funcion_objetivo(x_0, x_1, x_2):
f= x_0**2 + x_1**2 + x_2**2
return(f)
>>> poblacion.evaluar_poblacion(
funcion_objetivo = funcion_objetivo,
optimizacion = "minimizar",
verbose = True
)
>>> poblacion.seleccionar_individuo(
n = 2,
return_indices = True,
metodo_seleccion = "tournament",
verbose = True
)
Notes
-----
La forma en que se seleccionan los individuos que participan en cada cruce
difiere en las distintas implementaciones de los algoritmos genéticos.
Por lo general, todas ellas tienden a favorecer la selección de aquellos
individuos con mayor fitness. Algunas de las estrategias más comunes son:
Método de ruleta: la probabilidad de que un individuo sea seleccionado
es proporcional a su fitness relativo, es decir, a su fitness dividido
por la suma del fitness de todos los individuos de la población. Si el
fitness de un individuo es el doble que el de otro, también lo será la
probabilidad de que sea seleccionado. Este método presenta problemas si
el fitness de unos pocos individuos es muy superior (varios órdenes de
magnitud) al resto, ya que estos serán seleccionados de forma repetida y
casi todos los individuos de la siguiente generación serán “hijos” de
los mismos “padres” (poca variación).
Método rank: la probabilidad de selección de un individuo es inversamente
proporcional a la posición que ocupa tras ordenar todos los individuos
de mayor a menor fitness. Este método es menos agresivo que el método
ruleta cuando la diferencia entre los mayores fitness es varios órdenes
de magnitud superior al resto.
Selección competitiva (tournament): se seleccionan aleatoriamente dos
parejas de individuos de la población (todos con la misma probabilidad).
De cada pareja se selecciona el que tenga mayor fitness. Finalmente,
se comparan los dos finalistas y se selecciona el de mayor fitness. Este
método tiende a generar una distribución de la probabilidad de selección
más equilibrada que las dos anteriores.
Selección truncada (truncated selection): se realizan selecciones
aleatorias de individuos, habiendo descartado primero los n individuos
con menor fitness de la población.
"""
# COMPROBACIONES INICIALES: EXCEPTIONS Y WARNINGS
# ----------------------------------------------------------------------
if metodo_seleccion not in ["ruleta", "rank", "tournament"]:
raise Exception(
"El método de selección debe de ser ruleta, rank o tournament"
)
# SELECCIÓN DE INDIVIDUOS
# ----------------------------------------------------------------------
# Se crea un array con el fitness de cada individuo de la población.
array_fitness = np.repeat(None, self.n_individuos)
for i in np.arange(self.n_individuos):
array_fitness[i] = copy.copy(self.individuos[i].fitness)
# Se calcula la probabilidad de selección de cada individuo en función
# de su fitness.
if metodo_seleccion == "ruleta":
probabilidad_seleccion = array_fitness / np.sum(array_fitness)
ind_seleccionado = np.random.choice(
a = np.arange(self.n_individuos),
size = n,
p = list(probabilidad_seleccion),
replace = True
)
elif metodo_seleccion == "rank":
# La probabilidad con este método es inversamente proporcional a la
# posición en la que quedan ordenados los individuos de menor a mayor
# fitness.
order = np.flip(np.argsort(a=array_fitness) + 1)
ranks = np.argsort(order) + 1
probabilidad_seleccion = 1 / ranks
probabilidad_seleccion = probabilidad_seleccion / np.sum(probabilidad_seleccion)
ind_seleccionado = np.random.choice(
a = np.arange(self.n_individuos),
size = n,
p = list(probabilidad_seleccion),
replace = True
)
elif metodo_seleccion == "tournament":
ind_seleccionado = np.repeat(None,n)
for i in np.arange(n):
# Se seleccionan aleatoriamente dos parejas de individuos.
candidatos_a = np.random.choice(
a = np.arange(self.n_individuos),
size = 2,
replace = False
)
candidatos_b = np.random.choice(
a = np.arange(self.n_individuos),
size = 2,
replace = False
)
# De cada pareja se selecciona el de mayor fitness.
if array_fitness[candidatos_a[0]] > array_fitness[candidatos_a[1]]:
ganador_a = candidatos_a[0]
else:
ganador_a = candidatos_a[1]
if array_fitness[candidatos_b[0]] > array_fitness[candidatos_b[1]]:
ganador_b = candidatos_b[0]
else:
ganador_b = candidatos_b[1]
# Se comparan los dos ganadores de cada pareja.
if array_fitness[ganador_a] > array_fitness[ganador_b]:
ind_final = ganador_a
else:
ind_final = ganador_b
ind_seleccionado[i] = ind_final
# INFORMACIÓN DEL PROCESO (VERBOSE)
# ----------------------------------------------------------------------
if verbose:
print("---------------")
print("Individuo seleccionado")
print("---------------")
print("Método selección: " + metodo_seleccion)
print("")
if(return_indices):
return(ind_seleccionado)
else:
if n == 1:
return(copy.deepcopy(self.individuos[int(ind_seleccionado)]))
if n > 1:
return(
[copy.deepcopy(self.individuos[i]) for i in ind_seleccionado]
)
[docs] def crear_nueva_generecion(self, metodo_seleccion="tournament",
elitismo=0.1, prob_mut=0.01,
distribucion="uniforme",
media_distribucion=1, sd_distribucion=1,
min_distribucion=-1, max_distribucion=1,
verbose=False, verbose_seleccion=False,
verbose_cruce=False, verbose_mutacion=False):
"""
Este método somete la población a una nueva generación.
Parameters
----------
metodo_seleccion : {"ruleta", "rank", "tournament"}
método de selección de selección, ver notas para más información.
(default `tournament`)
elitismo : `float`, optional
porcentaje de mejores individuos de la población actual que pasan
directamente a la siguiente población. De esta forma, se asegura
que, la siguiente generación, no sea nunca peor. (default `0.1`)
prob_mut : `float`, optional
probabilidad que tiene cada posición del individuo de mutar.
(default 0.01)
distribucion : {"normal", "uniforme", "aleatoria"}, optional
distribución de la que obtener el factor de mutación.
(default "uniforme")
media_distribucion : `float`, optional
media de la distribución si se selecciona `distribucion = "normal"`
(default 1)
sd_distribucion : `float`, optional
desviación estándar de la distribución si se selecciona
`distribucion = "normal"`. (default 1)
min_distribucion : `float`, optional
mínimo de la distribución si se selecciona
`distribucion = "uniforme"`. (default -1)
max_distribucion : `float`, optional
máximo de la distribución si se selecciona
`distribucion = "uniforme"`. (default +1)
verbose : `bool`, optional
mostrar información del proceso por pantalla. (default ``False``)
verbose_seleccion : `bool`, optional
mostrar información de cada selección por pantalla.
(default ``False``)
verbose_cruce : `bool`, optional
mostrar información de cada cruce por pantalla.
(default ``False``)
verbose_mutacion : `bool`, optional
mostrar información de cada mutación por pantalla.
(default ``False``)
Examples
--------
>>> poblacion = Poblacion(
n_individuos = 5,
n_variables = 3,
limites_inf = [-5,-5,-5],
limites_sup = [5,5,5],
verbose = True
)
>>> def funcion_objetivo(x_0, x_1, x_2):
f= x_0**2 + x_1**2 + x_2**2
return(f)
>>> poblacion.evaluar_poblacion(
funcion_objetivo = funcion_objetivo,
optimizacion = "minimizar",
verbose = True
)
>>> poblacion.crear_nueva_generecion(
metodo_seleccion = "tournament",
elitismo = 0.1,
prob_mut = 0.01,
distribucion = "uniforme",
verbose = True,
verbose_seleccion = False,
verbose_cruce = False,
verbose_mutacion = False
)
"""
# Lista donde almacenar los individuos de la nueva generación.
nuevos_individuos = []
# ELITISMO
# ----------------------------------------------------------------------
if elitismo > 0:
# Número de individuos que pasan directamente a la siguiente
# generación.
n_elitismo = int(np.ceil(self.n_individuos*elitismo))
# Se identifican los n_elitismo individuos con mayor fitness (élite).
array_fitness = np.repeat(None, self.n_individuos)
for i in np.arange(self.n_individuos):
array_fitness[i] = copy.copy(self.individuos[i].fitness)
rank = np.flip(np.argsort(array_fitness))
elite = [copy.deepcopy(self.individuos[i]) for i in rank[:n_elitismo]]
# Se añaden los individuos élite a la lista de nuevos individuos.
nuevos_individuos = nuevos_individuos + elite
else:
n_elitismo = 0
# CREACIÓN DE NUEVOS INDIVIDUOS POR CRUCES
# ----------------------------------------------------------------------
for i in np.arange(self.n_individuos-n_elitismo):
# Seleccionar parentales
indice_parentales = self.seleccionar_individuo(
n = 2,
return_indices = True,
metodo_seleccion = metodo_seleccion,
verbose = verbose_seleccion
)
# Cruzar parentales para obtener la descendencia
descendencia = self.cruzar_individuos(
parental_1 = indice_parentales[0],
parental_2 = indice_parentales[1],
verbose = verbose_cruce
)
# Mutar la descendencia
descendencia.mutar(
prob_mut = prob_mut,
distribucion = distribucion,
min_distribucion = min_distribucion,
max_distribucion = max_distribucion,
verbose = verbose_mutacion
)
# Se añade la descendencia a la lista de nuevos individuos. Para
# que no de error la unión, se introduce el individuo descendencia
# dentro de una lista.
nuevos_individuos = nuevos_individuos + [descendencia]
# ACTUALIZACIÓN INFORMACIÓN DE LA POBLACIÓN
# ----------------------------------------------------------------------
self.individuos = copy.deepcopy(nuevos_individuos)
self.mejor_individuo = None
self.mejor_fitness = None
self.mejor_valor_variables = None
self.mejor_valor_funcion = None
# INFORMACIÓN DEL PROCESO (VERBOSE)
# ----------------------------------------------------------------------
if verbose:
print("-----------------------")
print("Nueva generación creada")
print("-----------------------")
print("Método selección: " + metodo_seleccion)
print("Elitismo: " + str(elitismo))
print("Número individuos élite: " + str(n_elitismo))
print("Número de nuevos individuos: "\
+ str(self.n_individuos-n_elitismo))
print("")
[docs] def optimizar(self, funcion_objetivo, optimizacion, n_generaciones = 50,
metodo_seleccion="tournament", elitismo=0.1, prob_mut=0.01,
distribucion="uniforme", media_distribucion=1,
sd_distribucion=1, min_distribucion=-1, max_distribucion=1,
parada_temprana=False, rondas_parada=None,
tolerancia_parada=None,verbose=False,
verbose_nueva_generacion=False,
verbose_seleccion=False, verbose_cruce=False,
verbose_mutacion=False, verbose_evaluacion=False):
"""
Este método realiza el proceso de optimización de una población.
Parameters
----------
funcion_objetivo : `function`
función que se quiere optimizar.
optimizacion : {"maximizar" o "minimizar"}
si se desea maximizar o minimizar la función.
n_generaciones : `int` , optional
número de generaciones de optimización. (default ``50``)
metodo_seleccion : {"ruleta", "rank", "tournament"}
método de selección de selección, ver notas para más información.
(default `tournament`)
elitismo : `float`, optional
porcentaje de mejores individuos de la población actual que pasan
directamente a la siguiente población. De esta forma, se asegura
que, la siguiente generación, no sea nunca peor. (default `0.1`)
prob_mut : `float`, optional
probabilidad que tiene cada posición del individuo de mutar.
(default 0.01)
distribucion : {"normal", "uniforme", "aleatoria"}, optional
distribución de la que obtener el factor de mutación.
(default "uniforme")
media_distribucion : `float`, optional
media de la distribución si se selecciona `distribucion = "normal"`
(default 1)
sd_distribucion : `float`, optional
desviación estándar de la distribución si se selecciona
`distribucion = "normal"`. (default 1)
min_distribucion : `float`, optional
mínimo de la distribución si se selecciona
`distribucion = "uniforme"`. (default -1)
max_distribucion : `float`, optional
máximo de la distribución si se selecciona
`distribucion = "uniforme"`. (default +1)
parada_temprana : `bool`, optional
si durante las últimas `rondas_parada` generaciones la diferencia
absoluta entre mejores individuos no es superior al valor de
`tolerancia_parada`, se detiene el algoritmo y no se crean nuevas
generaciones. (default ``False``)
rondas_parada : `int`, optional
número de generaciones consecutivas sin mejora mínima para que se
active la parada temprana. (default ``None``)
tolerancia_parada : `float` or `int`, optional
valor mínimo que debe tener la diferencia de generaciones consecutivas
para considerar que hay cambio. (default ``None``)
verbose : `bool`, optional
mostrar información del proceso por pantalla. (default ``False``)
verbose_nueva_generacion : `bool`, optional
mostrar información de cada nueva generación por pantalla.
(default ``False``)
verbose_seleccion : `bool`, optional
mostrar información de cada selección por pantalla.
(default ``False``)
verbose_cruce : `bool`, optional
mostrar información de cada cruce por pantalla.
(default ``False``)
verbose_mutacion : `bool`, optional
mostrar información de cada mutación por pantalla.
(default ``False``)
Raises
------
raise Exception
si se indica `parada_temprana = True` y los argumentos `rondas_parada`
o `tolerancia_parada` son ``None``.
raise Exception
si el argumento `metodo_seleccion` no es 'ruleta', 'rank' o
'tournament'.
raise Exception
si el argumento `optimizacion` es distinto de 'maximizar' o
'minimizar
Examples
--------
Ejemplo optimización
>>> def funcion_objetivo(x_0, x_1):
# Para la región acotada entre −10<=x_0<=0 y −6.5<=x_1<=0 la
# función tiene múltiples mínimos locales y un único minimo
# global en f(−3.1302468,−1.5821422)= −106.7645367.
f = np.sin(x_1)*np.exp(1-np.cos(x_0))**2 \
+ np.cos(x_0)*np.exp(1-np.sin(x_1))**2 \
+ (x_0-x_1)**2
return(f)
>>> poblacion = Poblacion(
n_individuos = 50,
n_variables = 2,
limites_inf = [-10, -6.5],
limites_sup = [0, 0],
verbose = False
)
>>> poblacion.optimizar(
funcion_objetivo = funcion_objetivo,
optimizacion = "minimizar",
n_generaciones = 250,
metodo_seleccion = "tournament",
elitismo = 0.1,
prob_mut = 0.01,
distribucion = "uniforme",
media_distribucion = 1,
sd_distribucion = 1,
min_distribucion = -1,
max_distribucion = 1,
parada_temprana = True,
rondas_parada = 10,
tolerancia_parada = 10**-16,
verbose = False
)
"""
# COMPROBACIONES INICIALES: EXCEPTIONS Y WARNINGS
# ----------------------------------------------------------------------
# Si se activa la parada temprana, hay que especificar los argumentos
# rondas_parada y tolerancia_parada.
if parada_temprana \
and (rondas_parada is None or tolerancia_parada is None):
raise Exception(
"Para activar la parada temprana es necesario indicar un " \
+ " valor de rondas_parada y de tolerancia_parada."
)
# ITERACIONES (GENERACIONES)
# ----------------------------------------------------------------------
start = time.time()
for i in np.arange(n_generaciones):
if verbose:
print("-------------")
print("Generación: " + str(i))
print("-------------")
# EVALUAR INDIVIDUOS DE LA POBLACIÓN
# ------------------------------------------------------------------
self.evaluar_poblacion(
funcion_objetivo = funcion_objetivo,
optimizacion = optimizacion,
verbose = verbose_evaluacion
)
# SE ALMACENA LA INFORMACIÓN DE LA GENERACIÓN EN LOS HISTÓRICOS
# ------------------------------------------------------------------
self.historico_individuos.append(copy.deepcopy(self.individuos))
self.historico_mejor_fitness.append(copy.deepcopy(self.mejor_fitness))
self.historico_mejor_valor_variables.append(
copy.deepcopy(self.mejor_valor_variables)
)
self.historico_mejor_valor_funcion.append(
copy.deepcopy(self.mejor_valor_funcion)
)
# SE CALCULA LA DIFERENCIA ABSOLUTA RESPECTO A LA GENERACIÓN ANTERIOR
# ------------------------------------------------------------------
# La diferencia solo puede calcularse a partir de la segunda
# generación.
if i == 0:
self.diferencia_abs.append(None)
else:
diferencia = abs(self.historico_mejor_fitness[i] \
- self.historico_mejor_fitness[i-1])
self.diferencia_abs.append(diferencia)
# CRITERIO DE PARADA
# ------------------------------------------------------------------
# Si durante las últimas n generaciones, la diferencia absoluta entre
# mejores individuos no es superior al valor de tolerancia_parada,
# se detiene el algoritmo y no se crean nuevas generaciones.
if parada_temprana and i > rondas_parada:
ultimos_n = np.array(self.diferencia_abs[-(rondas_parada): ])
if all(ultimos_n < tolerancia_parada):
print("Algoritmo detenido en la generación "
+ str(i) \
+ " por falta cambio absoluto mínimo de " \
+ str(tolerancia_parada) \
+ " durante " \
+ str(rondas_parada) \
+ " generaciones consecutivas.")
break
# CREAR UNA NUEVA GENERACIÓN
# ------------------------------------------------------------------
self.crear_nueva_generecion(
metodo_seleccion = metodo_seleccion,
elitismo = elitismo,
prob_mut = prob_mut,
distribucion = distribucion,
verbose = verbose_nueva_generacion,
verbose_seleccion = verbose_seleccion,
verbose_cruce = verbose_cruce,
verbose_mutacion = verbose_mutacion
)
end = time.time()
self.optimizado = True
self.iter_optimizacion = i
# IDENTIFICACIÓN DEL MEJOR INDIVIDUO DE TODO EL PROCESO
# ----------------------------------------------------------------------
indice_valor_optimo = np.argmax(np.array(self.historico_mejor_fitness))
self.fitness_optimo = self.historico_mejor_fitness[indice_valor_optimo]
self.valor_funcion_optimo = self\
.historico_mejor_valor_funcion[indice_valor_optimo]
self.valor_variables_optimo = self\
.historico_mejor_valor_variables[indice_valor_optimo]
# CREACIÓN DE UN DATAFRAME CON LOS RESULTADOS
# ----------------------------------------------------------------------
self.resultados_df = pd.DataFrame(
{
"mejor_fitness" : self.historico_mejor_fitness,
"mejor_valor_funcion" : self.historico_mejor_fitness,
"mejor_valor_variables": self.historico_mejor_valor_variables,
"diferencia_abs" : self.diferencia_abs
}
)
self.resultados_df["generacion"] = self.resultados_df.index
print("-------------------------------------------")
print("Optimización finalizada " \
+ datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
print("-------------------------------------------")
print("Duración optimización: " + str(end - start))
print("Número de generaciones: " + str(self.iter_optimizacion))
print("Valor óptimo de las variables: " + str(self.valor_variables_optimo))
print("Valor función objetivo: " + str(self.valor_funcion_optimo))
print("")