8

Tôi đã xây dựng một thiết lập ANN-BP thông thường với một đơn vị trên lớp đầu vào và đầu ra và 4 nút ẩn với sigmoid. Cung cấp cho nó một nhiệm vụ đơn giản để ước tính tuyến tính f(n) = n với n trong phạm vi 0-100.Hồi quy ANN, xấp xỉ hàm tuyến tính

VẤN ĐỀ: Bất kể số lượng các lớp, các đơn vị trong lớp ẩn hay hay không Tôi đang sử dụng thiên vị trong nút đánh giá cao nó học để xấp xỉ f (n) = Average (dữ liệu) như sau:

enter image description here

Mã được viết bằng JavaScript làm bằng chứng về khái niệm. Tôi đã định nghĩa ba lớp: Net, Layer và Connection, trong đó Layer là một mảng các giá trị đầu vào, thiên vị và đầu ra, kết nối là một mảng 2D có trọng số và trọng số delta. Đây là mã lớp nơi mà tất cả các tính toán quan trọng xảy ra:

Ann.Layer = function(nId, oNet, oConfig, bUseBias, aInitBiases) { 
var _oThis = this; 

var _initialize = function() { 
     _oThis.id  = nId; 
     _oThis.length = oConfig.nodes; 
     _oThis.outputs = new Array(oConfig.nodes); 
     _oThis.inputs = new Array(oConfig.nodes); 
     _oThis.gradients = new Array(oConfig.nodes); 
     _oThis.biases = new Array(oConfig.nodes); 

     _oThis.outputs.fill(0); 
     _oThis.inputs.fill(0); 
     _oThis.biases.fill(0); 

     if (bUseBias) { 
      for (var n=0; n<oConfig.nodes; n++) { 
       _oThis.biases[n] = Ann.random(aInitBiases[0], aInitBiases[1]); 
      } 
     } 
    }; 

/****************** PUBLIC ******************/ 

this.id; 
this.length; 
this.inputs; 
this.outputs; 
this.gradients; 
this.biases; 
this.next; 
this.previous; 

this.inConnection; 
this.outConnection; 

this.isInput = function() { return !this.previous;  } 
this.isOutput = function() { return !this.next;   } 

this.calculateGradients = function(aTarget) { 
    var n, n1, nOutputError, 
     fDerivative = Ann.Activation.Derivative[oConfig.activation]; 

    if (this.isOutput()) { 
     for (n=0; n<oConfig.nodes; n++) { 
      nOutputError = this.outputs[n] - aTarget[n]; 
      this.gradients[n] = nOutputError * fDerivative(this.outputs[n]); 
     } 
    } else { 
     for (n=0; n<oConfig.nodes; n++) { 
      nOutputError = 0.0; 
      for (n1=0; n1<this.outConnection.weights[n].length; n1++) { 
       nOutputError += this.outConnection.weights[n][n1] * this.next.gradients[n1]; 
      } 
      // console.log(this.id, nOutputError, this.outputs[n], fDerivative(this.outputs[n])); 
      this.gradients[n] = nOutputError * fDerivative(this.outputs[n]); 
     } 
    } 
} 

this.updateInputWeights = function() { 
    if (!this.isInput()) { 
     var nY, 
      nX, 
      nOldDeltaWeight, 
      nNewDeltaWeight; 

     for (nX=0; nX<this.previous.length; nX++) { 
      for (nY=0; nY<this.length; nY++) { 
       nOldDeltaWeight = this.inConnection.deltaWeights[nX][nY]; 
       nNewDeltaWeight = 
        - oNet.learningRate 
        * this.previous.outputs[nX] 
        * this.gradients[nY] 
        // Add momentum, a fraction of old delta weight 
        + oNet.learningMomentum 
        * nOldDeltaWeight; 

       if (nNewDeltaWeight == 0 && nOldDeltaWeight != 0) { 
        console.log('Double overflow'); 
       } 

       this.inConnection.deltaWeights[nX][nY] = nNewDeltaWeight; 
       this.inConnection.weights[nX][nY]  += nNewDeltaWeight; 
      } 
     } 
    } 
} 

this.updateInputBiases = function() { 
    if (bUseBias && !this.isInput()) { 
     var n, 
      nNewDeltaBias; 

     for (n=0; n<this.length; n++) { 
      nNewDeltaBias = 
       - oNet.learningRate 
       * this.gradients[n]; 

      this.biases[n] += nNewDeltaBias; 
     } 
    } 
} 

this.feedForward = function(a) { 
    var fActivation = Ann.Activation[oConfig.activation]; 

    this.inputs = a; 

    if (this.isInput()) { 
     this.outputs = this.inputs; 
    } else { 
     for (var n=0; n<a.length; n++) { 
      this.outputs[n] = fActivation(a[n] + this.biases[n]); 
     } 
    } 
    if (!this.isOutput()) { 
     this.outConnection.feedForward(this.outputs); 
    } 
} 

_initialize(); 
} 

feedforward chính và chức năng backProp được định nghĩa như sau:

this.feedForward = function(a) { 
    this.layers[0].feedForward(a); 
    this.netError = 0; 
} 

this.backPropagate = function(aExample, aTarget) { 
    this.target = aTarget; 

    if (aExample.length != this.getInputCount()) { throw "Wrong input count in training data"; } 
    if (aTarget.length != this.getOutputCount()) { throw "Wrong output count in training data"; } 

    this.feedForward(aExample); 
    _calculateNetError(aTarget); 

    var oLayer = null, 
     nLast = this.layers.length-1, 
     n; 

    for (n=nLast; n>0; n--) { 
     if (n === nLast) { 
      this.layers[n].calculateGradients(aTarget); 
     } else { 
      this.layers[n].calculateGradients(); 
     } 
    } 

    for (n=nLast; n>0; n--) { 
     this.layers[n].updateInputWeights(); 
     this.layers[n].updateInputBiases(); 
    } 
} 

Mã kết nối khá đơn giản:

Ann.Connection = function(oNet, oConfig, aInitWeights) { 
var _oThis = this; 

var _initialize = function() { 
     var nX, nY, nIn, nOut; 

     _oThis.from = oNet.layers[oConfig.from]; 
     _oThis.to = oNet.layers[oConfig.to]; 

     nIn = _oThis.from.length; 
     nOut = _oThis.to.length; 

     _oThis.weights  = new Array(nIn); 
     _oThis.deltaWeights = new Array(nIn); 

     for (nX=0; nX<nIn; nX++) { 
      _oThis.weights[nX]  = new Array(nOut); 
      _oThis.deltaWeights[nX] = new Array(nOut); 
      _oThis.deltaWeights[nX].fill(0); 
      for (nY=0; nY<nOut; nY++) { 
       _oThis.weights[nX][nY] = Ann.random(aInitWeights[0], aInitWeights[1]); 
      } 
     } 
    }; 

/****************** PUBLIC ******************/ 

this.weights; 
this.deltaWeights; 
this.from; 
this.to; 

this.feedForward = function(a) { 
    var n, nX, nY, aOut = new Array(this.to.length); 

    for (nY=0; nY<this.to.length; nY++) { 
     n = 0; 
     for (nX=0; nX<this.from.length; nX++) { 
      n += a[nX] * this.weights[nX][nY]; 
     } 
     aOut[nY] = n; 
    } 

    this.to.feedForward(aOut); 
} 

_initialize(); 
} 

Và chức năng kích hoạt và các dẫn xuất của tôi được xác định như sau:

Ann.Activation = { 
    linear : function(n) { return n; }, 
    sigma : function(n) { return 1.0/(1.0 + Math.exp(-n)); }, 
    tanh : function(n) { return Math.tanh(n); } 
} 

Ann.Activation.Derivative = { 
    linear : function(n) { return 1.0; }, 
    sigma : function(n) { return n * (1.0 - n); }, 
    tanh : function(n) { return 1.0 - n * n; } 
} 

Và cấu hình JSON cho mạng như sau:

var Config = { 
    id : "Config1", 

    learning_rate  : 0.01, 
    learning_momentum : 0, 
    init_weight  : [-1, 1], 
    init_bias   : [-1, 1], 
    use_bias   : false, 

    layers: [ 
     {nodes : 1}, 
     {nodes : 4, activation : "sigma"}, 
     {nodes : 1, activation : "linear"} 
    ], 

    connections: [ 
     {from : 0, to : 1}, 
     {from : 1, to : 2} 
    ] 
} 

Có lẽ, mắt kinh nghiệm của bạn có thể phát hiện các vấn đề với tính toán của tôi?

See example in JSFiddle

Trả lời

1

Đầu tiên ... Tôi thực sự thích mã này. Tôi biết rất ít về NN (chỉ mới bắt đầu) nên tôi tha thứ cho tôi nếu có.

Đây là một bản tóm tắt các thay đổi mà tôi thực hiện:

//updateInputWeights has this in the middle now: 
 

 
nNewDeltaWeight = 
 
oNet.learningRate 
 
* this.gradients[nY] 
 
/this.previous.outputs[nX] 
 
// Add momentum, a fraction of old delta weight 
 
+ oNet.learningMomentum 
 
* nOldDeltaWeight; 
 

 

 
//updateInputWeights has this at the bottom now: 
 

 
this.inConnection.deltaWeights[nX][nY] += nNewDeltaWeight; // += added 
 
this.inConnection.weights[nX][nY]  += nNewDeltaWeight; 
 

 
// I modified the following: 
 

 
\t _calculateNetError2 = function(aTarget) { 
 
\t \t var oOutputLayer = _oThis.getOutputLayer(), 
 
\t \t \t nOutputCount = oOutputLayer.length, 
 
\t \t \t nError = 0.0, 
 
\t \t \t nDelta = 0.0, 
 
\t \t \t n; 
 

 
\t \t for (n=0; n<nOutputCount; n++) { 
 
\t \t \t nDelta = aTarget[n] - oOutputLayer.outputs[n]; 
 
\t \t \t nError += nDelta; 
 
\t \t } 
 

 
\t \t _oThis.netError = nError; 
 
\t };

Phần cấu hình trông như thế này bây giờ:

var Config = { 
 
id : "Config1", 
 

 
learning_rate  : 0.001, 
 
learning_momentum : 0.001, 
 
init_weight  : [-1.0, 1.0], 
 
init_bias   : [-1.0, 1.0], 
 
use_bias   : false, 
 

 
/* 
 
layers: [ 
 
\t {nodes : 1, activation : "linear"}, 
 
\t {nodes : 5, activation : "linear"}, 
 
\t {nodes : 1, activation : "linear"} 
 
], 
 

 
connections: [ 
 
\t {from : 0, to : 1} 
 
\t ,{from : 1, to : 2} 
 
] 
 
*/ 
 

 

 
layers: [ 
 
\t {nodes : 1, activation : "linear"}, 
 
\t {nodes : 2, activation : "linear"}, 
 
\t {nodes : 2, activation : "linear"}, 
 
\t {nodes : 2, activation : "linear"}, 
 
\t {nodes : 2, activation : "linear"}, 
 
\t {nodes : 1, activation : "linear"} 
 
], 
 

 
connections: [ 
 
\t {from : 0, to : 1} 
 
\t ,{from : 1, to : 2} 
 
\t ,{from : 2, to : 3} 
 
\t ,{from : 3, to : 4} 
 
\t ,{from : 4, to : 5} 
 
] 
 

 
}

These were my resulting images:

+1

Cảm ơn sự quan tâm của bạn, nhưng tôi không hiểu: 1) Tại sao chúng tôi đang tích lũy delta_weights? 2) Tại sao chúng ta cần 4 lớp ẩn cho một xấp xỉ đơn giản? –

+0

Tôi đã sai khi tích lũy, cảm ơn vì đã chỉ ra điều đó. Đối với chiều sâu, nó làm việc tốt hơn theo cách đó sau khi các mã nhẹ khác thay đổi. Tôi đã giảm nó không 1,2,2,1 ... học tỷ lệ 0,06 và động lượng 0,04. Nhìn chung, mã có vẻ hoạt động tốt hơn nó. Nếu bạn không đồng ý, đó là tốt. Tôi chỉ cố gắng giúp đỡ trong khi tôi học. –

+0

Cảm ơn. Một điều khác mà tôi vừa mới nhận thấy, bạn đã loại bỏ bình phương lỗi, điều này cho phép một dấu hiệu lỗi để lẻn vào tính toán. Điều này có nghĩa là lỗi âm sẽ hủy bỏ lỗi tích cực trong vòng lặp hoặc lỗi âm có thể tích lũy và ngăn chúng tôi đo khi dừng đào tạo. –

2

Tôi đã không nhìn rộng rãi vào mã (vì nó là rất nhiều mã để xem xét, sẽ cần phải mất nhiều thời gian hơn cho rằng sau này, và tôi không quen thuộc với javascript). Dù bằng cách nào, tôi tin rằng Stephen đã giới thiệu một số thay đổi về cách tính trọng số và mã của anh ấy dường như cho kết quả chính xác, vì vậy tôi khuyên bạn nên xem xét điều đó.

Dưới đây là một vài điểm rằng mặc dù không nhất thiết về tính chính xác của tính toán, nhưng vẫn có thể giúp:

  • bao nhiêu ví dụ được bạn cho thấy mạng lưới đào tạo?Bạn có hiển thị cùng một đầu vào nhiều lần không? Bạn nên hiển thị mọi ví dụ mà bạn có (đầu vào) nhiều lần; cho thấy mỗi ví dụ chỉ có một thời gian là không đủ cho các thuật toán dựa trên gradient descent để tìm hiểu, vì chúng chỉ di chuyển một chút theo đúng hướng mỗi lần. Có thể tất cả các mã của bạn là chính xác, nhưng bạn chỉ cần cung cấp cho nó thêm một chút thời gian để đào tạo.
  • Giới thiệu thêm các lớp ẩn như Stephen đã làm có thể giúp tăng tốc độ đào tạo hoặc có thể gây hại. Đây thường là điều bạn muốn thử nghiệm cho trường hợp cụ thể của mình. Nó chắc chắn không cần thiết cho vấn đề đơn giản này. Tôi nghi ngờ một sự khác biệt quan trọng hơn giữa cấu hình của bạn và cấu hình của Stephen có thể là chức năng kích hoạt được sử dụng trong (các) lớp ẩn. Bạn đã sử dụng sigmoid, có nghĩa là tất cả các giá trị đầu vào bị đè bẹp nằm dưới 1.0 trong lớp ẩn, và sau đó bạn cần trọng số rất lớn để chuyển đổi những con số này về đầu ra mong muốn (có thể lên đến giá trị của 100). Stephen đã sử dụng các hàm kích hoạt tuyến tính cho tất cả các lớp, trong trường hợp cụ thể này có khả năng làm cho việc đào tạo trở nên dễ dàng hơn nhiều bởi vì bạn đang thực sự cố gắng tìm hiểu một hàm tuyến tính. Trong nhiều trường hợp khác, nó sẽ là mong muốn để giới thiệu phi tuyến tính mặc dù.
  • Có thể có ích khi chuyển đổi (chuẩn hóa) cả đầu vào và đầu ra mong muốn của bạn nằm trong [0, 1] thay vì [0, 100]. Điều này sẽ khiến cho lớp sigmoid của bạn có khả năng tạo ra kết quả tốt hơn (mặc dù tôi vẫn không chắc liệu nó có đủ hay không, vì bạn vẫn đang giới thiệu một phi tuyến trong trường hợp bạn định học hàm tuyến tính, và bạn có thể cần thêm các nút ẩn để sửa cho điều đó). Trong trường hợp '' thế giới thực '', nơi bạn có nhiều biến đầu vào khác nhau, điều này cũng thường được thực hiện, bởi vì nó đảm bảo rằng tất cả các biến đầu vào được coi là quan trọng không kém phần quan trọng ban đầu. Bạn luôn có thể thực hiện một bước tiền xử lý nơi bạn chuẩn hóa đầu vào thành [0, 1], cho nó làm đầu vào cho mạng, đào tạo nó để tạo ra kết quả trong [0, 1], và sau đó thêm một bước xử lý hậu kỳ nơi bạn chuyển đổi đầu ra trở lại phạm vi ban đầu.
+0

Một điểm rất hợp lệ về chức năng sigmoid so với tuyến tính. Cảm ơn Steven vì đã chọn nó. Khi tôi hiểu nó là sigmoid với bình thường hóa hoặc tuyến tính-tất cả các xung quanh mà không bình thường hóa? –

+0

Liên quan đến việc phải có nhiều lớp, không mâu thuẫn với điều này: https://en.wikipedia.org/wiki/Universal_approximation_theorem? –

+0

@LexPodgorny không, bạn cũng có thể thực hiện bình thường hóa trong trường hợp các hàm kích hoạt tuyến tính. Tôi nghi ngờ nó là ít cần thiết ở đó, nhưng vẫn có thể giúp (lỗi nhỏ hơn và gradient có thể ổn định hơn về số lượng). Đối với định lý, chỉ mô tả rằng một lớp với số lượng hữu hạn các nút về mặt lý thuyết là đủ. Đó vẫn có thể là một lớp với số lượng lớn các nút (hữu hạn, nhưng rất lớn), và đòi hỏi một lượng thời gian đào tạo lớn. Vì vậy, không phải là một mâu thuẫn. –

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