Compare commits

...

12 Commits

Author SHA1 Message Date
Ninluc 7c223e0ba8 Increase max request time
linter / quality (push) Successful in 5m1s
tests / ci (8.3) (push) Successful in 3m47s
2026-09-19 15:28:52 +02:00
Ninluc 6d8e3438f0 Track some events
linter / quality (push) Has been cancelled
tests / ci (8.3) (push) Successful in 3m57s
2026-09-19 10:43:05 +02:00
Ninluc ae676343a5 Fix send cancel request
linter / quality (push) Successful in 5m1s
tests / ci (8.3) (push) Successful in 4m28s
2026-09-18 23:58:51 +02:00
Ninluc 9389aef632 Fix cancel route
linter / quality (push) Has been cancelled
tests / ci (8.3) (push) Successful in 3m49s
2026-09-18 23:41:31 +02:00
Ninluc fa6c427d76 Fix cancel ?
linter / quality (push) Failing after 1m3s
tests / ci (8.3) (push) Successful in 3m48s
2026-09-18 23:24:13 +02:00
Ninluc 25b03fc39a Cancel Training
linter / quality (push) Failing after 1m11s
tests / ci (8.3) (push) Successful in 3m55s
2026-09-18 23:08:19 +02:00
Ninluc d80bfe3cdd Updated Interface Image and added excalidraws
linter / quality (push) Successful in 6m26s
tests / ci (8.3) (push) Successful in 5m11s
2026-09-18 14:19:17 +02:00
Ninluc 2332e805e8 Logarithmic error scale
linter / quality (push) Successful in 5m12s
tests / ci (8.3) (push) Successful in 4m29s
2026-09-18 14:12:23 +02:00
Ninluc 4610334f72 Do not exclude get parameters
linter / quality (push) Successful in 4m54s
tests / ci (8.3) (push) Successful in 4m2s
2026-09-18 09:03:46 +02:00
Ninluc 9ae3a6942b Default perrceptron to simple
linter / quality (push) Successful in 4m36s
tests / ci (8.3) (push) Successful in 3m48s
2026-09-17 21:55:58 +02:00
Ninluc 3a19691591 Remove debug config value
linter / quality (push) Successful in 4m11s
tests / ci (8.3) (push) Successful in 3m39s
2026-09-17 21:46:49 +02:00
Ninluc 2d7c1873bb Fix Bar chart
linter / quality (push) Successful in 5m36s
tests / ci (8.3) (push) Successful in 4m42s
2026-09-17 21:24:42 +02:00
22 changed files with 9656 additions and 248 deletions
+2
View File
@@ -24,6 +24,8 @@ RUN npm run build
# Final PHP image # Final PHP image
FROM dunglas/frankenphp:1.12.2-php8.3-alpine AS final FROM dunglas/frankenphp:1.12.2-php8.3-alpine AS final
COPY docker/php.ini-production "$PHP_INI_DIR/php.ini"
# Install system dependencies # Install system dependencies
RUN apk add --no-cache \ RUN apk add --no-cache \
bash \ bash \
@@ -0,0 +1,9 @@
<?php
namespace App\Exceptions;
use RuntimeException;
class TrainingCancelledException extends RuntimeException
{
}
+30 -6
View File
@@ -3,6 +3,7 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Events\PerceptronInitialization; use App\Events\PerceptronInitialization;
use App\Exceptions\TrainingCancelledException;
use App\Http\Requests\RunPerceptronRequest; use App\Http\Requests\RunPerceptronRequest;
use App\Models\NetworksTraining\ADALINEPerceptronTraining; use App\Models\NetworksTraining\ADALINEPerceptronTraining;
use App\Models\NetworksTraining\GradientDescentPerceptronTraining; use App\Models\NetworksTraining\GradientDescentPerceptronTraining;
@@ -18,15 +19,31 @@ use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider;
use App\Services\SynapticWeightsProvider\RandomSynapticWeights; use App\Services\SynapticWeightsProvider\RandomSynapticWeights;
use App\Services\SynapticWeightsProvider\ZeroSynapticWeights; use App\Services\SynapticWeightsProvider\ZeroSynapticWeights;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class PerceptronController extends Controller class PerceptronController extends Controller
{ {
private function cancellationKey(string $trainingId): string
{
return "perceptron-training-cancelled:{$trainingId}";
}
public function cancel(Request $request)
{
$trainingId = $request->validate([
'training_id' => ['required', 'string', 'max:100'],
])['training_id'];
Cache::put($this->cancellationKey($trainingId), true, now()->addHour());
return response()->noContent();
}
/** /**
* Display the specified resource. * Display the specified resource.
*/ */
public function index(Request $request) public function index(Request $request)
{ {
$perceptronType = $request->query('type'); $perceptronType = $request->query('type', 'simple'); ;
$learningRate = 0.01; $learningRate = 0.01;
$maxIterations = 200; $maxIterations = 200;
@@ -220,6 +237,8 @@ class PerceptronController extends Controller
$sessionId = $request->input('session_id', session()->getId()); $sessionId = $request->input('session_id', session()->getId());
$trainingId = $request->input('training_id'); $trainingId = $request->input('training_id');
Cache::forget($this->cancellationKey($trainingId));
// Zero initialization prevents hidden layers from receiving a gradient. // Zero initialization prevents hidden layers from receiving a gradient.
if ($perceptronType === 'multilayer' && $weightInitMethod === 'zeros') { if ($perceptronType === 'multilayer' && $weightInitMethod === 'zeros') {
$synapticWeightsProvider = new RandomSynapticWeights; $synapticWeightsProvider = new RandomSynapticWeights;
@@ -236,17 +255,22 @@ class PerceptronController extends Controller
$datasetReader = $this->getDataSetReader($dataSet); $datasetReader = $this->getDataSetReader($dataSet);
$networkTraining = match ($perceptronType) { $networkTraining = match ($perceptronType) {
'simple' => new SimpleBinaryPerceptronTraining($datasetReader, $learningRate, $maxEpochs, $synapticWeightsProvider, $iterationEventBuffer, $sessionId, $trainingId), '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), '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), '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), '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), 'multilayer' => new MultiLayerPerceptronTraining($datasetReader, $learningRate, $maxEpochs, $hiddenLayers, $hiddenLayersNeurons, $synapticWeightsProvider, $iterationEventBuffer, $sessionId, $trainingId, $minError, fn (): bool => connection_aborted() || Cache::has($this->cancellationKey($trainingId))),
default => null, default => null,
}; };
event(new PerceptronInitialization($datasetReader->lines, $networkTraining->activationFunction, $sessionId, $trainingId)); event(new PerceptronInitialization($datasetReader->lines, $networkTraining->activationFunction, $sessionId, $trainingId));
try {
$networkTraining->start(); $networkTraining->start();
} catch (TrainingCancelledException) {
$networkTraining->cancel();
Cache::forget($this->cancellationKey($trainingId));
}
return back()->with('success', [ return back()->with('success', [
'message' => 'Training completed', 'message' => 'Training completed',
@@ -8,6 +8,7 @@ use App\Models\Perceptrons\Perceptron;
use App\Services\DatasetReader\IDataSetReader; use App\Services\DatasetReader\IDataSetReader;
use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer; use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer;
use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider; use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider;
use Closure;
class ADALINEPerceptronTraining extends NetworkTraining class ADALINEPerceptronTraining extends NetworkTraining
{ {
@@ -26,8 +27,9 @@ class ADALINEPerceptronTraining extends NetworkTraining
string $sessionId, string $sessionId,
string $trainingId, string $trainingId,
private float $minError, 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())); $this->perceptron = new GradientDescentPerceptron($synapticWeightsProvider->generate($datasetReader->getInputSize()));
} }
@@ -8,6 +8,7 @@ use App\Models\Perceptrons\Perceptron;
use App\Services\DatasetReader\IDataSetReader; use App\Services\DatasetReader\IDataSetReader;
use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer; use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer;
use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider; use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider;
use Closure;
class GradientDescentPerceptronTraining extends NetworkTraining class GradientDescentPerceptronTraining extends NetworkTraining
{ {
@@ -26,8 +27,9 @@ class GradientDescentPerceptronTraining extends NetworkTraining
string $sessionId, string $sessionId,
string $trainingId, string $trainingId,
private float $minError, 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())); $this->perceptron = new GradientDescentPerceptron($synapticWeightsProvider->generate($datasetReader->getInputSize()));
} }
@@ -10,6 +10,7 @@ use App\Services\DatasetReader\IDataSetReader;
use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer; use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer;
use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider; use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider;
use App\Services\SynapticWeightsProvider\SimpleNetworkWeightsProvider; use App\Services\SynapticWeightsProvider\SimpleNetworkWeightsProvider;
use Closure;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
class MonoLayerPerceptronTraining extends NetworkTraining class MonoLayerPerceptronTraining extends NetworkTraining
@@ -35,8 +36,9 @@ class MonoLayerPerceptronTraining extends NetworkTraining
string $sessionId, string $sessionId,
string $trainingId, string $trainingId,
private float $minError, 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; $this->isRegression = $datasetReader->getInputSize() === 1;
$networkWeightsProvider = new SimpleNetworkWeightsProvider($synapticWeightsProvider); $networkWeightsProvider = new SimpleNetworkWeightsProvider($synapticWeightsProvider);
$this->network = new NetworkPerceptron( $this->network = new NetworkPerceptron(
@@ -11,6 +11,7 @@ use App\Services\DatasetReader\IDataSetReader;
use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer; use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer;
use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider; use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider;
use App\Services\SynapticWeightsProvider\SimpleNetworkWeightsProvider; use App\Services\SynapticWeightsProvider\SimpleNetworkWeightsProvider;
use Closure;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
class MultiLayerPerceptronTraining extends NetworkTraining class MultiLayerPerceptronTraining extends NetworkTraining
@@ -37,8 +38,9 @@ class MultiLayerPerceptronTraining extends NetworkTraining
string $sessionId, string $sessionId,
string $trainingId, string $trainingId,
private float $minError, 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->labels = $datasetReader->getLabels();
$this->isRegression = $datasetReader->getOutputSize() === 1 $this->isRegression = $datasetReader->getOutputSize() === 1
|| ($datasetReader->getOutputSize() > 2 || ($datasetReader->getOutputSize() > 2
@@ -3,9 +3,11 @@
namespace App\Models\NetworksTraining; namespace App\Models\NetworksTraining;
use App\Events\PerceptronTrainingEnded; use App\Events\PerceptronTrainingEnded;
use App\Exceptions\TrainingCancelledException;
use App\Models\ActivationsFunctions; use App\Models\ActivationsFunctions;
use App\Services\DatasetReader\IDataSetReader; use App\Services\DatasetReader\IDataSetReader;
use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer; use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer;
use Closure;
abstract class NetworkTraining abstract class NetworkTraining
{ {
@@ -24,6 +26,7 @@ abstract class NetworkTraining
protected IPerceptronIterationEventBuffer $iterationEventBuffer, protected IPerceptronIterationEventBuffer $iterationEventBuffer,
protected string $sessionId, protected string $sessionId,
protected string $trainingId, protected string $trainingId,
protected ?Closure $isCancelled = null,
) {} ) {}
abstract public function start(): void; abstract public function start(): void;
@@ -50,9 +53,18 @@ abstract class NetworkTraining
protected function addIterationToBuffer(float $error, array $synapticWeights) 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); $this->iterationEventBuffer->addIteration($this->epoch, $this->datasetReader->getLastReadLineIndex(), $error, $synapticWeights);
} }
public function cancel(): void
{
$this->broadcastTrainingEnded('Entraînement annulé');
}
public function getEpoch(): int public function getEpoch(): int
{ {
return $this->epoch; return $this->epoch;
@@ -9,6 +9,7 @@ use App\Models\Perceptrons\SimpleBinaryPerceptron;
use App\Services\DatasetReader\IDataSetReader; use App\Services\DatasetReader\IDataSetReader;
use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer; use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer;
use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider; use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider;
use Closure;
class SimpleBinaryPerceptronTraining extends NetworkTraining class SimpleBinaryPerceptronTraining extends NetworkTraining
{ {
@@ -28,8 +29,9 @@ class SimpleBinaryPerceptronTraining extends NetworkTraining
IPerceptronIterationEventBuffer $iterationEventBuffer, IPerceptronIterationEventBuffer $iterationEventBuffer,
string $sessionId, string $sessionId,
string $trainingId, 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())); $this->perceptron = new SimpleBinaryPerceptron($synapticWeightsProvider->generate($datasetReader->getInputSize()));
} }
+1 -1
View File
@@ -22,7 +22,7 @@ return [
/** /**
* Minimum time between training progress broadcasts, in milliseconds. * Minimum time between training progress broadcasts, in milliseconds.
*/ */
'broadcast_minimum_interval_ms' => 0, 'broadcast_minimum_interval_ms' => 100,
/** /**
* Maximum number of weights for which all iteration weights are broadcast * Maximum number of weights for which all iteration weights are broadcast
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -59,7 +59,7 @@ Reprenons le dataset oblique afin de pouvoir comparer les résultats, et diminuo
Puis cliquez sur "Lancer" tout en laissant les autres paramètres intacts. Puis cliquez sur "Lancer" tout en laissant les autres paramètres intacts.
On peut voir en dessous du tableau que l'entraînement s'est arrêté car le nombre maximal d'époques a été atteint. On pourrait l'augmenter (max 5000 pour mon petit serveur), mais ce serait du temps perdu. La solution du réseau est suffisante et il n'y aurait pas beaucoup de gain, même pour le triple d'itérations maximales en plus. Pour le prouver, cliquez sur le bouton `Afficher uniquement l'erreur quadratique moyenne`, la ligne que l'on peut voir ressemble fortement à la fonction logarithme $y = log(x^{-1})$. La progression de la descente du gradient est donc logarithmique ; les plus gros changements se font dans les premières époques. On peut voir en dessous du tableau que l'entraînement s'est arrêté car le nombre maximal d'époques a été atteint. On pourrait l'augmenter (max 5000 pour mon petit serveur), mais ce serait du temps perdu. La solution du réseau est suffisante et il n'y aurait pas beaucoup de gain, même pour le triple d'itérations maximales en plus. Pour le prouver, cliquez sur le bouton `Afficher uniquement l'erreur quadratique moyenne`, la ligne que l'on peut voir ressemble fortement à la fonction logarithme $y = log(x^{-1})$ (attention à l'échelle qui est elle-même logarithmique). La progression de la descente du gradient est donc logarithmique ; les plus gros changements se font dans les premières époques.
</div> </div>
File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 KiB

After

Width:  |  Height:  |  Size: 205 KiB

File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1
View File
@@ -33,6 +33,7 @@ createInertiaApp({
'data-auto-track': 'true', 'data-auto-track': 'true',
'data-performance': 'true', 'data-performance': 'true',
'data-domains': 'perceptron.matthiasg.dev,matthiasg.dev,www.matthiasg.dev', 'data-domains': 'perceptron.matthiasg.dev,matthiasg.dev,www.matthiasg.dev',
'data-exclude-search': 'false',
} }
}), }),
) )
@@ -113,8 +113,8 @@ const datasets = computed<ErrorDataset[]>(() => {
min: 0, min: 0,
}, },
y: { y: {
type: !epochErrorOnly ? 'linear' : 'logarithmic',
stacked: true, stacked: true,
beginAtZero: !props.isRegression,
grid: { grid: {
color: function (context) { color: function (context) {
if (context.tick.value == 0) { if (context.tick.value == 0) {
+112 -27
View File
@@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useForm } from '@inertiajs/vue3'; import { useForm } from '@inertiajs/vue3';
import { trackUmamiEvent } from '@jaseeey/vue-umami-plugin';
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { import {
Form, Form,
@@ -12,6 +13,7 @@ import {
NativeSelect, NativeSelect,
NativeSelectOption, NativeSelectOption,
} from '@/components/ui/native-select'; } from '@/components/ui/native-select';
import { cancel } from '@/routes/perceptron';
import type { import type {
Dataset, Dataset,
InitializationMethod, InitializationMethod,
@@ -23,8 +25,9 @@ import Card from './ui/card/Card.vue';
import CardContent from './ui/card/CardContent.vue'; import CardContent from './ui/card/CardContent.vue';
import CardHeader from './ui/card/CardHeader.vue'; import CardHeader from './ui/card/CardHeader.vue';
import CardTitle from './ui/card/CardTitle.vue'; import CardTitle from './ui/card/CardTitle.vue';
import Input from './ui/input/Input.vue';
import FormError from './ui/form/FormError.vue'; import FormError from './ui/form/FormError.vue';
import Input from './ui/input/Input.vue';
import Spinner from './ui/spinner/Spinner.vue';
const props = defineProps<{ const props = defineProps<{
type: PerceptronType; type: PerceptronType;
@@ -98,43 +101,69 @@ watch(maxIterations, (value) => {
watch(selectedDatasetCopy, (newvalue) => { watch(selectedDatasetCopy, (newvalue) => {
form.clearErrors('dataset'); form.clearErrors('dataset');
const selectedDatasetCopy = props.datasets.find( const selectedDatasetCopy =
(dataset) => dataset.label === newvalue props.datasets.find((dataset) => dataset.label === newvalue) || null;
) || null;
// LearningRate // LearningRate
learningRate.value = props.defaultLearningRate; learningRate.value = props.defaultLearningRate;
if (selectedDatasetCopy && selectedDatasetCopy.defaultLearningRate !== undefined) { if (
selectedDatasetCopy &&
selectedDatasetCopy.defaultLearningRate !== undefined
) {
learningRate.value = selectedDatasetCopy.defaultLearningRate; learningRate.value = selectedDatasetCopy.defaultLearningRate;
} }
// MinError // MinError
minError.value = props.minError; minError.value = props.minError;
if (selectedDatasetCopy && selectedDatasetCopy.defaultMinError !== undefined) { if (
selectedDatasetCopy &&
selectedDatasetCopy.defaultMinError !== undefined
) {
minError.value = selectedDatasetCopy.defaultMinError; minError.value = selectedDatasetCopy.defaultMinError;
} }
// MaxIterations // MaxIterations
maxIterations.value = props.defaultMaxIterations; maxIterations.value = props.defaultMaxIterations;
if (selectedDatasetCopy && selectedDatasetCopy.defaultMaxIterations !== undefined) { if (
selectedDatasetCopy &&
selectedDatasetCopy.defaultMaxIterations !== undefined
) {
maxIterations.value = selectedDatasetCopy.defaultMaxIterations; maxIterations.value = selectedDatasetCopy.defaultMaxIterations;
} }
// HiddenLayers // HiddenLayers
hiddenLayers.value = props.hiddenLayers; hiddenLayers.value = props.hiddenLayers;
if (selectedDatasetCopy && selectedDatasetCopy.defaultHiddenLayers !== undefined) { if (
selectedDatasetCopy &&
selectedDatasetCopy.defaultHiddenLayers !== undefined
) {
hiddenLayers.value = selectedDatasetCopy.defaultHiddenLayers; hiddenLayers.value = selectedDatasetCopy.defaultHiddenLayers;
} }
hiddenLayersNeurons.value = props.hiddenLayersNeurons; hiddenLayersNeurons.value = props.hiddenLayersNeurons;
if (selectedDatasetCopy && selectedDatasetCopy.defaultHiddenLayersNeurons !== undefined) { if (
hiddenLayersNeurons.value = selectedDatasetCopy.defaultHiddenLayersNeurons; selectedDatasetCopy &&
selectedDatasetCopy.defaultHiddenLayersNeurons !== undefined
) {
hiddenLayersNeurons.value =
selectedDatasetCopy.defaultHiddenLayersNeurons;
} }
}) });
const trainingId = ref<string>(''); const trainingId = ref<string>('');
function startTraining() { function startTraining() {
trackUmamiEvent('perceptron-training-start', {
type: props.type,
dataset: selectedDatasetCopy.value,
weight_init_method: selectedMethod.value,
hidden_layers: hiddenLayers.value,
hidden_layers_neurons: hiddenLayersNeurons.value,
min_error: minError.value,
learning_rate: learningRate.value,
max_iterations: maxIterations.value,
});
if (!selectedDatasetCopy.value) { if (!selectedDatasetCopy.value) {
form.setError( form.setError(
'dataset', 'dataset',
'Un dataset est nécessaire avant de lancer l\'entraînement.', "Un dataset est nécessaire avant de lancer l'entraînement.",
); );
console.log(form.errors); console.log(form.errors);
return; return;
@@ -156,19 +185,33 @@ function startTraining() {
max_iterations: maxIterations.value, max_iterations: maxIterations.value,
}); });
console.debug('[max_iterations] submitting', {
displayed: maxIterationsInput.value?.inputElement?.value,
refValue: maxIterations.value,
formValue: form.max_iterations,
limit: props.maxIterationsLimit,
});
form.post('/perceptron/run', { form.post('/perceptron/run', {
preserveScroll: true, preserveScroll: true,
}); });
} }
const emit = defineEmits(['update:selectedDataset', 'update:trainingId']); const emit = defineEmits(['update:selectedDataset', 'update:trainingId']);
async function cancelTraining() {
trackUmamiEvent('perceptron-training-cancel', {
training_id: trainingId.value,
});
form.cancel();
await fetch(cancel().url, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify({
training_id: trainingId.value,
}),
});
}
watch(selectedDatasetCopy, (newValue) => { watch(selectedDatasetCopy, (newValue) => {
emit('update:selectedDataset', newValue); emit('update:selectedDataset', newValue);
}); });
@@ -182,6 +225,7 @@ watch(selectedDatasetCopy, (newValue) => {
<CardContent> <CardContent>
<Form <Form
class="grid auto-cols-max grid-flow-row grid-cols-1 gap-4 space-y-6 md:grid-cols-2" class="grid auto-cols-max grid-flow-row grid-cols-1 gap-4 space-y-6 md:grid-cols-2"
cancel-on-unmount
> >
<!-- DATASET --> <!-- DATASET -->
<FormField name="dataset"> <FormField name="dataset">
@@ -206,7 +250,12 @@ watch(selectedDatasetCopy, (newValue) => {
</NativeSelectOption> </NativeSelectOption>
</NativeSelect> </NativeSelect>
</FormControl> </FormControl>
<FormError :error="form.errors.dataset || props.errors?.selectedDatasetCopy" /> <FormError
:error="
form.errors.dataset ||
props.errors?.selectedDatasetCopy
"
/>
</FormItem> </FormItem>
</FormField> </FormField>
@@ -224,7 +273,9 @@ watch(selectedDatasetCopy, (newValue) => {
class="cursor-pointer" class="cursor-pointer"
> >
<NativeSelectOption <NativeSelectOption
v-for="method in (props.type == 'multilayer' ? ['random'] : ['zeros', 'random'])" v-for="method in props.type == 'multilayer'
? ['random']
: ['zeros', 'random']"
v-bind:key="method" v-bind:key="method"
:value="method" :value="method"
> >
@@ -236,7 +287,10 @@ watch(selectedDatasetCopy, (newValue) => {
</FormField> </FormField>
<!-- HIDDEN LAYERS --> <!-- HIDDEN LAYERS -->
<FormField name="hidden_layers" v-if="props.type === 'multilayer'"> <FormField
name="hidden_layers"
v-if="props.type === 'multilayer'"
>
<FormItem> <FormItem>
<FormLabel>Nombre de couches cachées</FormLabel> <FormLabel>Nombre de couches cachées</FormLabel>
<FormControl> <FormControl>
@@ -255,9 +309,14 @@ watch(selectedDatasetCopy, (newValue) => {
</FormField> </FormField>
<!-- HIDDEN LAYERS NEURONS --> <!-- HIDDEN LAYERS NEURONS -->
<FormField name="hidden_layers_neurons" v-if="props.type === 'multilayer'"> <FormField
name="hidden_layers_neurons"
v-if="props.type === 'multilayer'"
>
<FormItem> <FormItem>
<FormLabel>Nombre de neurones par couche cachée</FormLabel> <FormLabel
>Nombre de neurones par couche cachée</FormLabel
>
<FormControl> <FormControl>
<!-- TODO : MAX input --> <!-- TODO : MAX input -->
<Input <Input
@@ -319,13 +378,39 @@ watch(selectedDatasetCopy, (newValue) => {
@input="handleMaxIterationsInput" @input="handleMaxIterationsInput"
/> />
</FormControl> </FormControl>
<div v-if="form.errors.max_iterations || props.errors.max_iterations"> <div
{{ form.errors.max_iterations || props.errors.max_iterations }} v-if="
form.errors.max_iterations ||
props.errors.max_iterations
"
>
{{
form.errors.max_iterations ||
props.errors.max_iterations
}}
</div> </div>
</FormItem> </FormItem>
</FormField> </FormField>
</Form> </Form>
<Button variant="outline" class="cursor-pointer mt-6" @click="startTraining">Lancer</Button>
<Transition name="fade">
<Button
variant="outline"
class="mt-6 cursor-pointer"
:disabled="form.processing"
@click="startTraining"
>Lancer<Spinner v-if="form.processing" class="ml-1"
/></Button>
</Transition>
<Transition name="fade">
<Button
variant="outline"
class="mt-6 ml-4 cursor-pointer"
@click="cancelTraining"
v-if="form.processing"
>Annuler</Button
>
</Transition>
</CardContent> </CardContent>
</Card> </Card>
</template> </template>
+15 -8
View File
@@ -1,5 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { Head } from '@inertiajs/vue3'; import { Head } from '@inertiajs/vue3';
import { trackUmamiEvent } from '@jaseeey/vue-umami-plugin';
import { BookOpenText } from '@lucide/vue';
import { useEventListener } from '@vueuse/core'; import { useEventListener } from '@vueuse/core';
import { import {
Chart as ChartJS, Chart as ChartJS,
@@ -13,11 +15,21 @@ import {
LineElement, LineElement,
ScatterController, ScatterController,
LineController, LineController,
BarController,
LogarithmicScale,
} from 'chart.js'; } from 'chart.js';
import { ArrowDown, ArrowUp } from 'lucide-vue-next'; import { ArrowDown, ArrowUp } from 'lucide-vue-next';
import { computed, nextTick, ref, watch } from 'vue'; import { computed, nextTick, ref, watch } from 'vue';
import HelpText from '@/components/HelpText.vue';
import LinkHeader from '@/components/LinkHeader.vue'; import LinkHeader from '@/components/LinkHeader.vue';
import Button from '@/components/ui/button/Button.vue'; import Button from '@/components/ui/button/Button.vue';
import Drawer from '@/components/ui/drawer/Drawer.vue';
import DrawerContent from '@/components/ui/drawer/DrawerContent.vue';
import DrawerTitle from '@/components/ui/drawer/DrawerTitle.vue';
import DrawerTrigger from '@/components/ui/drawer/DrawerTrigger.vue';
import Kbd from '@/components/ui/kbd/Kbd.vue';
import KbdGroup from '@/components/ui/kbd/KbdGroup.vue';
import ScrollArea from '@/components/ui/scroll-area/ScrollArea.vue'; import ScrollArea from '@/components/ui/scroll-area/ScrollArea.vue';
import { import {
Tooltip as UiTooltip, Tooltip as UiTooltip,
@@ -31,14 +43,6 @@ import IterationTable from '../components/IterationTable.vue';
import PerceptronDecisionGraph from '../components/PerceptronDecisionGraph.vue'; import PerceptronDecisionGraph from '../components/PerceptronDecisionGraph.vue';
import PerceptronIterationsErrorsGraph from '../components/PerceptronIterationsErrorsGraph.vue'; import PerceptronIterationsErrorsGraph from '../components/PerceptronIterationsErrorsGraph.vue';
import PerceptronSetup from '../components/PerceptronSetup.vue'; import PerceptronSetup from '../components/PerceptronSetup.vue';
import HelpText from '@/components/HelpText.vue';
import { BookOpenText } from '@lucide/vue';
import Drawer from '@/components/ui/drawer/Drawer.vue';
import DrawerTrigger from '@/components/ui/drawer/DrawerTrigger.vue';
import DrawerContent from '@/components/ui/drawer/DrawerContent.vue';
import DrawerTitle from '@/components/ui/drawer/DrawerTitle.vue';
import KbdGroup from '@/components/ui/kbd/KbdGroup.vue';
import Kbd from '@/components/ui/kbd/Kbd.vue';
ChartJS.register( ChartJS.register(
Title, Title,
@@ -51,6 +55,8 @@ ChartJS.register(
LineElement, LineElement,
ScatterController, ScatterController,
LineController, LineController,
BarController,
LogarithmicScale
); );
ChartJS.defaults.font.size = 16; ChartJS.defaults.font.size = 16;
ChartJS.defaults.color = '#FFF'; ChartJS.defaults.color = '#FFF';
@@ -141,6 +147,7 @@ const handleDrawerOpenChange = async (open: boolean) => {
if (open) { if (open) {
await restoreHelpScroll(); await restoreHelpScroll();
trackUmamiEvent('wiki-opened', { perceptronType: props.type });
} }
}; };
+6
View File
@@ -1,2 +1,8 @@
<?php <?php
use App\Http\Controllers\PerceptronController;
use Illuminate\Support\Facades\Route;
Route::post('perceptron/cancel', [PerceptronController::class, 'cancel'])
->name('perceptron.cancel');