Classification linéaire
Au cours de cette démo pratique (et de la suivante), vous allez coder un classificateur
linéaire avec coûts quadratique et logistique (régression logistique).
Fin de la démo précédente
Il est important d'avoir été capable de coder un perceptron qui tourne correctement
dans la démo 5, afin de maîtriser les concepts de base de PLearn.
Alors si ce n'est pas encore le cas, retournez-y (et n'hésitez pas à poser des questions
pour ne pas rester bloqué).
Installation des fichiers de la démo
- Téléchargez le fichier demo_6.tar.gz
et décompressez-le dans un répertoire de travail par tar zxvf demo_6.tar.gz
Classificateur linéaire
Au cours de la partie théorique, vous avez calculé le gradient
de la fonction de coût (quadratique ou logistique) par rapport aux paramétres
du modèle.
Vous allez utiliser la formule ainsi trouvée dans votre propre code.
En vous inspirant du code de votre Perceptron, complétez la classe LinearClassifier
(fichiers LinearClassifier.cc et LinearClassifier.h) pour implémenter
un classificateur linéaire.
Ce classificateur doit avoir un champ model_type de type string, qui devra
être déclaré comme option dans LinearClassifier::declareOptions
afin de pouvoir spécifier sa valeur dans les scripts.
Ce champ doit pouvoir prendre deux valeurs :
- logistic_regression : coût de la régression logistique (remarque : c'est le nom officiel même quand on l'utilise en classification)
- least_squares_classification : coût de la classification linéaire (remarque : c'est en fait le
même coût que la régression linéaire, mais on va l'utiliser ici pour la classification)
Pour compiler votre code dans PLearn, vous devrez éditer le fichier plearn.cc
dans votre répertoire d'installation de PLearn (faites which plearn si vous ne savez plus où
il est), et rajouter une ligne #include "YYY/demo_6/LinearClassifier.h" où YYY est le répertoire
dans lequel vous avez décompressé demo_6.tar.gz.
Vous devrez, en plus de rajouter les champs nécessaires, implémenter les méthodes suivantes :
- build_() : cette méthode sert en général à initialiser un objet PLearn afin de s'assurer qu'il
est prêt à être utilisé.
Afin de simplifier le code, le perceptron ne l'utilisait pas, mais on vous demande ici d'y faire les opérations suivantes :
- Vérifier que l'option model_type a une des deux valeurs autorisées. Vous pouvez
quitter le programme en affichant une erreur par exemple par PLERROR("Wrong value for model_type: %s", model_type.c_str())
- Initialiser les paramètres à la bonne taille (plutôt que de le faire dans train() lorsque stage == 0 comme
on l'a fait dans le perceptron).
Comme cette taille dépend de l'ensemble d'entraînement, il vous faudra écrire ce code
dans un bloc if (train_set) { ... }, pour s'assurer de sa présence.
- forget() : cette méthode sert à réinitialiser l'algorithme avant l'entraînement.
L'appel à inherited::forget() (notez que inherited == PLearner) fait dans le code du Perceptron
ne fait que réinitialiser le générateur de nombres aléatoires.
Il vous faudra y rajouter deux choses :
- le champ stage doit être réinitialisé à zéro
- les paramètres doivent être initialisés aléatoirement (toujours dans un intervalle [-delta, +delta]
avec delta = 1.0 / train_set->inputsize()), avec un biais égal à zéro (note : dans le cas least_squares_classification,
si vous utilisez comme cibles 0 et 1 au lieu de -1 et 1, il serait plus logique d'initialiser le biais à 0.5)
- train : cette méthode optimise les paramètres pour un nombre d'époques égal à nstages,
c'est-à-dire que l'algorithme va voir nstage fois chaque ensemble d'apprentissage.
-
Vous implémenterez la version stochastique de la descente de gradient, c.à.d. où
les paramètres sont mis à jour après chaque exemple (et pas après chaque époque) :
la formule de mise à jour est du type w(k) ← w(k-1) - n(k) dC/dw avec dC/dw le
gradient du coût C par rapport aux paramètres w, pour un seul exemple.
Cette formule de mise à jour est donc répétée pour chaque époque autant de fois qu'il y a d'exemples d'entraînement.
Notez que le paramétre k est ici le nombre total d'exemples vus depuis le début de l'entraînement,
et non pas le nombre d'époques comme dans la démo précédente.
- Notez aussi que vous ne devriez pas avoir besoin de faire un cas spécial pour stage == 0, puisque
tout devrait être correctement initialisé dans build_() et forget().
- computeOutput : contrairement a ce qui a été fait pour le Perceptron, cette méthode ne va pas
renvoyer uniquement la classe. La raison est qu'on souhaite afficher l'évolution du coût optimisé. Regardez
le commentaire de computeOutput dans LinearClassifier.h pour voir ce que cette méthode doit
faire précisément.
Voici quelques fonctions PLearn utiles (voir la démo 5 pour d'autres fonctions) :
- sigmoid(real x) retourne la fonction sigmoide 1 / (1 + e^-x)
- is_equal(real x, real y) permet de tester l'égalité entre deux nombres flottants de manière stable numériquement
(utile pour tester par exemple si la cible est 0 ou 1)
- multiply(Vec& source, real x, Vec& destination) : remplit le vecteur destination par x * source
Lorsque vous pensez que votre code pourrait fonctionner, lancez par exemple une évaluation en train/valid/test par :
plearn train-valid-test-progression.plearn train_set=mnist_train_4_9.amat valid_set=mnist_valid_4_9.amat test_set=mnist_test_4_9.amat slr=0.001 mt=least_squares_classification dc=0 nstages=200 plearner=lin_classif.plearn
et ensuite :
plearn vmat view train-valid-test-progression-expdir/Split0/LearnerExpdir/Strat0results.pmat
Dans le fichier de résultats ainsi visulalisé, test1, test2 et test3 correspondent respectivement aux
ensembles d'entraînement, de validation et de test.
Vous pouvez aussi jouer avec pyplot pour afficher par exemple des surfaces de décision sur des exemples jouets.
gdb 101
Comme généralement rien ne marche du premier coup, vous allez avoir le plaisir de débugguer votre programme.
Tapez gdb plearn, puis la simple commande r qui va lancer PLearn sans argument
(cela permet de s'assurer que la librairie entière est bien chargée).
Des commandes utiles sont alors :
- r(un) args : lance le programme avec les arguments args
- b(reak) PLearn::errormsg : met un breakpoint sur les erreurs PLearn
- b(reak) MyFile.cc:150 : met un breakpoint à la ligne 150 de MyFile.cc
- b(reak) PLearn::MyClass::myFunc : met un breakpoint sur la méthode myFunc de la classe MyClass dans
le namespace PLearn
- up : remonte d'un niveau dans la pile des appels de fonctions
- do(wn) : descend d'un niveau dans la pile des appels de fonctions
- l(ist) : affiche le code autour de la ligne actuelle
- n(ext) : exécute l'instruction courante et passe à la ligne suivante
- s(tep) : commence à exécuter l'instruction courante, et s'arrête dans le premier appel de fonction (ou à la ligne suivante
si aucune fonction n'est appelée dans l'instruction courante)
- p(rint) x : affiche x (qui peut être une variable dans le code, ou même un nombre). Par exemple,
p w.storage.ptr->data[0]@5 affiche les 5 premiers éléments de la variable w de type Vec.