2015-07-04 95 views
5

我试图用反向传播在Java中实现一个FFNN,并且不知道我在做什么错误。当我在网络中只有一个神经元时,它工作,但我写了另一个类来处理更大的网络,没有收敛。这在数学中似乎是一个问题 - 或者说我的数学实现 - 但我已经检查了好几次,我找不到任何错误。这应该是工作。
Node类:
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; 
} 
} 

结构的快速解释:每个节点具有一个活化函数f; f.eval评估函数,f.deriv评估其派生。 Functions.SIG是标准S形函数,Functions.HSF是Heaviside阶梯函数。为了设置功能的输入,您可以调用addIW,其值已包含前一个输出的权重。在addEW的反向传播中也做了类似的事情。节点被组织在一个二维数组中,并且权重按照所描述的以三维数组分开组织。

我意识到这可能有点多问 - 我当然意识到这个代码有多少Java约定 - 但我很感谢任何人都可以提供的帮助。

编辑:由于这个问题和我的代码是如此巨大的墙壁文本,如果有一个线条涉及很多复杂的表达式,你不想弄出来的括号中,添加一个评论或一些问我和我'我会尽可能快地回答它。

编辑2:这里的具体问题是,这个网络不会收敛在异或。下面是一些输出来说明这一点:

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
每行是格式TEST NUMBER: {INPUTS}: EXPECTED ACTUAL 网络调用train与每个测试的,所以这种网络backpropagating 10000次。

这里有两个额外的课程,如果有人想运行它

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

现在,甚至更长的时间。该死。

+2

具体问题是什么?预期的结果是什么?你能制作一个更短的程序来重现它吗?现在我决定关闭这个功能,因为“寻求调试帮助的问题(”为什么这个代码不工作?“)必须包含所需的行为,特定的问题或错误以及在问题中重现它的最短代码本身没有明确问题陈述的问题对其他读者没有用处。“ –

+0

如果您可以训练单个神经元,则问题可能出现在您的反向传播方法中。你有没有尝试用一个小网“手工”计算来比较它? 如果您可以发布缺失的类,以便您的代码可以运行,它也将有所帮助。 – jbkm

+0

@kerlandsson:我添加了具体的问题,我会研究一个较短的程序,但它肯定需要时间,因为我不完全确定什么是不工作,因此我能够拿出。 –

回答

1

在main方法:

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

您创建一个随机输入(1组合和0),接收目标0.由于Math.random有特定的内部种子(有没有真正的随机性)你是不是能够保证超过10000次迭代,所有4个XOR输入都是通过这种技术以均衡的量生成的。这又意味着有可能在10000次迭代中{0.0,0.0}仅被训练了几百次,而{1.0,0.0}{0.0,1.0}被训练了大约8000次。如果是这种情况,这将清楚地解释你的结果并限制你的训练。

而不是随机生成您的输入数据,随机从中挑。保持外部(时代)循环,并引入一个第二循环,您可以在此选择一个随机样本,您在此时期选择了而不是(或者直接通过数据顺序进行,没有任何随机性,对XOR来说这不是真正的问题)。没有任何随机性的伪代码:

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

这样,您可以保证在10000次迭代中看到每个样本2500次。

+0

我做了连续的方法,它没有工作;取决于学习常数和迭代次数,网络训练到可预测的序列 - 但不是正确的。有一次它让每一个人都错了,另一次它把所有东西都转移了一个 - 网络本身并没有收敛。 –