Compare commits
23 Commits
8af26db16c
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f4db07918 | |||
| f6620c2eca | |||
| ca9c0dc511 | |||
| 0876588550 | |||
| a37afcec07 | |||
| b052d792f8 | |||
| 5880024933 | |||
| a92a47288c | |||
| bcaf334380 | |||
| dea908c63e | |||
| 236fa503fb | |||
| 0f92af4a1e | |||
| ef90236adc | |||
| 9d4b02fab5 | |||
| 88a932391b | |||
| abb16aa6c1 | |||
| 29498e45ac | |||
| af67830fbb | |||
| 42e07de287 | |||
| 47991fe736 | |||
| 03d8688f56 | |||
| 72775d09ef | |||
| 87aa942e0b |
63
.github/workflows/lint.yml
vendored
63
.github/workflows/lint.yml
vendored
@@ -20,30 +20,59 @@ permissions:
|
||||
jobs:
|
||||
quality:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# -------------------------
|
||||
# Cache Composer
|
||||
# -------------------------
|
||||
- name: Cache Composer dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.composer/cache
|
||||
key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
composer-${{ runner.os }}-
|
||||
|
||||
# -------------------------
|
||||
# Cache Node
|
||||
# -------------------------
|
||||
- name: Cache Node dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: node-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
node-${{ runner.os }}-
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.4'
|
||||
coverage: none
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: 'npm'
|
||||
|
||||
# -------------------------
|
||||
# Install dependencies
|
||||
# -------------------------
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
|
||||
npm install
|
||||
composer install --no-interaction --prefer-dist --no-progress --no-scripts
|
||||
npm ci
|
||||
|
||||
- name: Run Pint
|
||||
run: composer lint
|
||||
|
||||
- name: Format Frontend
|
||||
run: npm run format
|
||||
|
||||
- name: Lint Frontend
|
||||
run: npm run lint
|
||||
|
||||
# - name: Commit Changes
|
||||
# uses: stefanzweifel/git-auto-commit-action@v7
|
||||
# with:
|
||||
# commit_message: fix code style
|
||||
# commit_options: '--no-verify'
|
||||
# -------------------------
|
||||
# Run linters in parallel
|
||||
# -------------------------
|
||||
- name: Run linters
|
||||
run: |
|
||||
composer lint &
|
||||
npm run format &
|
||||
npm run lint &
|
||||
wait
|
||||
|
||||
56
.github/workflows/tests.yml
vendored
56
.github/workflows/tests.yml
vendored
@@ -25,32 +25,66 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# -------------------------
|
||||
# Cache Composer
|
||||
# -------------------------
|
||||
- name: Cache Composer dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.composer/cache
|
||||
key: composer-${{ runner.os }}-${{ matrix.php-version }}-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: |
|
||||
composer-${{ runner.os }}-${{ matrix.php-version }}-
|
||||
|
||||
# -------------------------
|
||||
# Cache Node
|
||||
# -------------------------
|
||||
- name: Cache Node dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: node-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
node-${{ runner.os }}-
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
tools: composer:v2
|
||||
coverage: xdebug
|
||||
coverage: none
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: 'npm'
|
||||
|
||||
# -------------------------
|
||||
# Install dependencies
|
||||
# -------------------------
|
||||
- name: Install Node Dependencies
|
||||
run: npm i
|
||||
run: npm ci
|
||||
|
||||
- name: Install Dependencies
|
||||
run: composer install --no-interaction --prefer-dist --optimize-autoloader
|
||||
- name: Install PHP Dependencies
|
||||
run: composer install --no-interaction --prefer-dist --optimize-autoloader --no-progress
|
||||
|
||||
- name: Copy Environment File
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Generate Application Key
|
||||
run: php artisan key:generate
|
||||
# -------------------------
|
||||
# Laravel setup
|
||||
# -------------------------
|
||||
- name: Prepare environment
|
||||
run: |
|
||||
cp .env.example .env
|
||||
php artisan key:generate
|
||||
|
||||
# -------------------------
|
||||
# Build (optional – remove if not needed for tests)
|
||||
# -------------------------
|
||||
- name: Build Assets
|
||||
run: npm run build
|
||||
|
||||
- name: Tests
|
||||
run: ./vendor/bin/phpunit
|
||||
# -------------------------
|
||||
# Run tests (parallel)
|
||||
# -------------------------
|
||||
- name: Run Tests
|
||||
run: php artisan test --parallel
|
||||
|
||||
@@ -23,10 +23,10 @@ Using Laravel and Vue JS
|
||||
3. NodeJs (Node + NPM)
|
||||
<https://nodejs.org/en/download>
|
||||
|
||||
2. Install dependencies
|
||||
2. Setup project and install dependencies
|
||||
|
||||
```shell
|
||||
composer install
|
||||
composer run setup
|
||||
```
|
||||
|
||||
## Running the project
|
||||
|
||||
@@ -21,8 +21,7 @@ class PerceptronInitialization implements ShouldBroadcast
|
||||
public ActivationsFunctions $activationFunction,
|
||||
public string $sessionId,
|
||||
public string $trainingId,
|
||||
)
|
||||
{
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
@@ -34,7 +33,7 @@ class PerceptronInitialization implements ShouldBroadcast
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [
|
||||
new Channel($this->sessionId . '-perceptron-training'),
|
||||
new Channel($this->sessionId.'-perceptron-training'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
@@ -21,8 +19,7 @@ class PerceptronTrainingEnded implements ShouldBroadcast
|
||||
public string $reason,
|
||||
public string $sessionId,
|
||||
public string $trainingId,
|
||||
)
|
||||
{
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
@@ -34,7 +31,7 @@ class PerceptronTrainingEnded implements ShouldBroadcast
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [
|
||||
new Channel($this->sessionId . '-perceptron-training'),
|
||||
new Channel($this->sessionId.'-perceptron-training'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,7 @@ class PerceptronTrainingIteration implements ShouldBroadcast
|
||||
public array $iterations, // ["epoch" => int, "exampleIndex" => int, "error" => float, "synaptic_weights" => array]
|
||||
public string $sessionId,
|
||||
public string $trainingId,
|
||||
)
|
||||
{
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
||||
@@ -33,7 +32,7 @@ class PerceptronTrainingIteration implements ShouldBroadcast
|
||||
{
|
||||
// Log::debug("Broadcasting on channel: " . $this->sessionId . '-perceptron-training');
|
||||
return [
|
||||
new Channel($this->sessionId . '-perceptron-training'),
|
||||
new Channel($this->sessionId.'-perceptron-training'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -3,15 +3,21 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Events\PerceptronInitialization;
|
||||
use App\Models\GradientDescentPerceptronTraining;
|
||||
use App\Models\SimpleBinaryPerceptronTraining;
|
||||
use App\Models\NetworksTraining\ADALINEPerceptronTraining;
|
||||
use App\Models\NetworksTraining\GradientDescentPerceptronTraining;
|
||||
use App\Models\NetworksTraining\MonoLayerPerceptronTraining;
|
||||
use App\Models\NetworksTraining\SimpleBinaryPerceptronTraining;
|
||||
use App\Services\DatasetReader\IDataSetReader;
|
||||
use App\Services\DatasetReader\LinearOrderDataSetReader;
|
||||
use App\Services\DatasetReader\RandomOrderDataSetReader;
|
||||
use App\Services\IterationEventBuffer\PerceptronIterationEventBuffer;
|
||||
use App\Services\IterationEventBuffer\PerceptronLimitedEpochEventBuffer;
|
||||
use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider;
|
||||
use App\Services\SynapticWeightsProvider\ZeroSynapticWeights;
|
||||
use Illuminate\Contracts\Queue\Job;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
use Tests\Services\IterationEventBuffer\DullIterationEventBuffer;
|
||||
|
||||
class PerceptronController extends Controller
|
||||
@@ -32,8 +38,9 @@ class PerceptronController extends Controller
|
||||
$learningRate = 0.015;
|
||||
$maxIterations = 150;
|
||||
break;
|
||||
case 'gradientdescent':
|
||||
case 'gradientdescent' || 'adaline':
|
||||
$learningRate = 0.00003;
|
||||
break;
|
||||
}
|
||||
|
||||
return inertia('PerceptronViewer', [
|
||||
@@ -55,7 +62,7 @@ class PerceptronController extends Controller
|
||||
if (pathinfo($file, PATHINFO_EXTENSION) === 'csv') {
|
||||
$dataset = [];
|
||||
$dataset['label'] = str_replace('.csv', '', $file);
|
||||
$dataSetReader = new LinearOrderDataSetReader($dataSetsDirectory . '/' . $file);
|
||||
$dataSetReader = new LinearOrderDataSetReader($dataSetsDirectory.'/'.$file);
|
||||
$dataset['data'] = [];
|
||||
switch (count($dataSetReader->lines[0])) {
|
||||
case 3:
|
||||
@@ -88,6 +95,10 @@ class PerceptronController extends Controller
|
||||
$dataset['defaultLearningRate'] = 0.3;
|
||||
$dataset['defaultMinError'] = 0.125;
|
||||
break;
|
||||
case 'adaline':
|
||||
$dataset['defaultLearningRate'] = 0.05;
|
||||
$dataset['defaultMinError'] = 0.125;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'table_2_9':
|
||||
@@ -95,73 +106,72 @@ class PerceptronController extends Controller
|
||||
case 'simple':
|
||||
$dataset['defaultLearningRate'] = 0.015;
|
||||
break;
|
||||
case 'gradientdescent':
|
||||
case 'gradientdescent' || 'adaline':
|
||||
$dataset['defaultLearningRate'] = 0.001;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'table_2_11':
|
||||
$dataset['defaultMinError'] = 1.0;
|
||||
$dataset['defaultMinError'] = 0.02;
|
||||
break;
|
||||
}
|
||||
$datasets[] = $dataset;
|
||||
}
|
||||
}
|
||||
|
||||
return $datasets;
|
||||
}
|
||||
|
||||
private function getDataSetReader(string $dataSet): IDataSetReader
|
||||
{
|
||||
$dataSetFileName = "data_sets/{$dataSet}.csv";
|
||||
return new LinearOrderDataSetReader($dataSetFileName);
|
||||
|
||||
return new RandomOrderDataSetReader($dataSetFileName);
|
||||
}
|
||||
|
||||
public function run(Request $request, ISynapticWeightsProvider $synapticWeightsProvider)
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
|
||||
$perceptronType = $request->input('type');
|
||||
$minError = $request->input('min_error', 0.01);
|
||||
$weightInitMethod = $request->input('weight_init_method', 'random');
|
||||
$dataSet = $request->input('dataset');
|
||||
$learningRate = $request->input('learning_rate', 0.015);
|
||||
$maxIterations = $request->input('max_iterations', 100);
|
||||
$maxEpochs = $request->input('max_iterations', 100);
|
||||
$sessionId = $request->input('session_id', session()->getId());
|
||||
$trainingId = $request->input('training_id');
|
||||
|
||||
// Remove the jobs for the sessionId
|
||||
DB::table('jobs')->where('payload', 'like', '%s:9:\"sessionId\";s:40:\"'. $sessionId .'\";%')->delete();
|
||||
|
||||
if ($weightInitMethod === 'zeros') {
|
||||
$synapticWeightsProvider = new ZeroSynapticWeights();
|
||||
$synapticWeightsProvider = new ZeroSynapticWeights;
|
||||
}
|
||||
|
||||
$iterationEventBuffer = new PerceptronIterationEventBuffer($sessionId, $trainingId);
|
||||
if ($maxIterations > config('perceptron.limited_broadcast_iterations')) {
|
||||
$iterationsInterval = (int)($maxIterations / config('perceptron.limited_broadcast_iterations'));
|
||||
if ($maxEpochs > config('perceptron.limited_broadcast_iterations')) {
|
||||
$iterationsInterval = (int) ($maxEpochs / config('perceptron.limited_broadcast_iterations'));
|
||||
$iterationEventBuffer = new PerceptronLimitedEpochEventBuffer($sessionId, $trainingId, $iterationsInterval);
|
||||
}
|
||||
|
||||
$dataSetReader = $this->getDataSetReader($dataSet);
|
||||
|
||||
$datasetReader = $this->getDataSetReader($dataSet);
|
||||
|
||||
$networkTraining = match ($perceptronType) {
|
||||
'simple' => new SimpleBinaryPerceptronTraining($dataSetReader, $learningRate, $maxIterations, $synapticWeightsProvider, $iterationEventBuffer, $sessionId, $trainingId),
|
||||
'gradientdescent' => new GradientDescentPerceptronTraining($dataSetReader, $learningRate, $maxIterations, $synapticWeightsProvider, $iterationEventBuffer, $sessionId, $trainingId, $minError),
|
||||
'gradientdescentTest' => new GradientDescentPerceptronTraining(
|
||||
datasetReader: new LinearOrderDataSetReader(public_path('data_sets/logic_and_gradient.csv')),
|
||||
learningRate: 0.2,
|
||||
maxEpochs: 100,
|
||||
synapticWeightsProvider: new ZeroSynapticWeights(),
|
||||
iterationEventBuffer: $iterationEventBuffer,
|
||||
sessionId: 'test-session',
|
||||
trainingId: 'test-training',
|
||||
minError: 0.125001,
|
||||
),
|
||||
'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),
|
||||
default => null,
|
||||
};
|
||||
|
||||
event(new PerceptronInitialization($dataSetReader->lines, $networkTraining->activationFunction, $sessionId, $trainingId));
|
||||
event(new PerceptronInitialization($datasetReader->lines, $networkTraining->activationFunction, $sessionId, $trainingId));
|
||||
|
||||
$networkTraining->start();
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Training completed',
|
||||
'execution_time' => microtime(true) - $startTime,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
abstract class Network
|
||||
{
|
||||
|
||||
}
|
||||
106
app/Models/NetworksTraining/ADALINEPerceptronTraining.php
Normal file
106
app/Models/NetworksTraining/ADALINEPerceptronTraining.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\NetworksTraining;
|
||||
|
||||
use App\Events\PerceptronTrainingEnded;
|
||||
use App\Models\ActivationsFunctions;
|
||||
use App\Models\Perceptrons\GradientDescentPerceptron;
|
||||
use App\Models\Perceptrons\Perceptron;
|
||||
use App\Services\DatasetReader\IDataSetReader;
|
||||
use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer;
|
||||
use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider;
|
||||
|
||||
class ADALINEPerceptronTraining extends NetworkTraining
|
||||
{
|
||||
private Perceptron $perceptron;
|
||||
|
||||
public ActivationsFunctions $activationFunction = ActivationsFunctions::LINEAR;
|
||||
|
||||
private float $epochError;
|
||||
|
||||
public function __construct(
|
||||
IDataSetReader $datasetReader,
|
||||
protected float $learningRate,
|
||||
int $maxEpochs,
|
||||
protected ISynapticWeightsProvider $synapticWeightsProvider,
|
||||
IPerceptronIterationEventBuffer $iterationEventBuffer,
|
||||
string $sessionId,
|
||||
string $trainingId,
|
||||
private float $minError,
|
||||
) {
|
||||
parent::__construct($datasetReader, $maxEpochs, $iterationEventBuffer, $sessionId, $trainingId);
|
||||
$this->perceptron = new GradientDescentPerceptron($synapticWeightsProvider->generate($datasetReader->getInputSize()));
|
||||
}
|
||||
|
||||
public function start(): void
|
||||
{
|
||||
$this->epoch = 0;
|
||||
do {
|
||||
$this->epochError = 0;
|
||||
$this->epoch++;
|
||||
|
||||
$inputsForCurrentEpoch = [];
|
||||
|
||||
while ($nextRow = $this->datasetReader->getNextLine()) {
|
||||
$inputsForCurrentEpoch[] = $nextRow;
|
||||
$inputs = array_slice($nextRow, 0, -1);
|
||||
$correctOutput = (float) end($nextRow);
|
||||
|
||||
$iterationError = $this->iterationFunction($inputs, $correctOutput);
|
||||
|
||||
// Synaptic weights correction after each example
|
||||
$synaptic_weights = $this->perceptron->getSynapticWeights();
|
||||
$inputs_with_bias = array_merge([1], $inputs); // Add bias input
|
||||
$new_weights = array_map(
|
||||
fn ($weight, $weightIndex) => $weight + ($this->learningRate * $iterationError * $inputs_with_bias[$weightIndex]),
|
||||
$synaptic_weights,
|
||||
array_keys($synaptic_weights)
|
||||
);
|
||||
$this->perceptron->setSynapticWeights($new_weights);
|
||||
|
||||
// Broadcast the training iteration event
|
||||
$this->addIterationToBuffer($iterationError, [[$this->perceptron->getSynapticWeights()]]);
|
||||
}
|
||||
|
||||
// Calculte the average error for the epoch with the last synaptic weights
|
||||
foreach ($inputsForCurrentEpoch as $inputsWithLabel) {
|
||||
$inputs = array_slice($inputsWithLabel, 0, -1);
|
||||
$correctOutput = (float) end($inputsWithLabel);
|
||||
$output = $this->perceptron->test($inputs)[0];
|
||||
$iterationError = $correctOutput - $output;
|
||||
$this->epochError += ($iterationError ** 2) / 2; // Squared error for the example
|
||||
}
|
||||
$this->epochError /= $this->datasetReader->getEpochExamplesCount(); // Average error for the epoch
|
||||
|
||||
$this->datasetReader->reset(); // Reset the dataset for the next iteration
|
||||
} while ($this->epoch < $this->maxEpochs && ! $this->stopCondition());
|
||||
|
||||
$this->iterationEventBuffer->flush(); // Ensure all iterations are sent to the frontend
|
||||
|
||||
$this->checkPassedMaxIterations($this->epochError);
|
||||
}
|
||||
|
||||
protected function stopCondition(): bool
|
||||
{
|
||||
$condition = $this->epochError <= $this->minError;
|
||||
if ($condition === true) {
|
||||
event(new PerceptronTrainingEnded('Le perceptron à atteint l\'erreur minimale', $this->sessionId, $this->trainingId));
|
||||
}
|
||||
|
||||
return $condition;
|
||||
}
|
||||
|
||||
private function iterationFunction(array $inputs, float $correctOutput): float
|
||||
{
|
||||
$output = $this->perceptron->test($inputs)[0];
|
||||
|
||||
$error = $correctOutput - $output;
|
||||
|
||||
return $error;
|
||||
}
|
||||
|
||||
public function getSynapticWeights(): array
|
||||
{
|
||||
return [[$this->perceptron->getSynapticWeights()]];
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace App\Models\NetworksTraining;
|
||||
|
||||
use App\Events\PerceptronTrainingEnded;
|
||||
use App\Models\ActivationsFunctions;
|
||||
use App\Models\Perceptrons\GradientDescentPerceptron;
|
||||
use App\Models\Perceptrons\Perceptron;
|
||||
use App\Services\DatasetReader\IDataSetReader;
|
||||
use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer;
|
||||
use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider;
|
||||
@@ -59,14 +62,14 @@ class GradientDescentPerceptronTraining extends NetworkTraining
|
||||
// Synaptic weights correction after each epoch
|
||||
$synaptic_weights = $this->perceptron->getSynapticWeights();
|
||||
$new_weights = array_map(
|
||||
fn($weight, $weightIndex) => $weight + $this->learningRate * array_sum($epochCorrectorPerWeight[$weightIndex]),
|
||||
fn ($weight, $weightIndex) => $weight + $this->learningRate * array_sum($epochCorrectorPerWeight[$weightIndex]),
|
||||
$synaptic_weights,
|
||||
array_keys($synaptic_weights)
|
||||
);
|
||||
$this->perceptron->setSynapticWeights($new_weights);
|
||||
|
||||
$this->datasetReader->reset(); // Reset the dataset for the next iteration
|
||||
} while ($this->epoch < $this->maxEpochs && !$this->stopCondition());
|
||||
} while ($this->epoch < $this->maxEpochs && ! $this->stopCondition());
|
||||
|
||||
$this->iterationEventBuffer->flush(); // Ensure all iterations are sent to the frontend
|
||||
|
||||
@@ -75,16 +78,17 @@ class GradientDescentPerceptronTraining extends NetworkTraining
|
||||
|
||||
protected function stopCondition(): bool
|
||||
{
|
||||
$condition = $this->epochError <= $this->minError && $this->perceptron->getSynapticWeights() !== [[0.0, 0.0, 0.0]];
|
||||
$condition = $this->epochError <= $this->minError;
|
||||
if ($condition === true) {
|
||||
event(new PerceptronTrainingEnded('Le perceptron à atteint l\'erreur minimale', $this->sessionId, $this->trainingId));
|
||||
}
|
||||
|
||||
return $condition;
|
||||
}
|
||||
|
||||
private function iterationFunction(array $inputs, int $correctOutput)
|
||||
private function iterationFunction(array $inputs, float $correctOutput): float
|
||||
{
|
||||
$output = $this->perceptron->test($inputs);
|
||||
$output = $this->perceptron->test($inputs)[0];
|
||||
|
||||
$error = $correctOutput - $output;
|
||||
|
||||
157
app/Models/NetworksTraining/MonoLayerPerceptronTraining.php
Normal file
157
app/Models/NetworksTraining/MonoLayerPerceptronTraining.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\NetworksTraining;
|
||||
|
||||
use App\Events\PerceptronTrainingEnded;
|
||||
use App\Models\ActivationsFunctions;
|
||||
use App\Models\Perceptrons\GradientDescentPerceptron;
|
||||
use App\Models\Perceptrons\NetworkPerceptron;
|
||||
use App\Models\Perceptrons\Perceptron;
|
||||
use App\Models\Perceptrons\SimpleBinaryPerceptron2;
|
||||
use App\Models\Perceptrons\SimpleBinaryPerceptron;
|
||||
use App\Services\DatasetReader\IDataSetReader;
|
||||
use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer;
|
||||
use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider;
|
||||
use App\Services\SynapticWeightsProvider\SimpleNetworkWeightsProvider;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class MonoLayerPerceptronTraining extends NetworkTraining
|
||||
{
|
||||
private Perceptron $network;
|
||||
|
||||
private array $labels;
|
||||
|
||||
public ActivationsFunctions $activationFunction = ActivationsFunctions::LINEAR;
|
||||
public ?ActivationsFunctions $presentationLayerActivationFunction = ActivationsFunctions::STEP;
|
||||
|
||||
private float $epochError;
|
||||
|
||||
public function __construct(
|
||||
IDataSetReader $datasetReader,
|
||||
protected float $learningRate,
|
||||
int $maxEpochs,
|
||||
ISynapticWeightsProvider $synapticWeightsProvider,
|
||||
IPerceptronIterationEventBuffer $iterationEventBuffer,
|
||||
string $sessionId,
|
||||
string $trainingId,
|
||||
private float $minError,
|
||||
) {
|
||||
parent::__construct($datasetReader, $maxEpochs, $iterationEventBuffer, $sessionId, $trainingId);
|
||||
$networkWeightsProvider = new SimpleNetworkWeightsProvider($synapticWeightsProvider);
|
||||
$this->network = new NetworkPerceptron(
|
||||
$networkWeightsProvider->generate(
|
||||
$datasetReader->getInputSize(),
|
||||
$datasetReader->getOutputSize(),
|
||||
0, // No hidden layer
|
||||
0, // No hidden layer neurons
|
||||
),
|
||||
$datasetReader->getInputSize(),
|
||||
GradientDescentPerceptron::class, // No hidden layer
|
||||
SimpleBinaryPerceptron2::class,
|
||||
);
|
||||
$this->labels = $datasetReader->getLabels();
|
||||
}
|
||||
|
||||
public function start(): void
|
||||
{
|
||||
$this->epoch = 0;
|
||||
do {
|
||||
$this->epochError = 0;
|
||||
$this->epoch++;
|
||||
|
||||
$inputsForCurrentEpoch = [];
|
||||
|
||||
while ($nextRow = $this->datasetReader->getNextLine()) {
|
||||
$inputsForCurrentEpoch[] = $nextRow;
|
||||
$inputs = array_slice($nextRow, 0, -1);
|
||||
$correctOutput = (int) end($nextRow);
|
||||
|
||||
$iterationError = $this->iterationFunction($inputs, $correctOutput);
|
||||
|
||||
// Synaptic weights correction after each example
|
||||
$synaptic_weights = $this->network->getSynapticWeights();
|
||||
$inputs_with_bias = array_merge([1], $inputs); // Add bias input
|
||||
|
||||
// Updates the weights
|
||||
$this->network->setSynapticWeights(
|
||||
$this->getUpdatedSynapticWeights($synaptic_weights, $iterationError, $inputs_with_bias)
|
||||
);
|
||||
|
||||
// Broadcast the training iteration event
|
||||
$this->addIterationToBuffer(array_sum($iterationError), $this->network->getSynapticWeights());
|
||||
}
|
||||
|
||||
// Calculte the average error for the epoch with the last synaptic weights
|
||||
foreach ($inputsForCurrentEpoch as $inputsWithLabel) {
|
||||
$inputs = array_slice($inputsWithLabel, 0, -1);
|
||||
$correctOutput = (float) end($inputsWithLabel);
|
||||
$iterationError = $this->iterationFunction($inputs, $correctOutput);
|
||||
foreach ($iterationError as $error) {
|
||||
$this->epochError += ($error ** 2) / 2; // Squared error for the example
|
||||
}
|
||||
}
|
||||
$this->epochError /= $this->datasetReader->getEpochExamplesCount(); // Average error for the epoch
|
||||
|
||||
$this->datasetReader->reset(); // Reset the dataset for the next iteration
|
||||
} while ($this->epoch < $this->maxEpochs && ! $this->stopCondition());
|
||||
|
||||
$this->iterationEventBuffer->flush(); // Ensure all iterations are sent to the frontend
|
||||
|
||||
$this->checkPassedMaxIterations($this->epochError);
|
||||
}
|
||||
|
||||
protected function stopCondition(): bool
|
||||
{
|
||||
$condition = $this->epochError <= $this->minError;
|
||||
if ($condition === true) {
|
||||
event(new PerceptronTrainingEnded('Le perceptron à atteint l\'erreur minimale', $this->sessionId, $this->trainingId));
|
||||
}
|
||||
|
||||
return $condition;
|
||||
}
|
||||
|
||||
private function iterationFunction(array $inputs, int $correctOutput): array
|
||||
{
|
||||
$outputs = $this->network->test($inputs);
|
||||
$desiredOutput = $this->getDesiredOutputFromCorrectOutput($correctOutput);
|
||||
|
||||
$errors = [];
|
||||
foreach ($outputs as $index => $output) {
|
||||
$error = $desiredOutput[$index] - $output;
|
||||
$errors[] = $error;
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
private function getUpdatedSynapticWeights(array $synaptic_weights, array $iterationError, array $inputs): array
|
||||
{
|
||||
$updatedWeights = [];
|
||||
foreach ($synaptic_weights[0] as $neuronIndex => $neuronWeights) { // There is only one layer of weights
|
||||
$updatedNeuronWeights = [];
|
||||
foreach ($neuronWeights as $weightIndex => $weight) {
|
||||
$updatedWeight = $weight + ($this->learningRate * $iterationError[$neuronIndex] * $inputs[$weightIndex]);
|
||||
$updatedNeuronWeights[] = $updatedWeight;
|
||||
}
|
||||
$updatedWeights[] = $updatedNeuronWeights;
|
||||
}
|
||||
|
||||
return [$updatedWeights];
|
||||
}
|
||||
|
||||
private function getDesiredOutputFromCorrectOutput(int $correctOutput): array
|
||||
{
|
||||
$desiredOutput = array_fill(0, count($this->labels), -1);
|
||||
$labelIndex = Arr::first(array_keys($this->labels), fn($key) => $this->labels[$key] == $correctOutput);
|
||||
if ($labelIndex !== null) {
|
||||
$desiredOutput[$labelIndex] = 1;
|
||||
}
|
||||
|
||||
return $desiredOutput;
|
||||
}
|
||||
|
||||
public function getSynapticWeights(): array
|
||||
{
|
||||
return [[$this->network->getSynapticWeights()]];
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace App\Models\NetworksTraining;
|
||||
|
||||
use App\Events\PerceptronTrainingEnded;
|
||||
use App\Models\ActivationsFunctions;
|
||||
use App\Services\DatasetReader\IDataSetReader;
|
||||
use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer;
|
||||
|
||||
@@ -12,23 +13,25 @@ abstract class NetworkTraining
|
||||
|
||||
/**
|
||||
* @abstract
|
||||
* @var ActivationsFunctions
|
||||
*/
|
||||
public ActivationsFunctions $activationFunction;
|
||||
|
||||
public ?ActivationsFunctions $presentationLayerActivationFunction = null;
|
||||
|
||||
public function __construct(
|
||||
protected IDataSetReader $datasetReader,
|
||||
protected int $maxEpochs,
|
||||
protected IPerceptronIterationEventBuffer $iterationEventBuffer,
|
||||
protected string $sessionId,
|
||||
protected string $trainingId,
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
abstract public function start(): void;
|
||||
|
||||
abstract public function start() : void;
|
||||
abstract protected function stopCondition(): bool;
|
||||
|
||||
protected function checkPassedMaxIterations(?float $finalError) {
|
||||
protected function checkPassedMaxIterations(?float $finalError)
|
||||
{
|
||||
if ($this->epoch >= $this->maxEpochs) {
|
||||
$message = 'Le nombre maximal d\'epoch a été atteint';
|
||||
if ($finalError) {
|
||||
@@ -39,7 +42,8 @@ abstract class NetworkTraining
|
||||
}
|
||||
}
|
||||
|
||||
protected function addIterationToBuffer(float $error, array $synapticWeights) {
|
||||
protected function addIterationToBuffer(float $error, array $synapticWeights)
|
||||
{
|
||||
$this->iterationEventBuffer->addIteration($this->epoch, $this->datasetReader->getLastReadLineIndex(), $error, $synapticWeights);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace App\Models\NetworksTraining;
|
||||
|
||||
use App\Events\PerceptronTrainingEnded;
|
||||
use App\Models\ActivationsFunctions;
|
||||
use App\Models\Perceptrons\Perceptron;
|
||||
use App\Models\Perceptrons\SimpleBinaryPerceptron;
|
||||
use App\Services\DatasetReader\IDataSetReader;
|
||||
use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer;
|
||||
use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider;
|
||||
@@ -10,6 +13,7 @@ use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider;
|
||||
class SimpleBinaryPerceptronTraining extends NetworkTraining
|
||||
{
|
||||
private Perceptron $perceptron;
|
||||
|
||||
private int $iterationErrorCounter = 0;
|
||||
|
||||
public ActivationsFunctions $activationFunction = ActivationsFunctions::STEP;
|
||||
@@ -48,7 +52,7 @@ class SimpleBinaryPerceptronTraining extends NetworkTraining
|
||||
$this->addIterationToBuffer($error, [[$this->perceptron->getSynapticWeights()]]);
|
||||
}
|
||||
$this->datasetReader->reset(); // Reset the dataset for the next iteration
|
||||
} while ($this->epoch < $this->maxEpochs && !$this->stopCondition());
|
||||
} while ($this->epoch < $this->maxEpochs && ! $this->stopCondition());
|
||||
|
||||
$this->iterationEventBuffer->flush(); // Ensure all iterations are sent to the frontend
|
||||
|
||||
@@ -61,12 +65,13 @@ class SimpleBinaryPerceptronTraining extends NetworkTraining
|
||||
if ($condition === true) {
|
||||
event(new PerceptronTrainingEnded('Le perceptron ne commet plus d\'erreurs sur aucune des données', $this->sessionId, $this->trainingId));
|
||||
}
|
||||
|
||||
return $this->iterationErrorCounter == 0;
|
||||
}
|
||||
|
||||
private function iterationFunction(array $inputs, int $correctOutput)
|
||||
{
|
||||
$output = $this->perceptron->test($inputs);
|
||||
$output = $this->perceptron->test($inputs)[0];
|
||||
|
||||
$error = $correctOutput - $output;
|
||||
if (abs($error) > $this::MIN_ERROR) {
|
||||
@@ -76,9 +81,10 @@ class SimpleBinaryPerceptronTraining extends NetworkTraining
|
||||
if ($error !== 0) { // Update synaptic weights if needed
|
||||
$synaptic_weights = $this->perceptron->getSynapticWeights();
|
||||
$inputs_with_bias = array_merge([1], $inputs); // Add bias input
|
||||
$new_weights = array_map(fn($weight, $input) => $weight + $this->learningRate * $error * $input, $synaptic_weights, $inputs_with_bias);
|
||||
$new_weights = array_map(fn ($weight, $input) => $weight + $this->learningRate * $error * $input, $synaptic_weights, $inputs_with_bias);
|
||||
$this->perceptron->setSynapticWeights($new_weights);
|
||||
}
|
||||
|
||||
return $error;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class GradientDescentPerceptron extends Perceptron {
|
||||
namespace App\Models\Perceptrons;
|
||||
|
||||
class GradientDescentPerceptron extends Perceptron
|
||||
{
|
||||
public function __construct(
|
||||
array $synaptic_weights,
|
||||
) {
|
||||
@@ -14,5 +14,4 @@ class GradientDescentPerceptron extends Perceptron {
|
||||
{
|
||||
return $weighted_sum;
|
||||
}
|
||||
|
||||
}
|
||||
26
app/Models/Perceptrons/InputNeuron.php
Normal file
26
app/Models/Perceptrons/InputNeuron.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Perceptrons;
|
||||
|
||||
class InputNeuron extends Perceptron
|
||||
{
|
||||
public function __construct(
|
||||
) {
|
||||
parent::__construct([]);
|
||||
}
|
||||
|
||||
public function setInput(float $input): void
|
||||
{
|
||||
$this->input = $input;
|
||||
}
|
||||
|
||||
public function test(array $inputs): array
|
||||
{
|
||||
return [$this->input];
|
||||
}
|
||||
|
||||
public function activationFunction(float $input): float
|
||||
{
|
||||
return $input; // Identity function for input neurons
|
||||
}
|
||||
}
|
||||
76
app/Models/Perceptrons/NetworkPerceptron.php
Normal file
76
app/Models/Perceptrons/NetworkPerceptron.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Perceptrons;
|
||||
|
||||
class NetworkPerceptron extends Perceptron
|
||||
{
|
||||
public array $network = [];
|
||||
|
||||
public function __construct(
|
||||
private array $synaptic_weights,
|
||||
private int $inputLayerNeuronsCount,
|
||||
private string $hiddenLayerNeuronClass,
|
||||
private string $outputLayerNeuronClass,
|
||||
) {
|
||||
parent::__construct($synaptic_weights);
|
||||
$this->initializeNetwork($synaptic_weights);
|
||||
}
|
||||
|
||||
private function initializeNetwork(array $synaptic_weights): void
|
||||
{
|
||||
// Input Layer
|
||||
$this->network[0] = [];
|
||||
foreach (range(0, $this->inputLayerNeuronsCount - 1) as $i) {
|
||||
$this->network[0][] = new InputNeuron();
|
||||
}
|
||||
|
||||
// Hidden Layer
|
||||
for ($layerIndex = 0; $layerIndex < count($synaptic_weights) - 2; $layerIndex++) {
|
||||
$this->network[$layerIndex + 1] = [];
|
||||
|
||||
foreach ($synaptic_weights[$layerIndex] as $neuronWeights) {
|
||||
$this->network[$layerIndex + 1][] = new $this->hiddenLayerNeuronClass($neuronWeights);
|
||||
}
|
||||
}
|
||||
|
||||
// Output Layer
|
||||
$outputLayer = $synaptic_weights[count($synaptic_weights) - 1];
|
||||
$this->network[count($synaptic_weights)] = [];
|
||||
|
||||
foreach ($outputLayer as $neuronWeights) {
|
||||
$this->network[count($synaptic_weights)][] = new $this->outputLayerNeuronClass($neuronWeights);
|
||||
}
|
||||
}
|
||||
|
||||
public function test(array $inputs): array
|
||||
{
|
||||
// Set the inputs for the input layer
|
||||
foreach ($this->network[0] as $index => $inputNeuron) {
|
||||
$inputNeuron->setInput($inputs[$index]);
|
||||
}
|
||||
|
||||
// Pass through the hidden and output layers
|
||||
$output = [];
|
||||
for ($layerIndex = 0; $layerIndex < count($this->network); $layerIndex++) {
|
||||
$lastLayerOutput = $output;
|
||||
$output = [];
|
||||
foreach ($this->network[$layerIndex] as $neuron) {
|
||||
$output[] = $neuron->test($lastLayerOutput)[0];
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
public function activationFunction(float $weighted_sum): float
|
||||
{
|
||||
return $weighted_sum;
|
||||
}
|
||||
|
||||
public function setSynapticWeights(array $synaptic_weights): void
|
||||
{
|
||||
parent::setSynapticWeights($synaptic_weights);
|
||||
$this->network = [];
|
||||
$this->initializeNetwork($synaptic_weights);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace App\Models\Perceptrons;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
// use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
abstract class Perceptron extends Model
|
||||
abstract class Perceptron
|
||||
{
|
||||
public function __construct(
|
||||
private array $synaptic_weights,
|
||||
@@ -12,16 +12,17 @@ abstract class Perceptron extends Model
|
||||
$this->synaptic_weights = $synaptic_weights;
|
||||
}
|
||||
|
||||
public function test(array $inputs): float
|
||||
public function test(array $inputs): array
|
||||
{
|
||||
$inputs = array_merge([1], $inputs); // Add bias input
|
||||
|
||||
if (count($inputs) !== count($this->synaptic_weights)) { // Check
|
||||
throw new \InvalidArgumentException("Number of inputs must match number of synaptic weights.");
|
||||
throw new \InvalidArgumentException('Number of inputs must match number of synaptic weights.');
|
||||
}
|
||||
|
||||
$weighted_sum = array_sum(array_map(fn($input, $weight) => $input * $weight, $inputs, $this->synaptic_weights));
|
||||
return $this->activationFunction($weighted_sum);
|
||||
$weighted_sum = array_sum(array_map(fn ($input, $weight) => $input * $weight, $inputs, $this->synaptic_weights));
|
||||
|
||||
return [$this->activationFunction($weighted_sum)];
|
||||
}
|
||||
|
||||
abstract public function activationFunction(float $weighted_sum): float;
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class SimpleBinaryPerceptron extends Perceptron {
|
||||
namespace App\Models\Perceptrons;
|
||||
|
||||
class SimpleBinaryPerceptron extends Perceptron
|
||||
{
|
||||
public function __construct(
|
||||
array $synaptic_weights,
|
||||
) {
|
||||
@@ -14,5 +14,4 @@ class SimpleBinaryPerceptron extends Perceptron {
|
||||
{
|
||||
return $weighted_sum >= 0.0 ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
}
|
||||
18
app/Models/Perceptrons/SimpleBinaryPerceptron2.php
Normal file
18
app/Models/Perceptrons/SimpleBinaryPerceptron2.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Perceptrons;
|
||||
|
||||
class SimpleBinaryPerceptron2 extends Perceptron
|
||||
{
|
||||
public function __construct(
|
||||
array $synaptic_weights,
|
||||
) {
|
||||
parent::__construct($synaptic_weights);
|
||||
}
|
||||
|
||||
public function activationFunction(float $weighted_sum): float
|
||||
{
|
||||
// return $weighted_sum >= 0.0 ? 1.0 : -1.0;
|
||||
return $weighted_sum;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ use Carbon\CarbonImmutable;
|
||||
use Illuminate\Support\Facades\Date;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@ class InitialSynapticWeightsProvider extends ServiceProvider
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->singleton(ISynapticWeightsProvider::class, function ($app) {
|
||||
return new RandomSynapticWeights();
|
||||
return new RandomSynapticWeights;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
class CsvReader {
|
||||
class CsvReader
|
||||
{
|
||||
private $file;
|
||||
// private array $headers;
|
||||
|
||||
@@ -10,11 +11,10 @@ class CsvReader {
|
||||
|
||||
public function __construct(
|
||||
public string $filename,
|
||||
)
|
||||
{
|
||||
$this->file = fopen($filename, "r");
|
||||
if (!$this->file) {
|
||||
throw new \RuntimeException("Failed to open file: " . $filename);
|
||||
) {
|
||||
$this->file = fopen($filename, 'r');
|
||||
if (! $this->file) {
|
||||
throw new \RuntimeException('Failed to open file: '.$filename);
|
||||
}
|
||||
|
||||
// $this->headers = $this->readNextLine();
|
||||
@@ -22,9 +22,10 @@ class CsvReader {
|
||||
|
||||
public function readNextLine(): ?array
|
||||
{
|
||||
if (($data = fgetcsv($this->file, 1000, ",")) !== FALSE) {
|
||||
if (($data = fgetcsv($this->file, 1000, ',')) !== false) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
return null; // End of file or error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,19 @@
|
||||
|
||||
namespace App\Services\DatasetReader;
|
||||
|
||||
interface IDataSetReader {
|
||||
public function getNextLine(): array | null;
|
||||
interface IDataSetReader
|
||||
{
|
||||
public function getNextLine(): ?array;
|
||||
|
||||
public function getInputSize(): int;
|
||||
|
||||
public function getOutputSize(): int;
|
||||
|
||||
public function getLabels(): array;
|
||||
|
||||
public function reset(): void;
|
||||
|
||||
public function getLastReadLineIndex(): int;
|
||||
|
||||
public function getEpochExamplesCount(): int;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ namespace App\Services\DatasetReader;
|
||||
|
||||
use App\Services\CsvReader;
|
||||
|
||||
class LinearOrderDataSetReader implements IDataSetReader {
|
||||
class LinearOrderDataSetReader implements IDataSetReader
|
||||
{
|
||||
public array $lines = [];
|
||||
|
||||
private array $currentLines = [];
|
||||
|
||||
private int $lastReadLineIndex = -1;
|
||||
@@ -27,17 +29,13 @@ class LinearOrderDataSetReader implements IDataSetReader {
|
||||
$newLine[] = (float) $value;
|
||||
}
|
||||
|
||||
// if the dataset is for regression, we add a fake label of 0
|
||||
if (count($newLine) === 2) {
|
||||
$newLine[] = 0.0;
|
||||
}
|
||||
|
||||
$this->lines[] = $newLine;
|
||||
}
|
||||
}
|
||||
|
||||
public function getNextLine(): array | null {
|
||||
if (!isset($this->currentLines[0])) {
|
||||
public function getNextLine(): ?array
|
||||
{
|
||||
if (! isset($this->currentLines[0])) {
|
||||
return null; // No more lines to read
|
||||
}
|
||||
|
||||
@@ -51,6 +49,19 @@ class LinearOrderDataSetReader implements IDataSetReader {
|
||||
return count($this->lines[0]) - 1; // Don't count the label
|
||||
}
|
||||
|
||||
public function getOutputSize(): int
|
||||
{
|
||||
// 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;
|
||||
|
||||
@@ -4,8 +4,10 @@ namespace App\Services\DatasetReader;
|
||||
|
||||
use App\Services\CsvReader;
|
||||
|
||||
class RandomOrderDataSetReaders implements IDataSetReader {
|
||||
class RandomOrderDataSetReader implements IDataSetReader
|
||||
{
|
||||
public array $lines = [];
|
||||
|
||||
private array $currentLines = [];
|
||||
|
||||
private int $lastReadLineIndex = -1;
|
||||
@@ -27,16 +29,11 @@ class RandomOrderDataSetReaders implements IDataSetReader {
|
||||
$newLine[] = (float) $value;
|
||||
}
|
||||
|
||||
// if the dataset is for regression, we add a fake label of 0
|
||||
if (count($newLine) === 2) {
|
||||
$newLine[] = 0.0;
|
||||
}
|
||||
|
||||
$this->lines[] = $newLine;
|
||||
}
|
||||
}
|
||||
|
||||
public function getNextLine(): array | null
|
||||
public function getNextLine(): ?array
|
||||
{
|
||||
if (empty($this->currentLines)) {
|
||||
return null; // No more lines to read
|
||||
@@ -58,6 +55,19 @@ class RandomOrderDataSetReaders implements IDataSetReader {
|
||||
return count($this->lines[0]) - 1; // Don't count the label
|
||||
}
|
||||
|
||||
public function getOutputSize(): int
|
||||
{
|
||||
// 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;
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace App\Services\IterationEventBuffer;
|
||||
|
||||
interface IPerceptronIterationEventBuffer {
|
||||
interface IPerceptronIterationEventBuffer
|
||||
{
|
||||
public function flush(): void;
|
||||
|
||||
public function flush(): void ;
|
||||
|
||||
public function addIteration(int $iteration, int $exampleIndex, float $error, array $synaptic_weights): void ;
|
||||
public function addIteration(int $iteration, int $exampleIndex, float $error, array $synaptic_weights): void;
|
||||
}
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
|
||||
namespace App\Services\IterationEventBuffer;
|
||||
|
||||
class PerceptronIterationEventBuffer implements IPerceptronIterationEventBuffer {
|
||||
class PerceptronIterationEventBuffer implements IPerceptronIterationEventBuffer
|
||||
{
|
||||
private $data;
|
||||
|
||||
private int $nextSizeIncreaseThreshold;
|
||||
|
||||
private int $underSizeIncreaseCount = 0;
|
||||
|
||||
public function __construct(
|
||||
@@ -17,24 +20,25 @@ class PerceptronIterationEventBuffer implements IPerceptronIterationEventBuffer
|
||||
$this->nextSizeIncreaseThreshold = $sizeIncreaseStart;
|
||||
}
|
||||
|
||||
public function flush(): void {
|
||||
public function flush(): void
|
||||
{
|
||||
event(new \App\Events\PerceptronTrainingIteration($this->data, $this->sessionId, $this->trainingId));
|
||||
$this->data = [];
|
||||
}
|
||||
|
||||
public function addIteration(int $epoch, int $exampleIndex, float $error, array $synaptic_weights): void {
|
||||
public function addIteration(int $epoch, int $exampleIndex, float $error, array $synaptic_weights): void
|
||||
{
|
||||
$this->data[] = [
|
||||
"epoch" => $epoch,
|
||||
"exampleIndex" => $exampleIndex,
|
||||
"error" => $error,
|
||||
"weights" => $synaptic_weights,
|
||||
'epoch' => $epoch,
|
||||
'exampleIndex' => $exampleIndex,
|
||||
'error' => $error,
|
||||
'weights' => $synaptic_weights,
|
||||
];
|
||||
|
||||
if ($this->underSizeIncreaseCount <= $this->sizeIncreaseStart) { // We can still send a single date because we are under the increase start threshold
|
||||
$this->underSizeIncreaseCount++;
|
||||
$this->flush();
|
||||
}
|
||||
else if (count($this->data) >= $this->nextSizeIncreaseThreshold) {
|
||||
} elseif (count($this->data) >= $this->nextSizeIncreaseThreshold) {
|
||||
$this->flush();
|
||||
$this->nextSizeIncreaseThreshold *= $this->sizeIncreaseFactor;
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
namespace App\Services\IterationEventBuffer;
|
||||
|
||||
class PerceptronLimitedEpochEventBuffer implements IPerceptronIterationEventBuffer {
|
||||
class PerceptronLimitedEpochEventBuffer implements IPerceptronIterationEventBuffer
|
||||
{
|
||||
private array $data;
|
||||
|
||||
private int $underSizeIncreaseCount = 0;
|
||||
|
||||
public function __construct(
|
||||
@@ -15,33 +17,27 @@ class PerceptronLimitedEpochEventBuffer implements IPerceptronIterationEventBuff
|
||||
$this->data = [];
|
||||
}
|
||||
|
||||
public function flush(): void {
|
||||
public function flush(): void
|
||||
{
|
||||
event(new \App\Events\PerceptronTrainingIteration($this->data, $this->sessionId, $this->trainingId));
|
||||
$this->data = [];
|
||||
}
|
||||
|
||||
public function addIteration(int $epoch, int $exampleIndex, float $error, array $synaptic_weights): void {
|
||||
public function addIteration(int $epoch, int $exampleIndex, float $error, array $synaptic_weights): void
|
||||
{
|
||||
$newData = [
|
||||
"epoch" => $epoch,
|
||||
"exampleIndex" => $exampleIndex,
|
||||
"error" => $error,
|
||||
"weights" => $synaptic_weights,
|
||||
'epoch' => $epoch,
|
||||
'exampleIndex' => $exampleIndex,
|
||||
'error' => $error,
|
||||
'weights' => $synaptic_weights,
|
||||
];
|
||||
|
||||
if ($this->underSizeIncreaseCount <= $this->sizeIncreaseStart) { // Special case where we need to send each iteration separately
|
||||
$this->underSizeIncreaseCount++;
|
||||
$this->data[] = $newData;
|
||||
$this->flush();
|
||||
return;
|
||||
}
|
||||
|
||||
$lastEpoch = $this->data[0]['epoch'] ?? null;
|
||||
if ($this->data && $lastEpoch !== $epoch) { // Current Epoch has changed from the last one
|
||||
if ($lastEpoch % $this->epochInterval === 0) { // The last epoch need to be sent
|
||||
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 = [];
|
||||
} else {
|
||||
$this->data = []; // We clear the data without sending it as we are saving the next epoch data
|
||||
}
|
||||
|
||||
$lastEpoch = $epoch;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\SynapticWeightsProvider;
|
||||
|
||||
interface INetworkSynapticWeightsProvider
|
||||
{
|
||||
public function generate(int $input_size, int $output_size, int $hidden_layers_count, int $hidden_layers_neurons_count): array;
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Services\SynapticWeightsProvider;
|
||||
|
||||
interface ISynapticWeightsProvider {
|
||||
interface ISynapticWeightsProvider
|
||||
{
|
||||
public function generate(int $input_size): array;
|
||||
}
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
|
||||
namespace App\Services\SynapticWeightsProvider;
|
||||
|
||||
class RandomSynapticWeights implements ISynapticWeightsProvider {
|
||||
class RandomSynapticWeights implements ISynapticWeightsProvider
|
||||
{
|
||||
public function generate(int $input_size): array
|
||||
{
|
||||
$weights = [];
|
||||
for ($i = 0; $i < $input_size + 1; $i++) { // +1 for bias weight
|
||||
$weights[] = rand(-100, 100) / 100; // Random weights between -1 and 1
|
||||
}
|
||||
|
||||
return $weights;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\SynapticWeightsProvider;
|
||||
|
||||
use App\Services\SynapticWeightsProvider\INetworkSynapticWeightsProvider;
|
||||
|
||||
class SimpleNetworkWeightsProvider implements INetworkSynapticWeightsProvider
|
||||
{
|
||||
public function __construct(
|
||||
private ISynapticWeightsProvider $synapticWeightsProvider,
|
||||
) {
|
||||
}
|
||||
|
||||
public function generate(int $input_size, int $output_size, int $hidden_layers_count, int $hidden_layers_neurons_count): array
|
||||
{
|
||||
$synaptic_weights = [];
|
||||
$lastLayerSize = $input_size;
|
||||
|
||||
// Generate Hidden Layer weights
|
||||
for ($hiddenLayerNeuronIndex = 0; $hiddenLayerNeuronIndex < $hidden_layers_count; $hiddenLayerNeuronIndex++) {
|
||||
for ($neuronIndex = 0; $neuronIndex < $hidden_layers_neurons_count; $neuronIndex++) {
|
||||
$synaptic_weights[] = $this->synapticWeightsProvider->generate($lastLayerSize);
|
||||
}
|
||||
$lastLayerSize = $hidden_layers_neurons_count;
|
||||
}
|
||||
|
||||
// Generate Output Layer weights
|
||||
$synaptic_weights[] = [];
|
||||
for ($outputNeuronIndex = 0; $outputNeuronIndex < $output_size; $outputNeuronIndex++) {
|
||||
$synaptic_weights[count($synaptic_weights) -1][] = $this->synapticWeightsProvider->generate($lastLayerSize);
|
||||
}
|
||||
|
||||
return $synaptic_weights;
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,15 @@
|
||||
|
||||
namespace App\Services\SynapticWeightsProvider;
|
||||
|
||||
class ZeroSynapticWeights implements ISynapticWeightsProvider {
|
||||
class ZeroSynapticWeights implements ISynapticWeightsProvider
|
||||
{
|
||||
public function generate(int $input_size): array
|
||||
{
|
||||
$weights = [];
|
||||
for ($i = 0; $i < $input_size + 1; $i++) { // +1 for bias weight
|
||||
$weights[] = 0; // Zero weights
|
||||
}
|
||||
|
||||
return $weights;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"laravel/wayfinder": "^0.1.9"
|
||||
},
|
||||
"require-dev": {
|
||||
"brianium/paratest": "^7.8",
|
||||
"fakerphp/faker": "^1.23",
|
||||
"laravel/pail": "^1.2.2",
|
||||
"laravel/pint": "^1.24",
|
||||
@@ -50,7 +51,7 @@
|
||||
],
|
||||
"dev": [
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1 --timeout=0\" \"php artisan pail --timeout=0\" \"npm run dev\" \"php artisan reverb:start --debug\" --names=server,queue,logs,vite,reverb --kill-others"
|
||||
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74,#79dff0\" \"php artisan serve\" \"php artisan queue:listen --tries=1 --timeout=0\" \"php artisan pail --timeout=0\" \"npm run dev\" \"php artisan reverb:start --debug\" --names=server,queue,logs,vite,reverb --kill-others"
|
||||
],
|
||||
"dev:ssr": [
|
||||
"npm run build:ssr",
|
||||
|
||||
216
composer.lock
generated
216
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "a72ab6feeee69457d0085c4a5e4580f7",
|
||||
"content-hash": "93a44ad3435bb0cb19a8bd3b2b700b4f",
|
||||
"packages": [
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
@@ -7544,6 +7544,99 @@
|
||||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "brianium/paratest",
|
||||
"version": "v7.8.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paratestphp/paratest.git",
|
||||
"reference": "9b324c8fc319cf9728b581c7a90e1c8f6361c5e5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paratestphp/paratest/zipball/9b324c8fc319cf9728b581c7a90e1c8f6361c5e5",
|
||||
"reference": "9b324c8fc319cf9728b581c7a90e1c8f6361c5e5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"ext-pcre": "*",
|
||||
"ext-reflection": "*",
|
||||
"ext-simplexml": "*",
|
||||
"fidry/cpu-core-counter": "^1.3.0",
|
||||
"jean85/pretty-package-versions": "^2.1.1",
|
||||
"php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
|
||||
"phpunit/php-code-coverage": "^11.0.12",
|
||||
"phpunit/php-file-iterator": "^5.1.0",
|
||||
"phpunit/php-timer": "^7.0.1",
|
||||
"phpunit/phpunit": "^11.5.46",
|
||||
"sebastian/environment": "^7.2.1",
|
||||
"symfony/console": "^6.4.22 || ^7.3.4 || ^8.0.3",
|
||||
"symfony/process": "^6.4.20 || ^7.3.4 || ^8.0.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^12.0.0",
|
||||
"ext-pcov": "*",
|
||||
"ext-posix": "*",
|
||||
"phpstan/phpstan": "^2.1.33",
|
||||
"phpstan/phpstan-deprecation-rules": "^2.0.3",
|
||||
"phpstan/phpstan-phpunit": "^2.0.11",
|
||||
"phpstan/phpstan-strict-rules": "^2.0.7",
|
||||
"squizlabs/php_codesniffer": "^3.13.5",
|
||||
"symfony/filesystem": "^6.4.13 || ^7.3.2 || ^8.0.1"
|
||||
},
|
||||
"bin": [
|
||||
"bin/paratest",
|
||||
"bin/paratest_for_phpstorm"
|
||||
],
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"ParaTest\\": [
|
||||
"src/"
|
||||
]
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Brian Scaturro",
|
||||
"email": "scaturrob@gmail.com",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "Filippo Tessarotto",
|
||||
"email": "zoeslam@gmail.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Parallel testing for PHP",
|
||||
"homepage": "https://github.com/paratestphp/paratest",
|
||||
"keywords": [
|
||||
"concurrent",
|
||||
"parallel",
|
||||
"phpunit",
|
||||
"testing"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/paratestphp/paratest/issues",
|
||||
"source": "https://github.com/paratestphp/paratest/tree/v7.8.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sponsors/Slamdunk",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://paypal.me/filippotessarotto",
|
||||
"type": "paypal"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-08T08:02:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fakerphp/faker",
|
||||
"version": "v1.24.1",
|
||||
@@ -7607,6 +7700,67 @@
|
||||
},
|
||||
"time": "2024-11-21T13:46:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fidry/cpu-core-counter",
|
||||
"version": "1.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/theofidry/cpu-core-counter.git",
|
||||
"reference": "db9508f7b1474469d9d3c53b86f817e344732678"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678",
|
||||
"reference": "db9508f7b1474469d9d3c53b86f817e344732678",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"fidry/makefile": "^0.2.0",
|
||||
"fidry/php-cs-fixer-config": "^1.1.2",
|
||||
"phpstan/extension-installer": "^1.2.0",
|
||||
"phpstan/phpstan": "^2.0",
|
||||
"phpstan/phpstan-deprecation-rules": "^2.0.0",
|
||||
"phpstan/phpstan-phpunit": "^2.0",
|
||||
"phpstan/phpstan-strict-rules": "^2.0",
|
||||
"phpunit/phpunit": "^8.5.31 || ^9.5.26",
|
||||
"webmozarts/strict-phpunit": "^7.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Fidry\\CpuCoreCounter\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Théo FIDRY",
|
||||
"email": "theo.fidry@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Tiny utility to get the number of CPU cores.",
|
||||
"keywords": [
|
||||
"CPU",
|
||||
"core"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/theofidry/cpu-core-counter/issues",
|
||||
"source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/theofidry",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-14T07:29:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "filp/whoops",
|
||||
"version": "2.18.4",
|
||||
@@ -7729,6 +7883,66 @@
|
||||
},
|
||||
"time": "2025-04-30T06:54:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "jean85/pretty-package-versions",
|
||||
"version": "2.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Jean85/pretty-package-versions.git",
|
||||
"reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a",
|
||||
"reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"composer-runtime-api": "^2.1.0",
|
||||
"php": "^7.4|^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^3.2",
|
||||
"jean85/composer-provided-replaced-stub-package": "^1.0",
|
||||
"phpstan/phpstan": "^2.0",
|
||||
"phpunit/phpunit": "^7.5|^8.5|^9.6",
|
||||
"rector/rector": "^2.0",
|
||||
"vimeo/psalm": "^4.3 || ^5.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Jean85\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Alessandro Lai",
|
||||
"email": "alessandro.lai85@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "A library to get pretty versions strings of installed dependencies",
|
||||
"keywords": [
|
||||
"composer",
|
||||
"package",
|
||||
"release",
|
||||
"versions"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Jean85/pretty-package-versions/issues",
|
||||
"source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1"
|
||||
},
|
||||
"time": "2025-03-19T14:43:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/pail",
|
||||
"version": "v1.2.6",
|
||||
|
||||
@@ -7,13 +7,13 @@ return [
|
||||
* Beyond this number of iterations, the broadcast will be splitted every x iterations,
|
||||
* x is limited_broadcast_number
|
||||
*/
|
||||
'limited_broadcast_iterations' => 200,
|
||||
'limited_broadcast_iterations' => 100,
|
||||
|
||||
/**
|
||||
* How much broadcasts is sent when in limmited broadcast mode
|
||||
*/
|
||||
'limited_broadcast_number' => 200,
|
||||
/**
|
||||
* How much broadcasts is sent when in limmited broadcast mode
|
||||
*/
|
||||
'limited_broadcast_number' => 100,
|
||||
|
||||
'broadcast_iteration_size' => 75,
|
||||
'broadcast_iteration_size' => 75,
|
||||
|
||||
];
|
||||
|
||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -14,7 +14,7 @@
|
||||
"laravel-vite-plugin": "^2.0.0",
|
||||
"lucide-vue-next": "^0.468.0",
|
||||
"radix-ui": "^1.4.3",
|
||||
"reka-ui": "^2.9.0",
|
||||
"reka-ui": "^2.9.2",
|
||||
"tailwind-merge": "^3.2.0",
|
||||
"tailwindcss": "^4.1.1",
|
||||
"tw-animate-css": "^1.2.5",
|
||||
@@ -7386,9 +7386,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/reka-ui": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.9.0.tgz",
|
||||
"integrity": "sha512-5dpp80u109iLTbRBu+jhAk8R/877/JN20gYGjb3GsuAgS7E/5QTX5ZxuzWtZAVbChBDYDpXc8pkaQAFpa6s+4w==",
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.9.2.tgz",
|
||||
"integrity": "sha512-/t4e6y1hcG+uDuRfpg6tbMz3uUEvRzNco6NeYTufoJeUghy5Iosxos5YL/p+ieAsid84sdMX9OrgDqpEuCJhBw==",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
"@floating-ui/vue": "^1.1.6",
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
"laravel-vite-plugin": "^2.0.0",
|
||||
"lucide-vue-next": "^0.468.0",
|
||||
"radix-ui": "^1.4.3",
|
||||
"reka-ui": "^2.9.0",
|
||||
"reka-ui": "^2.9.2",
|
||||
"tailwind-merge": "^3.2.0",
|
||||
"tailwindcss": "^4.1.1",
|
||||
"tw-animate-css": "^1.2.5",
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
<testsuite name="Unit">
|
||||
<directory>tests/Unit</directory>
|
||||
</testsuite>
|
||||
<testsuite name="Feature">
|
||||
<!-- <testsuite name="Feature">
|
||||
<directory>tests/Feature</directory>
|
||||
</testsuite>
|
||||
</testsuite> -->
|
||||
</testsuites>
|
||||
<source>
|
||||
<include>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 46 KiB |
@@ -1,150 +1,150 @@
|
||||
2.8,1.9,-1,-1,1
|
||||
2.9,1.8,-1,-1,1
|
||||
2.2,1.5,-1,-1,1
|
||||
1.3,5.6,-1,1,-1
|
||||
-1.2,1.6,1,-1,-1
|
||||
2.5,1.7,-1,-1,1
|
||||
3.1,2.3,-1,-1,1
|
||||
2.8,2.4,-1,-1,1
|
||||
-1.2,1.5,1,-1,-1
|
||||
1.4,6.1,-1,1,-1
|
||||
-1.2,1.3,1,-1,-1
|
||||
3.1,1.8,-1,-1,1
|
||||
3.3,2.5,-1,-1,1
|
||||
3.4,2.3,-1,-1,1
|
||||
1.5,5.9,-1,1,-1
|
||||
1.3,5.6,-1,1,-1
|
||||
-1.2,1,1,-1,-1
|
||||
3,2.1,-1,-1,1
|
||||
1.4,5.2,-1,1,-1
|
||||
1.2,5.5,-1,1,-1
|
||||
1.4,6.7,-1,1,-1
|
||||
-1.3,1.7,1,-1,-1
|
||||
1,5.7,-1,1,-1
|
||||
2.6,1.4,-1,-1,1
|
||||
1.6,6,-1,1,-1
|
||||
1.2,5.8,-1,1,-1
|
||||
1.4,6.1,-1,1,-1
|
||||
1.3,5.7,-1,1,-1
|
||||
-1.2,1.4,1,-1,-1
|
||||
1,5.5,-1,1,-1
|
||||
3.1,2.1,-1,-1,1
|
||||
-1.4,1.7,1,-1,-1
|
||||
1.6,6.3,-1,1,-1
|
||||
-1.2,1.4,1,-1,-1
|
||||
2.7,1.9,-1,-1,1
|
||||
1.1,5.5,-1,1,-1
|
||||
3,1.8,-1,-1,1
|
||||
3.8,2,-1,-1,1
|
||||
2.8,2.1,-1,-1,1
|
||||
1,5,-1,1,-1
|
||||
2.8,2,-1,-1,1
|
||||
-1.2,1.6,1,-1,-1
|
||||
1.3,6.6,-1,1,-1
|
||||
1.6,6,-1,1,-1
|
||||
3,1.6,-1,-1,1
|
||||
-1.1,1.1,1,-1,-1
|
||||
2.9,1.8,-1,-1,1
|
||||
3,1.8,-1,-1,1
|
||||
-1.2,1.4,1,-1,-1
|
||||
1,5.8,-1,1,-1
|
||||
1.2,5.7,-1,1,-1
|
||||
2.8,1.5,-1,-1,1
|
||||
-1.4,1.6,1,-1,-1
|
||||
-1.2,1.4,1,-1,-1
|
||||
2.8,2.2,-1,-1,1
|
||||
-1.5,1.7,1,-1,-1
|
||||
1,4.9,-1,1,-1
|
||||
1.3,5.7,-1,1,-1
|
||||
3.2,2.3,-1,-1,1
|
||||
-1.3,1.5,1,-1,-1
|
||||
-1.2,1.2,1,-1,-1
|
||||
-1.2,1.6,1,-1,-1
|
||||
3,2.1,-1,-1,1
|
||||
-1.4,1.5,1,-1,-1
|
||||
1.3,5.5,-1,1,-1
|
||||
3.3,2.5,-1,-1,1
|
||||
3.1,2.4,-1,-1,1
|
||||
1.8,5.9,-1,1,-1
|
||||
2.7,1.9,-1,-1,1
|
||||
1.5,6.3,-1,1,-1
|
||||
-1.3,1.4,1,-1,-1
|
||||
1,5,-1,1,-1
|
||||
3.2,2.3,-1,-1,1
|
||||
-1.3,1.4,1,-1,-1
|
||||
3,2.1,-1,-1,1
|
||||
3.2,2,-1,-1,1
|
||||
1.3,6.3,-1,1,-1
|
||||
1.4,7,-1,1,-1
|
||||
-1.2,1.7,1,-1,-1
|
||||
1.4,6.6,-1,1,-1
|
||||
1.2,5.8,-1,1,-1
|
||||
3,2,-1,-1,1
|
||||
-1.2,1.6,1,-1,-1
|
||||
1.5,6.2,-1,1,-1
|
||||
1.5,6.4,-1,1,-1
|
||||
-1.2,1.9,1,-1,-1
|
||||
3.2,2.3,-1,-1,1
|
||||
-1.2,1.5,1,-1,-1
|
||||
1.5,5.4,-1,1,-1
|
||||
1.5,6,-1,1,-1
|
||||
1.5,6.9,-1,1,-1
|
||||
-1.4,1.5,1,-1,-1
|
||||
1.3,6.4,-1,1,-1
|
||||
2.8,2,-1,-1,1
|
||||
-1.2,1.5,1,-1,-1
|
||||
-1.1,1.5,1,-1,-1
|
||||
3.6,2.5,-1,-1,1
|
||||
1.1,5.1,-1,1,-1
|
||||
-1.1,1.5,1,-1,-1
|
||||
-1.2,1.5,1,-1,-1
|
||||
3.4,2.4,-1,-1,1
|
||||
-1.3,1.3,1,-1,-1
|
||||
3,2.3,-1,-1,1
|
||||
-1.1,1.5,1,-1,-1
|
||||
1.1,5.6,-1,1,-1
|
||||
1.3,5.6,-1,1,-1
|
||||
-1.2,1.5,1,-1,-1
|
||||
-1.2,1.3,1,-1,-1
|
||||
1.7,6.7,-1,1,-1
|
||||
-1.2,1.3,1,-1,-1
|
||||
-1.4,1.3,1,-1,-1
|
||||
2.5,1.8,-1,-1,1
|
||||
-1.4,1.5,1,-1,-1
|
||||
1.5,6.5,-1,1,-1
|
||||
2.6,2.3,-1,-1,1
|
||||
-1.2,1.4,1,-1,-1
|
||||
1.5,5.6,-1,1,-1
|
||||
1.3,5.7,-1,1,-1
|
||||
1.2,6.1,-1,1,-1
|
||||
3,2.2,-1,-1,1
|
||||
3,1.8,-1,-1,1
|
||||
-1.1,1.4,1,-1,-1
|
||||
1.4,6.8,-1,1,-1
|
||||
-1.6,1.6,1,-1,-1
|
||||
-1.2,1.4,1,-1,-1
|
||||
3.2,1.8,-1,-1,1
|
||||
-1.2,1.5,1,-1,-1
|
||||
2.7,1.8,-1,-1,1
|
||||
-1.3,1.4,1,-1,-1
|
||||
1.3,6.2,-1,1,-1
|
||||
-1.3,1.3,1,-1,-1
|
||||
3,1.8,-1,-1,1
|
||||
2.7,1.9,-1,-1,1
|
||||
-1.2,1.4,1,-1,-1
|
||||
-1.2,1.2,1,-1,-1
|
||||
1.5,6.7,-1,1,-1
|
||||
2.5,1.9,-1,-1,1
|
||||
3.3,2.1,-1,-1,1
|
||||
2.8,1.8,-1,-1,1
|
||||
-1.2,1.3,1,-1,-1
|
||||
3.8,2.2,-1,-1,1
|
||||
2.5,2,-1,-1,1
|
||||
1.3,6.1,-1,1,-1
|
||||
-1.2,1.4,1,-1,-1
|
||||
-1.1,1.5,1,-1,-1
|
||||
3,2.3,-1,-1,1
|
||||
-1.4,1.9,1,-1,-1
|
||||
-1.2,1.6,1,-1,-1
|
||||
1,6,-1,1,-1
|
||||
1.3,5.5,-1,1,-1
|
||||
2.8,1.9,1
|
||||
2.9,1.8,1
|
||||
2.2,1.5,1
|
||||
1.3,5.6,0
|
||||
-1.2,1.6,-1
|
||||
2.5,1.7,1
|
||||
3.1,2.3,1
|
||||
2.8,2.4,1
|
||||
-1.2,1.5,-1
|
||||
1.4,6.1,0
|
||||
-1.2,1.3,-1
|
||||
3.1,1.8,1
|
||||
3.3,2.5,1
|
||||
3.4,2.3,1
|
||||
1.5,5.9,0
|
||||
1.3,5.6,0
|
||||
-1.2,1,-1
|
||||
3,2.1,1
|
||||
1.4,5.2,0
|
||||
1.2,5.5,0
|
||||
1.4,6.7,0
|
||||
-1.3,1.7,-1
|
||||
1,5.7,0
|
||||
2.6,1.4,1
|
||||
1.6,6,0
|
||||
1.2,5.8,0
|
||||
1.4,6.1,0
|
||||
1.3,5.7,0
|
||||
-1.2,1.4,-1
|
||||
1,5.5,0
|
||||
3.1,2.1,1
|
||||
-1.4,1.7,-1
|
||||
1.6,6.3,0
|
||||
-1.2,1.4,-1
|
||||
2.7,1.9,1
|
||||
1.1,5.5,0
|
||||
3,1.8,1
|
||||
3.8,2,1
|
||||
2.8,2.1,1
|
||||
1,5,0
|
||||
2.8,2,1
|
||||
-1.2,1.6,-1
|
||||
1.3,6.6,0
|
||||
1.6,6,0
|
||||
3,1.6,1
|
||||
-1.1,1.1,-1
|
||||
2.9,1.8,1
|
||||
3,1.8,1
|
||||
-1.2,1.4,-1
|
||||
1,5.8,0
|
||||
1.2,5.7,0
|
||||
2.8,1.5,1
|
||||
-1.4,1.6,-1
|
||||
-1.2,1.4,-1
|
||||
2.8,2.2,1
|
||||
-1.5,1.7,-1
|
||||
1,4.9,0
|
||||
1.3,5.7,0
|
||||
3.2,2.3,1
|
||||
-1.3,1.5,-1
|
||||
-1.2,1.2,-1
|
||||
-1.2,1.6,-1
|
||||
3,2.1,1
|
||||
-1.4,1.5,-1
|
||||
1.3,5.5,0
|
||||
3.3,2.5,1
|
||||
3.1,2.4,1
|
||||
1.8,5.9,0
|
||||
2.7,1.9,1
|
||||
1.5,6.3,0
|
||||
-1.3,1.4,-1
|
||||
1,5,0
|
||||
3.2,2.3,1
|
||||
-1.3,1.4,-1
|
||||
3,2.1,1
|
||||
3.2,2,1
|
||||
1.3,6.3,0
|
||||
1.4,7,0
|
||||
-1.2,1.7,-1
|
||||
1.4,6.6,0
|
||||
1.2,5.8,0
|
||||
3,2,1
|
||||
-1.2,1.6,-1
|
||||
1.5,6.2,0
|
||||
1.5,6.4,0
|
||||
-1.2,1.9,-1
|
||||
3.2,2.3,1
|
||||
-1.2,1.5,-1
|
||||
1.5,5.4,0
|
||||
1.5,6,0
|
||||
1.5,6.9,0
|
||||
-1.4,1.5,-1
|
||||
1.3,6.4,0
|
||||
2.8,2,1
|
||||
-1.2,1.5,-1
|
||||
-1.1,1.5,-1
|
||||
3.6,2.5,1
|
||||
1.1,5.1,0
|
||||
-1.1,1.5,-1
|
||||
-1.2,1.5,-1
|
||||
3.4,2.4,1
|
||||
-1.3,1.3,-1
|
||||
3,2.3,1
|
||||
-1.1,1.5,-1
|
||||
1.1,5.6,0
|
||||
1.3,5.6,0
|
||||
-1.2,1.5,-1
|
||||
-1.2,1.3,-1
|
||||
1.7,6.7,0
|
||||
-1.2,1.3,-1
|
||||
-1.4,1.3,-1
|
||||
2.5,1.8,1
|
||||
-1.4,1.5,-1
|
||||
1.5,6.5,0
|
||||
2.6,2.3,1
|
||||
-1.2,1.4,-1
|
||||
1.5,5.6,0
|
||||
1.3,5.7,0
|
||||
1.2,6.1,0
|
||||
3,2.2,1
|
||||
3,1.8,1
|
||||
-1.1,1.4,-1
|
||||
1.4,6.8,0
|
||||
-1.6,1.6,-1
|
||||
-1.2,1.4,-1
|
||||
3.2,1.8,1
|
||||
-1.2,1.5,-1
|
||||
2.7,1.8,1
|
||||
-1.3,1.4,-1
|
||||
1.3,6.2,0
|
||||
-1.3,1.3,-1
|
||||
3,1.8,1
|
||||
2.7,1.9,1
|
||||
-1.2,1.4,-1
|
||||
-1.2,1.2,-1
|
||||
1.5,6.7,0
|
||||
2.5,1.9,1
|
||||
3.3,2.1,1
|
||||
2.8,1.8,1
|
||||
-1.2,1.3,-1
|
||||
3.8,2.2,1
|
||||
2.5,2,1
|
||||
1.3,6.1,0
|
||||
-1.2,1.4,-1
|
||||
-1.1,1.5,-1
|
||||
3,2.3,1
|
||||
-1.4,1.9,-1
|
||||
-1.2,1.6,-1
|
||||
1,6,0
|
||||
1.3,5.5,0
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,-1,-1,-1
|
||||
0,0,1,0,0,0,0,1,0,0,1,1,1,1,1,0,0,1,0,0,0,0,1,0,0,-1,1,-1,-1
|
||||
1,0,0,0,1,0,1,0,1,0,0,0,1,0,0,0,1,0,1,0,1,0,0,0,1,-1,-1,1,-1
|
||||
0,0,0,0,0,0,1,1,1,0,0,1,0,1,0,0,1,1,1,0,0,0,0,0,0,-1,-1,-1,1
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||
0,0,1,0,0,0,0,1,0,0,1,1,1,1,1,0,0,1,0,0,0,0,1,0,0,1
|
||||
1,0,0,0,1,0,1,0,1,0,0,0,1,0,0,0,1,0,1,0,1,0,0,0,1,2
|
||||
0,0,0,0,0,0,1,1,1,0,0,1,0,1,0,0,1,1,1,0,0,0,0,0,0,3
|
||||
|
||||
|
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 24 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="166" height="166" viewBox="0 0 166 166" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M162.041 38.7592C162.099 38.9767 162.129 39.201 162.13 39.4264V74.4524C162.13 74.9019 162.011 75.3435 161.786 75.7325C161.561 76.1216 161.237 76.4442 160.847 76.6678L131.462 93.5935V127.141C131.462 128.054 130.977 128.897 130.186 129.357L68.8474 164.683C68.707 164.763 68.5538 164.814 68.4007 164.868C68.3432 164.887 68.289 164.922 68.2284 164.938C67.7996 165.051 67.3489 165.051 66.9201 164.938C66.8499 164.919 66.7861 164.881 66.7191 164.855C66.5787 164.804 66.4319 164.76 66.2979 164.683L4.97219 129.357C4.58261 129.133 4.2589 128.81 4.0337 128.421C3.8085 128.032 3.68976 127.591 3.68945 127.141L3.68945 22.0634C3.68945 21.8336 3.72136 21.6101 3.7788 21.393C3.79794 21.3196 3.84262 21.2526 3.86814 21.1791C3.91601 21.0451 3.96068 20.9078 4.03088 20.7833C4.07874 20.7003 4.14894 20.6333 4.20638 20.5566C4.27977 20.4545 4.34678 20.3491 4.43293 20.2598C4.50632 20.1863 4.60205 20.1321 4.68501 20.0682C4.77755 19.9916 4.86051 19.9086 4.96581 19.848L35.6334 2.18492C36.0217 1.96139 36.4618 1.84375 36.9098 1.84375C37.3578 1.84375 37.7979 1.96139 38.1862 2.18492L68.8506 19.848H68.857C68.9591 19.9118 69.0452 19.9916 69.1378 20.065C69.2207 20.1289 69.3133 20.1863 69.3867 20.2566C69.476 20.3491 69.5398 20.4545 69.6164 20.5566C69.6707 20.6333 69.7441 20.7003 69.7887 20.7833C69.8621 20.911 69.9036 21.0451 69.9546 21.1791C69.9802 21.2526 70.0248 21.3196 70.044 21.3962C70.1027 21.6138 70.1328 21.8381 70.1333 22.0634V87.6941L95.686 72.9743V39.4232C95.686 39.1997 95.7179 38.9731 95.7753 38.7592C95.7977 38.6826 95.8391 38.6155 95.8647 38.5421C95.9157 38.408 95.9604 38.2708 96.0306 38.1463C96.0785 38.0633 96.1487 37.9962 96.2029 37.9196C96.2795 37.8175 96.3433 37.7121 96.4326 37.6227C96.506 37.5493 96.5986 37.495 96.6815 37.4312C96.7773 37.3546 96.8602 37.2716 96.9623 37.2109L127.633 19.5479C128.021 19.324 128.461 19.2062 128.91 19.2062C129.358 19.2062 129.798 19.324 130.186 19.5479L160.85 37.2109C160.959 37.2748 161.042 37.3546 161.137 37.428C161.217 37.4918 161.31 37.5493 161.383 37.6195C161.473 37.7121 161.536 37.8175 161.613 37.9196C161.67 37.9962 161.741 38.0633 161.785 38.1463C161.859 38.2708 161.9 38.408 161.951 38.5421C161.98 38.6155 162.021 38.6826 162.041 38.7592ZM157.018 72.9743V43.8477L146.287 50.028L131.462 58.5675V87.6941L157.021 72.9743H157.018ZM126.354 125.663V96.5176L111.771 104.85L70.1301 128.626V158.046L126.354 125.663ZM8.80126 26.4848V125.663L65.0183 158.043V128.629L35.6494 112L35.6398 111.994L35.6271 111.988C35.5281 111.93 35.4452 111.847 35.3526 111.777C35.2729 111.713 35.1803 111.662 35.1101 111.592L35.1038 111.582C35.0208 111.502 34.9634 111.403 34.8932 111.314C34.8293 111.228 34.7528 111.154 34.7017 111.065L34.6985 111.055C34.6411 110.96 34.606 110.845 34.5645 110.736C34.523 110.64 34.4688 110.551 34.4432 110.449C34.4113 110.328 34.4049 110.197 34.3922 110.072C34.3794 109.976 34.3539 109.881 34.3539 109.785V109.778V41.2045L19.5322 32.6619L8.80126 26.4848ZM36.913 7.35007L11.3635 22.0634L36.9066 36.7768L62.4529 22.0602L36.9066 7.35007H36.913ZM50.1999 99.1736L65.0215 90.6374V26.4848L54.2906 32.6651L39.4657 41.2045V105.357L50.1999 99.1736ZM128.91 24.713L103.363 39.4264L128.91 54.1397L154.453 39.4232L128.91 24.713ZM126.354 58.5675L111.529 50.028L100.798 43.8477V72.9743L115.619 81.5106L126.354 87.6941V58.5675ZM67.5711 124.205L105.042 102.803L123.772 92.109L98.2451 77.4053L68.8538 94.3341L42.0663 109.762L67.5711 124.205Z" fill="#FF2D20"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.5 KiB |
@@ -33,9 +33,7 @@ const rowBgDark = computed(() => {
|
||||
<th>Époch</th>
|
||||
<th>Exemple</th>
|
||||
<th
|
||||
v-for="(weight, index) in allWeightPerIteration[
|
||||
allWeightPerIteration.length - 1
|
||||
]"
|
||||
v-for="(weight, index) in allWeightPerIteration[0]"
|
||||
v-bind:key="index"
|
||||
>
|
||||
X<sub>{{ index }}</sub>
|
||||
@@ -46,7 +44,7 @@ const rowBgDark = computed(() => {
|
||||
v-for="(iteration, index) in props.iterations"
|
||||
v-bind:key="index"
|
||||
:class="{
|
||||
'bg-gray-900': rowBgDark[index],
|
||||
'bg-gray-300 dark:bg-gray-900': rowBgDark[index],
|
||||
}"
|
||||
>
|
||||
<td>{{ iteration.epoch }}</td>
|
||||
@@ -60,7 +58,10 @@ const rowBgDark = computed(() => {
|
||||
<td>{{ iteration.error.toFixed(2) }}</td>
|
||||
</tr>
|
||||
|
||||
<tr v-if="props.trainingEnded" class="bg-red-900 text-center">
|
||||
<tr
|
||||
v-if="props.trainingEnded"
|
||||
class="bg-red-400 text-center dark:bg-red-900"
|
||||
>
|
||||
<td colspan="100%">
|
||||
<strong>Entraînement terminé :</strong>
|
||||
{{ props.trainingEndReason }}
|
||||
|
||||
@@ -15,6 +15,21 @@ const links = [
|
||||
href: '/perceptron',
|
||||
data: { type: 'gradientdescent' },
|
||||
},
|
||||
{
|
||||
name: 'ADALINE',
|
||||
href: '/perceptron',
|
||||
data: { type: 'adaline' },
|
||||
},
|
||||
{
|
||||
name: 'Mono-couche',
|
||||
href: '/perceptron',
|
||||
data: { type: 'monolayer' },
|
||||
},
|
||||
{
|
||||
name: 'Multi-couche',
|
||||
href: '/perceptron',
|
||||
data: { type: 'multilayer' },
|
||||
},
|
||||
];
|
||||
|
||||
const isActiveLink = (link: any) => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
BubbleDataPoint,
|
||||
Point,
|
||||
} from 'chart.js';
|
||||
import { computed } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { Chart } from 'vue-chartjs';
|
||||
import { colors, gridColor, gridColorBold } from '@/types/graphs';
|
||||
import type { Iteration } from '@/types/perceptron';
|
||||
@@ -16,115 +16,184 @@ const props = defineProps<{
|
||||
activationFunction: (x: number) => number;
|
||||
}>();
|
||||
|
||||
const examplesNumber = computed(() => {
|
||||
return props.cleanedDataset.reduce((sum, dataset) => sum + dataset.data.length, 0);
|
||||
});
|
||||
|
||||
const farLeftDataPointX = computed(() => {
|
||||
if (props.cleanedDataset.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
const minX = Math.min(...props.cleanedDataset.flatMap((d) => d.data.map((point) => point.x)));
|
||||
const minX = Math.min(
|
||||
...props.cleanedDataset.flatMap((d) => d.data.map((point) => point.x)),
|
||||
);
|
||||
return minX;
|
||||
});
|
||||
const farBottomDataPointY = computed(() => {
|
||||
if (props.cleanedDataset.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
const minY = Math.min(
|
||||
...props.cleanedDataset.flatMap((d) => d.data.map((point) => point.y)),
|
||||
);
|
||||
return minY;
|
||||
});
|
||||
const farRightDataPointX = computed(() => {
|
||||
if (props.cleanedDataset.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
const maxX = Math.max(...props.cleanedDataset.flatMap((d) => d.data.map((point) => point.x)));
|
||||
const maxX = Math.max(
|
||||
...props.cleanedDataset.flatMap((d) => d.data.map((point) => point.x)),
|
||||
);
|
||||
return maxX;
|
||||
});
|
||||
const farTopDataPointY = computed(() => {
|
||||
if (props.cleanedDataset.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
const maxY = Math.max(
|
||||
...props.cleanedDataset.flatMap((d) => d.data.map((point) => point.y)),
|
||||
);
|
||||
return maxY;
|
||||
});
|
||||
|
||||
function getPerceptronOutput(
|
||||
weightsNetwork: number[][][],
|
||||
inputs: number[],
|
||||
): number[] {
|
||||
for (const layer of weightsNetwork) {
|
||||
const nextInputs: number[] = [];
|
||||
|
||||
for (const neuron of layer) {
|
||||
const bias = neuron[0];
|
||||
const weights = neuron.slice(1);
|
||||
|
||||
let sum = bias;
|
||||
|
||||
for (let i = 0; i < weights.length; i++) {
|
||||
sum += weights[i] * inputs[i];
|
||||
}
|
||||
|
||||
const activated = props.activationFunction(sum);
|
||||
|
||||
nextInputs.push(activated);
|
||||
}
|
||||
|
||||
inputs = nextInputs;
|
||||
}
|
||||
return inputs;
|
||||
}
|
||||
|
||||
const nonLinearGraph = ref<boolean>(false);
|
||||
function getPerceptronDecisionBoundaryDataset(
|
||||
networkWeights: number[][][],
|
||||
activationFunction: (x: number) => number = (x) => x,
|
||||
): ChartDataset<
|
||||
keyof ChartTypeRegistry,
|
||||
number | Point | [number, number] | BubbleDataPoint | null
|
||||
> {
|
||||
>[] {
|
||||
const label = 'Ligne de décision du Perceptron';
|
||||
console.log('Calculating decision boundary with weights:', networkWeights);
|
||||
|
||||
if (
|
||||
networkWeights.length == 1 &&
|
||||
networkWeights[0].length == 1 &&
|
||||
networkWeights[0][0].length == 3
|
||||
) { // Unique, 3 weights perceptron
|
||||
const perceptronWeights = networkWeights[0][0]; // We take the unique perceptron
|
||||
networkWeights[0][0].length <= 3
|
||||
) {
|
||||
nonLinearGraph.value = false;
|
||||
// Unique, 3 weights perceptron
|
||||
const perceptronWeights = [...networkWeights[0][0]]; // Copy of the unique perceptron weights
|
||||
|
||||
function perceptronLine(x: number): number {
|
||||
if (perceptronWeights.length < 3) {
|
||||
// If we have less than 3 weights, we assume missing weights are zero
|
||||
return getPerceptronOutput(networkWeights, [x])[0];
|
||||
}
|
||||
|
||||
// w0 + w1*x + w2*y = 0 => y = -(w1/w2)*x - w0/w2
|
||||
const w2 = perceptronWeights[2] == 0 ? 1e-6 : perceptronWeights[2]; // Avoid division by zero
|
||||
return -(perceptronWeights[1] / w2) * x - perceptronWeights[0] / w2;
|
||||
}
|
||||
|
||||
// Simple line
|
||||
return {
|
||||
type: 'line',
|
||||
label: label,
|
||||
data: [
|
||||
{
|
||||
x: farLeftDataPointX.value - 1,
|
||||
y: perceptronLine(farLeftDataPointX.value - 1),
|
||||
},
|
||||
{
|
||||
x: farRightDataPointX.value + 1,
|
||||
y: perceptronLine(farRightDataPointX.value + 1),
|
||||
},
|
||||
],
|
||||
borderColor: '#FFF',
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
};
|
||||
return [
|
||||
{
|
||||
type: 'line',
|
||||
label: label,
|
||||
data: [
|
||||
{
|
||||
x: farLeftDataPointX.value - 1,
|
||||
y: perceptronLine(farLeftDataPointX.value - 1),
|
||||
},
|
||||
{
|
||||
x: farRightDataPointX.value + 1,
|
||||
y: perceptronLine(farRightDataPointX.value + 1),
|
||||
},
|
||||
],
|
||||
borderColor: '#FFF',
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
function forward(x1: number, x2: number): number {
|
||||
let activations: number[] = [x1, x2];
|
||||
nonLinearGraph.value = true;
|
||||
|
||||
for (const layer of networkWeights) {
|
||||
const nextActivations: number[] = [];
|
||||
const bubbleTransparency = '30';
|
||||
const isInDataThreshold = 0.0;
|
||||
|
||||
for (const neuron of layer) {
|
||||
const bias = neuron[0];
|
||||
const weights = neuron.slice(1);
|
||||
|
||||
let sum = bias;
|
||||
|
||||
for (let i = 0; i < weights.length; i++) {
|
||||
sum += weights[i] * activations[i];
|
||||
}
|
||||
|
||||
const activated = activationFunction(sum);
|
||||
|
||||
nextActivations.push(activated);
|
||||
}
|
||||
|
||||
activations = nextActivations;
|
||||
}
|
||||
|
||||
return activations[0]; // on suppose sortie unique
|
||||
// -------- 1️⃣ Construction des datasets --------
|
||||
const datasets: {
|
||||
type: string;
|
||||
label: string;
|
||||
data: Point[];
|
||||
backgroundColor: string;
|
||||
pointRadius: number;
|
||||
borderWidth: number;
|
||||
order: number;
|
||||
}[] = [];
|
||||
// For the number of neuron in the last layer
|
||||
const lastLayer = networkWeights[networkWeights.length - 1];
|
||||
for (let i = 0; i < lastLayer.length; i++) {
|
||||
const dataset = {
|
||||
type: 'scatter',
|
||||
label: label,
|
||||
data: [], // Will be filled with the decision boundary points
|
||||
backgroundColor: colors[i] + bubbleTransparency || '#AAA',
|
||||
pointRadius: 15,
|
||||
borderWidth: 0,
|
||||
order: -1,
|
||||
};
|
||||
datasets.push(dataset);
|
||||
}
|
||||
|
||||
// -------- 2️⃣ Échantillonnage grille --------
|
||||
const decisionBoundary: Point[] = [];
|
||||
const min = -2;
|
||||
const max = 2;
|
||||
const step = 0.03;
|
||||
const epsilon = 0.01;
|
||||
const step =
|
||||
Math.abs(
|
||||
farRightDataPointX.value + 1 - (farLeftDataPointX.value - 1),
|
||||
) / 50;
|
||||
|
||||
for (let x = min; x <= max; x += step) {
|
||||
for (let y = min; y <= max; y += step) {
|
||||
const value = forward(x, y);
|
||||
|
||||
if (Math.abs(value) < epsilon) {
|
||||
decisionBoundary.push({ x, y });
|
||||
}
|
||||
for (
|
||||
let x = farLeftDataPointX.value - 1;
|
||||
x <= farRightDataPointX.value + 1;
|
||||
x += step
|
||||
) {
|
||||
for (
|
||||
let y = farBottomDataPointY.value - 1;
|
||||
y <= farTopDataPointY.value + 1;
|
||||
y += step
|
||||
) {
|
||||
const values = getPerceptronOutput(networkWeights, [x, y]);
|
||||
values.forEach((v, i) => {
|
||||
if (v > isInDataThreshold) {
|
||||
datasets[i].data.push({ x, y });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// -------- 3️⃣ Dataset ChartJS --------
|
||||
return {
|
||||
type: 'scatter',
|
||||
label: label,
|
||||
data: decisionBoundary,
|
||||
backgroundColor: '#FFFFFF',
|
||||
pointRadius: 1,
|
||||
};
|
||||
return datasets;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -132,7 +201,8 @@ function getPerceptronDecisionBoundaryDataset(
|
||||
<template>
|
||||
<Chart
|
||||
v-if="props.cleanedDataset.length > 0 || props.iterations.length > 0"
|
||||
class="flex"
|
||||
class="flex bg-primary dark:bg-transparent!"
|
||||
type="scatter"
|
||||
:options="{
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
@@ -145,6 +215,9 @@ function getPerceptronDecisionBoundaryDataset(
|
||||
text: 'Ligne de décision du Perceptron',
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
duration: nonLinearGraph || examplesNumber > 10 ? 0 : 1000, // Disable animations for instant updates
|
||||
},
|
||||
layout: {
|
||||
padding: {
|
||||
left: 10,
|
||||
@@ -189,12 +262,11 @@ function getPerceptronDecisionBoundaryDataset(
|
||||
type: 'scatter',
|
||||
label: `Label ${dataset.label}`,
|
||||
data: dataset.data,
|
||||
backgroundColor:
|
||||
colors[index] || '#AAA',
|
||||
backgroundColor: colors[index] || '#AAA',
|
||||
})),
|
||||
|
||||
// Perceptron decision boundary
|
||||
getPerceptronDecisionBoundaryDataset(
|
||||
...getPerceptronDecisionBoundaryDataset(
|
||||
props.iterations.length > 0
|
||||
? props.iterations[props.iterations.length - 1].weights
|
||||
: [[[0, 0, 0]]],
|
||||
|
||||
@@ -1,46 +1,55 @@
|
||||
<script setup lang="ts">
|
||||
import type { ChartData } from 'chart.js';
|
||||
import { computed, ref } from 'vue';
|
||||
import { Bar } from 'vue-chartjs';
|
||||
import { colors, gridColor, gridColorBold } from '@/types/graphs';
|
||||
import type { Iteration } from '@/types/perceptron';
|
||||
import Toggle from './ui/toggle/Toggle.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
iterations: Iteration[];
|
||||
}>();
|
||||
|
||||
const epochErrorOnly = ref<boolean>(false);
|
||||
|
||||
/**
|
||||
* Return the datasets of the iterations with the form { label: `Exemple ${exampleIndex}`, data: [error for iteration 1, error for iteration 2, ...] }
|
||||
* Datasets of the iterations with the form { label: `Exemple ${exampleIndex}`, data: [error for iteration 1, error for iteration 2, ...] }
|
||||
*/
|
||||
function getPerceptronErrorsPerIteration(): ChartData<
|
||||
'bar',
|
||||
(number | [number, number] | null)[]
|
||||
>[] {
|
||||
const datasets = computed<
|
||||
ChartData<'bar', (number | [number, number] | null)[]>[]
|
||||
>(() => {
|
||||
const datasets: ChartData<'bar', (number | [number, number] | null)[]>[] =
|
||||
[];
|
||||
const epochAverageError: number[] = [];
|
||||
|
||||
const backgroundColors = colors;
|
||||
|
||||
const exampleCountPerEpoch: Record<number, number> = {};
|
||||
|
||||
props.iterations.forEach((iteration) => {
|
||||
const exampleLabel = `Exemple ${iteration.exampleIndex}`;
|
||||
let dataset = datasets.find((d) => d.label === exampleLabel);
|
||||
if (!dataset) {
|
||||
dataset = {
|
||||
label: exampleLabel,
|
||||
data: [],
|
||||
order: 1,
|
||||
backgroundColor:
|
||||
backgroundColors[
|
||||
iteration.exampleIndex % backgroundColors.length
|
||||
],
|
||||
};
|
||||
datasets.push(dataset);
|
||||
if (!epochErrorOnly.value) {
|
||||
const exampleLabel = `Exemple ${iteration.exampleIndex}`;
|
||||
let dataset = datasets.find((d) => d.label === exampleLabel);
|
||||
if (!dataset) {
|
||||
dataset = {
|
||||
label: exampleLabel,
|
||||
data: [],
|
||||
order: 1,
|
||||
backgroundColor:
|
||||
backgroundColors[
|
||||
iteration.exampleIndex % backgroundColors.length
|
||||
],
|
||||
};
|
||||
datasets.push(dataset);
|
||||
}
|
||||
dataset.data.push(iteration.error);
|
||||
}
|
||||
dataset.data.push(iteration.error);
|
||||
|
||||
exampleCountPerEpoch[iteration.epoch] = (exampleCountPerEpoch[iteration.epoch] || 0) + 1;
|
||||
|
||||
// Epoch error
|
||||
epochAverageError[iteration.epoch - 1] =
|
||||
(epochAverageError[iteration.epoch - 1] || 0) +
|
||||
epochAverageError[iteration.epoch] =
|
||||
(epochAverageError[iteration.epoch] || 0) +
|
||||
iteration.error ** 2 / 2;
|
||||
});
|
||||
|
||||
@@ -54,7 +63,7 @@ function getPerceptronErrorsPerIteration(): ChartData<
|
||||
// Epoch error
|
||||
const epochErrorDataset = {
|
||||
type: 'line',
|
||||
label: "Erreur de l'époque",
|
||||
label: "Erreur quadratique moyenne de l'époque",
|
||||
data: [],
|
||||
backgroundColor: '#fff',
|
||||
borderColor: '#fff',
|
||||
@@ -62,19 +71,20 @@ function getPerceptronErrorsPerIteration(): ChartData<
|
||||
tension: 0.3,
|
||||
};
|
||||
|
||||
epochAverageError.forEach((error) => {
|
||||
epochErrorDataset.data.push(error);
|
||||
epochAverageError.forEach((error, index) => {
|
||||
const exampleCount = exampleCountPerEpoch[index] || 1; // Avoid division by zero
|
||||
epochErrorDataset.data.push(error / exampleCount);
|
||||
});
|
||||
|
||||
datasets.push(epochErrorDataset);
|
||||
|
||||
return datasets;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Bar
|
||||
class="flex"
|
||||
class="bg-primary dark:bg-transparent!"
|
||||
:options="{
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
@@ -84,6 +94,9 @@ function getPerceptronErrorsPerIteration(): ChartData<
|
||||
text: 'Nombre d\'erreurs par epoch',
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
duration: iterations.length > 100 ? 0 : 1000, // Disable animations for instant updates
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
@@ -111,7 +124,12 @@ function getPerceptronErrorsPerIteration(): ChartData<
|
||||
}
|
||||
return labels;
|
||||
}, [] as string[]),
|
||||
datasets: getPerceptronErrorsPerIteration(),
|
||||
datasets: datasets,
|
||||
}"
|
||||
/>
|
||||
<div class="flex items-center gap-3">
|
||||
<Toggle v-model="epochErrorOnly" class="cursor-pointer" variant="outline" id="epoch-error-only"
|
||||
>Afficher uniquement l'erreur quadratique moyenne de l'époque</Toggle
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -127,6 +127,7 @@ watch(selectedDatasetCopy, (newValue) => {
|
||||
name="dataset"
|
||||
id="dataset-select"
|
||||
v-model="selectedDatasetCopy"
|
||||
class="cursor-pointer"
|
||||
>
|
||||
<NativeSelectOption value="" disabled
|
||||
>Sélectionnez un dataset</NativeSelectOption
|
||||
@@ -154,6 +155,7 @@ watch(selectedDatasetCopy, (newValue) => {
|
||||
name="weight_init_method"
|
||||
id="weight_init_method"
|
||||
v-model="selectedMethod"
|
||||
class="cursor-pointer"
|
||||
>
|
||||
<NativeSelectOption
|
||||
v-for="method in ['zeros', 'random']"
|
||||
|
||||
35
resources/js/components/ui/toggle/Toggle.vue
Normal file
35
resources/js/components/ui/toggle/Toggle.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
import type { ToggleEmits, ToggleProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import type { ToggleVariants } from "."
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import { Toggle, useForwardPropsEmits } from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { toggleVariants } from "."
|
||||
|
||||
const props = withDefaults(defineProps<ToggleProps & {
|
||||
class?: HTMLAttributes["class"]
|
||||
variant?: ToggleVariants["variant"]
|
||||
size?: ToggleVariants["size"]
|
||||
}>(), {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
disabled: false,
|
||||
})
|
||||
|
||||
const emits = defineEmits<ToggleEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class", "size", "variant")
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Toggle
|
||||
v-slot="slotProps"
|
||||
data-slot="toggle"
|
||||
v-bind="forwarded"
|
||||
:class="cn(toggleVariants({ variant, size }), props.class)"
|
||||
>
|
||||
<slot v-bind="slotProps" />
|
||||
</Toggle>
|
||||
</template>
|
||||
28
resources/js/components/ui/toggle/index.ts
Normal file
28
resources/js/components/ui/toggle/index.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { VariantProps } from "class-variance-authority"
|
||||
import { cva } from "class-variance-authority"
|
||||
|
||||
export { default as Toggle } from "./Toggle.vue"
|
||||
|
||||
export const toggleVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-transparent",
|
||||
outline:
|
||||
"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-2 min-w-9",
|
||||
sm: "h-8 px-1.5 min-w-8",
|
||||
lg: "h-10 px-2.5 min-w-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
export type ToggleVariants = VariantProps<typeof toggleVariants>
|
||||
@@ -33,7 +33,6 @@
|
||||
<title inertia>{{ config('app.name', 'Laravel') }}</title>
|
||||
|
||||
<link rel="icon" href="/favicon.ico" sizes="any">
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
||||
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\PerceptronController;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::post('perceptron/run', [PerceptronController::class, 'run'])->name('perceptron.run');
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
|
||||
use Illuminate\Support\Facades\Broadcast;
|
||||
|
||||
Broadcast::channel(session()->getId() . '-perceptron-training', function ($user) {
|
||||
Broadcast::channel(session()->getId().'-perceptron-training', function ($user) {
|
||||
return $user;
|
||||
});
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ExampleTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_returns_a_successful_response()
|
||||
{
|
||||
$response = $this->get(route('home'));
|
||||
|
||||
$response->assertOk();
|
||||
}
|
||||
}
|
||||
@@ -4,19 +4,13 @@ namespace Tests\Services\IterationEventBuffer;
|
||||
|
||||
use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer;
|
||||
|
||||
class DullIterationEventBuffer implements IPerceptronIterationEventBuffer {
|
||||
|
||||
class DullIterationEventBuffer implements IPerceptronIterationEventBuffer
|
||||
{
|
||||
public function __construct(
|
||||
|
||||
) {
|
||||
) {}
|
||||
|
||||
}
|
||||
public function flush(): void {}
|
||||
|
||||
public function flush(): void {
|
||||
return;
|
||||
}
|
||||
|
||||
public function addIteration(int $epoch, int $exampleIndex, float $error, array $synaptic_weights): void {
|
||||
return;
|
||||
}
|
||||
public function addIteration(int $epoch, int $exampleIndex, float $error, array $synaptic_weights): void {}
|
||||
}
|
||||
|
||||
73
tests/Unit/Training/ADALINEPerceptronTest.php
Normal file
73
tests/Unit/Training/ADALINEPerceptronTest.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Training;
|
||||
|
||||
use App\Models\NetworksTraining\ADALINEPerceptronTraining;
|
||||
use App\Services\DatasetReader\LinearOrderDataSetReader;
|
||||
use App\Services\SynapticWeightsProvider\ZeroSynapticWeights;
|
||||
use Tests\Services\IterationEventBuffer\DullIterationEventBuffer;
|
||||
|
||||
class ADALINEPerceptronTest extends TrainingTestCase
|
||||
{
|
||||
public function test_adaline_perceptron_training_logic_and()
|
||||
{
|
||||
$training = new ADALINEPerceptronTraining(
|
||||
datasetReader: new LinearOrderDataSetReader(public_path('data_sets/logic_and_gradient.csv')),
|
||||
learningRate: 0.03,
|
||||
maxEpochs: 10000,
|
||||
synapticWeightsProvider: new ZeroSynapticWeights,
|
||||
iterationEventBuffer: new DullIterationEventBuffer,
|
||||
sessionId: 'test-session',
|
||||
trainingId: 'test-training',
|
||||
minError: 0.1251,
|
||||
);
|
||||
|
||||
$this->verifyTrainingResults(
|
||||
training: $training,
|
||||
expectedWeights: [[[-1.503867, 0.992594, 0.976844]]],
|
||||
expectedEpochs: 202,
|
||||
marginOfError: 0.1,
|
||||
);
|
||||
}
|
||||
|
||||
// public function test_adaline_perceptron_training_table_2_9()
|
||||
// {
|
||||
// $training = new ADALINEPerceptronTraining(
|
||||
// datasetReader: new LinearOrderDataSetReader(public_path('data_sets/table_2_9.csv')),
|
||||
// learningRate: 0.0012,
|
||||
// maxEpochs: 1000,
|
||||
// synapticWeightsProvider: new ZeroSynapticWeights(),
|
||||
// iterationEventBuffer: new DullIterationEventBuffer(),
|
||||
// sessionId: 'test-session',
|
||||
// trainingId: 'test-training',
|
||||
// minError: 5.670337, // Impossible pour un dataset avec des labels -1 et 1 d'avoir une erreur moyenne supérieure à 2
|
||||
// );
|
||||
|
||||
// $this->verifyTrainingResults(
|
||||
// training: $training,
|
||||
// expectedWeights: [[[-0.664816, -0.522798, 0.342044]]],
|
||||
// expectedEpochs: 92
|
||||
// );
|
||||
// }
|
||||
|
||||
public function test_adaline_perceptron_training_table_2_10()
|
||||
{
|
||||
$training = new ADALINEPerceptronTraining(
|
||||
datasetReader: new LinearOrderDataSetReader(public_path('data_sets/table_2_10.csv')),
|
||||
learningRate: 0.0015,
|
||||
maxEpochs: 1000,
|
||||
synapticWeightsProvider: new ZeroSynapticWeights,
|
||||
iterationEventBuffer: new DullIterationEventBuffer,
|
||||
sessionId: 'test-session',
|
||||
trainingId: 'test-training',
|
||||
// minError: 16.597077, // Impossible pour un dataset avec des labels -1 et 1 d'avoir une erreur moyenne supérieure à 2
|
||||
minError: 0.000000001,
|
||||
);
|
||||
|
||||
$this->verifyTrainingResults(
|
||||
training: $training,
|
||||
expectedWeights: [[[-0.022673, -0.25422, 0.294955]]],
|
||||
expectedEpochs: 1000
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,22 +2,21 @@
|
||||
|
||||
namespace Tests\Unit\Training;
|
||||
|
||||
use App\Models\GradientDescentPerceptronTraining;
|
||||
use App\Models\NetworksTraining\GradientDescentPerceptronTraining;
|
||||
use App\Services\DatasetReader\LinearOrderDataSetReader;
|
||||
use Tests\Services\IterationEventBuffer\DullIterationEventBuffer;
|
||||
use App\Services\SynapticWeightsProvider\ZeroSynapticWeights;
|
||||
use Tests\Services\IterationEventBuffer\DullIterationEventBuffer;
|
||||
|
||||
class GradientDescentPerceptronTest extends TrainingTestCase
|
||||
{
|
||||
|
||||
public function test_simple_perceptron_training_logic_and()
|
||||
public function test_gradient_descent_perceptron_training_logic_and()
|
||||
{
|
||||
$training = new GradientDescentPerceptronTraining(
|
||||
datasetReader: new LinearOrderDataSetReader(public_path('data_sets/logic_and_gradient.csv')),
|
||||
learningRate: 0.2,
|
||||
maxEpochs: 100,
|
||||
synapticWeightsProvider: new ZeroSynapticWeights(),
|
||||
iterationEventBuffer: new DullIterationEventBuffer(),
|
||||
synapticWeightsProvider: new ZeroSynapticWeights,
|
||||
iterationEventBuffer: new DullIterationEventBuffer,
|
||||
sessionId: 'test-session',
|
||||
trainingId: 'test-training',
|
||||
minError: 0.125001,
|
||||
@@ -29,4 +28,45 @@ class GradientDescentPerceptronTest extends TrainingTestCase
|
||||
expectedEpochs: 49
|
||||
);
|
||||
}
|
||||
|
||||
// public function test_gradient_descent_perceptron_training_table_2_9()
|
||||
// {
|
||||
// $training = new GradientDescentPerceptronTraining(
|
||||
// datasetReader: new LinearOrderDataSetReader(public_path('data_sets/table_2_9.csv')),
|
||||
// learningRate: 0.0011,
|
||||
// maxEpochs: 1000,
|
||||
// synapticWeightsProvider: new ZeroSynapticWeights(),
|
||||
// iterationEventBuffer: new DullIterationEventBuffer(),
|
||||
// sessionId: 'test-session',
|
||||
// trainingId: 'test-training',
|
||||
// minError: 5.524889, // Impossible pour un dataset avec des labels -1 et 1 d'avoir une erreur moyenne supérieure à 2
|
||||
// );
|
||||
|
||||
// $this->verifyTrainingResults(
|
||||
// training: $training,
|
||||
// expectedWeights: [[[-0.664816, -0.522798, 0.342044]]],
|
||||
// expectedEpochs: 402
|
||||
// );
|
||||
// }
|
||||
|
||||
public function test_gradient_descent_perceptron_training_table_2_10()
|
||||
{
|
||||
$training = new GradientDescentPerceptronTraining(
|
||||
datasetReader: new LinearOrderDataSetReader(public_path('data_sets/table_2_10.csv')),
|
||||
learningRate: 0.0015,
|
||||
maxEpochs: 1000,
|
||||
synapticWeightsProvider: new ZeroSynapticWeights,
|
||||
iterationEventBuffer: new DullIterationEventBuffer,
|
||||
sessionId: 'test-session',
|
||||
trainingId: 'test-training',
|
||||
// minError: 16.388103, // Impossible pour un dataset avec des labels -1 et 1 d'avoir une erreur moyenne supérieure à 2
|
||||
minError: 0.000000001,
|
||||
);
|
||||
|
||||
$this->verifyTrainingResults(
|
||||
training: $training,
|
||||
expectedWeights: [[[-0.04107, -0.263527, 0.286406]]],
|
||||
expectedEpochs: 1000
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,22 +2,21 @@
|
||||
|
||||
namespace Tests\Unit\Training;
|
||||
|
||||
use App\Models\SimpleBinaryPerceptronTraining;
|
||||
use App\Models\NetworksTraining\SimpleBinaryPerceptronTraining;
|
||||
use App\Services\DatasetReader\LinearOrderDataSetReader;
|
||||
use Tests\Services\IterationEventBuffer\DullIterationEventBuffer;
|
||||
use App\Services\SynapticWeightsProvider\ZeroSynapticWeights;
|
||||
use Tests\Services\IterationEventBuffer\DullIterationEventBuffer;
|
||||
|
||||
class SimplePerceptronTest extends TrainingTestCase
|
||||
{
|
||||
|
||||
public function test_simple_perceptron_training_logic_and()
|
||||
{
|
||||
$training = new SimpleBinaryPerceptronTraining(
|
||||
datasetReader: new LinearOrderDataSetReader(public_path('data_sets/logic_and.csv')),
|
||||
learningRate: 1.0,
|
||||
maxEpochs: 100,
|
||||
synapticWeightsProvider: new ZeroSynapticWeights(),
|
||||
iterationEventBuffer: new DullIterationEventBuffer(),
|
||||
synapticWeightsProvider: new ZeroSynapticWeights,
|
||||
iterationEventBuffer: new DullIterationEventBuffer,
|
||||
sessionId: 'test-session',
|
||||
trainingId: 'test-training',
|
||||
);
|
||||
|
||||
@@ -2,24 +2,22 @@
|
||||
|
||||
namespace Tests\Unit\Training;
|
||||
|
||||
use App\Models\NetworkTraining;
|
||||
use App\Models\NetworksTraining\NetworkTraining;
|
||||
use Tests\TestCase;
|
||||
|
||||
class TrainingTestCase extends TestCase
|
||||
{
|
||||
public const MARGIN_OF_ERROR = 0.001;
|
||||
public const DEFAULT_MARGIN_OF_ERROR = 0.001;
|
||||
|
||||
public function verifyTrainingResults(NetworkTraining $training, array $expectedWeights, int $expectedEpochs): void
|
||||
public function verifyTrainingResults(NetworkTraining $training, array $expectedWeights, int $expectedEpochs, float $marginOfError = self::DEFAULT_MARGIN_OF_ERROR): void
|
||||
{
|
||||
$training->start();
|
||||
|
||||
|
||||
// Assert that the final synaptic weights are as expected withing the margin of error
|
||||
$finalWeights = $training->getSynapticWeights();
|
||||
$this->assertEqualsWithDelta($expectedWeights, $finalWeights, self::MARGIN_OF_ERROR, "Final synaptic weights do not match expected values.");
|
||||
$this->assertEqualsWithDelta($expectedWeights, $finalWeights, $marginOfError, "Final synaptic weights do not match expected values.");
|
||||
|
||||
// Assert that the number of epochs taken is as expected
|
||||
$this->assertEquals($expectedEpochs, $training->getEpoch(), "Expected training to take $expectedEpochs epochs, but it took {$training->getEpoch()} epochs.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user