Multilayer neuron network
This commit is contained in:
@@ -6,6 +6,7 @@ const props = defineProps<{
|
||||
iterations: Iteration[];
|
||||
trainingEnded: boolean;
|
||||
trainingEndReason: string;
|
||||
maxDisplayedWeights: number;
|
||||
}>();
|
||||
|
||||
// All weight in a simple array
|
||||
@@ -16,6 +17,12 @@ const allWeightPerIteration: ComputedRef<number[][]> = computed(() => {
|
||||
});
|
||||
});
|
||||
|
||||
const displayedWeights = computed(() => {
|
||||
const weights = allWeightPerIteration.value.find((weights) => weights.length > 0) || [];
|
||||
|
||||
return weights.length <= props.maxDisplayedWeights ? weights : [];
|
||||
});
|
||||
|
||||
const rowBgDark = computed(() => {
|
||||
let isEven = false;
|
||||
return props.iterations.map((iteration, index, arr) => {
|
||||
@@ -33,7 +40,7 @@ const rowBgDark = computed(() => {
|
||||
<th>Époch</th>
|
||||
<th>Exemple</th>
|
||||
<th
|
||||
v-for="(weight, index) in allWeightPerIteration[0]"
|
||||
v-for="(weight, index) in displayedWeights"
|
||||
v-bind:key="index"
|
||||
>
|
||||
X<sub>{{ index }}</sub>
|
||||
@@ -49,12 +56,14 @@ const rowBgDark = computed(() => {
|
||||
>
|
||||
<td>{{ iteration.epoch }}</td>
|
||||
<td>{{ iteration.exampleIndex }}</td>
|
||||
<td
|
||||
v-for="(weight, index) in allWeightPerIteration[index]"
|
||||
v-bind:key="index"
|
||||
>
|
||||
{{ weight.toFixed(2) }}
|
||||
</td>
|
||||
<template v-if="displayedWeights.length > 0">
|
||||
<td
|
||||
v-for="(weight, weightIndex) in allWeightPerIteration[index]"
|
||||
v-bind:key="weightIndex"
|
||||
>
|
||||
{{ weight.toFixed(2) }}
|
||||
</td>
|
||||
</template>
|
||||
<td>{{ iteration.error.toFixed(2) }}</td>
|
||||
</tr>
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -8,6 +8,7 @@ import Toggle from './ui/toggle/Toggle.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
iterations: Iteration[];
|
||||
isRegression: boolean;
|
||||
}>();
|
||||
|
||||
const epochErrorOnly = ref<boolean>(false);
|
||||
@@ -42,7 +43,11 @@ const datasets = computed<
|
||||
};
|
||||
datasets.push(dataset);
|
||||
}
|
||||
dataset.data.push(iteration.error);
|
||||
dataset.data.push(
|
||||
props.isRegression
|
||||
? Math.abs(iteration.error)
|
||||
: iteration.error,
|
||||
);
|
||||
}
|
||||
|
||||
exampleCountPerEpoch[iteration.epoch] = (exampleCountPerEpoch[iteration.epoch] || 0) + 1;
|
||||
@@ -91,7 +96,9 @@ const datasets = computed<
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Nombre d\'erreurs par epoch',
|
||||
text: props.isRegression
|
||||
? 'Erreur de prédiction par epoch'
|
||||
: 'Nombre d\'erreurs par epoch',
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
@@ -104,7 +111,7 @@ const datasets = computed<
|
||||
},
|
||||
y: {
|
||||
stacked: true,
|
||||
beginAtZero: true,
|
||||
beginAtZero: !props.isRegression,
|
||||
grid: {
|
||||
color: function (context) {
|
||||
if (context.tick.value == 0) {
|
||||
|
||||
@@ -30,6 +30,8 @@ const props = defineProps<{
|
||||
datasets: Dataset[];
|
||||
selectedDataset: string;
|
||||
initializationMethod: InitializationMethod;
|
||||
hiddenLayers: number;
|
||||
hiddenLayersNeurons: number;
|
||||
minError: number;
|
||||
defaultLearningRate: number;
|
||||
sessionId: string;
|
||||
@@ -38,6 +40,8 @@ const props = defineProps<{
|
||||
|
||||
const selectedDatasetCopy = ref(props.selectedDataset);
|
||||
const selectedMethod = ref(props.initializationMethod);
|
||||
const hiddenLayers = ref(props.hiddenLayers);
|
||||
const hiddenLayersNeurons = ref(props.hiddenLayersNeurons);
|
||||
const minError = ref(props.minError);
|
||||
const learningRate = ref(props.defaultLearningRate);
|
||||
const maxIterations = ref(props.defaultMaxIterations);
|
||||
@@ -59,6 +63,9 @@ watch(selectedDatasetCopy, (newvalue) => {
|
||||
}
|
||||
// MaxIterations
|
||||
maxIterations.value = props.defaultMaxIterations;
|
||||
if (selectedDatasetCopy && selectedDatasetCopy.defaultMaxIterations !== undefined) {
|
||||
maxIterations.value = selectedDatasetCopy.defaultMaxIterations;
|
||||
}
|
||||
})
|
||||
|
||||
const trainingId = ref<string>('');
|
||||
@@ -82,6 +89,8 @@ function startTraining() {
|
||||
type: props.type,
|
||||
dataset: selectedDatasetCopy.value,
|
||||
weight_init_method: selectedMethod.value,
|
||||
hidden_layers: hiddenLayers.value,
|
||||
hidden_layers_neurons: hiddenLayersNeurons.value,
|
||||
min_error: minError.value,
|
||||
learning_rate: learningRate.value,
|
||||
session_id: props.sessionId,
|
||||
@@ -158,7 +167,7 @@ watch(selectedDatasetCopy, (newValue) => {
|
||||
class="cursor-pointer"
|
||||
>
|
||||
<NativeSelectOption
|
||||
v-for="method in ['zeros', 'random']"
|
||||
v-for="method in (props.type == 'multilayer' ? ['random'] : ['zeros', 'random'])"
|
||||
v-bind:key="method"
|
||||
:value="method"
|
||||
>
|
||||
@@ -169,6 +178,42 @@ watch(selectedDatasetCopy, (newValue) => {
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- HIDDEN LAYERS -->
|
||||
<FormField name="hidden_layers" v-if="props.type === 'multilayer'">
|
||||
<FormItem>
|
||||
<FormLabel>Nombre de couches cachées</FormLabel>
|
||||
<FormControl>
|
||||
<!-- TODO : MAX input -->
|
||||
<Input
|
||||
type="number"
|
||||
v-model="hiddenLayers"
|
||||
min="1"
|
||||
max="5"
|
||||
step="1"
|
||||
class="w-min"
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- HIDDEN LAYERS NEURONS -->
|
||||
<FormField name="hidden_layers_neurons" v-if="props.type === 'multilayer'">
|
||||
<FormItem>
|
||||
<FormLabel>Nombre de neurones par couche cachée</FormLabel>
|
||||
<FormControl>
|
||||
<!-- TODO : MAX input -->
|
||||
<Input
|
||||
type="number"
|
||||
v-model="hiddenLayersNeurons"
|
||||
min="1"
|
||||
max="5"
|
||||
step="1"
|
||||
class="w-min"
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<!-- MIN ERROR -->
|
||||
<FormField name="min_error" v-if="props.type !== 'simple'">
|
||||
<FormItem>
|
||||
@@ -210,6 +255,7 @@ watch(selectedDatasetCopy, (newValue) => {
|
||||
type="number"
|
||||
v-model="maxIterations"
|
||||
min="0"
|
||||
max="5000"
|
||||
step="1"
|
||||
class="w-min"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user