Comment créer un réseau neuronal et faire des prédictions – Real Python

By | mars 17, 2021

python pour débutant

Si vous débutez dans le monde de l'intelligence artificielle (IA), alors Python est un excellent langage à apprendre car la plupart des outils sont construits en l'utilisant. L'apprentissage en profondeur est une technique utilisée pour faire des prédictions à l'aide de données, et elle repose fortement sur les réseaux de neurones. Aujourd'hui, vous allez apprendre à créer un réseau neuronal à partir de rien.

Dans un contexte de production, vous utiliseriez un cadre d'apprentissage en profondeur comme TensorFlow ou PyTorch au lieu de créer votre propre réseau de neurones. Cela dit, il est utile d'avoir une certaine connaissance du fonctionnement des réseaux de neurones, car vous pouvez l'utiliser pour mieux concevoir vos modèles d'apprentissage en profondeur.

Présentation de l'intelligence artificielle

En termes simples, le but de l'utilisation de l'IA est de faire en sorte que les ordinateurs pensent comme les humains. Cela peut paraître nouveau, mais le domaine est né dans les années 1950.

Imaginez que vous ayez besoin d'écrire un programme Python qui utilise l'IA pour résoudre un problème de sudoku. Un moyen d'y parvenir est d'écrire des instructions conditionnelles et de vérifier les contraintes pour voir si vous pouvez placer un nombre dans chaque position. Eh bien, ce script Python est déjà une application de l'IA car vous avez programmé un ordinateur pour résoudre un problème!

Apprentissage automatique (ML) et apprentissage en profondeur (DL) sont également des approches pour résoudre des problèmes. La différence entre ces techniques et un script Python est que ML et DL utilisent données d'entraînement au lieu de règles codées en dur, mais toutes peuvent être utilisées pour résoudre des problèmes à l'aide de l'IA. Dans les sections suivantes, vous en apprendrez plus sur ce qui différencie ces deux techniques.

Apprentissage automatique

L'apprentissage automatique est une technique dans laquelle vous formez le système à résoudre un problème au lieu de programmer explicitement les règles. Revenant à l'exemple de sudoku dans la section précédente, pour résoudre le problème à l'aide de l'apprentissage automatique, vous collecteriez des données à partir de jeux de sudoku résolus et entraîneriez un modèle statistique. Les modèles statistiques sont des moyens mathématiquement formalisés d'approximer le comportement d'un phénomène.

Une tâche d'apprentissage automatique courante est l'apprentissage supervisé, dans lequel vous disposez d'un ensemble de données avec des entrées et des sorties connues. La tâche consiste à utiliser cet ensemble de données pour entraîner un modèle qui prédit les sorties correctes en fonction des entrées. L'image ci-dessous présente le flux de travail pour former un modèle à l'aide de l'apprentissage supervisé:

Flux de travail d'apprentissage supervisé
Workflow pour former un modèle d'apprentissage automatique

La combinaison des données d'entraînement avec l'algorithme d'apprentissage automatique crée le modèle. Ensuite, avec ce modèle, vous pouvez faire des prédictions pour de nouvelles données.

Le but des tâches d'apprentissage supervisé est de faire des prédictions pour de nouvelles données invisibles. Pour ce faire, vous supposez que ces données invisibles suivent une distribution de probabilité similaire à la distribution de l'ensemble de données d'entraînement. Si, à l'avenir, cette distribution change, vous devez à nouveau entraîner votre modèle à l'aide du nouvel ensemble de données d'entraînement.

Ingénierie des fonctionnalités

Les problèmes de prédiction deviennent plus difficiles lorsque vous utilisez différents types de données comme entrées. Le problème du sudoku est relativement simple car vous traitez directement avec les nombres. Que faire si vous souhaitez entraîner un modèle pour prédire le sentiment dans une phrase? Ou que faire si vous avez une image et que vous voulez savoir si elle représente un chat?

Un autre nom pour les données d'entrée est fonctionnalité, et ingénierie des fonctionnalités est le processus d'extraction de fonctionnalités à partir de données brutes. Lorsque vous traitez avec différents types de données, vous devez trouver des moyens de représenter ces données afin d'en extraire des informations significatives.

Un exemple de technique d'ingénierie d'entités est la lemmatisation, dans laquelle vous supprimez l'inflexion des mots d'une phrase. Par exemple, les formes fléchies du verbe «regarder», comme «regarder», «regarder» et «regarder», seraient réduites à leur lemme, ou forme de base: "montre".

Si vous utilisez des tableaux pour stocker chaque mot d'un corpus, en appliquant la lemmatisation, vous vous retrouvez avec une matrice moins clairsemée. Cela peut augmenter les performances de certains algorithmes d'apprentissage automatique. L'image suivante présente le processus de lemmatisation et de représentation à l'aide d'un modèle de sac de mots:

Ingénierie des fonctionnalités du texte au tableau numérique
Création d'entités à l'aide d'un modèle de sac de mots

Premièrement, la forme fléchie de chaque mot est réduite à son lemme. Ensuite, le nombre d'occurrences de ce mot est calculé. Le résultat est un tableau contenant le nombre d'occurrences de chaque mot dans le texte.

L'apprentissage en profondeur

L'apprentissage en profondeur est une technique dans laquelle vous laissez le réseau de neurones déterminer par lui-même quelles fonctionnalités sont importantes au lieu d'appliquer des techniques d'ingénierie de fonctionnalités. Cela signifie qu'avec l'apprentissage en profondeur, vous pouvez contourner le processus d'ingénierie des fonctionnalités.

