Optimisation de la colonie de fourmis

Optimisation de la colonie de fourmis

1. introduction

The aim of this series est àexplain the idea of genetic algorithms and show the most known implementations.

Dans ce tutoriel, nous allonsdescribe the concept of the ant colony optimization (ACO), suivi de l'exemple de code.

2. Comment fonctionne ACO

ACO est un algorithme génétique inspiré du comportement naturel d’une fourmi. Pour bien comprendre l’algorithme ACO, nous devons nous familiariser avec ses concepts de base:

  • les fourmis utilisent des phéromones pour trouver le chemin le plus court entre la maison et la source de nourriture

  • les phéromones s'évaporent rapidement

  • les fourmis préfèrent utiliser des chemins plus courts avec une phéromone plus dense

Montrons un exemple simple d’ACO utilisé dans lesTraveling Salesman Problem. Dans le cas suivant, nous devons trouver le chemin le plus court entre tous les nœuds du graphique:

 

image Suite à des comportements naturels, les fourmis commenceront à explorer de nouveaux chemins pendant l'exploration. La couleur bleue plus forte indique les chemins utilisés plus souvent que les autres, tandis que la couleur verte indique le chemin le plus court actuellement trouvé:

 

imageEn conséquence, nous obtiendrons le chemin le plus court entre tous les nœuds:

 

imageLe bel outil basé sur l'interface graphique pour les tests ACO peut être trouvéhere.

3. Implémentation Java

3.1. Paramètres ACO

Discutons des principaux paramètres de l'algorithme ACO, déclarés dans la classeAntColonyOptimization:

private double c = 1.0;
private double alpha = 1;
private double beta = 5;
private double evaporation = 0.5;
private double Q = 500;
private double antFactor = 0.8;
private double randomFactor = 0.01;

Le paramètrec indique le nombre d'origine de traces, au début de la simulation. De plus,alpha contrôle l'importance des phéromones, tandis quebeta contrôle la priorité de distance. In general, the beta parameter should be greater than alpha for the best results.

Ensuite, la variableevaporation indique le pourcentage d'évaporation de la phéromone à chaque itération, tandis queQ fournit des informations sur la quantité totale de phéromone laissée sur la piste par chaqueAnt etantFactor nous indique combien de fourmis nous utiliserons par ville.

Enfin, nous devons avoir un peu d'aléatoire dans nos simulations, et cela est couvert parrandomFactor.

3.2. Créer des fourmis

ChaqueAnt pourra visiter une ville spécifique, se souvenir de toutes les villes visitées et suivre la longueur du sentier:

public void visitCity(int currentIndex, int city) {
    trail[currentIndex + 1] = city;
    visited[city] = true;
}

public boolean visited(int i) {
    return visited[i];
}

public double trailLength(double graph[][]) {
    double length = graph[trail[trailSize - 1]][trail[0]];
    for (int i = 0; i < trailSize - 1; i++) {
        length += graph[trail[i]][trail[i + 1]];
    }
    return length;
}

3.3. Configurer les fourmis

Au tout début, nous devonsinitialize our ACO code implementation en fournissant des matrices de sentiers et de fourmis:

graph = generateRandomMatrix(noOfCities);
numberOfCities = graph.length;
numberOfAnts = (int) (numberOfCities * antFactor);

trails = new double[numberOfCities][numberOfCities];
probabilities = new double[numberOfCities];
ants = new Ant[numberOfAnts];
IntStream.range(0, numberOfAnts).forEach(i -> ants.add(new Ant(numberOfCities)));

Ensuite, nous devonssetup the ants matrix pour commencer avec une ville aléatoire:

public void setupAnts() {
    IntStream.range(0, numberOfAnts)
      .forEach(i -> {
          ants.forEach(ant -> {
              ant.clear();
              ant.visitCity(-1, random.nextInt(numberOfCities));
          });
      });
    currentIndex = 0;
}

Pour chaque itération de la boucle, nous allons effectuer les opérations suivantes:

IntStream.range(0, maxIterations).forEach(i -> {
    moveAnts();
    updateTrails();
    updateBest();
});

