Несколько вопросов по прототипированию NEAT в JavaScript

Недавно я прочитал оригинальную статью о NeuroEvolution аугментации. Топологии Кеннета О. Стэнли, и сейчас я сам пытаюсь создать прототип на JavaScript. Я наткнулся на несколько вопросов, на которые не могу ответить.


Мои вопросы:

  1. #P2# <блочная цитата> #P3#
  2. Есть ли причина для хранения типа узла (входной, скрытый, выходной)?

  3. В исходной статье только соединения имеют номер инновации, но в других источниках узлы также . Это необходимо для кроссовера? (Об этом уже спрашивали здесь.)

  4. Как я могу ограничить функции мутации, чтобы не добавлять повторяющиеся соединения?

Я думаю, что это все на данный момент. Вся помощь приветствуется.


Соответствующие части моего кода:

Геном

class Genome {
    constructor(inputs, outputs) {
        this.inputs = inputs;
        this.outputs = outputs;
        this.nodes = [];
        this.connections = [];
        for (let i = 0; i < inputs + outputs; i++) {
            this.nodes.push(new Node());
        }
        for (let i = 0; i < inputs; i++) {
            for (let o = 0; o < outputs; o++) {
                let c = new Connection(this.nodes[i], this.nodes[inputs + o], outputs * i + o);
                this.connections.push(c);
            }
        }
        innovation = inputs * outputs;
    }
    weightMutatePerturb() {
        let w = this.connections[Math.floor(random(this.connections.length))].weight;
        w += random(-0.5, 0.5);
    }
    weightMutateCreate() {
        this.connections[Math.floor(random(this.connections.length))].weight = random(-2, 2);
    }
    connectionMutate() {
        let i = this.nodes[Math.floor(random(this.nodes.length))];
        let o = this.nodes[Math.floor(random(this.inputs, this.nodes.length))];
        let c = Connection.exists(this.connections, i, o);
        if (c) {
            c.enabled = true;
        } else {
            this.connections.push(new Connection(i, o, innovation));
            innovation++;
        }
    }
    nodeMutate() {
        let oldCon = this.connections[Math.floor(Math.random(this.connections.length))];
        oldCon.enabled = false;
        let newNode = new Node();
        this.nodes.push(newNode);
        this.connections.push(new Connection(oldCon.input, newNode, innovation, 1));
        innovation++;
        this.connections.push(new Connection(newNode, oldCon.output, innovation, oldCon.weight));
        innovation++;
    }
}

Узел

class Node {
    constructor() {
        this.value = 0;
        this.previousValue = 0;
    }
}

Связь

class Connection {
    constructor(input, output, innov, weight) {
        this.input = input;
        this.output = output;
        this.innov = innov;
        this.weight = weight ? weight : random(-2, 2);
        this.enabled = true;
    }
    static exists(connections, i, o) {
        for (let c = 0; c < connections.length; c++) {
            if (connections[c].input === i && connections[c].output === o) {
                return connections[c];
            }
        }
        return false;
    }
}

Приветствуются все ответы и источники. (Вы замечательный человек!)


comment
Существует ли четкое определение разницы между мутацией и кроссовером?   -  person Sam H.    schedule 01.04.2018
comment
Мутация — это случайное изменение одного генома (например, добавление узла), а кроссовер означает объединение двух геномов на основе их пригодности без какой-либо случайности.   -  person Nigk    schedule 01.04.2018
comment
Итак, исходя из этого, не делает ли КРАСИВАЯ газета Кроссовер?   -  person Sam H.    schedule 01.04.2018
comment
Да, это так, но эта часть кода не имеет отношения к моему вопросу.   -  person Nigk    schedule 01.04.2018
comment
Ну, в 3 вы спрашиваете в других источниках, узлы [имеют номер инновации]. Это необходимо для кроссовера? и поэтому я думаю, что это актуально   -  person Sam H.    schedule 01.04.2018
comment
Кроме того, для 4 вы говорите, как я могу ограничить функции мутации, чтобы не добавлять соединения обратного распространения? Вы имеете в виду, что есть определенные архитектуры, которых вы хотите избежать? Потому что backprop — это метод определения весов. В этой статье используется альтернативный метод вычисления весов, поэтому обратного распространения не происходит. Вы говорите, что есть связи, связанные с алгоритмом обратного распространения, которых вы хотите избежать?   -  person Sam H.    schedule 01.04.2018
comment
В вопросе 4 я имел в виду повторяющийся. Я исправил это сейчас.   -  person Nigk    schedule 01.04.2018
comment
хм... разве для понимания топологии сети не требуется либо функция мутации, либо функция пригодности? Разве понимание этой статьи явно не делает этого?   -  person Sam H.    schedule 01.04.2018
comment
Могу я спросить, какова цель наказания за повторяющиеся соединения?   -  person Sam H.    schedule 01.04.2018
comment
Я не могу найти его нигде прямо сейчас, но я где-то читал, что если вы хотите протестировать NEAT на примере xor, вам нужно отключить рекуррентные соединения, потому что иначе можно найти решение без единого скрытого нейрона.   -  person Nigk    schedule 01.04.2018
comment
Хорошо, смотрите мой ответ. Я отредактировал его, чтобы включить то, что вы сказали в комментариях, и после повторного прочтения статьи, и после просмотра реализации Python neat-python.readthedocs.io/en/latest/neat_overview.html   -  person Sam H.    schedule 01.04.2018


