Refactor and optimizations

This commit is contained in:
2026-09-08 19:06:02 +02:00
parent 8a50f492ac
commit 0e177f8491
11 changed files with 277 additions and 177 deletions
+4 -15
View File
@@ -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;
}
@@ -0,0 +1,29 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class RunPerceptronRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'type' => ['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'],
];
}
}
@@ -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
@@ -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
@@ -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');
}
}
@@ -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');
}
}