Otimização de colônias de formigas

Otimização de colônias de formigas

1. Introdução

The aim of this series é paraexplain the idea of genetic algorithms and show the most known implementations.

Neste tutorial, vamosdescribe the concept of the ant colony optimization (ACO), seguido pelo exemplo de código.

2. Como ACO funciona

O ACO é um algoritmo genético inspirado no comportamento natural de uma formiga. Para entender completamente o algoritmo ACO, precisamos nos familiarizar com seus conceitos básicos:

  • formigas usam feromônios para encontrar o caminho mais curto entre a fonte doméstica e a comida

  • os feromônios evaporam rapidamente

  • formigas preferem usar caminhos mais curtos com feromônio mais denso

Vamos mostrar um exemplo simples de ACO usado emTraveling Salesman Problem. No caso a seguir, precisamos encontrar o caminho mais curto entre todos os nós no gráfico:

 

imageSeguindo os comportamentos naturais, as formigas começarão a explorar novos caminhos durante a exploração. A cor azul mais forte indica os caminhos usados ​​com mais frequência que os outros, enquanto a cor verde indica o caminho mais curto atual encontrado:

 

imageComo resultado, alcançaremos o caminho mais curto entre todos os nós:

 

imageA boa ferramenta baseada em GUI para testes ACO pode ser encontradahere.

3. Implementação Java

3.1. Parâmetros ACO

Vamos discutir os principais parâmetros do algoritmo ACO, declarados na 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;

O parâmetroc indica o número original de trilhas, no início da simulação. Além disso,alpha controla a importância do feromônio, enquantobeta controla a prioridade de distância. In general, the beta parameter should be greater than alpha for the best results.

Em seguida, a variávelevaporation mostra a porcentagem de quanto o feromônio está evaporando em cada iteração, enquantoQ fornece informações sobre a quantidade total de feromônio deixado na trilha por cadaAnt eantFactor nos diz quantas formigas usaremos por cidade.

Finalmente, precisamos ter um pouco de aleatoriedade em nossas simulações, e isso é coberto porrandomFactor.

3.2. Crie formigas

CadaAnt será capaz de visitar uma cidade específica, lembrar de todas as cidades visitadas e acompanhar a extensão da trilha:

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. Configurar formigas

No início, precisamosinitialize our ACO code implementation fornecendo trilhas e matrizes de formigas:

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)));

Em seguida, precisamossetup the ants matrix para começar com uma cidade aleatória:

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

Para cada iteração do loop, realizaremos as seguintes operações:

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

3.4. Mover formigas

Vamos começar com o métodomoveAnts(). Precisamoschoose the next city for all ants, lembrando que cada formiga tenta seguir as trilhas das outras formigas:

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. Devemos selecionar a próxima cidade com base na lógica de probabilidade. Primeiro, podemos verificar seAnt deve visitar uma cidade aleatória:

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();
    }
}

Se não selecionamos nenhuma cidade aleatória, precisamos calcular as probabilidades de selecionar a próxima cidade, lembrando que as formigas preferem seguir trilhas mais fortes e mais curtas. Podemos fazer isso armazenando a probabilidade de mudar para cada cidade na matriz:

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;
        }
    }
}

Depois de calcular as probabilidades, podemos decidir para qual cidade ir usando:

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

3.5. Trilhas de atualização

Nesta etapa, devemos atualizar as trilhas e o feromônio esquerdo:

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. Atualize a melhor solução

Esta é a última etapa de cada iteração. Precisamos atualizar a melhor solução para manter a referência a ela:

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();
        }
    }
}

Após todas as iterações, o resultado final indicará o melhor caminho encontrado pelo ACO. Please note that by increasing the number of cities, the probability of finding the shortest path decreases.

4. Conclusão

Este tutorialintroduces the Ant Colony Optimization algorithm. Você pode aprender sobre algoritmos genéticoswithout any previous knowledge desta área, tendo apenas habilidades básicas de programação de computadores.

O código-fonte completo para os trechos de código neste tutorial está disponívelin the GitHub project.

Para todos os artigos da série, incluindo outros exemplos de algoritmos genéticos, confira os seguintes links: