Algorithme d’optimisation multi-essaims en Java

Algorithme d'optimisation multi-groupes en Java

1. introduction

Dans cet article, nous allons examiner un algorithme d'optimisation multi-essaim. Comme d’autres algorithmes de la même classe, son objectif est de trouver la meilleure solution à un problème en maximisant ou en minimisant une fonction spécifique, appelée fonction de mise en forme.

Commençons par une théorie.

2. Fonctionnement de l'optimisation multi-essaims

Le Multi-swarm est une variante de l'algorithmeSwarm. Comme son nom l'indique,the Swarm algorithm solves a problem by simulating the movement of a group of objects in the space of possible solutions. Dans la version multi-essaims, il y a plusieurs essaims au lieu d'un seul.

Le composant de base d'un essaim s'appelle une particule. La particule est définie par sa position réelle, qui est également une solution possible à notre problème, et par sa vitesse, utilisée pour calculer la position suivante.

La vitesse de la particule change constamment, menant vers la meilleure position trouvée parmi toutes les particules de tous les essaims avec un certain degré d'aléatoire pour augmenter la quantité d'espace couvert.

Cela conduit finalement la plupart des particules à un ensemble fini de points qui sont des minima ou des maxima locaux dans la fonction de fitness, selon que nous essayons de le minimiser ou de le maximiser.

Bien que le point trouvé soit toujours un minimum ou un maximum local de la fonction, ce n’est pas nécessairement un point global car il n’ya aucune garantie que l’algorithme ait complètement exploré l’espace des solutions.

Pour cette raison, on dit que le multi-essaim est un métaheuristique -the solutions it finds are among the best, but they may not be the absolute best.

3. la mise en oeuvre

Maintenant que nous savons ce qu'est un multi-essaim et comment il fonctionne, voyons comment le mettre en œuvre.

Pour notre exemple, nous allons essayer d’adresserthis real-life optimization problem posted on StackExchange:

Dans League of Legends, la santé effective d’un joueur en défense contre les dégâts physiques est donnée parE=H(100+A)/100, oùH est la santé etA l’armure.

Santé coûte 2,5 or par unité et Armor 18 or par unité. Vous avez 3600 PO et vous devez optimiser l’efficacitéEde votre santé et de votre armure pour survivre le plus longtemps possible contre les attaques de l’équipe ennemie. Combien de chaque devriez-vous acheter?

3.1. Particule

Nous commençons par modéliser notre construction de base, une particule. L'état d'une particule inclut sa position actuelle, qui correspond à une paire de valeurs de santé et d'armure qui résolvent le problème, à la vitesse de la particule sur les deux axes et au score de forme des particules.

Nous enregistrerons également la meilleure position et le meilleur score de forme que nous trouvons, car nous en aurons besoin pour mettre à jour la vitesse des particules:

public class Particle {
    private long[] position;
    private long[] speed;
    private double fitness;
    private long[] bestPosition;
    private double bestFitness = Double.NEGATIVE_INFINITY;

    // constructors and other methods
}

Nous choisissons d'utiliser les tableauxlong pour représenter à la fois la vitesse et la position car nous pouvons déduire de l'énoncé du problème que nous ne pouvons pas acheter des fractions d'armure ou de santé, donc la solution doit être dans le domaine entier.

Nous ne voulons pas utiliserint car cela peut entraîner des problèmes de dépassement lors des calculs.

3.2. Essaim

Ensuite, définissons un essaim comme un ensemble de particules. Une fois de plus, nous enregistrerons également la meilleure position historique et le meilleur score pour un calcul ultérieur.

L'essaim devra également s'occuper de l'initialisation de ses particules en attribuant une position et une vitesse initiales aléatoires à chacune d'entre elles.

Nous pouvons estimer approximativement une limite pour la solution, nous ajoutons donc cette limite au générateur de nombres aléatoires.

Cela réduira la puissance de calcul et le temps nécessaire pour exécuter l'algorithme:

public class Swarm {
    private Particle[] particles;
    private long[] bestPosition;
    private double bestFitness = Double.NEGATIVE_INFINITY;

    public Swarm(int numParticles) {
        particles = new Particle[numParticles];
        for (int i = 0; i < numParticles; i++) {
            long[] initialParticlePosition = {
              random.nextInt(Constants.PARTICLE_UPPER_BOUND),
              random.nextInt(Constants.PARTICLE_UPPER_BOUND)
            };
            long[] initialParticleSpeed = {
              random.nextInt(Constants.PARTICLE_UPPER_BOUND),
              random.nextInt(Constants.PARTICLE_UPPER_BOUND)
            };
            particles[i] = new Particle(
              initialParticlePosition, initialParticleSpeed);
        }
    }

    // methods omitted
}

3.3. Multiswarm

Enfin, terminons notre modèle en créant une classe Multiswarm.

À l'instar de l'essaim, nous garderons une trace d'une collection d'essaims et de la meilleure position et de la meilleure forme de particule parmi tous les essaims.

Nous enregistrerons également une référence à la fonction de remise en forme pour une utilisation ultérieure:

public class Multiswarm {
    private Swarm[] swarms;
    private long[] bestPosition;
    private double bestFitness = Double.NEGATIVE_INFINITY;
    private FitnessFunction fitnessFunction;