Il est bon de ne pas avoir à gérer l'ingénierie d'entités, car le processus devient plus difficile à mesure que les jeux de données deviennent plus complexes. Par exemple, comment extraire les données pour prédire l'humeur d'une personne à partir d'une photo de son visage? Avec les réseaux de neurones, vous n’avez pas à vous en soucier, car les réseaux peuvent apprendre les fonctionnalités par eux-mêmes. Dans les sections suivantes, vous plongerez profondément dans les réseaux de neurones pour mieux comprendre leur fonctionnement.

Réseaux de neurones: principaux concepts

Un réseau de neurones est un système qui apprend à faire des prédictions en suivant ces étapes:

  1. Prendre les données d'entrée
  2. Faire une prédiction
  3. Comparaison de la prédiction à la sortie souhaitée
  4. Ajuster son état interne pour prédire correctement la prochaine fois

Vecteurs, couches, et régression linéaire sont quelques-uns des éléments constitutifs des réseaux de neurones. Les données sont stockées sous forme de vecteurs, et avec Python, vous stockez ces vecteurs dans des tableaux. Chaque couche transforme les données provenant de la couche précédente. Vous pouvez considérer chaque couche comme une étape d'ingénierie d'entités, car chaque couche extrait une représentation des données fournies précédemment.

Une chose intéressante à propos des couches de réseau neuronal est que les mêmes calculs peuvent extraire des informations tout type de données. Cela signifie que peu importe si vous utilisez des données d'image ou des données textuelles. Le processus pour extraire des informations significatives et former le modèle d'apprentissage en profondeur est le même pour les deux scénarios.

Dans l'image ci-dessous, vous pouvez voir un exemple d'architecture réseau à deux couches:

Schéma montrant un réseau de neurones à deux couches
Un réseau de neurones à deux couches

Chaque couche transforme les données provenant de la couche précédente en appliquant certaines opérations mathématiques.

Le processus de formation d'un réseau neuronal

L'entraînement d'un réseau neuronal est similaire au processus d'essais et d'erreurs. Imaginez que vous jouez aux fléchettes pour la première fois. Lors de votre premier lancer, vous essayez de toucher le point central du jeu de fléchettes. Habituellement, le premier coup consiste simplement à avoir une idée de la façon dont la hauteur et la vitesse de votre main affectent le résultat. Si vous voyez que la fléchette est plus haute que le point central, vous ajustez votre main pour la lancer un peu plus bas, et ainsi de suite.

Voici les étapes pour essayer de frapper le centre d'un jeu de fléchettes:

Les étapes pour lancer des fléchettes
Étapes pour frapper le centre d'un jeu de fléchettes

Notez que vous continuez à évaluer l'erreur en observant où la fléchette a atterri (étape 2). Vous continuez jusqu'à ce que vous atteigniez enfin le centre du jeu de fléchettes.

Avec les réseaux de neurones, le processus est très similaire: vous commencez par poids et biais vecteurs, faites une prédiction, comparez-la à la sortie souhaitée et ajustez les vecteurs pour prédire plus précisément la prochaine fois. Le processus se poursuit jusqu'à ce que la différence entre la prédiction et les cibles correctes soit minimale.

Savoir quand arrêter la formation et quel objectif de précision définir est un aspect important de la formation des réseaux de neurones, principalement en raison de scénarios de surajustement et de sous-ajustement.

Vecteurs et poids

Travailler avec des réseaux de neurones consiste à faire des opérations avec des vecteurs. Vous représentez les vecteurs sous forme de tableaux multidimensionnels. Les vecteurs sont utiles dans l'apprentissage en profondeur principalement en raison d'une opération particulière: le produit scalaire. Le produit scalaire de deux vecteurs vous indique à quel point ils sont similaires en termes de direction et est mis à l'échelle par la magnitude des deux vecteurs.

Les principaux vecteurs à l'intérieur d'un réseau neuronal sont les poids et les vecteurs de biais. En gros, vous voulez que votre réseau de neurones vérifie si une entrée est similaire à d’autres entrées déjà vues. Si la nouvelle entrée est similaire aux entrées vues précédemment, les sorties seront également similaires. C’est ainsi que vous obtenez le résultat d’une prédiction.

Le modèle de régression linéaire

Régression est utilisé lorsque vous devez estimer la relation entre un variable dépendante et deux ou plus variables indépendantes. La régression linéaire est une méthode appliquée lorsque vous estimez la relation entre les variables comme linéaire. La méthode remonte au XIXe siècle et est la méthode de régression la plus populaire.

En modélisant la relation entre les variables comme linéaire, vous pouvez exprimer la variable dépendante sous la forme d'un somme pondérée des variables indépendantes. Ainsi, chaque variable indépendante sera multipliée par un vecteur appelé poids. Outre les poids et les variables indépendantes, vous ajoutez également un autre vecteur: le biais. Il définit le résultat lorsque toutes les autres variables indépendantes sont égales à zéro.

En tant qu'exemple réel de la façon de construire un modèle de régression linéaire, imaginez que vous souhaitez former un modèle pour prédire le prix des maisons en fonction de la superficie et de l'âge de la maison. Vous décidez de modéliser cette relation à l'aide de la régression linéaire. Le bloc de code suivant montre comment vous pouvez écrire un modèle de régression linéaire pour le problème déclaré dans le pseudocode:

price = (weights_area * area) + (weights_age * age) + biais

Dans l'exemple ci-dessus, il y a deux poids: weights_area et poids_age. Le processus de formation consiste à ajuster les pondérations et le biais afin que le modèle puisse prédire la valeur correcte du prix. Pour ce faire, vous devrez calculer l'erreur de prédiction et mettre à jour les pondérations en conséquence.

