【问题标题】:Simple Neural Network can't learn XOR简单的神经网络无法学习异或
【发布时间】:2012-12-22 00:37:48
【问题描述】:

我正在尝试学习神经网络,并编写了一个简单的反向传播神经网络,该网络使用 sigmoid 激活函数、随机权重初始化和学习/梯度动量。

当配置有 2 个输入、2 个隐藏节点和 1 个时,它无法学习 XOR 和 AND。但是,它会正确学习 OR。

我看不到我做错了什么,因此我们将不胜感激。

谢谢

编辑:如上所述,我使用 2 个隐藏节点进行了测试,但下面的代码显示配置为 3。在使用 3 个隐藏节点运行测试后,我只是忘记将其改回 2。 p>

network.rb:

module Neural

class Network

    attr_accessor :num_inputs, :num_hidden_nodes, :num_output_nodes, :input_weights, :hidden_weights, :hidden_nodes, 
                    :output_nodes, :inputs, :output_error_gradients, :hidden_error_gradients,
                    :previous_input_weight_deltas, :previous_hidden_weight_deltas

    def initialize(config)
        initialize_input(config)
        initialize_nodes(config)
        initialize_weights
    end

    def initialize_input(config)
        self.num_inputs = config[:inputs]
        self.inputs = Array.new(num_inputs+1)
        self.inputs[-1] = -1
    end

    def initialize_nodes(config)
        self.num_hidden_nodes = config[:hidden_nodes]
        self.num_output_nodes = config[:output_nodes]
        # treat threshold as an additional input/hidden node with no incoming inputs and a value of -1
        self.output_nodes = Array.new(num_output_nodes)
        self.hidden_nodes = Array.new(num_hidden_nodes+1)
        self.hidden_nodes[-1] = -1
    end

    def initialize_weights
        # treat threshold as an additional input/hidden node with no incoming inputs and a value of -1
        self.input_weights = Array.new(hidden_nodes.size){Array.new(num_inputs+1)}
        self.hidden_weights = Array.new(output_nodes.size){Array.new(num_hidden_nodes+1)}
        set_random_weights(input_weights)
        set_random_weights(hidden_weights)
        self.previous_input_weight_deltas = Array.new(hidden_nodes.size){Array.new(num_inputs+1){0}}
        self.previous_hidden_weight_deltas = Array.new(output_nodes.size){Array.new(num_hidden_nodes+1){0}}
    end

    def set_random_weights(weights)
        (0...weights.size).each do |i|
            (0...weights[i].size).each do |j|
                weights[i][j] = (rand(100) - 49).to_f / 100
            end
        end
    end

    def calculate_node_values(inputs)
        inputs.each_index do |i|
            self.inputs[i] = inputs[i]
        end

        set_node_values(self.inputs, input_weights, hidden_nodes)
        set_node_values(hidden_nodes, hidden_weights, output_nodes)
    end

    def set_node_values(values, weights, nodes)
        (0...weights.size).each do |i|
            nodes[i] = Network::sigmoid(values.zip(weights[i]).map{|v,w| v*w}.inject(:+))
        end
    end

    def predict(inputs)
        calculate_node_values(inputs)
        output_nodes.size == 1 ? output_nodes[0] : output_nodes
    end

    def train(inputs, desired_results, learning_rate, momentum_rate)
        calculate_node_values(inputs)
        backpropogate_weights(desired_results, learning_rate, momentum_rate)
    end

    def backpropogate_weights(desired_results, learning_rate, momentum_rate)
        output_error_gradients = calculate_output_error_gradients(desired_results)
        hidden_error_gradients = calculate_hidden_error_gradients(output_error_gradients)
        update_all_weights(inputs, desired_results, hidden_error_gradients, output_error_gradients, learning_rate, momentum_rate)
    end

    def self.sigmoid(x)
        1.0 / (1 + Math::E**-x)
    end

    def self.dsigmoid(x)
        sigmoid(x) * (1 - sigmoid(x))
    end

    def calculate_output_error_gradients(desired_results)
        desired_results.zip(output_nodes).map{|desired, result| (desired - result) * Network::dsigmoid(result)}
    end

    def reversed_hidden_weights
        # array[hidden node][weights to output nodes]
        reversed = Array.new(hidden_nodes.size){Array.new(output_nodes.size)}
        hidden_weights.each_index do |i|
            hidden_weights[i].each_index do |j|
                reversed[j][i] = hidden_weights[i][j];
            end
        end
        reversed

    end

    def calculate_hidden_error_gradients(output_error_gradients)
        reversed = reversed_hidden_weights
        hidden_nodes.each_with_index.map do |node, i|
            Network::dsigmoid(hidden_nodes[i]) * output_error_gradients.zip(reversed[i]).map{|error, weight| error*weight}.inject(:+)
        end
    end

    def update_all_weights(inputs, desired_results, hidden_error_gradients, output_error_gradients, learning_rate, momentum_rate)
        update_weights(hidden_nodes, inputs, input_weights, hidden_error_gradients, learning_rate, previous_input_weight_deltas, momentum_rate)
        update_weights(output_nodes, hidden_nodes, hidden_weights, output_error_gradients, learning_rate, previous_hidden_weight_deltas, momentum_rate)
    end

    def update_weights(nodes, values, weights, gradients, learning_rate, previous_deltas, momentum_rate)
        weights.each_index do |i|
            weights[i].each_index do |j|
                delta = learning_rate * gradients[i] * values[j]
                weights[i][j] += delta + momentum_rate * previous_deltas[i][j]
                previous_deltas[i][j] = delta
            end
        end


    end

