Алгоритм удаления бесхозных нейронов из нейронной сети

Я пытаюсь реализовать NEAT (Neuro Evolution of Augmenting Topologies).

У меня есть список сетевых подключений, называемых «генами». Связь между нейроном1 и нейроном2 будет иметь вид ген.от = нейрон1, ген.к = нейрон2.

Моя задача — сгенерировать нейронную сеть из этих генов (нейронная сеть — это просто карта от индекса до нейрона, ген.от и ген.то — ключи к нейронам на карте).

У меня есть numPossibleInputs входных узлов, поэтому мы добавляем их первыми (0-numPossibleInputs-1 — это входные нейроны).

У меня есть numOutputs выходных узла, поэтому мы их тоже добавляем.

Затем мы сортируем наши гены на основе их индексов связи «с».

Наконец, мы создаем нейроны скрытого слоя на основе генов... Поскольку нейронная сеть — это карта, мы просто проверяем, является ли соединение «к» или «от» уже нейроном, в противном случае создаем новый. Этот алгоритм прекрасно создает сети.

 public void generateNetwork()
{
    neuralNetwork.clear();

    for(int i = 0; i < numPossibleInputs; i++)
    {
        neuralNetwork.put(i, new Neuron());
    }

    for(int i = 0; i < numOutputs; i++)
    {
        neuralNetwork.put(i+numPossibleInputs+numPossibleHidden, new Neuron());
    }

    genes.sort((ConnectionGene g1, ConnectionGene g2)-> Integer.compare(g1.toNeuronIndex, g2.toNeuronIndex));

    for(ConnectionGene gene : getCleanGenes(genes))
    {
        if(gene.enabled)
        {
            if(!neuralNetwork.containsKey(gene.toNeuronIndex))
            {
                neuralNetwork.put(gene.toNeuronIndex, new Neuron());
            }
            neuralNetwork.get(gene.toNeuronIndex).incomingConnections.add(gene); // Add this gene to the incoming of the above neuron

            if(!neuralNetwork.containsKey(gene.fromNeuronIndex))
            {
                neuralNetwork.put(gene.fromNeuronIndex, new Neuron());
            }
        }
    }

}

Проблема возникает, когда алгоритм эволюции «выключает» некоторые гены (обратите внимание на gene.enabled). Например, рассмотрим следующие гены (есть и другие, но они отключены):

2->4

4->4

13->4

0->13

1->13

5->13

У нас также есть отключенные гены, 2-> 5 и 4-> 13. Их нельзя использовать в сети, так как они не выражены. (Вот почему я должен генерировать новую сеть каждое поколение, гены могут быть добавлены, включены, отключены и т. д.).

Это для numPossibleInputs ==3, поэтому 0 1 и 2 являются входными данными (2 — это смещение). 5 — это узел скрытого слоя, так как 5 > 3, но меньше 10 + 3 = 13. 13 — выходной узел, у меня было numPossibleHidden == 10, поэтому 10 + 3 = 13… всего один выход. Можно представить это так: [вход ввод ввод скрыт*10 вывод*1] для 3 входов, 10 скрытых и 1 выход

Это изображение этой наивно сгенерированной сети: Простая сеть

Как видим, в редуцированной сети вообще не должно быть ни 4, ни 5, так как они не влияют ни на какие выходы (в данном случае только на один выход, 13). Уменьшенная нейронная сеть будет просто 0-> 13 и 1-> 13.

У меня были некоторые первоначальные мысли о том, как решить эту проблему:

A. 1. Зациклить каждое соединение и хешировать ген из идентификаторов. Это идентификаторы нейронов, которые являются входными данными для чего-то еще. 2. После заполнения хэша повторите цикл и удалите все гены, в которых ген. хэш). 3. Повторяем пока ничего не удаляем

B. Создайте наивную сеть... затем проползите назад по сети, от каждого выхода, пока мы не сможем двигаться дальше (позаботьтесь о повторяющихся циклах). Хэшируйте каждый найденный узел. Как только наш поиск по графу будет завершен, сравните наш хэш найденных узлов с общим количеством узлов, выраженным в нашем списке генов. Используйте только гены с нейронами в хеше найденных узлов и переделайте сеть.

Я надеялся получить отзывы о том, какой алгоритм может быть лучшим для этого, основываясь на моем сетевом представлении — я думаю, что мой B лучше, чем A, но я надеялся, что есть более элегантное решение, в котором я не участвовал. разбор топологии графа. Возможно, я могу сделать что-то умное, отсортировав соединения (по кому, по откуда)?

Спасибо!


person user2770791    schedule 29.02.2016    source источник
comment
Я бы предложил алгоритм Уоршалла для создания транзитивного замыкания.   -  person Mehno    schedule 01.03.2016


Ответы (2)


Я использовал свое решение B, описанное выше, протестировал его со всеми видами различных типологий сетей, и оно отлично работает. То есть сеть избавится от всех узлов, у которых нет правильного пути от входов к выходам. Я опубликую код здесь, если кто-то захочет его использовать:

   private List<ConnectionGene> cleanGenes(Map<Integer,Neuron> network)
{
    // For each output, go backwards
    Set<Integer> visited = new HashSet();
    for(int i = 0; i < numOutputs; i++)
    {
        visited.add(i+numPossibleInputs+numPossibleHidden);
        cleanGenes(i+numPossibleInputs+numPossibleHidden, network, visited);
    }

    List<ConnectionGene> slimGenes = new ArrayList();
    for(ConnectionGene gene : genes)
    {
        // Only keep gene if from/to are nodes we visited
        if(visited.contains(gene.fromNeuronIndex) && visited.contains(gene.toNeuronIndex))
        {
            slimGenes.add(gene);
        }
    }
    return slimGenes;
}

private boolean cleanGenes(int neuronIndex, Map<Integer, Neuron> network, Set<Integer> visited)
{
    int numGoodConnections = 0;
    for(ConnectionGene gene : network.get(neuronIndex).incomingConnections)
    {
        numGoodConnections++;
        if(gene.enabled && !visited.contains(gene.fromNeuronIndex))
        {
            visited.add(gene.fromNeuronIndex);
            if(!cleanGenes(gene.fromNeuronIndex, network, visited))
            {
                numGoodConnections--;
                visited.remove(gene.fromNeuronIndex); // We don't want this node in visited, it has no incoming connections and isn't an input.
            }
        }
    }

    if(numGoodConnections == 0)
    {
        return neuronIndex < numPossibleInputs; // True if an input neuron, false if no incoming connections and not input
    }
    return true; // Success
}

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

person user2770791    schedule 01.03.2016

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

person PLEXATIC    schedule 15.09.2017