🧭 Sur la Route de l’Optimum : Recuit SimulĂ© pour le TSP

Recuit simulé avec pondération et opérations de voisinage multiples

Mlachahe Said Salimo
Wanabilini
15 min readJan 7, 2024

--

Plongeons une fois de plus dans le monde fascinant des mĂ©taheuristiques, un domaine que j’explore avec un enthousiasme renouvelĂ© Ă  chaque nouvel article.

‘La Nuit Ă©toilĂ©e’ de Vincent Van Gogh [source], qui capte l’essence mĂȘme des mĂ©taheuristiques oĂč l’ordre Ă©merge du chaos

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.

Résolution du Traveling Salesman Problem avec des villes des USA par une métaheuristique [source]

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.

Illustration des différentes étapes du recuit simulé [souce]

▶ 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.

Avec la sélection de la Roulette-Wheel, un choix possédant une plus grande part sur la roue a une plus grande chance de se retrouver devant le point fixe lorsque la roue est tournée [source]

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] :

Loi de décroissance exponentielle avec un facteur λ<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 :

RĂ©sultats avec les paramĂštres suivants : {‘initial temperature’: 10000, ‘gamma’: 0.99, ‘max solution selected’: 1500, ‘weights’: [100, 0, 0, 0]}
RĂ©sultats avec les paramĂštres suivants : {‘initial temperature’: 10000, ‘gamma’: 0.99, ‘max solution selected’: 1500, ‘weights’: [50, 15, 30, 5]}

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

Tableau rĂ©capitulatif des rĂ©sultats avec les paramĂštres suivant : {‘initial temperature’: 10000, ‘gamma’: 0.99, ‘max solution selected’: 1500} et 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

  1. Recuit Simulé, la page Wikipédia.
  2. Travelling Salesman Problem Using Simulated Annealing, l’article issu du blog Medium de Francis ALLANAH (2022).
  3. List-Based Simulated Annealing Algorithm for Traveling Salesman Problem, un article scientifique de S. Zhan, J. Lin, Z. Zhang, et Y. Zhong.
  4. Simulated annealing applied to the traveling salesman problem, l’article issu du blog Emmanuel GOOSAERT (2010).

Pour aller plus loin :

--

--

Mlachahe Said Salimo
Wanabilini

French Student in Computer Science for Health. J'Ă©cris mes notes ici + J'Ă©cris pour apprendre, pas parce que je sais !