397 lines
13 KiB
Vue
397 lines
13 KiB
Vue
<script setup lang="ts">
|
|
import { Head } from '@inertiajs/vue3';
|
|
import { useEcho } from '@laravel/echo-vue';
|
|
import {
|
|
Chart as ChartJS,
|
|
Title,
|
|
Tooltip,
|
|
Legend,
|
|
BarElement,
|
|
CategoryScale,
|
|
LinearScale,
|
|
PointElement,
|
|
LineElement,
|
|
ChartDataset,
|
|
ChartTypeRegistry,
|
|
BubbleDataPoint,
|
|
Point,
|
|
ChartData,
|
|
} from 'chart.js';
|
|
import { onMounted, ref } from 'vue';
|
|
import { Bar, Chart, Line } from 'vue-chartjs';
|
|
import LinkHeader from '@/components/LinkHeader.vue';
|
|
|
|
ChartJS.register(
|
|
Title,
|
|
Tooltip,
|
|
Legend,
|
|
BarElement,
|
|
CategoryScale,
|
|
LinearScale,
|
|
PointElement,
|
|
LineElement,
|
|
);
|
|
ChartJS.defaults.font.size = 16;
|
|
ChartJS.defaults.color = '#FFF';
|
|
ChartJS.defaults.backgroundColor = '#AAA';
|
|
|
|
const props = defineProps<{
|
|
type: string;
|
|
dataset: number[][];
|
|
sessionId: string;
|
|
}>();
|
|
|
|
console.log('Session ID:', props.sessionId);
|
|
|
|
useEcho(
|
|
`${props.sessionId}-perceptron-training`,
|
|
'PerceptronTrainingIteration',
|
|
percpetronIteration,
|
|
[{}],
|
|
'public',
|
|
);
|
|
useEcho(
|
|
`${props.sessionId}-perceptron-training`,
|
|
'PerceptronTrainingEnded',
|
|
perceptronTrainingEnded,
|
|
[{}],
|
|
'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;
|
|
}[]
|
|
>([]);
|
|
|
|
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,
|
|
});
|
|
}
|
|
|
|
const trainingEnded = ref(false);
|
|
const trainingEndReason = ref('');
|
|
function perceptronTrainingEnded(data: any) {
|
|
console.log('Perceptron training ended:', data);
|
|
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);
|
|
}
|
|
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',
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<Head title="Perceptron Viewer"></Head>
|
|
<main class="space-y-6">
|
|
<LinkHeader class="w-full" />
|
|
<div
|
|
class="align-items-start justify-content-center flex h-full min-h-dvh max-w-dvw"
|
|
>
|
|
<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>
|
|
</div>
|
|
<div class="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],
|
|
),
|
|
],
|
|
}"
|
|
/>
|
|
</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(),
|
|
}"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</template>
|