    public Multiswarm(
      int numSwarms, int particlesPerSwarm, FitnessFunction fitnessFunction) {
        this.fitnessFunction = fitnessFunction;
        this.swarms = new Swarm[numSwarms];
        for (int i = 0; i < numSwarms; i++) {
            swarms[i] = new Swarm(particlesPerSwarm);
        }
    }

    // methods omitted
}

3.4. Fonction de remise en forme

Implémentons maintenant la fonction de remise en forme.

Pour dissocier la logique de l'algorithme de ce problème spécifique, nous allons introduire une interface avec une seule méthode.

Cette méthode prend une position de particule en tant qu'argument et renvoie une valeur indiquant sa qualité:

public interface FitnessFunction {
    public double getFitness(long[] particlePosition);
}

Pourvu que le résultat trouvé soit valide en fonction des contraintes du problème, mesurer l'aptitude consiste simplement à rétablir la santé effective calculée que nous souhaitons maximiser.

Pour notre problème, nous avons les contraintes de validation spécifiques suivantes:

  • les solutions ne doivent être que des entiers positifs

  • les solutions doivent être réalisables avec la quantité d'or fournie

Lorsqu'une de ces contraintes est violée, nous renvoyons un nombre négatif qui indique à quelle distance nous sommes de la limite de validité.

C'est soit le nombre trouvé dans le premier cas, soit la quantité d'or non disponible dans le second:

public class LolFitnessFunction implements FitnessFunction {

    @Override
    public double getFitness(long[] particlePosition) {
        long health = particlePosition[0];
        long armor = particlePosition[1];

        if (health < 0 && armor < 0) {
            return -(health * armor);
        } else if (health < 0) {
            return health;
        } else if (armor < 0) {
            return armor;
        }

        double cost = (health * 2.5) + (armor * 18);
        if (cost > 3600) {
            return 3600 - cost;
        } else {
            long fitness = (health * (100 + armor)) / 100;
            return fitness;
        }
    }
}

3.5. Boucle principale

Le programme principal effectuera une itération entre toutes les particules de tous les essaims et procédera comme suit:

  • calculer la forme des particules

  • si une nouvelle meilleure position a été trouvée, met à jour l'historique des particules, des essaims et des multiswarm

  • calculer la nouvelle position des particules en ajoutant la vitesse actuelle à chaque dimension

  • calculer la nouvelle vitesse de particule

Pour le moment, nous allons laisser la mise à jour rapide à la section suivante en créant une méthode dédiée:

public void mainLoop() {
    for (Swarm swarm : swarms) {
        for (Particle particle : swarm.getParticles()) {
            long[] particleOldPosition = particle.getPosition().clone();
            particle.setFitness(fitnessFunction.getFitness(particleOldPosition));

            if (particle.getFitness() > particle.getBestFitness()) {
                particle.setBestFitness(particle.getFitness());
                particle.setBestPosition(particleOldPosition);
                if (particle.getFitness() > swarm.getBestFitness()) {
                    swarm.setBestFitness(particle.getFitness());
                    swarm.setBestPosition(particleOldPosition);
                    if (swarm.getBestFitness() > bestFitness) {
                        bestFitness = swarm.getBestFitness();
                        bestPosition = swarm.getBestPosition().clone();
                    }
                }
            }

            long[] position = particle.getPosition();
            long[] speed = particle.getSpeed();
            position[0] += speed[0];
            position[1] += speed[1];
            speed[0] = getNewParticleSpeedForIndex(particle, swarm, 0);
            speed[1] = getNewParticleSpeedForIndex(particle, swarm, 1);
        }
    }
}

3.6. Mise à jour de la vitesse

Il est essentiel que la particule change de vitesse car c’est ainsi qu’elle parvient à explorer différentes solutions possibles.

La vitesse de la particule devra faire en sorte que la particule se déplace vers la meilleure position trouvée par elle-même, par son essaim et par tous les essaims, en attribuant un certain poids à chacun d'entre eux. Nous appellerons ces poids,cognitive weight, social weight and global weight, respectivement.

Pour ajouter une certaine variation, nous multiplierons chacun de ces poids par un nombre aléatoire compris entre 0 et 1. We’ll also add an inertia factor to the formula qui incite la particule à ne pas trop ralentir:

private int getNewParticleSpeedForIndex(
  Particle particle, Swarm swarm, int index) {

    return (int) ((Constants.INERTIA_FACTOR * particle.getSpeed()[index])
      + (randomizePercentage(Constants.COGNITIVE_WEIGHT)
      * (particle.getBestPosition()[index] - particle.getPosition()[index]))
      + (randomizePercentage(Constants.SOCIAL_WEIGHT)
      * (swarm.getBestPosition()[index] - particle.getPosition()[index]))
      + (randomizePercentage(Constants.GLOBAL_WEIGHT)
      * (bestPosition[index] - particle.getPosition()[index])));
}

Les valeurs acceptées pour les poids d’inertie, cognitif, social et global sont respectivement 0,729, 1,49445, 1,49445 et 0,3645.

4. Conclusion

Dans ce tutoriel, nous avons abordé la théorie et la mise en œuvre d'un algorithme en essaim. Nous avons également vu comment concevoir une fonction de fitness en fonction d'un problème spécifique.

Si vous souhaitez en savoir plus sur ce sujet, jetez un œil auxthis book etthis article qui ont également été utilisés comme sources d'informations pour cet article.

Comme toujours, tout le code de l'exemple est disponibleover on the GitHub project.