2015-07-04 9 views
5

Ich versuche, ein FFNN in Java mit Backpropagation zu implementieren und habe keine Ahnung, was ich falsch mache. Es funktionierte, als ich nur ein einziges Neuron im Netzwerk hatte, aber ich schrieb eine andere Klasse, um mit größeren Netzwerken umzugehen, und nichts konvergierte. Es scheint ein Problem in der Mathematik zu sein - oder eher meine Implementierung der Mathematik - aber ich habe es mehrmals überprüft und ich kann nichts falsch finden. Dies sollte funktionieren.
Node Klasse:
Neural Network-Implementierung in Java

package arr; 

import util.ActivationFunction; 
import util.Functions; 

public class Node { 
    public ActivationFunction f; 
    public double output; 
    public double error; 

    private double sumInputs; 
    private double sumErrors; 
    public Node(){ 
     sumInputs = 0; 
     sumErrors = 0; 
     f = Functions.SIG; 
     output = 0; 
     error = 0; 
    } 
    public Node(ActivationFunction func){ 
     this(); 
     this.f = func; 
    } 

    public void addIW(double iw){ 
     sumInputs += iw; 
    } 
    public void addIW(double input, double weight){ 
     sumInputs += (input*weight); 
    } 
    public double calculateOut(){ 
     output = f.eval(sumInputs); 
     return output; 
    } 

    public void addEW(double ew){ 
     sumErrors+=ew; 
    } 
    public void addEW(double error, double weight){ 
     sumErrors+=(error*weight); 
    } 
    public double calculateError(){ 
     error = sumErrors * f.deriv(sumInputs); 
     return error; 
    } 
    public void resetValues(){ 
     sumErrors = 0; 
     sumInputs = 0; 
    } 
} 

LineNetwork Klasse:

package arr; 
import util.Functions; 

public class LineNetwork { 
public double[][][] weights; //layer of node to, # of node to, # of node from 
public Node[][] nodes;   //layer, # 
public double lc; 
public LineNetwork(){ 
    weights = new double[2][][]; 
    weights[0] = new double[2][1]; 
    weights[1] = new double[1][3]; 
    initializeWeights(); 
    nodes = new Node[2][]; 
    nodes[0] = new Node[2]; 
    nodes[1] = new Node[1]; 
    initializeNodes(); 
    lc = 1; 
} 
private void initializeWeights(){ 
    for(double[][] layer: weights) 
     for(double[] curNode: layer) 
      for(int i=0; i<curNode.length; i++) 
       curNode[i] = Math.random()/10; 
} 
private void initializeNodes(){ 
    for(Node[] layer: nodes) 
     for(int i=0; i<layer.length; i++) 
      layer[i] = new Node(); 
    nodes[nodes.length-1][0].f = Functions.HSF; 
} 
public double feedForward(double[] inputs) { 
    for(int j=0; j<nodes[0].length; j++) 
     nodes[0][j].addIW(inputs[j], weights[0][j][0]); 
    double[] outputs = new double[nodes[0].length]; 
    for(int i=0; i<nodes[0].length; i++) 
     outputs[i] = nodes[0][i].calculateOut(); 
    for(int l=1; l<nodes.length; l++){ 
     for(int i=0; i<nodes[l].length; i++){ 
      for(int j=0; j<nodes[l-1].length; j++) 
       nodes[l][i].addIW(
         outputs[j], 
         weights[l][i][j]); 
      nodes[l][i].addIW(weights[l][i][weights[l][i].length-1]); 
     } 
     outputs = new double[nodes[l].length]; 
     for(int i=0; i<nodes[l].length; i++) 
      outputs[i] = nodes[l][i].calculateOut(); 
    } 
    return outputs[0]; 
} 

public void backpropagate(double[] inputs, double expected) { 
    nodes[nodes.length-1][0].addEW(expected-nodes[nodes.length-1][0].output); 
    for(int l=nodes.length-2; l>=0; l--){ 
     for(Node n: nodes[l+1]) 
      n.calculateError(); 
     for(int i=0; i<nodes[l].length; i++) 
      for(int j=0; j<nodes[l+1].length; j++) 
       nodes[l][i].addEW(nodes[l+1][j].error, weights[l+1][j][i]); 
     for(int j=0; j<nodes[l+1].length; j++){ 
      for(int i=0; i<nodes[l].length; i++) 
       weights[l+1][j][i] += nodes[l][i].output*lc*nodes[l+1][j].error; 
      weights[l+1][j][nodes[l].length] += lc*nodes[l+1][j].error; 
     } 
    } 
    for(int i=0; i<nodes[0].length; i++){ 
     weights[0][i][0] += inputs[i]*lc*nodes[0][i].calculateError(); 
    } 
} 
public double train(double[] inputs, double expected) { 
    double r = feedForward(inputs); 
    backpropagate(inputs, expected); 
    return r; 
} 
public void resetValues() { 
    for(Node[] layer: nodes) 
     for(Node n: layer) 
      n.resetValues(); 
} 

public static void main(String[] args) { 
    LineNetwork ln = new LineNetwork(); 
    System.out.println(str2d(ln.weights[0])); 
    for(int i=0; i<10000; i++){ 
     double[] in = {Math.round(Math.random()),Math.round(Math.random())}; 
     int out = 0; 
     if(in[1]==1^in[0] ==1) out = 1; 
     ln.resetValues(); 
     System.out.print(i+": {"+in[0]+", "+in[1]+"}: "+out+" "); 
     System.out.println((int)ln.train(in, out)); 
    } 
    System.out.println(str2d(ln.weights[0])); 
} 
private static String str2d(double[][] a){ 
    String str = "["; 
    for(double[] arr: a) 
     str = str + str1d(arr) + ",\n"; 
    str = str.substring(0, str.length()-2)+"]"; 
    return str; 
} 
private static String str1d(double[] a){ 
    String str = "["; 
    for(double d: a) 
     str = str+d+", "; 
    str = str.substring(0, str.length()-2)+"]"; 
    return str; 
} 
} 

