189 lines
5.6 KiB
Vue
189 lines
5.6 KiB
Vue
<script setup lang="ts">
|
||
import type {
|
||
ChartDataset,
|
||
ChartTypeRegistry,
|
||
BubbleDataPoint,
|
||
Point,
|
||
} from 'chart.js';
|
||
import { computed } from 'vue';
|
||
import { Chart } from 'vue-chartjs';
|
||
import { colors } 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
|
||
|
||
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"
|
||
: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>
|