Added configuration panel datasets, back-end refactor and others
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user