Kurze Erklärung der Struktur: jeder Knoten eine Aktivierungsfunktion f aufweist; f.eval wertet die Funktion aus und f.deriv wertet seine Ableitung aus. Functions.SIG ist die Standard-Sigmoidalfunktion und Functions.HSF ist die Heaviside-Schrittfunktion. Um die Eingänge einer Funktion zu setzen, rufen Sie addIW mit einem Wert auf, der bereits das Gewicht des vorherigen Ausgangs enthält. Ähnlich verhält es sich bei der Rückwärtsausbreitung mit addEW. Knoten sind in einem 2d-Array organisiert und Gewichtungen sind wie beschrieben in einem 3d-Array getrennt organisiert.

Ich weiß, dass dies ein bisschen viel zu fragen ist - und ich verstehe sicherlich, wie viele Java Konventionen dieser Code bricht - aber ich schätze jede Hilfe, die jemand anbieten kann.

EDIT: Da diese Frage und mein Code sind solche riesigen Wände aus Text, wenn es eine Zeile mit vielen komplizierten Ausdrücke in Klammern, die Sie nicht herausfinden wollen, einen Kommentar oder etwas fragen mich und ich ' Ich werde versuchen, so schnell wie möglich zu antworten.

EDIT 2: Das spezifische Problem hier ist, dass dieses Netzwerk nicht auf XOR konvergiert. Hier einige Ausgabe dies zu verdeutlichen:

9995: {1.0, 0.0}: 1 1
9996: {0.0, 1.0}: 1 1
9997: {0.0, 0.0}: 0 1
9998 : {0,0, 1,0}: 1 0
9999: {0,0, 1,0}: 1 1
Jede Zeile des Formats TEST NUMBER: {INPUTS}: EXPECTED ACTUAL Das Netzwerk ruft train bei jedem Test, so wird dieses Netzwerk backpropagating 10000mal ist.

Hier sind die beiden zusätzlichen Klassen, wenn jemand will, um es auszuführen:

package util; 

public class Functions { 
public static final ActivationFunction LIN = new ActivationFunction(){ 
      public double eval(double x) { 
       return x; 
      } 

      public double deriv(double x) { 
       return 1; 
      } 
}; 
public static final ActivationFunction SIG = new ActivationFunction(){ 
      public double eval(double x) { 
       return 1/(1+Math.exp(-x)); 
      } 

      public double deriv(double x) { 
       double ev = eval(x); 
       return ev * (1-ev); 
      } 
}; 
public static final ActivationFunction HSF = new ActivationFunction(){ 
      public double eval(double x) { 
       if(x>0) return 1; 
       return 0; 
      } 

      public double deriv(double x) { 
       return (1); 
      } 
}; 
} 

package util; 

public interface ActivationFunction { 
public double eval(double x); 
public double deriv(double x); 
} 

Jetzt noch länger es ist. Verdammt.

+2

Was ist das spezifische Problem? Was ist das erwartete Ergebnis? Können Sie ein kürzeres Programm erstellen, um es zu reproduzieren? So wie es jetzt aussieht, stimme ich dafür ab, dies aufgrund von "Fragen, die Debugging-Hilfe suchen" ("Warum funktioniert dieser Code nicht?") Das gewünschte Verhalten, ein spezifisches Problem oder einen Fehler und den kürzesten Code, der notwendig ist, um es in der Frage zu reproduzieren, zu schließen Fragen ohne klare Problembeschreibung sind für andere Leser nicht nützlich. " –

+0

Wenn Sie ein einzelnes Neuron trainieren können, ist das Problem wahrscheinlich in Ihrer Backpropagate-Methode. Haben Sie eine "von Hand" Berechnung mit einem kleinen Netz versucht, um es zu vergleichen? Es wäre auch hilfreich, wenn Sie die fehlenden Klassen veröffentlichen könnten, damit Ihr Code ausgeführt werden könnte. – jbkm

+0

@KErlandsson: Ich habe das spezifische Problem hinzugefügt, ich werde in ein kürzeres Programm schauen, aber es wird sicherlich Zeit brauchen, da ich nicht ganz sicher bin, was nicht funktioniert und was ich daher herausnehmen kann. –

Antwort

1

In Ihrer Hauptmethode:

double[] in = {Math.round(Math.random()),Math.round(Math.random())}; 
int out = 0; 
if(in[1]==1^in[0] ==1) out = 1; 

erstellen Sie eine zufällige Eingabe (Kombination aus 1 und 0), die 0. Da Math.random Ziel erhält eine spezifische internen Samen (es gibt keine echte Zufälligkeit) sind Sie nicht In der Lage zu garantieren, dass über 10000 Iterationen alle 4 Eingänge von XOR mit dieser Technik in einem ausgewogenen Verhältnis erzeugt werden. Dies wiederum bedeutet, dass es möglich ist, dass in 10000 Iterationen {0.0,0.0} nur ein paar hundert Mal trainiert wurde, während {1.0,0.0}{0.0,1.0} etwa 8000 mal trainiert wurde. Wenn dies der Fall ist, würde dies Ihre Ergebnisse klar erklären und Ihr Training einschränken.

Anstatt zufällig Ihre Eingabedaten zu generieren, zufällig wählen Sie daraus. Halten Sie die äußere (Epochen) Schleife und führen Sie eine zweite Schleife, wo Sie eine zufällige Probe, die Sie in dieser Epoche noch ausgewählt haben (oder gehen Sie einfach durch Ihre Daten sequentiell ohne Zufälligkeit, es ist nicht wirklich ein Problem für XOR). Pseudo-Code ohne Zufälligkeit:

// use a custom class to realize the data structure (that defines the target values): 
TrainingSet = { {(0,0),0}, {(0,1),1}, {(1,0),1}, {(1,1),0} } 
for epochNr < epochs: 
    while(TrainingSet.hasNext()): 
     input = TrainingSet.getNext(); 
     network.feedInput(input) 

Auf diese Weise können Sie garantieren, dass jede Probe 2500mal in 10000 Iterationen zu sehen ist.

+0

Ich habe die sequentielle Methode gemacht und es hat nicht funktioniert; abhängig von der Lernkonstante und der Anzahl der Iterationen wurde das Netzwerk zu einer vorhersagbaren Sequenz trainiert - aber nicht zu einer korrekten. Einmal war es genau falsch, ein anderes Mal hatte es alles über eins verschoben - das Netzwerk selbst konvergiert nicht. –

Verwandte Themen