network = new NetworkPerceptron( $networkWeightsProvider->generate( $datasetReader->getInputSize(), $datasetReader->getOutputSize(), 0, // No hidden layer 0, // No hidden layer neurons ), $datasetReader->getInputSize(), GradientDescentPerceptron::class, // No hidden layer SimpleBinaryPerceptron2::class, ); $this->labels = $datasetReader->getLabels(); } public function start(): void { $this->epoch = 0; do { $this->epochError = 0; $this->epoch++; $inputsForCurrentEpoch = []; while ($nextRow = $this->datasetReader->getNextLine()) { $inputsForCurrentEpoch[] = $nextRow; $inputs = array_slice($nextRow, 0, -1); $correctOutput = (int) end($nextRow); $iterationError = $this->iterationFunction($inputs, $correctOutput); // Synaptic weights correction after each example $synaptic_weights = $this->network->getSynapticWeights(); $inputs_with_bias = array_merge([1], $inputs); // Add bias input // Updates the weights $this->network->setSynapticWeights( $this->getUpdatedSynapticWeights($synaptic_weights, $iterationError, $inputs_with_bias) ); // Broadcast the training iteration event $this->addIterationToBuffer(array_sum($iterationError), $this->network->getSynapticWeights()); } // Calculte the average error for the epoch with the last synaptic weights foreach ($inputsForCurrentEpoch as $inputsWithLabel) { $inputs = array_slice($inputsWithLabel, 0, -1); $correctOutput = (float) end($inputsWithLabel); $iterationError = $this->iterationFunction($inputs, $correctOutput); foreach ($iterationError as $error) { $this->epochError += ($error ** 2) / 2; // Squared error for the example } } $this->epochError /= $this->datasetReader->getEpochExamplesCount(); // Average error for the epoch $this->datasetReader->reset(); // Reset the dataset for the next iteration } while ($this->epoch < $this->maxEpochs && ! $this->stopCondition()); $this->iterationEventBuffer->flush(); // Ensure all iterations are sent to the frontend $this->checkPassedMaxIterations($this->epochError); } protected function stopCondition(): bool { $condition = $this->epochError <= $this->minError; if ($condition === true) { event(new PerceptronTrainingEnded('Le perceptron à atteint l\'erreur minimale', $this->sessionId, $this->trainingId)); } return $condition; } private function iterationFunction(array $inputs, int $correctOutput): array { $outputs = $this->network->test($inputs); $desiredOutput = $this->getDesiredOutputFromCorrectOutput($correctOutput); $errors = []; foreach ($outputs as $index => $output) { $error = $desiredOutput[$index] - $output; $errors[] = $error; } return $errors; } private function getUpdatedSynapticWeights(array $synaptic_weights, array $iterationError, array $inputs): array { $updatedWeights = []; foreach ($synaptic_weights[0] as $neuronIndex => $neuronWeights) { // There is only one layer of weights $updatedNeuronWeights = []; foreach ($neuronWeights as $weightIndex => $weight) { $updatedWeight = $weight + ($this->learningRate * $iterationError[$neuronIndex] * $inputs[$weightIndex]); $updatedNeuronWeights[] = $updatedWeight; } $updatedWeights[] = $updatedNeuronWeights; } return [$updatedWeights]; } private function getDesiredOutputFromCorrectOutput(int $correctOutput): array { $desiredOutput = array_fill(0, count($this->labels), -1); $labelIndex = Arr::first(array_keys($this->labels), fn($key) => $this->labels[$key] == $correctOutput); if ($labelIndex !== null) { $desiredOutput[$labelIndex] = 1; } return $desiredOutput; } public function getSynapticWeights(): array { return [[$this->network->getSynapticWeights()]]; } }