2015-07-04 18 views
5

Tôi đang cố triển khai FFNN bằng Java với tính năng truyền ngược và không biết tôi đang làm gì sai. Nó hoạt động khi tôi chỉ có một tế bào thần kinh duy nhất trong mạng, nhưng tôi đã viết một lớp khác để xử lý các mạng lớn hơn và không có gì hội tụ. Nó có vẻ như là một vấn đề trong toán học - hay đúng hơn là việc thực hiện toán học của tôi - nhưng tôi đã kiểm tra nó nhiều lần và tôi không thể tìm thấy bất cứ điều gì sai trái. Điều này sẽ được làm việc.
Node lớp:
Thực hiện mạng Neural trong 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 lớp:

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

nhanh giải thích về cấu trúc: mỗi nút có chức năng kích hoạt f; f.eval đánh giá hàm và f.deriv đánh giá đạo hàm của nó. Functions.SIG là hàm sigmoidal tiêu chuẩn và Functions.HSF là hàm bước Heaviside. Để thiết lập đầu vào của một hàm, bạn gọi addIW với giá trị đã bao gồm trọng số của đầu ra trước đó. Một điều tương tự được thực hiện trong backpropagation với addEW. Các nút được sắp xếp theo mảng 2d và trọng số được tổ chức riêng trong một mảng 3D như được mô tả.

Tôi nhận ra điều này có thể hơi hỏi một chút - và tôi chắc chắn nhận ra có bao nhiêu Java quy ước mã này bị phá vỡ - nhưng tôi đánh giá cao bất kỳ trợ giúp nào có thể cung cấp. EDIT: Vì câu hỏi này và mã của tôi là những bức tường khổng lồ của văn bản, nếu có một dòng liên quan đến nhiều biểu thức phức tạp trong ngoặc mà bạn không muốn tìm ra, hãy thêm nhận xét hoặc điều gì đó hỏi tôi và tôi ' sẽ cố gắng trả lời nó nhanh nhất có thể.

EDIT 2: Vấn đề cụ thể ở đây là mạng này không hội tụ trên XOR. Dưới đây là một số đầu ra để minh họa điều này:

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
mỗi dòng là định dạng TEST NUMBER: {INPUTS}: EXPECTED ACTUAL mạng lưới gọi train với mỗi bài kiểm tra, do đó, mạng này được backpropagating 10000 lần.

Dưới đây là hai lớp học thêm nếu có ai muốn chạy nó:

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

Bây giờ nó thậm chí còn lâu hơn. Darn.

+2

Vấn đề cụ thể là gì? kết quả được mong đợi là gì? Bạn có thể tạo một chương trình ngắn hơn để tái tạo nó không? Vì nó bây giờ tôi bỏ phiếu để đóng này do "Câu hỏi tìm kiếm trợ giúp gỡ lỗi (" tại sao mã này không hoạt động? ") Phải bao gồm hành vi mong muốn, một vấn đề hoặc lỗi cụ thể và mã ngắn nhất cần thiết để tái tạo nó trong câu hỏi Các câu hỏi không có tuyên bố rõ ràng về vấn đề không hữu ích cho những độc giả khác. ” –

+0

Nếu bạn có thể huấn luyện một nơ-ron đơn, vấn đề có thể xảy ra trong phương pháp backpropagate của bạn. Bạn đã thử tính toán "bằng tay" với một mạng nhỏ để so sánh nó với? Nó cũng sẽ giúp ích nếu bạn có thể đăng các lớp bị thiếu để mã của bạn có thể được chạy. – jbkm

+0

@KErlandsson: Tôi đã thêm vấn đề cụ thể, tôi sẽ xem xét một chương trình ngắn hơn nhưng chắc chắn sẽ mất thời gian vì tôi không hoàn toàn chắc chắn điều gì không hiệu quả và tôi sẽ có thể đưa ra điều gì. –

Trả lời

1

Trong phương pháp chính của bạn:

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

Bạn tạo một đầu vào ngẫu nhiên (sự kết hợp của 1 và 0) tiếp nhận mục tiêu 0. Kể từ Math.random có một hạt giống nội bộ cụ thể (không có tính ngẫu nhiên đúng) bạn không có thể đảm bảo rằng hơn 10000 lần lặp lại tất cả 4 đầu vào của XOR được tạo ra bởi một số tiền cân bằng với kỹ thuật này. Điều này có nghĩa là có thể trong 10000 lần lặp lại {0.0,0.0} chỉ được đào tạo vài trăm lần trong khi {1.0,0.0}{0.0,1.0} được đào tạo khoảng 8000 lần. Nếu đây là trường hợp, điều này sẽ giải thích rõ ràng kết quả của bạn và hạn chế đào tạo của bạn.

Thay vì ngẫu nhiên tạo dữ liệu đầu vào của bạn, ngẫu nhiên chọn từ đó. Giữ vòng lặp bên ngoài (epochs) và giới thiệu một vòng lặp thứ hai nơi bạn chọn một mẫu ngẫu nhiên mà bạn có không phải được chọn trong kỷ nguyên này (hoặc đơn giản là đi qua dữ liệu của bạn liên tục mà không có bất kỳ ngẫu nhiên nào, nó không thực sự là vấn đề đối với XOR). Mã giả mà không có bất kỳ ngẫu nhiên nào:

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

Bằng cách này bạn có thể đảm bảo rằng mỗi mẫu được xem 2500 lần trong 10000 lần lặp lại.

+0

Tôi đã làm phương pháp tuần tự và nó không hoạt động; tùy thuộc vào hằng số học tập và số lần lặp lại, mạng được đào tạo theo trình tự có thể dự đoán được - nhưng không phải là trình tự chính xác. Một lần nó nhận được tất cả mọi thứ một cách chính xác sai, một thời gian khác nó đã có tất cả mọi thứ chuyển qua một - mạng lưới chính nó không hội tụ. –

Các vấn đề liên quan