Comme je l'ai dit dans l'introduction de la premiĂšre partie , je suis un dĂ©veloppeur frontend, et mon langage natif est JavaScript, nous allons implĂ©menter notre rĂ©seau de neurones dans cet article en l'utilisant. Tout d'abord, quelques mots sur la structure. Ă l'exception de diverses propriĂ©tĂ©s et mĂ©thodes calculĂ©es, l'objet rĂ©seau de neurones contiendra un tableau de couches, chaque couche contiendra un tableau de neurones, et chaque neurone, Ă son tour, contiendra un tableau d '«entrĂ©es» - connexions avec les neurones du couche d'entrĂ©es prĂ©cĂ©dente. De plus, comme nous avons des choses communes Ă l'ensemble du rĂ©seau, telles que la fonction d'activation, son dĂ©rivĂ© et le taux d'apprentissage, et que nous devons y accĂ©der Ă partir de chaque neurone, admettons que le neurone aura un lien _layer vers la couche Ă laquelle il appartient, et la couche aura _network - un lien vers le rĂ©seau lui-mĂȘme.
Passons du privé au général et décrivons d'abord la classe d'entrée du neurone.
class Input {
constructor(neuron, weight) {
this.neuron = neuron;
this.weight = weight;
}
}
Tout est assez simple ici. Chaque entrĂ©e a un poids numĂ©rique et une rĂ©fĂ©rence au neurone de la couche prĂ©cĂ©dente. Allons plus loin. DĂ©crivons la classe du neurone lui-mĂȘme.
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;
});
}
}
Voyons ce qui se passe ici. On peut passer deux paramÚtres au constructeur du neurone: la couche sur laquelle se trouve ce neurone et, si ce n'est pas la couche d'entrée du réseau de neurones, un lien vers la couche précédente.
Dans le constructeur, pour chaque neurone de la couche précédente, nous allons créer une entrée qui connectera les neurones et aura un poids aléatoire, et écrirons toutes les entrées dans le tableau d'entrées. S'il s'agit de la couche d'entrée du réseau, alors le tableau d'entrées sera constitué d'une seule valeur numérique, celle que nous transmettons à l'entrée.
$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}`)
});
});
, . .