From 25b03fc39adde0710c9a24cb3f59e72e376d1352 Mon Sep 17 00:00:00 2001 From: Matthias Guillitte Date: Fri, 18 Sep 2026 23:08:19 +0200 Subject: [PATCH] Cancel Training --- app/Exceptions/TrainingCancelledException.php | 9 ++ app/Http/Controllers/PerceptronController.php | 36 +++++- .../ADALINEPerceptronTraining.php | 4 +- .../GradientDescentPerceptronTraining.php | 4 +- .../MonoLayerPerceptronTraining.php | 4 +- .../MultiLayerPerceptronTraining.php | 4 +- .../NetworksTraining/NetworkTraining.php | 12 ++ .../SimpleBinaryPerceptronTraining.php | 4 +- resources/js/components/PerceptronSetup.vue | 118 ++++++++++++++---- resources/js/pages/PerceptronViewer.vue | 1 + routes/web.php | 5 + 11 files changed, 168 insertions(+), 33 deletions(-) create mode 100644 app/Exceptions/TrainingCancelledException.php diff --git a/app/Exceptions/TrainingCancelledException.php b/app/Exceptions/TrainingCancelledException.php new file mode 100644 index 0000000..46a4bf2 --- /dev/null +++ b/app/Exceptions/TrainingCancelledException.php @@ -0,0 +1,9 @@ +validate([ + 'training_id' => ['required', 'string', 'max:100'], + ])['training_id']; + + Cache::put($this->cancellationKey($trainingId), true, now()->addHour()); + + return response()->noContent(); + } /** * Display the specified resource. */ @@ -220,6 +237,8 @@ class PerceptronController extends Controller $sessionId = $request->input('session_id', session()->getId()); $trainingId = $request->input('training_id'); + Cache::forget($this->cancellationKey($trainingId)); + // Zero initialization prevents hidden layers from receiving a gradient. if ($perceptronType === 'multilayer' && $weightInitMethod === 'zeros') { $synapticWeightsProvider = new RandomSynapticWeights; @@ -236,17 +255,22 @@ class PerceptronController extends Controller $datasetReader = $this->getDataSetReader($dataSet); $networkTraining = match ($perceptronType) { - 'simple' => new SimpleBinaryPerceptronTraining($datasetReader, $learningRate, $maxEpochs, $synapticWeightsProvider, $iterationEventBuffer, $sessionId, $trainingId), - 'gradientdescent' => new GradientDescentPerceptronTraining($datasetReader, $learningRate, $maxEpochs, $synapticWeightsProvider, $iterationEventBuffer, $sessionId, $trainingId, $minError), - 'adaline' => new ADALINEPerceptronTraining($datasetReader, $learningRate, $maxEpochs, $synapticWeightsProvider, $iterationEventBuffer, $sessionId, $trainingId, $minError), - 'monolayer' => new MonoLayerPerceptronTraining($datasetReader, $learningRate, $maxEpochs, $synapticWeightsProvider, $iterationEventBuffer, $sessionId, $trainingId, $minError), - 'multilayer' => new MultiLayerPerceptronTraining($datasetReader, $learningRate, $maxEpochs, $hiddenLayers, $hiddenLayersNeurons, $synapticWeightsProvider, $iterationEventBuffer, $sessionId, $trainingId, $minError), + 'simple' => new SimpleBinaryPerceptronTraining($datasetReader, $learningRate, $maxEpochs, $synapticWeightsProvider, $iterationEventBuffer, $sessionId, $trainingId, fn (): bool => connection_aborted() || Cache::has($this->cancellationKey($trainingId))), + 'gradientdescent' => new GradientDescentPerceptronTraining($datasetReader, $learningRate, $maxEpochs, $synapticWeightsProvider, $iterationEventBuffer, $sessionId, $trainingId, $minError, fn (): bool => connection_aborted() || Cache::has($this->cancellationKey($trainingId))), + 'adaline' => new ADALINEPerceptronTraining($datasetReader, $learningRate, $maxEpochs, $synapticWeightsProvider, $iterationEventBuffer, $sessionId, $trainingId, $minError, fn (): bool => connection_aborted() || Cache::has($this->cancellationKey($trainingId))), + 'monolayer' => new MonoLayerPerceptronTraining($datasetReader, $learningRate, $maxEpochs, $synapticWeightsProvider, $iterationEventBuffer, $sessionId, $trainingId, $minError, fn (): bool => connection_aborted() || Cache::has($this->cancellationKey($trainingId))), + 'multilayer' => new MultiLayerPerceptronTraining($datasetReader, $learningRate, $maxEpochs, $hiddenLayers, $hiddenLayersNeurons, $synapticWeightsProvider, $iterationEventBuffer, $sessionId, $trainingId, $minError, fn (): bool => connection_aborted() || Cache::has($this->cancellationKey($trainingId))), default => null, }; event(new PerceptronInitialization($datasetReader->lines, $networkTraining->activationFunction, $sessionId, $trainingId)); - $networkTraining->start(); + try { + $networkTraining->start(); + } catch (TrainingCancelledException) { + $networkTraining->cancel(); + Cache::forget($this->cancellationKey($trainingId)); + } return back()->with('success', [ 'message' => 'Training completed', diff --git a/app/Models/NetworksTraining/ADALINEPerceptronTraining.php b/app/Models/NetworksTraining/ADALINEPerceptronTraining.php index 6999087..17989cb 100644 --- a/app/Models/NetworksTraining/ADALINEPerceptronTraining.php +++ b/app/Models/NetworksTraining/ADALINEPerceptronTraining.php @@ -8,6 +8,7 @@ use App\Models\Perceptrons\Perceptron; use App\Services\DatasetReader\IDataSetReader; use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer; use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider; +use Closure; class ADALINEPerceptronTraining extends NetworkTraining { @@ -26,8 +27,9 @@ class ADALINEPerceptronTraining extends NetworkTraining string $sessionId, string $trainingId, private float $minError, + ?Closure $isCancelled = null, ) { - parent::__construct($datasetReader, $maxEpochs, $iterationEventBuffer, $sessionId, $trainingId); + parent::__construct($datasetReader, $maxEpochs, $iterationEventBuffer, $sessionId, $trainingId, $isCancelled); $this->perceptron = new GradientDescentPerceptron($synapticWeightsProvider->generate($datasetReader->getInputSize())); } diff --git a/app/Models/NetworksTraining/GradientDescentPerceptronTraining.php b/app/Models/NetworksTraining/GradientDescentPerceptronTraining.php index a40dd69..c421a79 100644 --- a/app/Models/NetworksTraining/GradientDescentPerceptronTraining.php +++ b/app/Models/NetworksTraining/GradientDescentPerceptronTraining.php @@ -8,6 +8,7 @@ use App\Models\Perceptrons\Perceptron; use App\Services\DatasetReader\IDataSetReader; use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer; use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider; +use Closure; class GradientDescentPerceptronTraining extends NetworkTraining { @@ -26,8 +27,9 @@ class GradientDescentPerceptronTraining extends NetworkTraining string $sessionId, string $trainingId, private float $minError, + ?Closure $isCancelled = null, ) { - parent::__construct($datasetReader, $maxEpochs, $iterationEventBuffer, $sessionId, $trainingId); + parent::__construct($datasetReader, $maxEpochs, $iterationEventBuffer, $sessionId, $trainingId, $isCancelled); $this->perceptron = new GradientDescentPerceptron($synapticWeightsProvider->generate($datasetReader->getInputSize())); } diff --git a/app/Models/NetworksTraining/MonoLayerPerceptronTraining.php b/app/Models/NetworksTraining/MonoLayerPerceptronTraining.php index 49b34a0..f485cbf 100644 --- a/app/Models/NetworksTraining/MonoLayerPerceptronTraining.php +++ b/app/Models/NetworksTraining/MonoLayerPerceptronTraining.php @@ -10,6 +10,7 @@ use App\Services\DatasetReader\IDataSetReader; use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer; use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider; use App\Services\SynapticWeightsProvider\SimpleNetworkWeightsProvider; +use Closure; use Illuminate\Support\Arr; class MonoLayerPerceptronTraining extends NetworkTraining @@ -35,8 +36,9 @@ class MonoLayerPerceptronTraining extends NetworkTraining string $sessionId, string $trainingId, private float $minError, + ?Closure $isCancelled = null, ) { - parent::__construct($datasetReader, $maxEpochs, $iterationEventBuffer, $sessionId, $trainingId); + parent::__construct($datasetReader, $maxEpochs, $iterationEventBuffer, $sessionId, $trainingId, $isCancelled); $this->isRegression = $datasetReader->getInputSize() === 1; $networkWeightsProvider = new SimpleNetworkWeightsProvider($synapticWeightsProvider); $this->network = new NetworkPerceptron( diff --git a/app/Models/NetworksTraining/MultiLayerPerceptronTraining.php b/app/Models/NetworksTraining/MultiLayerPerceptronTraining.php index f53be66..24eb5b2 100644 --- a/app/Models/NetworksTraining/MultiLayerPerceptronTraining.php +++ b/app/Models/NetworksTraining/MultiLayerPerceptronTraining.php @@ -11,6 +11,7 @@ use App\Services\DatasetReader\IDataSetReader; use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer; use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider; use App\Services\SynapticWeightsProvider\SimpleNetworkWeightsProvider; +use Closure; use Illuminate\Support\Arr; class MultiLayerPerceptronTraining extends NetworkTraining @@ -37,8 +38,9 @@ class MultiLayerPerceptronTraining extends NetworkTraining string $sessionId, string $trainingId, private float $minError, + ?Closure $isCancelled = null, ) { - parent::__construct($datasetReader, $maxEpochs, $iterationEventBuffer, $sessionId, $trainingId); + parent::__construct($datasetReader, $maxEpochs, $iterationEventBuffer, $sessionId, $trainingId, $isCancelled); $this->labels = $datasetReader->getLabels(); $this->isRegression = $datasetReader->getOutputSize() === 1 || ($datasetReader->getOutputSize() > 2 diff --git a/app/Models/NetworksTraining/NetworkTraining.php b/app/Models/NetworksTraining/NetworkTraining.php index 27a8403..b23d194 100644 --- a/app/Models/NetworksTraining/NetworkTraining.php +++ b/app/Models/NetworksTraining/NetworkTraining.php @@ -3,9 +3,11 @@ namespace App\Models\NetworksTraining; use App\Events\PerceptronTrainingEnded; +use App\Exceptions\TrainingCancelledException; use App\Models\ActivationsFunctions; use App\Services\DatasetReader\IDataSetReader; use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer; +use Closure; abstract class NetworkTraining { @@ -24,6 +26,7 @@ abstract class NetworkTraining protected IPerceptronIterationEventBuffer $iterationEventBuffer, protected string $sessionId, protected string $trainingId, + protected ?Closure $isCancelled = null, ) {} abstract public function start(): void; @@ -50,9 +53,18 @@ abstract class NetworkTraining protected function addIterationToBuffer(float $error, array $synapticWeights) { + if ($this->isCancelled !== null && ($this->isCancelled)()) { + throw new TrainingCancelledException; + } + $this->iterationEventBuffer->addIteration($this->epoch, $this->datasetReader->getLastReadLineIndex(), $error, $synapticWeights); } + public function cancel(): void + { + $this->broadcastTrainingEnded('Entraînement annulé'); + } + public function getEpoch(): int { return $this->epoch; diff --git a/app/Models/NetworksTraining/SimpleBinaryPerceptronTraining.php b/app/Models/NetworksTraining/SimpleBinaryPerceptronTraining.php index 4118c59..41ee400 100644 --- a/app/Models/NetworksTraining/SimpleBinaryPerceptronTraining.php +++ b/app/Models/NetworksTraining/SimpleBinaryPerceptronTraining.php @@ -9,6 +9,7 @@ use App\Models\Perceptrons\SimpleBinaryPerceptron; use App\Services\DatasetReader\IDataSetReader; use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer; use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider; +use Closure; class SimpleBinaryPerceptronTraining extends NetworkTraining { @@ -28,8 +29,9 @@ class SimpleBinaryPerceptronTraining extends NetworkTraining IPerceptronIterationEventBuffer $iterationEventBuffer, string $sessionId, string $trainingId, + ?Closure $isCancelled = null, ) { - parent::__construct($datasetReader, $maxEpochs, $iterationEventBuffer, $sessionId, $trainingId); + parent::__construct($datasetReader, $maxEpochs, $iterationEventBuffer, $sessionId, $trainingId, $isCancelled); $this->perceptron = new SimpleBinaryPerceptron($synapticWeightsProvider->generate($datasetReader->getInputSize())); } diff --git a/resources/js/components/PerceptronSetup.vue b/resources/js/components/PerceptronSetup.vue index 4a28ce4..5a6c48a 100644 --- a/resources/js/components/PerceptronSetup.vue +++ b/resources/js/components/PerceptronSetup.vue @@ -1,5 +1,5 @@