Files
perceptron-viewer/resources/js/components/PerceptronIterationsErrorsGraph.vue
T
Ninluc 69e683bcaf
linter / quality (push) Successful in 4m24s
tests / ci (8.4) (push) Successful in 4m47s
tests / ci (8.5) (push) Successful in 5m0s
Some bugfixes and misc
2026-09-08 20:01:52 +02:00

146 lines
4.6 KiB
Vue

<script setup lang="ts">
import type { ChartDataset } from 'chart.js';
import { computed, ref } from 'vue';
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';
const props = defineProps<{
iterations: Iteration[];
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<ErrorDataset[]>(() => {
const datasets: ErrorDataset[] = [];
const epochAverageError: number[] = [];
const backgroundColors = colors;
const exampleCountPerEpoch: Record<number, number> = {};
props.iterations.forEach((iteration) => {
const error = iteration.error ?? 0;
if (!epochErrorOnly.value) {
const exampleLabel = `Exemple ${iteration.exampleIndex}`;
let dataset = datasets.find((d) => d.label === exampleLabel);
if (!dataset) {
dataset = {
label: exampleLabel,
data: [],
order: 1,
backgroundColor:
backgroundColors[
iteration.exampleIndex % backgroundColors.length
],
};
datasets.push(dataset);
}
dataset.data.push(
props.isRegression
? Math.abs(error)
: error,
);
}
exampleCountPerEpoch[iteration.epoch] = (exampleCountPerEpoch[iteration.epoch] || 0) + 1;
// Epoch error
epochAverageError[iteration.epoch] =
(epochAverageError[iteration.epoch] || 0) +
error ** 2 / 2;
});
// 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;
});
// Epoch error
const epochErrorDataset: ErrorDataset = {
type: 'line' as const,
label: "Erreur quadratique moyenne de l'époque",
data: [],
backgroundColor: '#fff',
borderColor: '#fff',
order: 0,
tension: 0.3,
};
epochAverageError.forEach((error, index) => {
const exampleCount = exampleCountPerEpoch[index] || 1; // Avoid division by zero
epochErrorDataset.data.push(error / exampleCount);
});
datasets.push(epochErrorDataset);
return datasets;
});
</script>
<template>
<Chart
type="bar"
class="bg-primary dark:bg-transparent!"
:options="{
responsive: true,
maintainAspectRatio: true,
plugins: {
title: {
display: true,
text: props.isRegression
? 'Erreur de prédiction par epoch'
: 'Nombre d\'erreurs par epoch',
},
},
animation: {
duration: iterations.length > 100 ? 0 : 1000, // Disable animations for instant updates
},
scales: {
x: {
stacked: true,
min: 0,
},
y: {
stacked: true,
beginAtZero: !props.isRegression,
grid: {
color: function (context) {
if (context.tick.value == 0) {
return gridColorBold;
}
return gridColor;
},
},
},
},
}"
:data="{
labels: props.iterations.reduce((labels, iteration) => {
if (!labels.includes(`Époch ${iteration.epoch}`)) {
labels.push(`Époch ${iteration.epoch}`);
}
return labels;
}, [] as string[]),
datasets: datasets,
}"
/>
<div class="flex items-center gap-3">
<Toggle v-model="epochErrorOnly" class="cursor-pointer" variant="outline" id="epoch-error-only"
>Afficher uniquement l'erreur quadratique moyenne de l'époque</Toggle
>
</div>
</template>