Multilayer neuron network
linter / quality (push) Successful in 7m10s
tests / ci (8.4) (push) Successful in 4m43s
tests / ci (8.5) (push) Successful in 4m47s

This commit is contained in:
2026-09-08 16:29:02 +02:00
parent 2f4db07918
commit 08aa04fe56
17 changed files with 630 additions and 50 deletions
@@ -10,10 +10,16 @@ import { Chart } from 'vue-chartjs';
import { colors, gridColor, gridColorBold } from '@/types/graphs';
import type { Iteration } from '@/types/perceptron';
type GraphDataset = ChartDataset<
keyof ChartTypeRegistry,
(number | Point | [number, number] | BubbleDataPoint | null)[]
>;
const props = defineProps<{
cleanedDataset: { label: number; data: { x: number; y: number }[] }[];
iterations: Iteration[];
activationFunction: (x: number) => number;
isRegression: boolean;
}>();
const examplesNumber = computed(() => {
@@ -60,8 +66,9 @@ const farTopDataPointY = computed(() => {
function getPerceptronOutput(
weightsNetwork: number[][][],
inputs: number[],
activationFunction: (x: number) => number = props.activationFunction,
): number[] {
for (const layer of weightsNetwork) {
for (const [layerIndex, layer] of weightsNetwork.entries()) {
const nextInputs: number[] = [];
for (const neuron of layer) {
@@ -74,7 +81,8 @@ function getPerceptronOutput(
sum += weights[i] * inputs[i];
}
const activated = props.activationFunction(sum);
const isOutputLayer = layerIndex === weightsNetwork.length - 1;
const activated = isOutputLayer ? sum : activationFunction(sum);
nextInputs.push(activated);
}
@@ -84,17 +92,126 @@ function getPerceptronOutput(
return inputs;
}
function normalizeNetworkWeights(weightsNetwork: number[][][][] | number[][][]): number[][][] {
if (
weightsNetwork.length === 1 &&
weightsNetwork[0].length === 1 &&
Array.isArray(weightsNetwork[0][0]) &&
Array.isArray(weightsNetwork[0][0][0])
) {
return weightsNetwork[0][0] as unknown as number[][][];
}
return weightsNetwork as number[][][];
}
const nonLinearGraph = ref<boolean>(false);
function getPerceptronDecisionBoundaryDataset(
networkWeights: number[][][],
rawNetworkWeights: number[][][] | number[][][][],
activationFunction: (x: number) => number = (x) => x,
): ChartDataset<
keyof ChartTypeRegistry,
number | Point | [number, number] | BubbleDataPoint | null
>[] {
): GraphDataset[] {
const networkWeights = normalizeNetworkWeights(rawNetworkWeights);
const label = 'Ligne de décision du Perceptron';
console.log('Calculating decision boundary with weights:', networkWeights);
if (props.isRegression) {
const hiddenActivation = (value: number) =>
1 / (1 + Math.exp(-value));
const inputCount = networkWeights[0]?.[0]?.length - 1;
if (inputCount === 1) {
if (networkWeights.length > 1) {
nonLinearGraph.value = true;
const data: Point[] = [];
const firstIntegerX = Math.ceil(farLeftDataPointX.value - 1);
const lastIntegerX = Math.floor(farRightDataPointX.value + 1);
for (
let x = firstIntegerX;
x <= lastIntegerX;
x+= 0.1
) {
data.push({
x,
y: getPerceptronOutput(
networkWeights,
[x],
hiddenActivation,
)[0],
});
}
return [
{
type: 'line',
label: 'Prédictions de régression',
data,
borderColor: '#FFF',
backgroundColor: '#FFF',
pointBackgroundColor: '#FFF',
pointRadius: 0,
borderWidth: 2,
tension: 0.4,
order: -1,
},
];
}
nonLinearGraph.value = false;
const data: Point[] = [
{
x: farLeftDataPointX.value - 1,
y: getPerceptronOutput(
networkWeights,
[farLeftDataPointX.value - 1],
hiddenActivation,
)[0],
},
{
x: farRightDataPointX.value + 1,
y: getPerceptronOutput(
networkWeights,
[farRightDataPointX.value + 1],
hiddenActivation,
)[0],
},
];
return [
{
type: 'line',
label: 'Régression du Perceptron',
data,
borderColor: '#FFF',
borderWidth: 2,
pointRadius: 0,
order: -1,
},
];
}
nonLinearGraph.value = true;
const predictionPoints = props.cleanedDataset.flatMap((dataset) =>
dataset.data.map((point) => ({
x: point.x,
y: point.y,
})),
);
return [
{
type: 'scatter',
label: 'Prédictions de régression',
data: predictionPoints,
backgroundColor: '#FFF',
pointBackgroundColor: '#FFF8',
pointRadius: 5,
borderWidth: 0,
order: -1,
},
];
}
if (
networkWeights.length == 1 &&
networkWeights[0].length == 1 &&
@@ -107,7 +224,7 @@ function getPerceptronDecisionBoundaryDataset(
function perceptronLine(x: number): number {
if (perceptronWeights.length < 3) {
// If we have less than 3 weights, we assume missing weights are zero
return getPerceptronOutput(networkWeights, [x])[0];
return getPerceptronOutput(networkWeights, [x], activationFunction)[0];
}
// w0 + w1*x + w2*y = 0 => y = -(w1/w2)*x - w0/w2
@@ -139,15 +256,14 @@ function getPerceptronDecisionBoundaryDataset(
nonLinearGraph.value = true;
const bubbleTransparency = '30';
const isInDataThreshold = 0.0;
// -------- 1️⃣ Construction des datasets --------
// -------- Construction des datasets --------
const datasets: {
type: string;
type: 'scatter';
label: string;
data: Point[];
backgroundColor: string;
pointBackgroundColor: string;
pointRadius: number;
borderWidth: number;
order: number;
@@ -155,11 +271,21 @@ function getPerceptronDecisionBoundaryDataset(
// 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 = {
const dataset: {
type: 'scatter';
label: string;
data: Point[];
backgroundColor: string;
pointBackgroundColor: string;
pointRadius: number;
borderWidth: number;
order: number;
} = {
type: 'scatter',
label: label,
data: [], // Will be filled with the decision boundary points
backgroundColor: colors[i] + bubbleTransparency || '#AAA',
backgroundColor: colors[i] || '#AAA',
pointBackgroundColor: (colors[i] || '#AAA') + bubbleTransparency,
pointRadius: 15,
borderWidth: 0,
order: -1,
@@ -167,7 +293,7 @@ function getPerceptronDecisionBoundaryDataset(
datasets.push(dataset);
}
// -------- 2️⃣ Échantillonnage grille --------
// -------- Échantillonnage grille --------
const step =
Math.abs(
farRightDataPointX.value + 1 - (farLeftDataPointX.value - 1),
@@ -183,16 +309,21 @@ function getPerceptronDecisionBoundaryDataset(
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 });
}
});
const values = getPerceptronOutput(
networkWeights,
[x, y],
activationFunction,
);
const dominantValue = Math.max(...values);
const dominantIndex = values.indexOf(dominantValue);
if (dominantIndex >= 0) {
datasets[dominantIndex].data.push({ x, y });
}
}
}
// -------- 3️⃣ Dataset ChartJS --------
// -------- Dataset ChartJS --------
return datasets;
}
}
@@ -259,7 +390,7 @@ function getPerceptronDecisionBoundaryDataset(
datasets: [
// Points from the dataset
...props.cleanedDataset.map((dataset, index) => ({
type: 'scatter',
type: 'scatter' as const,
label: `Label ${dataset.label}`,
data: dataset.data,
backgroundColor: colors[index] || '#AAA',