Introducción.
El Problema del Agente Viajero o TSP (del inglés Travelling Salesman Problem) básicamente es un problema de Optimización que busca la minimización. Surge con la necesidad de buscar la ruta más corta o de menor costo en un conjunto de ciudades o puntos, esto sin depender del orden a seguir en el viaje siempre y cuando el recorrido sea cerrado.
El TSP es muy importante ya que se aplicar en la vida diaria cuando simplemente estamos de viaje y queremos dar el tour más corto en los lugares turísticos, o hasta en la industria por ejemplo cuando se busca hacer taladrado en piezas de metal tomando el menor tiempo, otras situaciones que pueden aplicar son en el autobús escolar, el repartidor de comida y el diseño de tarjetas electrónicas.
La dificultad de este problema crece a medida de que aumentas las ciudades ya que se generan cada vez más combinaciones que por fuerza bruta; lo cual sería muy tedioso y cansado para resolver.
La Optimización por Colonia de Hormigas o ACO (del inglés Ant Colony Optimization) es una metaheurística para la resolución de problemas como la ruta más corta, la cual se inspira en las colonias de hormigas reales ya que por naturaleza se organizan para ir por la comida cada vez por una mejor ruta.
El ACO es justamente lo que el TSP necesita porque es un Algoritmo que nos ayudará a encontrar la ruta con menor costo.
En esta entrada implementaremos el ACO para la resolución de instancias TSP, primero dando un enfoque teórico para conocer y entender cómo se soluciona este problema, y después la implementación con un programa de la solución de una instancia donde se mostraran los parámetros utilizados y finalmente la verificación de resultados.
Marco Teórico.
El TSP esta dentro de la clase de los problemas NP-Completos y se dice que pertenece a este ya que difícilmente existe un algoritmo eficiente para su resolución para mejor información ver referencias.
En una forma más formal el TSP consiste primeramente dando un grafo ponderado en las aristas por lo general no dirigido, dónde cada vértice representa una ciudad y la arista el costo del viaje, cada vértice esta unido con los vértices restantes. Éste grafo puede ser representado por una matriz. Ya teniendo este grafo o matriz el problema a resolver es encontrar una ruta que permita pasar por todos los vértices (ciudades) y regresar al origen generando el menor costo (ruta más corta).
Una instancia (ejemplo concreto acerca del problema) del TSP sería especificar el número de ciudades con sus respectivos costos o distancias de cada una de las ciudades a todas las demás, he aquí de donde proviene que dependiendo del tamaño de la instancia crece la dificultad de resolverlo ya que el tamaño del espacio de búsqueda está dado por permutaciones, tal que para una instancia de 4 ciudades habría 24 posibles soluciones, pero para una de 10 habría 3,628,800 posibles soluciones.
Veamos una instancia agregando contexto tomado y adaptado del libro de Investigación de Operaciones de Hamdy A. Taha.
"Un vendedor de libros que reside en el País México debe visitar al mes cuatro de sus clientes localizados en los estados de Monterrey, San Luis Potosí, Saltillo, Reynosa, sin importar el orden, lo importante es visitar todos los sitios, lograr el menor recorrido posible y regresar al punto inicial.
Las Distancias en Km entre las ciudades son:"
Monterrey
|
San Luis
|
Saltillo
|
Reynosa
| |
Monterrey
|
0
|
515
|
88.3
|
221
|
San Luis
|
515
|
0
|
451
|
745
|
Saltillo
|
88.3
|
451
|
0
|
318
|
Reynosa
|
221
|
745
|
318
|
0
|
En esta ocasión resolveremos la instancia con la Ayuda de la Fuerza Bruta que cosiste en obtener todos los resultados posibles y observar el menor, esto es sencillo con instancias pequeñas pero se vuelve un dolor de cabeza con algunas más grandes.
Para observar de una mejor manera la instancia se hizo un grafo.
Después se obtuvieron todas las posibles soluciones (rutas)
Para las 4 ciudades se obtuvieron 24 posibles caminos, dónde el camino más o de menor costo fue de 1505 y fue repetido en varios recorridos.
Como se menciono anteriormente ACO (Colonización Basada en Colonias de Hormigas) nos ayuda bastante a resolver el problema TSP ya que su principal objetivo es encontrar la ruta que lo lleve a tener un menor costo .
Como se menciono anteriormente ACO (Colonización Basada en Colonias de Hormigas) nos ayuda bastante a resolver el problema TSP ya que su principal objetivo es encontrar la ruta que lo lleve a tener un menor costo .
El ACO es una metaheurística que básada en la naturaleza y esta dentro de la clase de los algoritmos de Swarm Intelligence (Enjambre), creada por Marco Dorigo en el año 1992.
NOTA: Principalemente las formulas y alguna de la información que se presenta a continuación
fue basada del Documento de Marco Dorigo, acerca de ACO en el 2006, revisar Referencias.
Esta técnica se basa en la metáfora natural de las Hormigas y como es su organización que Dorigo la llama "Stigmergy" que es como un mecanismo de coordinación indirecta, las hormigas son ciegas entonces en su búsqueda de alimento ellas dejan un rastro de feromonas por el recorrido que van haciendo la cual cualquier otra hormiga es capaz de detectar.
Al llegar a un cruce de camino (bifurcación) la hormiga elije de forma probabilística ya que toma el camino que contenga un mayor rastro de feromona.
Los mejores caminos serán los que están más cerca a la comida y por lo tanto más feromona ya que un mayor numero de hormigas pasa por ahí aumentando el rastro de feromona.
Los menos prometedores poco a poco se quedan sin feromona debido a la evaporación de ésta ya que pocas hormigas pasas por ahí.
Después de varios recorridos se encuentra el mejor camino.
A partir de esta observación se llego al siguiente algoritmo.
El documento de Alonso acerca de ACO nos muestra un pseudocódigo que nos ayudará a realizar nuestra implementación y es el siguiente:
Procedimiento Metaheuristica_OCH() Inicializacion_de_parámetros mientras (criterio_de_terminación_no_satisfecho) Programación_de_actividades Generación_de_Hormigas_y_actividad() Evaporacion_de_Feromona() fin Programación_de_actividades fin mientras fin Procedimiento Procedimiento Generación_de_Hormigas_y_actividad() repetir en paralelo desde k=1 hasta m (numero_hormigas) Nueva_Hormiga(k) fin repetir en paralelo fin Procedimiento Procedimiento Nueva_Hormiga(id_Hormiga) inicializa_hormiga(id_Hormiga) L = actualiza_memoria_hormiga() mientras (estado_actual "pi" estado_objetivo) P = calcular_probabilidades_de_transición(A,L,W) siguiente_estado = aplicar_política_decisión(P,W) mover_al_siguiente_estado(siguiente_estado) si (actualizacion_feromona_en_linea_paso_a_paso) depositar_feromona_en_el_arco_vistado() fin si L = actualizar_estado_interno() fin mientras si (actualizacion_feromona_en_linea_a_posteriori) para cada arco visitado depositar_feromona_en_el_arco_visitado() fin para fin si liberar_recursos_hormiga(id_hormiga) fin Procedimiento
Para realizar el ACO Dorigo implemento ciertas formulas las cuales ayudan a la selección de camino (arista), la actualización de la feromona (aquí interviene el depósito y evaporación).
En cada iteración, los valores de las feromonas son actualizados por todas las hormigas "m" que han construido una solución. τi j es la feromona asociada con la arista que une a la ciudad i y j , la actualización se lleva a cabo de esta forma:
Dónde:
ρ = Factor de decremento de la feromona (evaporación).
Δτ^k i j = Cantidad de feromona que deposita en la arista (i, j) de la hormiga k:
Esta dada por:![]() |
Si k paso por la arista(i, j) en este recorrido toma el arriba
Si no dejes feromona.
|
Donde Q es el factor constante del incremento de la feromona y Lk es la longitud de los caminos (cuantas ciudades pasa al completar el recorrido. Es constante para cualquier recorrido. depende dlel numero de ciudades).
Para encontrar la Probabilidad de la hormiga k de selección de Aristas de i a j, se sigue la Formula:
![]() |
Si cij esta en el conjunto de los posibles candidatos. realizar la formula sino asignar 0 |
α = Influencia en el rastro de feromona. (Darle mayor importancia a la feromona).
β = Influencia a la distancia de cada arista.
ηij = información heurística de las distancias o costos de cada arista.
ηil = información heurísitica de los demás caminos candidatos.
τil = feromona de los demás caminos candidatos.
Para comprender mejor la formula de probabilidad la parte del denominador se refiere simplemente a la Sumatoria del producto de la Informacion Heuristica elevado a beta y la feromona elevada a la alfa de todos los candidatos a obtener probabilidades. Es decir a las demás ciudades las cuales puedes seguir el recorrido.
Por último hace falta como obtener la información heurística acerca de las distancias o costos. Aquí dij es la distancia que hay en la arista (i,j) o bien entre la ciudad i y j.
Tanto los costos, la información heurísitica, las feromonas, y el recorrido actual que tiene la hormiga es necesario tenerlos almacenados en algún especie de lista, o arreglo.
Desarrollo.
A continuación se presenta un programa el cual realiza la resolución de las 2 instancias, la primera resuelve el problema de las ciudades y el vendedor de libros (4 ciudades) que es la instancia propuesta en el marco teórico, después se presenta la instancia 2 la cual el programa genera una instancia de 10 ciudades de manera aleatoria, las dos instancias se resuelven basándose en ACO.
Generador de Instancias Aleatorias.
Aquí se muestra el generador de instancias aleatorias el cual recibiendo el parámetro de cuantas ciudades es como realiza la matriz de números aleatorios.
def generador_random(self,NoCiudades): self.ciudades = NoCiudades ciudad = [[0 for i in range(self.ciudades)] for i in range(self.ciudades)] print ciudad random.seed() for i in range(self.ciudades): for j in range(self.ciudades): if i == j: ciudad[i][j] = 0 else: if i>0 and j < i and i != j: ciudad[i][j]=ciudad[j][i] else: ciudad[i][j] = random.randint(1,25) self.feromonas = [[0.01 for i in xrange(0,self.ciudades)]for i in xrange(0,self.ciudades)] print ciudad self.instancia = ciudadInicialización del Algoritmo.
El método correrHormiga(), nos ayuda a inicializar el algoritmo el cual realiza las funciones de seleccionar Arista y la Actualización de Feromona
def correrHormiga(self): gral = [] for hormigas in range(self.ciudades): r = self.seleccionarArista() #mando a correr a una hormiga y almacen en r su recorrido gral.append(r)#mando los recorridos a una arreglo general self.actualizarFeromonas(r) #al final de cada hormiga actualizo la feromona print "Recorridos de cada hormiga: ",gral self.evaluarRecorridos(gral) #Evaluo los recorridos de cada Hormiga
Selección de Aristas.
El método de selección de Aristas, obtiene las probabilidades de los caminos a seguir, y después por medio de la ruleta decide que camino tomar.
def correrHormiga(self): gral = [] for hormigas in range(self.ciudades): r = self.seleccionarArista() #mando a correr a una hormiga y almacen en r su recorrido gral.append(r)#mando los recorridos a una arreglo general self.actualizarFeromonas(r) #al final de cada hormiga actualizo la feromona print "Recorridos de cada hormiga: ",gral self.evaluarRecorridos(gral) #Evaluo los recorridos de cada Hormiga def seleccionarArista(self):#,instancia): #for i in xrange(2): ruleta = [] pos_hormiga = self.pos_hormiga #variables de la formula sumatoria = 0 Tij_and_Nij = [] pos_ant = 0 pos_act = 0 aristas_checar = [] recorridos=[0] for hormigas in range(self.ciudades-1): aristas_checar = [] for i in range(self.ciudades): #checar que puede usar la lista if not i in recorridos and i !=0: aristas_checar.append(i) if self.debug: print "soy aristas a checar: ",aristas_checar Tij_and_Nij = [] for elemento in aristas_checar: #sacar la sumatoria #calcular tij and nij if self.debug: print "elemento: ",elemento," pos_hormgiga: ",pos_hormiga print "\t elemento instancia: ",self.instancia[pos_hormiga][elemento] Tij = self.feromonas[pos_hormiga][elemento] Nij = 1.0/self.instancia[pos_hormiga][elemento] elev = math.pow(Tij,self.alfa)*math.pow(Nij,self.beta) Tij_and_Nij.append(elev) for elemento in Tij_and_Nij: sumatoria += elemento if self.debug: print "soy TN: ",Tij_and_Nij print "voy a ruleta" ruleta = [] i=0 for T_N in Tij_and_Nij:#asignar a ruleta en base a probabilidad Pij = T_N/sumatoria if self.debug: print "Soy Pij de elem: ",Pij for j in xrange(int(math.ceil(Pij*10.0)+1)): ruleta.append(i) i+=1 if self.debug: print "Tij_and_Nij: ",Tij_and_Nij print "Sumatoria: ",sumatoria print "Ruleta: ",ruleta random.shuffle(ruleta)#chaca chaca #SELECCIONAR ARISTA DE LA RULETA arista = aristas_checar[ruleta[0]] #seleccinamos una arista if self.debug: print "Arista selccionada: ",arista pos_ant = pos_act pos_act = arista recorridos.append(pos_act) if self.debug: print "vengo de : ",pos_ant,"voy a : ",pos_act print "mis sitios visitados: ",recorridos self.ponerFeromonas(pos_ant,pos_act) recorridos.append(0) return recorridos
Actualización de Feromonas.
El metodo actualizarFeromonas() es el metodo que realiza el depósito y evaporación de las feromonas además utiliza otro método llamado sumaTij_K() y recibe una lista de recorridos los cuales hace referencia al recorrido que paso la hormiga en esta iteración.
def sumaTij_K(self,recorridos): suma = 0 #for i in range(self.ciudades): L_k = 0 for elemento in recorridos: L_k += elemento suma += self.Q/L_k return suma def actualizarFeromonas(self,recorridos): sumaT_i_j = self.sumaTij_K(recorridos) nuevas_feromonas = [] for i in range(len(self.feromonas)): lote = [] for elemento in self.feromonas[i]: lote.append( (1-self.P)*elemento + sumaT_i_j ) nuevas_feromonas.append(lote) self.feromonas = nuevas_feromonas if self.debug: print "Nuevas feromonas: ",self.feromonas
Pueden obtener el código de la siguiente liga: ACO_TSP.py
Resultados.
Ahora bien a partir del código hecho en python se realizarán las pruebas y los resultados.
Para las dos instancias se tomaron estos valores para la probabilidad en la selecciones de Aristas.
α = 1.0
β =1.0
El coeficiente de evaporación se le asignó un valor de p =0.5 y para el coeficiente de depósito de feromona se tomó el valor de Q= 10.
Por razones principales de tiempo no se pudo hacer más experimentación con diferentes valores de parámetros aunque con la lectura de los documentos de Dorigo vimos que estos parámetros son los más genéricos para obtener la solución.
El programa se corre con argumentos de consola en la captura de pantalla se muestran.
El primero es para debug 1 es si 0 es no.
El segundo es para cual instancia utilizar si la predefinida (instancia 1 = 1) o la aleatoria = 0
El tercero si se eligió la aleatoria se designa el tamaño de la instancia si no no especificar.
Resultado de Instancia 1.
Analizando y comparando vemos que coincide con la instancia resuelta por fuerza bruta.
Resultado instancia 2.
Esta es una instancia de 10 ciudades con números aleatorios.
Resultado instancia 3.
Instancia aleatorea la cual confirma que el camino mas corto es el 2.
Resultado instancia 4.
Instancia aleatoria con 7 ciudades y el camino mas corto es el 4.
α = 1.0
β =1.0
El coeficiente de evaporación se le asignó un valor de p =0.5 y para el coeficiente de depósito de feromona se tomó el valor de Q= 10.
Por razones principales de tiempo no se pudo hacer más experimentación con diferentes valores de parámetros aunque con la lectura de los documentos de Dorigo vimos que estos parámetros son los más genéricos para obtener la solución.
El programa se corre con argumentos de consola en la captura de pantalla se muestran.
El primero es para debug 1 es si 0 es no.
El segundo es para cual instancia utilizar si la predefinida (instancia 1 = 1) o la aleatoria = 0
El tercero si se eligió la aleatoria se designa el tamaño de la instancia si no no especificar.
Resultado de Instancia 1.
Analizando y comparando vemos que coincide con la instancia resuelta por fuerza bruta.
Resultado instancia 2.
Esta es una instancia de 10 ciudades con números aleatorios.
Resultado instancia 3.
Instancia aleatorea la cual confirma que el camino mas corto es el 2.
Resultado instancia 4.
Instancia aleatoria con 7 ciudades y el camino mas corto es el 4.
Conclusiones.
Realmente entender un algoritmo o metaheurística puede resultar algo no tan complicado, sin embargo implementarlo en un lenguaje de programación puede resultar ser un poco más difícil, a pesar de que ACO ayuda a la resolver el TSP también si la instancia es muy pequeña como la que presentamos primero y nunca se probara con instancias más grandes resulta sencillo, más no mejor, implementar un algoritmo de Fuerza Bruta que una Metaheurística como ACO, sin embargo, si la instancia crece resulta ser mejor tener una implementación que ayude a resolver instancias del mismo tamaño y más grandes.
En relación con los resultados si coinciden la instancia uno que fue resuelta por fuerza bruta, se ve que el algoritmo cumple con la búsqueda del mejor camino.
Para terminar, realizar este programa no fue fácil tiene su grado de dificulta y ahora vemos por que tanto tiempo de anticipación se dio y aún así se nos estuvimos apresurados sin embargo pues se trató de entregar el programa funcionando con la mejor calidad posible.
Referencias.
http://es.wikipedia.org/wiki/Clases_de_complejidad_P_y_NP#NP-Completo
Ant Colony Optimization, Marco Dorigo,Thomas Stützle, A Bradford Book, The MIT Press, Cambridge, Massachusetts London, England, 2006 .http://elisa.dyndns-web.com/~saraelena/material/sistadap/dorigo_aco_2006.pdf
ftp://iridia.ulb.ac.be/pub/mdorigo/journals/IJ.10-SMC96.pdf
Ant Colony Optimization, Marco Dorigo,Thomas Stützle, A Bradford Book, The MIT Press, Cambridge, Massachusetts London, England, 2006 .http://elisa.dyndns-web.com/~saraelena/material/sistadap/dorigo_aco_2006.pdf
ftp://iridia.ulb.ac.be/pub/mdorigo/journals/IJ.10-SMC96.pdf
Me da un error en la linea 187, dice Index Error: list index out of range
ResponderEliminar