import json import numpy as np class Perceptron: """ Classe Perceptron pour le réseau de neurones. """ LAYER_INPUT = 784 # Couche d'entrée : 784 "neurones" (les 784 pixels de l'image). LAYER_HIDDEN = 128 # Couche cachée : Choisissez un nombre de neurones, par exemple 128 neurones. LAYER_OUTPUT = 10 # Couche de sortie : 10 neurones (un pour chaque chiffre de 0 à 9). hidden_activations = [] logit = [] learning_rate = 0.01 # Taux d'apprentissage pour l'entraînement def __init__(self): """ Initialisation du perceptron. """ self.initialize_weights() #region Initialisation des variables de base def load_data(self, file_path): """ Chargement et prétraitement des données """ with open(file_path, 'r') as f: data = json.load(f) images = [] labels = [] for item in data: # Normaliser les images normalized_image = [pixel / 255.0 for pixel in item['image']] images.append(normalized_image) labels.append(item['label']) return images, labels def initialize_weights(self): """ Initialiser les poids des couches """ self.weight_inputToHidden = np.random.randn(self.LAYER_INPUT, self.LAYER_HIDDEN) * 0.01 # Poids (entrée vers cachée) self.hidden_bias = [0] * self.LAYER_HIDDEN # Biais (cachée) self.weight_hiddenToOutput = np.random.randn(self.LAYER_HIDDEN, self.LAYER_OUTPUT) * 0.01 # Poids de la couche cachée à la couche de sortie self.output_bias = [0] * self.LAYER_OUTPUT # Biais de la couche de sortie #endregion #region Propagation avant (Comment le réseau fait une prédiction) def calculate_hidden_layer(self, image): """ Calcul de la couche cachée """ # Réinitialiser la liste des activations self.hidden_activations = [] # La boucle pour chaque neurone caché doit englober tout le calcul for h in range(0, self.LAYER_HIDDEN): weighted_sum = 0.0 # Ajoutez pixel_actuel * poids_entre_ce_pixel_et_ce_neurone_cache à somme_ponderee. for i in range(0, self.LAYER_INPUT): weighted_sum += image[i] * self.weight_inputToHidden[i][h] # Ajoutez le biais_de_ce_neurone_cache à somme_ponderee. weighted_sum += self.hidden_bias[h] # Appliquez la fonction ReLU activation = self.ReLU(weighted_sum) # Stockez ce résultat (l'activation) du neurone caché. self.hidden_activations.append(activation) def ReLU(self, number): """ Fonction ReLU Si somme_ponderee est négative, le résultat pour ce neurone caché est 0. Sinon, c'est somme_ponderee. """ return max(0, number) def calculate_output_layer(self): """ Calcul de la couche de sortie """ # Réinitialiser la liste des logits self.logit = [] # Initialisez une somme_ponderee à 0. for i in range(self.LAYER_OUTPUT): weighted_sum = 0.0 # Ajoutez activation_neurone_cache * poids_entre_ce_neurone_cache_et_ce_neurone_sortie à somme_ponderee. for j in range(self.LAYER_HIDDEN): weighted_sum += self.hidden_activations[j] * self.weight_hiddenToOutput[j][i] # Ajoutez le biais_de_ce_neurone_sortie à somme_ponderee. weighted_sum += self.output_bias[i] # Stockez ce résultat. C'est la "score" ou "logit" pour cette classe. self.logit.append(weighted_sum) #endregion def final_decision(self): """ Décision finale """ # Utiliser self.logit au lieu de self.score max_index = np.argmax(self.logit) return max_index def predict(self, image): """ Méthode pour faire une prédiction sur une seule image """ self.calculate_hidden_layer(image) self.calculate_output_layer() return self.final_decision() #region Entraînement : Apprendre de ses erreurs (Règle d'apprentissage simplifiée) def train(self, train_images, train_labels): """ C'est la partie où le réseau ajuste ses poids et biais pour s'améliorer. Au lieu de calculs complexes de dérivées, nous allons utiliser une règle simple. """ print("\nEntraînement sur 10 époques...") for epoque in range(0, 10): correct_predictions_count = 0 print(f"Époque {epoque + 1}/10") # Itérer correctement sur les images et labels for image, label in zip(train_images, train_labels): # Effectuez la propagation self.calculate_hidden_layer(image) self.calculate_output_layer() # Trouve la prédiction prediction = 0 correct_value = self.logit[0] for i in range(1, len(self.logit)): if self.logit[i] > correct_value: correct_value = self.logit[i] prediction = i print(f"Image: {image}, Prédiction: {prediction}, Label: {label}") if prediction == label: correct_predictions_count += 1 if prediction == label: # Si la prédiction est correcte # Félicitez légèrement les neurones # Pour le neurone de sortie correct : Augmentez un peu son biais self.output_bias[label] += self.learning_rate * 0.1 # Pour chaque neurone caché qui a contribué positivement à ce neurone de sortie for h in range(self.LAYER_HIDDEN): if self.hidden_activations[h] > 0: # augmentez un peu le poids de la connexion self.weight_hiddenToOutput[h][label] += self.learning_rate * 0.1 # légèrement augmenter leurs poids d'entrée for i in range(self.LAYER_INPUT): if image[i] > 0: self.weight_inputToHidden[i][h] += self.learning_rate * 0.05 else: #Si la prédiction est incorrecte # Pour le neurone de sortie qui aurait dû gagner (la vraie étiquette) # Augmentez son biais self.output_bias[label] += self.learning_rate # Augmentez les poids des connexions qui lui arrivent depuis la couche cachée. for h in range(self.LAYER_HIDDEN): if self.hidden_activations[h] > 0: self.weight_hiddenToOutput[h][label] += self.learning_rate # Pour le neurone de sortie qui a gagné par erreur # Diminuez son biais self.output_bias[prediction] -= self.learning_rate # Diminuez les poids des connexions qui lui arrivent depuis la couche cachée. for h in range(self.LAYER_HIDDEN): if self.hidden_activations[h] > 0: self.weight_hiddenToOutput[h][prediction] -= self.learning_rate # Pour la couche cachée for h in range(self.LAYER_HIDDEN): good = self.weight_hiddenToOutput[h][label] bad = self.weight_hiddenToOutput[h][prediction] if good > bad: # Si un neurone caché a fortement contribué à un bon neurone de sortie (celui qui aurait dû gagner) for i in range(self.LAYER_INPUT): if image[i] > 0: # Ajustez légèrement ses poids d'entrée pour qu'il s'active plus self.weight_inputToHidden[i][h] += self.learning_rate * 0.1 else: # Si un neurone caché a fortement contribué à un mauvais neurone de sortie (celui qui a gagné par erreur) for i in range(self.LAYER_INPUT): if image[i] > 0: # Ajustez légèrement ses poids d'entrée pour qu'il s'active moins. self.weight_inputToHidden[i][h] -= self.learning_rate * 0.1 # Afficher la précision de l'époque accuracy = (correct_predictions_count / len(train_images)) * 100 print(f"Précision de l'époque {epoque + 1}: {accuracy:.2f}%") #endregion #region Évaluation du modèle def evaluate_model(self, test_images, test_labels): """ Évalue le modèle sur les données de test. """ correct_predictions = 0 for image, label in zip(test_images, test_labels): # Effectuez une propagation avant pour obtenir la prédiction. self.calculate_hidden_layer(image) self.calculate_output_layer() # Décision finale prediction = self.final_decision() # Comparez la prédiction à la vraie étiquette. if prediction == label: correct_predictions += 1 # Comptez combien de prédictions sont correctes. # Calculez la précision (accuracy) : (Nombre de prédictions correctes / Nombre total d'images de test) * 100%. accuracy = (correct_predictions / len(test_images)) * 100 return accuracy, correct_predictions #endregion # je me suis aidée de l'intelligence artificielle pour optimiser le code car l'execution était beaucoup trop longue class PerceptronOptimized: """ Classe Perceptron pour le réseau de neurones - Version optimisée. """ LAYER_INPUT = 784 # Couche d'entrée : 784 "neurones" (les 784 pixels de l'image). LAYER_HIDDEN = 128 # Couche cachée : Choisissez un nombre de neurones, par exemple 128 neurones. LAYER_OUTPUT = 10 # Couche de sortie : 10 neurones (un pour chaque chiffre de 0 à 9). learning_rate = 0.01 # Taux d'apprentissage pour l'entraînement def __init__(self): """ Initialisation du perceptron. """ self.initialize_weights() #region Initialisation des variables de base def load_data(self, file_path): """ Chargement et prétraitement des données - Version optimisée """ with open(file_path, 'r') as f: data = json.load(f) # Utiliser numpy pour des opérations plus rapides images = np.array([item['image'] for item in data], dtype=np.float32) / 255.0 labels = np.array([item['label'] for item in data], dtype=np.int32) return images, labels def initialize_weights(self): """ Initialiser les poids des couches - utilisation de numpy """ # Initialisation Xavier/Glorot pour une meilleure convergence self.weight_inputToHidden = np.random.normal(0, np.sqrt(2.0/self.LAYER_INPUT), (self.LAYER_INPUT, self.LAYER_HIDDEN)).astype(np.float32) self.hidden_bias = np.zeros(self.LAYER_HIDDEN, dtype=np.float32) self.weight_hiddenToOutput = np.random.normal(0, np.sqrt(2.0/self.LAYER_HIDDEN), (self.LAYER_HIDDEN, self.LAYER_OUTPUT)).astype(np.float32) self.output_bias = np.zeros(self.LAYER_OUTPUT, dtype=np.float32) #endregion #region Propagation avant (Comment le réseau fait une prédiction) - Version vectorisée def forward_pass(self, images): """ Propagation avant vectorisée pour traiter plusieurs images en même temps """ # Si c'est une seule image, ajouter une dimension batch if len(images.shape) == 1: images = images.reshape(1, -1) # Couche cachée : multiplication matricielle + biais + ReLU hidden_input = np.dot(images, self.weight_inputToHidden) + self.hidden_bias hidden_activations = np.maximum(0, hidden_input) # ReLU vectorisé # Couche de sortie : multiplication matricielle + biais output_logits = np.dot(hidden_activations, self.weight_hiddenToOutput) + self.output_bias return hidden_activations, output_logits def predict(self, images): """ Prédiction vectorisée """ _, logits = self.forward_pass(images) return np.argmax(logits, axis=1) def predict_single(self, image): """ Prédiction pour une seule image """ return self.predict(image)[0] #endregion #region Entraînement optimisé par batch def train(self, train_images, train_labels, batch_size=128, epochs=10): """ Entraînement optimisé avec traitement par batch """ n_samples = len(train_images) n_batches = (n_samples + batch_size - 1) // batch_size print(f"\nEntraînement sur {epochs} époques avec batch_size={batch_size}...") for epoch in range(epochs): correct_predictions = 0 total_loss = 0 # Mélanger les données à chaque époque indices = np.random.permutation(n_samples) train_images_shuffled = train_images[indices] train_labels_shuffled = train_labels[indices] print(f"Époque {epoch + 1}/{epochs}") for batch_idx in range(n_batches): start_idx = batch_idx * batch_size end_idx = min(start_idx + batch_size, n_samples) batch_images = train_images_shuffled[start_idx:end_idx] batch_labels = train_labels_shuffled[start_idx:end_idx] # Propagation avant hidden_activations, logits = self.forward_pass(batch_images) # Prédictions predictions = np.argmax(logits, axis=1) correct_predictions += np.sum(predictions == batch_labels) # Mise à jour des poids self._update_weights_batch(batch_images, batch_labels, hidden_activations, logits, predictions) # Affichage du progrès if batch_idx % 50 == 0: progress = (batch_idx / n_batches) * 100 print(f" Batch {batch_idx}/{n_batches} ({progress:.1f}%)") # Précision de l'époque epoch_accuracy = (correct_predictions / n_samples) * 100 print(f" Précision: {epoch_accuracy:.2f}%\n") def _update_weights_batch(self, batch_images, batch_labels, hidden_activations, logits, predictions): """ Mise à jour des poids pour un batch (règle d'apprentissage simplifiée) """ batch_size = len(batch_images) # Masques pour les prédictions correctes et incorrectes correct_mask = (predictions == batch_labels) incorrect_mask = ~correct_mask # Pour les prédictions correctes (renforcement léger) if np.any(correct_mask): correct_labels = batch_labels[correct_mask] correct_hidden = hidden_activations[correct_mask] correct_images = batch_images[correct_mask] # Mise à jour des biais de sortie for label in correct_labels: self.output_bias[label] += self.learning_rate * 0.1 # Mise à jour des poids cachée->sortie for i, label in enumerate(correct_labels): if np.any(correct_hidden[i] > 0): active_hidden = correct_hidden[i] > 0 self.weight_hiddenToOutput[active_hidden, label] += self.learning_rate * 0.1 # Pour les prédictions incorrectes (correction plus forte) if np.any(incorrect_mask): incorrect_labels = batch_labels[incorrect_mask] incorrect_predictions = predictions[incorrect_mask] incorrect_hidden = hidden_activations[incorrect_mask] incorrect_images = batch_images[incorrect_mask] for i in range(len(incorrect_labels)): true_label = incorrect_labels[i] pred_label = incorrect_predictions[i] hidden_vec = incorrect_hidden[i] image_vec = incorrect_images[i] # Renforcer le bon neurone de sortie self.output_bias[true_label] += self.learning_rate active_hidden = hidden_vec > 0 self.weight_hiddenToOutput[active_hidden, true_label] += self.learning_rate # Affaiblir le mauvais neurone de sortie self.output_bias[pred_label] -= self.learning_rate self.weight_hiddenToOutput[active_hidden, pred_label] -= self.learning_rate # Ajuster les poids d'entrée (plus sélectif pour éviter le sur-apprentissage) good_contrib = self.weight_hiddenToOutput[:, true_label] bad_contrib = self.weight_hiddenToOutput[:, pred_label] for h in range(self.LAYER_HIDDEN): if hidden_vec[h] > 0: if good_contrib[h] > bad_contrib[h]: # Renforcer les connexions utiles active_pixels = image_vec > 0.1 # Seuil pour éviter le bruit self.weight_inputToHidden[active_pixels, h] += self.learning_rate * 0.05 else: # Affaiblir les connexions nuisibles active_pixels = image_vec > 0.1 self.weight_inputToHidden[active_pixels, h] -= self.learning_rate * 0.05 #endregion #region Évaluation optimisée def evaluate_model(self, test_images, test_labels, batch_size=1000): """ Évaluation optimisée par batch """ n_samples = len(test_images) n_batches = (n_samples + batch_size - 1) // batch_size correct_predictions = 0 print("Évaluation du modèle...") for batch_idx in range(n_batches): start_idx = batch_idx * batch_size end_idx = min(start_idx + batch_size, n_samples) batch_images = test_images[start_idx:end_idx] batch_labels = test_labels[start_idx:end_idx] predictions = self.predict(batch_images) correct_predictions += np.sum(predictions == batch_labels) accuracy = (correct_predictions / n_samples) * 100 return accuracy, correct_predictions #endregion def executeNonOptimizedVersion(): """ Main function to execute the data loading and normalization. """ perceptron = Perceptron() # Chargement des données de test print("Loading test data...") file_path = 'MNIST/mnist_handwritten_test.json' images_test, labels_test = perceptron.load_data(file_path) if(len(images_test) == 0 or len(labels_test) == 0): raise ValueError("Test data is empty. Please check the file path or content.") print(f"Test data loaded successfully. {len(images_test)} images loaded.") # Chargement des données d'entraînement print("Loading train data...") file_path = 'MNIST/mnist_handwritten_train.json' images_train, labels_train = perceptron.load_data(file_path) if(len(images_train) == 0 or len(labels_train) == 0): raise ValueError("Train data is empty. Please check the file path or content.") print(f"Train data loaded successfully. {len(images_train)} images loaded.") # Entraînement print("Training the perceptron...") perceptron.train(images_train, labels_train) print("Training completed.") # Évaluation finale print("\n=== RÉSULTATS FINAUX ===") final_accuracy, correct = perceptron.evaluate_model(images_test, labels_test) total = len(images_test) print(f"Précision finale: {final_accuracy:.2f}%") print(f"Prédictions correctes: {correct}/{total}") # Test sur quelques exemples print("\n=== EXEMPLES DE PRÉDICTIONS ===") for i in range(min(10, len(images_test))): prediction = perceptron.predict(images_test[i]) correct_label = labels_test[i] if (prediction == correct_label): status = "Prédiction correcte !" else: status = "Prédiction incorrecte !" print(f"Image {i+1}: Prediction : {prediction}, Correct number : {correct_label} {status}") def executeOptimizedVersion(): """ Main function optimisée """ perceptron = Perceptron() # Chargement des données de test print("Chargement des données de test...") file_path = 'MNIST/mnist_handwritten_test.json' images_test, labels_test = perceptron.load_data(file_path) if len(images_test) == 0 or len(labels_test) == 0: raise ValueError("Les données de test sont vides.") print(f"Données de test chargées: {len(images_test)} images.") # Chargement des données d'entraînement print("Chargement des données d'entraînement...") file_path = 'MNIST/mnist_handwritten_train.json' images_train, labels_train = perceptron.load_data(file_path) if len(images_train) == 0 or len(labels_train) == 0: raise ValueError("Les données d'entraînement sont vides.") print(f"Données d'entraînement chargées: {len(images_train)} images.") # Entraînement optimisé print("Démarrage de l'entraînement...") perceptron.train(images_train, labels_train, batch_size=256, epochs=5) # Moins d'époques pour tester print("Entraînement terminé.") # Évaluation finale print("\n=== RÉSULTATS FINAUX ===") final_accuracy, correct = perceptron.evaluate_model(images_test, labels_test) total = len(images_test) print(f"Précision finale: {final_accuracy:.2f}%") print(f"Prédictions correctes: {correct}/{total}") # Test sur quelques exemples print("\n=== EXEMPLES DE PRÉDICTIONS ===") for i in range(min(10, len(images_test))): prediction = perceptron.predict_single(images_test[i]) correct_label = labels_test[i] status = "✓ Correct" if prediction == correct_label else "✗ Incorrect" print(f"Image {i+1}: Prédiction={prediction}, Attendu={correct_label} {status}")