đ§ Sur la Route de lâOptimum : Recuit SimulĂ© pour le TSP
Recuit simulé avec pondération et opérations de voisinage multiples
Plongeons une fois de plus dans le monde fascinant des mĂ©taheuristiques, un domaine que jâexplore avec un enthousiasme renouvelĂ© Ă chaque nouvel article.
Dans cet esprit, je vous invite Ă dĂ©couvrir notre dernier pĂ©riple autour du Traveling Salesman Problem (TSP). Aussi appelĂ© le problĂšme du voyageur de commerce en français, câest une Ă©nigme classique de lâoptimisation combinatoire qui, malgrĂ© sa simplicitĂ© conceptuelle â trouver la route la plus courte pour visiter une sĂ©rie de villes et revenir Ă son point de dĂ©part (dans sa version symĂ©trique) â , reprĂ©sente un dĂ©fi complexe et multidimensionnel.
Il trouve des applications dans divers domaines, notamment la logistique, la planification de routes et lâintelligence artificielle.
VoilĂ pourquoi lâobjectif principal de cet article sera de dĂ©velopper, implĂ©menter et affiner une mĂ©thode de recuit simulĂ© pour le TSP.
đŁ Lâalgorithme de recuit simulĂ©
Le recuit simulĂ© est une technique dâoptimisation inspirĂ©e du processus de recuit en mĂ©tallurgie. Lâalgorithme simule un processus oĂč les solutions sont progressivement amĂ©liorĂ©es Ă travers des itĂ©rations, en permettant dâaccepter occasionnellement les solutions de moins bonne qualitĂ© pour Ă©viter les minimums locaux.
ⶠFonctionnement
Globalement le fonctionnement du recuit simulé est basé sur les principes suivant :
âŒïž Initialisation : Commence par un Ă©tat alĂ©atoire du systĂšme avec une Ă©nergie (fitness) initiale et une tempĂ©rature Ă©levĂ©e.
âŒïž Processus ItĂ©ratif : A chaque itĂ©ration, une petite modification est apportĂ©e Ă lâĂ©tat actuel, gĂ©nĂ©rant un nouvel Ă©tat.
- Si le nouvel état a une énergie inférieure (meilleure solution), il est accepté.
- Si lâĂ©nergie est supĂ©rieure (solution de moindre qualitĂ©), il peut ĂȘtre acceptĂ© avec une certaine probabilitĂ© qui dĂ©pend de la diffĂ©rence dâĂ©nergie et de la tempĂ©rature actuelle.
âŒïž Refroidissement : La tempĂ©rature est progressivement diminuĂ©e selon une loi de dĂ©croissance (linĂ©aire, exponentielle, etc.), rĂ©duisant la probabilitĂ© dâaccepter des solutions de moins bonne qualitĂ©. Câest ce qui nous fait converger vers la meilleur solution.
âŒïž CritĂšre dâAcceptation : Si la nouvelle solution est meilleure, elle est systĂ©matiquement acceptĂ©e. Si elle est moins bonne, elle peut tout de mĂȘme ĂȘtre acceptĂ©e avec une certaine probabilitĂ©, qui diminue au fur et Ă mesure du refroidissement. Cela permet dâĂ©viter de rester bloquĂ© dans un minimum local en acceptant temporairement des solutions de moindre qualitĂ©, favorisant ainsi lâexploration de lâespace des solutions de maniĂšre plus exhaustive.
ⶠPseudo-code
Le pseudo-code suivant met en Ćuvre le recuit simulĂ© :
i := i0 (* Solution initiale *)
T := T0 (* Température initiale *)
Tant que la condition d'arrĂȘt n'est pas vĂ©rifiĂ©e
Tant que la fin du pallier n'est pas atteinte
Générer nouvelle solution j
Î = e(j) - e(i)
Si Î < 0 alors
i := j
Sinon
i := j avec une proba de exp(-Î/T)
Fin si
Fin tant que
Abaisser T
Fin tant que
đšđżâđ» ImplĂ©mentation du Recuit SimulĂ© pour le TSP
LâimplĂ©mentation du recuit simulĂ© pour rĂ©soudre le problĂšme du voyageur de commerce (TSP) reprĂ©sente un dĂ©fi fascinant en optimisation. Dans les sections suivantes, nous allons dĂ©tailler les Ă©tapes clĂ©s de notre implĂ©mentation.
Nous commençons par lire un fichier TSP et extraire une liste des coordonnées des villes. Pour simplifier notre démonstration, nous nous concentrons sur les six premiÚres villes, constituant ainsi un ensemble de données gérable pour illustrer clairement le fonctionnement de nos fonctions.
tsp_cities = read_tsp_data(tsp_data_filepath)[:6]
tsp_cities
'''
output:
[(1, 565.0, 575.0),
(2, 25.0, 185.0),
(3, 345.0, 750.0),
(4, 945.0, 685.0),
(5, 845.0, 655.0),
(6, 880.0, 660.0)]
'''
ⶠCrĂ©ation dâune solution initiale
Dans mon implémentation, une solution initiale est représentée par une liste de tuples, chacun indiquant une ville avec ses coordonnées numériques. Cette solution initiale est générée en mélangeant aléatoirement les villes issues de notre ensemble de données, tout en gardant la ville de départ fixe. Dans le cadre du TSP symétrique, le chemin doit impérativement revenir au point de départ, donc la ville de départ rajouté à la fin.
La fonction pour générer cette solution initiale est structurée comme suit :
# Generate a random solution (starting point)
def initial_solution(tsp_cities):
# Start with the first element
start = tsp_cities[0]
# Exclude the first element when shuffling the rest of the data
rest_of_cities = tsp_cities[1:]
random.shuffle(rest_of_cities)
# Return the individual starting and ending with the first element
individual = [start] + rest_of_cities + [start]
return individual
sol = initial_solution(tsp_cities)
sol
'''
output:
[(1, 565.0, 575.0),
(6, 880.0, 660.0),
(4, 945.0, 685.0),
(5, 845.0, 655.0),
(2, 25.0, 185.0),
(3, 345.0, 750.0),
(1, 565.0, 575.0)]
'''
ⶠDéfinition de la fonction objectif
La clĂ© de toute optimisation rĂ©side dans la dĂ©finition adĂ©quate dâune fonction objectif, qui peut aussi servir de fonction dâĂ©valuation. Pour le TSP, cette fonction (aussi appelĂ©e fonction coĂ»t) est la somme des distances entre les villes consĂ©cutives.
Pour optimiser les calculs, jâai prĂ©conçu une matrice des distances entre toutes les paires de villes.
Voici lâimplĂ©mentation de la matrice des distances :
# Compute the distance
def compute_distance(a, b):
_, x1, y1 = a
_, x2, y2 = b
distance = math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
return distance
# Create a distance matrix for all cities
def create_distance_matrix(tsp_cities):
num_cities = len(tsp_cities)
distance_matrix = np.zeros((num_cities, num_cities))
for i in range(num_cities):
for j in range(i + 1, num_cities):
distance = compute_distance(tsp_cities[i], tsp_cities[j])
distance_matrix[i][j] = distance
distance_matrix[j][i] = distance # Mirror the distance as the matrix is symmetric
return distance_matrix
distance_matrix = create_distance_matrix(tsp_cities)
print(distance_matrix.shape)
distance_matrix
'''
output:
(6, 6)
array([[ 0. , 666.10809934, 281.11385594, 395.6008089 ,
291.20439557, 326.26676202],
[ 666.10809934, 0. , 649.32657423, 1047.09120902,
945.14549145, 978.08486339],
[ 281.11385594, 649.32657423, 0. , 603.51056329,
508.9449872 , 542.51728083],
[ 395.6008089 , 1047.09120902, 603.51056329, 0. ,
104.40306509, 69.64194139],
[ 291.20439557, 945.14549145, 508.9449872 , 104.40306509,
0. , 35.35533906],
[ 326.26676202, 978.08486339, 542.51728083, 69.64194139,
35.35533906, 0. ]])
'''
Cette stratégie permet de récupérer les distances nécessaires sans recalculs inutiles et donc de gagner du temps [4].
En effet, le calcul de la distance totale du chemin permet dâĂ©valuer chaque solution proposĂ©e par lâalgorithme. Plus cette distance est courte, meilleure est la solution.
Voici lâimplĂ©mentation de la fonction dâĂ©valuation :
# Compute the sum of the distances between cities / the longer of the path
def fitness(solution, distance_matrix):
total_distance = 0
num_cities = len(solution)
for i in range(num_cities - 1):
city_from = solution[i][0]
city_to = solution[i + 1][0]
total_distance += distance_matrix[city_from-1][city_to-1]
return total_distance
print('solution:', sol)
print('distance:', fitness(sol, distance_matrix))
'''
output:
solution: [(1, 565.0, 575.0), (6, 880.0, 660.0), (4, 945.0, 685.0), (5, 845.0, 655.0), (2, 25.0, 185.0), (3, 345.0, 750.0), (1, 565.0, 575.0)]
distance: 2375.8976901086
'''
En poursuivant cette dĂ©marche, lâobjectif est de raffiner progressivement les solutions Ă travers les diverses itĂ©rations de lâalgorithme de recuit simulĂ©.
ⶠGénération de solutions voisines
Pour gĂ©nĂ©rer des solutions voisines, je mâinspire grandement du travail de Francis ALLANAH [2]. En effet, diffĂ©rents opĂ©rateurs de voisinages comme lâĂ©change (swap) de villes, lâinsertion dâune ville, ainsi que lâinsertion et lâinversion de sous-itinĂ©raires sont utilisĂ©s. Ces opĂ©rations permettent de diversifier la recherche de nouvelles solutions potentiellement meilleures.
- Swap Operator : Cet opĂ©rateur modifie une solution en Ă©changeant les positions de deux villes choisies alĂ©atoirement dans lâitinĂ©raire, Ă lâexception des Ă©lĂ©ments de dĂ©but et de fin.
# Swap Operator: This operator exchanges the positions of two cities in a route.
def swap_operator(solution):
new_solution = solution.copy()
# Randomly select two indexes, avoiding the first and last elements
idx1, idx2 = np.random.choice(range(1, len(new_solution) - 1), 2, replace=False)
# Perform the swap
new_solution[idx1], new_solution[idx2] = new_solution[idx2], new_solution[idx1]
return new_solution
print('solution:', sol)
print('swap_operator:', swap_operator(sol))
'''
output:
solution: [(1, 565.0, 575.0), (6, 880.0, 660.0), (4, 945.0, 685.0), (5, 845.0, 655.0), (2, 25.0, 185.0), (3, 345.0, 750.0), (1, 565.0, 575.0)]
swap_operator: [(1, 565.0, 575.0), (6, 880.0, 660.0), (5, 845.0, 655.0), (4, 945.0, 685.0), (2, 25.0, 185.0), (3, 345.0, 750.0), (1, 565.0, 575.0)]
'''
- Insert Operator : Il dĂ©place une ville dâune position alĂ©atoire Ă une autre dans lâitinĂ©raire, ce qui peut radicalement changer la configuration du parcours.
# Insert Operator: This operator moves a city from one random position to another.
def insert_operator(solution):
new_solution = solution.copy()
# Randomly select two indexes, avoiding the first and last elements
idx1, idx2 = np.random.choice(range(1, len(new_solution) - 1), 2, replace=False)
# Perform the insert operation
city_to_move = new_solution.pop(idx1)
new_solution.insert(idx2, city_to_move)
return new_solution
print('solution:', sol)
print('insert_operator:', insert_operator(sol))
'''
output:
solution: [(1, 565.0, 575.0), (6, 880.0, 660.0), (4, 945.0, 685.0), (5, 845.0, 655.0), (2, 25.0, 185.0), (3, 345.0, 750.0), (1, 565.0, 575.0)]
insert_operator: [(1, 565.0, 575.0), (4, 945.0, 685.0), (5, 845.0, 655.0), (2, 25.0, 185.0), (3, 345.0, 750.0), (6, 880.0, 660.0), (1, 565.0, 575.0)]
'''
- Inverse Subroutes Operator : Cette opĂ©ration inverse lâordre des villes dans un segment alĂ©atoire de lâitinĂ©raire, offrant une variation significative de la solution.
# Inverse Subroutes Operator: This operator reverses the order of the routes between two randomly selected nodes.
def inverse_subroutes_operator(solution):
new_solution = solution.copy()
# Select two random indexes and sort them
idx1, idx2 = sorted(np.random.choice(range(1, len(new_solution) - 1), 2, replace=False))
# Perform the inverse operation
new_solution[idx1:idx2+1] = reversed(new_solution[idx1:idx2+1])
return new_solution
print('solution:', sol)
print('inverse_operator:', inverse_subroutes_operator(sol))
'''
output:
solution: [(1, 565.0, 575.0), (6, 880.0, 660.0), (4, 945.0, 685.0), (5, 845.0, 655.0), (2, 25.0, 185.0), (3, 345.0, 750.0), (1, 565.0, 575.0)]
inverse_subroutes_operator: [(1, 565.0, 575.0), (6, 880.0, 660.0), (2, 25.0, 185.0), (5, 845.0, 655.0), (4, 945.0, 685.0), (3, 345.0, 750.0), (1, 565.0, 575.0)]
'''
- Insert Subroutes Operator : Cet opĂ©rateur sĂ©lectionne un sous-ensemble de lâitinĂ©raire et lâinsĂšre Ă une position diffĂ©rente, permettant une restructuration plus complĂšte de la solution.
# Insert Subroutes Operator: This operator selects a range of cities and inserts them at a random position.
def insert_subroutes_operator(solution):
# Ensure there are enough cities for the operation
if len(solution) <= 3:
return solution
new_solution = solution.copy()
# Select two random indexes and sort them
idx1, idx2 = sorted(np.random.choice(range(1, len(new_solution) - 1), 2, replace=False))
# Extract the subroute
subroute = new_solution[idx1:idx2+1]
# Remove the subroute from the original position
new_solution = new_solution[:idx1] + new_solution[idx2+1:]
# Insert the subroute at a new position
if len(new_solution) > len(subroute):
insert_idx = np.random.choice(range(1, len(new_solution) - len(subroute) + 1))
else:
insert_idx = 1 # or another backup logic if new_solution is too short
new_solution = new_solution[:insert_idx] + subroute + new_solution[insert_idx:]
return new_solution
print('solution:', sol)
print('insert_subroutes_operator:', insert_subroutes_operator(sol))
'''
output:
solution: [(1, 565.0, 575.0), (6, 880.0, 660.0), (4, 945.0, 685.0), (5, 845.0, 655.0), (2, 25.0, 185.0), (3, 345.0, 750.0), (1, 565.0, 575.0)]
insert_subroutes_operator: [(1, 565.0, 575.0), (2, 25.0, 185.0), (3, 345.0, 750.0), (6, 880.0, 660.0), (4, 945.0, 685.0), (5, 845.0, 655.0), (1, 565.0, 575.0)]
'''
Pour lâapplication de ces opĂ©rateurs Ă la solution, jâai Ă©laborĂ© une mĂ©thode qui les applique de façon pondĂ©rĂ©e et rĂ©pĂ©titive, en tenant compte de la tempĂ©rature de lâalgorithme.
Par âpondĂ©rĂ©â jâentends que, contrairement Ă une approche classique oĂč toutes les opĂ©rations possĂšdent une probabilitĂ© Ă©gale dâĂȘtre choisies, certaines opĂ©rations sont privilĂ©giĂ©es par rapport Ă dâautres. Autrement dit, des probabilitĂ©s diffĂ©rentes sont attribuĂ©es Ă chaque opĂ©rateur, reflĂ©tant leur âpoidsâ dans le processus de sĂ©lection. .
Quant Ă lâaspect ârĂ©pĂ©titifâ, cela signifie que la solution peut subir plusieurs modifications consĂ©cutives. En dâautres termes, les opĂ©rateurs de voisinage peuvent ĂȘtre appliquĂ©s de maniĂšre itĂ©rative sur une mĂȘme solution, permettant ainsi de gĂ©nĂ©rer des solutions nouvelles et potentiellement plus optimales.
Ă noter que notre mĂ©thode ressemble Ă la sĂ©lection de la Roulette-Wheel, mais avec une diffĂ©rence : les probabilitĂ©s associĂ©es Ă chaque option sont fixĂ©es manuellement au dĂ©but lors de lâinitialisation de la fonction. De plus, le processus peut ĂȘtre comparĂ© Ă faire tourner la roulette plusieurs fois, bien que le nombre de tours diminue Ă mesure que la tempĂ©rature baisse.
En effet, le nombre dâopĂ©rations effectuĂ©es est ajustĂ© dynamiquement en fonction de la tempĂ©rature mais aussi de la taille de la solution (le nombre de villes). Ainsi, il est proportionnel Ă la taille de la solution, tout en Ă©tant inversement proportionnel Ă la tempĂ©rature. Cette relation est dĂ©terminĂ©e par la formule suivante, que jâai Ă©laborĂ©e :
# the dimension of the solution: number of cities
dim = len(new_solution)
# Determines a scaling factor
factor = ((temperature)/(max_temperature))**(1/dim*10)
# Determines the number of operations to perform
nbr_operations = 1 + int(round(0.2 * dim * factor))
Le nombre dâopĂ©rations de voisinage Ă appliquer est fixĂ© Ă une base de 1, plus un nombre supplĂ©mentaire qui dĂ©pend de :
factor
un facteur dâĂ©chelle. Il calculĂ© en prenant le rapport de la tempĂ©rature actuelle sur la tempĂ©rature maximale (normalisant ainsi le facteur entre 0 et 1), puis en Ă©levant ce rapport Ă une puissance qui est inversĂ©ment proportionnelle Ă la dimension de la solution. Cela signifie que pour des solutions plus grandes (avec plus de villes), le facteur diminue et converge plus lentement, permettant ainsi potentiellement plus d'opĂ©rations de voisinage Ă des tempĂ©ratures plus Ă©levĂ©es. Cependant, la diminution est assez lente, dâoĂč la multiplication de dim par 10 dans le dĂ©nominateur pour accĂ©lĂ©rer la convergence.0.2*dim
un pourcentage de la dimension de la solution.
Cette approche garantit que :
- Davantage dâopĂ©rations sont effectuĂ©es Ă des tempĂ©ratures plus Ă©levĂ©es (exploration plus large) et moins Ă des tempĂ©ratures plus basses (exploration plus fine)
- Davantage dâopĂ©rations sont effectuĂ©es sur des solutions de grande taille et moins sur des solutions de plus petite taille.
Voici lâimplĂ©mentation final de la fonction pour la gĂ©nĂ©ration de solutions voisines :
# Neighbour Generation by Weighted Random Operator Choice
def neighbour(solution, temperature, max_temperature, weights=[50, 30, 10, 10]):
list_operator_choice = []
new_solution = solution.copy()
# the dimension of the solution.
dim = len(new_solution)
# Determines a scaling factor based on the current temperature and the maximum temperature,
factor = (temperature)**(1/dim*2) / (max_temperature)**(1/dim*2)
# Determines the number of operations (neighbourhood changes) to perform.
nbr_operations = 1 + int(round(0.2 * dim * factor))
for _ in range(nbr_operations):
operator_choice = random.choices([0,1,2,3], weights=weights)[0]
if operator_choice == 0:
new_solution = swap_operator(new_solution)
elif operator_choice == 1:
new_solution = insert_operator(new_solution)
elif operator_choice == 2 :
new_solution = inverse_subroutes_operator(new_solution)
else:
new_solution = insert_subroutes_operator(new_solution)
list_operator_choice.append(operator_choice)
return new_solution, list_operator_choice
print('solution:', sol)
new_solution, list_operator_choice = neighbour(sol, 500, 1000, weights=[50, 30, 10, 10])
print('new_solution:', new_solution)
print('nbr_operations:', len(list_operator_choice))
print('list_operator_choice:', list_operator_choice)
'''
output:
print('solution:', sol)
new_solution, list_operator_choice = neighbour(sol, 500, 1000, weights=[50, 30, 10, 10])
print('new_solution:', new_solution)
print('nbr_operations:', len(list_operator_choice))
print('list_operator_choice:', list_operator_choice)
'''
ⶠMécanisme de refroidissement
En effet, lâessence du recuit simulĂ© repose sur un mĂ©canisme de refroidissement [1] qui diminue progressivement la tempĂ©rature. Cette baisse de tempĂ©rature rĂ©duit la probabilitĂ© dâaccepter des solutions de moins bonne qualitĂ© au fil du temps.
Pour gĂ©rer cette dĂ©croissance, jâai optĂ© pour une approche continue, en utilisant une loi exponentielle commune [1] :
ⶠCritĂšre dâacceptation des nouvelles solutions
Le critĂšre dâacceptation des nouvelles solutions dans un algorithme de recuit simulĂ© joue un rĂŽle crucial pour lâĂ©quilibre entre lâexploration et lâexploitation dans la recherche de la meilleure solution.
Notre algorithme adopte lâapproche probabiliste classique en recuit simulĂ© pour dĂ©cider dâaccepter ou de rejeter les nouvelles solutions, en fonction de la diffĂ©rence de coĂ»t (la qualitĂ© de la solution) et de la tempĂ©rature actuelle du systĂšme [4].
Voici lâimplĂ©mentation de la fonction dâacceptance :
# Decision function to accept or reject the new point
def accept_probability(old_cost, new_cost, temperature):
if new_cost < old_cost:
return 1.0
else:
# The smaller the temperature, the smaller the probability of acceptance
return np.exp((old_cost - new_cost) / temperature) # between 0 and 1
ⶠCritĂšres dâarrĂȘt de lâalgorithme
Passons maintenant aux critĂšres dâarrĂȘt de lâalgorithme, qui reprĂ©sentent une autre dimension cruciale de notre approche. Le critĂšre dâarrĂȘt dâun algorithme de recuit simulĂ© est essentiel pour dĂ©terminer quand lâalgorithme doit cesser dâexĂ©cuter et renvoyer la meilleure solution trouvĂ©e.
Dans notre cas, plutĂŽt que de simplement arrĂȘter lâalgorithme lorsque la tempĂ©rature atteint zĂ©ro, nous allons nous arrĂȘter lorsque la mĂȘme solution candidate a Ă©tĂ© sĂ©lectionnĂ©e un certain nombre de fois [2].
Ce critĂšre augmente les chances de trouver la meilleure solution possible. Cette mĂ©thode est particuliĂšrement utile pour explorer de vastes espaces de recherche oĂč la convergence vers un optimum global est difficile Ă atteindre rapidement.
ⶠImplémentation final du recuit simulé
Ă prĂ©sent, vous trouverez mon implĂ©mentation final du recuit simulĂ© â sâappuyant sur les fonctions que nous avons soigneusement Ă©laborĂ©es auparavant â en accĂ©dant au repository github du projet :
đ Tests, RĂ©sultats et Performance
Pour cette Ă©valuer la performance de notre algorithme, nous avons choisi de nous appuyer sur des benchmarks disponibles sur le site TSPLIB : uni-heidelberg.de, Ă savoir :
- ulysses22.tsp : qui contient 22 villes
- berlin52.tsp : qui contient 52 villes
- st70.tsp : qui contient 70 villes
- ch130.tsp : qui contient 789 villes.. non 130 villes
Pour une analyse approfondie, nous enregistrons puis affichons le meilleur itinĂ©raire trouvĂ©, ainsi que lâhistorique des coĂ»ts, des tempĂ©ratures, des probabilitĂ©s dâacceptation et des choix dâopĂ©rateurs pour chaque itĂ©ration.
ⶠAffichage et Analyse des résultats
Nos résultats montrent comment le recuit simulé performe sur les benchmarks sélectionnés.
Voici quelques résultats avec différents paramÚtres en entrée :
Ces rĂ©sultats soulignent lâimportance de bien choisir les paramĂštres du recuit simulĂ© et de la stratĂ©gie de voisinage. Ils dĂ©montrent que la diversification des opĂ©rations de voisinage peut conduire Ă des rĂ©sultats nettement meilleurs, en Ă©vitant les piĂšges des optima locaux et en explorant plus efficacement lâespace des solutions.
ⶠAnalyse des paramĂštres de lâalgorithme
Par ailleurs, en parlant de bonne ajustage des paramÚtres, suite à de multiples expérimentations pour optimiser les performances et la qualité des solutions, nous avons observé les points suivants :
- La valeur initiale de la tempĂ©rature âinitial temperatureâ semble avoir un impact modĂ©rĂ© sur la qualitĂ© finale de la solution.
- A contrario, le taux de dĂ©croissance de la tempĂ©rature âgammaâ joue un rĂŽle important. Un âgammaâ Ă©levĂ© ralentit la convergence de lâalgorithme.
- Concernant la valeur 'max solution selectedâ, un nombre plus Ă©levĂ© offre des solutions de meilleure qualitĂ©, mais augmente Ă©galement le nombre dâitĂ©rations.
- Le choix des poids dâopĂ©rations de voisinage âweightsâ est crucial. De fait, les opĂ©rateurs dâĂ©change apportent des modifications plus subtiles par rapport aux opĂ©rateurs dâinsertion. Trouver un Ă©quilibre adĂ©quat entre eux est essentiel pour gĂ©nĂ©rer des solutions Ă la fois nouvelles et optimales, Ă©vitant ainsi une trop grande similitude ou divergence des itinĂ©raires proposĂ©s.
ⶠĂvaluation des performances de lâalgorithme
Au final, nous avons compilĂ© les performances de lâalgorithme sur les diffĂ©rents benchmarks dans un tableau rĂ©capitulatif, avec comme paramĂštres dâentrĂ©e : {âinitial temperatureâ: 10000, âgammaâ: 0.99, âmax solution selectedâ: 1500} avec des valeurs de âweightsâ diffĂ©rentes
On observe que les rĂ©sultats sont meilleurs, câest-Ă -dire possĂšde le plus faible taux dâerreur, lorsque la balance âopĂ©rateurs dâĂ©change & opĂ©rateurs dâinsertionâ est configurĂ© de la sorte : [40, 30, 35, 5]
â Piste dâamĂ©lioration
Les rĂ©sultats obtenus jusquâici sont encourageants et ouvrent la voie Ă de multiples ajustements et expĂ©rimentations susceptibles dâoptimiser davantage notre approche. AprĂšs un plus gros travail bibliographique, les pistes suivantes peuvent ĂȘtre envisagĂ©es pour peaufiner notre algorithme et Ă©largir le champ de nos recherches :
- Approfondissement significatif de la bibliographie, notamment autour du âMulti-Neighborhood simulated annealingâ â car honnĂȘtement, je nâai pas eu le temps de me pencher longuement lĂ -dessus â , pour renforcer la robustesse de notre approche et surtout lâaligner sur les derniĂšres avancĂ©es sur notre sujet.
- RĂ©vision globale de lâimplĂ©mentation du code en se concentrant sur la rĂ©duction de sa complexitĂ©, avec notamment dans changement dans le suivi dĂ©taillĂ© de lâhistorique qui, bien que prĂ©cieux pour lâanalyse, pourrait ĂȘtre omis ou simplifiĂ© pour allĂ©ger les calculs et amĂ©liorer lâefficacitĂ© globale.
- Tests sur un spectre plus large de benchmarks, particuliĂšrement avec des TSP de plus grandes dimensions pour Ă©valuer lâefficacitĂ© de lâalgorithme Ă plus grande Ă©chelle.
- Adoption de stratĂ©gies de refroidissement alternatives, comme des paliers de tempĂ©rature fixes, pour une exploration plus mĂ©thodique de lâespace des solutions.
- Application dâoptimisations dâhyperparamĂštres automatisĂ©es, telles que la recherche par grille ou bayĂ©sienne, pour identifier les configurations optimales de lâalgorithme.
đ Conclusion
En rĂ©sumĂ©, notre article a confirmĂ© lâefficacitĂ© du recuit simulĂ© dans la recherche de solutions approchĂ©es pour le problĂšme du voyageur de commerce.
Par dessus tout, il est met en avant la bonne performance â bien quâelle reste perfectible â de notre approche de recuit simulĂ© avec pondĂ©ration et opĂ©rations de voisinage multiples, celle-ci permettant une exploration encore plus agressive et diversifiĂ©e des solutions au dĂ©but.
En dehors de tout cela, on peut remarquer que les opĂ©rateurs de voisinage utilisĂ©s suggĂšrent un potentiel intĂ©ressant sâils Ă©taient intĂ©grĂ©s comme opĂ©rateurs de mutation dans un algorithme gĂ©nĂ©tique. Cette hybridation pourrait potentiellement combiner les meilleures caractĂ©ristiques des deux approches, menant Ă des solutions encore plus performantes pour le TSP et au-delĂ . Peut-ĂȘtre que cette synergie prometteuse Ă dĂ©jĂ Ă©tĂ© abordĂ© dans des papiers scientifiques, Ă voir encore une fois.
Mlachahe SAID SALIMO.
Article rĂ©digĂ© suite Ă un travail pratique du professeur Salim BITAM, de lâUniversitĂ© de Biskra.
Références
- Recuit Simulé, la page Wikipédia.
- Travelling Salesman Problem Using Simulated Annealing, lâarticle issu du blog Medium de Francis ALLANAH (2022).
- List-Based Simulated Annealing Algorithm for Traveling Salesman Problem, un article scientifique de S. Zhan, J. Lin, Z. Zhang, et Y. Zhong.
- Simulated annealing applied to the traveling salesman problem, lâarticle issu du blog Emmanuel GOOSAERT (2010).
Pour aller plus loin :
- Performance Comparison of Simulated Annealing, GA and ACO Applied to TSP, article scientifique de H. Mukhairez et A. Maghari (2015)
- Genetic Algorithms: Comparing Evolution With and Without a Simulated Annealing-inspired Selection, manuscript de Mats ANDERSSON & David MELLIN (2019)