Compare commits
26 Commits
3f1fcef1bc
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c223e0ba8 | |||
| 6d8e3438f0 | |||
| ae676343a5 | |||
| 9389aef632 | |||
| fa6c427d76 | |||
| 25b03fc39a | |||
| d80bfe3cdd | |||
| 2332e805e8 | |||
| 4610334f72 | |||
| 9ae3a6942b | |||
| 3a19691591 | |||
| 2d7c1873bb | |||
| 5d68757e9d | |||
| 2de27af03b | |||
| be7b276dec | |||
| 2345b850b8 | |||
| 538ddc9679 | |||
| 099318c79e | |||
| 1517920fce | |||
| d4f98517cd | |||
| feac0feaeb | |||
| ba4b951a69 | |||
| 41b0e8a64a | |||
| da53bf16e9 | |||
| 3ad2e7d999 | |||
| 00f379811f |
@@ -3,6 +3,7 @@ APP_ENV=production
|
|||||||
APP_KEY=base64:WE5DlJGDeIrl+B1ZqahVJSg4TN6oY3N1ph8CDKNo0w4=
|
APP_KEY=base64:WE5DlJGDeIrl+B1ZqahVJSg4TN6oY3N1ph8CDKNo0w4=
|
||||||
APP_DEBUG=false
|
APP_DEBUG=false
|
||||||
APP_URL=https://perceptron.matthiasg.dev
|
APP_URL=https://perceptron.matthiasg.dev
|
||||||
|
ASSET_URL=https://perceptron.matthiasg.dev
|
||||||
|
|
||||||
APP_LOCALE=fr
|
APP_LOCALE=fr
|
||||||
APP_FALLBACK_LOCALE=fr
|
APP_FALLBACK_LOCALE=fr
|
||||||
@@ -11,6 +12,10 @@ APP_FAKER_LOCALE=fr_FR
|
|||||||
APP_MAINTENANCE_DRIVER=file
|
APP_MAINTENANCE_DRIVER=file
|
||||||
# APP_MAINTENANCE_STORE=database
|
# APP_MAINTENANCE_STORE=database
|
||||||
|
|
||||||
|
PHP_CLI_SERVER_WORKERS=4
|
||||||
|
|
||||||
|
BCRYPT_ROUNDS=12
|
||||||
|
|
||||||
LOG_CHANNEL=single
|
LOG_CHANNEL=single
|
||||||
LOG_STACK=single
|
LOG_STACK=single
|
||||||
LOG_DEPRECATIONS_CHANNEL=null
|
LOG_DEPRECATIONS_CHANNEL=null
|
||||||
@@ -40,6 +45,9 @@ REVERB_APP_SECRET=oosuq0v9jgaslzp9cmhv
|
|||||||
REVERB_HOST="perceptron.matthiasg.dev"
|
REVERB_HOST="perceptron.matthiasg.dev"
|
||||||
REVERB_PORT=443
|
REVERB_PORT=443
|
||||||
REVERB_SCHEME=https
|
REVERB_SCHEME=https
|
||||||
|
REVERB_BROADCAST_HOST=reverb
|
||||||
|
REVERB_BROADCAST_PORT=8080
|
||||||
|
REVERB_BROADCAST_SCHEME=http
|
||||||
REVERB_MAX_REQUEST_SIZE=100000
|
REVERB_MAX_REQUEST_SIZE=100000
|
||||||
|
|
||||||
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
|
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
env:
|
env:
|
||||||
RUNNER_TOOL_CACHE: /toolcache
|
RUNNER_TOOL_CACHE: /toolcache
|
||||||
|
REVERB_APP_KEY: ${{ secrets.REVERB_APP_KEY }}
|
||||||
|
REVERB_APP_SECRET: ${{ secrets.REVERB_APP_SECRET }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository code
|
- name: Check out repository code
|
||||||
@@ -47,7 +49,12 @@ jobs:
|
|||||||
context: .
|
context: .
|
||||||
file: Dockerfile
|
file: Dockerfile
|
||||||
push: true
|
push: true
|
||||||
tags: git.matthiasg.dev/Ninluc/perceptron-viewer/perceptron-viewer:latest, git.matthiasg.dev/Ninluc/perceptron-viewer/perceptron-viewer:${{ steps.get_version.outputs.version }}
|
build-args: |
|
||||||
|
VITE_REVERB_APP_KEY=${{ secrets.REVERB_APP_KEY }}
|
||||||
|
VITE_REVERB_HOST=perceptron.matthiasg.dev
|
||||||
|
VITE_REVERB_PORT=443
|
||||||
|
VITE_REVERB_SCHEME=https
|
||||||
|
tags: git.matthiasg.dev/ninluc/perceptron-viewer/perceptron-viewer:latest, git.matthiasg.dev/ninluc/perceptron-viewer/perceptron-viewer:${{ steps.get_version.outputs.version }}
|
||||||
|
|
||||||
# notify:
|
# notify:
|
||||||
# needs: build
|
# needs: build
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
php-version: ['8.4', '8.5']
|
php-version: ['8.3']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -80,8 +80,8 @@ jobs:
|
|||||||
# -------------------------
|
# -------------------------
|
||||||
# Build (optional – remove if not needed for tests)
|
# Build (optional – remove if not needed for tests)
|
||||||
# -------------------------
|
# -------------------------
|
||||||
- name: Build Assets
|
# - name: Build Assets
|
||||||
run: npm run build
|
# run: npm run build
|
||||||
|
|
||||||
# -------------------------
|
# -------------------------
|
||||||
# Run tests (parallel)
|
# Run tests (parallel)
|
||||||
|
|||||||
+12
@@ -9,11 +9,23 @@ RUN composer install --no-dev --optimize-autoloader --no-interaction
|
|||||||
# Install node and npm
|
# Install node and npm
|
||||||
RUN apk add --no-cache nodejs npm
|
RUN apk add --no-cache nodejs npm
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|
||||||
|
ARG VITE_REVERB_APP_KEY
|
||||||
|
ARG VITE_REVERB_HOST
|
||||||
|
ARG VITE_REVERB_PORT=443
|
||||||
|
ARG VITE_REVERB_SCHEME=https
|
||||||
|
ENV VITE_REVERB_APP_KEY=$VITE_REVERB_APP_KEY \
|
||||||
|
VITE_REVERB_HOST=$VITE_REVERB_HOST \
|
||||||
|
VITE_REVERB_PORT=$VITE_REVERB_PORT \
|
||||||
|
VITE_REVERB_SCHEME=$VITE_REVERB_SCHEME
|
||||||
|
|
||||||
RUN npm run build
|
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 \
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Perceptron and neuronal networks
|
# PerceptronViewer
|
||||||
|
|
||||||
Using Laravel and Vue JS
|
Using Laravel and Vue JS
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exceptions;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class TrainingCancelledException extends RuntimeException
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -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()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
HandleInertiaRequests::class,
|
HandleInertiaRequests::class,
|
||||||
AddLinkHeadersForPreloadedAssets::class,
|
AddLinkHeadersForPreloadedAssets::class,
|
||||||
]);
|
]);
|
||||||
|
$middleware->trustProxies(at: [
|
||||||
|
'*'
|
||||||
|
]);
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions): void {
|
->withExceptions(function (Exceptions $exceptions): void {
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -36,10 +36,10 @@ return [
|
|||||||
'secret' => env('REVERB_APP_SECRET'),
|
'secret' => env('REVERB_APP_SECRET'),
|
||||||
'app_id' => env('REVERB_APP_ID'),
|
'app_id' => env('REVERB_APP_ID'),
|
||||||
'options' => [
|
'options' => [
|
||||||
'host' => env('REVERB_HOST'),
|
'host' => env('REVERB_BROADCAST_HOST', env('REVERB_HOST')),
|
||||||
'port' => env('REVERB_PORT', 443),
|
'port' => env('REVERB_BROADCAST_PORT', env('REVERB_PORT', 443)),
|
||||||
'scheme' => env('REVERB_SCHEME', 'https'),
|
'scheme' => env('REVERB_BROADCAST_SCHEME', env('REVERB_SCHEME', 'https')),
|
||||||
'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
|
'useTLS' => env('REVERB_BROADCAST_SCHEME', env('REVERB_SCHEME', 'https')) === 'https',
|
||||||
],
|
],
|
||||||
'client_options' => [
|
'client_options' => [
|
||||||
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
|
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
|
||||||
|
|||||||
@@ -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,5 +1,8 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Migrate the database
|
||||||
|
php ./artisan migrate --force || exit 1
|
||||||
|
|
||||||
# Cache
|
# Cache
|
||||||
php ./artisan optimize:clear && php ./artisan optimize
|
php ./artisan optimize:clear && php ./artisan optimize
|
||||||
|
|
||||||
|
|||||||
Generated
+6
@@ -6,6 +6,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@inertiajs/vue3": "^2.3.7",
|
"@inertiajs/vue3": "^2.3.7",
|
||||||
|
"@jaseeey/vue-umami-plugin": "^1.6.1",
|
||||||
"@lucide/vue": "^1.46.0",
|
"@lucide/vue": "^1.46.0",
|
||||||
"@tailwindcss/typography": "^0.5.20",
|
"@tailwindcss/typography": "^0.5.20",
|
||||||
"@vee-validate/zod": "^4.15.1",
|
"@vee-validate/zod": "^4.15.1",
|
||||||
@@ -861,6 +862,11 @@
|
|||||||
"@swc/helpers": "^0.5.0"
|
"@swc/helpers": "^0.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@jaseeey/vue-umami-plugin": {
|
||||||
|
"version": "1.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jaseeey/vue-umami-plugin/-/vue-umami-plugin-1.6.1.tgz",
|
||||||
|
"integrity": "sha512-a38bB7cYbwMP5HmlrT2Dk2RHdGFxVvLRci2GhY8cIwCpM419qXtvSM6SKmIJ3U3u7zYdcQLVAySOcXY150Z14Q=="
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@inertiajs/vue3": "^2.3.7",
|
"@inertiajs/vue3": "^2.3.7",
|
||||||
|
"@jaseeey/vue-umami-plugin": "^1.6.1",
|
||||||
"@lucide/vue": "^1.46.0",
|
"@lucide/vue": "^1.46.0",
|
||||||
"@tailwindcss/typography": "^0.5.20",
|
"@tailwindcss/typography": "^0.5.20",
|
||||||
"@vee-validate/zod": "^4.15.1",
|
"@vee-validate/zod": "^4.15.1",
|
||||||
|
|||||||
@@ -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,4 +1,5 @@
|
|||||||
import { createInertiaApp } from '@inertiajs/vue3';
|
import { createInertiaApp } from '@inertiajs/vue3';
|
||||||
|
import { VueUmamiPlugin } from '@jaseeey/vue-umami-plugin';
|
||||||
import { configureEcho } from '@laravel/echo-vue';
|
import { configureEcho } from '@laravel/echo-vue';
|
||||||
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
|
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
|
||||||
import type { DefineComponent } from 'vue';
|
import type { DefineComponent } from 'vue';
|
||||||
@@ -22,6 +23,20 @@ createInertiaApp({
|
|||||||
setup({ el, App, props, plugin }) {
|
setup({ el, App, props, plugin }) {
|
||||||
createApp({ render: () => h(App, props) })
|
createApp({ render: () => h(App, props) })
|
||||||
.use(plugin)
|
.use(plugin)
|
||||||
|
.use(
|
||||||
|
VueUmamiPlugin({
|
||||||
|
websiteID: 'e616767f-e0d3-4ce9-b551-33c7c3806fc0',
|
||||||
|
scriptSrc: 'https://abcd.matthiasg.dev/script.js',
|
||||||
|
// autoTrack: true,
|
||||||
|
debug: import.meta.env.DEV,
|
||||||
|
extraDataAttributes: {
|
||||||
|
'data-auto-track': 'true',
|
||||||
|
'data-performance': 'true',
|
||||||
|
'data-domains': 'perceptron.matthiasg.dev,matthiasg.dev,www.matthiasg.dev',
|
||||||
|
'data-exclude-search': 'false',
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
.mount(el);
|
.mount(el);
|
||||||
},
|
},
|
||||||
progress: {
|
progress: {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -11,11 +13,23 @@ import {
|
|||||||
LinearScale,
|
LinearScale,
|
||||||
PointElement,
|
PointElement,
|
||||||
LineElement,
|
LineElement,
|
||||||
|
ScatterController,
|
||||||
|
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,
|
||||||
@@ -29,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,
|
||||||
@@ -47,6 +53,10 @@ ChartJS.register(
|
|||||||
LinearScale,
|
LinearScale,
|
||||||
PointElement,
|
PointElement,
|
||||||
LineElement,
|
LineElement,
|
||||||
|
ScatterController,
|
||||||
|
LineController,
|
||||||
|
BarController,
|
||||||
|
LogarithmicScale
|
||||||
);
|
);
|
||||||
ChartJS.defaults.font.size = 16;
|
ChartJS.defaults.font.size = 16;
|
||||||
ChartJS.defaults.color = '#FFF';
|
ChartJS.defaults.color = '#FFF';
|
||||||
@@ -137,6 +147,7 @@ const handleDrawerOpenChange = async (open: boolean) => {
|
|||||||
|
|
||||||
if (open) {
|
if (open) {
|
||||||
await restoreHelpScroll();
|
await restoreHelpScroll();
|
||||||
|
trackUmamiEvent('wiki-opened', { perceptronType: props.type });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,9 @@
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
{{-- Anonymous analytics --}}
|
||||||
|
<script defer src="https://analytics.matthiasg.dev/recorder.js" data-website-id="e616767f-e0d3-4ce9-b551-33c7c3806fc0"></script>
|
||||||
|
|
||||||
{{-- Inline style to set the HTML background color based on our theme in app.css --}}
|
{{-- Inline style to set the HTML background color based on our theme in app.css --}}
|
||||||
<style>
|
<style>
|
||||||
html {
|
html {
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user