MonoLayer Perceptron
This commit is contained in:
@@ -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]]],
|
||||
|
||||
Reference in New Issue
Block a user