diff --git a/app/Events/PerceptronInitialization.php b/app/Events/PerceptronInitialization.php index 4a957ec..858d4cc 100644 --- a/app/Events/PerceptronInitialization.php +++ b/app/Events/PerceptronInitialization.php @@ -5,11 +5,11 @@ namespace App\Events; use App\Models\ActivationsFunctions; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; -use Illuminate\Contracts\Broadcasting\ShouldBroadcast; +use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; -class PerceptronInitialization implements ShouldBroadcast +class PerceptronInitialization implements ShouldBroadcastNow { use Dispatchable, InteractsWithSockets, SerializesModels; diff --git a/app/Events/PerceptronTrainingEnded.php b/app/Events/PerceptronTrainingEnded.php index 36ee705..f4092d7 100644 --- a/app/Events/PerceptronTrainingEnded.php +++ b/app/Events/PerceptronTrainingEnded.php @@ -4,11 +4,11 @@ namespace App\Events; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; -use Illuminate\Contracts\Broadcasting\ShouldBroadcast; +use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; -class PerceptronTrainingEnded implements ShouldBroadcast +class PerceptronTrainingEnded implements ShouldBroadcastNow { use Dispatchable, InteractsWithSockets, SerializesModels; diff --git a/app/Events/PerceptronTrainingIteration.php b/app/Events/PerceptronTrainingIteration.php index d9e6605..3834352 100644 --- a/app/Events/PerceptronTrainingIteration.php +++ b/app/Events/PerceptronTrainingIteration.php @@ -4,12 +4,12 @@ namespace App\Events; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; -use Illuminate\Contracts\Broadcasting\ShouldBroadcast; +use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow; use Illuminate\Foundation\Events\Dispatchable; -use Illuminate\Support\Arr; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Arr; -class PerceptronTrainingIteration implements ShouldBroadcast +class PerceptronTrainingIteration implements ShouldBroadcastNow { use Dispatchable, InteractsWithSockets, SerializesModels; @@ -39,19 +39,20 @@ class PerceptronTrainingIteration implements ShouldBroadcast public function broadcastWith(): array { - $weights = collect($this->iterations) + $iterations = self::normalizeForJson($this->iterations); + $weights = collect($iterations) ->pluck('weights') ->first(fn (array $weights): bool => $weights !== []); $shouldBroadcastAllWeights = $weights !== null && count(Arr::flatten($weights)) <= config('perceptron.max_displayed_weights'); - $lastIterationIndex = count($this->iterations) - 1; + $lastIterationIndex = count($iterations) - 1; $iterations = array_map( fn (array $iteration, int $index): array => $shouldBroadcastAllWeights || $index === $lastIterationIndex ? $iteration : [...$iteration, 'weights' => []], - $this->iterations, - array_keys($this->iterations), + $iterations, + array_keys($iterations), ); return [ @@ -59,4 +60,20 @@ class PerceptronTrainingIteration implements ShouldBroadcast 'trainingId' => $this->trainingId, ]; } + + public static function normalizeForJson(mixed $value): mixed + { + if (is_float($value) && ! is_finite($value)) { + return null; + } + + if (! is_array($value)) { + return $value; + } + + return array_map( + fn (mixed $item): mixed => self::normalizeForJson($item), + $value, + ); + } } diff --git a/app/Http/Controllers/PerceptronController.php b/app/Http/Controllers/PerceptronController.php index 8c74119..f6c9906 100644 --- a/app/Http/Controllers/PerceptronController.php +++ b/app/Http/Controllers/PerceptronController.php @@ -18,7 +18,6 @@ use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider; use App\Services\SynapticWeightsProvider\RandomSynapticWeights; use App\Services\SynapticWeightsProvider\ZeroSynapticWeights; use Illuminate\Http\Request; -use Illuminate\Support\Facades\DB; class PerceptronController extends Controller { @@ -134,6 +133,11 @@ class PerceptronController extends Controller break; case 'table_2_11': $dataset['defaultMinError'] = 0.02; + switch ($perceptronType) { + case 'monolayer': + $dataset['defaultLearningRate'] = 0.0015; + break; + } break; case 'table_4_12': switch ($perceptronType) { @@ -145,11 +149,13 @@ class PerceptronController extends Controller } break; case 'table_4_17': + $dataset['defaultMinError'] = 0.055; switch ($perceptronType) { case 'multilayer': - $dataset['defaultLearningRate'] = 0.5; - $dataset['defaultMinError'] = 0.08; + $dataset['defaultLearningRate'] = 0.3; $dataset['defaultMaxIterations'] = 400; + $dataset['defaultHiddenLayers'] = 2; + $dataset['defaultHiddenLayersNeurons'] = 2; break; } break; @@ -183,9 +189,6 @@ class PerceptronController extends Controller $sessionId = $request->input('session_id', session()->getId()); $trainingId = $request->input('training_id'); - // Remove the jobs for the sessionId - 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; diff --git a/app/Models/NetworksTraining/MonoLayerPerceptronTraining.php b/app/Models/NetworksTraining/MonoLayerPerceptronTraining.php index 524157c..c88722d 100644 --- a/app/Models/NetworksTraining/MonoLayerPerceptronTraining.php +++ b/app/Models/NetworksTraining/MonoLayerPerceptronTraining.php @@ -20,6 +20,7 @@ class MonoLayerPerceptronTraining extends NetworkTraining private array $labels; public ActivationsFunctions $activationFunction = ActivationsFunctions::LINEAR; + public ?ActivationsFunctions $presentationLayerActivationFunction = ActivationsFunctions::STEP; private float $epochError; @@ -62,7 +63,7 @@ class MonoLayerPerceptronTraining extends NetworkTraining while ($nextRow = $this->datasetReader->getNextLine()) { $inputsForCurrentEpoch[] = $nextRow; $inputs = array_slice($nextRow, 0, -1); - $correctOutput = (int) end($nextRow); + $correctOutput = (float) end($nextRow); $iterationError = $this->iterationFunction($inputs, $correctOutput); @@ -108,7 +109,7 @@ class MonoLayerPerceptronTraining extends NetworkTraining return $condition; } - private function iterationFunction(array $inputs, int $correctOutput): array + private function iterationFunction(array $inputs, float $correctOutput): array { $outputs = $this->network->test($inputs); $desiredOutput = $this->getDesiredOutputFromCorrectOutput($correctOutput); @@ -137,10 +138,10 @@ class MonoLayerPerceptronTraining extends NetworkTraining return [$updatedWeights]; } - private function getDesiredOutputFromCorrectOutput(int $correctOutput): array + private function getDesiredOutputFromCorrectOutput(float $correctOutput): array { $desiredOutput = array_fill(0, count($this->labels), -1); - $labelIndex = Arr::first(array_keys($this->labels), fn($key) => $this->labels[$key] == $correctOutput); + $labelIndex = Arr::first(array_keys($this->labels), fn ($key) => $this->labels[$key] == $correctOutput); if ($labelIndex !== null) { $desiredOutput[$labelIndex] = 1; } diff --git a/app/Models/Perceptrons/InputNeuron.php b/app/Models/Perceptrons/InputNeuron.php index 7de86a6..6b429e0 100644 --- a/app/Models/Perceptrons/InputNeuron.php +++ b/app/Models/Perceptrons/InputNeuron.php @@ -4,6 +4,8 @@ namespace App\Models\Perceptrons; class InputNeuron extends Perceptron { + private float $input = 0.0; + public function __construct( ) { parent::__construct([]); diff --git a/app/Services/IterationEventBuffer/PerceptronIterationEventBuffer.php b/app/Services/IterationEventBuffer/PerceptronIterationEventBuffer.php index dc68b56..b14d3d0 100644 --- a/app/Services/IterationEventBuffer/PerceptronIterationEventBuffer.php +++ b/app/Services/IterationEventBuffer/PerceptronIterationEventBuffer.php @@ -8,6 +8,8 @@ class PerceptronIterationEventBuffer implements IPerceptronIterationEventBuffer { private array $data = []; + private ?float $lastBroadcastAt = null; + public function __construct( private string $sessionId, private string $trainingId, @@ -19,7 +21,9 @@ class PerceptronIterationEventBuffer implements IPerceptronIterationEventBuffer return; } + $this->waitForBroadcastInterval(); event(new PerceptronTrainingIteration($this->data, $this->sessionId, $this->trainingId)); + $this->lastBroadcastAt = microtime(true); $this->data = []; } @@ -48,8 +52,22 @@ class PerceptronIterationEventBuffer implements IPerceptronIterationEventBuffer private function payloadExceedsLimit(): bool { return strlen(json_encode([ - 'iterations' => $this->data, + 'iterations' => PerceptronTrainingIteration::normalizeForJson($this->data), 'trainingId' => $this->trainingId, ], JSON_THROW_ON_ERROR)) > config('broadcasting.broadcast_max_payload_size'); } + + private function waitForBroadcastInterval(): void + { + if ($this->lastBroadcastAt === null) { + return; + } + + $minimumInterval = config('perceptron.broadcast_minimum_interval_ms') / 1000; + $remainingInterval = $minimumInterval - (microtime(true) - $this->lastBroadcastAt); + + if ($remainingInterval > 0) { + usleep((int) ceil($remainingInterval * 1_000_000)); + } + } } diff --git a/app/Services/IterationEventBuffer/PerceptronLimitedEpochEventBuffer.php b/app/Services/IterationEventBuffer/PerceptronLimitedEpochEventBuffer.php index a7cf121..007e354 100644 --- a/app/Services/IterationEventBuffer/PerceptronLimitedEpochEventBuffer.php +++ b/app/Services/IterationEventBuffer/PerceptronLimitedEpochEventBuffer.php @@ -12,6 +12,8 @@ class PerceptronLimitedEpochEventBuffer implements IPerceptronIterationEventBuff private bool $shouldBroadcastEpoch = false; + private ?float $lastBroadcastAt = null; + public function __construct( private string $sessionId, private string $trainingId, @@ -24,7 +26,9 @@ class PerceptronLimitedEpochEventBuffer implements IPerceptronIterationEventBuff return; } + $this->waitForBroadcastInterval(); event(new PerceptronTrainingIteration($this->data, $this->sessionId, $this->trainingId)); + $this->lastBroadcastAt = microtime(true); $this->data = []; } @@ -57,8 +61,22 @@ class PerceptronLimitedEpochEventBuffer implements IPerceptronIterationEventBuff private function payloadExceedsLimit(): bool { return strlen(json_encode([ - 'iterations' => $this->data, + 'iterations' => PerceptronTrainingIteration::normalizeForJson($this->data), 'trainingId' => $this->trainingId, ], JSON_THROW_ON_ERROR)) > config('broadcasting.broadcast_max_payload_size'); } + + private function waitForBroadcastInterval(): void + { + if ($this->lastBroadcastAt === null) { + return; + } + + $minimumInterval = config('perceptron.broadcast_minimum_interval_ms') / 1000; + $remainingInterval = $minimumInterval - (microtime(true) - $this->lastBroadcastAt); + + if ($remainingInterval > 0) { + usleep((int) ceil($remainingInterval * 1_000_000)); + } + } } diff --git a/config/perceptron.php b/config/perceptron.php index 13812fb..0f4abc1 100644 --- a/config/perceptron.php +++ b/config/perceptron.php @@ -10,7 +10,7 @@ return [ 'limited_broadcast_iterations' => 100, /** - * How much broadcasts is sent when in limmited broadcast mode + * How much broadcasts is sent when in limited broadcast mode */ 'limited_broadcast_number' => 100, @@ -19,6 +19,11 @@ return [ */ 'broadcast_iteration_size' => 75, + /** + * Minimum time between training progress broadcasts, in milliseconds. + */ + 'broadcast_minimum_interval_ms' => 150, + /** * Maximum number of weights for which all iteration weights are broadcast * and displayed in the iteration table. diff --git a/resources/js/components/IterationTable.vue b/resources/js/components/IterationTable.vue index 0fbab5e..b03cea3 100644 --- a/resources/js/components/IterationTable.vue +++ b/resources/js/components/IterationTable.vue @@ -1,5 +1,6 @@