Source code for pso.clases.class_enjambre

################################################################################
#                              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("")