Added configuration panel datasets, back-end refactor and others
This commit is contained in:
52
resources/js/components/IterationTable.vue
Normal file
52
resources/js/components/IterationTable.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ComputedRef } from 'vue';
|
||||
import type { Iteration } from '@/types/perceptron';
|
||||
|
||||
const props = defineProps<{
|
||||
iterations: Iteration[];
|
||||
trainingEnded: boolean;
|
||||
trainingEndReason: string;
|
||||
}>();
|
||||
|
||||
// All weight in a simple array
|
||||
const allWeightPerIteration: ComputedRef<number[][]> = computed(() => {
|
||||
return props.iterations.map((iteration) => {
|
||||
// We flatten the weights
|
||||
return iteration.weights.flat(2);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table class="table w-full border-collapse border border-gray-300">
|
||||
<tr class="text-left" v-if="props.iterations.length > 0">
|
||||
<th>Itération</th>
|
||||
<th>Exemple</th>
|
||||
<th v-for="(weight, index) in allWeightPerIteration[allWeightPerIteration.length - 1]" v-bind:key="index">
|
||||
X<sub>{{ index }}</sub>
|
||||
</th>
|
||||
<th>Erreur</th>
|
||||
</tr>
|
||||
<tr
|
||||
v-for="(iteration, index) in props.iterations"
|
||||
v-bind:key="index"
|
||||
:class="{
|
||||
'bg-gray-900': iteration.iteration % 2 === 0,
|
||||
}"
|
||||
>
|
||||
<td>{{ iteration.iteration }}</td>
|
||||
<td>{{ iteration.exampleIndex }}</td>
|
||||
<td v-for="(weight, index) in allWeightPerIteration[index]" v-bind:key="index">
|
||||
{{ weight.toFixed(2) }}
|
||||
</td>
|
||||
<td>{{ iteration.error.toFixed(2) }}</td>
|
||||
</tr>
|
||||
|
||||
<tr v-if="props.trainingEnded" class="bg-red-900 text-center">
|
||||
<td colspan="100%">
|
||||
<strong>Entraînement terminé :</strong>
|
||||
{{ props.trainingEndReason }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
188
resources/js/components/PerceptronDecisionGraph.vue
Normal file
188
resources/js/components/PerceptronDecisionGraph.vue
Normal file
@@ -0,0 +1,188 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
ChartDataset,
|
||||
ChartTypeRegistry,
|
||||
BubbleDataPoint,
|
||||
Point,
|
||||
} from 'chart.js';
|
||||
import { Chart } from 'vue-chartjs';
|
||||
import type { Iteration } from '@/types/perceptron';
|
||||
import { colors } from '@/types/graphs';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
cleanedDataset: { label: number; data: { x: number; y: number }[] }[];
|
||||
iterations: Iteration[];
|
||||
activationFunction: (x: number) => number;
|
||||
}>();
|
||||
|
||||
const farLeftDataPointX = computed(() => {
|
||||
if (props.cleanedDataset.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
const minX = Math.min(...props.cleanedDataset.flatMap((d) => d.data.map((point) => point.x)));
|
||||
return minX;
|
||||
});
|
||||
const farRightDataPointX = computed(() => {
|
||||
if (props.cleanedDataset.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
const maxX = Math.max(...props.cleanedDataset.flatMap((d) => d.data.map((point) => point.x)));
|
||||
return maxX;
|
||||
});
|
||||
|
||||
function getPerceptronDecisionBoundaryDataset(
|
||||
networkWeights: number[][][],
|
||||
activationFunction: (x: number) => number = (x) => x,
|
||||
): ChartDataset<
|
||||
keyof ChartTypeRegistry,
|
||||
number | Point | [number, number] | BubbleDataPoint | null
|
||||
> {
|
||||
const label = 'Ligne de décision du Perceptron';
|
||||
console.log('Calculating decision boundary with weights:', networkWeights);
|
||||
|
||||
if (
|
||||
networkWeights.length == 1 &&
|
||||
networkWeights[0].length == 1 &&
|
||||
networkWeights[0][0].length == 3
|
||||
) {
|
||||
// Unique, 3 weights perceptron
|
||||
const perceptronWeights = networkWeights[0][0]; // We take the unique
|
||||
|
||||
function perceptronLine(x: number): number {
|
||||
// w0 + w1*x + w2*y = 0 => y = -(w1/w2)*x - w0/w2
|
||||
return -(perceptronWeights[1] / perceptronWeights[2]) * x - perceptronWeights[0] / perceptronWeights[2];
|
||||
}
|
||||
|
||||
// Simple line
|
||||
return {
|
||||
type: 'line',
|
||||
label: label,
|
||||
data: [
|
||||
{
|
||||
x: farLeftDataPointX.value - 1,
|
||||
y: perceptronLine(farLeftDataPointX.value - 1),
|
||||
},
|
||||
{
|
||||
x: farRightDataPointX.value + 1,
|
||||
y: perceptronLine(farRightDataPointX.value + 1),
|
||||
},
|
||||
],
|
||||
borderColor: '#FFF',
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
};
|
||||
} else {
|
||||
function forward(x1: number, x2: number): number {
|
||||
let activations: number[] = [x1, x2];
|
||||
|
||||
for (const layer of networkWeights) {
|
||||
const nextActivations: number[] = [];
|
||||
|
||||
for (const neuron of layer) {
|
||||
const bias = neuron[0];
|
||||
const weights = neuron.slice(1);
|
||||
|
||||
let sum = bias;
|
||||
|
||||
for (let i = 0; i < weights.length; i++) {
|
||||
sum += weights[i] * activations[i];
|
||||
}
|
||||
|
||||
const activated = activationFunction(sum);
|
||||
|
||||
nextActivations.push(activated);
|
||||
}
|
||||
|
||||
activations = nextActivations;
|
||||
}
|
||||
|
||||
return activations[0]; // on suppose sortie unique
|
||||
}
|
||||
|
||||
// -------- 2️⃣ Échantillonnage grille --------
|
||||
const decisionBoundary: Point[] = [];
|
||||
const min = -2;
|
||||
const max = 2;
|
||||
const step = 0.03;
|
||||
const epsilon = 0.01;
|
||||
|
||||
for (let x = min; x <= max; x += step) {
|
||||
for (let y = min; y <= max; y += step) {
|
||||
const value = forward(x, y);
|
||||
|
||||
if (Math.abs(value) < epsilon) {
|
||||
decisionBoundary.push({ x, y });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------- 3️⃣ Dataset ChartJS --------
|
||||
return {
|
||||
type: 'scatter',
|
||||
label: label,
|
||||
data: decisionBoundary,
|
||||
backgroundColor: '#FFFFFF',
|
||||
pointRadius: 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Chart
|
||||
v-if="props.cleanedDataset.length > 0 || props.iterations.length > 0"
|
||||
class="flex"
|
||||
:options="{
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Ligne de décision du Perceptron',
|
||||
},
|
||||
},
|
||||
layout: {
|
||||
padding: {
|
||||
left: 10,
|
||||
right: 10,
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
type: 'linear',
|
||||
position: 'bottom',
|
||||
},
|
||||
y: {
|
||||
type: 'linear',
|
||||
position: 'left',
|
||||
},
|
||||
},
|
||||
}"
|
||||
:data="{
|
||||
datasets: [
|
||||
// Points from the dataset
|
||||
...props.cleanedDataset.map((dataset, index) => ({
|
||||
type: 'scatter',
|
||||
label: `Label ${dataset.label}`,
|
||||
data: dataset.data,
|
||||
backgroundColor:
|
||||
colors[index] || '#AAA',
|
||||
})),
|
||||
|
||||
// Perceptron decision boundary
|
||||
getPerceptronDecisionBoundaryDataset(
|
||||
props.iterations.length > 0
|
||||
? props.iterations[props.iterations.length - 1].weights
|
||||
: [[[0, 0, 0]]],
|
||||
props.activationFunction,
|
||||
),
|
||||
],
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
84
resources/js/components/PerceptronIterationsErrorsGraph.vue
Normal file
84
resources/js/components/PerceptronIterationsErrorsGraph.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<script setup lang="ts">
|
||||
import type { ChartData } from 'chart.js';
|
||||
import { Bar } from 'vue-chartjs';
|
||||
import { colors } from '@/types/graphs';
|
||||
import type { Iteration } from '@/types/perceptron';
|
||||
|
||||
const props = defineProps<{
|
||||
iterations: Iteration[];
|
||||
}>();
|
||||
|
||||
/**
|
||||
* Return the datasets of the iterations with the form { label: `Exemple ${exampleIndex}`, data: [error for iteration 1, error for iteration 2, ...] }
|
||||
*/
|
||||
function getPerceptronErrorsPerIteration(): ChartData<
|
||||
'bar',
|
||||
(number | [number, number] | null)[]
|
||||
>[] {
|
||||
const datasets: ChartData<'bar', (number | [number, number] | null)[]>[] =
|
||||
[];
|
||||
|
||||
const backgroundColors = colors;
|
||||
|
||||
props.iterations.forEach((iteration) => {
|
||||
const exampleLabel = `Exemple ${iteration.exampleIndex}`;
|
||||
let dataset = datasets.find((d) => d.label === exampleLabel);
|
||||
if (!dataset) {
|
||||
dataset = {
|
||||
label: exampleLabel,
|
||||
data: [],
|
||||
backgroundColor:
|
||||
backgroundColors[
|
||||
iteration.exampleIndex % backgroundColors.length
|
||||
],
|
||||
};
|
||||
datasets.push(dataset);
|
||||
}
|
||||
dataset.data.push(iteration.error);
|
||||
});
|
||||
|
||||
// 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]);
|
||||
return aIndex - bIndex;
|
||||
});
|
||||
|
||||
return datasets;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Bar
|
||||
class="flex"
|
||||
:options="{
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Nombre d\'erreurs par itération',
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
min: 0,
|
||||
},
|
||||
y: {
|
||||
stacked: true,
|
||||
beginAtZero: true,
|
||||
},
|
||||
},
|
||||
}"
|
||||
:data="{
|
||||
labels: props.iterations.reduce((labels, iteration) => {
|
||||
if (!labels.includes(`Itération ${iteration.iteration}`)) {
|
||||
labels.push(`Itération ${iteration.iteration}`);
|
||||
}
|
||||
return labels;
|
||||
}, [] as string[]),
|
||||
datasets: getPerceptronErrorsPerIteration(),
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
215
resources/js/components/PerceptronSetup.vue
Normal file
215
resources/js/components/PerceptronSetup.vue
Normal file
@@ -0,0 +1,215 @@
|
||||
<script setup lang="ts">
|
||||
// import { Form } from '@inertiajs/vue3';
|
||||
|
||||
import { ref, watch } from 'vue';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
} from '@/components/ui/form';
|
||||
import {
|
||||
NativeSelect,
|
||||
NativeSelectOption,
|
||||
} from '@/components/ui/native-select';
|
||||
import type {
|
||||
Dataset,
|
||||
InitializationMethod,
|
||||
PerceptronType,
|
||||
} from '@/types/perceptron';
|
||||
import Button from './ui/button/Button.vue';
|
||||
import Card from './ui/card/Card.vue';
|
||||
import CardContent from './ui/card/CardContent.vue';
|
||||
import CardHeader from './ui/card/CardHeader.vue';
|
||||
import CardTitle from './ui/card/CardTitle.vue';
|
||||
import Input from './ui/input/Input.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
type: PerceptronType;
|
||||
datasets: Dataset[];
|
||||
selectedDataset: string;
|
||||
initializationMethod: InitializationMethod;
|
||||
minError: number;
|
||||
defaultLearningRate: number;
|
||||
sessionId: string;
|
||||
defaultMaxIterations: number;
|
||||
}>();
|
||||
|
||||
const selectedDatasetCopy = ref(props.selectedDataset);
|
||||
const selectedMethod = ref(props.initializationMethod);
|
||||
const minError = ref(props.minError);
|
||||
const learningRate = ref(props.defaultLearningRate);
|
||||
const maxIterations = ref(props.defaultMaxIterations);
|
||||
|
||||
watch(selectedDatasetCopy, (newvalue) => {
|
||||
const selectedDatasetCopy = props.datasets.find(
|
||||
(dataset) => dataset.label === newvalue
|
||||
) || null;
|
||||
|
||||
let defaultLearningRate = props.defaultLearningRate;
|
||||
if (selectedDatasetCopy && selectedDatasetCopy.defaultLearningRate !== undefined) {
|
||||
defaultLearningRate = selectedDatasetCopy.defaultLearningRate;
|
||||
}
|
||||
learningRate.value = defaultLearningRate;
|
||||
maxIterations.value = props.defaultMaxIterations;
|
||||
})
|
||||
|
||||
const trainingId = ref<string>('');
|
||||
|
||||
function startTraining() {
|
||||
if (!selectedDatasetCopy.value) {
|
||||
alert('Veuillez sélectionner un dataset avant de lancer l\'entraînement.');
|
||||
return;
|
||||
}
|
||||
|
||||
trainingId.value = `${props.sessionId}-${Date.now()}`; // Unique training ID based on session and timestamp
|
||||
emit('update:trainingId', trainingId.value); // Emit the training ID to the parent component
|
||||
|
||||
fetch('/api/perceptron/run', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify({
|
||||
type: 'simple',
|
||||
dataset: selectedDatasetCopy.value,
|
||||
weight_init_method: selectedMethod.value,
|
||||
min_error: 0.01,
|
||||
learning_rate: learningRate.value,
|
||||
session_id: props.sessionId,
|
||||
training_id: trainingId.value,
|
||||
max_iterations: maxIterations.value,
|
||||
}),
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
console.log('Perceptron training started:', data);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error starting perceptron training:', error);
|
||||
});
|
||||
}
|
||||
|
||||
const emit = defineEmits(['update:selectedDataset', 'update:trainingId']);
|
||||
watch(selectedDatasetCopy, (newValue) => {
|
||||
emit('update:selectedDataset', newValue);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Configuration du Perceptron</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form
|
||||
class="grid auto-cols-max grid-flow-row grid-cols-1 gap-4 space-y-6 md:grid-cols-2"
|
||||
>
|
||||
<!-- DATASET -->
|
||||
<FormField name="dataset">
|
||||
<FormItem>
|
||||
<FormLabel>Dataset</FormLabel>
|
||||
<FormControl>
|
||||
<NativeSelect
|
||||
name="dataset"
|
||||
id="dataset-select"
|
||||
v-model="selectedDatasetCopy"
|
||||
>
|
||||
<NativeSelectOption value="" disabled
|
||||
>Sélectionnez un dataset</NativeSelectOption
|
||||
>
|
||||
<NativeSelectOption
|
||||
v-for="dataset in props.datasets"
|
||||
v-bind:key="dataset.label"
|
||||
:value="dataset.label"
|
||||
>
|
||||
{{ dataset.label }}
|
||||
</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- DEFAULT WEIGHTS -->
|
||||
<FormField name="weight_init_method">
|
||||
<FormItem>
|
||||
<FormLabel
|
||||
>Méthode d'initialisation des poids</FormLabel
|
||||
>
|
||||
<FormControl>
|
||||
<NativeSelect
|
||||
name="weight_init_method"
|
||||
id="weight_init_method"
|
||||
v-model="selectedMethod"
|
||||
>
|
||||
<NativeSelectOption
|
||||
v-for="method in ['zeros', 'random']"
|
||||
v-bind:key="method"
|
||||
:value="method"
|
||||
>
|
||||
{{ method }}
|
||||
</NativeSelectOption>
|
||||
</NativeSelect>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- MIN ERROR -->
|
||||
<FormField name="min_error" v-if="props.type !== 'simple'">
|
||||
<FormItem>
|
||||
<FormLabel>Erreur minimale</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
v-model="minError"
|
||||
min="0"
|
||||
step="0.001"
|
||||
class="w-min"
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- LEARNING RATE -->
|
||||
<FormField name="learning_rate">
|
||||
<FormItem>
|
||||
<FormLabel>Taux d'apprentissage</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
v-model="learningRate"
|
||||
min="0"
|
||||
step="0.001"
|
||||
class="w-min"
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- MAX ITERATIONS -->
|
||||
<FormField name="max_iterations">
|
||||
<FormItem>
|
||||
<FormLabel>Nombre maximum d'itérations</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
v-model="maxIterations"
|
||||
min="0"
|
||||
step="1"
|
||||
class="w-min"
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</Form>
|
||||
<Button variant="outline" class="cursor-pointer mt-6" @click="startTraining">Lancer</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</template>
|
||||
17
resources/js/components/ui/form/FormControl.vue
Normal file
17
resources/js/components/ui/form/FormControl.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<script lang="ts" setup>
|
||||
import { Slot } from "reka-ui"
|
||||
import { useFormField } from "./useFormField"
|
||||
|
||||
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Slot
|
||||
:id="formItemId"
|
||||
data-slot="form-control"
|
||||
:aria-describedby="!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`"
|
||||
:aria-invalid="!!error"
|
||||
>
|
||||
<slot />
|
||||
</Slot>
|
||||
</template>
|
||||
21
resources/js/components/ui/form/FormDescription.vue
Normal file
21
resources/js/components/ui/form/FormDescription.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script lang="ts" setup>
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useFormField } from "./useFormField"
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes["class"]
|
||||
}>()
|
||||
|
||||
const { formDescriptionId } = useFormField()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<p
|
||||
:id="formDescriptionId"
|
||||
data-slot="form-description"
|
||||
:class="cn('text-muted-foreground text-sm', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</p>
|
||||
</template>
|
||||
23
resources/js/components/ui/form/FormItem.vue
Normal file
23
resources/js/components/ui/form/FormItem.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts" setup>
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { useId } from "reka-ui"
|
||||
import { provide } from "vue"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { FORM_ITEM_INJECTION_KEY } from "./injectionKeys"
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes["class"]
|
||||
}>()
|
||||
|
||||
const id = useId()
|
||||
provide(FORM_ITEM_INJECTION_KEY, id)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
data-slot="form-item"
|
||||
:class="cn('grid gap-2', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
25
resources/js/components/ui/form/FormLabel.vue
Normal file
25
resources/js/components/ui/form/FormLabel.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<script lang="ts" setup>
|
||||
import type { LabelProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { useFormField } from "./useFormField"
|
||||
|
||||
const props = defineProps<LabelProps & { class?: HTMLAttributes["class"] }>()
|
||||
|
||||
const { error, formItemId } = useFormField()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Label
|
||||
data-slot="form-label"
|
||||
:data-error="!!error"
|
||||
:class="cn(
|
||||
'data-[error=true]:text-destructive',
|
||||
props.class,
|
||||
)"
|
||||
:for="formItemId"
|
||||
>
|
||||
<slot />
|
||||
</Label>
|
||||
</template>
|
||||
23
resources/js/components/ui/form/FormMessage.vue
Normal file
23
resources/js/components/ui/form/FormMessage.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts" setup>
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { ErrorMessage } from "vee-validate"
|
||||
import { toValue } from "vue"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useFormField } from "./useFormField"
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes["class"]
|
||||
}>()
|
||||
|
||||
const { name, formMessageId } = useFormField()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ErrorMessage
|
||||
:id="formMessageId"
|
||||
data-slot="form-message"
|
||||
as="p"
|
||||
:name="toValue(name)"
|
||||
:class="cn('text-destructive text-sm', props.class)"
|
||||
/>
|
||||
</template>
|
||||
7
resources/js/components/ui/form/index.ts
Normal file
7
resources/js/components/ui/form/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export { default as FormControl } from "./FormControl.vue"
|
||||
export { default as FormDescription } from "./FormDescription.vue"
|
||||
export { default as FormItem } from "./FormItem.vue"
|
||||
export { default as FormLabel } from "./FormLabel.vue"
|
||||
export { default as FormMessage } from "./FormMessage.vue"
|
||||
export { FORM_ITEM_INJECTION_KEY } from "./injectionKeys"
|
||||
export { Form, Field as FormField, FieldArray as FormFieldArray } from "vee-validate"
|
||||
4
resources/js/components/ui/form/injectionKeys.ts
Normal file
4
resources/js/components/ui/form/injectionKeys.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import type { InjectionKey } from "vue"
|
||||
|
||||
export const FORM_ITEM_INJECTION_KEY
|
||||
= Symbol() as InjectionKey<string>
|
||||
30
resources/js/components/ui/form/useFormField.ts
Normal file
30
resources/js/components/ui/form/useFormField.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { FieldContextKey } from "vee-validate"
|
||||
import { computed, inject } from "vue"
|
||||
import { FORM_ITEM_INJECTION_KEY } from "./injectionKeys"
|
||||
|
||||
export function useFormField() {
|
||||
const fieldContext = inject(FieldContextKey)
|
||||
const fieldItemContext = inject(FORM_ITEM_INJECTION_KEY)
|
||||
|
||||
if (!fieldContext)
|
||||
throw new Error("useFormField should be used within <FormField>")
|
||||
|
||||
const { name, errorMessage: error, meta } = fieldContext
|
||||
const id = fieldItemContext
|
||||
|
||||
const fieldState = {
|
||||
valid: computed(() => meta.valid),
|
||||
isDirty: computed(() => meta.dirty),
|
||||
isTouched: computed(() => meta.touched),
|
||||
error,
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
formItemId: `${id}-form-item`,
|
||||
formDescriptionId: `${id}-form-item-description`,
|
||||
formMessageId: `${id}-form-item-message`,
|
||||
...fieldState,
|
||||
}
|
||||
}
|
||||
50
resources/js/components/ui/native-select/NativeSelect.vue
Normal file
50
resources/js/components/ui/native-select/NativeSelect.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
import type { AcceptableValue } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit, useVModel } from "@vueuse/core"
|
||||
import { ChevronDownIcon } from "lucide-vue-next"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
const props = defineProps<{ modelValue?: AcceptableValue | AcceptableValue[], class?: HTMLAttributes["class"] }>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
"update:modelValue": AcceptableValue
|
||||
}>()
|
||||
|
||||
const modelValue = useVModel(props, "modelValue", emit, {
|
||||
passive: true,
|
||||
defaultValue: "",
|
||||
})
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="group/native-select relative w-fit has-[select:disabled]:opacity-50"
|
||||
data-slot="native-select-wrapper"
|
||||
>
|
||||
<select
|
||||
v-bind="{ ...$attrs, ...delegatedProps }"
|
||||
v-model="modelValue"
|
||||
data-slot="native-select"
|
||||
:class="cn(
|
||||
'border-input placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 dark:hover:bg-input/50 h-9 w-full min-w-0 appearance-none rounded-md border bg-transparent px-3 py-2 pr-9 text-sm shadow-xs transition-[color,box-shadow] outline-none disabled:pointer-events-none disabled:cursor-not-allowed',
|
||||
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
||||
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
||||
props.class,
|
||||
)"
|
||||
>
|
||||
<slot />
|
||||
</select>
|
||||
<ChevronDownIcon
|
||||
class="text-muted-foreground pointer-events-none absolute top-[25%] right-3.5 size-4 -translate-y-1/2 opacity-50 select-none"
|
||||
aria-hidden="true"
|
||||
data-slot="native-select-icon"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
<!-- @fallthroughAttributes true -->
|
||||
<!-- @strictTemplates true -->
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<optgroup data-slot="native-select-optgroup" :class="cn('bg-popover text-popover-foreground', props.class)">
|
||||
<slot />
|
||||
</optgroup>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
<!-- @fallthroughAttributes true -->
|
||||
<!-- @strictTemplates true -->
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const props = defineProps<{ class?: HTMLAttributes["class"] }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<option data-slot="native-select-option" :class="cn('bg-popover text-popover-foreground', props.class)">
|
||||
<slot />
|
||||
</option>
|
||||
</template>
|
||||
3
resources/js/components/ui/native-select/index.ts
Normal file
3
resources/js/components/ui/native-select/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default as NativeSelect } from "./NativeSelect.vue"
|
||||
export { default as NativeSelectOptGroup } from "./NativeSelectOptGroup.vue"
|
||||
export { default as NativeSelectOption } from "./NativeSelectOption.vue"
|
||||
@@ -11,15 +11,19 @@ import {
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
ChartDataset,
|
||||
ChartTypeRegistry,
|
||||
BubbleDataPoint,
|
||||
Point,
|
||||
ChartData,
|
||||
} from 'chart.js';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { Bar, Chart, Line } from 'vue-chartjs';
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import LinkHeader from '@/components/LinkHeader.vue';
|
||||
import type {
|
||||
Dataset,
|
||||
InitializationMethod,
|
||||
Iteration,
|
||||
PerceptronType,
|
||||
} from '@/types/perceptron';
|
||||
import IterationTable from '../components/IterationTable.vue';
|
||||
import PerceptronDecisionGraph from '../components/PerceptronDecisionGraph.vue';
|
||||
import PerceptronIterationsErrorsGraph from '../components/PerceptronIterationsErrorsGraph.vue';
|
||||
import PerceptronSetup from '../components/PerceptronSetup.vue';
|
||||
|
||||
ChartJS.register(
|
||||
Title,
|
||||
@@ -36,11 +40,52 @@ ChartJS.defaults.color = '#FFF';
|
||||
ChartJS.defaults.backgroundColor = '#AAA';
|
||||
|
||||
const props = defineProps<{
|
||||
type: string;
|
||||
dataset: number[][];
|
||||
type: PerceptronType;
|
||||
sessionId: string;
|
||||
datasets: Dataset[];
|
||||
minError: number;
|
||||
learningRate: number;
|
||||
maxIterations: number;
|
||||
}>();
|
||||
|
||||
const selectedDatasetName = ref<string>('');
|
||||
const dataset = computed<number[][]>(() => {
|
||||
const selected = props.datasets.find(
|
||||
(d) => d.label === selectedDatasetName.value,
|
||||
);
|
||||
|
||||
return selected ? selected.data : [];
|
||||
});
|
||||
const cleanedDataset = computed<
|
||||
{
|
||||
label: number;
|
||||
data: { x: number; y: number }[];
|
||||
}[]
|
||||
>(() => {
|
||||
if (!dataset.value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const cleanedDataset: {
|
||||
label: number;
|
||||
data: { x: number; y: number }[];
|
||||
}[] = [];
|
||||
// Separate data into each dataset based on value of the last column (label)
|
||||
dataset.value.forEach((row) => {
|
||||
const label = row[row.length - 1];
|
||||
const dataPoint = { x: row[0], y: row[1] };
|
||||
|
||||
let dataset = cleanedDataset.find((d) => d.label === label);
|
||||
if (!dataset) {
|
||||
dataset = { label, data: [] };
|
||||
cleanedDataset.push(dataset);
|
||||
}
|
||||
dataset.data.push(dataPoint);
|
||||
});
|
||||
return cleanedDataset;
|
||||
});
|
||||
const initializationMethod = ref<InitializationMethod>('zeros');
|
||||
|
||||
console.log('Session ID:', props.sessionId);
|
||||
|
||||
useEcho(
|
||||
@@ -57,182 +102,71 @@ useEcho(
|
||||
[{}],
|
||||
'public',
|
||||
);
|
||||
useEcho(
|
||||
`${props.sessionId}-perceptron-training`,
|
||||
'PerceptronInitialization',
|
||||
perceptroninitialization,
|
||||
[{}],
|
||||
'public',
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
// make a POST request to start the perceptron training
|
||||
fetch('/api/perceptron/run', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify({
|
||||
type: 'simple',
|
||||
min_error: 0.01,
|
||||
session_id: props.sessionId,
|
||||
}),
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
console.log('Perceptron training started:', data);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error starting perceptron training:', error);
|
||||
});
|
||||
});
|
||||
|
||||
const iterations = ref<
|
||||
{
|
||||
iteration: number;
|
||||
exampleIndex: number;
|
||||
weights: number[];
|
||||
error: number;
|
||||
}[]
|
||||
>([]);
|
||||
const iterations = ref<Iteration[]>([]);
|
||||
|
||||
const trainingId = ref<string>('');
|
||||
function percpetronIteration(data: any) {
|
||||
console.log('Received perceptron iteration data:', data);
|
||||
iterations.value.push({
|
||||
iteration: data.iteration,
|
||||
exampleIndex: data.exampleIndex,
|
||||
weights: data.synaptic_weights,
|
||||
error: data.error,
|
||||
});
|
||||
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;
|
||||
}
|
||||
|
||||
// Separate data into each dataset based on value of the last column (label)
|
||||
const cleanedDataset: { label: number; data: { x: number; y: number }[] }[] =
|
||||
[];
|
||||
props.dataset.forEach((row) => {
|
||||
const label = row[row.length - 1];
|
||||
const dataPoint = { x: row[0], y: row[1] };
|
||||
|
||||
let dataset = cleanedDataset.find((d) => d.label === label);
|
||||
if (!dataset) {
|
||||
dataset = { label, data: [] };
|
||||
cleanedDataset.push(dataset);
|
||||
const activationFunction = ref<string>('');
|
||||
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;
|
||||
}
|
||||
dataset.data.push(dataPoint);
|
||||
});
|
||||
|
||||
function getPerceptronDecisionBoundaryDataset(
|
||||
weights: number[],
|
||||
): ChartDataset<
|
||||
keyof ChartTypeRegistry,
|
||||
number | Point | [number, number] | BubbleDataPoint | null
|
||||
> {
|
||||
const label = 'Ligne de décision du Perceptron';
|
||||
|
||||
if (weights.length == 3) {
|
||||
// Simple line
|
||||
return {
|
||||
type: 'line',
|
||||
label: label,
|
||||
data: [
|
||||
{
|
||||
x: -1,
|
||||
y:
|
||||
-(weights[1] / weights[2]) * -1 -
|
||||
weights[0] / weights[2],
|
||||
},
|
||||
{
|
||||
x: 2,
|
||||
y: -(weights[1] / weights[2]) * 2 - weights[0] / weights[2],
|
||||
},
|
||||
],
|
||||
borderColor: '#FFF',
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: 'scatter',
|
||||
label: label,
|
||||
data: (() => {
|
||||
const decisionBoundary = [];
|
||||
const latestWeights =
|
||||
iterations.value.length > 0
|
||||
? iterations.value[iterations.value.length - 1].weights
|
||||
: [0, 0, 0]; // default weights if no iterations yet
|
||||
for (let x = -2; x <= 2; x += 0.05) {
|
||||
for (let y = -2; y <= 2; y += 0.05) {
|
||||
let value = 0;
|
||||
for (let i = 0; i < latestWeights.length - 1; i++) {
|
||||
value += latestWeights[i] * (i === 0 ? x : y); // TODO : Fix formula
|
||||
}
|
||||
value += latestWeights[0]; // bias
|
||||
|
||||
if (Math.abs(value) < 0.003) {
|
||||
decisionBoundary.push({ x: x, y: y });
|
||||
}
|
||||
}
|
||||
}
|
||||
return decisionBoundary;
|
||||
})(),
|
||||
backgroundColor: '#FFF',
|
||||
};
|
||||
activationFunction.value = data.activation_function;
|
||||
}
|
||||
function getActivationFunction(type: string): (x: number) => number {
|
||||
switch (type) {
|
||||
case 'step':
|
||||
return (x) => (x >= 0 ? 1 : 0);
|
||||
case 'sigmoid':
|
||||
return (x) => 1 / (1 + Math.exp(-x));
|
||||
case 'tanh':
|
||||
return (x) => Math.tanh(x);
|
||||
default:
|
||||
return (x) => x; // Identity function as fallback
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the datasets of the iterations with the form { label: `Exemple ${exampleIndex}`, data: [error for iteration 1, error for iteration 2, ...] }
|
||||
*/
|
||||
function getPerceptronErrorsPerIteration(): ChartData<
|
||||
'bar',
|
||||
(number | [number, number] | null)[]
|
||||
>[] {
|
||||
const datasets: ChartData<'bar', (number | [number, number] | null)[]>[] =
|
||||
[];
|
||||
|
||||
const backgroundColors = [
|
||||
'#FF6384',
|
||||
'#36A2EB',
|
||||
'#FFCE56',
|
||||
'#4BC0C0',
|
||||
'#9D5C5C',
|
||||
'#8B4513',
|
||||
'#2E8B57',
|
||||
'#800080',
|
||||
];
|
||||
|
||||
iterations.value.forEach((iteration) => {
|
||||
const exampleLabel = `Exemple ${iteration.exampleIndex}`;
|
||||
let dataset = datasets.find((d) => d.label === exampleLabel);
|
||||
if (!dataset) {
|
||||
dataset = {
|
||||
label: exampleLabel,
|
||||
data: [],
|
||||
backgroundColor:
|
||||
backgroundColors[
|
||||
iteration.exampleIndex % backgroundColors.length
|
||||
],
|
||||
};
|
||||
datasets.push(dataset);
|
||||
}
|
||||
dataset.data.push(iteration.error);
|
||||
});
|
||||
|
||||
// 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]);
|
||||
return aIndex - bIndex;
|
||||
});
|
||||
|
||||
return datasets;
|
||||
function resetTraining() {
|
||||
iterations.value = [];
|
||||
trainingEnded.value = false;
|
||||
trainingEndReason.value = '';
|
||||
activationFunction.value = '';
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -240,154 +174,51 @@ function getPerceptronErrorsPerIteration(): ChartData<
|
||||
<Head title="Perceptron Viewer"></Head>
|
||||
<main class="space-y-6">
|
||||
<LinkHeader class="w-full" />
|
||||
<PerceptronSetup
|
||||
:type="props.type"
|
||||
:datasets="props.datasets"
|
||||
:selectedDataset="selectedDatasetName"
|
||||
:initializationMethod="initializationMethod"
|
||||
:minError="props.minError"
|
||||
:sessionId="props.sessionId"
|
||||
:defaultLearningRate="props.learningRate"
|
||||
:defaultMaxIterations="props.maxIterations"
|
||||
@update:selected-dataset="
|
||||
(newValue) => {
|
||||
selectedDatasetName = newValue;
|
||||
}
|
||||
"
|
||||
@update:training-id="
|
||||
(newValue) => {
|
||||
trainingId = newValue;
|
||||
resetTraining();
|
||||
}"
|
||||
/>
|
||||
<div
|
||||
class="align-items-start justify-content-center flex h-full min-h-dvh max-w-dvw"
|
||||
v-if="selectedDatasetName || iterations.length > 0"
|
||||
>
|
||||
<div class="max-h-full w-full overflow-y-scroll">
|
||||
<table
|
||||
class="table w-full border-collapse border border-gray-300"
|
||||
>
|
||||
<tr class="text-left" v-if="iterations.length > 0">
|
||||
<th>Itération</th>
|
||||
<th>Exemple</th>
|
||||
<th
|
||||
v-for="(weight, index) in iterations[0].weights"
|
||||
v-bind:key="index"
|
||||
>
|
||||
X<sub>{{ index }}</sub>
|
||||
</th>
|
||||
<th>Erreur</th>
|
||||
</tr>
|
||||
<tr
|
||||
v-for="(iteration, index) in iterations"
|
||||
v-bind:key="index"
|
||||
:class="{
|
||||
'bg-gray-900': iteration.iteration % 2 === 0,
|
||||
}"
|
||||
>
|
||||
<td>{{ iteration.iteration }}</td>
|
||||
<td>{{ iteration.exampleIndex }}</td>
|
||||
<td
|
||||
v-for="(weight, index) in iteration.weights"
|
||||
v-bind:key="index"
|
||||
>
|
||||
{{ weight.toFixed(2) }}
|
||||
</td>
|
||||
<td>{{ iteration.error.toFixed(2) }}</td>
|
||||
</tr>
|
||||
|
||||
<tr v-if="trainingEnded" class="bg-red-900 text-center">
|
||||
<td colspan="100%">
|
||||
<strong>Entraînement terminé :</strong>
|
||||
{{ trainingEndReason }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<IterationTable
|
||||
:iterations="iterations"
|
||||
:trainingEnded="trainingEnded"
|
||||
:trainingEndReason="trainingEndReason"
|
||||
/>
|
||||
</div>
|
||||
<div class="h-full w-full">
|
||||
<div class="sticky top-0 h-full w-full">
|
||||
<div>
|
||||
<Chart
|
||||
class="flex"
|
||||
:options="{
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Ligne de décision du Perceptron',
|
||||
},
|
||||
},
|
||||
layout: {
|
||||
padding: {
|
||||
left: 10,
|
||||
right: 10,
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
type: 'linear',
|
||||
position: 'bottom',
|
||||
},
|
||||
y: {
|
||||
type: 'linear',
|
||||
position: 'left',
|
||||
},
|
||||
},
|
||||
}"
|
||||
:data="{
|
||||
datasets: [
|
||||
// Points from the dataset
|
||||
...cleanedDataset.map((dataset, index) => ({
|
||||
type: 'scatter',
|
||||
label: `Classe ${dataset.label}`,
|
||||
data: dataset.data,
|
||||
backgroundColor:
|
||||
[
|
||||
'#FF6384',
|
||||
'#36A2EB',
|
||||
'#FFCE56',
|
||||
'#4BC0C0',
|
||||
'#9D5C5C',
|
||||
'#8B4513',
|
||||
'#2E8B57',
|
||||
'#800080',
|
||||
][index] || '#AAA',
|
||||
})),
|
||||
|
||||
// Perceptron decision boundary
|
||||
getPerceptronDecisionBoundaryDataset(
|
||||
iterations.length > 0
|
||||
? iterations[iterations.length - 1]
|
||||
.weights
|
||||
: [0, 0, 0],
|
||||
),
|
||||
],
|
||||
}"
|
||||
<PerceptronDecisionGraph
|
||||
:cleanedDataset="cleanedDataset"
|
||||
:iterations="iterations"
|
||||
:activation-function="
|
||||
getActivationFunction(activationFunction)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Bar
|
||||
class="flex"
|
||||
:options="{
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Nombre d\'erreurs par itération',
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
min: 0,
|
||||
},
|
||||
y: {
|
||||
stacked: true,
|
||||
beginAtZero: true,
|
||||
},
|
||||
},
|
||||
}"
|
||||
:data="{
|
||||
labels: iterations.reduce((labels, iteration) => {
|
||||
if (
|
||||
!labels.includes(
|
||||
`Itération ${iteration.iteration}`,
|
||||
)
|
||||
) {
|
||||
labels.push(
|
||||
`Itération ${iteration.iteration}`,
|
||||
);
|
||||
}
|
||||
return labels;
|
||||
}, [] as string[]),
|
||||
datasets: getPerceptronErrorsPerIteration(),
|
||||
}"
|
||||
<PerceptronIterationsErrorsGraph
|
||||
:iterations="iterations"
|
||||
v-if="iterations.length > 0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
34
resources/js/types/graphs.ts
Normal file
34
resources/js/types/graphs.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
|
||||
export const colors = [
|
||||
'#FF6384',
|
||||
'#36A2EB',
|
||||
'#FFCE56',
|
||||
'#4BC0C0',
|
||||
'#9D5C5C',
|
||||
'#8B4513',
|
||||
'#2E8B57',
|
||||
'#800080',
|
||||
'#FF4500',
|
||||
'#008080',
|
||||
'#FF1493',
|
||||
'#00CED1',
|
||||
'#FFD700',
|
||||
'#ADFF2F',
|
||||
'#FF69B4',
|
||||
'#20B2AA',
|
||||
'#FF6347',
|
||||
'#40E0D0',
|
||||
'#EE82EE',
|
||||
'#F08080',
|
||||
'#00FA9A',
|
||||
'#FFB6C1',
|
||||
'#48D1CC',
|
||||
'#C71585',
|
||||
'#00FF7F',
|
||||
'#FF00FF',
|
||||
'#00FFFF',
|
||||
'#FF8C00',
|
||||
'#7B68EE',
|
||||
'#DC143C',
|
||||
'#00FF00',
|
||||
] as const;
|
||||
16
resources/js/types/perceptron.ts
Normal file
16
resources/js/types/perceptron.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export type Iteration = {
|
||||
iteration: number;
|
||||
exampleIndex: number;
|
||||
weights: number[][][];
|
||||
error: number;
|
||||
};
|
||||
|
||||
export type Dataset = {
|
||||
label: string;
|
||||
data: { x: number; y: number }[];
|
||||
defaultLearningRate: number | undefined;
|
||||
};
|
||||
|
||||
export type InitializationMethod = 'zeros' | 'random';
|
||||
|
||||
export type PerceptronType = 'simple';
|
||||
Reference in New Issue
Block a user