Rede neural "faça você mesmo" do zero. Parte 2. Implementação

Como eu disse na introdução da primeira parte , sou um desenvolvedor front-end e minha linguagem nativa é JavaScript, vamos implementar nossa rede neural neste artigo usando-a. Primeiro, algumas palavras sobre estrutura. Exceto por várias propriedades e métodos computados, o objeto de rede neural conterá uma matriz de camadas, cada camada conterá uma matriz de neurônios e cada neurônio, por sua vez, conterá uma matriz de "entradas" - conexões com os neurônios do camada de entradas anterior. Além disso, uma vez que temos coisas comuns para toda a rede, como a função de ativação, sua derivada e a taxa de aprendizagem, e precisamos acessá-los de cada neurônio, vamos concordar que o neurônio terá um link _layer para a camada para a qual ele pertence, e a camada terá _network - um link para a própria rede.





Vamos passar de privado a geral e primeiro descrever a classe de entrada para o neurônio.





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



Tudo é muito simples aqui. Cada entrada possui um peso numérico e uma referência ao neurônio da camada anterior. Vamos mais longe. Vamos descrever a classe do próprio neurônio.





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



Vamos ver o que está acontecendo aqui. Podemos passar dois parâmetros para o construtor do neurônio: a camada em que esse neurônio está localizado e, se esta não for a camada de entrada da rede neural, um link para a camada anterior.





No construtor, para cada neurônio da camada anterior, criaremos uma entrada que conectará os neurônios e terá um peso aleatório, e escreveremos todas as entradas no array de entradas. Se esta for a camada de entrada da rede, a matriz de entradas consistirá em um único valor numérico, aquele que passamos para a entrada.





$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