end

end

test.rb:

#!/usr/bin/ruby

load "network.rb"

learning_rate = 0.3
momentum_rate = 0.2

nn = Neural::Network.new(:inputs => 2, :hidden_nodes => 3, :output_nodes => 1)
10000.times do |i|
    # XOR - doesn't work
    nn.train([0, 0], [0], learning_rate, momentum_rate)
    nn.train([1, 0], [1], learning_rate, momentum_rate)
    nn.train([0, 1], [1], learning_rate, momentum_rate)
    nn.train([1, 1], [0], learning_rate, momentum_rate)

    # AND - very rarely works
    # nn.train([0, 0], [0], learning_rate, momentum_rate)
    # nn.train([1, 0], [0], learning_rate, momentum_rate)
    # nn.train([0, 1], [0], learning_rate, momentum_rate)
    # nn.train([1, 1], [1], learning_rate, momentum_rate)

    # OR - works
    # nn.train([0, 0], [0], learning_rate, momentum_rate)
    # nn.train([1, 0], [1], learning_rate, momentum_rate)
    # nn.train([0, 1], [1], learning_rate, momentum_rate)
    # nn.train([1, 1], [1], learning_rate, momentum_rate)
end

puts "--- TESTING ---"
puts "[0, 0]"
puts "result "+nn.predict([0, 0]).to_s
puts
puts "[1, 0]"
puts "result "+nn.predict([1, 0]).to_s
puts
puts "[0, 1]"
puts "result "+nn.predict([0, 1]).to_s
puts
puts "[1, 1]"
puts "result "+nn.predict([1, 1]).to_s
puts

【问题讨论】:

  • 我会通过设置一个完整的测试用例来开始调试,其中包括两个示例的初始权重和导数/错误,然后进入代码。
  • 你真的应该把它减少到解释问题的最少位代码。您在这里拥有的几乎是一个完整的应用程序。
  • 您的程序看起来可能正在做一些有趣的事情。但是每种语言都有自己的约定,而 Ruby 的部分强大之处在于它的简洁性。您可以将此问题提交给 codereview.stackexchange.com。
  • 对 SO 上类似帖子的评论要求查看 OP 的代码,因此我决定发布我的整个应用程序。但是,我同意,这很长,坦率地说,我对这些数字或回复感到惊讶。在发布之前,我浏览了一个测试用例并验证了计算值与反向传播的每一步的预期值相匹配。我将尝试使用其他测试用例并阅读更多关于 BP 的内容,看看我是否误解了算法。

标签: ruby machine-learning neural-network


【解决方案1】:

我的答案不是关于 ruby​​,而是关于神经网络。 首先,你必须了解如何在纸上写下你的输入和你的网络。如果您实现二元运算符,您的空间将由 XY 平面上的四个点组成。在 X 和 Y 轴上标记真假并画出你的四个点。如果你做对了,你会收到这样的

现在(也许你不知道神经元的这种解释)尝试将神经元画成平面上的一条线,它可以根据需要分隔你的点。例如,这是 AND 的行: 这条线将正确答案与错误答案分开。如果你理解了,你可以写 OR 的那一行。异或会很麻烦。

作为此调试的最后一步,将神经元实现为一条线。找了一篇关于它的文献,我不记得如何通过现有的线构建神经元。这会很简单,真的。然后构建一个神经元向量并实现它。将 AND 实现为单个神经元网络,其中神经元定义为您的 AND,在纸上计算。如果你都做对了,你的网络就会做 AND 功能。 我写了这么多的信,只是因为你在理解一项任务之前写了一个程序。我不想粗暴,但你提到的 XOR 表明了这一点。如果您尝试在一个神经元上构建 XOR,您将一无所获——不可能将正确答案与错误答案区分开来。在书中它被称为“XOR 不是线性可分的”。因此,对于 XOR,您需要构建一个两层网络。例如,您将 AND 和 not-OR 作为第一层,AND 作为第二层。

如果您仍然阅读本文并且理解了我所写的内容,那么您在调试网络时就不会遇到麻烦。如果您的网络无法学习某些功能,请在纸上构建它,然后对您的网络进行硬编码并进行测试。如果它仍然失败,你在不正确的论文上构建它 - 重新阅读我的讲座;)

【讨论】:

  • 看起来有问题的代码有四个节点,分为两层:三个节点的中间层和一个节点的输出层。
  • 好的,谢谢,我没有仔细阅读代码。如此快速的答案可能是:尝试从两个节点制作中间层并重新运行您的算法。
【解决方案2】:

我遇到了同样的问题,答案是 - 使用更高的学习速度值。我使用以下lSpeed = 12.8 / epoch 和关于100 epoches 的NN 和phi(x) = x/(1 + |x|)

现在你的神经网络学习速度可能没有足够的“力量”来完成这项工作。

【讨论】:

    【解决方案3】:

    如果您想考虑神经进化,您可以查看neuroevo gem。运行规范以查看它在 15 次迭代中拟合 XOR[2,2,1] 前馈网络,XNES 优化器):

    https://github.com/giuse/neuroevo/blob/master/spec/solver_spec.rb

    全面披露:我是开发人员(您好!)。
    我最近才开始发布我的代码并正在寻找反馈。

    【讨论】:

      猜你喜欢
      • 2014-05-29
      • 1970-01-01
      • 2016-05-25
      • 2020-01-16
      • 2019-10-17
      • 2018-10-01
      • 2016-07-11
      • 2016-11-09
      相关资源
      最近更新 更多