3.4. Déplacer les fourmis

Commençons par la méthodemoveAnts(). Nous devonschoose the next city for all ants, se souvenir que chaque fourmi essaie de suivre les traces des autres fourmis:

public void moveAnts() {
    IntStream.range(currentIndex, numberOfCities - 1).forEach(i -> {
        ants.forEach(ant -> {
            ant.visitCity(currentIndex, selectNextCity(ant));
        });
        currentIndex++;
    });
}

The most important part is to properly select next city to visit. Nous devons sélectionner la ville suivante en fonction de la logique de probabilité. Tout d'abord, nous pouvons vérifier siAnt doit visiter une ville aléatoire:

int t = random.nextInt(numberOfCities - currentIndex);
if (random.nextDouble() < randomFactor) {
    OptionalInt cityIndex = IntStream.range(0, numberOfCities)
      .filter(i -> i == t && !ant.visited(i))
      .findFirst();
    if (cityIndex.isPresent()) {
        return cityIndex.getAsInt();
    }
}

Si nous n'avons sélectionné aucune ville au hasard, nous devons calculer les probabilités pour sélectionner la ville suivante, en nous rappelant que les fourmis préfèrent suivre des sentiers plus forts et plus courts. Nous pouvons le faire en stockant la probabilité de déplacement vers chaque ville du tableau:

public void calculateProbabilities(Ant ant) {
    int i = ant.trail[currentIndex];
    double pheromone = 0.0;
    for (int l = 0; l < numberOfCities; l++) {
        if (!ant.visited(l)){
            pheromone
              += Math.pow(trails[i][l], alpha) * Math.pow(1.0 / graph[i][l], beta);
        }
    }
    for (int j = 0; j < numberOfCities; j++) {
        if (ant.visited(j)) {
            probabilities[j] = 0.0;
        } else {
            double numerator
              = Math.pow(trails[i][j], alpha) * Math.pow(1.0 / graph[i][j], beta);
            probabilities[j] = numerator / pheromone;
        }
    }
}

Après avoir calculé les probabilités, nous pouvons décider dans quelle ville aller en utilisant:

double r = random.nextDouble();
double total = 0;
for (int i = 0; i < numberOfCities; i++) {
    total += probabilities[i];
    if (total >= r) {
        return i;
    }
}

3.5. Mettre à jour les sentiers

Dans cette étape, nous devrions mettre à jour les traces et la phéromone de gauche:

public void updateTrails() {
    for (int i = 0; i < numberOfCities; i++) {
        for (int j = 0; j < numberOfCities; j++) {
            trails[i][j] *= evaporation;
        }
    }
    for (Ant a : ants) {
        double contribution = Q / a.trailLength(graph);
        for (int i = 0; i < numberOfCities - 1; i++) {
            trails[a.trail[i]][a.trail[i + 1]] += contribution;
        }
        trails[a.trail[numberOfCities - 1]][a.trail[0]] += contribution;
    }
}

3.6. Mettez à jour la meilleure solution

C'est la dernière étape de chaque itération. Nous devons mettre à jour la meilleure solution afin de conserver la référence:

private void updateBest() {
    if (bestTourOrder == null) {
        bestTourOrder = ants[0].trail;
        bestTourLength = ants[0].trailLength(graph);
    }
    for (Ant a : ants) {
        if (a.trailLength(graph) < bestTourLength) {
            bestTourLength = a.trailLength(graph);
            bestTourOrder = a.trail.clone();
        }
    }
}

Après toutes les itérations, le résultat final indiquera le meilleur chemin trouvé par ACO. Please note that by increasing the number of cities, the probability of finding the shortest path decreases.

4. Conclusion

Ce tutorielintroduces the Ant Colony Optimization algorithm. Vous pouvez en apprendre davantage sur les algorithmes génétiqueswithout any previous knowledgede ce domaine, n'ayant que des compétences de base en programmation informatique.

Le code source complet des extraits de code de ce didacticiel est disponiblein the GitHub project.

Pour tous les articles de la série, y compris d'autres exemples d'algorithmes génétiques, consultez les liens suivants: