Lakukan sendiri jaringan saraf dari awal. Bagian 2. Implementasi

Seperti yang saya katakan di pengantar bagian pertama , saya adalah pengembang frontend, dan bahasa asli saya adalah JavaScript, kami akan menerapkan jaringan saraf di artikel ini menggunakannya. Pertama, beberapa kata tentang struktur. Kecuali untuk berbagai properti dan metode yang dihitung, objek jaringan saraf akan berisi larik lapisan, setiap lapisan akan berisi larik neuron, dan setiap neuron, pada gilirannya, akan berisi larik "masukan" - koneksi dengan neuron dari lapisan masukan sebelumnya. Juga, karena kita memiliki hal-hal umum untuk seluruh jaringan, seperti fungsi aktivasi, turunannya, dan kecepatan pembelajaran, dan kita perlu mengaksesnya dari setiap neuron, mari kita sepakati bahwa neuron akan memiliki tautan _layer ke lapisan tempat itu milik, dan lapisan akan memiliki _network - tautan ke jaringan itu sendiri.





Mari beralih dari privat ke umum dan pertama-tama gambarkan kelas input untuk neuron.





class Input {
 constructor(neuron, weight) {
   this.neuron = neuron;
   this.weight = weight;
 }
}
      
      



Semuanya sangat sederhana di sini. Setiap masukan memiliki bobot numerik dan referensi ke neuron dari lapisan sebelumnya. Mari melangkah lebih jauh. Mari kita gambarkan kelas neuron itu sendiri.





class Neuron {
 constructor(layer, previousLayer) {
   this._layer = layer;
   this.inputs = previousLayer
     ? previousLayer.neurons.map((neuron) => new Input(neuron, Math.random() - 0.5))
     : [0];
 }

 get $isFirstLayerNeuron() {
   return !(this.inputs[0] instanceof Input)
 }

 get inputSum() {
   return this.inputs.reduce((sum, input) => {
     return sum + input.neuron.value * input.weight;
   }, 0);
 }

 get value() {
   return this.$isFirstLayerNeuron
     ? this.inputs[0]
     : this._layer._network.activationFunction(this.inputSum);
 }

 set input(val) {
   if (!this.$isFirstLayerNeuron) {
     return;
   }

   this.inputs[0] = val;
 }

 set error(error) {
   if (this.$isFirstLayerNeuron) {
     return;
   }

   const wDelta = error * this._layer._network.derivativeFunction(this.inputSum);

   this.inputs.forEach((input) => {
     input.weight -= input.neuron.value * wDelta * this._layer._network.learningRate;
     input.neuron.error = input.weight * wDelta;
   });
 }
}
      
      



Mari kita lihat apa yang terjadi di sini. Kita dapat meneruskan dua parameter ke konstruktor neuron: lapisan tempat neuron ini berada dan, jika ini bukan lapisan masukan jaringan saraf, tautan ke lapisan sebelumnya.





Dalam konstruktor, untuk setiap neuron dari lapisan sebelumnya, kita akan membuat masukan yang akan menghubungkan neuron dan memiliki bobot acak, dan menulis semua masukan ke larik masukan. Jika ini adalah lapisan masukan jaringan, maka larik masukan akan terdiri dari satu nilai numerik, yang kita teruskan ke masukan.





$isFirstLayerNeuron - , , . , , .





inputSum - readonly , (, ) .





value - . , , inputSum.





:





input - , .





- error. , , error . , , .





. .





class Layer {
 constructor(neuronsCount, previousLayer, network) {
   this._network = network;
   this.neurons = [];
   for (let i = 0; i < neuronsCount; i++) {
     this.neurons.push(new Neuron(this, previousLayer));
   }
 }

 get $isFirstLayer() {
   return this.neurons[0].$isFirstLayerNeuron;
 }

 set input(val) {
   if (!this.$isFirstLayer) {
     return;
   }

   if (!Array.isArray(val)) {
     return;
   }

   if (val.length !== this.neurons.length) {
     return;
   }

   val.forEach((v, i) => this.neurons[i].input = v);
 }
}
      
      



- , neurons , , , .





$isFirstLayer - , , , input, , , . , .





, ,





class Network {
 static  sigmoid(x) {
   return 1 / (1 + Math.exp(-x));
 }

 static sigmoidDerivative(x) {
   return Network.sigmoid(x) * (1 - Network.sigmoid(x));
 }

 constructor(inputSize, outputSize, hiddenLayersCount = 1, learningRate = 0.5) {
   this.activationFunction = Network.sigmoid;
   this.derivativeFunction = Network.sigmoidDerivative;
   this.learningRate = learningRate;

   this.layers = [new Layer(inputSize, null, this)];

   for (let i = 0; i < hiddenLayersCount; i++) {
     const layerSize = Math.min(inputSize * 2 - 1, Math.ceil((inputSize * 2 / 3) + outputSize));
     this.layers.push(new Layer(layerSize, this.layers[this.layers.length - 1], this));
   }

   this.layers.push(new Layer(outputSize, this.layers[this.layers.length - 1], this));
 }

 set input(val) {
   this.layers[0].input = val;
 }

 get prediction() {
   return this.layers[this.layers.length - 1].neurons.map((neuron) => neuron.value);
 }

 trainOnce(dataSet) {
   if (!Array.isArray(dataSet)) {
     return;
   }

   dataSet.forEach((dataCase) => {
     const [input, expected] = dataCase;

     this.input = input;
     this.prediction.forEach((r, i) => {
       this.layers[this.layers.length - 1].neurons[i].error = r - expected[i];
     });
   });
 }

 train(dataSet, epochs = 100000) {
   return new Promise(resolve => {
     for (let i = 0; i < epochs; i++) {
       this.trainOnce(dataSet);
     }
     resolve();
   });
 }
}
      
      



, learning rate.





input - , .





prediction - , . .





trainOnce dataset - , , , - . , , . , , , .





train - , . . , .then, main thread.





, . - XOR.





.





const network = new Network(2, 1);
      
      



:





const data = [
 [[0, 0], [0]],
 [[0, 1], [1]],
 [[1, 0], [1]],
 [[1, 1], [0]],
];

      
      



, .





network.train(data).then(() => {
 const testData = [
   [0, 0],
   [0, 1],
   [1, 0],
   [1, 1],
 ];

 testData.forEach((input, index) => {
   network.input = input;
   console.log(`${input[0]} XOR ${input[1]} = ${network.prediction}`)
 });
});
      
      



, . .








All Articles