Алгоритм оптимизации нескольких роев в Java

Алгоритм оптимизации множества роев в Java

1. Вступление

В этой статье мы рассмотрим алгоритм оптимизации с несколькими роями. Как и другие алгоритмы того же класса, его цель - найти лучшее решение проблемы путем максимизации или минимизации конкретной функции, называемой фитнес-функцией.

Начнем с теории.

2. Как работает оптимизация с несколькими роями

Мульти-рой - это вариант алгоритмаSwarm. Как следует из названия,the Swarm algorithm solves a problem by simulating the movement of a group of objects in the space of possible solutions.. В версии с несколькими роями существует несколько роев, а не один.

Основной компонент роя называется частицей. Частица определяется ее фактическим положением, которое также является возможным решением нашей проблемы, и ее скоростью, которая используется для расчета следующего положения.

Скорость частицы постоянно изменяется, склоняясь к лучшему положению, найденному среди всех частиц во всех скоплениях, с определенной степенью случайности, чтобы увеличить количество покрытого пространства.

В конечном итоге это приводит большинство частиц к конечному набору точек, которые являются локальными минимумами или максимумами функции приспособленности, в зависимости от того, пытаемся ли мы ее минимизировать или максимизировать.

Хотя найденная точка всегда является локальным минимумом или максимумом функции, она не обязательно является глобальной, поскольку нет гарантии, что алгоритм полностью исследовал пространство решений.

По этой причине мульти-рой называется метаэвристическим -the solutions it finds are among the best, but they may not be the absolute best.

3. Реализация

Теперь, когда мы знаем, что такое мульти-рой и как он работает, давайте посмотрим, как его реализовать.

В нашем примере мы попытаемся обратиться кthis real-life optimization problem posted on StackExchange:

В League of Legends эффективное здоровье игрока при защите от физического урона выражается вE=H(100+A)/100, гдеH - здоровье, аA - броня.

Здоровье стоит 2,5 золота за единицу, а броня стоит 18 золота за единицу. У вас есть 3600 золота, и вам нужно оптимизироватьEэффективности вашего здоровья и брони, чтобы выжить как можно дольше против атак вражеской команды. Сколько из каждого вы должны купить?

3.1. Частицы

Мы начнем с моделирования нашей базовой конструкции, частицы. Состояние частицы включает ее текущее положение, которое представляет собой пару значений здоровья и брони, которые решают проблему, скорость частицы по обеим осям и показатель пригодности частиц.

Мы также сохраним лучшую позицию и оценку физической подготовки, которые мы найдем, поскольку они нам понадобятся для обновления скорости частиц:

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
}

Мы решили использовать массивыlong для представления как скорости, так и положения, потому что мы можем сделать вывод из постановки задачи, что мы не можем купить доли брони или здоровья, поэтому решение должно быть в целочисленной области.

Мы не хотим использоватьint, потому что это может вызвать проблемы с переполнением во время вычислений.

3.2. Рой

Теперь давайте определим рой как набор частиц. И снова мы сохраним историческую лучшую позицию и оценку для последующих вычислений.

Рой также должен позаботиться об инициализации своих частиц, назначив случайную начальную позицию и скорость каждой из них.

Мы можем приблизительно оценить границу для решения, поэтому мы добавляем этот предел к генератору случайных чисел.

Это уменьшит вычислительную мощность и время, необходимое для запуска алгоритма:

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

Наконец, давайте завершим нашу модель созданием класса Multiswarm.

Как и в случае с роем, мы будем отслеживать коллекцию роев, а также лучшее положение и приспособленность частиц среди всех роев.

Мы также сохраним ссылку на фитнес-функцию для дальнейшего использования:

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. Фитнес-функция

Теперь реализуем фитнес-функцию.

Чтобы отделить логику алгоритма от этой конкретной проблемы, мы представим интерфейс с одним методом.

Этот метод принимает положение частицы в качестве аргумента и возвращает значение, указывающее, насколько он хорош:

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

При условии, что найденный результат действителен в соответствии с ограничениями задачи, измерение пригодности - это всего лишь вопрос возвращения вычисленного эффективного здоровья, которое мы хотим максимизировать.

Для нашей проблемы у нас есть следующие конкретные ограничения проверки:

  • решения должны быть только положительными целыми числами

  • решения должны быть осуществимы с предоставленным количеством золота

Когда одно из этих ограничений нарушается, мы возвращаем отрицательное число, которое показывает, как далеко мы отошли от границы действительности.

Это либо число, найденное в первом случае, либо количество недоступного золота во втором:

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. Основной цикл

Основная программа будет перебирать все частицы во всех скоплениях и делать следующее:

  • вычислить пригодность частиц

  • если найдена новая лучшая позиция, обновите историю частиц, роя и многогрева

  • вычислить новую позицию частиц, добавив текущую скорость к каждому измерению

  • вычислить новую скорость частиц

На данный момент мы оставим обновление скорости для следующего раздела, создав специальный метод:

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. Скорость обновления

Для частицы очень важно изменять свою скорость, поскольку именно так ей удается исследовать различные возможные решения.

Скорость частицы должна будет заставить частицу двигаться к лучшему положению, найденному ею самим, ее роем и всеми роями, назначая определенный вес каждому из них. Назовем эти веса соответственноcognitive weight, social weight and global weight.

Чтобы добавить вариации, мы умножим каждый из этих весов на случайное число от 0 до 1. We’ll also add an inertia factor to the formula, который побуждает частицу не замедляться слишком сильно:

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

Допустимые значения инерции, когнитивного, социального и глобального весов составляют 0,729, 1,49445, 1,49445 и 0,3645 соответственно.

4. Заключение

В этом уроке мы рассмотрели теорию и реализацию алгоритма роя. Мы также увидели, как спроектировать фитнес-функцию в соответствии с конкретной проблемой.

Если вы хотите узнать больше об этой теме, взгляните наthis book иthis article, которые также использовались в качестве источников информации для этой статьи.

Как всегда, доступен весь код примераover on the GitHub project.