Реализация генератора состязательных примеров с использованием эволюционного алгоритма работает плохо - PullRequest
1 голос
/ 13 мая 2019

Я пытаюсь реализовать эволюционный алгоритм, используя эволюционную стратегию MAP-Elites и оператор полиномиальной мутации (как определено здесь в пункте 2.), как обсуждалось в этой статье. Я создал три разные модели MNIST и обучил их, используя tenorflow == 2.0.0a, используя API Keras Модели работают нормально (точность около 95%).

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

Проблема в том, что мои модели классифицируют случайный ввод, созданный с равномерным распределением, всегда как один и тот же класс с высокой степенью достоверности (то есть модель CNN всегда классифицирует его как 8). Таким образом, большинство или все образцы, с которыми я получаю доступ, классифицируются как один и тот же класс (с незначительно изменяющимися степенями принадлежности к другим классам) даже после большой начальной популяции и числа итераций (т.е. 1000 исходных образцов и 20000 итераций).

Входные выборки нормированы на диапазон [0.0, 1.0]. Все рассуждения, приведенные ниже, ограничены для упрощения только плотной моделью (CNN и упрощенный LeNet5, дающей аналогичные результаты), описанной внизу.

Использование нормального распределения со средним значением = 0,0 и стандартным значением = 0,3 или средним значением = 0,5 и стандартным стандартным значением = 0,3 для создания начальной популяции и вероятности мутации 0,3 (вместо 0,1, как в статье) дает аналогичные результаты.

Я пытался использовать (1, λ) эволюционную стратегию с таргетингом только на один класс (начальная популяция 100, 100 поколений), и она дает лучшие результаты, чем реализованная ниже MAP-Elites (я могу сгенерировать образец для более чем одного класса).

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

Отключение увеличения данных, когда тренировка, кажется, не дает эффекта.

Вот реализация, которую я написал.

def evolutionary_attack(model, population_size, generations_nmb, min=0.0, max=1.0, mutation_chance=0.1, mutation_power=15):
    population = [] #
    best_in_class = {} #dictionary of specimen performing best for given class

    for x in range(population_size):
        population.append(np.random.uniform(min, max, model.get_input_shape())) #initialize specimens with random values
        # population.append(np.random.normal(0.0, 0.3, model.get_input_shape())) #initialize specimens with random values

    for i in range(generations_nmb):
        current_specimen = random.choice(population) #choose specimen at random from the population
        mutated_specimen = mutate_specimen(current_specimen, min, max, mutation_chance, mutation_power)
        logits = model(tf.expand_dims(tf.convert_to_tensor(mutated_specimen, dtype=tf.float32), 0))
        certainties_per_class = tf.squeeze(logits).numpy()

        for cur_class in range(len(certainties_per_class)):
            if cur_class in best_in_class:
                _, best_certainty = best_in_class[cur_class]
                if certainties_per_class[cur_class] > best_certainty:
                    #if the new specimen performs better in given class make it a new best and add it to the population
                    best_in_class[cur_class] = (mutated_specimen, certainties_per_class[cur_class])
                    population.append(mutated_specimen)
            else:
                best_in_class[cur_class] = (mutated_specimen, certainties_per_class[cur_class]) #handles the case when there is no best specimen for the given class

def mutate_specimen(specimen, min, max, mutation_chance, mutation_power=15):
    specimen = np.copy(specimen)
    with np.nditer(specimen, op_flags=['readwrite']) as it:
        for old_val in it:
            if np.random.uniform() < mutation_chance:
                u = np.random.uniform()
                if u <= 0.5:
                    delta = ((2.0 * u) ** (1.0 / (mutation_power))) - 1.0
                    new_val = old_val + delta * (old_val - min)
                else:
                    delta = 1.0 - (2 * (1 - u))** (1 / (1 + mutation_power))
                    new_val = old_val + delta * (max - old_val)
                old_val[...] = new_val
    return np.clip(specimen, min, max)

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

Моя модель сконструирована так

DenseModel (функция активации сигмоида на всех слоях, кроме последнего)

input_1 (InputLayer) [(None, 28, 28, 1)] 0


flatten (Flatten) (Нет, 784) 0


плотный (плотный) (нет, 784) 615440


density_1 (Плотный) (Нет, 800) 628000


density_2 (Плотный) (Нет, 800) 640800


плотность_3 (Плотный) (нет, 10) 8010

Он был обучен для нескольких эпох с увеличением данных с помощью оптимизатора Адама.

РЕДАКТИРОВАТЬ: я просто заметил, что я не обрезать значения образцов после мутации. Если я сделаю это, то использование нормального распределения даст результаты, аналогичные использованию равномерного распределения. я исправил это в опубликованном коде. глупая ошибка

1 Ответ

1 голос
/ 13 мая 2019

Я думаю, вы неверно истолковали MAP-Elites.Вы отслеживаете население только для добавления отдельно от элиты, но согласно этой ссылке вы должны отслеживать только элиты.Поэтому я думаю, что строка для выбора текущего образца должна быть:

current_specimen, _ = random.choice(list(best_in_class.values()))

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

Модификация MAP-Elites

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

Кажется, ваша реализация фактически делает этоправильно разделить в соответствии с документом:

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

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

...