################################################################################
# CLASE ENJAMBRE (SWARM) #
################################################################################
[docs]class Enjambre:
"""
Esta clase crea un enjambre de n partículas.
Parameters
----------
n_particulas :`int`
número de partículas del enjambre.
n_variables : `int`
número de variables que definen la posición de las partícula.
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 is ``False``)
Attributes
----------
partículas : `list`
lista con todas las partículas del enjambre.
n_particulas :`int`
número de partículas del enjambre.
n_variables : `int`
número de variables que definen la posición de las partícula.
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_particula : `object particula`
la mejor partícula del enjambre en estado actual.
mejor_valor : `floar`
el mejor valor del enjambre en su estado actual.
historico_particulas : `list`
lista con el estado de las partículas en cada una de las iteraciones que
ha tenido el enjambre.
historico_mejor_posicion : `list`
lista con la mejor posición en cada una de las iteraciones que ha tenido
el enjambre.
historico_mejor_valor : `list`
lista con el mejor valor en cada una de las iteraciones que ha tenido el
enjambre.
diferencia_abs : `list`
diferencia absoluta entre el mejor valor de iteraciones consecutivas.
resultados_df : `pandas.core.frame.DataFrame`
dataframe con la información del mejor valor y posición encontrado en
cada iteración, así como la mejora respecto a la iteración anterior.
valor_optimo : `float`
mejor valor encontrado en todas las iteraciones.
posicion_optima : `numpy.narray`
posición donde se ha encontrado el valor_optimo.
optimizado : `bool`
si el enjambre ha sido optimizado.
iter_optimizacion : `int`
número de iteraciones de optimizacion.
Examples
--------
Ejemplo crear enjambre
>>> enjambre = Enjambre(
n_particulas = 5,
n_variables = 3,
limites_inf = [-5,-5,-5],
limites_sup = [5,5,5],
verbose = True
)
"""
def __init__(self, n_particulas, n_variables, limites_inf = None,
limites_sup = None, verbose = False):
# Número de partículas del enjambre
self.n_particulas = n_particulas
# Número de variables de cada partícula
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 las partículas del enjambre
self.particulas = []
# Etiqueta para saber si el enjambre ha sido optimizado
self.optimizado = False
# Número de iteraciones de optimización llevadas a cabo
self.iter_optimizacion = None
# Mejor partícula del enjambre
self.mejor_particula = None
# Mejor valor del enjambre
self.mejor_valor = None
# Posición del mejor valor del enjambre.
self.mejor_posicion = None
# Estado de todas las partículas del enjambre en cada iteración.
self.historico_particulas = []
# Mejor posición en cada iteración.
self.historico_mejor_posicion = []
# Mejor valor en cada iteración.
self.historico_mejor_valor = []
# Diferencia absoluta entre el mejor valor de iteraciones consecutivas.
self.diferencia_abs = []
# data.frame con la información del mejor valor y posición encontrado en
# cada iteración, así como la mejora respecto a la iteración anterior.
self.resultados_df = None
# Mejor valor de todas las iteraciones
self.valor_optimo = None
# Mejor posición de todas las iteraciones
self.posicion_optima = 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 LAS PARTÍCULAS DEL ENJAMBRE Y SE ALMACENAN
# ----------------------------------------------------------------------
for i in np.arange(n_particulas):
particula_i = Particula(
n_variables = self.n_variables,
limites_inf = self.limites_inf,
limites_sup = self.limites_sup,
verbose = verbose
)
self.particulas.append(particula_i)
# INFORMACIÓN DEL PROCESO (VERBOSE)
# ----------------------------------------------------------------------
if verbose:
print("---------------")
print("Enjambre creado")
print("---------------")
print("Número de partículas: " + str(self.n_particulas))
print("Límites inferiores de cada variable: "
+ str(self.limites_inf))
print("Límites superiores de cada variable: " \
+ str(self.limites_sup))
print("")
def __repr__(self):
"""
Información que se muestra cuando se imprime un objeto enjambre.
"""
texto = "============================" \
+ "\n" \
+ " Enjambre" \
+ "\n" \
+ "============================" \
+ "\n" \
+ "Número de partículas: " + str(self.n_particulas) \
+ "\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: " + str(self.iter_optimizacion) \
+ "\n" \
+ "\n" \
+ "Información mejor partícula:" \
+ "\n" \
+ "----------------------------" \
+ "\n" \
+ "Mejor posición actual: " + str(self.mejor_posicion) \
+ "\n" \
+ "Mejor valor actual: " + str(self.mejor_valor) \
+ "\n" \
+ "\n" \
+ "Resultados tras optimizar:" \
+ "\n" \
+ "----------------------------" \
+ "\n" \
+ "Posición óptima: " + str(self.posicion_optima) \
+ "\n" \
+ "Valor óptimo: " + str(self.valor_optimo)
return(texto)
[docs] def mostrar_particulas(self, n=None):
"""
Este método muestra la información de cada una de las n primeras
partículas del enjambre.
Parameters
----------
n : `int`
número de particulas que se muestran. Si no se indica el valor
(por defecto ``None``), se muestran todas. Si el valor es mayor
que `self.n_particulas` se muestran todas.
Examples
--------
>>> enjambre = Enjambre(
n_particulas = 5,
n_variables = 3,
limites_inf = [-5,-5,-5],
limites_sup = [5,5,5],
verbose = True
)
>>> enjambre.mostrar_particulas(n = 1)
"""
if n is None:
n = self.n_particulas
elif n > self.n_particulas:
n = self.n_particulas
for i in np.arange(n):
print(self.particulas[i])
return(None)
[docs] def evaluar_enjambre(self, funcion_objetivo, optimizacion, verbose = False):
"""
Este método evalúa todas las partículas del enjambre, actualiza sus
valores e identifica la mejor partícula.
Parameters
----------
funcion_objetivo : `function`
función que se quiere optimizar.
optimizacion : {maximizar o minimizar}
Dependiendo de esto, el mejor valor histórico de la partícula será
el mayor o el menor valorque ha tenido hasta el momento.
verbose : `bool`, optional
mostrar información del proceso por pantalla. (default is ``False``)
Examples
--------
Ejemplo evaluar enjambre
>>> enjambre = Enjambre(
n_particulas = 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)
>>> enjambre.evaluar_enjambre(
funcion_objetivo = funcion_objetivo,
optimizacion = "minimizar",
verbose = True
)
"""
# SE EVALÚA CADA PARTÍCULA DEL ENJAMBRE
# ----------------------------------------------------------------------
for i in np.arange(self.n_particulas):
self.particulas[i].evaluar_particula(
funcion_objetivo = funcion_objetivo,
optimizacion = optimizacion,
verbose = verbose
)
# MEJOR PARTÍCULA DEL ENJAMBRE
# ----------------------------------------------------------------------
# Se identifica la mejor partícula de todo el enjambre. Si se está
# maximizando, la mejor partícula es aquella con mayor valor.
# Lo contrario si se está minimizando.
# Se selecciona inicialmente como mejor partícula la primera.
self.mejor_particula = copy.deepcopy(self.particulas[0])
# Se comparan todas las partículas del enjambre.
for i in np.arange(self.n_particulas):
if optimizacion == "minimizar":
if self.particulas[i].valor < self.mejor_particula.valor:
self.mejor_particula = copy.deepcopy(self.particulas[i])
else:
if self.particulas[i].valor > self.mejor_particula.valor:
self.mejor_particula = copy.deepcopy(self.particulas[i])
# Se extrae la posición y valor de la mejor partícula y se almacenan
# como mejor valor y posición del enjambre.
self.mejor_valor = self.mejor_particula.valor
self.mejor_posicion = self.mejor_particula.posicion
# INFORMACIÓN DEL PROCESO (VERBOSE)
# ----------------------------------------------------------------------
if verbose:
print("-----------------")
print("Enjambre evaluado")
print("-----------------")
print("Mejor posición encontrada : " + str(self.mejor_posicion))
print("Mejor valor encontrado : " + str(self.mejor_valor))
print("")
[docs] def mover_enjambre(self, inercia, peso_cognitivo, peso_social,
verbose = False):
"""
Este método mueve todas las partículas del enjambre.
Parameters
----------
optimizacion : {'maximizar', 'minimizar'}
si se desea maximizar o minimizar la función.
inercia : `float` or `int`
coeficiente de inercia.
peso_cognitivo : `float` or `int`
coeficiente cognitivo.
peso_social : `float` or `int`
coeficiente social.
verbose : `bool`, optional
mostrar información del proceso por pantalla. (default is ``False``)
"""
# Se actualiza la posición de cada una de las partículas que forman el
# enjambre.
for i in np.arange(self.n_particulas):
self.particulas[i].mover_particula(
mejor_p_enjambre = self.mejor_posicion,
inercia = inercia,
peso_cognitivo = peso_cognitivo,
peso_social = peso_social,
verbose = verbose
)
# Información del proceso (VERBOSE)
# ----------------------------------------------------------------------
if verbose:
print("---------------------------------------------------------" \
"------------")
print("La posición de todas las partículas del enjambre ha sido " \
"actualizada.")
print("---------------------------------------------------------" \
"------------")
print("")
[docs] def optimizar(self, funcion_objetivo, optimizacion, n_iteraciones = 50,
inercia = 0.8, reduc_inercia = True, inercia_max = 0.9,
inercia_min = 0.4, peso_cognitivo = 2, peso_social = 2,
parada_temprana = False, rondas_parada = None,
tolerancia_parada = None, verbose = False):
"""
Este método realiza el proceso de optimización de un enjambre.
Parameters
----------
funcion_objetivo : `function`
función que se quiere optimizar.
optimizacion : {'maximizar' o 'minimizar'}
si se desea maximizar o minimizar la función.
m_iteraciones : `int` , optional
numero de iteraciones de optimización. (default is ``50``)
inercia : `float` or `int`, optional
coeficiente de inercia. (default is ``0.8``)
peso_cognitivo : `float` or `int`, optional
coeficiente cognitivo. (default is ``2``)
peso_social : `float` or `int`, optional
coeficiente social. (default is ``2``)
reduc_inercia: `bool`, optional
activar la reducción del coeficiente de inercia. En tal caso, el
argumento `inercia` es ignorado. (default is ``True``)
inercia_max : `float` or `int`, optional
valor inicial del coeficiente de inercia si se activa `reduc_inercia`.
(default is ``0.9``)
inercia_min : `float` or `int`, optional
valor minimo del coeficiente de inercia si se activa `reduc_min`.
(default is ``0.4``)
parada_temprana : `bool`, optional
si durante las últimas `rondas_parada` iteraciones la diferencia
absoluta entre mejores partículas no es superior al valor de
`tolerancia_parada`, se detiene el algoritmo y no se crean nuevas
iteraciones. (default is ``False``)
rondas_parada : `int`, optional
número de iteraciones consecutivas sin mejora mínima para que se
active la parada temprana. (default is ``None``)
tolerancia_parada : `float` or `int`, optional
valor mínimo que debe tener la diferencia de iteraciones consecutivas
para considerar que hay cambio. (default is ``None``)
verbose : `bool`, optional
mostrar información del proceso por pantalla. (default is ``False``)
Raises
------
raise Exception
si se indica `parada_temprana = True` y los argumentos `rondas_parada`
o `tolerancia_parada` son ``None``.
raise Exception
si se indica `reduc_inercia = True` y los argumentos `inercia_max`
o `inercia_min` son ``None``.
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 mínimo
# 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)
>>> enjambre = Enjambre(
n_particulas = 50,
n_variables = 2,
limites_inf = [-10, -6.5],
limites_sup = [0, 0],
verbose = False
)
>>> enjambre.optimizar(
funcion_objetivo = funcion_objetivo,
optimizacion = "minimizar",
n_iteraciones = 250,
inercia = 0.8,
reduc_inercia = True,
inercia_max = 0.9,
inercia_min = 0.4,
peso_cognitivo = 1,
peso_social = 2,
parada_temprana = True,
rondas_parada = 5,
tolerancia_parada = 10**-3,
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."
)
# Si se activa la reducción de inercia, hay que especificar los argumentos
# inercia_max y inercia_min.
if reduc_inercia \
and (inercia_max is None or inercia_min is None):
raise Exception(
"Para activar la reducción de inercia es necesario indicar un " \
+ "valor de inercia_max y de inercia_min."
)
# ITERACIONES
# ----------------------------------------------------------------------
start = time.time()
for i in np.arange(n_iteraciones):
if verbose:
print("-------------")
print("Iteracion: " + str(i))
print("-------------")
# EVALUAR PARTÍCULAS DEL ENJAMBRE
# ------------------------------------------------------------------
self.evaluar_enjambre(
funcion_objetivo = funcion_objetivo,
optimizacion = optimizacion,
verbose = verbose
)
# SE ALMACENA LA INFORMACIÓN DE LA ITERACIÓN EN LOS HISTÓRICOS
# ------------------------------------------------------------------
self.historico_particulas.append(copy.deepcopy(self.particulas))
self.historico_mejor_posicion.append(copy.deepcopy(self.mejor_posicion))
self.historico_mejor_valor.append(copy.deepcopy(self.mejor_valor))
# SE CALCULA LA DIFERENCIA ABSOLUTA RESPECTO A LA ITERACIÓN ANTERIOR
# ------------------------------------------------------------------
# La diferencia solo puede calcularse a partir de la segunda
# iteración.
if i == 0:
self.diferencia_abs.append(None)
else:
diferencia = abs(self.historico_mejor_valor[i] \
- self.historico_mejor_valor[i-1])
self.diferencia_abs.append(diferencia)
# CRITERIO DE PARADA
# ------------------------------------------------------------------
# Si durante las últimas n iteraciones, la diferencia absoluta entre
# mejores partículas no es superior al valor de tolerancia_parada,
# se detiene el algoritmo y no se crean nuevas iteraciones.
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 iteracion "
+ str(i) \
+ " por falta cambio absoluto mínimo de " \
+ str(tolerancia_parada) \
+ " durante " \
+ str(rondas_parada) \
+ " iteraciones consecutivas.")
break
# MOVER PARTÍCULAS DEL ENJAMBRE
# ------------------------------------------------------------------
# Si se ha activado la reducción de inercia, se recalcula su valor
# para la iteración actual.
if reduc_inercia:
inercia = ((inercia_max - inercia_min) \
* (n_iteraciones-i)/n_iteraciones) \
+ inercia_min
self.mover_enjambre(
inercia = inercia,
peso_cognitivo = peso_cognitivo,
peso_social = peso_social,
verbose = False
)
end = time.time()
self.optimizado = True
self.iter_optimizacion = i
# IDENTIFICACIÓN DEL MEJOR PARTÍCULA DE TODO EL PROCESO
# ----------------------------------------------------------------------
if optimizacion == "minimizar":
indice_valor_optimo=np.argmin(np.array(self.historico_mejor_valor))
else:
indice_valor_optimo=np.argmax(np.array(self.historico_mejor_valor))
self.valor_optimo = self.historico_mejor_valor[indice_valor_optimo]
self.posicion_optima = self.historico_mejor_posicion[indice_valor_optimo]
# CREACIÓN DE UN DATAFRAME CON LOS RESULTADOS
# ----------------------------------------------------------------------
self.resultados_df = pd.DataFrame(
{
"mejor_valor_enjambre" : self.historico_mejor_valor,
"mejor_posicion_enjambre": self.historico_mejor_posicion,
"diferencia_abs" : self.diferencia_abs
}
)
self.resultados_df["iteracion"] = 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 iteraciones: " + str(self.iter_optimizacion))
print("Posición óptima: " + str(self.posicion_optima))
print("Valor óptimo: " + str(self.valor_optimo))
print("")