Refactor and optimizations

This commit is contained in:
2026-09-08 19:06:02 +02:00
parent 8a50f492ac
commit 0e177f8491
11 changed files with 277 additions and 177 deletions
@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { ChartData } from 'chart.js';
import type { ChartDataset } from 'chart.js';
import { computed, ref } from 'vue';
import { Bar } from 'vue-chartjs';
import { Chart } from 'vue-chartjs';
import { colors, gridColor, gridColorBold } from '@/types/graphs';
import type { Iteration } from '@/types/perceptron';
import Toggle from './ui/toggle/Toggle.vue';
@@ -11,16 +11,16 @@ const props = defineProps<{
isRegression: boolean;
}>();
type ErrorValue = number | [number, number] | null;
type ErrorDataset = ChartDataset<'bar' | 'line', ErrorValue[]>;
const epochErrorOnly = ref<boolean>(false);
/**
* Datasets of the iterations with the form { label: `Exemple ${exampleIndex}`, data: [error for iteration 1, error for iteration 2, ...] }
*/
const datasets = computed<
ChartData<'bar', (number | [number, number] | null)[]>[]
>(() => {
const datasets: ChartData<'bar', (number | [number, number] | null)[]>[] =
[];
const datasets = computed<ErrorDataset[]>(() => {
const datasets: ErrorDataset[] = [];
const epochAverageError: number[] = [];
const backgroundColors = colors;
@@ -60,14 +60,14 @@ const datasets = computed<
// Sort dataset by label (Exemple 0, Exemple 1, ...)
datasets.sort((a, b) => {
const aIndex = parseInt(a.label.split(' ')[1]);
const bIndex = parseInt(b.label.split(' ')[1]);
const aIndex = parseInt((a.label ?? '').split(' ')[1]);
const bIndex = parseInt((b.label ?? '').split(' ')[1]);
return aIndex - bIndex;
});
// Epoch error
const epochErrorDataset = {
type: 'line',
const epochErrorDataset: ErrorDataset = {
type: 'line' as const,
label: "Erreur quadratique moyenne de l'époque",
data: [],
backgroundColor: '#fff',
@@ -88,7 +88,8 @@ const datasets = computed<
</script>
<template>
<Bar
<Chart
type="bar"
class="bg-primary dark:bg-transparent!"
:options="{
responsive: true,
@@ -0,0 +1,104 @@
import { useEcho } from '@laravel/echo-vue';
import { onBeforeUnmount, ref, shallowRef } from 'vue';
import type { Iteration } from '@/types/perceptron';
type TrainingEvent = {
trainingId: string;
};
type IterationEvent = TrainingEvent & {
iterations: Iteration[];
};
type InitializationEvent = TrainingEvent & {
activationFunction: string;
};
type TrainingEndedEvent = TrainingEvent & {
reason: string;
};
export function usePerceptronTraining(sessionId: string) {
const trainingId = ref('');
const iterations = shallowRef<Iteration[]>([]);
const trainingEnded = ref(false);
const trainingEndReason = ref('');
const activationFunction = ref('');
let pendingIterations: Iteration[] = [];
let renderFrame: number | null = null;
function isCurrentTraining(event: TrainingEvent): boolean {
return event.trainingId === trainingId.value;
}
function flushPendingIterations(): void {
renderFrame = null;
if (pendingIterations.length === 0) {
return;
}
iterations.value = [...iterations.value, ...pendingIterations];
pendingIterations = [];
}
function handleIterations(event: IterationEvent): void {
if (!isCurrentTraining(event)) {
return;
}
pendingIterations.push(...event.iterations);
if (renderFrame === null) {
renderFrame = requestAnimationFrame(flushPendingIterations);
}
}
function handleInitialization(event: InitializationEvent): void {
if (isCurrentTraining(event)) {
activationFunction.value = event.activationFunction;
}
}
function handleTrainingEnded(event: TrainingEndedEvent): void {
if (!isCurrentTraining(event)) {
return;
}
flushPendingIterations();
trainingEnded.value = true;
trainingEndReason.value = event.reason;
}
function reset(): void {
if (renderFrame !== null) {
cancelAnimationFrame(renderFrame);
renderFrame = null;
}
pendingIterations = [];
iterations.value = [];
trainingEnded.value = false;
trainingEndReason.value = '';
activationFunction.value = '';
}
function setTrainingId(newTrainingId: string): void {
reset();
trainingId.value = newTrainingId;
}
const channel = `${sessionId}-perceptron-training`;
useEcho(channel, 'PerceptronTrainingIteration', handleIterations, [{}], 'public');
useEcho(channel, 'PerceptronTrainingEnded', handleTrainingEnded, [{}], 'public');
useEcho(channel, 'PerceptronInitialization', handleInitialization, [{}], 'public');
onBeforeUnmount(reset);
return {
activationFunction,
iterations,
setTrainingId,
trainingEnded,
trainingEndReason,
};
}
+11 -85
View File
@@ -1,6 +1,5 @@
<script setup lang="ts">
import { Head } from '@inertiajs/vue3';
import { useEcho } from '@laravel/echo-vue';
import {
Chart as ChartJS,
Title,
@@ -14,13 +13,8 @@ import {
} from 'chart.js';
import { computed, ref } from 'vue';
import LinkHeader from '@/components/LinkHeader.vue';
import type {
Dataset,
DatasetPoint,
InitializationMethod,
Iteration,
PerceptronType,
} from '@/types/perceptron';
import { usePerceptronTraining } from '@/composables/usePerceptronTraining';
import type { Dataset, DatasetPoint, InitializationMethod, PerceptronType } from '@/types/perceptron';
import IterationTable from '../components/IterationTable.vue';
import PerceptronDecisionGraph from '../components/PerceptronDecisionGraph.vue';
import PerceptronIterationsErrorsGraph from '../components/PerceptronIterationsErrorsGraph.vue';
@@ -86,74 +80,16 @@ const cleanedDataset = computed<
const hiddenLayers = ref(3);
const hiddenLayersNeurons = ref(3);
const initializationMethod = ref<InitializationMethod>(props.type === 'multilayer' ? 'random' : 'zeros');
console.log('Session ID:', props.sessionId);
useEcho(
`${props.sessionId}-perceptron-training`,
'PerceptronTrainingIteration',
percpetronIteration,
[{}],
'public',
);
useEcho(
`${props.sessionId}-perceptron-training`,
'PerceptronTrainingEnded',
perceptronTrainingEnded,
[{}],
'public',
);
useEcho(
`${props.sessionId}-perceptron-training`,
'PerceptronInitialization',
perceptroninitialization,
[{}],
'public',
);
const iterations = ref<Iteration[]>([]);
const trainingId = ref<string>('');
function percpetronIteration(data: any) {
console.log('Received perceptron iteration data:', data);
if (data.trainingId !== trainingId.value) {
console.warn(
`Received iteration for training ID ${data.trainingId}, but current training ID is ${trainingId.value}. Ignoring this iteration.`
);
return;
}
iterations.value.push(...data.iterations);
}
const trainingEnded = ref(false);
const trainingEndReason = ref('');
function perceptronTrainingEnded(data: any) {
console.log('Perceptron training ended:', data);
if (data.trainingId !== trainingId.value) {
console.warn(
`Received training ended event for training ID ${data.trainingId}, but current training ID is ${trainingId.value}. Ignoring this event.`
);
return;
}
trainingEnded.value = true;
trainingEndReason.value = data.reason;
}
const activationFunction = ref<string>('');
const {
activationFunction,
iterations,
setTrainingId,
trainingEnded,
trainingEndReason,
} = usePerceptronTraining(props.sessionId);
const isRegression = computed(
() => props.type === 'multilayer' && activationFunction.value === 'linear',
() => (props.type === 'multilayer' || props.type === 'monolayer') && activationFunction.value === 'linear',
);
function perceptroninitialization(data: any) {
console.log('Perceptron training initialized:', data);
if (data.trainingId !== trainingId.value) {
console.warn(
`Received initialization event for training ID ${data.trainingId}, but current training ID is ${trainingId.value}. Ignoring this event.`
);
return;
}
activationFunction.value = data.activationFunction;
}
function getActivationFunction(type: string): (x: number) => number {
switch (type) {
case 'step':
@@ -169,12 +105,6 @@ function getActivationFunction(type: string): (x: number) => number {
}
}
function resetTraining() {
iterations.value = [];
trainingEnded.value = false;
trainingEndReason.value = '';
activationFunction.value = '';
}
</script>
<template>
@@ -197,11 +127,7 @@ function resetTraining() {
selectedDatasetName = newValue;
}
"
@update:training-id="
(newValue) => {
trainingId = newValue;
resetTraining();
}"
@update:training-id="setTrainingId"
/>
<div
class="align-items-start justify-content-center flex h-full min-h-dvh max-w-dvw"