2012-12-22 25 views
6

Sto cercando di apprendere le reti neurali e ho codificato una rete neurale semplice, retropropagante che utilizza funzioni di attivazione sigmoide, inizializzazione del peso casuale e momento di apprendimento/gradiente.Impossibile apprendere la rete neurale semplice XOR

Se configurato con 2 ingressi, 2 nodi nascosti e 1 non riesce a imparare XOR e AND. Tuttavia, imparerà correttamente OR.

Non riesco a vedere cosa ho fatto di sbagliato e quindi qualsiasi aiuto sarebbe molto apprezzato.

Grazie

EDIT: Come detto, ho provato con 2 nodi nascosti ma il codice di seguito mostra una configurazione di 3. semplicemente dimenticato di cambiare questo ritorna 2 dopo i test in esecuzione utilizzando 3 nodi nascosti.

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

Avvio il debug impostando un test case completo che include i pesi iniziali e le derivate/errori per due esempi, quindi passo nel codice. – Motasim

+2

Dovresti davvero ridurlo a un * minimo * di codice che spiega il problema. Quello che hai qui è praticamente un'intera applicazione. – tadman

+1

Sembra che il tuo programma stia facendo qualcosa di interessante. Ma ogni lingua ha le sue convenzioni e parte del potere di Ruby è la sua concisione. Potresti portare questa domanda a codereview.stackexchange.com. –

risposta

13

La mia risposta non sarà A proposito di Ruby, ma di rete neurale. Prima di tutto, devi capire come scrivere i tuoi input e la tua rete su un foglio. Se implementi gli operandi binari, il tuo spazio consisterà in quattro punti sul piano XY. Contrassegna true e false sugli assi X e Y e disegna i tuoi quattro punti. Se hai ragione, riceverai qualcosa come http://drawsave.com/1Tj

Ora (forse non conoscevi questa interpretazione del neurone) prova a disegnare il neurone come una linea su un piano, che separa i tuoi punti di cui hai bisogno. Ad esempio, questa è la linea per AND: enter image description here La riga separa le risposte corrette da errate. Se capisci, puoi scrivere la riga per OR. XOR sarà un problema.

E come ultimo passo di questo debug, realizzare un neurone come una linea. Trova una letteratura a riguardo, non ricordo come costruire neuroni con la linea esistente. Sarà semplice, davvero. Quindi costruisci un vettore neurone per AND implementalo. Realizza AND come una singola rete di neuroni, dove il neurone è definito come il tuo AND, calcolato su un foglio. Se tutto va bene, la tua rete funzionerà AND. Ho scritto un numero così grande di lettere solo perché scrivi un programma prima di capire un compito. Non voglio essere duro, ma la tua menzione di XOR lo ha dimostrato. Se proverai a costruire XOR su un neurone, non riceverai nulla - è impossibile separare le risposte corrette da quelle sbagliate. Nei libri si chiama "XOR non è lineare separabile". Quindi per XOR è necessario creare una rete a due strati. Ad esempio, avrai AND e not-OR come primo livello e AND come secondo livello.

Se continui a leggere questo e capisci cosa ho scritto, non avrai problemi con la rete di debug. Se la tua rete non riesce a imparare qualche funzione, allora la costruisci su un foglio di carta, quindi hardcode la rete e testarlo. Se continua a fallire, lo costruisci su una carta errata - rileggi la mia lezione;)

+0

Sembra che il codice in questione abbia quattro nodi disposti su due livelli: uno strato intermedio di tre nodi e uno strato di uscita di un nodo. –

+0

Ok, grazie, non ho letto attentamente il codice. Quindi una risposta rapida potrebbe essere: provare a fare il middle layer da due nodi e rieseguire il tuo algoritmo. –

+1

Questa dovrebbe essere la risposta accettata. – garbagecollector

0

Ho avuto lo stesso problema e la risposta è: usare valori più alti della velocità di apprendimento. Io uso il seguente lSpeed = 12.8/epoch e circa 100 epoches per il NN con phi(x) = x/(1 + |x|)

Possibile adesso la tua NN imparare velocità semplicemente non hanno abbastanza "potere" per rendere il lavoro.

0

Se si desidera considerare Neuroevolution, è possibile controllare la gemma neuroevo.Eseguire le specifiche per vederlo adatta XOR in 15 iterazioni (rete [2,2,1] feed-forward, XNES Optimizer):

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

Full disclosure: io sono lo sviluppatore (Hi there!).
Ho appena iniziato a pubblicare il mio codice e sto cercando feedback.

Problemi correlati