Ответы (2)


Во-первых, я бы очень настоятельно рекомендовал не внедрять NEAT самостоятельно. Если вы посмотрите на (много) доступных реализаций, это довольно большой проект!

  1. Структурная инновация — это любой новый узел или связь, которые добавляются в геном и которых раньше не было. Представьте, что у вас есть входные узлы 1, 2, 3 и выходные узлы 4, 5. Если доступно только соединение 2-4, введение соединения 3-4 было бы структурной инновацией. Чтобы проверить новизну, вам нужно хранить все видимые структуры (то есть список всех соединений и узлов) с уникальным идентификатором для каждой (на самом деле это основная идея NEAT!). В нашем примере соединение 2-4 может принять ID=1, а соединение 3-4 — ID=2. Вы можете видеть, что соединение является новым, поскольку никакое другое соединение в списке не соединяет 2 и 4. Узлы обычно вводятся путем создания «остановки» в соединении и просто берут следующий доступный идентификатор. Например, соединение 2-4 будет удалено, а у вас останутся соединения 2-5 и 5-4, где в процессе создается node ID=5 (а также два новых соединения). Обратите внимание, что идентификаторы для узлов и соединений могут быть независимыми (то есть: если вы вообще используете идентификаторы для соединений).
  2. Я изо всех сил пытаюсь придумать жесткое требование для этого. В принципе, вы могли бы просто хранить узлы в фиксированном порядке (сначала ввод, затем вывод, затем скрытие), а затем угадывать их тип по их индексу, как вы обычно делаете это в любом случае из соображений производительности (представьте, что вы пытаетесь удалить узел, вы бы хотите выбрать только скрытый узел, поэтому вы ограничите поиск этими индексами). Однако некоторые задачи могут быть более эффективными при наличии этой информации, например проверка повторяющихся соединений (см. 4).
  3. Идентификаторы полезны при кроссовере, поскольку они позволяют быстро узнать, какие элементы являются общими для двух геномов. Иметь ли идентификаторы для узлов, а также соединений — это открытое решение реализации. Отсутствие идентификаторов для соединений упрощает код (соединения идентифицируются по идентификаторам узлов, к которым они подключаются). Но вы теряете способность различать два соединения, соединяющие одни и те же узлы. Существует аргумент, согласно которому связь между двумя заданными узлами не обязательно означает одно и то же в разное время эволюции (см., как в вашей цитате упоминается «в одном и том же поколении»). Это, вероятно, не важный фактор, хотя! Как я уже сказал, удобство идентификаторов как для узлов, так и для соединений до сих пор обсуждается в сообществе NEAT.
  4. Во многих случаях вы не хотите разрешать повторяющиеся подключения. Стандартный способ сделать это — проверять повторение каждый раз, когда вы пытаетесь добавить соединение. Это дорогостоящий шаг, да!

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

person Pablo    schedule 03.04.2018
comment
В своем первом пункте вы сказали, что при создании нового узла старое соединение удаляется. Но на аккуратной бумаге изображение показывает, что соединение отключено. - person Akash Karnatak; 22.02.2020
comment
Да, я говорю о текущих деталях реализации (в частности, о реализации C#). При добавлении нового узла соединение разрывается и создаются два новых. Вы можете создать эквивалентную реализацию, в которой узел будет отключен, а не уничтожен. - person Pablo; 23.02.2020

Это не обычный вопрос JS! Спасибо за ссылки, действительно интересная статья. Я не могу претендовать на звание эксперта, я решал только игрушечные задачи ГА, но я читал эту статью и связанные с ней. Вот что я понимаю:

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

  2. Я могу найти по крайней мере одну причину: «В NEAT смещение — это узел, который может соединяться с любым узлом, кроме входов».

  3. Я полагаю, ваш вопрос: «Должны ли узлы иметь инновационный номер для кроссовера?» Ответ - нет. В оригинальной статье (например, на рис. 4) они показывают кроссовер, реализованный таким образом, что только соединения имеют номера инноваций.
  4. Если вы хотите изменить функцию мутации так, чтобы она учитывала архитектуру, а не избегать повторяющихся структур, вы можете явно добавить структуры, которые вам нужны. Предположим, вы хотите избежать повторяющихся соединений, потому что вы разрабатываете классификатор изображений и знаете, что свертки больше подходят для этой задачи. В этом случае вы хотите, чтобы ваша функция мутации могла добавлять/удалять слои (и необходимые соединения). Это было подробно изучено компанией Google Brain в прошлом году:

Некоторые мутации, воздействующие на эту ДНК, напоминают NEAT. Однако вместо отдельных узлов одна мутация может вставить целые слои, т.е. от десятков до сотен узлов одновременно. Мы также допускаем удаление этих слоев, чтобы эволюционный процесс мог не только усложнить, но и упростить архитектуру.

Судя по вашему комментарию о вашей мотивации к вопросу 4, я думаю, что вы ошибаетесь. В примере с XOR в оригинальной статье, рис. 5, они показывают начальный фенотип, который не включает скрытого слоя. Этот начальный фенотип не является решением проблемы XOR, но дает хорошую отправную точку: «NEAT очень последователен в поиске решения. Он не дал сбоев ни разу из 100 симуляций». То есть без каких-либо штрафов за повторение.

person Sam H.    schedule 31.03.2018