diff --git a/app/Http/Controllers/PerceptronController.php b/app/Http/Controllers/PerceptronController.php index 10e6ef4..8c74119 100644 --- a/app/Http/Controllers/PerceptronController.php +++ b/app/Http/Controllers/PerceptronController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; use App\Events\PerceptronInitialization; +use App\Http\Requests\RunPerceptronRequest; use App\Models\NetworksTraining\ADALINEPerceptronTraining; use App\Models\NetworksTraining\GradientDescentPerceptronTraining; use App\Models\NetworksTraining\MonoLayerPerceptronTraining; @@ -18,7 +19,6 @@ use App\Services\SynapticWeightsProvider\RandomSynapticWeights; use App\Services\SynapticWeightsProvider\ZeroSynapticWeights; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Validator; class PerceptronController extends Controller { @@ -168,20 +168,10 @@ class PerceptronController extends Controller return new RandomOrderDataSetReader($dataSetFileName); } - public function run(Request $request, ISynapticWeightsProvider $synapticWeightsProvider) + public function run(RunPerceptronRequest $request, ISynapticWeightsProvider $synapticWeightsProvider) { $startTime = microtime(true); - // Verifications - $validator = Validator::make($request->all(), config('perceptron.run_inputs_validation')); - - if ($validator->fails()) { - return response()->json([ - 'message' => 'Invalid input parameters', - 'errors' => $validator->errors(), - ], 400); - } - $perceptronType = $request->input('type'); $hiddenLayers = $request->input('hidden_layers', 2); $hiddenLayersNeurons = $request->input('hidden_layers_neurons', 3); @@ -194,13 +184,12 @@ class PerceptronController extends Controller $trainingId = $request->input('training_id'); // Remove the jobs for the sessionId - DB::table('jobs')->where('payload', 'like', '%s:9:\"sessionId\";s:40:\"'. $sessionId .'\";%')->delete(); + DB::table('jobs')->where('payload', 'like', '%s:9:\"sessionId\";s:40:\"'.$sessionId.'\";%')->delete(); // Zero initialization prevents hidden layers from receiving a gradient. if ($perceptronType === 'multilayer' && $weightInitMethod === 'zeros') { $synapticWeightsProvider = new RandomSynapticWeights; - } - else if ($weightInitMethod === 'zeros') { + } elseif ($weightInitMethod === 'zeros') { $synapticWeightsProvider = new ZeroSynapticWeights; } diff --git a/app/Http/Requests/RunPerceptronRequest.php b/app/Http/Requests/RunPerceptronRequest.php new file mode 100644 index 0000000..345c4f7 --- /dev/null +++ b/app/Http/Requests/RunPerceptronRequest.php @@ -0,0 +1,29 @@ + ['required', 'string', 'in:simple,gradientdescent,adaline,monolayer,multilayer'], + 'dataset' => ['required', 'string', 'max:100', 'regex:/^[A-Za-z0-9_-]+$/'], + 'weight_init_method' => ['required', 'string', 'in:random,zeros'], + 'learning_rate' => ['required', 'numeric', 'min:0'], + 'min_error' => ['required', 'numeric', 'min:0'], + 'hidden_layers' => ['required', 'integer', 'min:1', 'max:5'], + 'hidden_layers_neurons' => ['required', 'integer', 'min:1', 'max:5'], + 'max_iterations' => ['required', 'integer', 'min:1', 'max:5000'], + 'session_id' => ['required', 'string', 'max:100'], + 'training_id' => ['required', 'string', 'max:100'], + ]; + } +} diff --git a/app/Services/DatasetReader/LinearOrderDataSetReader.php b/app/Services/DatasetReader/LinearOrderDataSetReader.php index 219396e..e4af3a5 100644 --- a/app/Services/DatasetReader/LinearOrderDataSetReader.php +++ b/app/Services/DatasetReader/LinearOrderDataSetReader.php @@ -8,7 +8,7 @@ class LinearOrderDataSetReader implements IDataSetReader { public array $lines = []; - private array $currentLines = []; + private int $currentLineIndex = 0; private int $lastReadLineIndex = -1; @@ -35,13 +35,13 @@ class LinearOrderDataSetReader implements IDataSetReader public function getNextLine(): ?array { - if (! isset($this->currentLines[0])) { + if (! isset($this->lines[$this->currentLineIndex])) { return null; // No more lines to read } - $this->lastReadLineIndex = array_search($this->currentLines[0], $this->lines, true); + $this->lastReadLineIndex = $this->currentLineIndex; - return array_shift($this->currentLines); + return $this->lines[$this->currentLineIndex++]; } public function getInputSize(): int @@ -53,18 +53,20 @@ class LinearOrderDataSetReader implements IDataSetReader { // Count the number of unique labels in the dataset $labels = array_map(fn ($line) => end($line), $this->lines); + return count(array_unique($labels)); } public function getLabels(): array { $labels = array_map(fn ($line) => end($line), $this->lines); + return array_values(array_unique($labels)); } public function reset(): void { - $this->currentLines = $this->lines; + $this->currentLineIndex = 0; } public function getLastReadLineIndex(): int diff --git a/app/Services/DatasetReader/RandomOrderDataSetReader.php b/app/Services/DatasetReader/RandomOrderDataSetReader.php index 558a7f5..db064da 100644 --- a/app/Services/DatasetReader/RandomOrderDataSetReader.php +++ b/app/Services/DatasetReader/RandomOrderDataSetReader.php @@ -8,7 +8,9 @@ class RandomOrderDataSetReader implements IDataSetReader { public array $lines = []; - private array $currentLines = []; + private array $currentLineIndexes = []; + + private int $currentLineIndex = 0; private int $lastReadLineIndex = -1; @@ -35,19 +37,14 @@ class RandomOrderDataSetReader implements IDataSetReader public function getNextLine(): ?array { - if (empty($this->currentLines)) { + if (! isset($this->currentLineIndexes[$this->currentLineIndex])) { return null; // No more lines to read } - $randomNumber = array_rand($this->currentLines); - $randomLine = $this->currentLines[$randomNumber]; + $lineIndex = $this->currentLineIndexes[$this->currentLineIndex++]; - // Remove the line from the current lines to avoid repetition - unset($this->currentLines[$randomNumber]); + $this->lastReadLineIndex = $lineIndex; - // Remember the index of the last read line in the full list - $this->lastReadLineIndex = array_search($randomLine, $this->lines, true); - - return $randomLine; + return $this->lines[$lineIndex]; } public function getInputSize(): int @@ -59,18 +56,22 @@ class RandomOrderDataSetReader implements IDataSetReader { // Count the number of unique labels in the dataset $labels = array_map(fn ($line) => end($line), $this->lines); + return count(array_unique($labels)); } public function getLabels(): array { $labels = array_map(fn ($line) => end($line), $this->lines); + return array_values(array_unique($labels)); } public function reset(): void { - $this->currentLines = $this->lines; + $this->currentLineIndexes = array_keys($this->lines); + shuffle($this->currentLineIndexes); + $this->currentLineIndex = 0; } public function getLastReadLineIndex(): int diff --git a/app/Services/IterationEventBuffer/PerceptronIterationEventBuffer.php b/app/Services/IterationEventBuffer/PerceptronIterationEventBuffer.php index 0217ef5..dc68b56 100644 --- a/app/Services/IterationEventBuffer/PerceptronIterationEventBuffer.php +++ b/app/Services/IterationEventBuffer/PerceptronIterationEventBuffer.php @@ -2,27 +2,24 @@ namespace App\Services\IterationEventBuffer; +use App\Events\PerceptronTrainingIteration; + class PerceptronIterationEventBuffer implements IPerceptronIterationEventBuffer { - private $data; - - private int $nextSizeIncreaseThreshold; - - private int $underSizeIncreaseCount = 0; + private array $data = []; public function __construct( private string $sessionId, private string $trainingId, - private int $sizeIncreaseStart = 10, - private int $sizeIncreaseFactor = 2, - ) { - $this->data = []; - $this->nextSizeIncreaseThreshold = $sizeIncreaseStart; - } + ) {} public function flush(): void { - event(new \App\Events\PerceptronTrainingIteration($this->data, $this->sessionId, $this->trainingId)); + if ($this->data === []) { + return; + } + + event(new PerceptronTrainingIteration($this->data, $this->sessionId, $this->trainingId)); $this->data = []; } @@ -35,27 +32,24 @@ class PerceptronIterationEventBuffer implements IPerceptronIterationEventBuffer 'weights' => $synaptic_weights, ]; - $payload = [ - 'iterations' => [...$this->data, $iteration], - 'trainingId' => $this->trainingId, - ]; - - if ($this->data !== [] && strlen(json_encode($payload, JSON_THROW_ON_ERROR)) > config('broadcasting.broadcast_max_payload_size')) { - $this->flush(); - } - $this->data[] = $iteration; - if ($this->underSizeIncreaseCount <= $this->sizeIncreaseStart) { // We can still send a single date because we are under the increase start threshold - $this->underSizeIncreaseCount++; + if ($this->data !== [] && $this->payloadExceedsLimit()) { + $lastIteration = array_pop($this->data); $this->flush(); - } elseif (count($this->data) >= $this->nextSizeIncreaseThreshold) { - $this->flush(); - $this->nextSizeIncreaseThreshold *= $this->sizeIncreaseFactor; + $this->data[] = $lastIteration; + } - if ($this->nextSizeIncreaseThreshold > config('perceptron.broadcast_iteration_size')) { - $this->nextSizeIncreaseThreshold = config('perceptron.broadcast_iteration_size'); // Cap the threshold to the maximum size - } + if (count($this->data) >= config('perceptron.broadcast_iteration_size')) { + $this->flush(); } } + + private function payloadExceedsLimit(): bool + { + return strlen(json_encode([ + 'iterations' => $this->data, + 'trainingId' => $this->trainingId, + ], JSON_THROW_ON_ERROR)) > config('broadcasting.broadcast_max_payload_size'); + } } diff --git a/app/Services/IterationEventBuffer/PerceptronLimitedEpochEventBuffer.php b/app/Services/IterationEventBuffer/PerceptronLimitedEpochEventBuffer.php index 88fcac9..a7cf121 100644 --- a/app/Services/IterationEventBuffer/PerceptronLimitedEpochEventBuffer.php +++ b/app/Services/IterationEventBuffer/PerceptronLimitedEpochEventBuffer.php @@ -2,24 +2,29 @@ namespace App\Services\IterationEventBuffer; +use App\Events\PerceptronTrainingIteration; + class PerceptronLimitedEpochEventBuffer implements IPerceptronIterationEventBuffer { - private array $data; + private array $data = []; - private int $underSizeIncreaseCount = 0; + private ?int $activeEpoch = null; + + private bool $shouldBroadcastEpoch = false; public function __construct( private string $sessionId, private string $trainingId, private int $epochInterval, - private int $sizeIncreaseStart = 10, - ) { - $this->data = []; - } + ) {} public function flush(): void { - event(new \App\Events\PerceptronTrainingIteration($this->data, $this->sessionId, $this->trainingId)); + if ($this->data === []) { + return; + } + + event(new PerceptronTrainingIteration($this->data, $this->sessionId, $this->trainingId)); $this->data = []; } @@ -32,16 +37,28 @@ class PerceptronLimitedEpochEventBuffer implements IPerceptronIterationEventBuff 'weights' => $synaptic_weights, ]; - $lastEpoch = $this->data[0]['epoch'] ?? null; - if ($this->data && $lastEpoch !== $epoch) { // Current Epoch has changed from the last one - if ($lastEpoch == 1 || $lastEpoch % $this->epochInterval === 0) { // The last saved epoch need to be sent - $this->flush(); // Flush all data from the previous epoch - } else { - $this->data = []; // We clear the data without sending it as we are saving the next epoch data - } - - $lastEpoch = $epoch; + if ($this->activeEpoch !== $epoch) { + $this->flush(); + $this->activeEpoch = $epoch; + $this->shouldBroadcastEpoch = $epoch === 1 || $epoch % $this->epochInterval === 0; } + + if (! $this->shouldBroadcastEpoch) { + return; + } + $this->data[] = $newData; + + if ($this->payloadExceedsLimit() || count($this->data) >= config('perceptron.broadcast_iteration_size')) { + $this->flush(); + } + } + + private function payloadExceedsLimit(): bool + { + return strlen(json_encode([ + 'iterations' => $this->data, + 'trainingId' => $this->trainingId, + ], JSON_THROW_ON_ERROR)) > config('broadcasting.broadcast_max_payload_size'); } } diff --git a/public/data_sets/logic_or_gradient.csv b/public/data_sets/logic_or_gradient.csv deleted file mode 100644 index b6c0268..0000000 --- a/public/data_sets/logic_or_gradient.csv +++ /dev/null @@ -1,4 +0,0 @@ -0, 0, -1 -0, 1, 1 -1, 0, 1 -1, 1, 1 diff --git a/resources/js/components/PerceptronIterationsErrorsGraph.vue b/resources/js/components/PerceptronIterationsErrorsGraph.vue index 5dcdcfd..fc7ff47 100644 --- a/resources/js/components/PerceptronIterationsErrorsGraph.vue +++ b/resources/js/components/PerceptronIterationsErrorsGraph.vue @@ -1,7 +1,7 @@