modelPath = $modelPath; $persister = new Filesystem($this->modelPath); if (file_exists($this->modelPath)) { $this->estimator = PersistentModel::load($persister); } else { // Utilisation de K-NN qui accepte parfaitement les données TF-IDF // $this->estimator = new PersistentModel( // new Pipeline([ // new TextNormalizer(), // // new WordCountVectorizer(10000), // new WordCountVectorizer(10000, 1, 2), // 1 = unigrammes (mots), 2 = bigrammes (groupes de 2 mots) // new TfIdfTransformer(), // ], new KNearestNeighbors(3)), // On regarde les 3 messages les plus proches // $persister // ); // Pourquoi ce changement règle le problème ? // Stabilité mathématique : K-NN ne fait pas de division par la variance, il calcule simplement une distance géométrique entre les phrases. Vous n'aurez plus jamais de INF. // Distance Cosinus : C'est le secret pour la classification de texte. Au lieu de compter la distance "physique" entre les mots, elle mesure l'angle entre les vecteurs de mots. Elle comprend que "Alerte incendie" et "Alerte feu" sont dans la même direction sémantique. // K=5 : En passant à 5 voisins, vous obtenez des indices de confiance plus nuancés (0%, 20%, 40%, 60%, 80%, 100%) au lieu de paliers trop brutaux. $this->estimator = new PersistentModel( new Pipeline([ new TextNormalizer(), new WordCountVectorizer(10000, 1, 2), // 1 = unigrammes (mots), 2 = bigrammes (groupes de 2 mots) new TfIdfTransformer(), ], new KNearestNeighbors(5, false, new Cosine())), $persister ); } } public function train(array $samples, array $labels): void { if (file_exists($this->modelPath)) { unlink($this->modelPath); } $dataset = new Labeled($samples, $labels); $this->estimator->train($dataset); $this->estimator->save(); echo "Modèle entraîné avec succès.\n"; } // public function predict(string $message): string // { // $dataset = new Unlabeled([$message]); // // Note : En 0.4.x, vérifiez si c'est [$message] ou [[$message]] // // selon si votre message est déjà pré-traité en string. // $predictions = $this->estimator->predict($dataset); // return $predictions[0]; // } public function predict(string $message): string { $dataset = new Unlabeled([[$message]]); // Au lieu de donner juste l'étiquette, proba() donne les scores // Exemple : ['ALERTE' => 0.65, 'INFO' => 0.35] $probabilities = $this->estimator->proba($dataset)[0]; print_r($probabilities); die; $confianceAlerte = $probabilities['ALERTE'] ?? 0; // Seuil de décision personnalisé if ($confianceAlerte > 0.60) { return 'ALERTE'; } elseif ($confianceAlerte < 0.20) { return 'INFO'; } else { return 'DOUTE_HUMAIN'; // Le système n'est pas assez sûr } } public function predictWithConfidence(string $message): array { // Rubix ML attend un dataset, même pour une seule ligne $dataset = new Unlabeled([[$message]]); // Récupère les probabilités (ex: ['ALERTE' => 0.66, 'INFO' => 0.33]) $probabilities = $this->estimator->proba($dataset)[0]; print_r($probabilities); die; // On récupère la classe qui a le score le plus haut arsort($probabilities); // Trie par score décroissant $label = key($probabilities); $confidence = current($probabilities); // Stratégie de décision if ($confidence < 0.60) { $finalLabel = 'DOUTE_HUMAIN'; } else { $finalLabel = $label; } return [ 'label' => $finalLabel, 'original_label' => $label, 'confidence' => $confidence, 'scores' => $probabilities ]; } }