2012-12-22 59 views
6

我想了解神经网络,并编写了一个简单的反向传播神经网络,它使用S形激活函数,随机权重初始化和学习/梯度动量。简单的神经网络无法学习异或

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

我看不出有什么我做错了,因此任何帮助,将不胜感激。

由于

编辑:如上所述,我用2个隐藏节点测试,但下面的代码示出为3的配置我简单忘记改变这种回2使用3-隐藏节点运行测试之后。

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 
+0

我会通过设置一个完整的测试用例来开始调试,这个用例包括两个例子的初始权重和导数/错误,然后进入代码。 – Motasim

+2

你应该真的把它简化为解释问题的最小*代码。你在这里有几乎是一个完整的应用程序。 – tadman

+1

你的程序看起来可能会做一些有趣的事情。但是每种语言都有自己的约定,而Ruby的部分功能就是其简洁。您可能会将此问题带到codereview.stackexchange.com。 –

回答

13

我的答案是不是红宝石,而是神经网络。首先,你必须了解如何在纸上书写输入和网络。如果你实现二元操作,你的空间将由XY平面上的四个点组成。在X轴和Y轴上标记真假,绘制四个点。如果你是对的,你会收到类似这样的信息http://drawsave.com/1Tj

现在(也许你不知道神经元的这种解释)尝试将神经元绘制成平面上的一条线,根据需要分离你的点。例如,这是AND的行:enter image description here 该行将正确答案与错误分隔开。如果你明白,你可以写OR。 XOR将是一个麻烦。

而作为该调试的最后一步,实现神经元作为行。找到关于它的文献,我不记得如何通过现有的线建立神经元。这很简单,真的。然后建立一个神经元向量并实现它。实现AND作为单个神经元网络,其中神经元被定义为您的AND,并在论文中计算。如果你做的一切正确,你的网络将做和功能。 因为你在理解任务之前编写了一个程序,所以我写了大量的字母。我不想粗鲁,但你提到XOR显示它。如果您尝试在一个神经元上构建XOR,您将不会收到任何东西 - 不可能将正确答案与错误分开。在书中它被称为“XOR不是线性可分的”。因此,对于异或你需要建立一个双层网络。例如,你将有AND和NOT-OR作为第一层,AND作为第二层。

如果您仍然阅读本文,并且您明白我所写的内容,那么您在调试网络时不会遇到任何问题。如果您的网络无法学习某些功能,请将其构建在纸上,然后硬编码您的网络并对其进行测试。如果它仍然失败,你建立它在纸上不正确 - 重读我的演讲;)

+0

它看起来好像有问题的代码有四个节点排列成两层:三层节点的中间层和一层节点的输出层。 –

+0

好的,谢谢,我没有仔细阅读代码。所以,快速回答可能是:尝试从两个节点制作中间层并重新运行您的算法。 –

+1

这应该是被接受的答案。 – garbagecollector

0

我有同样的问题,答案是 - 使用更高的学习速度值。我用下面的lSpeed = 12.8/epoch100 epoches为NN与phi(x) = x/(1 + |x|)

可能现在你NN学习速度只是没有足够的“动力”,使工作。