IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Intelligence artificielle Discussion :

[Réseau neuronal] Rétropropagation du gradient de l'erreur


Sujet :

Intelligence artificielle

  1. #1
    Membre averti
    Homme Profil pro
    Développeur en formation
    Inscrit en
    Juillet 2013
    Messages
    300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur en formation
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Juillet 2013
    Messages : 300
    Points : 413
    Points
    413
    Par défaut [Réseau neuronal] Rétropropagation du gradient de l'erreur
    Bonjour !


    Je poste ce message car j'ai quelques difficultés pour coder (en Python 3) une classe ReseauDeNeurones qui doit me servir de base pour un projet. Si je pense avoir réussi à coder la partie initialisation et exécution, je me tourne vers vous pour ce qui est de l'algorithme de rétro-propagation du gradient de l'erreur qui devrait me permettre d'apprendre des choses à ce réseau de neurones.

    En utilisant wikipédia et un autres, j'avais codé une méthode pour cela, mais j'obtiens des résultats pour le moins... étranges, quelles que soient les différentes valeurs d'apprentissage, configurations, fonctions d'activation. De plus, quels que soient les sites sur lesquels j'ai cherché, soit l'algorithme était mal expliqué, les variables non nommées soit cela contenait des erreurs... Je me pose donc les questions suivantes :

    - Pour ce qui est de l'exécution en elle-même, dois-je appliquer la fonction d'activation aux neurones de la couche d'entrée ? aux neurones de la couche de sortie ?
    - Pour ce qui est de la rétropropagation, pourriez-vous me donner une description claire et organisée de l'algorithme (sans besoin de la preuve, uniquement la manière de procéder et les formules à utiliser - en clair, l'algorithme en langage naturel) ?
    - Pour ceux qui codent en Python, voici mon code :

    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    import random
    import math
     
     
    def f(x) :
        return 1/(1+math.e**(-x))
     
    def derivee(x) :
        return (math.e**(-x)) / ((1+math.e**(-x))**2)
     
    class ReseauDeNeurones :
        """Gere les principales fonctionalites d'un reseau de neurones."""
        def __init__( self, nb_neurones_par_couche=[10,8,1], valeur_par_defaut_synapses = "random.random()*2-1", fonction = f, derivee=derivee ) :
            """Constructeur de l'objet.
            nb_neurones_par_couche : liste contenant le nombre de neurones par couche
            le premier etant les entrees et le dernier les sorties
            valeur_par_defaut_synapses : contient la valeur par defaut des synapses sous la forme de str /!\
            fonction : fonction d'activation
            derivee : derivee de la fonction d'activation"""
     
            self.f = self.fonction = fonction
            self.f_ = derivee
     
            self.couches = []#contient self.couches[couche][neurone][poids]
            for nb in range(len(nb_neurones_par_couche)-1) :
                     self.couches.append([])#une couche de plus
                     for i in range(nb_neurones_par_couche[nb]) :
                         self.couches[-1].append([])#un neurone de plus
                         for synapse in range(nb_neurones_par_couche[nb+1]) :
                             self.couches[nb][i].append(eval(valeur_par_defaut_synapses))
     
        def executer(self,*entrees) :
            """Execute le reseau de neurones avec les entrees donnees (qui doivent etre egales au nombre de neurones).
            Les neurones de sortie et d'entree n'appliquent pas la fonction d'activation."""
            sortie=[0]*len(self.couches[1])
            for i in range(len(entrees)) :
                     entree=self.f(entrees[i])
                     for ii in range(len(self.couches[0][i])) :
                         poids = self.couches[0][i][ii]
                         sortie[ii]+=poids*entree
     
            for couche in self.couches[1:] :
                sortie,entrees = [0]*len(couche[0]), sortie
                for i in range(len(entrees)) :
                     entree=self.fonction(entrees[i])
                     for ii in range(len(couche[i])) :
                         poids = couche[i][ii]
                         sortie[ii]+=poids*entree
            return [self.f(v) for v in sortie]
     
        def _executer_(self,*entrees) :
            """Execute le reseau de neurones avec les entrees donnees (qui doivent etre egales au nombre de neurones).
            Les neurones de sortie et d'entree n'appliquent pas la fonction d'activation.
            fonction renvoyant les sorties des differentes couches"""
            S = []
            sortie=[0]*len(self.couches[1])
            for i in range(len(entrees)) :
                     entree=self.f(entrees[i])
                     for ii in range(len(self.couches[0][i])) :
                         poids = self.couches[0][i][ii]
                         sortie[ii]+=poids*entree
            S.append(list(sortie))
     
            for couche in self.couches[1:] :
                sortie,entrees = [0]*len(couche[0]), sortie
                for i in range(len(entrees)) :
                     entree=self.fonction(entrees[i])
                     for ii in range(len(couche[i])) :
                         poids = couche[i][ii]
                         sortie[ii]+=poids*entree
                S.append(list(sortie))
            return S #-> valeurs brutes sans seuillage par la fonction f
     
        def retropropagation(self, entree, sortie) :
            """entree -> entrees voulues
            sortie -> sortie voulue
            s -> sortie obtenue avant modification
            deja pour 3 couches"""
            nouveaux_poids = []
            for couche in range(len(self.couches)) :
                     nouveaux_poids.append([])
                     for neurone in self.couches[couche] :
                         nouveaux_poids[-1].append(list(neurone))
            s = self._executer_()
            #dernière couche : k
            E=[]
            for i in range(len(s[1])) : #i : position du neurone dans la couche 2 = sortie
                erreur_neurone = self.f_(s[1][i]) * ( sortie[i] - self.f(s[1][i]) )
                #on "repare" l'erreur
                for j in range(len(self.couches[1])) :
                    nouveaux_poids[1][j][i] -= erreur_neurone * self.couches[1][j][i]
                E.append(erreur_neurone)
     
            for i in range(len(s[0])) : #i : position du neurone dans la couche 1
                somme = 0
                for k in range(len(self.couches[1][i])) :
                    somme+=E[k]*self.couches[1][i][k]
     
                erreur = self.f_(s[0][i])*somme
     
                #on "repare" l'erreur
                for j in range(len(self.couches[0])) :
                    nouveaux_poids[0][j][i] -= erreur * self.couches[1][j][i]
     
            self.couches = nouveaux_poids

    Merci d'avance de vos réponses, elles me seraient d'une grande aide !


    EDIT : je précise que ces réseaux de neurones bâtis sur cette classe doivent pouvoir être également "analogiques", j'entends par là manipuler des entrées et des sorties au delà de [-1;1], dans ce cas une autre fonction que la sigmoïde sera plus appropriée
    Bouddha : Tout n'est qu'illusion en ce bas monde.
    Jésus : Tout est amour divin.
    Einstein : Tout est relatif dans cet espace-temps.
    Moi : Tout est binaire sur ce forum.

  2. #2
    Responsable Qt & Livres


    Avatar de dourouc05
    Homme Profil pro
    Ingénieur de recherche
    Inscrit en
    Août 2008
    Messages
    26 619
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur de recherche
    Secteur : Enseignement

    Informations forums :
    Inscription : Août 2008
    Messages : 26 619
    Points : 188 594
    Points
    188 594
    Par défaut


    Le principe de base d'un réseau de neurones est d'appliquer une certaine fonction compliquée sur les entrées pour donner les sorties. Tu appliques donc la fonction d'activation sur une combinaison linéaire des entrées du neurone considéré pour obtenir sa sortie.

    Pour l'algorithme, http://ufldl.stanford.edu/wiki/index...tion_Algorithm pourrait t'aider. (Pour comprendre les calculs, j'avais bien aimé http://research.microsoft.com/pubs/6...sian-nc-92.pdf.)
    Vous souhaitez participer aux rubriques Qt (tutoriels, FAQ, traductions) ou HPC ? Contactez-moi par MP.

    Créer des applications graphiques en Python avec PyQt5
    Créer des applications avec Qt 5.

    Pas de question d'ordre technique par MP !

  3. #3
    Membre averti
    Homme Profil pro
    Développeur en formation
    Inscrit en
    Juillet 2013
    Messages
    300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur en formation
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Juillet 2013
    Messages : 300
    Points : 413
    Points
    413
    Par défaut
    Citation Envoyé par dourouc05 Voir le message


    Le principe de base d'un réseau de neurones est d'appliquer une certaine fonction compliquée sur les entrées pour donner les sorties. Tu appliques donc la fonction d'activation sur une combinaison linéaire des entrées du neurone considéré pour obtenir sa sortie.
    Tout d'abord, merci pour la réponse .

    J'avais mal formulé ma question. Mon algorithme, pour ce qui est de la propagation "normale", en bref de l'exécution du réseau de neurones, je prends en effet la somme des valeurs de sortie des neurones précédents pondérée par les poids synaptiques de toutes les entrées, à laquelle j'applique la fonction d'activation (dans mon code une sigmoïde) pour avoir la sortie (du moins sur les neurones des couches cachées). Je l'ai éditée ; ma première question est toute bête et est :

    - Dois-je appliquer la fonction d'activation aux valeurs de la couche d'entrée, et dois-je l'appliquer aux valeurs reçues par les neurones de la couche d'entrée ?

    Car si cela n'a pas de grande importance quand on a un algorithme génétique, cela peut totalement fausser les résultats l'algorithme de rétropropagation.

    Pour donner un exemple parlant, imaginons un réseau"de 3 "couches" de 1 neurone, avec un neurone entrée, un dans la couche cachée, un en sortie. La fonction d'activation est une sigmoïde Nom : b06fa4daad1a9073fb09b327e1c6d005.png
Affichages : 1408
Taille : 679 octets (donc donnant des valeurs entre 0 et 1). L'entrée est 5.

    Si tous les neurones appliquent la fonction, avec dans tous les cas des poids synaptiques de 1 :
    10 -> Neurone d'entrée -> f(5)=0.9933071490757153 -> neurone caché -> f(0.9933071490757153) = 0.729740651093834 -> neurone de sortie -> f(0.729740651093834) = 0.6747483576700875

    Et maintenant on prend les valeurs d'entrée brutes et on prend les valeurs de sorties brutes :
    neurone d'entrée = 5 -> neurone caché -> f(5) = 0.9933071490757153 -> neurone de sortie = 0.9933071490757153 = résultat

    On obtient donc une différence. Ma question est donc laquelle des deux options prend-on ?


    A part ça, au niveau de la documentation, j'ai commencé à lire - même si l'anglais mathématique c'est pas mon truc. Enfin il va bien falloir que je m'y mette.
    Bouddha : Tout n'est qu'illusion en ce bas monde.
    Jésus : Tout est amour divin.
    Einstein : Tout est relatif dans cet espace-temps.
    Moi : Tout est binaire sur ce forum.

  4. #4
    Membre averti
    Homme Profil pro
    Développeur en formation
    Inscrit en
    Juillet 2013
    Messages
    300
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur en formation
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Juillet 2013
    Messages : 300
    Points : 413
    Points
    413
    Par défaut
    J'ai fini par réussir à en programmer une première version (marche uniquement avec une sigmoïde) grâce à une vidéo que je vous recommande vivement si vous vous demandez comment ça marche. C'est simplement expliqué avec un exemple, et en français québécois en plus !

    La voici et mon code aussi pour ceux que ça intéresse :



    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    import random
    import math
     
     
    def f(x) :
        try :
            return 1/(1+math.e**(-x))
        except OverflowError :
            return 0
     
    def derivee_partielle(x) :
        return 1-x
     
     
     
     
    class ReseauDeNeurones :
        """Gere les principales fonctions d'un reseau de neurones."""
        def __init__( self, nb_neurones_par_couche=[10,8,1], valeur_par_defaut_synapses = "random.random()*2-1", fonction = f, derivee=derivee_partielle ) :
            """Constructeur de l'objet.
            nb_neurones_par_couche : liste contenant le nombre de neurones par couche
            le premier etant les entrees et le dernier les sorties
            valeur_par_defaut_synapses : contient la valeur par defaut des synapses sous la forme de str /!\
            fonction : fonction d'activation (sigmoide par defaut)"""
     
            self.f = self.fonction = fonction
            self.f_ = derivee
     
            self.couches = []#contient self.couches[couche][neurone][poids]
            for nb in range(len(nb_neurones_par_couche)) :
                     self.couches.append([])#une couche de plus
                     for i in range(nb_neurones_par_couche[nb]) :
                         self.couches[nb].append([])#un neurone de plus
                         try :
                             for synapse in range(nb_neurones_par_couche[nb+1]) :
                                 self.couches[nb][i].append(eval(valeur_par_defaut_synapses))
                         except :
                             pass
     
        def executer(self,*entrees) :
            """Execute le reseau de neurones avec les entrees donnees (qui doivent etre egales au nombre de neurones).
            Les neurones de sortie et d'entree n'appliquent pas la fonction d'activation."""
            #https://www.youtube.com/watch?v=0jh-jlWfKwo
     
            a=[[]] #a[layer][neurone] -> valeur sortie neurone
            for i in range(len(self.couches[0])) :
                a[0].append(entrees[i])
     
            in_=[[]] #in[layer][neurone] -> valeur entree neurone
            for l in range(1,len(self.couches)) :
                a.append([])
                in_.append([])
                for j in range(len(self.couches[l])) :
                    in_[l].append(0)
                    for i in range(len(self.couches[l-1])) :
                        in_[l][j] += a[l-1][i] * self.couches[l-1][i][j]
                    a[l].append(self.f(in_[l][j]))
     
            return a[-1]
            #return in_[-1]
     
     
     
        def retropropagation(self, entrees, sortie, taux=0.1) :
            """entrees -> entrees voulues
            sortie -> sortie voulue
            taux -> taux d'apprentissage"""
            #1) propagation normale
     
            a=[[]] #a[layer][neurone] -> valeur sortie neurone
            for i in range(len(self.couches[0])) :
                a[0].append(entrees[i])
     
            in_=[[]] #in[layer][neurone] -> valeur entree neurone
            for l in range(1,len(self.couches)) :
                a.append([])
                in_.append([])
                for j in range(len(self.couches[l])) :
                    in_[l].append(0)
                    for i in range(len(self.couches[l-1])) :
                        in_[l][j] += a[l-1][i] * self.couches[l-1][i][j]
                    a[l].append(self.f(in_[l][j]))
     
     
            #2) retropropagation
     
            delta=[[]]*len(self.couches) #delta[layer][neurone] -> erreur
            for j in range(len(self.couches[-1])) :
                delta[-1].append(sortie[j]-a[-1][j])
            for l in range(len(self.couches)-2, 0, -1) :
                for i in range(len(self.couches[l])) :
                    delta[l].append( a[l][i] * (1-a[l][i]) )
                    v=0
                    for j in range(len(self.couches[l+1])) :
                        v+=delta[l+1][j]*self.couches[l][i][j]
                    delta[l][i]*=v
     
            for l in range(len(self.couches)-1) :
                for i in range(len(self.couches[l])) :
                    for j in range(len(self.couches[l+1])) :
                        self.couches[l][i][j] += taux * a[l][i] * delta[l+1][j]

    Mais évidemment, ce ne serait pas drôle si je n'avais pas de question :
    - Maintenant, comment faire pour adapter à n'importe quelle fonction d'activation ?

    La ligne clé est la ligne suivante : "delta[l].append( a[l][i] * (1-a[l][i]) )" (plus ce qui suit)
    Soit littéralement :
    -> erreur du dernier neurone de la couche l = f(entrée_brute_neurone) * (1 - f(entrée_brute_neurone) ) * somme_pondérée_des_erreurs_des_neurones_suivants

    C'est sur la première partie de la formule que le bât blesse. Je crois qu'il y a une histoire de dérivée partielle que je n'ai pas comprise. Si quelqu'un pouvait me donner la formule pour calculer l'erreur d'un neurone en couche cachée ce serait l'idéal.

    Merci d'avance !
    Bouddha : Tout n'est qu'illusion en ce bas monde.
    Jésus : Tout est amour divin.
    Einstein : Tout est relatif dans cet espace-temps.
    Moi : Tout est binaire sur ce forum.

  5. #5
    Membre à l'essai
    Homme Profil pro
    Ingénieur qualité méthodes
    Inscrit en
    Février 2011
    Messages
    21
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur qualité méthodes

    Informations forums :
    Inscription : Février 2011
    Messages : 21
    Points : 24
    Points
    24
    Par défaut
    Oui, je déterre un vieux sujet, mais puisque que je suis tombé dessus par une recherche, ça pourrait rendre service à quelqu'un...

    Ce que tu appelle "entrée brute" est en fait la somme pondérée du neurone.

    Mais en fait f(x) est la sortie du neurone que tu as déja calculé lors du calcul avant (forward cad de gauche à droit) que tu as fait précédemment.
    Donc ça revient à prendre la sortie de ton neurone moins un et de la multiplier par la sortie du neurone.
    Si tu veux utiliser d'autres fonction d'activation que la sigmoïd, c'est très simple, tu remplace la formule de calcul par la fonction qui t’intéresse, puis la formule de propagation arrière par sa dérivée. (voir wikipedia en anglais, activation function)

Discussions similaires

  1. [Python 3.X] Réseau de neurones et rétro-propagation du gradient de l'erreur
    Par stalacta dans le forum Général Python
    Réponses: 5
    Dernier message: 07/12/2018, 10h40
  2. [Réseau de neurones] Rétropropagation : pas de bons résultats
    Par DJEcalcul dans le forum Méthodes prédictives
    Réponses: 11
    Dernier message: 06/03/2014, 12h21
  3. [Réseau de neurones] Rétropropagation et biais
    Par Tasty dans le forum Méthodes prédictives
    Réponses: 1
    Dernier message: 02/03/2014, 20h12
  4. Réponses: 1
    Dernier message: 20/06/2011, 11h08
  5. Reconnaissance de forme par réseau neuronal
    Par Rémiz dans le forum Méthodes prédictives
    Réponses: 34
    Dernier message: 28/05/2007, 16h41

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo