2015-07-04 36 views
5

Sto tentando di implementare un FFNN in Java con backpropagation e non ho idea di cosa sto facendo male. Ha funzionato quando avevo un solo neurone nella rete, ma ho scritto un'altra classe per gestire reti più grandi e nulla converge. Sembra un problema in matematica - o meglio la mia implementazione della matematica - ma l'ho controllato diverse volte e non riesco a trovare nulla di sbagliato. Questo dovrebbe funzionare. classe
Node: classe
Implementazione della rete neurale 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:

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

rapida spiegazione della struttura: ogni nodo ha una funzione di attivazione f; f.eval valuta la funzione e f.deriv ne valuta la derivata. Functions.SIG è la funzione sigmoidal standard e Functions.HSF è la funzione di passo Heaviside. Per impostare gli input di una funzione, si chiama addIW con un valore che include già il peso dell'output precedente. Una cosa simile è fatta in backpropagation con addEW. I nodi sono organizzati in un array 2d e i pesi sono organizzati separatamente in un array 3d come descritto.

Mi rendo conto che potrebbe essere un po 'troppo da chiedere - e certamente mi rendo conto di quante convenzioni Java questo codice si rompe - ma apprezzo qualsiasi aiuto che chiunque può offrire.

EDIT: Poiché questa domanda e il mio codice sono muri di testo così giganteschi, se c'è una riga che coinvolge un sacco di espressioni complicate tra parentesi che non vuoi capire, aggiungi un commento o qualcosa che mi chiede e io ' Proverò a rispondere il più velocemente possibile.

MODIFICA 2: Il problema specifico qui è che questa rete non converge su XOR. Ecco qualche uscita per illustrare questo:

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
ogni riga ha formato TEST NUMBER: {INPUTS}: EXPECTED ACTUAL la rete chiama train con ogni test, quindi questa rete è backpropagating 10000 volte.

Qui ci sono le due classi in più se qualcuno vuole eseguirlo:

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

Ora è ancora più a lungo. Darn.

+2

Qual è il problema specifico? Qual è il risultato atteso? Puoi fare un programma più breve per riprodurlo? Così come ora voterò per chiudere questo a causa di "Domande che cercano aiuto per il debug (" perché non funziona questo codice? ") Devono includere il comportamento desiderato, un problema specifico o un errore e il codice più breve necessario per riprodurlo nella domanda Domande senza una chiara affermazione del problema non sono utili ad altri lettori. " –

+0

Se riesci ad allenare un singolo neurone, il problema è probabilmente nel tuo metodo di backpropagate. Hai provato un calcolo "a mano" con una piccola rete per confrontarlo? Sarebbe anche d'aiuto se potessi pubblicare le classi mancanti in modo da poter eseguire il tuo codice. – jbkm

+0

@KErlandsson: Ho aggiunto il problema specifico, cercherò un programma più breve ma ci vorrà sicuramente del tempo perché non sono completamente sicuro di cosa non funzioni e di cosa potrò quindi fare. –

risposta

1

nel metodo principale:

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

Si crea un ingresso casuale (combinazione di 1 e 0) che riceve obiettivo 0. Poiché Math.random ha un seme interna specifica (non esiste una vera casualità) non si è in grado di garantire che oltre 10000 iterazioni tutti i 4 ingressi di XOR siano generati da una quantità bilanciata con questa tecnica. Questo a sua volta significa che è possibile che in 10000 iterazioni {0.0,0.0} sia stato addestrato solo un paio di volte mentre {1.0,0.0}{0.0,1.0} è stato addestrato circa 8000 volte. Se questo è il caso, questo spiegherebbe chiaramente i tuoi risultati e limiterà la tua formazione.

Anziché casualmente generare dati di input, in modo casuale scegliere da esso. Tieni il ciclo esterno (epoche) e presenta un secondo ciclo in cui scegli un campione casuale che hai non selezionato in questa epoca (o semplicemente passa i tuoi dati in sequenza senza alcuna casualità, non è davvero un problema per XOR). Pseudocodice senza alcuna casualità:

// 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) 

In questo modo si può garantire che ogni campione è visto 2500 volte in 10000 iterazioni.

+0

Ho eseguito il metodo sequenziale e non ha funzionato; a seconda della costante di apprendimento e del numero di iterazioni, la rete viene addestrata in una sequenza prevedibile, ma non corretta. Una volta ha sbagliato esattamente tutti, un'altra volta aveva tutto spostato su uno - la rete stessa non sta convergendo. –

Problemi correlati