Ce sont les bases du fonctionnement du mécanisme du réseau neuronal. Il est maintenant temps de voir comment appliquer ces concepts à l'aide de Python.

Python AI: commencer à construire votre premier réseau de neurones

La première étape de la construction d'un réseau neuronal consiste à générer une sortie à partir des données d'entrée. Vous ferez cela en créant une somme pondérée des variables. La première chose à faire est de représenter les entrées avec Python et NumPy.

Emballage des entrées du réseau neuronal avec NumPy

Vous utiliserez NumPy pour représenter les vecteurs d'entrée du réseau sous forme de tableaux. Mais avant d'utiliser NumPy, c'est une bonne idée de jouer avec les vecteurs en Python pur pour mieux comprendre ce qui se passe.

Dans ce premier exemple, vous avez un vecteur d'entrée et les deux autres vecteurs de poids. Le but est de trouver lequel des poids est le plus similaire à l'entrée, en tenant compte de la direction et de la magnitude. Voici à quoi ressemblent les vecteurs si vous les tracez:

Trois vecteurs dans un plan de coordonnées cartésien
Trois vecteurs dans un plan de coordonnées cartésien

poids_2 est plus similaire au vecteur d'entrée car il pointe dans la même direction et la magnitude est également similaire. Alors, comment déterminer quels vecteurs sont similaires en utilisant Python?

Tout d'abord, vous définissez les trois vecteurs, un pour l'entrée et les deux autres pour les poids. Ensuite, vous calculez à quel point input_vector et poids_1 sommes. Pour ce faire, vous appliquerez le produit scalaire. Puisque tous les vecteurs sont des vecteurs bidimensionnels, voici les étapes pour le faire:

  1. Multipliez le premier indice de input_vector par le premier index de poids_1.
  2. Multipliez le deuxième indice de input_vector par le deuxième indice de poids_2.
  3. Additionnez les résultats des deux multiplications.

Vous pouvez utiliser une console IPython ou un bloc-notes Jupyter pour suivre. Il est recommandé de créer un nouvel environnement virtuel à chaque fois que vous démarrez un nouveau projet Python. Vous devez donc le faire en premier. venv est livré avec les versions 3.3 et supérieures de Python, et c'est pratique pour créer un environnement virtuel:

$ python -m venv ~ / .my-env
$ la source ~ / .my-env / bin / activer

À l'aide des commandes ci-dessus, vous créez d'abord l'environnement virtuel, puis vous l'activez. Il est maintenant temps d'installer la console IPython à l'aide de pépin. Étant donné que vous aurez également besoin de NumPy et Matplotlib, il est judicieux de les installer également:

(mon-env) $ python -m pip installer ipython numpy matplotlib
(mon-env) $ ipython

Vous êtes maintenant prêt à commencer à coder. C'est le code pour calculer le produit scalaire de input_vector et poids_1:

>>>

