Université Sidi Mohamed Ben Abdellah Faculté Des Sciences et Techniques Fès Compte Rendu du TP N°2 Réseau de neurones Réalisé par : Encadré par : MAADOUR Soufiane ZYATE Mahmoud KAMRANI Abdelhalim HAMOUT Hamza BOUTAYEB Aymane Date début du projet : 18/02/2015 Pr. MAJDA Aicha Date Fin du projet : 05/05/2015 Lien du Blog : www.gsirx.wordpress.com Objectifs Détailler le fonctionnement de l’application du TP N°2 des réseaux de neurones – Perceptron multicouche. 2014 – 2015 Sommaire SOMMAIRE ......................................................................................................................................................... 2 INTRODUCTION .................................................................................................................................................. 3 PARTIE 1 ............................................................................................................................................................. 4 Application du calcul effectué dans un PCM ...................................................................................................... 4 QUESTION 1 : ............................................................................................................................................................ 4 QUESTION 2 : ............................................................................................................................................................ 6 PARTIE 2 ............................................................................................................................................................. 7 Implémentation de perceptron multicouche ..................................................................................................... 7 VUE GENERALE DE LA CONCEPTION ................................................................................................................................. 7 DESCRIPTION DES CLASSES UTILISEES ............................................................................................................................... 8 L’interface graphique......................................................................................................................................... 8 Main.java ......................................................................................................................................................................... 8 GraphPanel.java ............................................................................................................................................................ 10 NetworkGraph.java ....................................................................................................................................................... 12 Structure de donnée ........................................................................................................................................ 12 Character.java................................................................................................................................................................ 12 Data.txt .......................................................................................................................................................................... 12 DataHandler.java ........................................................................................................................................................... 12 Implémentation de l’algorithme de rétropropagation .................................................................................... 12 BackPropagation.java .................................................................................................................................................... 12 Apprentissage ........................................................................................................................................................... 13 Test de classification ................................................................................................................................................. 15 2 GROUPE SIRX MSIR 2014 Introduction Un réseau de neurones artificiels est un modèle de calcul dont la conception est très schématiquement inspirée du fonctionnement des neurones biologiques, ils sont généralement utilisés dans des méthodes d'apprentissages afin de prendre des décisions. Notre objectif et d'arriver à concevoir un programme capable d’identifier des objets, plus exactement des lettres latins. On a utilisé dans un premier temps les réseaux bayésiens qui sont déjà expliqué et détaillé dans le premier projet, alors que dans ce projet nous allons opter pour les réseaux de neurones, plus exactement le perceptron multi couche et son algorithme de rétropropagation. Ce document est divisé en deux partie, dans la première on montre le calcule effectuer dans une seule itération de l’algorithme de rétropropagation. Ensuite nous allons implémenter l’algorithme et l’utiliser dans une application pour classifier les lettres. 3 GROUPE SIRX MSIR 2014 Partie 1 Application du calcul effectué dans un PCM On considère le PCM suivant Avec : xi : Les entrées du réseau. hj : Les sorties de la couche cachée. y : La sortie du réseau. Question 1 : Fonction XOR-logique En utilisant l’algorithme de la rétro-propagation pour l’apprentissage de la fonction logique XOR, on calcule les nouveaux poids synaptiques wij pour une seule itération. On prend comme exemple d’apprentissage le vecteur (x1=0, x2=1). Les données de notre réseau sont : La fonction de transfert sigmoïde: f(x)=1/ (1+e(-x)) Le taux d’apprentissage α = 0.7 4 GROUPE SIRX MSIR 2014 Les biais b1 = 0.4 b2 =0.6 b3 = -0.3 Les poids W11 = 0.7 W21 = -0.4 W12 = -0.2 W22 = 0.3 W1 = 0.5 W2= 0.1 L’apprentissage de notre fonction comporte 3 phases : La propagation en avant -Cette étape consiste à calculer les valeurs des neurones de la couche cachée et ceux de la couche de sortie par la fonction sigmoïde calculée pour la somme pondéré de chaque neurone. Somme pondérée du neurone i : Si = Σ Wj Xj + bi avec Wj les poids synaptiques entrants au neurone Fonction sigmoïde : f(x)=1/ (1+e(-x)) pour tout réel x Neurones H1 (couche cachée) Valeurs des neurones 0.55 H2 (couche cachée) Y (couche de sortie) 0.71 0.51 Y (couche de sortie) Le calcul de l’erreur La formule utilisée pour calculer l’erreur est : Neurones H1 (couche cachée) H2 (couche cachée) Erreur des neurones 0.12 0.015 0.025 La rétro-propagation de l’erreur On doit maintenant ajuster les poids en rétro-propageant l’erreur observée par le biais de la relation suivante : 5 GROUPE SIRX MSIR 2014 Poids Nouvelle valeur B3 -0.26 W1 B1 0.51 0.43 W11 0.7 W12 W22 B2 W21 W2 -0.19 0.30 0.60 -0.4 0.12 Donc notre réseau devient comme suit : Question 2 : La fonction OU-logique Pour l’apprentissage de la fonction logique OU, c’est le même traitement effectué pour le XOR, car la sortie désirée est la même (d=1) par le vecteur d’apprentissage (0,1). 6 GROUPE SIRX MSIR 2014 Partie 2 Implémentation de perceptron multicouche On se propose d’implémenter dans cette partie un perceptron multicouche afin de faire avec la classification de caractères alphabétiques majuscules. L’implémentation est faite entièrement en langage JAVA. Notre perceptron comprends, en plus de sa couche d’entré et de sortie, une seule couche caché composée de 200 neurones (hormis le biais de cette couche). La couche d’entrée est fixée à 2500 neurones, évidemment puisqu’on travaille avec des images de 50*50 pixels. La couche de sortie contient initialement 26 neurones pour chaque lettre d’alphabet. Vue générale de la conception Nos caractères sont à la base des images binaires composées de 0 et de 1. On se propose de les saisir dans nos programmes puis de transformer la matrice d’image en vecteur. La sauvegarde de l’information se fait suivant la structure de la classe Character.java qu’on détaillera par la suite. L’interface propose en premier lieu de faire l’apprentissage, que ce soit de sa base de connaissance déjà stockées dans le fichier data.txt ou avec de nouveaux exemples que l’utilisateur ajoute (et qui seront éventuellement stockés dans notre fichier). Une fois l’apprentissage est terminé, une courbe de variation de l’erreur quadratique durant les itérations de l’apprentissage. L’interface propose également de faire le test ou plus exactement de classifier les images de caractères alphabétiques que l’utilisateur importe tout en traçant un réseau simplifié montrant les valeurs de sortie sur lesquelles le réseau de neurone a appuyé sa décision. 7 GROUPE SIRX MSIR 2014 Description des classes utilisées L’application de classification de caractères alphabétiques se base sur sept classes. Quatre classes à connotation graphique, deux classes utilitaires et une classe modèle. L’interface graphique Le coté graphique est géré par 4 classes, une classe principale Main.java faisant appel en cas de besoin à trois autres classes qui sont des implémentations de panels : GraphPanel.java, NetworkGraph.java et Loading.java. Main.java Il s’agit du corps général et la classe pilote de l’interface. En plus de la présentation de la vue, la gestion des événements y est incluse ainsi que le chargement de données depuis le fichier data.txt. FIGURE 1 : PARAMETRES PAR DEFAUT On propose deux sections, la première pour l’apprentissage et la seconde pour la classification. Dans celle de l’apprentissage l’utilisateur peut choisir entre « utiliser la base d’exemples d’entrainement existante » ou « ajouter un exemple à la base d’entrainement ». On utilise les exemples existants par défaut. Quand l’ajout d’exemple est coché, des choix supplémentaires apparaissent à l’utilisateur lui demandant de spécifier la nature des caractères qu’il souhaite ajouter à la base d’apprentissage. Il s’agit d’exemple existant quand la classe figure déjà dans notre liste de classes (ce qui présente la liste défilante) et qu’on veut 8 GROUPE SIRX MSIR 2014 enrichir ses modèles. Et encore, il s’agit d’un nouvel exemple quand il n’y pas de classe correspondante, l’utilisateur doit entrer dans ce cas dans le champ de texte de quel caractère il s’agit. FIGURE 2 : OPTIONS D 'AJOUT D ' EXEMPLES Le bouton « Importer » ouvre l’interface d’import de fichier. Une fois son choix effectué, l’image sera transformée en vecteur grâce à une méthode de la classe DataHandler.java et ajoutée dans le fichier data.txt. Le bouton « Lancer l’apprentissage », permet bien entendu de calculer la propagation et la rétro propagation jusqu’à atteinte de poids satisfaisants en faisant appel à la méthode Learning(ArrayList<Character> baseExemples) de la classe BackPropagation.java qui sera détaillé par la suite. Malencontreusement, on ne peut prédire le temps d’exécution de l’apprentissage. L’exécution est lente car non seulement l’algorithme de rétro-propagation peut s’exécuter jusqu’à 1000 fois pour atteindre une erreur quadratique acceptable mais la taille d’image qui interfère aussi. Nos images sont de 50*50 pixel, soit 2500, on calcule les poids pour 200 neurones de la couche caché et ensuite pour 26 neurones de sortie (ce qui inclus déjà plusieurs calculs : la valeur d’activation de chaque neurone ainsi que les deltas d’erreurs et la correction de valeurs) nous laissant ainsi avec des milliers de calculs à faire dans l’unique itération. A noter que lors de nos premiers tests pour l’implémentation de l’algorithme on utilisait des images de 5*5 pixels et donc 25 neurones d’entrée, on allait jusqu’à 3500 itération mais en si peu de temps. On a du coup pensé à permettre à l’utilisateur de paramétrer l’erreur quadratique désirée. Emax = 2 par défaut. 9 GROUPE SIRX MSIR 2014 GraphPanel.java Une fois terminé on récupère la collection contenant l’erreur quadratique calculée à chaque itération et on instancie avec le dessin du graphe de sa variation. C’est la classe GraphPanel.java qui s’occupe de cette tâche pour avoir au final un graphe comme ci-dessous. FIGURE 3 : APERÇU DU GRAPHE DE VARIATION D' ERREUR QUADRATIQUE Cette exécution indique qu’on a fait à peu près 320 itérations avant d’attendre une erreur quadratique égal à 0.99 et qui satisfait nos paramètres. À ce stade, l’apprentissage du perceptron multicouche est effectué. On peut ainsi passer à l’autre section, celle de la classification. Présentée de la manière la plus simple, l’utilisateur est invité à importer l’image de caractère voulu puis pourra ensuite lancer le test. 10 GROUPE SIRX MSIR 2014 FIGURE 4 : PROCEDURE D' IMPORT D 'IMAGE A CLASSIFIER Le bouton de test fait appelle à la méthode Testing(int[] lettre) qui prend en paramètre le vecteur contenant le valeurs de la couche d’entrée du réseau de neurones. Ce vecteur est en effet l’image entrée par l’utilisateur transformée par une méthode de la classe DataHandler.java. FIGURE 5 : APERÇU DE TEST POUR UNE IMAGE CONTENANT LA LETTRE T. 11 GROUPE SIRX MSIR 2014 NetworkGraph.java Quand l’exécution du test est terminé, en récupère les valeurs calculées sur les neurones de la couche de sortie et les passent en paramètres dans une instance de la classe NetworkGraph.java. Cette dernière dessine un réseau un graph simplifié du perceptron multicouche et liste l’ensemble des caractères de sortie ainsi que les valeurs sur lesquels le perceptron s’est appuyé pour sa décision comme montré dans la figure 5. Structure de donnée L’interface repose principalement sur des images contenant des caractères. On a donc besoin de saisir ces informations et les transformer selon nos besoins. Character.java Cette classe contient le modèle qu’on a utilisé; une instance de cette classe correspond à un caractère que ce soit d’apprentissage ou de validation. On peut accéder à son nom grâce à l’attribut classe ou encore à son vecteur de valeurs grâce à l’attribut vecteur. Data.txt Le fichier est en guise de base de données. Il contient pour chaque exemple de la base d’apprentissage les données de son vecteur. Il est structuré de la manière suivante : Ou d’une façon simplifiée : <la classe du caractère> : <vecteur des valeurs>. DataHandler.java Pour manipuler ce fichier ou encore transformer l’image importée en vecteur on fait appel à cette classe. La base de connaissance est stockée dans une collection d’instances de la classe Character.java. Implémentation de l’algorithme de rétropropagation On va simplement lister le contenu de la classe BackPropagation.java import java.util.ArrayList; public class BackPropagation { private static double Emax = 2; //l'erreur quadratique désirée private static final double nu = 0.1; // le paramètre nu du taux d'apprentissage. public static String Classes =""; //contient l'ensemble des classes pris en compte. public static String Exemples ="";//contient la suite des exemples d'apprentisages. public static int nombreClasses; // le nombre de classes. static int[] input; // la couche d'entrée static double[] hidenVector; //la couche cachée static double[] output; //la couche de sortie 12 GROUPE SIRX MSIR 2014 static static static static static static double[] outBias; //les poids du biais de la couche de sortie double[] hidBias; //les poids du biais de la couche cachée double[][] inW; // les poids de la couche d'entrée double[][] hidW; //les poids de la couche cachée int vectorSize = 2500; // le nombre de pixels ArrayList<Double> cycleError; //la collection contenant l'erreur quadratique //calculée dans chaque itération. private static void Allocation() // simple allocation des vecteurs et matrices { input = new int[vectorSize]; hidenVector = new double[200]; output = new double[nombreClasses]; outBias = new double[nombreClasses]; hidBias = new double[200]; inW = new double[vectorSize][200]; hidW = new double[200][nombreClasses]; cycleError = new ArrayList<Double>(); } public static void setInput(int[] input) { for (int i = 0; i < input.length; i++){ BackPropagation.input[i] = input[i]; } } public static void setEmax(double emax) { Emax = emax; } public static ArrayList<Double> getCycleError() { return cycleError; } public static double[] getOutput() { return output; } private static void initWeights() //initialisation des poids { for (int i = 0; i < vectorSize; i++) for (int j = 0; j < 200; j++){ inW[i][j] = (Math.random() - 0.5); // compris entre -0.5 et 0.5 } for (int i = 0; i < 200; i++) for (int j = 0; j < nombreClasses; j++){ hidW[i][j] = (Math.random() - 0.5); } for (int j = 0; j outBias[j] } for (int i = 0; i hidBias[i] } < nombreClasses; j++){ = (Math.random() - 0.5)/5; // compris entre -0.1 et 0.1 < 200; i++){ = (Math.random() - 0.5)/5; } Apprentissage //méthode de l'apprentissage public static void Learning(ArrayList<Character> baseExemples) { // TODO Auto-generated method stub //nombreExemples = baseExemples.size(); 13 GROUPE SIRX MSIR 2014 nombreClasses = Classes.length(); Allocation(); initWeights(); double E; do { E=0; for(Character exemple:baseExemples) { setInput(exemple.getVecteur()); //calcul des sorties de la couche cachée for (int k = 0; k < 200; k++) { double s = 0; for (int j = 0; j < vectorSize; j++){ s += input[j]*inW[j][k]; } s+=hidBias[k]; hidenVector[k] = 1.0 / (1.0 + Math.exp(-s)); } //calcul des sorties de la couche de sortie puis erreur double[] errOut = new double[nombreClasses]; for (int k = 0; k < nombreClasses; k++) { double s = 0; for (int j = 0; j < 200; j++){ s += hidenVector[j]*hidW[j][k]; } s+=outBias[k]; output[k] = 1.0 / (1.0 + Math.exp(-s)); //calcul de l'erreur de sortie – c’est délicat ici. int d = k==Classes.indexOf(""+exemple.getName())?1:0; //Deltam errOut[k]= (d-output[k])*output[k]*(1-output[k]); E+=Math.pow((d-output[k]), 2); //Erreur quadratique //Mise à jour des poids de la couche de sortie for (int j = 0; j < 200; j++){ hidW[j][k]+=nu*errOut[k]*hidenVector[j]; //Delta Wjk : modification de poids - couche de sortie } outBias[k]+= nu*errOut[k]; } // Calcul de l'erreur de la couche caché double[] errHid = new double[200]; for (int j = 0; j < 200; j++) { double contribution=0; for (int k = 0; k < nombreClasses; k++){ contribution+=errOut[k]*hidW[j][k]; } errHid[j]+=hidenVector[j]* (1 hidenVector[j])*contribution; //Mise à jour des poids de la couche cachée for (int k = 0; k < vectorSize; k++){ inW[k][j]+=nu*errHid[j]*input[k]; } hidBias[j]+=nu*errHid[j]; 14 GROUPE SIRX MSIR 2014 } } E=E/2; cycleError.add(E); //System.out.println(E); }while(E>Emax); //System.out.println("terminé!"); //System.out.println(cycleError.size()); } Test de classification //méthode du test public static void Testing(int[] lettre) { setInput(lettre); //calcul des sorties de la couche cachée for (int k = 0; k < 200; k++) { double s = 0; for (int j = 0; j < vectorSize; j++){ s += input[j]*inW[j][k]; } s+=hidBias[k]; hidenVector[k] = 1.0 / (1.0 + Math.exp(-s)); } //calcul des sorties de la couche de sortie for (int k = 0; k < nombreClasses; k++) { double s = 0; for (int j = 0; j < 200; j++){ s += hidenVector[j]*hidW[j][k]; } s+=outBias[k]; output[k] = 1.0 / (1.0 + Math.exp(-s)); //System.out.println(output[k]); } } } 15 GROUPE SIRX MSIR 2014