-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathchapitre1.tex
382 lines (297 loc) · 28.5 KB
/
chapitre1.tex
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
%!TEX root = main.tex
% \begin{figure}[htb]
% \begin{center}
% %optional pour enlever un peu d'espace blanc de votre silhouette
% %\vspace{-.3cm}
% %\includegraphics[keepaspectratio,width=0.5\textwidth]{fig/nomde}
% % analoog
% %\vspace{-0.6cm}
% % \caption{ici un logo}
% % \label{fig:ruglogo}
% %analoog
% %\vspace{-.6cm}
% \end{center}
% \end{figure}
% Utilisation du template
% \cite(reference à citer)
% \ref(figure à référencer)
\chapter{Introduction aux réseaux de neurones et premières applications}
Le première partie de ce projet a pour but de comprendre le fonctionnement des réseaux de neurones et leurs applications à la classification. Une structure informatique en python pour les utiliser sera également mise en place.
\section{Outils utilisés pour le projet}
Ce projet possède une dimension de conception logicielle. Il s'agit de programmer des réseaux de neurones efficaces et performants adaptés aux problèmes que nous souhaitons résoudre sans utiliser de bibliothèques existantes.\\
Étant donné l'évolution prévue de notre code (du perceptron simple pour les problèmes de XOR ou du MNIST, puis la mise en place du GAN, et enfin toute sortes d'améliorations utiles), nous devons être particulièrement vigilants sur la souplesse de notre code. La programmation en équipe, sur une longue durée et avec de telles contraintes, nécessite la mise en place d'outils et certains choix techniques.
\section{Réseaux de neurones et fonctionnement}
Les réseaux de neurones font partie des piliers de l'intelligence artificielle. Leur fonctionnement est basé sur une interprétation sommaire du cerveau humain. Des neurones seuls reçoivent des signaux, les traitent et renvoient un signal de sortie. Les neurones sont alors agrégés en un réseau avec des entrées et des sorties globales. On modélise la plasticité du cerveaux par des paramètres variables qui changent au cours de l'apprentissage. Celui-ci se fait en comparant les sorties de notre réseau aux résultats attendus.
Les réseaux ainsi obtenus et entrainés sont donc des fonctions. L'apprentissage permet aux réseaux d'approcher les fonctions que nous souhaitons. L'objectif est d'approcher des fonctions qui ne sont pas facilement réalisables avec les moyens usuels. Par exemple, il est très difficile de définir la fonction indicatrice des chiffres manuscrits alors qu'un réseau neuronal est capable de le faire.
\subsection{Le neurone} % (fold)
\label{sub:le_neurone}
L’unité de base du réseau est le neurone, on peut l’imaginer comme une fonction mathématique $F$. Il possède $n$ entrées (ou plutôt un vecteur $X$ de dimension $n$), chacune affectée d’un poids $w_i$ (on a donc un vecteur de poids $w$, et une fonction mathématique de $\R$ dans $\R$ non linéaire dite fonction d'activation $\sigma$. Le rôle du neurone sera de renvoyer le résultat de la fonction d'activation appliquée à la somme des entrées pondérées par leurs poids respectifs : on a $F(x) = \sigma(w^T x)$. On peut ajouter un biais $b$ comme paramètre de notre neurone, ce qui permet de donner un aspect affine au calcul : $F(x) = \sigma(w^T x + b)$
\begin{figure}[h]
\centerline{\includegraphics[width=0.6\linewidth]{fig/schemaneurone.jpg}}
\caption{Schéma d'un neurone}
\label{fig:neurone}
\end{figure}
Un exemple simple du réseau de neurones est la séparation d’un plan en deux.\\
Imaginons un neurone à deux entrées $e_1$ et $e_2$, chacune attribuée d’un poids $w_1$ et $w_2$. On affecte au neurone un biais $b$ et une fonction d’activation seuil (Heavyside par exemple).\\
Notre neurone renverra : 1 si $(e_1w_1+e_2w_2) - b >0$ et 0 sinon.
Si les $e_1$ et $e_2$ représentent les abscisses et ordonnées d’un point du plan, on reconnaît dans l’argument de la fonction d’activation l’équation d’une droite affine. Notre neurone pourra donc distinguer les points du plan selon le côté de la droite où ils se trouvent.
On peut déjà voir qu’une modification des poids entraînera une délimitation différente du plan. On peut donc imaginer faire « apprendre » au réseau une délimitation choisi en modifiant ses poids. Nous reviendrons sur ce concept par la suite.\\
Cependant, les applications d’un neurone seul sont vite limitées. C’est pourquoi on va s’intéresser à en connecter plusieurs entre eux.
\subsection{Réseau de neurones et perceptron} % (fold)
\label{sub:reseau_de_neurones}
On a déjà vu que le neurone se prêtait bien à une séparation binaire des données. On va voir que l’organisation de neurones en réseau permet de meilleures classifications.\\
L’organisation du réseau se fera au moyen de couches de neurones. Une couche est un ensemble de neurones possédant les mêmes entrées. Cette couche aura alors plus d'une sortie (une par neurone dans la couche), ce qui permet de généraliser aux sorties vectorielles le concept du neurone seul. En ayant ainsi une sortie vectorielle, on peut utiliser les sorties d'une couche en tant qu'entrée d'une autre couche, ce qui complexifie encore les fonctions possiblement décrites par les neurones.
On peut imaginer des réseaux plus complexes où la sortie n'est pas réutilisée dans la couche suivante mais plusieurs couches plus loin ou dans des couches antérieures. On peut en réalité construire le graphe de neurones que l'on veut, mais, dans la pratique, seules certaines structures sont utilisés.
\begin{figure}[ht]
\centerline{\includegraphics[width=0.5\linewidth]{fig/schemacouche.jpg}}
\caption{Exemple d'une couche de neurones}
\label{fig:couche}
\end{figure}
Le perceptron est un modèle de réseau de neurones auquel on va s’intéresser particulièrement. Il s'agit d'un réseau linéaire où chaque couche est entièrement connectée à la suivante, c'est-à-dire que chaque neurone d'une couche prend en entrée toutes les sorties de la couche précédente. On ne trouve aucune boucle dans le graphe d'un perceptron, c'est donc une propagation vers l'avant.
L’utilité d’avoir plusieurs couches se comprend facilement. Si on reprend notre exemple du problème de classification des points, on peut imaginer, par exemple, quatre neurones qui enverront leur sortie sur un neurone à quatre entrées. Chacun des neurones réalisera la séparation du plan en deux selon le principe déjà évoqué précédemment. Le neurone de la couche de sortie pourra réaliser facilement le rôle d’un ET logique. On vient de sélectionner un carré dans le plan. En étendant le raisonnement, on voit qu’un réseau à deux couches permet de sélectionner n’importe quelle zone convexe de l’espace des entrées (ici du plan). De même, un réseau à trois couches pourra sélectionner n’importe quelle zone concave de l’espace des entrées.
\begin{figure}[ht]
\centerline{\includegraphics[width=0.5\linewidth]{fig/espace1neurone.jpg}}
\caption{Frontière de décision pour un perceptron simple à 1 neurone et 2 entrées}
\label{fig:espace1neurone}
\end{figure}
On appelle la dernière couche, celle qui donne le résultat du réseau de neurones, la couche de sortie; et la première couche où l'on donne les entrées est appelée couche d'entrée. Les autres couches sont appelées des couches cachées. Cette appellation vient du fait qu’a priori, nous n’avons aucun moyen de voir ou de corriger les comportements des neurones cachés. En effet, avec la seule donnée de la sortie, l’influence des poids des couches cachées sur celle-ci n'est pas évidente. C'est ce que nous allons étudier dans la partie suivante.
% subsection reseau_de_neurones (end)
% subsection le_neurone (end)
\subsection{Apprentissage par rétro-propagation}
Il existe plusieurs types d'apprentissages du réseau. Les deux grandes catégories sont l'apprentissage supervisé et l'apprentissage non supervisé.
Un apprentissage supervisé nécessite une base d'apprentissage à enseigner au réseau. Elle est composée d'associations entre entrées et sorties voulues. Le réseau déduira de cette base les autres cas qu'on ne lui aura pas appris. Une apprentissage non supervisé ne nécessite pas une telle base d'apprentissage.
Dans le cadre du perceptron, nous utilisons un apprentissage supervisé, la rétro-propagation des erreurs. Il s'agit en fait d'effectuer une descente de gradient. Nous avons notre réseau qui représente $F$ une fonction, cette fonction dépend de nombreux paramètres qui sont les poids $W$ de tous les neurones de toutes les couches (ainsi que les biais). Lorsque l'on veut faire tendre $F$ vers une fonction $G$, et que l'on peut avoir une distance entre ces deux fonctions, on obtient alors un problème de minimisation, avec pour paramètres d'optimisation les poids $W$. L'algorithme de descente de gradient consiste à calculer pour chaque paramètre le gradient par rapport à la sortie (c'est une descente de Newton quand on est à une dimension) afin de déplacer ce paramètre dans la bonne direction pour minimiser la sortie.
Comme dit précédemment, on n'a pas facilement accès aux couches cachées, et il parait très coûteux d'opter pour des calculs de dérivées discrètes pour obtenir les gradients voulus pour tous les paramètres.
La rétro-propagation consiste à calculer l’influence de chaque paramètre sur la sortie et à les mettre à jour en fonction de cette influence. On a vu que les couches de neurones avaient leurs propres sorties, on peut donc calculer l'influence des poids d'une couche sur sa sortie propre, puis s'en servir pour calculer l'influence sur les entrées, qui sont la sortie d'une couche précédente, on procède comme suit :
Les paramètres que l’on fait évoluer sont les poids et les biais.
La formule de mise à jour est la suivante :
\[
W(t+1) = W(t) + \eta \frac{\partial E}{\partial W}
\]
avec $\eta$ le pas de convergence, $\frac{\partial E}{\partial W} $ la matrice de terme général $\frac{\partial E}{\partial W_{i,j}} $.\\
Pour pouvoir mettre à jour les poids, il faut donc calculer les $\frac{\partial E}{\partial W_{i,j}} $.\\
A la couche $k$ l'influence des poids est donnée par :
\[
\frac{\partial E^p}{\partial W _k} = \frac{\partial F}{\partial W}(W_k, X_{k-1})\frac{\partial E^p}{\partial X_k}
\]
Avec $\frac{\partial F}{\partial W}(W_k, X_{k-1})$ la matrice jacobienne de F par rapport à la variable $W_k$.
Pour pouvoir calculer l'influence des poids de toutes les couches, il faut donc calculer $\frac{\partial E^p}{\partial W_k}$
On peut calculer par récurrence cette valeur pour toutes les couches.
\[
\frac{\partial E^p}{\partial X _{k-1}} = \frac{\partial F}{\partial X}(W_k, X_{k-1})\frac{\partial E^p}{\partial X_k}
\]
Avec $\frac{\partial F}{\partial X }(W_k, X_{k-1})$ la matrice jacobienne de $F$ par rapport à la variable $X_k$. De plus, dans un perceptron, on peut noter la sortie de la couche $k$ :
\[
Y_k = W_k X_k \]
\[
X_k = F(Y_k)
\]
On obtient donc ces 3 équations :
\begin{align*}
\frac{\partial E^p}{\partial y_k^i} &= f'(x_k^i)\frac{\partial E^p}{\partial x_k^i} \\
\frac{\partial E^p}{\partial w_k^{i,j}}&= x^j_{k-1} \frac{\partial E^p}{\partial y_k^i}\\
\frac{\partial E^p}{\partial x_{k-1}^m} &= \sum_i(w_k^{im}\frac{\partial E^p}{\partial X_k})
\end{align*}
En forme matricielle, ces équations donnent :
\begin{align*}
\frac{\partial E^p}{\partial Y_k} &= Diag(f'(x_k^i))\frac{\partial E^p}{\partial x_k^i} \\
\frac{\partial E^p}{\partial W_k}&= X^T_{k-1} \frac{\partial E^p}{\partial Y_k}\\
\frac{\partial E^p}{\partial X_{k-1}} &= W_k^T\frac{\partial E^p}{\partial X_k}
\end{align*}
On obtient donc une formule de récurrence que l'on doit propager de la dernière couche à la première couche (d'où le terme rétro-propagation). On sait donc maintenant mathématiquement faire l'apprentissage d'un perceptron : il nous faut des données connues, une fonction d'erreur (appelée Loss Function dans la littérature), et appliquer cet algorithme.
\section{Conception logicielle des réseaux de neurones}
\paragraph*{} % (fold)
L'un des objectifs du projet est la conception d'une librairie permettant l'implémentation de réseaux de neurones. Notre démarche est la suivante, nous cherchons à mettre en place la structure la plus simple possible mais également la plus souple possible. Ainsi nous ne cherchons pas l'exhaustivité de notre librairie, mais nous pouvons facilement la compléter dès lors que nous avons besoin de fonctionnalité supplémentaire.
\paragraph*{} % (fold)
Notre code est structuré autour de 3 types de classes, les classes permettant la création et le fonctionnement d'un ou plusieurs réseaux de neurones (ce sont les classes qui font l'intelligence du programme, noté $brain$), les classes apportant des outils de compréhension et de travail sur les réseaux (affichage de résultats, chargement et sauvegarde de paramètres, etc) et les classes permettant de lancer une expérience (classes $main$, instanciant les objets et les expériences).
Le projet est découpé en 2 répertoire Github, le premier correspondant au code le plus simple, fonctionnel sur le problème du XOR, le second correspondant au développement suivant. Ces derniers développement correspondent à la généralisation à tout types de problèmes d'apprentissage de perception simple, puis à la mise en place du GAN et toutes les évolutions que nous avons mis en place.
Vous pouvez retrouver les codes sur https://github.com/Supelec-GAN/Salamandre-XOR.git et sur https://github.com/Supelec-GAN/Salamandre-Code.git .
\subsection{Structure du code} % (fold)
\label{sub:structure_du_code}
Afin de pouvoir implémenter facilement les calculs matriciels obtenus plus tôt, nous définissons notre plus bas niveau d'intelligence par une classe NeuronLayer représentant une couche de neurone. \\
Une couche de neurones est définie par sa matrice de poids $weights$, son vecteur de biais $biais$ ainsi que sa fonction d'activation $activation\_function$, nécessairement commune à tous les neurones dans cette structure.
\paragraph{Présentation des méthodes} % (fold)
\label{par:presentatation_des_methodes}
\begin{itemize}
\item $compute$ : Propagation d'une entrée au sein de cette couche.
\item $backprop$ : Rétro-Propagation de l'influence de l'erreur de la sortie de la couche avec mise à jour des poids et calculs de l'influence de l'erreur sur la sortie.
\item $derivate\_error$ : Calcul intermédiaire dans la rétro-propagation.
\item $init\_derivate\_error$ : Calcul de la dérivée de l'erreur pour la couche de sortie
\end{itemize}
Pour faire nos réseaux de Neurones, nous utilisons une classe Network qui permet de relier les différents NetworkLayer. Ainsi elle possède \emph{self\_layer\_list} qui est une liste de NetworkLayer. Les méthodes principales sont :
\begin{itemize}
\item $compute$ : Propagation depuis l'entrée au sein de toutes les couches.
\item $backprop$ : Rétro-Propagation de l'influence de l'erreur de la sortie du réseau et itération de \emph{backprop} sur chaque NetworkLayer.
\item $reset$ : Remet tout les NetworkLayer à l'état initial.
\end{itemize}
% paragraph presentatation_des_methodes (end)
Afin de simplifier l'utilisation des fonctions au sein de nos réseaux, nous créons une classe Function. Elle comprend les fonctions d'activations, les fonctions d'erreur, mais également des fonctions d'apprentissage i.e. les sorties attendues pour une entrée lors de l'apprentissage (ex : XorTest renvoie le résultat du XOR, MnistTest renvoie le label pour une image labellisée donnée).
Finalement pour utiliser et interpréter des réseaux, nous avons une classe Interface, celle si comporte plusieurs méthodes importantes. $learning\_manager$ permet d'organiser l'apprentissage et la récupération de données. Elle contient également des méthodes pour afficher des résultats.
\begin{figure}[ht!]
\includegraphics[width=\linewidth]{fig/uml_xor}
\caption{Diagramme UML du code minimal et initial}
\label{fig:uml_xor}
\end{figure}
Après évolution du code, on trouvera dans DataProcessor des méthodes pour effectuer des calculs sur les données, tels que la moyenne sur plusieurs séries d'expérience et l'écart-type. La classe DataInterface sert à la sauvegarde des résultats et des paramètres ainsi qu'à leur chargement pour utilisation ultérieure.
Finalement les scripts run.py et tests.py servent à lancer des expériences en utilisant la librairie mise en place.
% subsection structure_du_code (end)
\section{Application au problème du XOR}
\paragraph*{}
Lorsque l'on souhaite travailler sur des algorithmes d'apprentissage par ordinateur, il est recommandé de les essayer sur des problèmes connus afin d'en vérifier les performances. \\
Le problème du XOR est l'un des plus classiques car il apporte de nombreuses difficultés.
L'objectif du XOR est de séparer le plan complexe en quatre cadrants, $(x >0, y > 0)$, $ (x>0, y<0)$, $ (x<0, y>0)$ et $ (x>0, y<0) $. Pour l'expérimentation, on restreint le plan à $[-1;1]^2$. Les sorties attendues par le réseau de neurones sont alors 1 pour les points tel que $x*y > 0 $ et -1 pour les points tels que $x*y<0$. \\
Le premier intérêt de ce problème est qu'il est non linéaire. Cela se traduit par le fait qu'une droite séparant le plan en 2 ne répond pas du tout au problème.
C'est en se basant sur la résolution du XOR que nous avons construit notre structure de réseau et vérifié la cohérence de notre code. La littérature propose comme réseau le plus simple pour ce problème une couche cachée de 2 neurones, avec 2 entrées ($x$ et $y$) et 1 sortie dans $[-1, 1]$. Nous avons étudié également quelques autres formes de réseaux pour comparer les résultats.
\paragraph{Notion de résultats} % (fold)
\label{par:notion_de_resultats}
La notion de résultats nécessite d'être correctement définie afin de pouvoir être interprétée correctement, en particulier pour la comparaison à d'autres résultats obtenus par nous-même ou par d'autres personnes. \\
La structure de perceptron sous cette forme classe les objets que l'on donne en entrée. Généralement, le résultat est défini par rapport à un pourcentage de succès dans cette classification. Pour l'obtenir, on commence par définir une erreur relative, c'est-à-dire une distance entre la sortie cible et la sortie obtenue. Un seuil est alors appliqué afin de définir une sortie booléenne de la classification de l'entrée.\\
Dans le cas du XOR, on met en place un seuil de 0.5, c'est à dire que, si l'erreur est inférieure à 50\%, le réseau a raison. Cela peut s’interpréter comme suit : le réseau donne un résultat qui indique sa confiance dans la sortie. 1 ou 0 si il est certain que la sortie doit être 1 ou 0, 0.5 si il ne peut départager l'un ou l'autre, le seuil consiste à dire que sa réponse est celle en qui il a le plus confiance.\
On cherche également à évaluer la vitesse d'apprentissage. Ainsi, on calcule le pourcentage de succès du réseau à intervalles réguliers au cours de l'apprentissage. Les réseaux étant soumis à une forte composante aléatoire (l'ordre d'apprentissage, ainsi que l'initialisation des poids), on effectue des apprentissages dans les mêmes conditions plusieurs fois afin d'obtenir des courbes moyennes, et des intervalles de confiances justifiant nos résultats.
% paragraph notion_de_résultats (end)
\paragraph{Réseau en $2\rightarrow2\rightarrow1$} % (fold)
Les résultats obtenus au début sur ce réseau extrêmement simple semblaient tout à fait aléatoires et nous ont permis de détecter des erreurs de traduction des équations de rétro-propagation en code Python. Nous avons finalement pu obtenir des résultats satisfaisants, comme le montre la figure \ref{fig:2_2_1}.
\begin{figure}[ht!]
\includegraphics[width=\linewidth]{fig/xor221_eta009.png}
\caption{Application d'un réseau 2-2-1 au plan pour xor}
\label{fig:2_2_1}
\end{figure}
% paragraph réseau_en_2_2_1 (end)
\paragraph{Importance du pas d'apprentissage $\eta$}
L'un des paramètres de la descente de gradient est le pas d'apprentissage $\eta$. Il intervient dans le formule de mise à jour des poids :
\[
W(t+1) = W(t) + \eta \frac{\partial E}{\partial W}
\]
Plus il est petit, plus l'algorithme avancera lentement. Cependant, s'il est trop grand, l'algorithme ne se rapprochera pas du minimum mais divergera. Il faut donc le choisir suffisamment petit pour que cela converge mais suffisamment grand pour que ce ne soit pas trop lent. La figure \ref{fig:2_2_2_1} montre en 10000 apprentissages le résultat d'un réseau entraîné sur XOR selon le pas d'apprentissage. On remarque notamment que, lors qu'il est trop élevé, le réseau n'arrive pas à découper l'espace.
\begin{figure}[ht!]
\centering
\begin{subfigure}[b]{.3\linewidth}
\includegraphics[width=\linewidth]{fig/xor2221_eta01.png}
\caption{eta =0.1}
\end{subfigure}
\quad
\begin{subfigure}[b]{.3\linewidth}
\includegraphics[width=\linewidth]{fig/xor2221_eta05.png}
\caption{eta =0.5}
\end{subfigure}
\quad
\begin{subfigure}[b]{.3\linewidth}
\includegraphics[width=\linewidth]{fig/xor2221_eta09.png}
\caption{eta =0.9}
\end{subfigure}
\caption{Xor en 2-2-2-1 avec différents pas d'apprentissages au bout de 10000 apprentissages}
\label{fig:2_2_2_1}
\end{figure}
\paragraph{Structure du réseau}
La structure du réseau choisie est également importante. Comme expliqué plus haut, si le réseau est 2-2-1, il ne peut pas délimiter l'espace comme on le souhaite. Cela signifie que, pour la fonction XOR avec laquelle on travaille (à savoir qu'on représente VRAI par 1 et FAUX par -1), il ne peut pas découper le plan selon les axes des abscisses et ordonnées. En revanche, il va essayer de le délimiter de façon à accorder aux points (1,1) et (-1, -1) la valeur VRAIE et (1,-1) et (-1,1) la valeur FAUX. Pour cela, il trace une bande comme sur la figure \ref{fig:struct221}. En effet, il y a priorité sur ces valeurs puisque la base d'apprentissage de notre réseau est constituée de couples de -1 et de 1.
\begin{figure}[ht!]
\centering
\begin{subfigure}[b]{.3\linewidth}
\includegraphics[width=\linewidth]{fig/xor221_eta009.png}
\caption{Structure 2-2-1}
\label{fig:struct221}
\end{subfigure}
\quad
\begin{subfigure}[b]{.3\linewidth}
\includegraphics[width=\linewidth]{fig/xor241_eta016.png}
\caption{Structure 2-4-1}
\label{fig:struct241}
\end{subfigure}
\quad
\begin{subfigure}[b]{.3\linewidth}
\includegraphics[width=\linewidth]{fig/xor2221_eta05.png}
\caption{Structure 2-2-2-1}
\label{fig:struct2221}
\end{subfigure}
\caption{Xor avec différentes structures}
\label{structxor}
\end{figure}
Cependant, lorsqu'on lui accorde plus de neurones, 4 neurones sur la première couche comme sur la figure \ref{fig:struct241}, ou 3 couches par exemple comme sur la figure \ref{fig:struct2221}, le réseau découpe l'espace comme souhaité. En revanche, le XOR avec la structure 2-2-2-1 est assez instable : pour les mêmes paramètres, on peut obtenir un réseau qui converge et un autre qui se plante complètement comme le montre la figure \ref{fig:2_2_2_1_instable}
\begin{figure}[ht!]
\centering
\begin{subfigure}[b]{.4\linewidth}
\includegraphics[width=\linewidth]{fig/xor2221_eta05.png}
\caption{XOR qui converge}
\label{fig:_05}
\end{subfigure}
\quad
\begin{subfigure}[b]{.4\linewidth}
\includegraphics[width=\linewidth]{fig/xor2221_eta05v2.png}
\caption{XOR planté}
\end{subfigure}
\caption{XOR en 2-2-2-1 avec eta =0.5}
\label{fig:2_2_2_1_instable}
\end{figure}
\paragraph{Conclusion sur le XOR} % (fold)
\label{par:conclusion_sur_le_xor}
Pas d'apprentissage très petit par rapport à la littérature\\
Influence des fonctions d'activation
% paragraph conclusion_sur_le_xor (end)
\section{Application à la base de données MNIST}
\subsection*{Description du problème} % (fold)
Pour le problème du MNIST qui consiste à apprendre à reconnaitre des chiffres manuscrits, les données étaient les suivantes :
\begin{itemize}
\item 60000 images pour l’apprentissage, avec leurs étiquettes
\item 10000 images de test
\end{itemize}
Toutes les images ont une dimension de 28*28 pixels en noir et blanc. Ces sets d’images sont récupérables sur le site http://yann.lecun.com/exdb/mnist/ sous le format IDX. L’extraction de ce format vers une liste python est faite grâce au module python-mnist.
\subsection*{Paramètres généraux utilisés :} % (fold)
Les fonctions d’activation utilisées pour toutes les expériences ici sont des sigmoïdes de paramètre $\mu$ :
$\sigma_\mu(x) = \frac{1}{1+e^{-\mu x}}$\\
On pourra étudier l’influence de $\mu$ sur la vitesse de convergence.
Puisque les fonctions d’activation utilisées sont des sigmoïdes dont la sortie est dans [0, 1], les valeurs d’entrées situées entre 0 et 255 sont normalisées entre 0 et 1.\\
L’erreur utilisée sur la couche de sortie est l’erreur quadratique.\\
Les poids sont initialisés avec une répartition gaussienne centrée réduite. Les biais sont initialisés à 0.\\
De bons résultats ont été obtenus avec le réseau suivant, conformément à la littérature :
\begin{itemize}
\item Eta 0.2
\item Sigmoïde 0.1
\item Réseau à une couche cachée de 300 neurones et 10 neurones de sortie (784-300-10)
\item Apprentissage stochastique
\end{itemize}
Avec ce type de réseau, on obtient rapidement des taux de succès proche de 95\% après 10 passes de l’ensemble du set d’apprentissages, et un écart-type final de 0.001223411. Nous allons maintenant voir l’influence des différents paramètres.
\begin{figure}[ht!]
\includegraphics[width=\linewidth]{fig/MNIST_result_1.png}
\caption{Courbe de réussite du réseau sur MNIST}
\label{fig:mnist_result_1}
\end{figure}
\subsubsection*{Variation d’êta :}
Sur le réseau 300-10 précédent, une augmentation du êta de 0.2 à 10 ne semble qu’améliorer la vitesse de convergence comme le montre la figure \ref{fig:mnist_influence_eta}.
L’écart-type n’augmente pas et on obtient une meilleure précision à la fin.
\begin{figure}[ht!]
\includegraphics[width=\linewidth]{fig/MNIST_influence_eta.png}
\caption{Courbe de réussite du réseau sur MNIST avec différents $\eta$}
\label{fig:mnist_influence_eta}
\end{figure}
\subsubsection*{Choix du paramètre de la sigmoïde :}
Ici, l’influence du choix de la sigmoïde est observée. Les tests ont été effectués avec $\mu = 0.1$ et $\mu = 0.5$ comme paramètre de sigmoïdes.
[[insert courbe (cf 17/01/18)]]
On remarque qu’en tout point, choisir 0.1 en paramètre à la place de 0.5 est mieux : vitesse de convergence, précision finale, écart-type. Ce résultat empire avec un êta plus élevé, au point de ne plus réussir à apprendre. On restera donc sur une sigmoïde de paramètre 0.1 pour la suite des expériences.
\begin{figure}[ht!]
\centering
\begin{subfigure}[b]{.5\linewidth}
\includegraphics[width=\linewidth]{fig/MNIST_inflsigm_eta02.png}
\caption{$\eta=0.2$}
\end{subfigure}
\quad
\begin{subfigure}[b]{.5\linewidth}
\includegraphics[width=\linewidth]{fig/MNIST_inflsigm_eta07.png}
\caption{$\eta=0.7$}
\end{subfigure}
\begin{subfigure}[b]{.5\linewidth}
\includegraphics[width=\linewidth]{fig/MNIST_inflsigm_eta1.png}
\caption{$\eta=1$}
\end{subfigure}
\caption{Comparaison de deux sigmoïdes pour plusieurs $\eta$}
\label{fig:MNIST_inflsigm}
\end{figure}
\subsubsection*{Différents réseaux :}
Intuitivement, un réseau avec plus de couches cachées devrait obtenir une meilleure précision, mais devrait avoir un temps d’apprentissage plus long. Ces résultats se confirment avec des expériences sur le réseau à une couche cachée (300 neurones) précédent, et un réseau sans couche cachée. Ce dernier converge très vite aussi bien vis-à-vis du nombre d’apprentissages nécessaire que du temps de calcul. Cependant, il est difficile de dépasser les 90\% de succès. Alors que sur le réseau avec couche cachée, on arrive à obtenir moins de 5\% d’erreur. En revanche, les temps de calculs sont plus élevés. Le réseau avec deux couches cachées, 1000 puis 300, a aussi été testé. Les résultats ici sont satisfaisants, cependant l’amélioration des résultats n’est pas très importante, alors que les temps de calculs augmentent fortement.
[[insert courbe (cf 17/01/18)]]
Ce problème est bien connu dans l'étude des réseaux de neurones, et a justifié l'apparition de l'apprentissage profond, que nous évoquerons plus tard.
Temps de calcul approximatifs pour une passe du set d’apprentissage, et un test tous les 1000 apprentissages :
\begin{itemize}
\item 5-6 minutes pour le 784-1000-300-10
\item 4 minutes pour le 784-300-10
\end{itemize}
Ces temps peuvent être améliorés avec l’introduction du batch learning qui permet de calculer les résultats des tests plus rapidement.