{ "metadata": { "name": "", "signature": "sha256:3d11ec4798b83738e3c4aeb50630f222d11ce0563a0024a12d559c8a6e0cdb36" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "Probl\u00e8me facile: deux lunes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On va s'attaquer au probl\u00e8me bien simple, mais qui n'est pas lin\u00e9airement s\u00e9parable, afin de se familiariser avec les r\u00e9seaux de neurones feedforward, aussi appel\u00e9 MLP (multilayer perceptron). Vous devez t\u00e9l\u00e9charger les donn\u00e9es [ici](http://www.iro.umontreal.ca/~memisevr/teaching/ift3395_2014/demos/2moons.txt)." ] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "R\u00e9gression logistique" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%load_ext autoreload\n", "%autoreload 2\n", "%pylab inline\n", "%aimport numpy\n", "np=numpy\n", "import time\n", "import utilitaires7 as utilitaires" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Regardez dans le fichier utilitaires7 et lisez la documentation des classes MulticlassLogisticRegression puis SGDTraining. Vous devrez initialiser la class MultiClassLogisticRegression et l'entra\u00eener. Vous pouvez ex\u00e9cuter la cellule suivante pour visualiser les r\u00e9sultats et comprendre pourquoi l'ensemble se nomme 2moons. " ] }, { "cell_type": "code", "collapsed": false, "input": [ "[train_x, train_y], [test_x, test_y] = utilitaires.load_2moons()\n", "\n", "dim = train_x.shape[1]\n", "n_classes = np.unique(train_y).shape[0]\n", "learning_rate = 0.1\n", "\n", "modele = utilitaires.MultiClassLogisticRegression(?)\n", "modele.train(?)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "utilitaires.plot_training_curves(modele.epochs, modele.loss_curves, \n", " title=u\"Courbe d'apprentissage d'un\\n mod\u00e8le de r\u00e9gression logistique - Fonction de perte\",\n", " ylabel=\"Perte\")\n", "utilitaires.plot_training_curves(modele.epochs, modele.cost_curves, \n", " title=u\"Courbe d'apprentissage d'un\\n mod\u00e8le de r\u00e9gression logistique - Erreur de classification\",\n", " ylabel=\"Taux d'erreur\")\n", "utilitaires.plot_decision_frontiers(modele, train_x, train_y, test_x, test_y, n_points=50)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "C'est tout pour la r\u00e9gression logistique pour l'instant. Nous y reviendrons apr\u00e8s pour le comparer avec les r\u00e9seaux de neurones sur les donn\u00e9es MNIST" ] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "R\u00e9seaux de neurones" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lisez maintenant la documentation des classes FeedForwardNeuralNet et FeedForwardNeuralNetLayer pour comprendre leur fonctionnement. Initialisez le mod\u00e8le avec une seule couche cach\u00e9es de 2 unit\u00e9s cach\u00e9es et lancez son entra\u00eenement. Vous pouvez encore une fois ex\u00e9cuter la cellule suivante pour visualiser les r\u00e9sultats. \n", "\n", "Une fois cela fait pour 2 unit\u00e9s cach\u00e9es, r\u00e9essayez avec 3, 4 puis 5 unit\u00e9s cach\u00e9es. Qu'observez vous comme diff\u00e9rence? Comment est-ce que la fonti\u00e8re de d\u00e9cision est fragment\u00e9? Vous pouvez vous amuser et augmenter le nombre d'unit\u00e9 d'un ordre de grandeur \u00e0 50 ou plus. Qu'est-ce qu'il se passe, c'est mieux ou pas? " ] }, { "cell_type": "code", "collapsed": false, "input": [ "[train_x, train_y], [test_x, test_y] = utilitaires.load_2moons()\n", "\n", "n_in = train_x.shape[1]\n", "n_classes = np.unique(train_y).shape[0]\n", "learning_rate = 0.5\n", "\n", "modele = utilitaires.FeedForwardNeuralNet(?)\n", "modele.train(train_data=train_x, train_labels=train_y, learning_rate=learning_rate, max_epoch=1000)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "utilitaires.plot_training_curves(modele.epochs, modele.loss_curves, \n", " title=u\"Courbe d'apprentissage d'un\\n r\u00e9seau feedforward - Fonction de perte\",\n", " ylabel=\"Perte\")\n", "utilitaires.plot_training_curves(modele.epochs, modele.cost_curves, \n", " title=u\"Courbe d'apprentissage d'un\\n r\u00e9seau feedforward - Erreur de classification\",\n", " ylabel=\"Taux d'erreur\")\n", "utilitaires.plot_decision_frontiers(modele, train_x, train_y, test_x, test_y, n_points=50)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "Probl\u00e8me difficile: MNIST (reconnaissance de chiffres manuscrits)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "L'ensemble MNIST est bien plus difficile \u00e0 classifier. Vous vous rappelez surement des r\u00e9sultats obtenus dans votre premier devoir, de 15 et 19% d'erreur de classification environ sur l'ensemble de test. On va commencer par am\u00e9liorer ces r\u00e9sultats avec la r\u00e9gression logisitique multiclasse, qui fait mieux que la r\u00e9gression avec erreur quadratique.\n", "\n", "Pour rendre les exp\u00e9riences plus rapide, trois version de MNIST sont disponibles pour vous avec les fonctions `load_mini_mnist`, `load_medium_mnist` et `load_full_mnist`. Vous devez t\u00e9l\u00e9charger [ici](http://deeplearning.net/data/mnist/mnist.pkl.gz) l'ensemble pour avoir acc\u00e8s au format moyen et complet de MNIST.\n", "\n", "Le mini MNIST se trouve ici en 4 fichiers\n", "\n", "- www.iro.umontreal.ca/~memisevr/teaching/ift3395_2014/devoirs/train_images.txt\n", "- www.iro.umontreal.ca/~memisevr/teaching/ift3395_2014/devoirs/test_images.txt\n", "- www.iro.umontreal.ca/~memisevr/teaching/ift3395_2014/devoirs/train_labels.txt\n", "- www.iro.umontreal.ca/~memisevr/teaching/ift3395_2014/devoirs/test_labels.txt" ] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "R\u00e9gression logistique" ] }, { "cell_type": "code", "collapsed": false, "input": [ "[train_x, train_y], [valid_x, valid_y], [test_x, test_y] = utilitaires.load_mini_mnist()\n", "\n", "dim = train_x.shape[1]\n", "n_classes = np.unique(train_y).shape[0]\n", "learning_rate = 0.5\n", "\n", "modele = utilitaires.MultiClassLogisticRegression(?)\n", "modele.train(train_data=train_x, train_labels=train_y, learning_rate=learning_rate, batch_size=600, max_epoch=1000,\n", " monitoring_data={\"ensemble de validation\": (valid_x, valid_y)})" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "utilitaires.plot_training_curves(modele.epochs, modele.loss_curves, title=u\"Courbe d'apprentissage d'un\\n r\u00e9seau feedforward - Fonction de perte\", ylabel=\"Perte\")\n", "utilitaires.plot_training_curves(modele.epochs, modele.cost_curves, title=u\"Courbe d'apprentissage d'un\\n r\u00e9seau feedforward - Erreur de classification\", ylabel=\"Taux d'erreur\")" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On va utiliser l'ensemble de validation pour optimiser les hyper-param\u00e8tres et v\u00e9rifier le r\u00e9sultat final sur l'ensemble de test. Il ne faut jamais optimier les hyper-param\u00e8tres sur l'ensemble de test lui-m\u00eame, car on peut se retrouver \u00e0 \"over-fitter\" l'ensemble de test. Les hyper-param\u00e8tres donnant les meilleurs r\u00e9sultats sur l'ensemble de validation ne sont pas n\u00e9cessairement ceux qui donneront les meilleurs r\u00e9sultats du l'ensemble de test. Il faut donc prendre bien soin de garder un ensemble de test bien \u00e0 l'abris du mod\u00e8le pendant l'entra\u00eenement et l'optimisation des hyper-param\u00e8tres afin d'avoir une \u00e9valuation non biais\u00e9. \n", "\n", "Choisissez un hyper-param\u00e8tres que vous d\u00e9sirez optimiser. Je vous conseille le taux d'apprentissage, mais \u00e7a peut \u00eatre aussi une p\u00e9nalit\u00e9 sur les poids comme la p\u00e9nalit\u00e9 de norme L1 ou L2. Cr\u00e9ez une liste de valeur que vous voudriez tester et entra\u00eenez le mod\u00e8le sur chacune. Apr\u00e8s chaque entra\u00eenement, enregistrez dans deux listes `train_results` et `valid_results` les r\u00e9sultats de `compute_cost` sur l'ensemble d'entra\u00eenement et l'ensemble de validation. Gardez aussi les mod\u00e8les dans une liste `modeles`. " ] }, { "cell_type": "code", "collapsed": false, "input": [ "[train_x, train_y], [valid_x, valid_y], [test_x, test_y] = utilitaires.load_mini_mnist()\n", "\n", "n_in = train_x.shape[1]\n", "n_classes = np.unique(train_y).shape[0]\n", "learning_rate = 0.5\n", "\n", "train_results = []\n", "valid_results = []\n", "modeles = []\n", "\n", "hyper_parametres = [?]\n", "\n", "for hp_value in hyper_parameters:\n", " # instanciez ici un mod\u00e8le et entra\u00eener le sans oublier \n", " # d'enregistrer le mod\u00e8le, le co\u00fbt et la perte apr\u00e8s chaque entra\u00eenement\n", " \n", " # hyper_parameters, train_results et valid_results sont utilis\u00e9s \n", " # dans la prochaine cellule" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "utilitaires.plot_training_curves(hyper_parametres, {u\"ensemble d'entra\u00eenement\":train_results, u\"ensemble de validation\":valid_results}, \n", " title=u\"Taux de classification en\\n fonction d'un hyper-param\u00e8tre x\",\n", " xlabel=u\"Valeurs de l'hyper-param\u00e8tre x\",\n", " ylabel=u\"Taux d'erreur\",\n", " xlog=True)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Regardez maintenant pour quelle(s) valeur(s) vous avez obtenus le(s) meilleur(s) r\u00e9sultat(s) sur l'ensemble de validation. S\u00e9lectionnez le premier mod\u00e8le correspondant dans la liste `modeles` et testez le maintenant sur l'ensemble de test. \n", "\n", "Qu'est-ce que cela vous donne? Est-ce moins bon ou meilleur? Est-ce que ce serait une bonne id\u00e9e de refaire la s\u00e9lection de l'hyper-param\u00e8tre et rev\u00e9rifier sur l'ensemble de test?" ] }, { "cell_type": "code", "collapsed": false, "input": [ "print \"Meilleur r\u00e9sultat sur l'ensemble de validation\"\n", "print modeles[ lequel? ].compute_cost(valid_x, valid_y)\n", "print \"R\u00e9sultat du mod\u00e8le correspondant sur l'ensemble de test\"\n", "print modeles[ lequel? ].compute_cost(test_x, test_y)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "R\u00e9seaux de neurones" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Les r\u00e9seaux de neurones ne sont d\u00e9marqueront pas tellement sur le mini ensemble de MNIST. Commencez cependant par vous familiariser avec eux et MNIST avant de charger une version moyenne ou compl\u00e8te de MNIST. Le temps de calcul sera beaucoup plus long. Sur le plus gros ensemble, la r\u00e9gression logistique saturera vers 7% alors qu'une r\u00e9seau de neurones \u00e0 une seule couche cach\u00e9e peut descendre jusqu'\u00e0 1.6% d'erreur sur l'ensemble de test.\n", "\n", "Commencez par instancier un r\u00e9seau \u00e0 une seule couche cach\u00e9e. Le nombre d'unit\u00e9s cach\u00e9es devra \u00eatre beaucoup plus \u00e9lev\u00e9 pour MNIST que ce n'\u00e9tait le cas pour 2moons. Essayez diff\u00e9rentes valeurs entre 10 et 1000 et observez les diff\u00e9rences en affichant les courbes d'apprentissage. Pas besoin de faire une recherche exhaustif tout de suite, c'est ce qu'on se fera tout de suite apr\u00e8s." ] }, { "cell_type": "code", "collapsed": false, "input": [ "[train_x, train_y], [valid_x, valid_y], [test_x, test_y] = utilitaires.load_mini_mnist()\n", "\n", "n_in = train_x.shape[1]\n", "n_classes = np.unique(train_y).shape[0]\n", "learning_rate = 0.1\n", "\n", "modele = utilitaires.FeedForwardNeuralNet(n_in, n_hids=[100], n_out=n_classes, non_linearities=\"sigmoid\")\n", "modele.train(train_data=train_x, train_labels=train_y, learning_rate=learning_rate, max_epoch=1000, batch_size=600,\n", " monitoring_data={\"ensemble de validation\": (valid_x, valid_y)})\n", "\n", "print modele.compute_cost(test_x, test_y)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "utilitaires.plot_training_curves(modele.epochs, modele.loss_curves, title=u\"Courbe d'apprentissage d'un\\n r\u00e9seau feedforward - Fonction de perte\", ylabel=\"Perte\")\n", "utilitaires.plot_training_curves(modele.epochs, modele.cost_curves, title=u\"Courbe d'apprentissage d'un\\n r\u00e9seau feedforward - Erreur de classification\", ylabel=\"Taux d'erreur\")" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Dans le m\u00eame ordre d'id\u00e9e que pour la r\u00e9gression logistique, essayez d'optimiser diff\u00e9rent hyper-param\u00e8tres, un seul \u00e0 la fois. L'id\u00e9al serait de fixer un nombre d'unit\u00e9s cach\u00e9es d\u00e9cent de l'ordre de 200, et d'optimiser le taux d'apprentissage. Vous pourrez ensuite optimiser le nombre d'unit\u00e9s cach\u00e9es, puis r\u00e9optimiser le taux d'apprentissage. Et oui, la valeur des hyper-param\u00e8tres influence les autres et peut changer leur valeur optimale." ] }, { "cell_type": "code", "collapsed": false, "input": [ "[train_x, train_y], [valid_x, valid_y], [test_x, test_y] = utilitaires.load_mini_mnist()\n", "\n", "n_in = train_x.shape[1]\n", "n_classes = np.unique(train_y).shape[0]\n", "learning_rate = 0.1\n", "\n", "train_results = []\n", "valid_results = []\n", "modeles = []\n", "\n", "hyper_parametres = [?]\n", "\n", "for hp_value in hyper_parameters:\n", " # instanciez ici un mod\u00e8le et entra\u00eener le sans oublier \n", " # d'enregistrer le mod\u00e8le, le co\u00fbt et la perte apr\u00e8s chaque entra\u00eenement\n", " \n", " # hyper_parameters, train_results et valid_results sont utilis\u00e9s \n", " # dans la prochaine cellule" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "utilitaires.plot_training_curves(hyper_parametres, {u\"ensemble d'entra\u00eenement\":train_results, u\"ensemble de validation\":valid_results}, \n", " title=u\"Taux de classification en\\n fonction d'un hyper-param\u00e8tre x\",\n", " xlabel=u\"Valeurs de l'hyper-param\u00e8tre x\",\n", " ylabel=u\"Taux d'erreur\",\n", " xlog=True)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "Bonus: Early stopping" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Une m\u00e9thode simple et efficace souvent utilis\u00e9 \u00e0 l'entra\u00eenement par descente de gradient est ce qu'on appelle l'\u00abearly stopping\u00bb. Le principe est simple, on observe le co\u00fbt pendant l'entra\u00eenement et on arr\u00eate l'entra\u00eenement s'il arr\u00eate de descendre pendant plusieurs \u00e9poque. On peut enregistrer les param\u00e8tres au moment o\u00f9 le co\u00fbt \u00e9tait minimal sur l'ensemble de validation. Regardez la classe suivante qui impl\u00e9mente ce principe." ] }, { "cell_type": "code", "collapsed": false, "input": [ "class EarlyStopping:\n", " \"\"\"\n", " R\u00e8gle d'arr\u00eat pour l'entra\u00eenement d'un mod\u00e8le d'apprentissage machine\n", " \n", " Attributes\n", " ----------\n", " model: object\n", " Mod\u00e8le d'apprentissage machine (ex: r\u00e9seau de neurones)\n", " patience: int\n", " Nombre de fois un co\u00fbt sup\u00e9rieur au meilleur peut \u00eatre \n", " vu avant d'arr\u00eater l'apprentissage\n", " \n", " Methods\n", " -------\n", " restore_best_model()\n", " R\u00e9injecte la valeur des meilleurs param\u00e8tres dans \n", " le mod\u00e8le apr\u00e8s l'arr\u00eat de l'apprentissage\n", " \"\"\"\n", "\n", " def __init__(self, model, patience=5):\n", " \"\"\"\n", " Parameters\n", " ----------\n", " model: object\n", " Mod\u00e8le d'apprentissage machine (ex: r\u00e9seau de neurones)\n", " patience: int\n", " Nombre de fois un co\u00fbt sup\u00e9rieur au meilleur peut \u00eatre \n", " vu avant d'arr\u00eater l'apprentissage\n", " \"\"\"\n", " self.model = model\n", " self.patience = patience\n", " self.best_cost = float('inf')\n", " self.patience_buffer = patience\n", "\n", " def __call__(self, train_data, train_labels):\n", " \"\"\"\n", " M\u00e9thode principale pour l'arr\u00eat (stopping_rule)\n", " \n", " Parameters\n", " ----------\n", " train_data: ndarray\n", " Matrice d'exemples de format (n,d) o\u00f9 n est le nombre d'exemple et \n", " d la dimension\n", " train_labels: ndarray\n", " Vecteur de cibles, de format n\n", " \n", " Returns\n", " -------\n", " bool\n", " True si la patience est vide, False sinon et l'entra\u00eenement continue\n", " \"\"\"\n", " cost = self.model.compute_cost(train_data, train_labels)\n", " if cost < self.best_cost:\n", " self.best_cost = cost\n", " self.patience_buffer = self.patience\n", " self.params = [param.get_value() for param in self.model.params]\n", " else:\n", " self.patience_buffer -= 1\n", "\n", " return self.patience_buffer < 0\n", " \n", " def restore_best_model(self):\n", " \"\"\"\n", " R\u00e9injecte la valeur des meilleurs param\u00e8tres dans \n", " le mod\u00e8le apr\u00e8s l'arr\u00eat de l'apprentissage\n", " \"\"\"\n", " for param, best_values in zip(self.model.params, self.params):\n", " param.set_value(best_values)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Voici comment utiliser la classe EarlyStopping. Vous pouvez tester diff\u00e9rentes valeurs de patience pour observer son effet. Et n'oubliez pas, le early stopping se calcule sur l'ensemble de validation, pas l'ensemble de test! On test \u00e0 la toute fin seulement!" ] }, { "cell_type": "code", "collapsed": false, "input": [ "[train_x, train_y], [valid_x, valid_y], [test_x, test_y] = utilitaires.load_mini_mnist()\n", "\n", "n_in = train_x.shape[1]\n", "n_classes = np.unique(train_y).shape[0]\n", "learning_rate = 0.1\n", "max_epoch = 1000\n", "\n", "modele = utilitaires.FeedForwardNeuralNet(n_in, n_hids=[500], n_out=n_classes, non_linearities=\"sigmoid\")\n", "\n", "early_stopping = EarlyStopping(modele, patience=20 * max_epoch * modele.message_frequency)\n", "\n", "modele.train(train_data=train_x, train_labels=train_y, learning_rate=learning_rate, max_epoch=1000, batch_size=128,\n", " monitoring_data={\"ensemble de validation\": (valid_x, valid_y)},\n", " stopping_rule=early_stopping)\n", "\n", "print \"avant la r\u00e9injection\"\n", "print modele.compute_cost(test_x, test_y)\n", "\n", "# R\u00e9injecte les meilleurs valeurs dans le mod\u00e8le\n", "early_stopping.restore_best_model()\n", "\n", "print \"apr\u00e8s la r\u00e9injection\"\n", "print modele.compute_cost(test_x, test_y)" ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "Bonus: Affichage des filtres" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Il est int\u00e9ressant d'observer les filtres de la premi\u00e8re couche afin de voir \u00e0 quoi ils sont sensibles dans l'image. C'est beaucoup plus difficile \u00e0 interpr\u00e9ter pour les couches suivantes.\n", "\n", "Vous pouvez afficher les filters \u00e0 l'aide du code [ici](http://deeplearning.net/tutorial/code/utils.py)." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Instancier et entrainer un mod\u00e8le ici pour afficher ensuite les filtres." ], "language": "python", "metadata": {}, "outputs": [] }, { "cell_type": "code", "collapsed": false, "input": [ "import PIL.Image as Image\n", "from utils import tile_raster_images\n", "\n", "print modele.layers[0].W.get_value().shape\n", "\n", "image = Image.fromarray(tile_raster_images(X=modele.layers[0].W.get_value(borrow=True).T,\n", " img_shape=(28, 28), tile_shape=(10, 10),\n", " tile_spacing=(1, 1)))\n", "\n", "plt.imshow(image, cmap=\"gray\")" ], "language": "python", "metadata": {}, "outputs": [] } ], "metadata": {} } ] }