关于JavaScript中原型NEAT的几个问题

时间:2018-03-31 15:57:50

标签: javascript neural-network genetic-algorithm recurrent-neural-network evolutionary-algorithm

我最近阅读了有关NeuroEvolution的原始paper 由Kenneth O. Stanley撰写的扩充拓扑结构,我现在正在尝试使用JavaScript自行编写原型。我偶然发现了一些我无法回答的问题。

我的问题:

  1. "结构创新"的定义是什么?我如何存储这些以便我可以检查以前是否已经发生过创新?

      

    然而,   通过保留当前一代中发生的创新列表,它   可以确保当相同结构通过独立出现不止一次时   在同一代中突变,每个相同的突变被分配   相同的创新数字

  2. 是否有理由存储节点类型(输入,隐藏,输出)?

  3. 在原始论文中,只有连接具有创新编号,但在other sources中,节点也可以。交叉是否必要? (已经问过here.

  4. 如何限制突变功能不添加循环连接?

  5. 我认为现在就是这样。感谢所有帮助。

    我的代码的相关部分:

    基因组

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

    所有答案都欢迎来源。 (你是一个很棒的人!)

2 个答案:

答案 0 :(得分:1)

首先,我非常强烈建议不要自己实施NEAT。如果你看一下(很多)可用的实现,这是一个相当大的项目!

  1. 结构创新是添加到基因组中的任何新节点或连接,这是以前从未见过的。想象一下,您有输入节点1,2,3和输出节点4,5。如果只有连接2-4可用,引入连接3-4将是结构创新。要检查新颖性,您需要存储所有看到的结构(即所有连接和节点的列表),每个结构都有一个唯一的ID(这实际上是NEAT背后的核心思想!)。在我们的示例中,连接2-4可以采用ID = 1,连接3-4采用ID = 2。您可以看到连接是新的,因为列表中没有其他连接连接2和4.通常通过创建&#34;停止&#34;来引入节点。在连接中,只需获取下一个可用的ID。例如,连接2-4将被删除,您将具有连接2-5和5-4,其中在该过程中创建节点ID = 5(以及两个新连接)。请注意,节点和连接的ID可以是独立的(即:如果您根本使用ID)。
  2. 我很难想到的要求。原则上你可以简单地按固定顺序存储节点(首先输入,然后输出,然后隐藏),然后根据它们的索引猜测它们的类型,这是你通常为了性能原因而做的事情(想象试图删除一个节点,你会只想选择一个隐藏节点,所以你会限制搜索到那些索引)。但是,有些任务可能会更有效地使用该信息,例如检查重复连接(参见4)。
  3. ID在交叉中很有用,因为它们可以快速了解哪些元素在两个基因组之间是常见的。是否拥有节点ID和连接是一个开放的实现决策。没有连接ID可以使代码更简单(连接由它们连接的节点的ID标识)。但是你无法区分连接相同节点的两个连接。有一种说法认为两个给定节点之间的连接在进化的不同时间并不一定意味着相同(参见你的引用如何提及&#34;在同一代和#34;)。不过,这可能不是一个相关的因素!正如我所说,NEAT社区仍在争论节点和连接的ID的便利性。
  4. 在许多情况下,您不希望允许重复连接。执行此操作的标准方法是每次尝试添加连接时检查重复。这是一个代价高昂的步骤,是的!
  5. 如果您有更多疑问,我建议您查看Colin Green的this implementation作为参考。如果他不是那些更了解NEAT实施的人,他就会接近。

答案 1 :(得分:0)

这不是普通的JS问题!感谢这些链接,这是一篇非常有趣的论文。我不能自称是专家,我只做过玩具GA问题,但我确实读过这篇论文及相关论文。以下是我的理解:

  1. 我认为你需要担心的是,父母是否通过突变,在一代人中不止一次产生相同的新基因。也就是说,两个孩子,其最新创新号的基因是相同的。你可以马上剔除它们。我认为他们说同一基因有可能同时出现在两个物种中,他们基本上说这很好,这种情况很少见,不用担心。

  2. 我至少可以找到一个原因:“在NEAT中,偏差是一个可以连接到除输入之外的任何节点的节点。”

  3. 我相信你的问题是“节点是否必须有创新号才能进行交叉?”答案是不。在原始论文(例如图4)中,它们显示了以只有连接具有创新数字的方式实现的交叉。
  4. 如果要将变异函数更改为体系结构感知,而不是避免重复结构,则可能需要显式添加所需的结构。假设您要避免重复连接,因为您正在改进图像分类器,并且您知道卷积更适合该任务。在这种情况下,您希望您的变异函数能够添加/删除(以及所需的连接)。这是Google Brain的explored in detail last year
  5.   

    一些作用于这种DNA的突变让人想起NEAT。但是,不是单个节点,一个突变可以插入整个层 - 即。一次有几十到几百个节点。我们还允许删除这些层,以便演化过程可以简化架构以及复杂化。

    根据您对问题4动机的评论,我认为您错了。在原始论文的XOR示例中,图5显示了一个不涉及隐藏层的起始表型。这种起始表型不是XOR问题的解决方案,但它提供了一个很好的起点:“NEAT在寻找解决方案时非常一致。在100次模拟中它没有失败一次。”这对于复发没有任何惩罚。