Dans [1]: input_vector = [[[[1,72, 1,23]
Dans [2]: poids_1 = [[[[1,26, 0]
Dans [3]: poids_2 = [[[[2,17, 0,32]

Dans [4]: # Calcul du produit scalaire de input_vector et weights_1
Dans [5]: first_indexes_mult = input_vector[[[[0] * poids_1[[[[0]
Dans [6]: second_indexes_mult = input_vector[[[[1] * poids_1[[[[1]
Dans [7]: dot_product_1 = first_indexes_mult + second_indexes_mult

Dans [8]: impression(F"Le produit scalaire est: dot_product_1")
En dehors[8]: Le produit scalaire est: 2.1672

Le résultat du produit scalaire est 2.1672. Maintenant que vous savez comment calculer le produit scalaire, il est temps d'utiliser np.dot () de NumPy. Voici comment calculer dot_product_1 utilisant np.dot ():

>>>

Dans [9]: importer engourdi comme np

Dans [10]: dot_product_1 = np.point(input_vector, poids_1)

Dans [11]: impression(F"Le produit scalaire est: dot_product_1")
En dehors[11]: Le produit scalaire est: 2.1672

np.dot () fait la même chose que vous faisiez auparavant, mais il vous suffit maintenant de spécifier les deux tableaux comme arguments. Calculons maintenant le produit scalaire de input_vector et poids_2:

>>>

Dans [10]: dot_product_2 = np.point(input_vector, poids_2)

Dans [11]: impression(F"Le produit scalaire est: dot_product_2")
En dehors[11]: Le produit scalaire est: 4.1259

Cette fois, le résultat est 4.1259. En tant que manière différente de penser le produit scalaire, vous pouvez traiter la similitude entre les coordonnées vectorielles comme un interrupteur marche-arrêt. Si le résultat de la multiplication est 0, alors vous direz que les coordonnées sont ne pas similaire. Si le résultat est autre chose que 0, alors vous direz qu’ils sommes similaire.

De cette façon, vous pouvez voir le produit scalaire comme une mesure lâche de la similitude entre les vecteurs. Chaque fois que le résultat de la multiplication est 0, le produit scalaire final aura un résultat inférieur. Revenons aux vecteurs de l'exemple, puisque le produit scalaire de input_vector et poids_2 est 4.1259, et 4.1259 est supérieur à 2.1672, cela signifie que input_vector est plus similaire à poids_2. Vous utiliserez ce même mécanisme dans votre réseau neuronal.

Dans ce didacticiel, vous allez entraîner un modèle à faire des prédictions qui n'ont que deux résultats possibles. Le résultat de sortie peut être soit 0 ou 1. C'est un problème de classification, un sous-ensemble de problèmes d'apprentissage supervisé dans lequel vous avez un jeu de données avec les entrées et les cibles connues. Ce sont les entrées et les sorties de l'ensemble de données:

Vecteur d'entrée Cibler
[1.66, 1.56] 1
[2, 1.5] 0

Le cibler est la variable que vous souhaitez prédire. Dans cet exemple, vous avez affaire à un ensemble de données composé de nombres. Ce n’est pas courant dans un scénario de production réel. Habituellement, lorsqu'un modèle d'apprentissage en profondeur est nécessaire, les données sont présentées dans des fichiers, tels que des images ou du texte.

Faire votre première prédiction

Puisqu'il s'agit de votre tout premier réseau de neurones, vous garderez les choses simples et vous construirez un réseau avec seulement deux couches. Jusqu'à présent, vous avez vu que les deux seules opérations utilisées à l'intérieur du réseau neuronal étaient le produit scalaire et une somme. Les deux sont opérations linéaires.

Si vous ajoutez plus de couches mais que vous continuez à n'utiliser que des opérations linéaires, l'ajout de couches supplémentaires n'aura aucun effet car chaque couche aura toujours une certaine corrélation avec l'entrée de la couche précédente. Cela implique que, pour un réseau à plusieurs couches, il y aurait toujours un réseau avec moins de couches qui prédit les mêmes résultats.

Ce que vous voulez, c'est trouver une opération qui rend les couches intermédiaires parfois corrélées avec une entrée et parfois non corrélées.

Vous pouvez obtenir ce comportement en utilisant des fonctions non linéaires. Ces fonctions non linéaires sont appelées fonctions d'activation. Il existe de nombreux types de fonctions d'activation. Le ReLU (unité linéaire rectifiée), par exemple, est une fonction qui convertit tous les nombres négatifs en zéro. Cela signifie que le réseau peut "désactiver" une pondération si elle est négative, ajoutant de la non-linéarité.

Le réseau que vous construisez utilisera la fonction d’activation sigmoïde. Vous l'utiliserez dans le dernier calque, layer_2. Les deux seules sorties possibles dans l'ensemble de données sont 0 et 1, et la fonction sigmoïde limite la sortie à une plage comprise entre 0 et 1. Voici la formule pour exprimer la fonction sigmoïde:

Formule de la fonction sigmoïde
Formule de la fonction sigmoïde

Le e est une constante mathématique appelée nombre d'Euler, et vous pouvez utiliser np.exp (x) calculer .

Les fonctions de probabilité vous donnent la probabilité d'occurrence des résultats possibles d'un événement. Les deux seules sorties possibles de l'ensemble de données sont 0 et 1, et la distribution de Bernoulli est une distribution qui a également deux résultats possibles. La fonction sigmoïde est un bon choix si votre problème suit la distribution de Bernoulli, c'est pourquoi vous l'utilisez dans la dernière couche de votre réseau neuronal.

Étant donné que la fonction limite la sortie à une plage de 0 à 1, vous l'utiliserez pour prédire les probabilités. Si la sortie est supérieure à 0,5, alors vous direz que la prédiction est 1. Si c'est ci-dessous 0,5, alors vous direz que la prédiction est 0. Voici le flux des calculs à l'intérieur du réseau que vous construisez:

L'architecture d'un réseau de neurones à deux couches
Le flux des calculs à l'intérieur de votre réseau de neurones

Les hexagones jaunes représentent les fonctions et les rectangles bleus représentent les résultats intermédiaires. Il est maintenant temps de transformer toutes ces connaissances en code. Vous devrez également envelopper les vecteurs avec des tableaux NumPy. C'est le code qui applique les fonctions présentées dans l'image ci-dessus:

>>>

Dans [12]: # Emballage des vecteurs dans des tableaux NumPy
Dans [13]: input_vector = np.déployer([[[[1,66, 1,56])
Dans [14]: poids_1 = np.déployer([[[[1,45, -0,66])
Dans [15]: biais = np.déployer([[[[0,0])

Dans [16]: def sigmoïde(X):
            ...:     revenir 1 / (1 + np.exp(-X))

Dans [17]: def Faire des prédictions(input_vector, poids, biais):
            ...:      layer_1 = np.point(input_vector, poids) + biais
            ...:      layer_2 = sigmoïde(layer_1)
            ...:      revenir layer_2

Dans [18]: prédiction = Faire des prédictions(input_vector, poids_1, biais)

Dans [19]: impression(F"Le résultat de la prédiction est: prédiction")
En dehors[19]: Le résultat de la prédiction est: [0.7985731]

Le résultat brut de la prédiction est 0,79, qui est supérieur à 0,5, donc la sortie est 1. Le réseau a fait une prédiction correcte. Maintenant, essayez-le avec un autre vecteur d'entrée, np.array ([2, 1.5]). Le résultat correct pour cette entrée est 0. Il vous suffira de modifier le input_vector variable puisque tous les autres paramètres restent les mêmes:

>>>

Dans [20]: # Modification de la valeur de input_vector
Dans [21]: input_vector = np.déployer([[[[2, 1,5])

Dans [22]: prédiction = Faire des prédictions(input_vector, poids_1, biais)

Dans [23]: impression(F"Le résultat de la prédiction est: prédiction")
En dehors[23]: Le résultat de la prédiction est: [0.87101915]

Cette fois, le réseau a fait une fausse prédiction. Le résultat doit être inférieur à 0,5 puisque la cible de cette entrée est 0, mais le résultat brut était 0,87. Il a fait une mauvaise estimation, mais quelle était la gravité de l'erreur? La prochaine étape consiste à trouver un moyen d'évaluer cela.

Formez votre premier réseau neuronal

Au cours du processus d'entraînement du réseau neuronal, vous évaluez d'abord l'erreur, puis ajustez les poids en conséquence. Pour ajuster les poids, vous utiliserez le Descente graduelle et rétropropagation algorithmes. La descente de gradient est appliquée pour trouver la direction et la vitesse pour mettre à jour les paramètres.

Avant d'apporter des modifications au réseau, vous devez calculer l'erreur. C’est ce que vous ferez dans la section suivante.

Calcul de l'erreur de prédiction

Pour comprendre l'ampleur de l'erreur, vous devez choisir un moyen de la mesurer. La fonction utilisée pour mesurer l'erreur s'appelle le fonction de coût, ou fonction de perte. Dans ce didacticiel, vous utiliserez l'erreur quadratique moyenne (MSE) comme fonction de coût. Vous calculez le MSE en deux étapes:

  1. Calculez la différence entre la prédiction et la cible.
  2. Multipliez le résultat par lui-même.

Le réseau peut commettre une erreur en générant une valeur supérieure ou inférieure à la valeur correcte. Puisque le MSE est le au carré différence entre la prédiction et le résultat correct, avec cette métrique, vous obtiendrez toujours une valeur positive.

Voici l'expression complète pour calculer l'erreur pour la dernière prédiction précédente:

>>>

Dans [24]: cibler = 0

Dans [25]: mse = np.carré(prédiction - cibler)

Dans [26]: impression(F"Prédiction: prédiction; Erreur: mse")
En dehors[26]: Prédiction: [0.87101915]; Erreur: [0.7586743596667225]

Dans l'exemple ci-dessus, l'erreur est 0,75. Une implication de la multiplication de la différence par elle-même est que les erreurs plus importantes ont un impact encore plus grand, et les erreurs plus petites continuent de diminuer à mesure qu'elles diminuent.

Comprendre comment réduire l'erreur

L'objectif est de modifier les pondérations et les variables de biais afin de réduire l'erreur. Pour comprendre comment cela fonctionne, vous ne modifiez que la variable de pondération et laissez le biais corrigé pour le moment. Vous pouvez également vous débarrasser de la fonction sigmoïde et n'utiliser que le résultat de layer_1. Il ne vous reste plus qu'à déterminer comment vous pouvez modifier les pondérations afin que l'erreur disparaisse.

Vous calculez le MSE en faisant error = np.square (prédiction - cible). Si vous traitez (prédiction - cible) comme une seule variable X, alors tu as erreur = np.square (x), qui est une fonction quadratique. Voici à quoi ressemble la fonction si vous la tracez:

Un tracé d'une fonction quadratique avec deux points
Tracé d'une fonction quadratique

L'erreur est donnée par l'axe des y. Si vous êtes au point UNE et que vous voulez réduire l'erreur vers 0, alors vous devez amener le X valeur vers le bas. D'un autre côté, si vous êtes au point B et que vous voulez réduire l'erreur, alors vous devez apporter le X valoriser. Pour savoir dans quelle direction vous devez aller pour réduire l'erreur, utilisez le dérivé. Un dérivé explique exactement comment un modèle va changer.

Un autre mot pour le dérivé est pente. Descente graduelle est le nom de l'algorithme utilisé pour trouver la direction et la vitesse de mise à jour des paramètres du réseau.

Dans ce didacticiel, vous ne vous concentrerez pas sur la théorie des dérivés, vous appliquerez donc simplement les règles de dérivation pour chaque fonction que vous rencontrerez. La règle de puissance stipule que le dérivé de xⁿ est nx⁻¹⁾. Donc, le dérivé de np.square (x) est 2 * x, et le dérivé de X est 1.

N'oubliez pas que l'expression d'erreur est error = np.square (prédiction - cible). Lorsque vous traitez (prédiction - cible) comme une seule variable X, la dérivée de l'erreur est 2 * x. En prenant la dérivée de cette fonction, vous voulez savoir dans quelle direction faut-il changer X apporter le résultat de Erreur à zéro, réduisant ainsi l'erreur.

En ce qui concerne votre réseau de neurones, le dérivé vous indiquera la direction à prendre pour mettre à jour la variable de pondération. S'il s'agit d'un nombre positif, alors vous avez prédit trop haut et vous devez réduire les pondérations. S'il s'agit d'un nombre négatif, cela signifie que vous avez prédit trop bas et vous devez augmenter les pondérations.

Il est maintenant temps d'écrire le code pour savoir comment mettre à jour poids_1 pour la mauvaise prédiction précédente. Si l'erreur quadratique moyenne est 0,75, alors devriez-vous augmenter ou diminuer les poids? Puisque le dérivé est 2 * x, il vous suffit de multiplier la différence entre la prédiction et la cible par 2:

>>>

Dans [27]: dérivé = 2 * (prédiction - cibler)

Dans [28]: impression(F"Le dérivé est dérivé")
En dehors[28]: Le dérivé est: [1.7420383]

Le résultat est 1,74, un nombre positif, vous devez donc diminuer les poids. Vous faites cela en soustrayant le résultat dérivé du vecteur de poids. Maintenant vous pouvez mettre à jour poids_1 en conséquence et prédisez à nouveau pour voir comment cela affecte le résultat de la prédiction:

>>>

Dans [29]: # Mise à jour des poids
Dans [30]: poids_1 = poids_1 - dérivé

Dans [31]: prédiction = Faire des prédictions(input_vector, poids_1, biais)

Dans [32]: Erreur = (prédiction - cibler) ** 2

Dans [33]: impression(F"Prédiction: prédiction; Erreur: Erreur")
En dehors[33]: Prédiction: [0.01496248]; Erreur: [0.00022388]

L'erreur est tombée à presque 0! Beau, non? Dans cet exemple, le résultat dérivé était petit, mais il existe certains cas où le résultat dérivé est trop élevé. Prenons l'image de la fonction quadratique comme exemple. Les incréments élevés ne sont pas idéaux, car vous pouvez continuer à partir du point UNE droit au but B, ne jamais se rapprocher de zéro. Pour faire face à cela, vous mettez à jour les pondérations avec une fraction du résultat dérivé.

Pour définir une fraction de mise à jour des poids, vous utilisez le alpha paramètre, également appelé taux d'apprentissage. Si vous diminuez le taux d'apprentissage, les incréments sont plus petits. Si vous l'augmentez, les étapes sont plus élevées. Comment savoir quelle est la meilleure valeur du taux d’apprentissage? En faisant une supposition et en l'expérimentant.

Si vous prenez les nouveaux poids et faites une prédiction avec le premier vecteur d'entrée, vous verrez que maintenant, il fait une mauvaise prédiction pour celui-là. Si votre réseau de neurones fait une prédiction correcte pour chaque instance de votre ensemble d'entraînement, vous avez probablement un modèle sur-ajusté, où le modèle se souvient simplement comment classer les exemples au lieu d'apprendre à remarquer les caractéristiques des données.

Il existe des techniques pour éviter cela, notamment la régularisation de la descente du gradient stochastique. Dans ce didacticiel, vous utiliserez la descente de dégradé stochastique en ligne.

Maintenant que vous savez comment calculer l'erreur et comment ajuster les pondérations en conséquence, il est temps de revenir, continuez à construire votre réseau neuronal.

Application de la règle de la chaîne

Dans votre réseau neuronal, vous devez mettre à jour les deux poids et les vecteurs de biais. La fonction que vous utilisez pour mesurer l'erreur dépend de deux variables indépendantes, les pondérations et le biais. Étant donné que les pondérations et le biais sont des variables indépendantes, vous pouvez les modifier et les ajuster pour obtenir le résultat souhaité.

Le réseau que vous construisez comporte deux couches, et comme chaque couche a ses propres fonctions, vous avez affaire à une composition de fonctions. Cela signifie que la fonction d'erreur est toujours np.square (x), mais maintenant X est le résultat d'une autre fonction.

Pour reformuler le problème, vous voulez maintenant savoir comment changer poids_1 et biais pour réduire l'erreur. Vous avez déjà vu que vous pouvez utiliser des dérivés pour cela, mais au lieu d'une fonction avec seulement une somme à l'intérieur, vous avez maintenant une fonction qui produit son résultat en utilisant d'autres fonctions.

Puisque maintenant vous avez cette composition de fonction, pour prendre la dérivée de l'erreur concernant les paramètres, vous devrez utiliser la règle de chaîne de calcul. Avec la règle de chaîne, vous prenez les dérivées partielles de chaque fonction, vous les évaluez et multipliez toutes les dérivées partielles pour obtenir la dérivée souhaitée.

Vous pouvez maintenant commencer à mettre à jour les poids. Vous voulez savoir comment modifier les poids pour diminuer l'erreur. Cela implique que vous devez calculer la dérivée de l'erreur par rapport aux poids. Puisque l'erreur est calculée en combinant différentes fonctions, vous devez prendre les dérivées partielles de ces fonctions.

Voici une représentation visuelle de la façon dont vous appliquez la règle de chaîne pour trouver le dérivé de l'erreur par rapport aux pondérations:

Un diagramme montrant les dérivées partielles à l'intérieur d'un réseau de neurones
Un diagramme montrant les dérivées partielles à l'intérieur du réseau de neurones

La flèche rouge en gras indique le dérivé souhaité, derror_dweights. Vous partirez de l’hexagone rouge, en prenant le chemin inverse pour faire une prédiction et calculer les dérivées partielles de chaque fonction.

Dans l'image ci-dessus, chaque fonction est représentée par les hexagones jaunes et les dérivées partielles sont représentées par les flèches grises à gauche. En appliquant la règle de la chaîne, la valeur de derror_dweights sera le suivant:

derror_dweights = (
    derror_dprediction * dprediction_dlayer1 * dlayer1_dweights
)

Pour calculer la dérivée, vous multipliez toutes les dérivées partielles qui suivent le chemin de l'hexagone d'erreur (le rouge) à l'hexagone où vous trouvez les poids (le vert le plus à gauche).

Vous pouvez dire que le dérivé de y = f (x) est le dérivé de F par rapport à X. En utilisant cette nomenclature, pour derror_dprediction, vous voulez connaître le dérivé de la fonction qui calcule l'erreur par rapport à la valeur de prédiction.

Ce chemin inverse est appelé un passe en arrière. À chaque retour en arrière, vous calculez les dérivées partielles de chaque fonction, remplacez les variables par leurs valeurs et enfin multipliez tout.

Cette partie "prendre les dérivées partielles, évaluer et multiplier" est la façon dont vous appliquez le règle de la chaîne. Cet algorithme de mise à jour des paramètres du réseau neuronal est appelé rétropropagation.

Ajustement des paramètres avec la rétropropagation

Dans cette section, vous découvrirez pas à pas le processus de rétropropagation, en commençant par la mise à jour du biais. Vous voulez prendre la dérivée de la fonction d'erreur par rapport au biais, derror_dbias. Ensuite, vous continuerez à reculer, en prenant les dérivées partielles jusqu'à ce que vous trouviez le biais variable.

Puisque vous partez de la fin et reculez, vous devez d'abord prendre la dérivée partielle de l'erreur par rapport à la prédiction. C'est le derror_dprediction dans l'image ci-dessous:

Un diagramme montrant les dérivées partielles pour calculer comment changer le biais
Un diagramme montrant les dérivées partielles pour calculer le gradient de biais

La fonction qui produit l'erreur est une fonction carrée, et la dérivée de cette fonction est 2 * x, comme vous l'avez vu plus tôt. Vous avez appliqué la première dérivée partielle (derror_dprediction) et que vous n'avez toujours pas atteint le biais, vous devez donc prendre un autre pas en arrière et prendre la dérivée de la prédiction par rapport à la couche précédente, dprediction_dlayer1.

La prédiction est le résultat de la fonction sigmoïde. Vous pouvez prendre la dérivée de la fonction sigmoïde en multipliant sigmoïde (x) et 1 - sigmoïde (x). Cette formule dérivée est très pratique car vous pouvez utiliser le résultat sigmoïde qui a déjà été calculé pour en calculer le dérivé. Vous prenez ensuite cette dérivée partielle et continuez à reculer.

Vous allez maintenant prendre le dérivé de layer_1 en ce qui concerne le biais. Voilà, vous y êtes enfin arrivé! Le biais variable est une variable indépendante, donc le résultat après application de la règle de puissance est 1. Cool, maintenant que vous avez terminé cette passe en arrière, vous pouvez tout rassembler et calculer derror_dbias:

>>>

Dans [36]: def sigmoid_deriv(X):
            ...:     revenir sigmoïde(X) * (1-sigmoïde(X))

Dans [37]: derror_dprediction = 2 * (prédiction - cibler)
Dans [38]: layer_1 = np.point(input_vector, poids_1) + biais
Dans [39]: dprediction_dlayer1 = sigmoid_deriv(layer_1)
Dans [40]: dlayer1_dbias = 1

Dans [41]: derror_dbias = (
            ...:     derror_dprediction * dprediction_dlayer1 * dlayer1_dbias
            ...: )

Pour mettre à jour les poids, vous suivez le même processus, en reculant et en prenant les dérivées partielles jusqu'à ce que vous arriviez à la variable des poids. Puisque vous avez déjà calculé certaines des dérivées partielles, il vous suffira de calculer dlayer1_dweights. Le dérivé du produit scalaire est le dérivé du premier vecteur multiplié par le second vecteur, plus le dérivé du second vecteur multiplié par le premier vecteur.

Création de la classe de réseau neuronal

Vous savez maintenant comment écrire les expressions pour mettre à jour à la fois les pondérations et le biais. Il est temps de créer un classer for the neural network. Classes are the main building blocks of object-oriented programming (OOP). Le NeuralNetwork class generates random start values for the weights and bias variables.

When instantiating a NeuralNetwork object, you need to pass the learning_rate parameter. You’ll use predict() to make a prediction. The methods _compute_derivatives() et _update_parameters() have the computations you learned in this section. This is the final NeuralNetwork class:

classer NeuralNetwork:
    def __init__(soi, learning_rate):
        soi.weights = np.array([[[[np.random.randn(), np.random.randn()])
        soi.bias = np.random.randn()
        soi.learning_rate = learning_rate

    def _sigmoid(soi, X):
        revenir 1 / (1 + np.exp(-X))

    def _sigmoid_deriv(soi, X):
        revenir soi._sigmoid(X) * (1 - soi._sigmoid(X))

    def prédire(soi, input_vector):
        layer_1 = np.dot(input_vector, soi.weights) + soi.bias
        layer_2 = soi._sigmoid(layer_1)
        prediction = layer_2
        revenir prediction

    def _compute_gradients(soi, input_vector, cibler):
        layer_1 = np.dot(input_vector, soi.weights) + soi.bias
        layer_2 = soi._sigmoid(layer_1)
        prediction = layer_2

        derror_dprediction = 2 * (prediction - cibler)
        dprediction_dlayer1 = soi._sigmoid_deriv(layer_1)
        dlayer1_dbias = 1
        dlayer1_dweights = (0 * soi.weights) + (1 * input_vector)

        derror_dbias = (
            derror_dprediction * dprediction_dlayer1 * dlayer1_dbias
        )
        derror_dweights = (
            derror_dprediction * dprediction_dlayer1 * dlayer1_dweights
        )

        revenir derror_dbias, derror_dweights

    def _update_parameters(soi, derror_dbias, derror_dweights):
        soi.bias = soi.bias - (derror_dbias * soi.learning_rate)
        soi.weights = soi.weights - (
            derror_dweights * soi.learning_rate
        )

There you have it: That’s the code of your first neural network. Toutes nos félicitations! This code just puts together all the pieces you’ve seen so far. If you want to make a prediction, first you create an instance of NeuralNetwork(), and then you call .predict():

>>>

Dans [42]: learning_rate = 0.1

Dans [43]: neural_network = NeuralNetwork(learning_rate)

Dans [44]: neural_network.prédire(input_vector)
En dehors[44]: array([0.79412963])

The above code makes a prediction, but now you need to learn how to train the network. The goal is to make the network generalize over the training dataset. This means that you want it to adapt to new, unseen data that follow the same probability distribution as the training dataset. That’s what you’ll do in the next section.

Training the Network With More Data

You’ve already adjusted the weights and the bias for one data instance, but the goal is to make the network generalize over an entire dataset. Stochastic gradient descent is a technique in which, at every iteration, the model makes a prediction based on a randomly selected piece of training data, calculates the error, and updates the parameters.

Now it’s time to create the train() method of your NeuralNetwork class. You’ll save the error over all data points every 100 iterations because you want to plot a chart showing how this metric changes as the number of iterations increases. This is the final train() method of your neural network:

    1classer NeuralNetwork:
    2    # ...
    3
    4    def former(soi, input_vectors, targets, iterations):
    5        cumulative_errors = []
    6        pour current_iteration dans gamme(iterations):
    7            # Pick a data instance at random
    8            random_data_index = np.random.randint(len(input_vectors))
    9
dix            input_vector = input_vectors[[[[random_data_index]
11            cibler = targets[[[[random_data_index]
12
13            # Compute the gradients and update the weights
14            derror_dbias, derror_dweights = soi._compute_gradients(
15                input_vector, cibler
16            )
17
18            soi._update_parameters(derror_dbias, derror_dweights)
19
20            # Measure the cumulative error for all the instances
21            si current_iteration % 100 == 0:
22                cumulative_error = 0
23                # Loop through all the instances to measure the error
24                pour data_instance_index dans gamme(len(input_vectors)):
25                    data_point = input_vectors[[[[data_instance_index]
26                    cibler = targets[[[[data_instance_index]
27
28                    prediction = soi.prédire(data_point)
29                    Erreur = np.carré(prediction - cibler)
30
31                    cumulative_error = cumulative_error + Erreur
32                cumulative_errors.append(cumulative_error)
33
34        revenir cumulative_errors

There’s a lot going on in the above code block, so here’s a line-by-line breakdown:

  • Line 8 picks a random instance from the dataset.

  • Lines 14 to 16 calculate the partial derivatives and return the derivatives for the bias and the weights. They use _compute_gradients(), which you defined earlier.

  • Line 18 updates the bias and the weights using _update_parameters(), which you defined in the previous code block.

  • Ligne 21 checks if the current iteration index is a multiple of 100. You do this to observe how the error changes every 100 iterations.

  • Line 24 starts the loop that goes through all the data instances.

  • Line 28 computes the prediction result.

  • Line 29 computes the Erreur for every instance.

  • Line 31 is where you accumulate the sum of the errors using the cumulative_error variable. You do this because you want to plot a point with the error for all the data instances. Then, on line 32, you append the Erreur à cumulative_errors, the array that stores the errors. You’ll use this array to plot the graph.

In short, you pick a random instance from the dataset, compute the gradients, and update the weights and the bias. You also compute the cumulative error every 100 iterations and save those results in an array. You’ll plot this array to visualize how the error changes during the training process.

To keep things less complicated, you’ll use a dataset with just eight instances, the input_vectors array. Now you can call train() and use Matplotlib to plot the cumulative error for each iteration:

>>>

Dans [45]: # Paste the NeuralNetwork class code here
            ...: # (and don't forget to add the train method to the class)

Dans [46]: importer matplotlib.pyplot comme plt

Dans [47]: input_vectors = np.array(
            ...:     [[[[
            ...:         [[[[3, 1.5],
            ...:         [[[[2, 1],
            ...:         [[[[4, 1.5],
            ...:         [[[[3, 4],
            ...:         [[[[3.5, 0.5],
            ...:         [[[[2, 0.5],
            ...:         [[[[5.5, 1],
            ...:         [[[[1, 1],
            ...:     ]
            ...: )

Dans [48]: targets = np.array([[[[0, 1, 0, 1, 0, 1, 1, 0])

Dans [49]: learning_rate = 0.1

Dans [50]: neural_network = NeuralNetwork(learning_rate)

Dans [51]: training_error = neural_network.former(input_vectors, targets, 10000)

Dans [52]: plt.plot(training_error)
Dans [53]: plt.xlabel("Iterations")
Dans [54]: plt.ylabel("Error for all training instances")
Dans [54]: plt.savefig("cumulative_error.png")

You instantiate the NeuralNetwork class again and call train() en utilisant le input_vectors et le cibler valeurs. You specify that it should run 10000 fois. This is the graph showing the error for an instance of a neural network:

Line graph showing the cumulative neural network error decreasing
Graph showing the cumulative training error

The overall error is decreasing, which is what you want. The image is generated in the same directory where you’re running IPython. After the largest decrease, the error keeps going up and down quickly from one interaction to another. That’s because the dataset is random and very small, so it’s hard for the neural network to extract any features.

But it’s not a good idea to evaluate the performance using this metric because you’re evaluating it using data instances that the network already saw. This can lead to overfitting, when the model fits the training dataset so well that it doesn’t generalize to new data.

Adding More Layers to the Neural Network

The dataset in this tutorial was kept small for learning purposes. Usually, deep learning models need a large amount of data because the datasets are more complex and have a lot of nuances.

Since these datasets have more complex information, using only one or two layers isn’t enough. That’s why deep learning models are called “deep.” They usually have a large number of layers.

By adding more layers and using activation functions, you increase the network’s expressive power and can make very high-level predictions. An example of these types of predictions is face recognition, such as when you take a photo of your face with your phone, and the phone unlocks if it recognizes the image as you.

Conclusion

Toutes nos félicitations! Today, you built a neural network from scratch using NumPy. With this knowledge, you’re ready to dive deeper into the world of artificial intelligence in Python.

In this tutorial, you learned:

  • What deep learning is and what differentiates it from apprentissage automatique
  • How to represent vecteurs with NumPy
  • What activation functions are and why they’re used inside a neural network
  • What the backpropagation algorithm is and how it works
  • How to train a neural network and make predictions

The process of training a neural network mainly consists of applying operations to vectors. Today, you did it from scratch using only NumPy as a dependency. This isn’t recommended in a production setting because the whole process can be unproductive and error-prone. That’s one of the reasons why deep learning frameworks like Keras, PyTorch, and TensorFlow are so popular.

Further Reading

For additional information on topics covered in this tutorial, check out these resources:

[ad_2]