Files
Reseaux-de-neurones-artific…/resources/js/components/PerceptronDecisionGraph.vue
Matthias Guillitte 9d4b02fab5
Some checks failed
linter / quality (push) Failing after 6m3s
tests / ci (8.4) (push) Successful in 5m40s
tests / ci (8.5) (push) Successful in 5m1s
Light mode support
2026-03-22 17:14:23 +01:00

211 lines
6.3 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import type {
ChartDataset,
ChartTypeRegistry,
BubbleDataPoint,
Point,
} from 'chart.js';
import { computed } from 'vue';
import { Chart } from 'vue-chartjs';
import { colors, gridColor, gridColorBold } from '@/types/graphs';
import type { Iteration } from '@/types/perceptron';
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 perceptron
function perceptronLine(x: number): number {
// w0 + w1*x + w2*y = 0 => y = -(w1/w2)*x - w0/w2
const w2 = perceptronWeights[2] == 0 ? 1e-6 : perceptronWeights[2]; // Avoid division by zero
return -(perceptronWeights[1] / w2) * x - perceptronWeights[0] / w2;
}
// 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 bg-primary dark:bg-transparent!"
: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',
grid: {
color: function (context) {
if (context.tick.value == 0) {
return gridColorBold;
}
return gridColor;
},
},
},
y: {
type: 'linear',
position: 'left',
grid: {
color: function (context) {
if (context.tick.value == 0) {
return gridColorBold;
}
return gridColor;
},
},
},
},
}"
: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>