Compare commits

...

67 Commits

Author SHA1 Message Date
Ninluc 6545d23e4f Revert "Fix reverb host"
linter / quality (push) Has been cancelled
tests / ci (8.3) (push) Has been cancelled
This reverts commit 24b2d29a41.
2026-10-04 17:07:20 +02:00
Ninluc 24b2d29a41 Fix reverb host
linter / quality (push) Has been cancelled
tests / ci (8.3) (push) Has been cancelled
2026-10-04 17:05:06 +02:00
Ninluc 151de1dcb8 Responsive
linter / quality (push) Successful in 4m3s
tests / ci (8.3) (push) Successful in 3m55s
2026-09-23 20:24:32 +02:00
Ninluc d3f914ba9a Footer links 2026-09-23 20:03:55 +02:00
Ninluc 5f4f751406 Fix spelling
linter / quality (push) Successful in 3m54s
tests / ci (8.3) (push) Successful in 3m31s
2026-09-22 10:26:34 +02:00
Ninluc d5409b11b7 Ajusted default error
linter / quality (push) Successful in 4m17s
tests / ci (8.3) (push) Successful in 3m36s
2026-09-22 10:13:37 +02:00
Ninluc 7cb528406c Optimize docker build
linter / quality (push) Successful in 3m58s
tests / ci (8.3) (push) Successful in 3m25s
2026-09-21 12:17:05 +02:00
Ninluc e10b2f8d1f Revert "Multi architecture build"
linter / quality (push) Successful in 8m47s
tests / ci (8.3) (push) Successful in 7m11s
This reverts commit 3efc323d28.
2026-09-21 11:40:02 +02:00
Ninluc 646e4841bb Better lint workflow 2026-09-21 09:49:47 +02:00
Ninluc 3efc323d28 Multi architecture build
linter / quality (push) Successful in 4m17s
tests / ci (8.3) (push) Successful in 5m38s
2026-09-21 09:36:25 +02:00
Ninluc 21c02706de Dark mode by default
linter / quality (push) Successful in 4m25s
tests / ci (8.3) (push) Successful in 4m17s
2026-09-19 18:04:29 +02:00
Ninluc 7c223e0ba8 Increase max request time
linter / quality (push) Successful in 5m1s
tests / ci (8.3) (push) Successful in 3m47s
2026-09-19 15:28:52 +02:00
Ninluc 6d8e3438f0 Track some events
linter / quality (push) Has been cancelled
tests / ci (8.3) (push) Successful in 3m57s
2026-09-19 10:43:05 +02:00
Ninluc ae676343a5 Fix send cancel request
linter / quality (push) Successful in 5m1s
tests / ci (8.3) (push) Successful in 4m28s
2026-09-18 23:58:51 +02:00
Ninluc 9389aef632 Fix cancel route
linter / quality (push) Has been cancelled
tests / ci (8.3) (push) Successful in 3m49s
2026-09-18 23:41:31 +02:00
Ninluc fa6c427d76 Fix cancel ?
linter / quality (push) Failing after 1m3s
tests / ci (8.3) (push) Successful in 3m48s
2026-09-18 23:24:13 +02:00
Ninluc 25b03fc39a Cancel Training
linter / quality (push) Failing after 1m11s
tests / ci (8.3) (push) Successful in 3m55s
2026-09-18 23:08:19 +02:00
Ninluc d80bfe3cdd Updated Interface Image and added excalidraws
linter / quality (push) Successful in 6m26s
tests / ci (8.3) (push) Successful in 5m11s
2026-09-18 14:19:17 +02:00
Ninluc 2332e805e8 Logarithmic error scale
linter / quality (push) Successful in 5m12s
tests / ci (8.3) (push) Successful in 4m29s
2026-09-18 14:12:23 +02:00
Ninluc 4610334f72 Do not exclude get parameters
linter / quality (push) Successful in 4m54s
tests / ci (8.3) (push) Successful in 4m2s
2026-09-18 09:03:46 +02:00
Ninluc 9ae3a6942b Default perrceptron to simple
linter / quality (push) Successful in 4m36s
tests / ci (8.3) (push) Successful in 3m48s
2026-09-17 21:55:58 +02:00
Ninluc 3a19691591 Remove debug config value
linter / quality (push) Successful in 4m11s
tests / ci (8.3) (push) Successful in 3m39s
2026-09-17 21:46:49 +02:00
Ninluc 2d7c1873bb Fix Bar chart
linter / quality (push) Successful in 5m36s
tests / ci (8.3) (push) Successful in 4m42s
2026-09-17 21:24:42 +02:00
Ninluc 5d68757e9d Fix Reverb ?
linter / quality (push) Successful in 6m20s
tests / ci (8.3) (push) Successful in 4m57s
2026-09-17 21:01:15 +02:00
Ninluc 2de27af03b Some anonymous analytics I promise 2026-09-17 20:58:06 +02:00
Ninluc be7b276dec Remove asset building for test 2026-09-17 19:56:38 +02:00
Ninluc 2345b850b8 ix scatter graph
linter / quality (push) Successful in 5m52s
tests / ci (8.3) (push) Successful in 6m19s
2026-09-17 19:45:02 +02:00
Ninluc 538ddc9679 Fix reverb
linter / quality (push) Successful in 6m42s
tests / ci (8.3) (push) Successful in 5m46s
2026-09-17 18:58:54 +02:00
Ninluc 099318c79e USE secrets
linter / quality (push) Successful in 4m10s
tests / ci (8.3) (push) Successful in 4m21s
2026-09-17 09:30:50 +02:00
Ninluc 1517920fce Use env var
linter / quality (push) Successful in 4m46s
tests / ci (8.3) (push) Successful in 4m23s
2026-09-16 23:06:34 +02:00
Ninluc d4f98517cd misc
linter / quality (push) Successful in 4m27s
tests / ci (8.3) (push) Successful in 4m9s
2026-09-16 22:41:11 +02:00
Ninluc feac0feaeb Trust proxies 2026-09-16 22:41:03 +02:00
Ninluc ba4b951a69 Revert "Migrate database"
linter / quality (push) Successful in 5m8s
tests / ci (8.3) (push) Successful in 5m49s
This reverts commit 41b0e8a64a.
2026-09-16 21:58:12 +02:00
Ninluc 41b0e8a64a Migrate database
linter / quality (push) Failing after 2m48s
tests / ci (8.3) (push) Successful in 4m38s
2026-09-16 21:34:38 +02:00
Ninluc da53bf16e9 Fix repository name
linter / quality (push) Successful in 4m11s
tests / ci (8.3) (push) Successful in 4m12s
2026-09-16 20:50:39 +02:00
Ninluc 3ad2e7d999 Fix tests versions 2026-09-16 20:47:33 +02:00
Ninluc 00f379811f Renammed project 2026-09-16 20:35:08 +02:00
Ninluc 3f1fcef1bc Fix docker publish work
linter / quality (push) Successful in 5m1s
tests / ci (8.4) (push) Successful in 4m14s
tests / ci (8.5) (push) Successful in 4m17s
2026-09-16 20:34:36 +02:00
Ninluc 5d71962893 Docker and CICD
linter / quality (push) Successful in 4m26s
tests / ci (8.4) (push) Successful in 4m24s
tests / ci (8.5) (push) Successful in 4m24s
2026-09-16 20:03:18 +02:00
Ninluc 9086364db7 Form error message
linter / quality (push) Successful in 4m19s
tests / ci (8.4) (push) Successful in 5m21s
tests / ci (8.5) (push) Successful in 4m34s
2026-09-16 19:27:27 +02:00
Ninluc 6d4fdffee9 Wiki
linter / quality (push) Successful in 5m1s
tests / ci (8.4) (push) Successful in 5m8s
tests / ci (8.5) (push) Successful in 6m31s
2026-09-15 19:44:58 +02:00
Ninluc ca6c9fe38c Reworked the table scroll
linter / quality (push) Successful in 5m3s
tests / ci (8.4) (push) Failing after 4m29s
tests / ci (8.5) (push) Failing after 5m27s
2026-09-09 19:34:30 +02:00
Ninluc d9132fe6ee Removed non gradient version
linter / quality (push) Successful in 7m40s
tests / ci (8.4) (push) Failing after 8m38s
tests / ci (8.5) (push) Failing after 6m49s
2026-09-09 17:20:19 +02:00
Ninluc e42849bed4 Rate limiting 2026-09-09 17:20:11 +02:00
Ninluc 69e683bcaf Some bugfixes and misc
linter / quality (push) Successful in 4m24s
tests / ci (8.4) (push) Successful in 4m47s
tests / ci (8.5) (push) Successful in 5m0s
2026-09-08 20:01:52 +02:00
Ninluc 0e177f8491 Refactor and optimizations 2026-09-08 19:06:02 +02:00
Ninluc 8a50f492ac Various fixess and xor
linter / quality (push) Successful in 4m23s
tests / ci (8.4) (push) Successful in 4m35s
tests / ci (8.5) (push) Successful in 5m13s
2026-09-08 18:40:56 +02:00
Ninluc 08aa04fe56 Multilayer neuron network
linter / quality (push) Successful in 7m10s
tests / ci (8.4) (push) Successful in 4m43s
tests / ci (8.5) (push) Successful in 4m47s
2026-09-08 16:29:02 +02:00
Ninluc 2f4db07918 MonoLayer Perceptron
linter / quality (push) Successful in 6m16s
tests / ci (8.4) (push) Successful in 4m10s
tests / ci (8.5) (push) Successful in 4m29s
2026-04-04 16:45:04 +02:00
Ninluc f6620c2eca Fixed graph showing wrong decision border for regression
linter / quality (push) Successful in 8m11s
tests / ci (8.4) (push) Successful in 10m8s
tests / ci (8.5) (push) Successful in 12m59s
2026-03-24 17:33:58 +01:00
Ninluc ca9c0dc511 Misc Fixes for ADALINE 2026-03-24 17:12:15 +01:00
Ninluc 0876588550 Changed Favicon
linter / quality (push) Successful in 5m21s
tests / ci (8.4) (push) Successful in 6m49s
tests / ci (8.5) (push) Successful in 7m14s
2026-03-24 10:48:50 +01:00
Ninluc a37afcec07 Added brianium/paratest to run test in parallel
linter / quality (push) Successful in 8m4s
tests / ci (8.4) (push) Successful in 8m27s
tests / ci (8.5) (push) Successful in 8m26s
2026-03-23 16:33:58 +01:00
Ninluc b052d792f8 Added Cache to CICD
linter / quality (push) Successful in 11m58s
tests / ci (8.4) (push) Failing after 7m52s
tests / ci (8.5) (push) Has been cancelled
2026-03-23 16:13:12 +01:00
Ninluc 5880024933 Fix linting
linter / quality (push) Successful in 7m44s
tests / ci (8.5) (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
2026-03-23 16:02:07 +01:00
Ninluc a92a47288c Fixed Regression datasets
linter / quality (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled
2026-03-23 16:01:22 +01:00
Ninluc bcaf334380 Added pointer cursor to selects 2026-03-23 16:01:10 +01:00
Ninluc dea908c63e Modified limitedEventBuffer to send in linear delay 2026-03-23 16:00:51 +01:00
Ninluc 236fa503fb Fix setup command
linter / quality (push) Failing after 9m48s
tests / ci (8.4) (push) Successful in 9m2s
tests / ci (8.5) (push) Successful in 8m50s
2026-03-23 13:57:02 +01:00
Ninluc 0f92af4a1e Added Reverb terminal color 2026-03-23 13:56:53 +01:00
Ninluc ef90236adc Fix linting
linter / quality (push) Failing after 6m26s
tests / ci (8.4) (push) Successful in 5m6s
tests / ci (8.5) (push) Successful in 5m38s
2026-03-23 08:44:50 +01:00
Ninluc 9d4b02fab5 Light mode support
linter / quality (push) Failing after 6m3s
tests / ci (8.4) (push) Successful in 5m40s
tests / ci (8.5) (push) Successful in 5m1s
2026-03-22 17:14:23 +01:00
Ninluc 88a932391b Renamed tests + added some + misc
linter / quality (push) Successful in 7m39s
tests / ci (8.4) (push) Successful in 6m10s
tests / ci (8.5) (push) Has been cancelled
2026-03-22 16:57:12 +01:00
Ninluc abb16aa6c1 Added ADALINE training
linter / quality (push) Successful in 3m25s
tests / ci (8.4) (push) Successful in 3m52s
tests / ci (8.5) (push) Successful in 3m35s
2026-03-22 15:54:04 +01:00
Ninluc 29498e45ac Fix tests
linter / quality (push) Successful in 4m10s
tests / ci (8.4) (push) Successful in 3m20s
tests / ci (8.5) (push) Successful in 3m12s
2026-03-22 15:21:35 +01:00
Ninluc af67830fbb fixed error graph + added toggle only epoch error
linter / quality (push) Successful in 5m36s
tests / ci (8.4) (push) Failing after 4m11s
tests / ci (8.5) (push) Failing after 4m37s
2026-03-22 14:58:53 +01:00
Ninluc 42e07de287 Rafactored Perceptrons and network training 2026-03-22 14:58:34 +01:00
154 changed files with 42481 additions and 931 deletions
+56
View File
@@ -0,0 +1,56 @@
APP_NAME="PerceptronViewer"
APP_ENV=production
APP_KEY=base64:WE5DlJGDeIrl+B1ZqahVJSg4TN6oY3N1ph8CDKNo0w4=
APP_DEBUG=false
APP_URL=https://perceptron.matthiasg.dev
ASSET_URL=https://perceptron.matthiasg.dev
APP_LOCALE=fr
APP_FALLBACK_LOCALE=fr
APP_FAKER_LOCALE=fr_FR
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12
LOG_CHANNEL=single
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
BROADCAST_CONNECTION=reverb
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
# CACHE_PREFIX=
MEMCACHED_HOST=memcached
VITE_APP_NAME="${APP_NAME}"
REVERB_SERVER_HOST=0.0.0.0
REVERB_SERVER_PORT=8080
REVERB_APP_ID=848245
REVERB_APP_KEY=hkybzbkdvdl1hsvtzuqk
REVERB_APP_SECRET=oosuq0v9jgaslzp9cmhv
REVERB_HOST="perceptron.matthiasg.dev"
REVERB_PORT=443
REVERB_SCHEME=https
REVERB_BROADCAST_HOST=reverb
REVERB_BROADCAST_PORT=8080
REVERB_BROADCAST_SCHEME=http
REVERB_MAX_REQUEST_SIZE=100000
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
+61
View File
@@ -0,0 +1,61 @@
name: Build and Push Docker Image
on:
workflow_run:
workflows: ["tests"]
types:
- completed
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
env:
RUNNER_TOOL_CACHE: /toolcache
REVERB_APP_KEY: ${{ secrets.REVERB_APP_KEY }}
REVERB_APP_SECRET: ${{ secrets.REVERB_APP_SECRET }}
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: Get version
id: get_version
run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
registry: git.matthiasg.dev
username: ninluc
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VITE_REVERB_APP_KEY=${{ secrets.REVERB_APP_KEY }}
VITE_REVERB_HOST=perceptron.matthiasg.dev
VITE_REVERB_PORT=443
VITE_REVERB_SCHEME=https
tags: |
git.matthiasg.dev/ninluc/perceptron-viewer/perceptron-viewer:latest
git.matthiasg.dev/ninluc/perceptron-viewer/perceptron-viewer:${{ steps.get_version.outputs.version }}
# notify:
# needs: build
# runs-on: ubuntu-latest
# steps:
# - name: Notify Watchtower
# run: "curl -i -X POST -H \"Authorization: Bearer ${{ secrets.WATCHTOWER_TOKEN }}\" https://watchtower.beermaker.matthiasg.dev/v1/update"
+81
View File
@@ -0,0 +1,81 @@
name: linter
on:
push:
branches:
- develop
- main
- master
- workos
pull_request:
branches:
- develop
- main
- master
- workos
permissions:
contents: write
jobs:
quality:
runs-on: ubuntu-latest
steps:
- 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
tools: composer:v2
env:
phpexts: none
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
# -------------------------
# Install dependencies
# -------------------------
- name: Install Dependencies
run: |
composer install --no-interaction --prefer-dist --no-progress --no-scripts
npm ci
# -------------------------
# Run linters in parallel
# -------------------------
- name: Run linters
run: |
composer lint &
npm run format &
npm run lint &
wait
+90
View File
@@ -0,0 +1,90 @@
name: tests
on:
push:
branches:
- develop
- main
- master
- workos
pull_request:
branches:
- develop
- main
- master
- workos
jobs:
ci:
runs-on: ubuntu-latest
strategy:
matrix:
php-version: ['8.3']
steps:
- 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: none
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
# -------------------------
# Install dependencies
# -------------------------
- name: Install Node Dependencies
run: npm ci
- name: Install PHP Dependencies
run: composer install --no-interaction --prefer-dist --optimize-autoloader --no-progress
# -------------------------
# 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
# -------------------------
# Run tests (parallel)
# -------------------------
- name: Run Tests
run: php artisan test --parallel
-49
View File
@@ -1,49 +0,0 @@
name: linter
on:
push:
branches:
- develop
- main
- master
- workos
pull_request:
branches:
- develop
- main
- master
- workos
permissions:
contents: write
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
- name: Install Dependencies
run: |
composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
npm install
- 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'
-56
View File
@@ -1,56 +0,0 @@
name: tests
on:
push:
branches:
- develop
- main
- master
- workos
pull_request:
branches:
- develop
- main
- master
- workos
jobs:
ci:
runs-on: ubuntu-latest
strategy:
matrix:
php-version: ['8.4', '8.5']
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
tools: composer:v2
coverage: xdebug
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install Node Dependencies
run: npm i
- name: Install Dependencies
run: composer install --no-interaction --prefer-dist --optimize-autoloader
- name: Copy Environment File
run: cp .env.example .env
- name: Generate Application Key
run: php artisan key:generate
- name: Build Assets
run: npm run build
- name: Tests
run: ./vendor/bin/phpunit
+69
View File
@@ -0,0 +1,69 @@
# Composer dependencies
FROM composer:2 AS vendor
WORKDIR /usr/app
COPY . .
RUN composer install --no-dev --optimize-autoloader --no-interaction
# Build frontend assets
# Install node and npm
RUN apk add --no-cache nodejs npm
RUN npm install
ARG VITE_REVERB_APP_KEY
ARG VITE_REVERB_HOST
ARG VITE_REVERB_PORT=443
ARG VITE_REVERB_SCHEME=https
ENV VITE_REVERB_APP_KEY=$VITE_REVERB_APP_KEY \
VITE_REVERB_HOST=$VITE_REVERB_HOST \
VITE_REVERB_PORT=$VITE_REVERB_PORT \
VITE_REVERB_SCHEME=$VITE_REVERB_SCHEME
RUN npm run build
# Final PHP image
FROM dunglas/frankenphp:1.12.2-php8.3-alpine AS final
COPY docker/php.ini-production "$PHP_INI_DIR/php.ini"
# Install system dependencies
RUN apk add --no-cache \
bash \
libpng-dev \
libjpeg-turbo-dev \
freetype-dev \
oniguruma-dev \
zip \
unzip \
git \
curl \
libpq-dev \
php-pgsql
# Install PHP extensions
RUN docker-php-ext-install pdo pgsql pdo_pgsql mbstring exif pcntl bcmath gd
WORKDIR /app
# Copy app
COPY . .
RUN mv .env.docker .env
# Copy dependencies
COPY --from=vendor /usr/app/vendor ./vendor
# Copy built frontend
COPY --from=vendor /usr/app/public/build ./public/build
# Permissions
#RUN chown -R www-data:www-data /app
#RUN chown www-data:www-data /app/dockerEntryPoint.sh
RUN chmod +x /app/dockerEntryPoint.sh
HEALTHCHECK --interval=30s --timeout=30s --start-period=30s --retries=3 CMD curl -f http://127.0.0.1/up || exit 1
EXPOSE 80
EXPOSE 8080
CMD ["./dockerEntryPoint.sh"]
+3 -3
View File
@@ -1,4 +1,4 @@
# Perceptron and neuronal networks # PerceptronViewer
Using Laravel and Vue JS Using Laravel and Vue JS
@@ -23,10 +23,10 @@ Using Laravel and Vue JS
3. NodeJs (Node + NPM) 3. NodeJs (Node + NPM)
<https://nodejs.org/en/download> <https://nodejs.org/en/download>
2. Install dependencies 2. Setup project and install dependencies
```shell ```shell
composer install composer run setup
``` ```
## Running the project ## Running the project
+1
View File
@@ -0,0 +1 @@
1.0.0
+3 -4
View File
@@ -5,11 +5,11 @@ namespace App\Events;
use App\Models\ActivationsFunctions; use App\Models\ActivationsFunctions;
use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class PerceptronInitialization implements ShouldBroadcast class PerceptronInitialization implements ShouldBroadcastNow
{ {
use Dispatchable, InteractsWithSockets, SerializesModels; use Dispatchable, InteractsWithSockets, SerializesModels;
@@ -21,8 +21,7 @@ class PerceptronInitialization implements ShouldBroadcast
public ActivationsFunctions $activationFunction, public ActivationsFunctions $activationFunction,
public string $sessionId, public string $sessionId,
public string $trainingId, public string $trainingId,
) ) {
{
// //
} }
+3 -6
View File
@@ -4,13 +4,11 @@ namespace App\Events;
use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel; use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class PerceptronTrainingEnded implements ShouldBroadcast class PerceptronTrainingEnded implements ShouldBroadcastNow
{ {
use Dispatchable, InteractsWithSockets, SerializesModels; use Dispatchable, InteractsWithSockets, SerializesModels;
@@ -21,8 +19,7 @@ class PerceptronTrainingEnded implements ShouldBroadcast
public string $reason, public string $reason,
public string $sessionId, public string $sessionId,
public string $trainingId, public string $trainingId,
) ) {
{
// //
} }
+37 -5
View File
@@ -4,11 +4,12 @@ namespace App\Events;
use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Arr;
class PerceptronTrainingIteration implements ShouldBroadcast class PerceptronTrainingIteration implements ShouldBroadcastNow
{ {
use Dispatchable, InteractsWithSockets, SerializesModels; use Dispatchable, InteractsWithSockets, SerializesModels;
@@ -19,8 +20,7 @@ class PerceptronTrainingIteration implements ShouldBroadcast
public array $iterations, // ["epoch" => int, "exampleIndex" => int, "error" => float, "synaptic_weights" => array] public array $iterations, // ["epoch" => int, "exampleIndex" => int, "error" => float, "synaptic_weights" => array]
public string $sessionId, public string $sessionId,
public string $trainingId, public string $trainingId,
) ) {
{
// //
} }
@@ -39,9 +39,41 @@ class PerceptronTrainingIteration implements ShouldBroadcast
public function broadcastWith(): array public function broadcastWith(): array
{ {
$iterations = self::normalizeForJson($this->iterations);
$weights = collect($iterations)
->pluck('weights')
->first(fn (array $weights): bool => $weights !== []);
$shouldBroadcastAllWeights = $weights !== null
&& count(Arr::flatten($weights)) <= config('perceptron.max_displayed_weights');
$lastIterationIndex = count($iterations) - 1;
$iterations = array_map(
fn (array $iteration, int $index): array => $shouldBroadcastAllWeights || $index === $lastIterationIndex
? $iteration
: [...$iteration, 'weights' => []],
$iterations,
array_keys($iterations),
);
return [ return [
'iterations' => $this->iterations, 'iterations' => $iterations,
'trainingId' => $this->trainingId, 'trainingId' => $this->trainingId,
]; ];
} }
public static function normalizeForJson(mixed $value): mixed
{
if (is_float($value) && ! is_finite($value)) {
return null;
}
if (! is_array($value)) {
return $value;
}
return array_map(
fn (mixed $item): mixed => self::normalizeForJson($item),
$value,
);
}
} }
@@ -0,0 +1,9 @@
<?php
namespace App\Exceptions;
use RuntimeException;
class TrainingCancelledException extends RuntimeException
{
}
+153 -31
View File
@@ -3,25 +3,47 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Events\PerceptronInitialization; use App\Events\PerceptronInitialization;
use App\Models\GradientDescentPerceptronTraining; use App\Exceptions\TrainingCancelledException;
use App\Models\SimpleBinaryPerceptronTraining; use App\Http\Requests\RunPerceptronRequest;
use App\Models\NetworksTraining\ADALINEPerceptronTraining;
use App\Models\NetworksTraining\GradientDescentPerceptronTraining;
use App\Models\NetworksTraining\MonoLayerPerceptronTraining;
use App\Models\NetworksTraining\MultiLayerPerceptronTraining;
use App\Models\NetworksTraining\SimpleBinaryPerceptronTraining;
use App\Services\DatasetReader\IDataSetReader; use App\Services\DatasetReader\IDataSetReader;
use App\Services\DatasetReader\LinearOrderDataSetReader; use App\Services\DatasetReader\LinearOrderDataSetReader;
use App\Services\DatasetReader\RandomOrderDataSetReader;
use App\Services\IterationEventBuffer\PerceptronIterationEventBuffer; use App\Services\IterationEventBuffer\PerceptronIterationEventBuffer;
use App\Services\IterationEventBuffer\PerceptronLimitedEpochEventBuffer; use App\Services\IterationEventBuffer\PerceptronLimitedEpochEventBuffer;
use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider; use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider;
use App\Services\SynapticWeightsProvider\RandomSynapticWeights;
use App\Services\SynapticWeightsProvider\ZeroSynapticWeights; use App\Services\SynapticWeightsProvider\ZeroSynapticWeights;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Tests\Services\IterationEventBuffer\DullIterationEventBuffer; use Illuminate\Support\Facades\Cache;
class PerceptronController extends Controller class PerceptronController extends Controller
{ {
private function cancellationKey(string $trainingId): string
{
return "perceptron-training-cancelled:{$trainingId}";
}
public function cancel(Request $request)
{
$trainingId = $request->validate([
'training_id' => ['required', 'string', 'max:100'],
])['training_id'];
Cache::put($this->cancellationKey($trainingId), true, now()->addHour());
return response()->noContent();
}
/** /**
* Display the specified resource. * Display the specified resource.
*/ */
public function index(Request $request) public function index(Request $request)
{ {
$perceptronType = $request->query('type'); $perceptronType = $request->query('type', 'simple'); ;
$learningRate = 0.01; $learningRate = 0.01;
$maxIterations = 200; $maxIterations = 200;
@@ -32,8 +54,20 @@ class PerceptronController extends Controller
$learningRate = 0.015; $learningRate = 0.015;
$maxIterations = 150; $maxIterations = 150;
break; break;
case 'adaline':
$learningRate = 0.00003;
break;
case 'gradientdescent': case 'gradientdescent':
$learningRate = 0.00003; $learningRate = 0.00003;
$maxIterations = 300;
break;
case 'monolayer':
$learningRate = 0.001;
break;
case 'multilayer':
$learningRate = 0.8;
$maxIterations = 2000;
break;
} }
return inertia('PerceptronViewer', [ return inertia('PerceptronViewer', [
@@ -43,6 +77,8 @@ class PerceptronController extends Controller
'minError' => $minError, 'minError' => $minError,
'learningRate' => $learningRate, 'learningRate' => $learningRate,
'maxIterations' => $maxIterations, 'maxIterations' => $maxIterations,
'maxIterationsLimit' => config('perceptron.max_iterations'),
'maxDisplayedWeights' => config('perceptron.max_displayed_weights'),
]); ]);
} }
@@ -53,9 +89,14 @@ class PerceptronController extends Controller
$datasets = []; $datasets = [];
foreach ($files as $file) { foreach ($files as $file) {
if (pathinfo($file, PATHINFO_EXTENSION) === 'csv') { if (pathinfo($file, PATHINFO_EXTENSION) === 'csv') {
if (str_starts_with($file, 'hidden')) {
continue;
}
$dataset = []; $dataset = [];
$dataset['label'] = str_replace('.csv', '', $file); $dataset['label'] = str_replace('.csv', '', $file);
$dataSetReader = new LinearOrderDataSetReader($dataSetsDirectory.'/'.$file); $dataSetReader = new LinearOrderDataSetReader($dataSetsDirectory.'/'.$file);
$dataset['inputCount'] = count($dataSetReader->lines[0]) - 1;
$dataset['data'] = []; $dataset['data'] = [];
switch (count($dataSetReader->lines[0])) { switch (count($dataSetReader->lines[0])) {
case 3: case 3:
@@ -82,86 +123,167 @@ class PerceptronController extends Controller
} }
switch ($dataset['label']) { switch ($dataset['label']) {
case 'logic_and_gradient': case 'Classification_-_Porte_logique_ET_(linéairement_séparable)':
switch ($perceptronType) { switch ($perceptronType) {
case 'gradientdescent': case 'gradientdescent':
$dataset['defaultLearningRate'] = 0.3; $dataset['defaultLearningRate'] = 0.3;
$dataset['defaultMinError'] = 0.125; $dataset['defaultMinError'] = 0.125;
break; break;
case 'adaline':
$dataset['defaultLearningRate'] = 0.05;
$dataset['defaultMinError'] = 0.125;
break;
case 'monolayer':
$dataset['defaultMinError'] = 0.001;
break;
} }
break; break;
case 'table_2_9': case 'Classification_-_Porte_logique_XOR_(non_linéairement_séparable)':
switch ($perceptronType) {
case 'multilayer':
$dataset['defaultLearningRate'] = 0.3;
$dataset['defaultMinError'] = 0.001;
$dataset['defaultMaxIterations'] = 500;
$dataset['defaultHiddenLayers'] = 1;
$dataset['defaultHiddenLayersNeurons'] = 2;
break;
}
break;
case 'Classification_-_Oblique_(linéairement_séparable)':
switch ($perceptronType) { switch ($perceptronType) {
case 'simple': case 'simple':
$dataset['defaultLearningRate'] = 0.015; $dataset['defaultLearningRate'] = 0.015;
break; break;
case 'gradientdescent': case 'gradientdescent':
case 'adaline':
$dataset['defaultLearningRate'] = 0.001; $dataset['defaultLearningRate'] = 0.001;
$dataset['defaultMinError'] = 0.09;
$dataset['defaultMaxIterations'] = 2000;
break; break;
} }
break; break;
case 'table_2_11': case 'Classification_-_Oblique_modifié_(non_linéairement_séparable)':
$dataset['defaultMinError'] = 1.0; switch ($perceptronType) {
case 'gradientdescent':
case 'adaline':
$dataset['defaultLearningRate'] = 0.001;
$dataset['defaultMinError'] = 0.3;
$dataset['defaultMaxIterations'] = 2000;
break;
}
break;
case 'Régression_-_Oblique':
$dataset['defaultMinError'] = 0.02;
switch ($perceptronType) {
case 'gradientdescent':
case 'adaline':
$dataset['defaultMinError'] = 0.68;
$dataset['defaultMaxIterations'] = 100;
break;
case 'monolayer':
$dataset['defaultLearningRate'] = 0.0015;
break;
}
break;
case 'Classification_-_3_classes_(linéairement_séparable)':
switch ($perceptronType) {
case 'monolayer':
$dataset['defaultLearningRate'] = 0.005;
$dataset['defaultMaxIterations'] = 100;
break;
}
break;
case 'Classification_-_Donut_(non_linéairement_séparable)':
switch ($perceptronType) {
case 'multilayer':
$dataset['defaultLearningRate'] = 0.8;
$dataset['defaultMinError'] = 0.0001;
$dataset['defaultMaxIterations'] = 2000;
$dataset['defaultHiddenLayers'] = 3;
$dataset['defaultHiddenLayersNeurons'] = 4;
break;
}
break;
case 'Régression_-_Vague':
$dataset['defaultMinError'] = 0.055;
switch ($perceptronType) {
case 'multilayer':
$dataset['defaultLearningRate'] = 0.3;
$dataset['defaultMaxIterations'] = 400;
$dataset['defaultHiddenLayers'] = 2;
$dataset['defaultHiddenLayersNeurons'] = 2;
break;
}
break; break;
} }
$datasets[] = $dataset; $datasets[] = $dataset;
} }
} }
return $datasets; return $datasets;
} }
private function getDataSetReader(string $dataSet): IDataSetReader private function getDataSetReader(string $dataSet): IDataSetReader
{ {
$dataSetFileName = "data_sets/{$dataSet}.csv"; $dataSetFileName = "data_sets/{$dataSet}.csv";
return new LinearOrderDataSetReader($dataSetFileName);
return new RandomOrderDataSetReader($dataSetFileName);
} }
public function run(Request $request, ISynapticWeightsProvider $synapticWeightsProvider) public function run(RunPerceptronRequest $request, ISynapticWeightsProvider $synapticWeightsProvider)
{ {
$startTime = microtime(true);
$perceptronType = $request->input('type'); $perceptronType = $request->input('type');
$hiddenLayers = $request->input('hidden_layers', 2);
$hiddenLayersNeurons = $request->input('hidden_layers_neurons', 3);
$minError = $request->input('min_error', 0.01); $minError = $request->input('min_error', 0.01);
$weightInitMethod = $request->input('weight_init_method', 'random'); $weightInitMethod = $request->input('weight_init_method', 'random');
$dataSet = $request->input('dataset'); $dataSet = $request->input('dataset');
$learningRate = $request->input('learning_rate', 0.015); $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()); $sessionId = $request->input('session_id', session()->getId());
$trainingId = $request->input('training_id'); $trainingId = $request->input('training_id');
if ($weightInitMethod === 'zeros') { Cache::forget($this->cancellationKey($trainingId));
$synapticWeightsProvider = new ZeroSynapticWeights();
// Zero initialization prevents hidden layers from receiving a gradient.
if ($perceptronType === 'multilayer' && $weightInitMethod === 'zeros') {
$synapticWeightsProvider = new RandomSynapticWeights;
} elseif ($weightInitMethod === 'zeros') {
$synapticWeightsProvider = new ZeroSynapticWeights;
} }
$iterationEventBuffer = new PerceptronIterationEventBuffer($sessionId, $trainingId); $iterationEventBuffer = new PerceptronIterationEventBuffer($sessionId, $trainingId);
if ($maxIterations > config('perceptron.limited_broadcast_iterations')) { if ($maxEpochs > config('perceptron.limited_broadcast_iterations')) {
$iterationsInterval = (int)($maxIterations / config('perceptron.limited_broadcast_iterations')); $iterationsInterval = (int) ($maxEpochs / config('perceptron.limited_broadcast_iterations'));
$iterationEventBuffer = new PerceptronLimitedEpochEventBuffer($sessionId, $trainingId, $iterationsInterval); $iterationEventBuffer = new PerceptronLimitedEpochEventBuffer($sessionId, $trainingId, $iterationsInterval);
} }
$dataSetReader = $this->getDataSetReader($dataSet); $datasetReader = $this->getDataSetReader($dataSet);
$networkTraining = match ($perceptronType) { $networkTraining = match ($perceptronType) {
'simple' => new SimpleBinaryPerceptronTraining($dataSetReader, $learningRate, $maxIterations, $synapticWeightsProvider, $iterationEventBuffer, $sessionId, $trainingId), 'simple' => new SimpleBinaryPerceptronTraining($datasetReader, $learningRate, $maxEpochs, $synapticWeightsProvider, $iterationEventBuffer, $sessionId, $trainingId, fn (): bool => connection_aborted() || Cache::has($this->cancellationKey($trainingId))),
'gradientdescent' => new GradientDescentPerceptronTraining($dataSetReader, $learningRate, $maxIterations, $synapticWeightsProvider, $iterationEventBuffer, $sessionId, $trainingId, $minError), 'gradientdescent' => new GradientDescentPerceptronTraining($datasetReader, $learningRate, $maxEpochs, $synapticWeightsProvider, $iterationEventBuffer, $sessionId, $trainingId, $minError, fn (): bool => connection_aborted() || Cache::has($this->cancellationKey($trainingId))),
'gradientdescentTest' => new GradientDescentPerceptronTraining( 'adaline' => new ADALINEPerceptronTraining($datasetReader, $learningRate, $maxEpochs, $synapticWeightsProvider, $iterationEventBuffer, $sessionId, $trainingId, $minError, fn (): bool => connection_aborted() || Cache::has($this->cancellationKey($trainingId))),
datasetReader: new LinearOrderDataSetReader(public_path('data_sets/logic_and_gradient.csv')), 'monolayer' => new MonoLayerPerceptronTraining($datasetReader, $learningRate, $maxEpochs, $synapticWeightsProvider, $iterationEventBuffer, $sessionId, $trainingId, $minError, fn (): bool => connection_aborted() || Cache::has($this->cancellationKey($trainingId))),
learningRate: 0.2, 'multilayer' => new MultiLayerPerceptronTraining($datasetReader, $learningRate, $maxEpochs, $hiddenLayers, $hiddenLayersNeurons, $synapticWeightsProvider, $iterationEventBuffer, $sessionId, $trainingId, $minError, fn (): bool => connection_aborted() || Cache::has($this->cancellationKey($trainingId))),
maxEpochs: 100,
synapticWeightsProvider: new ZeroSynapticWeights(),
iterationEventBuffer: $iterationEventBuffer,
sessionId: 'test-session',
trainingId: 'test-training',
minError: 0.125001,
),
default => null, default => null,
}; };
event(new PerceptronInitialization($dataSetReader->lines, $networkTraining->activationFunction, $sessionId, $trainingId)); event(new PerceptronInitialization($datasetReader->lines, $networkTraining->activationFunction, $sessionId, $trainingId));
try {
$networkTraining->start(); $networkTraining->start();
} catch (TrainingCancelledException) {
$networkTraining->cancel();
Cache::forget($this->cancellationKey($trainingId));
}
return response()->json([ return back()->with('success', [
'message' => 'Training completed', 'message' => 'Training completed',
'execution_time' => microtime(true) - $startTime,
]); ]);
} }
} }
@@ -0,0 +1,29 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class RunPerceptronRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'type' => ['required', 'string', 'in:simple,gradientdescent,adaline,monolayer,multilayer'],
'dataset' => ['required', 'string', 'max:200'],
'weight_init_method' => ['required', 'string', 'in:random,zeros'],
'learning_rate' => ['required', 'numeric', 'min:0'],
'min_error' => ['required', 'numeric', 'min:0'],
'hidden_layers' => ['required', 'integer', 'min:1', 'max:5'],
'hidden_layers_neurons' => ['required', 'integer', 'min:1', 'max:5'],
'max_iterations' => ['required', 'integer', 'min:1', 'max:'.config('perceptron.max_iterations')],
'session_id' => ['required', 'string', 'max:100'],
'training_id' => ['required', 'string', 'max:100'],
];
}
}
-8
View File
@@ -1,8 +0,0 @@
<?php
namespace App\Models;
abstract class Network
{
}
@@ -0,0 +1,107 @@
<?php
namespace App\Models\NetworksTraining;
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;
use Closure;
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,
?Closure $isCancelled = null,
) {
parent::__construct($datasetReader, $maxEpochs, $iterationEventBuffer, $sessionId, $trainingId, $isCancelled);
$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) {
$this->broadcastTrainingEnded('Le perceptron à atteint l\'erreur minimale');
}
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,11 +1,14 @@
<?php <?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\DatasetReader\IDataSetReader;
use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer; use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer;
use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider; use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider;
use Closure;
class GradientDescentPerceptronTraining extends NetworkTraining class GradientDescentPerceptronTraining extends NetworkTraining
{ {
@@ -24,8 +27,9 @@ class GradientDescentPerceptronTraining extends NetworkTraining
string $sessionId, string $sessionId,
string $trainingId, string $trainingId,
private float $minError, private float $minError,
?Closure $isCancelled = null,
) { ) {
parent::__construct($datasetReader, $maxEpochs, $iterationEventBuffer, $sessionId, $trainingId); parent::__construct($datasetReader, $maxEpochs, $iterationEventBuffer, $sessionId, $trainingId, $isCancelled);
$this->perceptron = new GradientDescentPerceptron($synapticWeightsProvider->generate($datasetReader->getInputSize())); $this->perceptron = new GradientDescentPerceptron($synapticWeightsProvider->generate($datasetReader->getInputSize()));
} }
@@ -75,16 +79,17 @@ class GradientDescentPerceptronTraining extends NetworkTraining
protected function stopCondition(): bool 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) { if ($condition === true) {
event(new PerceptronTrainingEnded('Le perceptron à atteint l\'erreur minimale', $this->sessionId, $this->trainingId)); $this->broadcastTrainingEnded('Le perceptron à atteint l\'erreur minimale');
} }
return $condition; 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; $error = $correctOutput - $output;
@@ -0,0 +1,164 @@
<?php
namespace App\Models\NetworksTraining;
use App\Models\ActivationsFunctions;
use App\Models\Perceptrons\GradientDescentPerceptron;
use App\Models\Perceptrons\NetworkPerceptron;
use App\Models\Perceptrons\Perceptron;
use App\Services\DatasetReader\IDataSetReader;
use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer;
use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider;
use App\Services\SynapticWeightsProvider\SimpleNetworkWeightsProvider;
use Closure;
use Illuminate\Support\Arr;
class MonoLayerPerceptronTraining extends NetworkTraining
{
private Perceptron $network;
private array $labels;
private bool $isRegression;
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,
?Closure $isCancelled = null,
) {
parent::__construct($datasetReader, $maxEpochs, $iterationEventBuffer, $sessionId, $trainingId, $isCancelled);
$this->isRegression = $datasetReader->getInputSize() === 1;
$networkWeightsProvider = new SimpleNetworkWeightsProvider($synapticWeightsProvider);
$this->network = new NetworkPerceptron(
$networkWeightsProvider->generate(
$datasetReader->getInputSize(),
$this->isRegression ? 1 : $datasetReader->getOutputSize(),
0, // No hidden layer
0, // No hidden layer neurons
),
$datasetReader->getInputSize(),
GradientDescentPerceptron::class, // No hidden layer
GradientDescentPerceptron::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 = (float) 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) {
$this->broadcastTrainingEnded('Le perceptron à atteint l\'erreur minimale');
}
return $condition;
}
private function iterationFunction(array $inputs, float $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(float $correctOutput): array
{
if ($this->isRegression) {
return [$correctOutput];
}
$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()]];
}
}
@@ -0,0 +1,260 @@
<?php
namespace App\Models\NetworksTraining;
use App\Models\ActivationsFunctions;
use App\Models\Perceptrons\GradientDescentPerceptron;
use App\Models\Perceptrons\NetworkPerceptron;
use App\Models\Perceptrons\Perceptron;
use App\Models\Perceptrons\SigmoidPerceptron;
use App\Services\DatasetReader\IDataSetReader;
use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer;
use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider;
use App\Services\SynapticWeightsProvider\SimpleNetworkWeightsProvider;
use Closure;
use Illuminate\Support\Arr;
class MultiLayerPerceptronTraining extends NetworkTraining
{
private Perceptron $network;
private array $labels;
private bool $isRegression;
public ActivationsFunctions $activationFunction = ActivationsFunctions::SIGMOID;
public ?ActivationsFunctions $presentationLayerActivationFunction = ActivationsFunctions::STEP;
private float $epochError;
public function __construct(
IDataSetReader $datasetReader,
protected float $learningRate,
int $maxEpochs,
protected int $hiddenLayers,
protected int $hiddenLayersNeurons,
ISynapticWeightsProvider $synapticWeightsProvider,
IPerceptronIterationEventBuffer $iterationEventBuffer,
string $sessionId,
string $trainingId,
private float $minError,
?Closure $isCancelled = null,
) {
parent::__construct($datasetReader, $maxEpochs, $iterationEventBuffer, $sessionId, $trainingId, $isCancelled);
$this->labels = $datasetReader->getLabels();
$this->isRegression = $datasetReader->getOutputSize() === 1
|| ($datasetReader->getOutputSize() > 2
&& $datasetReader->getOutputSize() * 2 >= $datasetReader->getEpochExamplesCount());
if ($this->isRegression) {
$this->activationFunction = ActivationsFunctions::LINEAR;
}
$networkWeightsProvider = new SimpleNetworkWeightsProvider($synapticWeightsProvider);
$this->network = new NetworkPerceptron(
$networkWeightsProvider->generate(
$datasetReader->getInputSize(),
$this->isRegression ? 1 : $datasetReader->getOutputSize(),
$this->hiddenLayers,
$this->hiddenLayersNeurons,
),
$datasetReader->getInputSize(),
SigmoidPerceptron::class,
GradientDescentPerceptron::class,
);
}
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->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());
// $this->iterationEventBuffer->flush();
}
// 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) {
$this->broadcastTrainingEnded('Le perceptron à atteint l\'erreur minimale');
}
return $condition;
}
private function iterationFunction(array $inputs, float $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;
}
/**
* Backpropagation of error gradients to update synaptic weights.
*
*/
private function getUpdatedSynapticWeights(array $synaptic_weights, array $iterationError, array $inputs): array
{
$layerInputs = [$inputs];
// Reproduce NetworkPerceptron::test() using each neuron's activation function.
foreach ($synaptic_weights as $layerIndex => $layerWeights) {
$previousLayerOutputs = $layerInputs[array_key_last($layerInputs)];
$layerOutputs = [];
foreach ($layerWeights as $neuronIndex => $neuronWeights) {
$weightedSum = array_sum(array_map(
fn ($input, $weight): float => $input * $weight,
$previousLayerOutputs,
$neuronWeights,
));
$neuron = $this->network->network[$layerIndex + 1][$neuronIndex];
$layerOutputs[] = $neuron->activationFunction($weightedSum);
}
$layerInputs[] = array_merge([1], $layerOutputs);
}
$deltas = array_fill(0, count($synaptic_weights), []);
$lastLayerIndex = count($synaptic_weights) - 1;
// Output delta includes the output neuron's activation derivative.
foreach ($iterationError as $neuronIndex => $error) {
$neuron = $this->network->network[$lastLayerIndex + 1][$neuronIndex];
$output = $layerInputs[$lastLayerIndex + 1][$neuronIndex + 1];
$deltas[$lastLayerIndex][$neuronIndex] =
$error * $this->activationDerivative($neuron, $output);
}
// Hidden-layer deltas use the original, unchanged weights.
for ($layerIndex = $lastLayerIndex - 1; $layerIndex >= 0; $layerIndex--) {
foreach ($synaptic_weights[$layerIndex] as $neuronIndex => $unusedNeuronWeights) {
$nextLayerDelta = 0.0;
foreach ($synaptic_weights[$layerIndex + 1] as $nextNeuronIndex => $nextNeuronWeights) {
// Index zero is the next layer's bias weight.
$nextLayerDelta +=
$nextNeuronWeights[$neuronIndex + 1]
* $deltas[$layerIndex + 1][$nextNeuronIndex];
}
$neuron = $this->network->network[$layerIndex + 1][$neuronIndex];
$output = $layerInputs[$layerIndex + 1][$neuronIndex + 1];
$deltas[$layerIndex][$neuronIndex] =
$nextLayerDelta * $this->activationDerivative($neuron, $output);
}
}
$updatedWeights = [];
foreach ($synaptic_weights as $layerIndex => $layerWeights) {
$updatedLayerWeights = [];
foreach ($layerWeights as $neuronIndex => $neuronWeights) {
$updatedLayerWeights[] = array_map(
fn ($weight, $weightIndex): float => $weight
+ $this->learningRate
* $deltas[$layerIndex][$neuronIndex]
* $layerInputs[$layerIndex][$weightIndex],
$neuronWeights,
array_keys($neuronWeights),
);
}
$updatedWeights[] = $updatedLayerWeights;
}
return $updatedWeights;
}
private function activationDerivative(Perceptron $neuron, float $output): float
{
// Numerical derivative works for any activationFunction implementation.
// $epsilon = 1e-6;
// The activation function requires weighted input, which is unavailable here.
// For sigmoid, use its derivative directly.
if ($neuron instanceof SigmoidPerceptron) {
return $output * (1 - $output);
}
// Linear activation, used commonly by gradient-descent output neurons.
return 1.0;
}
private function getDesiredOutputFromCorrectOutput(float $correctOutput): array
{
if ($this->isRegression) {
return [$correctOutput];
}
$desiredOutput = array_fill(0, count($this->labels), 0);
$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,10 +1,13 @@
<?php <?php
namespace App\Models; namespace App\Models\NetworksTraining;
use App\Events\PerceptronTrainingEnded; use App\Events\PerceptronTrainingEnded;
use App\Exceptions\TrainingCancelledException;
use App\Models\ActivationsFunctions;
use App\Services\DatasetReader\IDataSetReader; use App\Services\DatasetReader\IDataSetReader;
use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer; use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer;
use Closure;
abstract class NetworkTraining abstract class NetworkTraining
{ {
@@ -12,25 +15,28 @@ abstract class NetworkTraining
/** /**
* @abstract * @abstract
* @var ActivationsFunctions
*/ */
public ActivationsFunctions $activationFunction; public ActivationsFunctions $activationFunction;
public ?ActivationsFunctions $presentationLayerActivationFunction = null;
public function __construct( public function __construct(
protected IDataSetReader $datasetReader, protected IDataSetReader $datasetReader,
protected int $maxEpochs, protected int $maxEpochs,
protected IPerceptronIterationEventBuffer $iterationEventBuffer, protected IPerceptronIterationEventBuffer $iterationEventBuffer,
protected string $sessionId, protected string $sessionId,
protected string $trainingId, protected string $trainingId,
) { protected ?Closure $isCancelled = null,
} ) {}
abstract public function start(): void; abstract public function start(): void;
abstract protected function stopCondition(): bool; abstract protected function stopCondition(): bool;
protected function checkPassedMaxIterations(?float $finalError) { protected function checkPassedMaxIterations(?float $finalError)
{
if ($this->epoch >= $this->maxEpochs) { if ($this->epoch >= $this->maxEpochs) {
$message = 'Le nombre maximal d\'epoch a été atteint'; $message = 'Le nombre maximal d\'époques a été atteint';
if ($finalError) { if ($finalError) {
$message .= " avec une erreur finale de $finalError"; $message .= " avec une erreur finale de $finalError";
} }
@@ -39,10 +45,26 @@ abstract class NetworkTraining
} }
} }
protected function addIterationToBuffer(float $error, array $synapticWeights) { protected function broadcastTrainingEnded(string $reason): void
{
$this->iterationEventBuffer->flush();
event(new PerceptronTrainingEnded($reason, $this->sessionId, $this->trainingId));
}
protected function addIterationToBuffer(float $error, array $synapticWeights)
{
if ($this->isCancelled !== null && ($this->isCancelled)()) {
throw new TrainingCancelledException;
}
$this->iterationEventBuffer->addIteration($this->epoch, $this->datasetReader->getLastReadLineIndex(), $error, $synapticWeights); $this->iterationEventBuffer->addIteration($this->epoch, $this->datasetReader->getLastReadLineIndex(), $error, $synapticWeights);
} }
public function cancel(): void
{
$this->broadcastTrainingEnded('Entraînement annulé');
}
public function getEpoch(): int public function getEpoch(): int
{ {
return $this->epoch; return $this->epoch;
@@ -1,15 +1,20 @@
<?php <?php
namespace App\Models; namespace App\Models\NetworksTraining;
use App\Events\PerceptronTrainingEnded; 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\DatasetReader\IDataSetReader;
use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer; use App\Services\IterationEventBuffer\IPerceptronIterationEventBuffer;
use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider; use App\Services\SynapticWeightsProvider\ISynapticWeightsProvider;
use Closure;
class SimpleBinaryPerceptronTraining extends NetworkTraining class SimpleBinaryPerceptronTraining extends NetworkTraining
{ {
private Perceptron $perceptron; private Perceptron $perceptron;
private int $iterationErrorCounter = 0; private int $iterationErrorCounter = 0;
public ActivationsFunctions $activationFunction = ActivationsFunctions::STEP; public ActivationsFunctions $activationFunction = ActivationsFunctions::STEP;
@@ -24,8 +29,9 @@ class SimpleBinaryPerceptronTraining extends NetworkTraining
IPerceptronIterationEventBuffer $iterationEventBuffer, IPerceptronIterationEventBuffer $iterationEventBuffer,
string $sessionId, string $sessionId,
string $trainingId, string $trainingId,
?Closure $isCancelled = null,
) { ) {
parent::__construct($datasetReader, $maxEpochs, $iterationEventBuffer, $sessionId, $trainingId); parent::__construct($datasetReader, $maxEpochs, $iterationEventBuffer, $sessionId, $trainingId, $isCancelled);
$this->perceptron = new SimpleBinaryPerceptron($synapticWeightsProvider->generate($datasetReader->getInputSize())); $this->perceptron = new SimpleBinaryPerceptron($synapticWeightsProvider->generate($datasetReader->getInputSize()));
} }
@@ -61,12 +67,13 @@ class SimpleBinaryPerceptronTraining extends NetworkTraining
if ($condition === true) { if ($condition === true) {
event(new PerceptronTrainingEnded('Le perceptron ne commet plus d\'erreurs sur aucune des données', $this->sessionId, $this->trainingId)); event(new PerceptronTrainingEnded('Le perceptron ne commet plus d\'erreurs sur aucune des données', $this->sessionId, $this->trainingId));
} }
return $this->iterationErrorCounter == 0; return $this->iterationErrorCounter == 0;
} }
private function iterationFunction(array $inputs, int $correctOutput) private function iterationFunction(array $inputs, int $correctOutput)
{ {
$output = $this->perceptron->test($inputs); $output = $this->perceptron->test($inputs)[0];
$error = $correctOutput - $output; $error = $correctOutput - $output;
if (abs($error) > $this::MIN_ERROR) { if (abs($error) > $this::MIN_ERROR) {
@@ -79,6 +86,7 @@ class SimpleBinaryPerceptronTraining extends NetworkTraining
$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); $this->perceptron->setSynapticWeights($new_weights);
} }
return $error; return $error;
} }
@@ -1,9 +1,9 @@
<?php <?php
namespace App\Models; namespace App\Models\Perceptrons;
class GradientDescentPerceptron extends Perceptron {
class GradientDescentPerceptron extends Perceptron
{
public function __construct( public function __construct(
array $synaptic_weights, array $synaptic_weights,
) { ) {
@@ -14,5 +14,4 @@ class GradientDescentPerceptron extends Perceptron {
{ {
return $weighted_sum; return $weighted_sum;
} }
} }
+28
View File
@@ -0,0 +1,28 @@
<?php
namespace App\Models\Perceptrons;
class InputNeuron extends Perceptron
{
private float $input = 0.0;
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
}
}
@@ -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) - 1; $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 <?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( public function __construct(
private array $synaptic_weights, private array $synaptic_weights,
@@ -12,16 +12,17 @@ abstract class Perceptron extends Model
$this->synaptic_weights = $synaptic_weights; $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 $inputs = array_merge([1], $inputs); // Add bias input
if (count($inputs) !== count($this->synaptic_weights)) { // Check 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)); $weighted_sum = array_sum(array_map(fn ($input, $weight) => $input * $weight, $inputs, $this->synaptic_weights));
return $this->activationFunction($weighted_sum);
return [$this->activationFunction($weighted_sum)];
} }
abstract public function activationFunction(float $weighted_sum): float; abstract public function activationFunction(float $weighted_sum): float;
@@ -0,0 +1,13 @@
<?php
namespace App\Models\Perceptrons;
class SigmoidPerceptron extends Perceptron
{
public function activationFunction(float $weighted_sum): float
{
$weighted_sum = max(-40, min(40, $weighted_sum));
return 1 / (1 + exp(-$weighted_sum));
}
}
@@ -1,9 +1,9 @@
<?php <?php
namespace App\Models; namespace App\Models\Perceptrons;
class SimpleBinaryPerceptron extends Perceptron {
class SimpleBinaryPerceptron extends Perceptron
{
public function __construct( public function __construct(
array $synaptic_weights, array $synaptic_weights,
) { ) {
@@ -14,5 +14,4 @@ class SimpleBinaryPerceptron extends Perceptron {
{ {
return $weighted_sum >= 0.0 ? 1.0 : 0.0; return $weighted_sum >= 0.0 ? 1.0 : 0.0;
} }
} }
@@ -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;
}
}
+10 -1
View File
@@ -3,10 +3,12 @@
namespace App\Providers; namespace App\Providers;
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\Date; use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Http\Request;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rules\Password;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
@@ -24,6 +26,13 @@ class AppServiceProvider extends ServiceProvider
public function boot(): void public function boot(): void
{ {
$this->configureDefaults(); $this->configureDefaults();
RateLimiter::for('training', function (Request $request) {
return [
Limit::perMinute(50),
Limit::perMinute(15)->by($request->ip()),
];
});
} }
/** /**
@@ -14,7 +14,7 @@ class InitialSynapticWeightsProvider extends ServiceProvider
public function register(): void public function register(): void
{ {
$this->app->singleton(ISynapticWeightsProvider::class, function ($app) { $this->app->singleton(ISynapticWeightsProvider::class, function ($app) {
return new RandomSynapticWeights(); return new RandomSynapticWeights;
}); });
} }
+7 -6
View File
@@ -2,7 +2,8 @@
namespace App\Services; namespace App\Services;
class CsvReader { class CsvReader
{
private $file; private $file;
// private array $headers; // private array $headers;
@@ -10,11 +11,10 @@ class CsvReader {
public function __construct( public function __construct(
public string $filename, public string $filename,
) ) {
{ $this->file = fopen($filename, 'r');
$this->file = fopen($filename, "r");
if (! $this->file) { if (! $this->file) {
throw new \RuntimeException("Failed to open file: " . $filename); throw new \RuntimeException('Failed to open file: '.$filename);
} }
// $this->headers = $this->readNextLine(); // $this->headers = $this->readNextLine();
@@ -22,9 +22,10 @@ class CsvReader {
public function readNextLine(): ?array public function readNextLine(): ?array
{ {
if (($data = fgetcsv($this->file, 1000, ",")) !== FALSE) { if (($data = fgetcsv($this->file, 1000, ',')) !== false) {
return $data; return $data;
} }
return null; // End of file or error return null; // End of file or error
} }
} }
+11 -2
View File
@@ -2,10 +2,19 @@
namespace App\Services\DatasetReader; namespace App\Services\DatasetReader;
interface IDataSetReader { interface IDataSetReader
public function getNextLine(): array | null; {
public function getNextLine(): ?array;
public function getInputSize(): int; public function getInputSize(): int;
public function getOutputSize(): int;
public function getLabels(): array;
public function reset(): void; public function reset(): void;
public function getLastReadLineIndex(): int; public function getLastReadLineIndex(): int;
public function getEpochExamplesCount(): int; public function getEpochExamplesCount(): int;
} }
@@ -4,9 +4,11 @@ namespace App\Services\DatasetReader;
use App\Services\CsvReader; use App\Services\CsvReader;
class LinearOrderDataSetReader implements IDataSetReader { class LinearOrderDataSetReader implements IDataSetReader
{
public array $lines = []; public array $lines = [];
private array $currentLines = [];
private int $currentLineIndex = 0;
private int $lastReadLineIndex = -1; private int $lastReadLineIndex = -1;
@@ -27,23 +29,19 @@ class LinearOrderDataSetReader implements IDataSetReader {
$newLine[] = (float) $value; $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; $this->lines[] = $newLine;
} }
} }
public function getNextLine(): array | null { public function getNextLine(): ?array
if (!isset($this->currentLines[0])) { {
if (! isset($this->lines[$this->currentLineIndex])) {
return null; // No more lines to read return null; // No more lines to read
} }
$this->lastReadLineIndex = array_search($this->currentLines[0], $this->lines, true); $this->lastReadLineIndex = $this->currentLineIndex;
return array_shift($this->currentLines); return $this->lines[$this->currentLineIndex++];
} }
public function getInputSize(): int public function getInputSize(): int
@@ -51,9 +49,24 @@ class LinearOrderDataSetReader implements IDataSetReader {
return count($this->lines[0]) - 1; // Don't count the label 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 public function reset(): void
{ {
$this->currentLines = $this->lines; $this->currentLineIndex = 0;
} }
public function getLastReadLineIndex(): int public function getLastReadLineIndex(): int
@@ -4,9 +4,13 @@ namespace App\Services\DatasetReader;
use App\Services\CsvReader; use App\Services\CsvReader;
class RandomOrderDataSetReaders implements IDataSetReader { class RandomOrderDataSetReader implements IDataSetReader
{
public array $lines = []; public array $lines = [];
private array $currentLines = [];
private array $currentLineIndexes = [];
private int $currentLineIndex = 0;
private int $lastReadLineIndex = -1; private int $lastReadLineIndex = -1;
@@ -27,30 +31,20 @@ class RandomOrderDataSetReaders implements IDataSetReader {
$newLine[] = (float) $value; $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; $this->lines[] = $newLine;
} }
} }
public function getNextLine(): array | null public function getNextLine(): ?array
{ {
if (empty($this->currentLines)) { if (! isset($this->currentLineIndexes[$this->currentLineIndex])) {
return null; // No more lines to read return null; // No more lines to read
} }
$randomNumber = array_rand($this->currentLines); $lineIndex = $this->currentLineIndexes[$this->currentLineIndex++];
$randomLine = $this->currentLines[$randomNumber];
// Remove the line from the current lines to avoid repetition $this->lastReadLineIndex = $lineIndex;
unset($this->currentLines[$randomNumber]);
// Remember the index of the last read line in the full list return $this->lines[$lineIndex];
$this->lastReadLineIndex = array_search($randomLine, $this->lines, true);
return $randomLine;
} }
public function getInputSize(): int public function getInputSize(): int
@@ -58,9 +52,26 @@ class RandomOrderDataSetReaders implements IDataSetReader {
return count($this->lines[0]) - 1; // Don't count the label 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 public function reset(): void
{ {
$this->currentLines = $this->lines; $this->currentLineIndexes = array_keys($this->lines);
shuffle($this->currentLineIndexes);
$this->currentLineIndex = 0;
} }
public function getLastReadLineIndex(): int public function getLastReadLineIndex(): int
@@ -2,8 +2,8 @@
namespace App\Services\IterationEventBuffer; 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,45 +2,72 @@
namespace App\Services\IterationEventBuffer; namespace App\Services\IterationEventBuffer;
class PerceptronIterationEventBuffer implements IPerceptronIterationEventBuffer { use App\Events\PerceptronTrainingIteration;
private $data;
private int $nextSizeIncreaseThreshold; class PerceptronIterationEventBuffer implements IPerceptronIterationEventBuffer
private int $underSizeIncreaseCount = 0; {
private array $data = [];
private ?float $lastBroadcastAt = null;
public function __construct( public function __construct(
private string $sessionId, private string $sessionId,
private string $trainingId, private string $trainingId,
private int $sizeIncreaseStart = 10, ) {}
private int $sizeIncreaseFactor = 2,
) { public function flush(): void
$this->data = []; {
$this->nextSizeIncreaseThreshold = $sizeIncreaseStart; if ($this->data === []) {
return;
} }
public function flush(): void { $this->waitForBroadcastInterval();
event(new \App\Events\PerceptronTrainingIteration($this->data, $this->sessionId, $this->trainingId)); event(new PerceptronTrainingIteration($this->data, $this->sessionId, $this->trainingId));
$this->lastBroadcastAt = microtime(true);
$this->data = []; $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, $iteration = [
"exampleIndex" => $exampleIndex, 'epoch' => $epoch,
"error" => $error, 'exampleIndex' => $exampleIndex,
"weights" => $synaptic_weights, '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->data[] = $iteration;
$this->underSizeIncreaseCount++;
$this->flush();
}
else if (count($this->data) >= $this->nextSizeIncreaseThreshold) {
$this->flush();
$this->nextSizeIncreaseThreshold *= $this->sizeIncreaseFactor;
if ($this->nextSizeIncreaseThreshold > config('perceptron.broadcast_iteration_size')) { if ($this->data !== [] && $this->payloadExceedsLimit()) {
$this->nextSizeIncreaseThreshold = config('perceptron.broadcast_iteration_size'); // Cap the threshold to the maximum size $lastIteration = array_pop($this->data);
} $this->flush();
$this->data[] = $lastIteration;
}
if (count($this->data) >= config('perceptron.broadcast_iteration_size')) {
$this->flush();
}
}
private function payloadExceedsLimit(): bool
{
return strlen(json_encode([
'iterations' => PerceptronTrainingIteration::normalizeForJson($this->data),
'trainingId' => $this->trainingId,
], JSON_THROW_ON_ERROR)) > config('broadcasting.broadcast_max_payload_size');
}
private function waitForBroadcastInterval(): void
{
if ($this->lastBroadcastAt === null) {
return;
}
$minimumInterval = config('perceptron.broadcast_minimum_interval_ms') / 1000;
$remainingInterval = $minimumInterval - (microtime(true) - $this->lastBroadcastAt);
if ($remainingInterval > 0) {
usleep((int) ceil($remainingInterval * 1_000_000));
} }
} }
} }
@@ -2,50 +2,104 @@
namespace App\Services\IterationEventBuffer; namespace App\Services\IterationEventBuffer;
class PerceptronLimitedEpochEventBuffer implements IPerceptronIterationEventBuffer { use App\Events\PerceptronTrainingIteration;
private array $data;
private int $underSizeIncreaseCount = 0; class PerceptronLimitedEpochEventBuffer implements IPerceptronIterationEventBuffer
{
private array $data = [];
private array $lastEpochData = [];
private ?int $activeEpoch = null;
private bool $shouldBroadcastEpoch = false;
private ?float $lastBroadcastAt = null;
public function __construct( public function __construct(
private string $sessionId, private string $sessionId,
private string $trainingId, private string $trainingId,
private int $epochInterval, private int $epochInterval,
private int $sizeIncreaseStart = 10, ) {}
) {
$this->data = [];
}
public function flush(): void { public function flush(): void
event(new \App\Events\PerceptronTrainingIteration($this->data, $this->sessionId, $this->trainingId)); {
$this->data = []; if ($this->data !== []) {
} $this->flushSelectedData();
public function addIteration(int $epoch, int $exampleIndex, float $error, array $synaptic_weights): void {
$newData = [
"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; return;
} }
$lastEpoch = $this->data[0]['epoch'] ?? null; if ($this->lastEpochData === [] || $this->shouldBroadcastEpoch) {
if ($this->data && $lastEpoch !== $epoch) { // Current Epoch has changed from the last one return;
if ($lastEpoch % $this->epochInterval === 0) { // The last epoch need to be sent
$this->flush(); // Flush all data from the previous epoch
} }
else {
$this->waitForBroadcastInterval();
event(new PerceptronTrainingIteration($this->lastEpochData, $this->sessionId, $this->trainingId));
$this->lastBroadcastAt = microtime(true);
$this->lastEpochData = [];
}
public function addIteration(int $epoch, int $exampleIndex, float $error, array $synaptic_weights): void
{
$newData = [
'epoch' => $epoch,
'exampleIndex' => $exampleIndex,
'error' => $error,
'weights' => $synaptic_weights,
];
if ($this->activeEpoch !== $epoch) {
$this->flushSelectedData();
$this->lastEpochData = [];
$this->activeEpoch = $epoch;
$this->shouldBroadcastEpoch = $epoch === 1 || $epoch % $this->epochInterval === 0;
}
if (! $this->shouldBroadcastEpoch) {
$this->lastEpochData[] = $newData;
return;
}
$this->data[] = $newData;
if ($this->payloadExceedsLimit() || count($this->data) >= config('perceptron.broadcast_iteration_size')) {
$this->flushSelectedData();
}
}
private function flushSelectedData(): void
{
if ($this->data === []) {
return;
}
$this->waitForBroadcastInterval();
event(new PerceptronTrainingIteration($this->data, $this->sessionId, $this->trainingId));
$this->lastBroadcastAt = microtime(true);
$this->data = []; $this->data = [];
} }
$lastEpoch = $epoch; private function payloadExceedsLimit(): bool
{
return strlen(json_encode([
'iterations' => PerceptronTrainingIteration::normalizeForJson($this->data),
'trainingId' => $this->trainingId,
], JSON_THROW_ON_ERROR)) > config('broadcasting.broadcast_max_payload_size');
}
private function waitForBroadcastInterval(): void
{
if ($this->lastBroadcastAt === null) {
return;
}
$minimumInterval = config('perceptron.broadcast_minimum_interval_ms') / 1000;
$remainingInterval = $minimumInterval - (microtime(true) - $this->lastBroadcastAt);
if ($remainingInterval > 0) {
usleep((int) ceil($remainingInterval * 1_000_000));
} }
$this->data[] = $newData;
} }
} }
@@ -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; namespace App\Services\SynapticWeightsProvider;
interface ISynapticWeightsProvider { interface ISynapticWeightsProvider
{
public function generate(int $input_size): array; public function generate(int $input_size): array;
} }
@@ -2,13 +2,15 @@
namespace App\Services\SynapticWeightsProvider; namespace App\Services\SynapticWeightsProvider;
class RandomSynapticWeights implements ISynapticWeightsProvider { class RandomSynapticWeights implements ISynapticWeightsProvider
{
public function generate(int $input_size): array public function generate(int $input_size): array
{ {
$weights = []; $weights = [];
for ($i = 0; $i < $input_size + 1; $i++) { // +1 for bias weight for ($i = 0; $i < $input_size + 1; $i++) { // +1 for bias weight
$weights[] = rand(-100, 100) / 100; // Random weights between -1 and 1 $weights[] = rand(-100, 100) / 100; // Random weights between -1 and 1
} }
return $weights; return $weights;
} }
} }
@@ -0,0 +1,37 @@
<?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++) {
$layer = [];
for ($neuronIndex = 0; $neuronIndex < $hidden_layers_neurons_count; $neuronIndex++) {
$layer[] = $this->synapticWeightsProvider->generate($lastLayerSize);
}
$synaptic_weights[] = $layer;
$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; namespace App\Services\SynapticWeightsProvider;
class ZeroSynapticWeights implements ISynapticWeightsProvider { class ZeroSynapticWeights implements ISynapticWeightsProvider
{
public function generate(int $input_size): array public function generate(int $input_size): array
{ {
$weights = []; $weights = [];
for ($i = 0; $i < $input_size + 1; $i++) { // +1 for bias weight for ($i = 0; $i < $input_size + 1; $i++) { // +1 for bias weight
$weights[] = 0; // Zero weights $weights[] = 0; // Zero weights
} }
return $weights; return $weights;
} }
} }
+3
View File
@@ -23,6 +23,9 @@ return Application::configure(basePath: dirname(__DIR__))
HandleInertiaRequests::class, HandleInertiaRequests::class,
AddLinkHeadersForPreloadedAssets::class, AddLinkHeadersForPreloadedAssets::class,
]); ]);
$middleware->trustProxies(at: [
'*'
]);
}) })
->withExceptions(function (Exceptions $exceptions): void { ->withExceptions(function (Exceptions $exceptions): void {
// //
+2 -1
View File
@@ -19,6 +19,7 @@
"laravel/wayfinder": "^0.1.9" "laravel/wayfinder": "^0.1.9"
}, },
"require-dev": { "require-dev": {
"brianium/paratest": "^7.8",
"fakerphp/faker": "^1.23", "fakerphp/faker": "^1.23",
"laravel/pail": "^1.2.2", "laravel/pail": "^1.2.2",
"laravel/pint": "^1.24", "laravel/pint": "^1.24",
@@ -50,7 +51,7 @@
], ],
"dev": [ "dev": [
"Composer\\Config::disableProcessTimeout", "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": [ "dev:ssr": [
"npm run build:ssr", "npm run build:ssr",
Generated
+215 -1
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "a72ab6feeee69457d0085c4a5e4580f7", "content-hash": "93a44ad3435bb0cb19a8bd3b2b700b4f",
"packages": [ "packages": [
{ {
"name": "bacon/bacon-qr-code", "name": "bacon/bacon-qr-code",
@@ -7544,6 +7544,99 @@
} }
], ],
"packages-dev": [ "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", "name": "fakerphp/faker",
"version": "v1.24.1", "version": "v1.24.1",
@@ -7607,6 +7700,67 @@
}, },
"time": "2024-11-21T13:46:39+00:00" "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", "name": "filp/whoops",
"version": "2.18.4", "version": "2.18.4",
@@ -7729,6 +7883,66 @@
}, },
"time": "2025-04-30T06:54:44+00:00" "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", "name": "laravel/pail",
"version": "v1.2.6", "version": "v1.2.6",
+9 -4
View File
@@ -36,10 +36,10 @@ return [
'secret' => env('REVERB_APP_SECRET'), 'secret' => env('REVERB_APP_SECRET'),
'app_id' => env('REVERB_APP_ID'), 'app_id' => env('REVERB_APP_ID'),
'options' => [ 'options' => [
'host' => env('REVERB_HOST'), 'host' => env('REVERB_BROADCAST_HOST', env('REVERB_HOST')),
'port' => env('REVERB_PORT', 443), 'port' => env('REVERB_BROADCAST_PORT', env('REVERB_PORT', 443)),
'scheme' => env('REVERB_SCHEME', 'https'), 'scheme' => env('REVERB_BROADCAST_SCHEME', env('REVERB_SCHEME', 'https')),
'useTLS' => env('REVERB_SCHEME', 'https') === 'https', 'useTLS' => env('REVERB_BROADCAST_SCHEME', env('REVERB_SCHEME', 'https')) === 'https',
], ],
'client_options' => [ 'client_options' => [
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
@@ -79,4 +79,9 @@ return [
], ],
/**
* Keep broadcast payloads below Pusher's event size limit.
*/
'broadcast_max_payload_size' => 9000,
]; ];
+19 -3
View File
@@ -7,13 +7,29 @@ return [
* Beyond this number of iterations, the broadcast will be splitted every x iterations, * Beyond this number of iterations, the broadcast will be splitted every x iterations,
* x is limited_broadcast_number * x is limited_broadcast_number
*/ */
'limited_broadcast_iterations' => 200, 'limited_broadcast_iterations' => 100,
/** /**
* How much broadcasts is sent when in limmited broadcast mode * How much broadcasts is sent when in limited broadcast mode
*/ */
'limited_broadcast_number' => 200, 'limited_broadcast_number' => 100,
/**
* The maximum number of iterations that can be sent in a single broadcast.
*/
'broadcast_iteration_size' => 75, 'broadcast_iteration_size' => 75,
/**
* Minimum time between training progress broadcasts, in milliseconds.
*/
'broadcast_minimum_interval_ms' => 100,
/**
* Maximum number of weights for which all iteration weights are broadcast
* and displayed in the iteration table.
*/
'max_displayed_weights' => 5,
'max_iterations' => 5000,
]; ];
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+10
View File
@@ -0,0 +1,10 @@
#!/bin/sh
# Migrate the database
php ./artisan migrate --force || exit 1
# Cache
php ./artisan optimize:clear && php ./artisan optimize
# Start the server
exec frankenphp php-server --root=public
+190 -11
View File
@@ -6,15 +6,21 @@
"": { "": {
"dependencies": { "dependencies": {
"@inertiajs/vue3": "^2.3.7", "@inertiajs/vue3": "^2.3.7",
"@jaseeey/vue-umami-plugin": "^1.6.1",
"@lucide/vue": "^1.46.0",
"@tailwindcss/typography": "^0.5.20",
"@vee-validate/zod": "^4.15.1", "@vee-validate/zod": "^4.15.1",
"@vueuse/core": "^12.8.2", "@vueuse/core": "^12.8.2",
"chart.js": "^4.5.1", "chart.js": "^4.5.1",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"katex": "^0.18.7",
"laravel-vite-plugin": "^2.0.0", "laravel-vite-plugin": "^2.0.0",
"lucide-vue-next": "^0.468.0", "lucide-vue-next": "^0.468.0",
"markdown-it": "^15.0.1",
"markdown-it-katex": "^2.0.3",
"radix-ui": "^1.4.3", "radix-ui": "^1.4.3",
"reka-ui": "^2.9.0", "reka-ui": "^2.10.4",
"tailwind-merge": "^3.2.0", "tailwind-merge": "^3.2.0",
"tailwindcss": "^4.1.1", "tailwindcss": "^4.1.1",
"tw-animate-css": "^1.2.5", "tw-animate-css": "^1.2.5",
@@ -856,6 +862,11 @@
"@swc/helpers": "^0.5.0" "@swc/helpers": "^0.5.0"
} }
}, },
"node_modules/@jaseeey/vue-umami-plugin": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/@jaseeey/vue-umami-plugin/-/vue-umami-plugin-1.6.1.tgz",
"integrity": "sha512-a38bB7cYbwMP5HmlrT2Dk2RHdGFxVvLRci2GhY8cIwCpM419qXtvSM6SKmIJ3U3u7zYdcQLVAySOcXY150Z14Q=="
},
"node_modules/@jridgewell/gen-mapping": { "node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13", "version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -925,6 +936,15 @@
"integrity": "sha512-yZYIr1iwuCQ7LFI+GsJk9vacw1HWMp3ZlDlW0pdfz3zXyKeu4US7oH79KmQQ031L0cYaSyaUMo/Ha1D4BosKqw==", "integrity": "sha512-yZYIr1iwuCQ7LFI+GsJk9vacw1HWMp3ZlDlW0pdfz3zXyKeu4US7oH79KmQQ031L0cYaSyaUMo/Ha1D4BosKqw==",
"dev": true "dev": true
}, },
"node_modules/@lucide/vue": {
"version": "1.46.0",
"resolved": "https://registry.npmjs.org/@lucide/vue/-/vue-1.46.0.tgz",
"integrity": "sha512-xp4mUamXKM/jYCve8TOcEcuikidFh3Jn9gs0THCeEKn/fAdeU0mKhPqk0fmIwn42fI4CwGLRWrLneLA69h8Nqw==",
"license": "ISC",
"peerDependencies": {
"vue": ">=3.0.1"
}
},
"node_modules/@napi-rs/wasm-runtime": { "node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.12", "version": "0.2.12",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
@@ -2978,6 +2998,29 @@
"node": ">= 20" "node": ">= 20"
} }
}, },
"node_modules/@tailwindcss/typography": {
"version": "0.5.20",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.20.tgz",
"integrity": "sha512-hwbzQuNUfcPvbegQFatVPl/MY/tcM9KLl963hQ5laJKPh81TEZ1+dNG9PirGvcaDBkp+BCshExAyKVPW91dozw==",
"dependencies": {
"postcss-selector-parser": "6.0.10"
},
"peerDependencies": {
"tailwindcss": ">=3.0.0 || >=4.0.0 || insiders"
}
},
"node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@tailwindcss/vite": { "node_modules/@tailwindcss/vite": {
"version": "4.2.1", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz",
@@ -3830,6 +3873,7 @@
"version": "12.8.2", "version": "12.8.2",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz", "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz",
"integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"@types/web-bluetooth": "^0.0.21", "@types/web-bluetooth": "^0.0.21",
"@vueuse/metadata": "12.8.2", "@vueuse/metadata": "12.8.2",
@@ -4300,6 +4344,14 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/commander": {
"version": "15.0.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz",
"integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==",
"engines": {
"node": ">=22.12.0"
}
},
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -4362,7 +4414,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true,
"bin": { "bin": {
"cssesc": "bin/cssesc" "cssesc": "bin/cssesc"
}, },
@@ -4490,9 +4541,10 @@
} }
}, },
"node_modules/defu": { "node_modules/defu": {
"version": "6.1.4", "version": "6.1.7",
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz",
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==" "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==",
"license": "MIT"
}, },
"node_modules/delayed-stream": { "node_modules/delayed-stream": {
"version": "1.0.0", "version": "1.0.0",
@@ -6254,6 +6306,21 @@
"json5": "lib/cli.js" "json5": "lib/cli.js"
} }
}, },
"node_modules/katex": {
"version": "0.18.7",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.18.7.tgz",
"integrity": "sha512-h+UCwkZ+4Jz8WQ7MLGfj7UVFrRCizGb912fwF4luGdYsC5paYG1vx+jy+KRcC/XkpjGva/P7nAWuxNnPzRvzHw==",
"funding": [
"https://opencollective.com/katex",
"https://github.com/sponsors/katex"
],
"dependencies": {
"commander": "^15.0.0"
},
"bin": {
"katex": "cli.js"
}
},
"node_modules/keyv": { "node_modules/keyv": {
"version": "4.5.4", "version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -6554,6 +6621,24 @@
"url": "https://opencollective.com/parcel" "url": "https://opencollective.com/parcel"
} }
}, },
"node_modules/linkify-it": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-6.1.0.tgz",
"integrity": "sha512-wJ/TwpSDTLepCrQoYWYIExIKg5Zchex2Nn5yk2mFnB+6PtdkHtyLx742md9csRjjOnGkKIS/RrbY7l8D6gT9Vw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/markdown-it"
}
],
"dependencies": {
"uc.micro": "^3.0.0"
}
},
"node_modules/locate-path": { "node_modules/locate-path": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -6602,6 +6687,82 @@
"@jridgewell/sourcemap-codec": "^1.5.5" "@jridgewell/sourcemap-codec": "^1.5.5"
} }
}, },
"node_modules/markdown-it": {
"version": "15.0.1",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-15.0.1.tgz",
"integrity": "sha512-9/7gE95FNPkfUWrjJIoHZza2iLmuJlPD0UNMxPi7bxUrbCR525YZY0r+zyfes0dZI5ZZ/uNIXUJca0pJvtw41g==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/markdown-it"
}
],
"dependencies": {
"argparse": "^3.0.0",
"entities": "^8.0.0",
"linkify-it": "^6.0.0",
"mdurl": "^2.1.0",
"punycode.js": "^2.3.1",
"uc.micro": "^3.0.0"
},
"bin": {
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/markdown-it-katex": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/markdown-it-katex/-/markdown-it-katex-2.0.3.tgz",
"integrity": "sha512-nUkkMtRWeg7OpdflamflE/Ho/pWl64Lk9wNBKOmaj33XkQdumhXAIYhI0WO03GeiycPCsxbmX536V5NEXpC3Ng==",
"dependencies": {
"katex": "^0.6.0"
}
},
"node_modules/markdown-it-katex/node_modules/katex": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/katex/-/katex-0.6.0.tgz",
"integrity": "sha512-rS4mY3SvHYg5LtQV6RBcK0if7ur6plyEukAOV+jGGPqFImuzu8fHL6M752iBmRGoUyF0bhZbAPoezehn7xYksA==",
"dependencies": {
"match-at": "^0.1.0"
},
"bin": {
"katex": "cli.js"
}
},
"node_modules/markdown-it/node_modules/argparse": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-3.0.1.tgz",
"integrity": "sha512-nM4mHF/KM1v59ZNKX7zfusQz5wUAxR511YG8Vo6TyiV4aqhu++rbJW4v04xsWhpSsHFj66flT8P7znVpyO20xQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/nodeca"
}
]
},
"node_modules/markdown-it/node_modules/entities": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-8.1.0.tgz",
"integrity": "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==",
"engines": {
"node": ">=20.19.0"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/match-at": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/match-at/-/match-at-0.1.1.tgz",
"integrity": "sha512-h4Yd392z9mST+dzc+yjuybOGFNOZjmXIPKWjxBd1Bb23r4SmDOsk2NYCU2BMUBGbSpZqwVsZYNq26QS3xfaT3Q=="
},
"node_modules/math-intrinsics": { "node_modules/math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -6610,6 +6771,11 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/mdurl": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.1.0.tgz",
"integrity": "sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg=="
},
"node_modules/merge2": { "node_modules/merge2": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -7137,6 +7303,14 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"engines": {
"node": ">=6"
}
},
"node_modules/pusher-js": { "node_modules/pusher-js": {
"version": "8.4.0", "version": "8.4.0",
"resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.4.0.tgz", "resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.4.0.tgz",
@@ -7386,9 +7560,10 @@
} }
}, },
"node_modules/reka-ui": { "node_modules/reka-ui": {
"version": "2.9.0", "version": "2.10.4",
"resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.9.0.tgz", "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.10.4.tgz",
"integrity": "sha512-5dpp80u109iLTbRBu+jhAk8R/877/JN20gYGjb3GsuAgS7E/5QTX5ZxuzWtZAVbChBDYDpXc8pkaQAFpa6s+4w==", "integrity": "sha512-kbS5GAbkHkYj0EVKAg5ZPEPndhBjeUFSa1Aq5m5ftQwyHnB1v5tw8X6fcwfKH/ry3StPXblMWM1DW82MVS71cw==",
"license": "MIT",
"dependencies": { "dependencies": {
"@floating-ui/dom": "^1.6.13", "@floating-ui/dom": "^1.6.13",
"@floating-ui/vue": "^1.1.6", "@floating-ui/vue": "^1.1.6",
@@ -7398,7 +7573,7 @@
"@vueuse/core": "^14.1.0", "@vueuse/core": "^14.1.0",
"@vueuse/shared": "^14.1.0", "@vueuse/shared": "^14.1.0",
"aria-hidden": "^1.2.4", "aria-hidden": "^1.2.4",
"defu": "^6.1.4", "defu": "^6.1.5",
"ohash": "^2.0.11" "ohash": "^2.0.11"
}, },
"funding": { "funding": {
@@ -8298,6 +8473,11 @@
"typescript": ">=4.8.4 <6.0.0" "typescript": ">=4.8.4 <6.0.0"
} }
}, },
"node_modules/uc.micro": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-3.0.0.tgz",
"integrity": "sha512-U3PppEkleoTnIfi8BozMx3yju3qc/L6SwqWo2Sw+54PX+PX0q9I+r1Um5HCmqD7n9VDX5/v3vQH/AjA6deDdtw=="
},
"node_modules/unbox-primitive": { "node_modules/unbox-primitive": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
@@ -8417,8 +8597,7 @@
"node_modules/util-deprecate": { "node_modules/util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
"dev": true
}, },
"node_modules/vee-validate": { "node_modules/vee-validate": {
"version": "4.15.1", "version": "4.15.1",
+7 -1
View File
@@ -37,15 +37,21 @@
}, },
"dependencies": { "dependencies": {
"@inertiajs/vue3": "^2.3.7", "@inertiajs/vue3": "^2.3.7",
"@jaseeey/vue-umami-plugin": "^1.6.1",
"@lucide/vue": "^1.46.0",
"@tailwindcss/typography": "^0.5.20",
"@vee-validate/zod": "^4.15.1", "@vee-validate/zod": "^4.15.1",
"@vueuse/core": "^12.8.2", "@vueuse/core": "^12.8.2",
"chart.js": "^4.5.1", "chart.js": "^4.5.1",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"katex": "^0.18.7",
"laravel-vite-plugin": "^2.0.0", "laravel-vite-plugin": "^2.0.0",
"lucide-vue-next": "^0.468.0", "lucide-vue-next": "^0.468.0",
"markdown-it": "^15.0.1",
"markdown-it-katex": "^2.0.3",
"radix-ui": "^1.4.3", "radix-ui": "^1.4.3",
"reka-ui": "^2.9.0", "reka-ui": "^2.10.4",
"tailwind-merge": "^3.2.0", "tailwind-merge": "^3.2.0",
"tailwindcss": "^4.1.1", "tailwindcss": "^4.1.1",
"tw-animate-css": "^1.2.5", "tw-animate-css": "^1.2.5",
+2 -2
View File
@@ -8,9 +8,9 @@
<testsuite name="Unit"> <testsuite name="Unit">
<directory>tests/Unit</directory> <directory>tests/Unit</directory>
</testsuite> </testsuite>
<testsuite name="Feature"> <!-- <testsuite name="Feature">
<directory>tests/Feature</directory> <directory>tests/Feature</directory>
</testsuite> </testsuite> -->
</testsuites> </testsuites>
<source> <source>
<include> <include>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 46 KiB

@@ -0,0 +1,150 @@
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 2.8 1.9 1
2 2.9 1.8 1
3 2.2 1.5 1
4 1.3 5.6 0
5 -1.2 1.6 -1
6 2.5 1.7 1
7 3.1 2.3 1
8 2.8 2.4 1
9 -1.2 1.5 -1
10 1.4 6.1 0
11 -1.2 1.3 -1
12 3.1 1.8 1
13 3.3 2.5 1
14 3.4 2.3 1
15 1.5 5.9 0
16 1.3 5.6 0
17 -1.2 1 -1
18 3 2.1 1
19 1.4 5.2 0
20 1.2 5.5 0
21 1.4 6.7 0
22 -1.3 1.7 -1
23 1 5.7 0
24 2.6 1.4 1
25 1.6 6 0
26 1.2 5.8 0
27 1.4 6.1 0
28 1.3 5.7 0
29 -1.2 1.4 -1
30 1 5.5 0
31 3.1 2.1 1
32 -1.4 1.7 -1
33 1.6 6.3 0
34 -1.2 1.4 -1
35 2.7 1.9 1
36 1.1 5.5 0
37 3 1.8 1
38 3.8 2 1
39 2.8 2.1 1
40 1 5 0
41 2.8 2 1
42 -1.2 1.6 -1
43 1.3 6.6 0
44 1.6 6 0
45 3 1.6 1
46 -1.1 1.1 -1
47 2.9 1.8 1
48 3 1.8 1
49 -1.2 1.4 -1
50 1 5.8 0
51 1.2 5.7 0
52 2.8 1.5 1
53 -1.4 1.6 -1
54 -1.2 1.4 -1
55 2.8 2.2 1
56 -1.5 1.7 -1
57 1 4.9 0
58 1.3 5.7 0
59 3.2 2.3 1
60 -1.3 1.5 -1
61 -1.2 1.2 -1
62 -1.2 1.6 -1
63 3 2.1 1
64 -1.4 1.5 -1
65 1.3 5.5 0
66 3.3 2.5 1
67 3.1 2.4 1
68 1.8 5.9 0
69 2.7 1.9 1
70 1.5 6.3 0
71 -1.3 1.4 -1
72 1 5 0
73 3.2 2.3 1
74 -1.3 1.4 -1
75 3 2.1 1
76 3.2 2 1
77 1.3 6.3 0
78 1.4 7 0
79 -1.2 1.7 -1
80 1.4 6.6 0
81 1.2 5.8 0
82 3 2 1
83 -1.2 1.6 -1
84 1.5 6.2 0
85 1.5 6.4 0
86 -1.2 1.9 -1
87 3.2 2.3 1
88 -1.2 1.5 -1
89 1.5 5.4 0
90 1.5 6 0
91 1.5 6.9 0
92 -1.4 1.5 -1
93 1.3 6.4 0
94 2.8 2 1
95 -1.2 1.5 -1
96 -1.1 1.5 -1
97 3.6 2.5 1
98 1.1 5.1 0
99 -1.1 1.5 -1
100 -1.2 1.5 -1
101 3.4 2.4 1
102 -1.3 1.3 -1
103 3 2.3 1
104 -1.1 1.5 -1
105 1.1 5.6 0
106 1.3 5.6 0
107 -1.2 1.5 -1
108 -1.2 1.3 -1
109 1.7 6.7 0
110 -1.2 1.3 -1
111 -1.4 1.3 -1
112 2.5 1.8 1
113 -1.4 1.5 -1
114 1.5 6.5 0
115 2.6 2.3 1
116 -1.2 1.4 -1
117 1.5 5.6 0
118 1.3 5.7 0
119 1.2 6.1 0
120 3 2.2 1
121 3 1.8 1
122 -1.1 1.4 -1
123 1.4 6.8 0
124 -1.6 1.6 -1
125 -1.2 1.4 -1
126 3.2 1.8 1
127 -1.2 1.5 -1
128 2.7 1.8 1
129 -1.3 1.4 -1
130 1.3 6.2 0
131 -1.3 1.3 -1
132 3 1.8 1
133 2.7 1.9 1
134 -1.2 1.4 -1
135 -1.2 1.2 -1
136 1.5 6.7 0
137 2.5 1.9 1
138 3.3 2.1 1
139 2.8 1.8 1
140 -1.2 1.3 -1
141 3.8 2.2 1
142 2.5 2 1
143 1.3 6.1 0
144 -1.2 1.4 -1
145 -1.1 1.5 -1
146 3 2.3 1
147 -1.4 1.9 -1
148 -1.2 1.6 -1
149 1 6 0
150 1.3 5.5 0
+4
View File
@@ -0,0 +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,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
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
2 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
3 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
4 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
-4
View File
@@ -1,4 +0,0 @@
0, 0, 0
0, 1, 0
1, 0, 0
1, 1, 1
1 0 0 0
2 0 1 0
3 1 0 0
4 1 1 1
-4
View File
@@ -1,4 +0,0 @@
0, 0, -1
0, 1, 1
1, 0, 1
1, 1, 1
1 0 0 -1
2 0 1 1
3 1 0 1
4 1 1 1
-150
View File
@@ -1,150 +0,0 @@
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
1 2.8 1.9 -1 -1 1
2 2.9 1.8 -1 -1 1
3 2.2 1.5 -1 -1 1
4 1.3 5.6 -1 1 -1
5 -1.2 1.6 1 -1 -1
6 2.5 1.7 -1 -1 1
7 3.1 2.3 -1 -1 1
8 2.8 2.4 -1 -1 1
9 -1.2 1.5 1 -1 -1
10 1.4 6.1 -1 1 -1
11 -1.2 1.3 1 -1 -1
12 3.1 1.8 -1 -1 1
13 3.3 2.5 -1 -1 1
14 3.4 2.3 -1 -1 1
15 1.5 5.9 -1 1 -1
16 1.3 5.6 -1 1 -1
17 -1.2 1 1 -1 -1
18 3 2.1 -1 -1 1
19 1.4 5.2 -1 1 -1
20 1.2 5.5 -1 1 -1
21 1.4 6.7 -1 1 -1
22 -1.3 1.7 1 -1 -1
23 1 5.7 -1 1 -1
24 2.6 1.4 -1 -1 1
25 1.6 6 -1 1 -1
26 1.2 5.8 -1 1 -1
27 1.4 6.1 -1 1 -1
28 1.3 5.7 -1 1 -1
29 -1.2 1.4 1 -1 -1
30 1 5.5 -1 1 -1
31 3.1 2.1 -1 -1 1
32 -1.4 1.7 1 -1 -1
33 1.6 6.3 -1 1 -1
34 -1.2 1.4 1 -1 -1
35 2.7 1.9 -1 -1 1
36 1.1 5.5 -1 1 -1
37 3 1.8 -1 -1 1
38 3.8 2 -1 -1 1
39 2.8 2.1 -1 -1 1
40 1 5 -1 1 -1
41 2.8 2 -1 -1 1
42 -1.2 1.6 1 -1 -1
43 1.3 6.6 -1 1 -1
44 1.6 6 -1 1 -1
45 3 1.6 -1 -1 1
46 -1.1 1.1 1 -1 -1
47 2.9 1.8 -1 -1 1
48 3 1.8 -1 -1 1
49 -1.2 1.4 1 -1 -1
50 1 5.8 -1 1 -1
51 1.2 5.7 -1 1 -1
52 2.8 1.5 -1 -1 1
53 -1.4 1.6 1 -1 -1
54 -1.2 1.4 1 -1 -1
55 2.8 2.2 -1 -1 1
56 -1.5 1.7 1 -1 -1
57 1 4.9 -1 1 -1
58 1.3 5.7 -1 1 -1
59 3.2 2.3 -1 -1 1
60 -1.3 1.5 1 -1 -1
61 -1.2 1.2 1 -1 -1
62 -1.2 1.6 1 -1 -1
63 3 2.1 -1 -1 1
64 -1.4 1.5 1 -1 -1
65 1.3 5.5 -1 1 -1
66 3.3 2.5 -1 -1 1
67 3.1 2.4 -1 -1 1
68 1.8 5.9 -1 1 -1
69 2.7 1.9 -1 -1 1
70 1.5 6.3 -1 1 -1
71 -1.3 1.4 1 -1 -1
72 1 5 -1 1 -1
73 3.2 2.3 -1 -1 1
74 -1.3 1.4 1 -1 -1
75 3 2.1 -1 -1 1
76 3.2 2 -1 -1 1
77 1.3 6.3 -1 1 -1
78 1.4 7 -1 1 -1
79 -1.2 1.7 1 -1 -1
80 1.4 6.6 -1 1 -1
81 1.2 5.8 -1 1 -1
82 3 2 -1 -1 1
83 -1.2 1.6 1 -1 -1
84 1.5 6.2 -1 1 -1
85 1.5 6.4 -1 1 -1
86 -1.2 1.9 1 -1 -1
87 3.2 2.3 -1 -1 1
88 -1.2 1.5 1 -1 -1
89 1.5 5.4 -1 1 -1
90 1.5 6 -1 1 -1
91 1.5 6.9 -1 1 -1
92 -1.4 1.5 1 -1 -1
93 1.3 6.4 -1 1 -1
94 2.8 2 -1 -1 1
95 -1.2 1.5 1 -1 -1
96 -1.1 1.5 1 -1 -1
97 3.6 2.5 -1 -1 1
98 1.1 5.1 -1 1 -1
99 -1.1 1.5 1 -1 -1
100 -1.2 1.5 1 -1 -1
101 3.4 2.4 -1 -1 1
102 -1.3 1.3 1 -1 -1
103 3 2.3 -1 -1 1
104 -1.1 1.5 1 -1 -1
105 1.1 5.6 -1 1 -1
106 1.3 5.6 -1 1 -1
107 -1.2 1.5 1 -1 -1
108 -1.2 1.3 1 -1 -1
109 1.7 6.7 -1 1 -1
110 -1.2 1.3 1 -1 -1
111 -1.4 1.3 1 -1 -1
112 2.5 1.8 -1 -1 1
113 -1.4 1.5 1 -1 -1
114 1.5 6.5 -1 1 -1
115 2.6 2.3 -1 -1 1
116 -1.2 1.4 1 -1 -1
117 1.5 5.6 -1 1 -1
118 1.3 5.7 -1 1 -1
119 1.2 6.1 -1 1 -1
120 3 2.2 -1 -1 1
121 3 1.8 -1 -1 1
122 -1.1 1.4 1 -1 -1
123 1.4 6.8 -1 1 -1
124 -1.6 1.6 1 -1 -1
125 -1.2 1.4 1 -1 -1
126 3.2 1.8 -1 -1 1
127 -1.2 1.5 1 -1 -1
128 2.7 1.8 -1 -1 1
129 -1.3 1.4 1 -1 -1
130 1.3 6.2 -1 1 -1
131 -1.3 1.3 1 -1 -1
132 3 1.8 -1 -1 1
133 2.7 1.9 -1 -1 1
134 -1.2 1.4 1 -1 -1
135 -1.2 1.2 1 -1 -1
136 1.5 6.7 -1 1 -1
137 2.5 1.9 -1 -1 1
138 3.3 2.1 -1 -1 1
139 2.8 1.8 -1 -1 1
140 -1.2 1.3 1 -1 -1
141 3.8 2.2 -1 -1 1
142 2.5 2 -1 -1 1
143 1.3 6.1 -1 1 -1
144 -1.2 1.4 1 -1 -1
145 -1.1 1.5 1 -1 -1
146 3 2.3 -1 -1 1
147 -1.4 1.9 1 -1 -1
148 -1.2 1.6 1 -1 -1
149 1 6 -1 1 -1
150 1.3 5.5 -1 1 -1
-4
View File
@@ -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,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
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 1 -1 -1 -1
2 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
3 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
4 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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 24 KiB

-3
View File
@@ -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

+72
View File
@@ -0,0 +1,72 @@
# ADAptative LINear Element
ADALINE est une variante de la descente du gradient
La descente du gradient attend d'avoir évalué chaque exemple du dataset avant de corriger les poids synaptiques et de calculer l'erreur quadratique moyenne. ADALINE rends le processus plus rapide en corrigeant les poids à chaque exemple, comme le Perceptron.
On peut considérer ADALINE comme un Perceptron avec la fonction linéaire comme fonction d'activation et une condition d'arrêt de l'entraînement qui utilise l'erreur quadratique moyenne.
## Structure d'ADALINE
Pas de changement par rapport à la structure de la Descente du gradient.
![Schéma général de la structure d'un neurone ADALINE](helptext/img/gradientDescentSchema.png)
## Fonctionnement de l'entraînement
En comparaison à la Descente du gradient, la correction des poids se fait à chaque évaluation d'un exemple, comme pour le Perceptron.
1. On initialise les poids synaptiques.
2. On commence l'entraînement : pour chaque exemple du dataset choisi, on évalue la sortie du Perceptron ($y$).
3. On fait le calcul de l'erreur (notée $e$), en soustrayant la sortie du Perceptron à la sortie attendue (le *label*).
4. On met à jour les poids synaptiques avec la même formule que le Perceptron : on ajoute à la valeur du poids l'erreur multipliée par la valeur de l'exemple assignée à l'entrée, multipliée par le **Taux d'apprentissage**. Ce qui donne : $w_i' = w_i + \eta \times (\text{label } - y) \times x_i$ pour tout poids synaptique $i$ où $w_i'$ est le poids synaptique corrigé et $x_i$ est l'entrée numéro $i$.
5. Une fois tous les exemples du dataset parcourus, on calcule l'erreur quadratique moyenne ($E$) avec les derniers poids synaptiques.
6. Si l'erreur quadratique moyenne est plus petite que l'**erreur minimale** ou que le nombre maximal d'époques a été atteint, l'entraînement est terminé. Sinon, on recommence à partir du point 2.
<div class="exercice">
Reprenons le dataset oblique afin de faire un test contrôle :
- <span>Dataset</span> : `Classification - Oblique (linéairement séparable)`
Puis cliquez sur "Lancer" tout en laissant les autres paramètres intacts.
Cliquez sur le bouton `Afficher uniquement l'erreur quadratique moyenne`, la ligne que l'on peut voir est moins "douce" et contient des "bosses" avec les mêmes paramètres que la descente du gradient. Cela est dû au fait que les corrections se font à chaque exemple du dataset. Le taux d'apprentissage devrait être mis un peu plus bas pour faire disparaître ces "bosses", mais ici elles sont encore petites et ne posent pas de problèmes.
</div>
## Ajustements du Taux d'apprentissage
La descente du gradient est plus lente que le Perceptron. Est-ce qu'ADALINE règle ce problème ?
<div class="exercice">
Reprenons le dataset oblique afin de pouvoir comparer les résultats, et augmentons un peu le taux d'apprentissage :
- <span>Dataset</span> : `Classification - Oblique (linéairement séparable)`
- <span>Taux d'apprentissage</span> : entre $0.002$ et $0.01$ (ex: $0.005$)
- (Optionnel) <span>Nombre maximum d'époques</span> : $1000$ (plus d'époques seront affichées)
Puis cliquez sur "Lancer" tout en laissant les autres paramètres intacts.
On peut voir que le nombre d'époques que l'entraînement a pris est bien plus petit : de 200 à 900 époques en fonction du taux d'apprentissage choisi, contre presque 2000 pour le test contrôle.
Cliquez sur le bouton `Afficher uniquement l'erreur quadratique moyenne`, la ligne que l'on peut observer est encore moins douce. Les "bosses" sont amplifiées à cause de la hausse du taux d'apprentissage.
</div>
### Avec un taux d'apprentissage trop haut
On a déjà vu avec la descente du gradient ce que faisait un taux d'apprentissage trop bas et trop haut. Mais comme on le voit, l'erreur quadratique moyenne du perceptron forme des bosses qui s'amplifient avec le taux d'apprentissage. Que se passe-t-il alors quand le paramètre est trop haut ?
<div class="exercice">
Reprenons le dataset oblique afin de pouvoir comparer les résultats, et augmentons le taux d'apprentissage à une valeur trop grande :
- <span>Dataset</span> : `Classification - Oblique (linéairement séparable)`
- <span>Taux d'apprentissage</span> : $0.023$
Puis cliquez sur "Lancer" tout en laissant les autres paramètres intacts.
Les erreurs vacillent et le neurone a du mal à converger.
Cliquez sur le bouton `Afficher uniquement l'erreur quadratique moyenne`, les bosses de la ligne sont trop amplifiées et l'entraînement se joue sur la chance de tomber sur le bon prochain exemple. Relancer l'entraînement plusieurs fois et vous aurez des nombres d'époque finale différents. Ce n'est bien sûr pas optimal sauf si l'on se sent chanceux.
</div>
Mettre une valeur de taux d'apprentissage encore plus haute va donner le même résultat que la descente du gradient : des valeurs de poids qui s'emballent dans des extrêmes.
+9
View File
@@ -0,0 +1,9 @@
<div class="exercice">
Reprenons le dataset oblique afin de pouvoir comparer les résultats, et diminuons l'erreur minimale :
- <span>Dataset</span> : `Classification - Oblique (linéairement séparable)`
- <span>Erreur minimale</span> : en dessous de $0.05$ ou même $0$
- (Optionnel) <span>Nombre maximum d'époques</span> : $4000$
Puis cliquez sur "Lancer" tout en laissant les autres paramètres intacts.
</div>
+137
View File
@@ -0,0 +1,137 @@
# La Descente du gradient
Comme on a pu le voir, le [Perceptron](/perceptron?type=simple) présente quelques limitations :
- Il est incapable de faire de la régression
- Il n'optimise pas sa solution, la limite de décision "colle" les points du dataset.
La descente du gradient essaye d'améliorer l'algorithme d'entraînement et rend les valeurs des entrées et des sorties réelles (des nombres à décimale, positifs, négatifs ou nuls).
La descente du gradient n'essaye plus de ne plus faire aucune erreur comme le Perceptron, mais à la place va essayer de **minimiser l'erreur quadratique moyenne** (d'avoir une erreur quadratique moyenne la plus petite possible). Cette méthode de minimisation de l'erreur est appelée *descente du gradient*. L'erreur quadratique moyenne est calculée comme suit : la somme de l'erreur du réseau sur tous les exemples du dataset au carré, le tout divisé par $2$. Cela revient à faire
$$E = \frac{1}{2} \sum^K_{k=1}{(e^{(k)})^2}$$
où :
- $E$ est l'erreur quadratique moyenne.
- $K$ le nombre d'exemples dans le dataset.
- $e^{(k)}$ l'erreur du réseau sur l'exemple $k$ du dataset. ($e = \text{ label } - y$)
## Structure de la Descente du gradient
La structure ne change pas par rapport au Perceptron simple, si ce n'est que la fonction d'activation est maintenant une droite $y = x$. Cela veut dire que la descente du gradient retourne des valeurs réelles non bornées.
![Schéma général de la structure d'un neurone avec la descente du gradient](helptext/img/gradientDescentSchema.png)
## Fonctionnement de l'entraînement
Par rapport au Perceptron, la correction des poids se fait après chaque époque, et la condition de fin d'entraînement s'établit sur l'erreur quadratique moyenne.
1. On initialise les poids synaptiques.
2. On commence l'entraînement : pour chaque exemple du dataset choisi, on évalue la sortie du Perceptron ($y$).
3. On fait le calcul de l'erreur (notée $e$), en soustrayant la sortie du Perceptron à la sortie attendue (le *label*). Comme l'entrée et la sortie de la descente du gradient sont des réels, ici l'erreur est plus précise que le perceptron qui avait une erreur de -1, 0 ou 1.
4. On calcule, pour chaque poids synaptique, un signal d'erreur simmilaire au perceptron. Le signal d'erreur est égal à l'erreur multipliée par la donnée de l'exemple assignée à cette l'entrée, multipliée par le Taux d'apprentissage. Ce signal est ajouté à une variable de correction (notée $Dw_1$, $Dw_2$, $Dw_3$, ...) qui garde la somme des signaux d'erreurs pour chaque poids synaptique. Cette variable de correction est utilisée car on ne modifie les poids synaptiques qu'à la fin de l'époque, quand tous les exemples ont été évalués.
5. Une fois tous les exemples du dataset parcourus, on actualise les poids synaptiques en appliquant leur variable de correction respectives.
6. On calcule l'erreur quadratique moyenne ($E$) avec les nouveaux poids.
7. Si l'erreur quadratique moyenne est plus petite que l'**erreur minimale** ou que le nombre maximal d'époques a été atteint, l'entraînement est terminé. Sinon, on recommence à partir du point 2. L'erreur minimale est un paramètre détaillé plus bas.
<div class="exercice">
Reprenons le dataset oblique utilisé avec le Perceptron afin de vérifier que la descente du gradient corrige bien ses limitations :
- <span>Dataset</span> : `Classification - Oblique (linéairement séparable)`
Puis cliquez sur "Lancer" tout en laissant les autres paramètres intacts.
On peut voir que la ligne de décision est au centre entre les deux classes, et qu'elle ne colle pas aux points. Toutefois un point négatif que l'on peut noter est qu'il est bien **plus lent**. Le nombre d'itérations est au-dessus de 1000 comparé à une centaine pour le perceptron.
</div>
## Qu'est-ce que l'Erreur minimale fait ?
L'erreur minimale est un paramètre choisi par le développeur, c'est le seuil que le réseau doit atteindre pour pouvoir terminer l'entraînement. Plus le seuil est bas, plus le réseau doit être précis avant de pouvoir arrêter l'entraînement.
### Avec une erreur minimale trop basse
L'on pourrait alors se dire de mettre l'erreur minimale à 0, pour avoir un modèle le plus précis possible.
<div class="exercice">
Reprenons le dataset oblique afin de pouvoir comparer les résultats, et diminuons l'erreur minimale :
- <span>Dataset</span> : `Classification - Oblique (linéairement séparable)`
- <span>Erreur minimale</span> : en dessous de $0.05$ ou même $0$
Puis cliquez sur "Lancer" tout en laissant les autres paramètres intacts.
On peut voir en dessous du tableau que l'entraînement s'est arrêté car le nombre maximal d'époques a été atteint. On pourrait l'augmenter (max 5000 pour mon petit serveur), mais ce serait du temps perdu. La solution du réseau est suffisante et il n'y aurait pas beaucoup de gain, même pour le triple d'itérations maximales en plus. Pour le prouver, cliquez sur le bouton `Afficher uniquement l'erreur quadratique moyenne`, la ligne que l'on peut voir ressemble fortement à la fonction logarithme $y = log(x^{-1})$ (attention à l'échelle qui est elle-même logarithmique). La progression de la descente du gradient est donc logarithmique ; les plus gros changements se font dans les premières époques.
</div>
### Avec une erreur minimale trop haute
Alors que fait une erreur minimale mise trop haute par le développeur ?
<div class="exercice">
Reprenons le dataset oblique afin de pouvoir comparer les résultats, et augmentons l'erreur minimale :
- <span>Dataset</span> : `Classification - Oblique (linéairement séparable)`
- <span>Erreur minimale</span> : au-dessus de $0.15$ (ex: $0.2$)
Puis cliquez sur "Lancer" tout en laissant les autres paramètres intacts.
L'entraînement est très rapide, même un peu trop rapide... Le réseau ne donne pas une bonne solution, voire une solution sur laquelle l'entraînement du perceptron ne se serait pas arrêté.
</div>
### Comment choisir une bonne valeur pour l'erreur minimale ?
Une méthode consiste à mettre l'erreur minimale à 0, mettre le nombre maximum d'époques à une valeur très haute, et surveiller l'erreur quadratique moyenne : si elle stagne, le réseau ne fera plus beaucoup d'avancées sur un nombre raisonnable d'époques.
## Qu'est-ce que le Taux d'Apprentissage fait ?
Comme expliqué dans le processus d'entraînement du Perceptron et de la descente du gradient, le taux d'apprentissage est un paramètre permettant de contrôler avec quelle "force" les poids synaptiques se mettent à jour.
### Avec un Taux d'Apprentissage trop haut
<div class="exercice">
Reprenons le même dataset oblique que le test contrôle, mais en augmentant considérablement le Taux d'Apprentissage :
- <span>Dataset</span> : `Classification - Oblique (linéairement séparable)`
- <span>Taux d'apprentissage</span> : au dessus de $0.001$ (ex: $0.002$)
- (Optionnel) <span>Nombre maximum d'époques</span> : $200$
Puis cliquez sur "Lancer" tout en laissant les autres paramètres intacts.
Les valeurs des graphes et du tableau vont dans les extrêmes (même à NaN ("Not A Number")). À cause du taux d'apprentissage trop haut, à chaque correction des poids synaptiques, leurs valeurs ont augmentées de façon exponentielle. Créant un cycle vicieux.
</div>
### Avec un Taux d'Apprentissage trop bas
<div class="exercice">
Reprenons le même dataset oblique que le test contrôle, mais en diminuant considérablement le Taux d'Apprentissage :
- <span>Dataset</span> : `Classification - Oblique (linéairement séparable)`
- <span>Taux d'apprentissage</span> : en dessous de $0.0005$ (ex: $0.0001$)
- (Optionnel) <span>Nombre maximum d'époques</span> : $4000$
Puis cliquez sur "Lancer" tout en laissant les autres paramètres intacts.
L'entraînement est beaucoup plus long, le réseau atteint même peut-être le nombre maximum d'époques. Cela est dû à la correction des poids synaptiques qui est devenue plus petite à chaque époque. L'on pourrait enlever la limite du nombre maximum d'époques, attendre un peu plus longtemps, et avoir un résultat précis. Mais, le temps, c'est de l'argent. Faisons le parallèle avec OpenAI : l'entraînement d'un nouveau modèle coûte des millions de dollars en puissance de calcul et peut prendre des mois entiers. Si l'on réduit la vitesse à laquelle le modèle s'entraîne, la facture de l'entraînement peut vite être salée.
</div>
En note annexe : dans les frameworks actuels, cette variable est gérée automatiquement et je ne pense pas qu'elle pose vraiment un problème à OpenAI.
### Comment choisir une bonne valeur pour le taux d'apprentissage ?
À l'essai et erreur ! Commencer avec une valeur arbitraire et diminuer tant que les poids synaptiques partent dans des valeurs infiniment grandes. Augmenter dans le cas contraire jusqu'à trouver une bonne valeur.
## Test avec la régression
Comme dit dans l'[introduction](/), quand un réseau de neurones fait de la régression, il essaye de créer la fonction mathématique qui épouse au mieux le nuage de données du dataset. Une des limitations du perceptron est le fait qu'il est incapable de faire de la régression. Grâce à ses entrées et sorties réelles, la descente du gradient est capable de faire de la régression.
<div class="exercice">
Testons la régression avec un dataset simple :
- <span>Dataset</span> : `Régression - Oblique`
Une fois sélectionné, on peut voir que le nuage de points du dataset forme presque une droite oblique. Imaginer une droite qui traverse au mieux tous les points, c'est-à-dire une droite qui est le plus proche possible de tous les points en même temps.
Cliquez sur "Lancer" tout en laissant les autres paramètres intacts.
On peut constater que la droite se dirige rapidement au bon endroit, le reste des époques ne font que du peaufinage. La descente du gradient essaye de minimiser l'erreur sans essayer de n'en faire aucune, ce qui est parfait comme avec un dataset comme celui-ci, il est impossible de relier tous les points avec une droite, et donc impossible de ne pas faire d'erreur.
</div>
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

+96
View File
@@ -0,0 +1,96 @@
# Perceptron Viewer
Cet outil vous permet d'expérimenter avec les réseaux de neurones et les perceptrons. Vous pouvez ajuster les paramètres et observer l'apprentissage en temps réel.
## Contexte
Ce projet a été d'abord créé pour un cours de réseaux de neurones artificiels que j'ai suivi durant mon master en Architecture des Systèmes Informatiques à la Haute École de la Province de Liège. Il s'agit ici d'une version plus conviviale, documentée et optimisée.
Je conseille de finir de lire les prochains chapitres qui traitent des bases des réseaux de neurones et qui présentent l'interface. Je conseille ensuite d'utiliser les onglets au-dessus de la page dans l'ordre indiqué. Chaque onglet présente une évolution des réseaux de neurones dans l'ordre chronologique des découvertes et des améliorations faites.
## Introduction aux Réseaux de Neurones Artificiels
Le but principal d'un réseau de neurones artificiels est de **reproduire une fonction mathématique complexe**. En résumé, un réseau de neurones est conçu pour apprendre des relations complexes entre des données d'entrée et des données de sortie (la prédiction souhaitée). Il apprend à faire correspondre une entrée $X$ à une sortie $Y$ via une série de calculs pondérés et d'activations.
### Premières Découvertes des neurones artificiels
Les bases des réseaux de neurones remontent à la modélisation biologique du cerveau. Les premières tentatives par un neuropsychiatre et logicien vers 1943 visaient à créer des modèles simplifiés pour comprendre comment le cerveau traite l'information. Il s'agissait de **simuler la capacité du cerveau à apprendre et à prendre des décisions à partir de données brutes**, ce qui était essentiel pour comprendre la cognition et le traitement du *signal*.
Le résultat qui nous intéresse de cette recherche est l'"Electronic Brain", très semblable au [Perceptron](/perceptron?type=simple) que l'on va voir au premier onglet. Il transforme les données d'entrée ($X$) pour en sortie donner une décision ($Y$)
Exemple d'Electronic Brain à 3 entrées : ![Exemple d'Electronic Brain](helptext/img/electronicBrain.png)
### Le Machine Learning
Les neurones artificiels sont la structure "intelligente" du réseau. Cependant, il faut d'abord l'entraîner afin de déterminer les paramètres qui rendent ce réseau intelligent. C'est là que rentre en jeu le Machine Learning (souvent abrévié "ML"). On distingue principalement deux grandes catégories :
#### L'Apprentissage Supervisé
Des données d'entraînement sont données au modèle, appelées *exemples*. À chaque exemple est associée une bonne réponse appelée *tag* (ou *étiquette* en français).
Pendant l'apprentissage, pour chaque exemple, le réseau de neurones est consulté. Si la sortie du réseau ($Y$) n'est pas égale à la réponse attendue (le *tag* de l'exemple), les paramètres du réseau sont modifiés. Enfin, l'on passe à l'exemple suivant.
L'apprentissage supervisé demande donc d'étiqueter (ou "tagger") chaque exemple avec une réponse attendue précise et fiable. À défaut, le réseau de neurones pourrait manquer de précision et de fiabilité.
#### L'Apprentissage Non Supervisé
L'apprentissage non supervisé n'utilise pas de *tag* dans les données. À la place, il utilise des méthodes d'analyse de données mathématiques comme l'[ACP](https://notes.matthiasg.dev/#/page/school%2Fexploration%20de%20donn%C3%A9es%2F1.%20acp).
Cet apprentissage est utilisé pour trouver des pattern dans des données.
Tous les réseaux présentés ici utilisent l'**Apprentissage Supervisé**. L'apprentissage non supervisé ne sera donc pas vu plus en détail.
### Types de problèmes à résoudre
Les réseaux de neurones peuvent résoudre plusieurs types de problèmes. Nous allons ici en voir deux :
#### La Régression
Le réseau de neurones estime la fonction mathématique continue d'une suite ou d'un nuage de points, afin par exemple de pouvoir prédire les données suivantes.
Exemple : à partir des températures des cinq dernières années, entraîner un réseau de neurones pour qu'il reproduise la fonction mathématique qui se rapproche le plus de tous les points. Une fois entraîné, le réseau est capable d'estimer les prochaines températures.
![Exemple de régression](helptext/img/regressionExample.png)
#### La classification
Le réseau de neurones va attribuer la catégorie la plus probable à chaque exemple. Ces catégories sont appelées *classes* en mathématique. Un réseau de neurones peut classer les données dans autant de classes que nécessaire, mais le réseau doit être adapté en conséquence au niveau de la *couche de sortie* que l'on verra dans les réseaux multicouches.
Exemples : détection d'objets dans une image, déterminer si un e-mail est un spam ou non, déterminer l'état de santé d'un patient à partir de ses données vitales, ...
![Exemple de classification](helptext/img/classificationExample.png)
Chaque point contenu dans les zones sera catégorisé par le réseau de neurones dans la classe correspondante.
### [Quand la technologie copie la nature](https://fr.wikipedia.org/wiki/Biomim%C3%A9tisme)
Les neurones artificiels sont très semblables aux neurones biologiques. Un neurone biologique est une unité de traitement, capable de recevoir des signaux, de les traiter et de transmettre des informations via des connexions appelées *synapses*.
### Anatomie du Neurone Artificiel
Un neurone artificiel (ou une couche neuronale) est composé de plusieurs éléments interconnectés qui collaborent pour produire une sortie. Voici les composants clés :
![Schéma général d'un neurone](helptext/img/neuronSchema.png)
- **Entrée ($x_1$, $x_2$, $x_3$, ...)** : Les données brutes des exemples donnés au réseau (ex : la température en janvier, février, mars, etc.).
- **Poids Synaptique ($w_1$, $w_2$, $w_3$, ...)** : Un coefficient numérique qui détermine l'importance de chaque entrée pour la prochaine étape. Ce sont les paramètres que le réseau modifie quand il "apprend".
- **Biais ou Entrée Fictive ($w_0$)** : Une poids ajouté qui permet de déplacer la fonction d'activation, aidant le modèle à mieux s'ajuster aux données. Ce poids est toujours lié à une entrée constante avec comme valeur $1$.
- **Potentiel ($p$)** : La somme pondérée de toutes les entrées et des biais : $p = (\sum w_i x_i) + w_0$.
- **Fonction d'Activation ($f(x)$)** : Une fonction non linéaire qui va borner la valeur. Cette fonction est choisie par le développeur ou imposée par le type de neurone, comme on va le voir par exemple pour [ADALINE](perceptron?type=adaline).
- **Sortie ($y$)** : Le résultat final du neurone, la sortie du réseau, ce que le réseau a déterminé à partir de l'exemple.
## Autre vocabulaire
- **Dataset** : collections d'exemples étiquetté ayant la même structure et traitant du même sujet. Exemple : un dataset contenant la température enregistrée pour chaque jour des cinq dernières années. Pour chaque exemple on a donc le jour et la température.
## Présentation de Perceptron Viewer
Perceptron Viewer permet de modifier les paramètres d'entraînement, d'entraîner des réseaux de neurones et perceptrons sur un serveur, et de visualiser en direct les erreurs, les zones décisionnelles et les prédictions du réseau.
### Présentation de l'interface
![Présentation globale de l'interface](helptext/img/globalInterfacePresentation.png)
La configuration de l'entraînement et les graphes seront expliqués plus en détail dans l'onglet [Perceptron](/perceptron?type=simple).
---
Je vous invite dès à présent à vous rendre sur l'[onglet 1 : Perceptron Simple](/perceptron?type=simple).
+51
View File
@@ -0,0 +1,51 @@
# Le Réseau Monocouche
Jusqu'ici, nous avons vu des réseaux avec un seul neurone. Une des limites d'un tel réseau est qu'**un seul neurone ne peut séparer que deux classes**. En effet, un neurone ne peut pas donner simultanément l'appartenance ou non à plusieurs classes.
Pour séparer des données de plusieurs classes, il devient nécessaire d'utiliser plusieurs neurones, chacun étant spécialisé pour détecter l'appartenance ou non à une classe spécifique.
Exemples :
- Pour séparer 3 classes, il faut au moins 3 neurones.
- Pour séparer 2 classes, un seul neurone suffit (si $y > 0.5$ alors classe A, sinon classe B).
C'est exactement ce qu'un réseau monocouche fait : il permet d'avoir une quantité infinie de neurones. Le nombre de neurones est contrôlé automatiquement par PerceptronViewer en fonction du nombre de classes différentes dans le dataset.
<div class="exercice">
Reprenons le dataset oblique afin de pouvoir comparer les résultats avec ceux d'un réseau à un neurone :
- <span>Dataset</span> : `Classification - Oblique (linéairement séparable)`
Puis cliquez sur "Lancer" tout en laissant les autres paramètres intacts.
On peut voir maintenant des zones apparaître sur le graphe de décision. Chacune de ces zones représente la ligne de décision d'un neurone : si un point se trouve dans une de ces zones, il sera considéré comme faisant partie de la classe correspondante.
</div>
## Structure du réseau monocouche
Le réseau monocouche permet de choisir quel type de neurone utiliser. Dans le cadre de PerceptronViewer, des neurones ADALINE ont été utilisés.
Afin de clarifier les schémas de réseaux complexes, les perceptrons sont représentés par un cercle.
![Schéma général de la structure d'un réseau de neurones monocouche](helptext/img/monolayerSchema.png)
Chaque neurone est connecté à toutes les entrées et la valeur de leur sortie va déterminer la classe à laquelle l'exemple appartient.
## Fonctionnement de l'entraînement
L'entraînement va dépendre du type de neurone utilisé. Globalement, deux approches principales existent :
1. Entraîner tous les neurones simultanément : au moment de la correction des poids synaptiques, on parcourt tous les neurones.
2. Entraîner les neurones séparément : considérer les neurones comme des réseaux séparés. On entraîne le premier réseau, puis on passe au suivant.
La méthode d'entraînement utilisée dans PerceptronViewer est la première car elle se rapproche de l'entraînement du réseau multicouches, ce qui évite d'implémenter des méthodes différentes.
<div class="exercice">
Exploitons l'avantage du réseau monocouche en utilisant un dataset composé de 3 classes :
- <span>Dataset</span> : `Classification - 3 classes (linéairement séparable)`
Puis cliquez sur "Lancer" tout en laissant les autres paramètres intacts.
On obtient une séparation nette entre les 3 classes qui est assez bien généralisée (si l'on ajoute un nouveau point, il sera dans la classe la plus proche)
</div>
+78
View File
@@ -0,0 +1,78 @@
# Le Réseau Multicouches
> Et les datasets non linéairement séparables dans tout ça ?
Jusqu'à présent, nous avons utilisé que des datasets linéairement séparables, pour la simple raison que les réseaux à une seule couche ne produisent que des frontières de décision linéaires (des droites).
Pour séparer des classes de manière non linéaire, il est nécessaire d'utiliser des fonctions non linéaires au sein du réseau. C'est là que les réseaux multicouches rentrent en jeu : utiliser plusieurs couches permet d'avoir des frontières de décision non linéaires.
## Structure du réseau monocouche
Par rapport au réseau monocouche, on rajoute des couches dans lesquelles tous les neurones sont interconnectés aux neurones de la couche suivante.
![Schéma général de la structure d'un réseau de neurones multicouches](helptext/img/multilayerSchema.png)
On distingue 3 types de couches :
- La couche d'entrée : ne contient pas de neurones à proprement parler car ils n'effectuent aucun traitement sur les données d'entrées.
- Les couches cachées : chaque couche contient des neurones qui vont "mâcher" les données pour les couches suivantes. Dans PerceptronViewer, le nombre de neurones par couches cachées peut être choisi avec le paramètre **Nombre de neurones par couche cachée** et le nombre de couches cachées peut être choisi avec le paramètre **Nombre de couches cachées**.
- La couche de sortie : contient les neurones finaux qui vont transformer les données de sortie de la couche cachée en données utilisables (prédiction ou classification).
## Fonctionnement de l'entraînement
L'entraînement d'un réseau multicouche demande plus de technique à cause de la présence de plusieurs couches.
Voici le fonctionnement de l'entraînement dans les grandes lignes :
1. On initialise les poids synaptiques.
2. On commence l'entraînement : pour chaque exemple du dataset choisi, on évalue la sortie du réseau avec la *propagation avant* (forward propagation en anglais) : les données passent de la couche d'entrée, traversent les couches cachées et arrivent enfin à la couche de sortie. Pour chaque couche, l'entrée de chaque neurone correspond à la sortie des neurones de la couche précédente.
3. On calcule l'erreur du réseau sur cet exemple en utilisant la *rétropropagation* (backpropagation en anglais) : on calcule le signal d'erreur de la couche de sortie et on le propage à travers les couches jusqu'à la couche d'entrée.
4. On met à jour les poids synaptiques en fonction du signal d'erreur.
Je ne détaillerai pas ici la rétropropagation et la mise à jour des poids synaptiques. Cependant, si vous êtes intéressés, vous pouvez trouver les détails ici : [Wikipédia - Apprentissage du perceptron multicouche](https://fr.wikipedia.org/wiki/Perceptron_multicouche#Apprentissage).
<div class="exercice">
Exploitons l'avantage du réseau multicouche avec un dataset non linéairement séparable :
- <span>Dataset</span> : `Classification - Donut (non linéairement séparable)`
Puis cliquez sur "Lancer" tout en laissant les autres paramètres intacts.
Le réseau commence avec une zone uniforme qui couvre tout le graphe, ensuite, progressivement, crée une forme ovale qui englobe la classe du milieu du donut. Le reste des époques est passé à peaufiner la forme pour qu'elle englobe au mieux tous les exemples.
</div>
## Qu'est-ce que le nombre de couches et de neurones par couche fait ?
Ces deux paramètres sont très importants car ils vont déterminer la précision et le temps d'entraînement du réseau. Il n'y a pas réellement de formule magique, les bonnes valeurs sont obtenues par essai et erreur. Voici tout de même ce que va changer chaque paramètre et comment mieux choisir les valeurs.
### Quels sont les effets
Augmenter les paramètres va augmenter le nombre total de neurones. Plus il y a de neurones, plus la ligne de décision peut être complexe. Une ligne plus complexe va pouvoir dessiner les contours plus précisément et suivre des courbes complexes.
Il faut un minimum de nombre total de couches en fonction du dataset :
- 1 seule couche : la ligne de décision est une droite, comme les perceptrons et le réseau monocouche vu précédemment.
- 2 couches : la ligne de décision peux courber et modéliser des formes non linéaires (résout des datasets non linéairement séparables).
- +2 couches : la ligne de décision a la possibilité d'être de plus en plus complexe, plus le nombre de couches est haut.
Il est recommandé de ne pas avoir plus de neurones par couche qu'il y a d'exemples dans le dataset. Sinon le modèle risque d'apprendre par cœur le dataset en retenant un exemple par neurone. Ce problème est appelé **surapprentissage**, il apparaît quand le modèle est tellement bon sur le dataset, qu'il est incapable de généraliser.
Trop peu de neurones empêche le réseau de pouvoir faire le contour des classes et de devoir faire des erreurs.
## La régression avec un réseau multicouches
La régression peut également demander plus de couches de neurones pour pouvoir épouser des formes complexes.
<div class="exercice">
Essayons cela avec un dataset de régression trop complexe pour les réseaux à une seule couche :
- <span>Dataset</span> : `Régression - Vague`
Puis cliquez sur "Lancer" tout en laissant les autres paramètres intacts.
Là où un réseau monocouche essaierait de trouver la moyenne des points avec une droite, le réseau multicouches est capable d'épouser les formes complexes du nuage.
</div>
---
Ici s'achève la théorie. Quelques datasets n'ont pas été utilisés comme `Classification - Oblique modifié (non linéairement séparable)`, qui contient quelques points ajoutés ou dont la classe a été inversée (erreur d'encodage ? Anomalies dans la récolte des données ? Valeur anormale ? À vous de voir). Ce dataset est intéressant à faire avec tous les types de perceptrons et réseaux car il montre le comportement de chacun face à un dataset non linéairement séparable mais toujours réalisable.
+80
View File
@@ -0,0 +1,80 @@
# Perceptron Simple
Un perceptron est un neurone artificiel avec un mécanisme d'apprentissage supervisé.
Découvert par Frank Rosenblatt en simulant le neurone artificiel sur une machine IBM 704
Le nombre d'entrées du Perceptron ($x_1$, $x_2$, $x_3$, ...) peut être infini, mais le Perceptron n'aura toujours qu'une seule sortie ($y$). Étant donné que le perceptron ne contient qu'un seul neurone, il n'est capable de sortir qu'une réponse binaire, ce qui veut dire qu'il ne peut faire que de la classification à $2$ classes.
## Structure du Perceptron
Structure classique d'un neurone, avec l'utilisation de la fonction en escalier qui va limiter les valeurs possibles de sortie à $0$ ou $1$.
![Schéma général de la structure d'un perceptron simple](helptext/img/simplePerceptronSchema.png)
Voici le procédé qu'un Perceptron utilise pour transformer un exemple d'entrée ($X$) en signal de sortie ($Y$) :
1. On assigne chaque donnée de l'exemple du dataset à une entrée ($x_1$, $x_2$, $x_3$, ...). Dans notre cas, on utilise des points dans un espace 2D, donc $x_1 = \text{ Coordonnee x du point d'exemple}$ et $x_2 = \text{ Coordonnee y du point d'exemple}$ pour la classification.
2. On multiplie chaque entrée par le poids synaptique correspondant ($x_1 \times w_1$, $x_2 \times w_2$, ...), sans oublier l'entrée fictive ($1 \times w_0$).
3. On additionne le tout ($(1 \times w_1) + (x_1 \times w_1) + (x_2 \times w_2) + \cdots$), ce qui donne le *potentiel* ($p$).
4. On passe le potentiel dans la fonction d'activation qui, dans le cas du Perceptron, est la fonction en escalier. Cela revient à faire : si $p < 0$ alors le Perceptron retourne $0$, sinon le Perceptron retourne $1$. Le résultat du Perceptron est le signal $y$.
## Fonctionnement de l'entraînement
1. On initialise les poids synaptiques. Il existe plusieurs manières de les initialiser, ici PerceptronViewer permet de choisir entre "zeros" et "random" (aléatoire). Il n'y a pas beaucoup de différence entre les deux méthodes, si ce n'est qu'avec l'initialisation à $0$, l'on peux reproduire le comportement de l'entraînement. Cette reproductibilité permet de voir directement l'impact de modifications des autres paramètres d'entraînement.
2. On commence l'entraînement : pour chaque exemple du dataset choisis, on évalue la sortie du Perceptron ($y$), avec le procédé vu plus haut. Le passage sur tous les exemples du dataset s'appelle une **époque** ("epoch" en anglais).
3. On fait le calcul de l'erreur (notée $e$), en soustrayant la sortie du Perceptron à la sortie attendue (le *label*). Cela revient à $e = \text{ label } - y$. Si le perceptron a bien estimé la sortie, alors l'erreur est de $0$. Dans le cas contraire où le Perceptron n'a pas bien estimé la sortie, l'erreur est de $1$ ou $-1$.
4. Si l'erreur n'est pas nulle ($\neq 0$), alors on *corrige les poids synaptiques* du Perceptron ($w_0$, $w_1$, $w_2$, ...) comme suit pour chaque poids synaptique :
- Ajouter à la valeur du poids l'erreur multipliée par la valeur de l'exemple assignée à l'entrée, multipliée par une variable nommée **Taux d'apprentissage** (notée $\eta$). Ce qui donne : $w_i' = w_i + \eta \times (\text{label } - y) \times x_i$ pour tout poids synaptique $i$ où $w_i'$ est le poids synaptique corrigé et $x_i$ est l'entrée numéro $i$. Le taux d'apprentissage est défini par le développeur et est un paramètre très important comme on le verra par la suite.
5. Une fois tous les exemples du dataset parcourus. Si le Perceptron a commis une erreur ($e \neq 0$) pour un des exemples de ce dataset, alors l'entraînement n'est pas terminé et l'on recommence à partir du point 2. Si le Perceptron n'a pas commis d'erreur, alors l'entraînement est terminé.
Afin d'éviter que le Perceptron ne s'entraîne sans s'arrêter pour les datasets impossibles, un paramètre **Nombre maximum d'époques** a été ajouté. L'entraînement s'arrêtera si le nombre d'époques maximal a été atteint.
<div class="exercice">
Il est temps de tester ! Essayez de lancer un entraînement avec :
- <span>Dataset</span> : `Classification - Porte logique ET (linéairement séparable)`
Puis cliquez sur "Lancer" tout en laissant les autres paramètres intacts. Le Perceptron devrait vite converger et trouver une solution (de 3 à 8 époques d'après mes tests).
Le graphe du dessus montre le dataset avec chaque donnée représentée comme un point ayant la couleur de la classe correspondante. Dans le cas du dataset de la porte logique ET, il n'y a qu'un seul point faisant partie de la classe 1.
⚠️ Le nommage des classes est arbitraire et ne représente pas l'étiquette. La ligne de décision (en blanc) représente la bordure entre la zone où le Perceptron va considérer le point comme faisant partie de la classe A ou B. Comme on peut le voir, la ligne de décision est très proche des points, c'est une des limites dont nous parlerons plus bas.
Le graphe du dessous montre les erreurs faites par le perceptron, chaque bâton représente une époque, avec chaque bloc de couleur représentant l'erreur pour l'exemple correspondant. Le graphe contient aussi une ligne de l'**Erreur quadratique moyenne de l'époque** qui est une métrique utilisée pour examiner l'évolution de l'erreur du réseau de neurones sur le dataset à chaque époque. Comme on va le voir plus loin, cette métrique permet de mieux comprendre ce qu'il se passe durant l'entraînement et de juger les modifications nécessaires. Pour l'instant, on peut voir que cette métrique tombe à $0$ à la dernière époque, ce qui montre que le Perceptron ne fait plus d'erreurs sur le dataset et a donc terminé son entraînement.
</div>
## Comment choisir un dataset ?
Comme vous pouvez le voir dans la liste déroulante "Dataset", il en existe plusieurs types. Voici leurs significations :
### Classification VS Régression
Comme écrit dans l'introduction, la classification et la régression sont deux types de problèmes que les réseaux de neurones que nous voyons ici peuvent résoudre. Comme tous les datasets contiennent des points en 2D, cela veux dire :
- Pour la classification, le Perceptron va avoir 2 entrées ($x_1 = \text{ Coordonnee x du point d'exemple}$ et $x_2 = \text{ Coordonnee y du point d'exemple}$) et une sortie qui indique si le point fait partie d'une classe A ou B.
- Pour la régression, le Perceptron va avoir 1 entrée ($x_1 = \text{ Coordonnee x du point d'exemple}$) et la sortie représentera la coordonnée y du point d'exemple puisqu'il n'y a qu'une seule classe dans un dataset de régression.
### Classification : linéairement séparable ou non linéairement séparable
La notion de séparabilité linéaire dans la classification fait référence à la capacité d'un modèle à séparer les classes par un plan (ou une frontière) simple. Si les données sont linéairement séparables, cela veut dire qu'elles peuvent être séparées à l'aide d'une fonction mathématique linéaire (une droite).
## Limitations du Perceptron
Étant donné que sa sortie est binaire, le Perceptron présente deux limitations fondamentales :
- Il est incapable de faire de la régression. Car il s'arrête dès qu'il ne fait plus d'erreurs, mais avec une seule classe, il est impossible de faire une erreur.
- Il est incapable de faire de la classification à plus de deux classes différentes.
La limite de décision du Perceptron est souvent collée aux points. Une solution parfaite mettrait la limite à égale distance entre les points des deux classes. Cele est dû à l'entraînement qui s'arrête au moment où il n'y a plus d'erreurs, sans chercher à "optimiser" ou "généraliser" la solution.
<div class="exercice">
Pour voir le perceptron qui colle les points, essayez de lancer un entraînement avec :
- <span>Dataset</span> : `Classification - Oblique (linéairement séparable)`
L'on peut voir que l'entraînement dure déjà plus longtemps car il faut que le Perceptron trouve une pente et une hauteur qui fait passer la droite de décision dans un "trou" plus étroit. Mais l'on voit également que le Perceptron va coller les points à l'entrée et à la sortie de ce "trou".
</div>
Ces limitations ont été éliminées par [la descente du gradient](/perceptron?type=gradientdescent) qui utilise des entrées et des sorties réelles.
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

+15 -1
View File
@@ -1,10 +1,10 @@
@import 'tailwindcss'; @import 'tailwindcss';
@plugin "@tailwindcss/typography";
@import 'tw-animate-css'; @import 'tw-animate-css';
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php'; @source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
@source '../../storage/framework/views/*.php'; @source '../../storage/framework/views/*.php';
@custom-variant dark (&:is(.dark *)); @custom-variant dark (&:is(.dark *));
@theme inline { @theme inline {
@@ -170,3 +170,17 @@
@apply bg-background text-foreground; @apply bg-background text-foreground;
} }
} }
@layer components {
.quill-content .ql-align-center { text-align: center; }
.quill-content .ql-align-right { text-align: right; }
.quill-content .ql-align-justify { text-align: justify; }
.quill-content .ql-size-small { font-size: 0.875rem; }
.quill-content .ql-size-large { font-size: 1.25rem; }
.quill-content .ql-size-huge { font-size: 1.5rem; }
.quill-content .ql-indent-1 { padding-left: 1.5rem; }
.quill-content .ql-indent-2 { padding-left: 3rem; }
.quill-content .ql-indent-3 { padding-left: 4.5rem; }
}
+15
View File
@@ -1,4 +1,5 @@
import { createInertiaApp } from '@inertiajs/vue3'; import { createInertiaApp } from '@inertiajs/vue3';
import { VueUmamiPlugin } from '@jaseeey/vue-umami-plugin';
import { configureEcho } from '@laravel/echo-vue'; import { configureEcho } from '@laravel/echo-vue';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import type { DefineComponent } from 'vue'; import type { DefineComponent } from 'vue';
@@ -22,6 +23,20 @@ createInertiaApp({
setup({ el, App, props, plugin }) { setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) }) createApp({ render: () => h(App, props) })
.use(plugin) .use(plugin)
.use(
VueUmamiPlugin({
websiteID: 'e616767f-e0d3-4ce9-b551-33c7c3806fc0',
scriptSrc: 'https://abcd.matthiasg.dev/script.js',
// autoTrack: true,
debug: import.meta.env.DEV,
extraDataAttributes: {
'data-auto-track': 'true',
'data-performance': 'true',
'data-domains': 'perceptron.matthiasg.dev,matthiasg.dev,www.matthiasg.dev',
'data-exclude-search': 'false',
}
}),
)
.mount(el); .mount(el);
}, },
progress: { progress: {
+2 -4
View File
@@ -1,13 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { Monitor, Moon, Sun } from 'lucide-vue-next'; import { Moon } from 'lucide-vue-next';
import { useAppearance } from '@/composables/useAppearance'; import { useAppearance } from '@/composables/useAppearance';
const { appearance, updateAppearance } = useAppearance(); const { appearance, updateAppearance } = useAppearance();
const tabs = [ const tabs = [
{ value: 'light', Icon: Sun, label: 'Light' },
{ value: 'dark', Icon: Moon, label: 'Dark' }, { value: 'dark', Icon: Moon, label: 'Dark' },
{ value: 'system', Icon: Monitor, label: 'System' },
] as const; ] as const;
</script> </script>
@@ -18,7 +16,7 @@ const tabs = [
<button <button
v-for="{ value, Icon, label } in tabs" v-for="{ value, Icon, label } in tabs"
:key="value" :key="value"
@click="updateAppearance(value)" @click="updateAppearance()"
:class="[ :class="[
'flex items-center rounded-md px-3.5 py-1.5 transition-colors', 'flex items-center rounded-md px-3.5 py-1.5 transition-colors',
appearance === value appearance === value

Some files were not shown because too many files have changed in this diff Show More