406 lines
14 KiB
Vue
406 lines
14 KiB
Vue
<script setup lang="ts">
|
|
import { router, useForm } from '@inertiajs/vue3';
|
|
import { ref, watch } from 'vue';
|
|
import {
|
|
Form,
|
|
FormControl,
|
|
FormField,
|
|
FormItem,
|
|
FormLabel,
|
|
} from '@/components/ui/form';
|
|
import {
|
|
NativeSelect,
|
|
NativeSelectOption,
|
|
} from '@/components/ui/native-select';
|
|
import { cancel } from '@/routes/perceptron';
|
|
import type {
|
|
Dataset,
|
|
InitializationMethod,
|
|
PerceptronType,
|
|
ValidationErrors,
|
|
} from '@/types/perceptron';
|
|
import Button from './ui/button/Button.vue';
|
|
import Card from './ui/card/Card.vue';
|
|
import CardContent from './ui/card/CardContent.vue';
|
|
import CardHeader from './ui/card/CardHeader.vue';
|
|
import CardTitle from './ui/card/CardTitle.vue';
|
|
import FormError from './ui/form/FormError.vue';
|
|
import Input from './ui/input/Input.vue';
|
|
import Spinner from './ui/spinner/Spinner.vue';
|
|
|
|
const props = defineProps<{
|
|
type: PerceptronType;
|
|
datasets: Dataset[];
|
|
selectedDataset: string;
|
|
initializationMethod: InitializationMethod;
|
|
hiddenLayers: number;
|
|
hiddenLayersNeurons: number;
|
|
minError: number;
|
|
defaultLearningRate: number;
|
|
sessionId: string;
|
|
defaultMaxIterations: number;
|
|
maxIterationsLimit: number;
|
|
errors: ValidationErrors;
|
|
}>();
|
|
|
|
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);
|
|
const maxIterationsInput = ref<{
|
|
inputElement: HTMLInputElement | null;
|
|
} | null>(null);
|
|
const form = useForm({
|
|
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,
|
|
training_id: '',
|
|
max_iterations: maxIterations.value,
|
|
});
|
|
|
|
function handleMaxIterationsInput(event: Event) {
|
|
const input = event.target as HTMLInputElement;
|
|
const numericValue = Number(input.value);
|
|
const normalizedValue = Number.isFinite(numericValue)
|
|
? Math.min(Math.max(numericValue, 1), props.maxIterationsLimit)
|
|
: 1;
|
|
|
|
console.debug('[max_iterations] native input', {
|
|
raw: input.value,
|
|
numericValue,
|
|
normalizedValue,
|
|
limit: props.maxIterationsLimit,
|
|
});
|
|
|
|
input.value = String(normalizedValue);
|
|
maxIterations.value = normalizedValue;
|
|
}
|
|
|
|
watch(maxIterations, (value) => {
|
|
const input = maxIterationsInput.value?.inputElement;
|
|
|
|
console.debug('[max_iterations] ref changed', {
|
|
value,
|
|
displayed: input?.value,
|
|
});
|
|
|
|
if (input && input.value !== String(value)) {
|
|
input.value = String(value);
|
|
}
|
|
});
|
|
|
|
watch(selectedDatasetCopy, (newvalue) => {
|
|
form.clearErrors('dataset');
|
|
|
|
const selectedDatasetCopy =
|
|
props.datasets.find((dataset) => dataset.label === newvalue) || null;
|
|
|
|
// LearningRate
|
|
learningRate.value = props.defaultLearningRate;
|
|
if (
|
|
selectedDatasetCopy &&
|
|
selectedDatasetCopy.defaultLearningRate !== undefined
|
|
) {
|
|
learningRate.value = selectedDatasetCopy.defaultLearningRate;
|
|
}
|
|
// MinError
|
|
minError.value = props.minError;
|
|
if (
|
|
selectedDatasetCopy &&
|
|
selectedDatasetCopy.defaultMinError !== undefined
|
|
) {
|
|
minError.value = selectedDatasetCopy.defaultMinError;
|
|
}
|
|
// MaxIterations
|
|
maxIterations.value = props.defaultMaxIterations;
|
|
if (
|
|
selectedDatasetCopy &&
|
|
selectedDatasetCopy.defaultMaxIterations !== undefined
|
|
) {
|
|
maxIterations.value = selectedDatasetCopy.defaultMaxIterations;
|
|
}
|
|
// HiddenLayers
|
|
hiddenLayers.value = props.hiddenLayers;
|
|
if (
|
|
selectedDatasetCopy &&
|
|
selectedDatasetCopy.defaultHiddenLayers !== undefined
|
|
) {
|
|
hiddenLayers.value = selectedDatasetCopy.defaultHiddenLayers;
|
|
}
|
|
hiddenLayersNeurons.value = props.hiddenLayersNeurons;
|
|
if (
|
|
selectedDatasetCopy &&
|
|
selectedDatasetCopy.defaultHiddenLayersNeurons !== undefined
|
|
) {
|
|
hiddenLayersNeurons.value =
|
|
selectedDatasetCopy.defaultHiddenLayersNeurons;
|
|
}
|
|
});
|
|
|
|
const trainingId = ref<string>('');
|
|
|
|
function startTraining() {
|
|
if (!selectedDatasetCopy.value) {
|
|
form.setError(
|
|
'dataset',
|
|
"Un dataset est nécessaire avant de lancer l'entraînement.",
|
|
);
|
|
console.log(form.errors);
|
|
return;
|
|
}
|
|
|
|
trainingId.value = `${props.sessionId}-${Date.now()}`;
|
|
emit('update:trainingId', trainingId.value);
|
|
|
|
Object.assign(form, {
|
|
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,
|
|
training_id: trainingId.value,
|
|
max_iterations: maxIterations.value,
|
|
});
|
|
|
|
console.debug('[max_iterations] submitting', {
|
|
displayed: maxIterationsInput.value?.inputElement?.value,
|
|
refValue: maxIterations.value,
|
|
formValue: form.max_iterations,
|
|
limit: props.maxIterationsLimit,
|
|
});
|
|
|
|
form.post('/perceptron/run', {
|
|
preserveScroll: true,
|
|
});
|
|
}
|
|
|
|
const emit = defineEmits(['update:selectedDataset', 'update:trainingId']);
|
|
|
|
async function cancelTraining() {
|
|
form.cancel();
|
|
|
|
router.post(
|
|
cancel(),
|
|
{
|
|
training_id: trainingId.value,
|
|
},
|
|
{
|
|
preserveScroll: true,
|
|
},
|
|
);
|
|
}
|
|
|
|
watch(selectedDatasetCopy, (newValue) => {
|
|
emit('update:selectedDataset', newValue);
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Configuration du Perceptron</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Form
|
|
class="grid auto-cols-max grid-flow-row grid-cols-1 gap-4 space-y-6 md:grid-cols-2"
|
|
cancel-on-unmount
|
|
>
|
|
<!-- DATASET -->
|
|
<FormField name="dataset">
|
|
<FormItem>
|
|
<FormLabel>Dataset</FormLabel>
|
|
<FormControl>
|
|
<NativeSelect
|
|
name="dataset"
|
|
id="dataset-select"
|
|
v-model="selectedDatasetCopy"
|
|
class="cursor-pointer"
|
|
>
|
|
<NativeSelectOption value="" disabled
|
|
>Sélectionnez un dataset</NativeSelectOption
|
|
>
|
|
<NativeSelectOption
|
|
v-for="dataset in props.datasets"
|
|
v-bind:key="dataset.label"
|
|
:value="dataset.label"
|
|
>
|
|
{{ dataset.label.replace(/_/g, ' ') }}
|
|
</NativeSelectOption>
|
|
</NativeSelect>
|
|
</FormControl>
|
|
<FormError
|
|
:error="
|
|
form.errors.dataset ||
|
|
props.errors?.selectedDatasetCopy
|
|
"
|
|
/>
|
|
</FormItem>
|
|
</FormField>
|
|
|
|
<!-- DEFAULT WEIGHTS -->
|
|
<FormField name="weight_init_method">
|
|
<FormItem>
|
|
<FormLabel
|
|
>Méthode d'initialisation des poids</FormLabel
|
|
>
|
|
<FormControl>
|
|
<NativeSelect
|
|
name="weight_init_method"
|
|
id="weight_init_method"
|
|
v-model="selectedMethod"
|
|
class="cursor-pointer"
|
|
>
|
|
<NativeSelectOption
|
|
v-for="method in props.type == 'multilayer'
|
|
? ['random']
|
|
: ['zeros', 'random']"
|
|
v-bind:key="method"
|
|
:value="method"
|
|
>
|
|
{{ method }}
|
|
</NativeSelectOption>
|
|
</NativeSelect>
|
|
</FormControl>
|
|
</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
|
|
ref="maxIterationsInput"
|
|
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>
|
|
<FormLabel>Erreur minimale</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="number"
|
|
v-model="minError"
|
|
min="0"
|
|
step="0.001"
|
|
class="w-min"
|
|
/>
|
|
</FormControl>
|
|
</FormItem>
|
|
</FormField>
|
|
|
|
<!-- LEARNING RATE -->
|
|
<FormField name="learning_rate">
|
|
<FormItem>
|
|
<FormLabel>Taux d'apprentissage</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="number"
|
|
v-model="learningRate"
|
|
min="0"
|
|
step="0.001"
|
|
class="w-min"
|
|
/>
|
|
</FormControl>
|
|
</FormItem>
|
|
</FormField>
|
|
|
|
<!-- MAX ITERATIONS -->
|
|
<FormField name="max_iterations">
|
|
<FormItem>
|
|
<FormLabel>Nombre maximum d'époques</FormLabel>
|
|
<FormControl>
|
|
<Input
|
|
type="number"
|
|
:model-value="maxIterations"
|
|
min="1"
|
|
:max="props.maxIterationsLimit"
|
|
step="1"
|
|
class="w-min"
|
|
@input="handleMaxIterationsInput"
|
|
/>
|
|
</FormControl>
|
|
<div
|
|
v-if="
|
|
form.errors.max_iterations ||
|
|
props.errors.max_iterations
|
|
"
|
|
>
|
|
{{
|
|
form.errors.max_iterations ||
|
|
props.errors.max_iterations
|
|
}}
|
|
</div>
|
|
</FormItem>
|
|
</FormField>
|
|
</Form>
|
|
|
|
<Transition name="fade">
|
|
<Button
|
|
variant="outline"
|
|
class="mt-6 cursor-pointer"
|
|
:disabled="form.processing"
|
|
@click="startTraining"
|
|
>Lancer<Spinner v-if="form.processing" class="ml-1"
|
|
/></Button>
|
|
</Transition>
|
|
<Transition name="fade">
|
|
<Button
|
|
variant="outline"
|
|
class="mt-6 ml-4 cursor-pointer"
|
|
@click="cancelTraining"
|
|
v-if="form.processing"
|
|
>Annuler</Button
|
|
>
|
|
</Transition>
|
|
</CardContent>
|
|
</Card>
|
|
</template>
|