MonoLayer Perceptron
All checks were successful
linter / quality (push) Successful in 6m16s
tests / ci (8.4) (push) Successful in 4m10s
tests / ci (8.5) (push) Successful in 4m29s

This commit is contained in:
2026-04-04 16:45:04 +02:00
parent f6620c2eca
commit 2f4db07918
21 changed files with 641 additions and 226 deletions

View File

@@ -20,6 +20,16 @@ const links = [
href: '/perceptron',
data: { type: 'adaline' },
},
{
name: 'Mono-couche',
href: '/perceptron',
data: { type: 'monolayer' },
},
{
name: 'Multi-couche',
href: '/perceptron',
data: { type: 'multilayer' },
},
];
const isActiveLink = (link: any) => {

View File

@@ -5,7 +5,7 @@ import type {
BubbleDataPoint,
Point,
} from 'chart.js';
import { computed } from 'vue';
import { computed, ref } from 'vue';
import { Chart } from 'vue-chartjs';
import { colors, gridColor, gridColorBold } from '@/types/graphs';
import type { Iteration } from '@/types/perceptron';
@@ -16,6 +16,10 @@ const props = defineProps<{
activationFunction: (x: number) => number;
}>();
const examplesNumber = computed(() => {
return props.cleanedDataset.reduce((sum, dataset) => sum + dataset.data.length, 0);
});
const farLeftDataPointX = computed(() => {
if (props.cleanedDataset.length === 0) {
return 0;
@@ -25,6 +29,15 @@ const farLeftDataPointX = computed(() => {
);
return minX;
});
const farBottomDataPointY = computed(() => {
if (props.cleanedDataset.length === 0) {
return 0;
}
const minY = Math.min(
...props.cleanedDataset.flatMap((d) => d.data.map((point) => point.y)),
);
return minY;
});
const farRightDataPointX = computed(() => {
if (props.cleanedDataset.length === 0) {
return 0;
@@ -34,8 +47,20 @@ const farRightDataPointX = computed(() => {
);
return maxX;
});
const farTopDataPointY = computed(() => {
if (props.cleanedDataset.length === 0) {
return 0;
}
const maxY = Math.max(
...props.cleanedDataset.flatMap((d) => d.data.map((point) => point.y)),
);
return maxY;
});
function getPerceptronOutput(weightsNetwork: number[][], inputs: number[]): number[] {
function getPerceptronOutput(
weightsNetwork: number[][][],
inputs: number[],
): number[] {
for (const layer of weightsNetwork) {
const nextInputs: number[] = [];
@@ -59,13 +84,14 @@ function getPerceptronOutput(weightsNetwork: number[][], inputs: number[]): numb
return inputs;
}
const nonLinearGraph = ref<boolean>(false);
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);
@@ -74,6 +100,7 @@ function getPerceptronDecisionBoundaryDataset(
networkWeights[0].length == 1 &&
networkWeights[0][0].length <= 3
) {
nonLinearGraph.value = false;
// Unique, 3 weights perceptron
const perceptronWeights = [...networkWeights[0][0]]; // Copy of the unique perceptron weights
@@ -89,76 +116,84 @@ function getPerceptronDecisionBoundaryDataset(
}
// 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,
};
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];
nonLinearGraph.value = true;
for (const layer of networkWeights) {
const nextActivations: number[] = [];
const bubbleTransparency = '30';
const isInDataThreshold = 0.0;
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
// -------- 1⃣ Construction des datasets --------
const datasets: {
type: string;
label: string;
data: Point[];
backgroundColor: string;
pointRadius: number;
borderWidth: number;
order: number;
}[] = [];
// For the number of neuron in the last layer
const lastLayer = networkWeights[networkWeights.length - 1];
for (let i = 0; i < lastLayer.length; i++) {
const dataset = {
type: 'scatter',
label: label,
data: [], // Will be filled with the decision boundary points
backgroundColor: colors[i] + bubbleTransparency || '#AAA',
pointRadius: 15,
borderWidth: 0,
order: -1,
};
datasets.push(dataset);
}
// -------- 2⃣ Échantillonnage grille --------
const decisionBoundary: Point[] = [];
const min = -2;
const max = 2;
const step = 0.03;
const epsilon = 0.01;
const step =
Math.abs(
farRightDataPointX.value + 1 - (farLeftDataPointX.value - 1),
) / 50;
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 });
}
for (
let x = farLeftDataPointX.value - 1;
x <= farRightDataPointX.value + 1;
x += step
) {
for (
let y = farBottomDataPointY.value - 1;
y <= farTopDataPointY.value + 1;
y += step
) {
const values = getPerceptronOutput(networkWeights, [x, y]);
values.forEach((v, i) => {
if (v > isInDataThreshold) {
datasets[i].data.push({ x, y });
}
});
}
}
// -------- 3⃣ Dataset ChartJS --------
return {
type: 'scatter',
label: label,
data: decisionBoundary,
backgroundColor: '#FFFFFF',
pointRadius: 1,
};
return datasets;
}
}
</script>
@@ -180,6 +215,9 @@ function getPerceptronDecisionBoundaryDataset(
text: 'Ligne de décision du Perceptron',
},
},
animation: {
duration: nonLinearGraph || examplesNumber > 10 ? 0 : 1000, // Disable animations for instant updates
},
layout: {
padding: {
left: 10,
@@ -228,7 +266,7 @@ function getPerceptronDecisionBoundaryDataset(
})),
// Perceptron decision boundary
getPerceptronDecisionBoundaryDataset(
...getPerceptronDecisionBoundaryDataset(
props.iterations.length > 0
? props.iterations[props.iterations.length - 1].weights
: [[[0, 0, 0]]],

View File

@@ -94,6 +94,9 @@ const datasets = computed<
text: 'Nombre d\'erreurs par epoch',
},
},
animation: {
duration: iterations.length > 100 ? 0 : 1000, // Disable animations for instant updates
},
scales: {
x: {
stacked: true,