TP1 : Organisation du projet d algorithmique Enseignants: Antoine Bonnefoy et Sokol Koço Sujet: Liva Ralaivola, Antoine Bonnefoy et Sokol Koço 15 janvier 2014 1 Chasser à l aveuglette 1.1 Le projet Le projet d algorithmique a pour but de vous faire appliquer des problématiques algorithmiques ludiques à l aide du langage C. Cette mise en pratique vous permettra de vous familiariser avec les concepts du C que vous avez déjà abordés : pointeurs, listes chainées, structures, programmation modulaire, makefile. Nous utiliserons aussi la bibliothèque graphique Gtk+ pour illustrer les algorithmes développés durant le projet. Plus précisément vous réaliserez une application mettant en scène le problème des bandits à K-bras. Cette problématique vous sera détaillée lors du prochain cours. Pour expliquer en quelques mots le problème des bandits à K-bras, disons que nous disposons de K machines à sous, le jeu est simple il suffit d actionner le bras pour savoir si/combien on a gagné. Chaque machine possède une espérance de gain différente qu on ne connait pas et on aimerait savoir sur quelle machine faut-il jouer pour maximiser nos gains. Autrement dit comment trouver les meilleurs bras combinant d une part, l exploration des K-bras (machines à sous) pour trouver les meilleurs, et d autre part l exploitation des bons bras déjà explorés. Les algorithmes UCB (Upper Confidence Bound) et ɛ-glouton, qui vous seront présentés la semaine prochaine, servent justement à trouver le bon compromis entre "exploration" et "exploitation". 1.2 Le Chat et la Souris Le problème des bandits à K-bras est illustré à travers une course poursuite endiablée entre un chat et une souris. Pour pimenter une peu cette course poursuite imaginons qu elle se fait dans le noir complet et que nos deux protagonistes ne peuvent se fier qu à leur odorat. La seule information dont ils disposent alors est la distance qui les sépare. Quelle est la meilleur direction à prendre? Ce problème est exactement celui d un bandit à K-bras, pour lequel chaque bras représente une direction possible de déplacement. 2 Organisation du projet Pour vous aider à avancer dans votre projet nous vous demandons 3 rendus avant la fin du projet : 1. le 02/02 vous devrez rendre un rapport écrit de maximum 4 pages et les codes sources de votre programme implémentant au moins 1 bandit à K-bras, pouvant utiliser la méthode UCB et ɛ-glouton. Le projet devra être programmé de façon modulaire et être compilé à l aide d un makefile. (20% de la note finale) 2. le 02/03 vous devrez rendre un rapport écrit de maximum 4 pages et les codes sources de votre programme qui contiendra une application graphique programmée à l aide de 1
Figure 1 Un example d application graphique avec 3 chats (trace en vert) et une souris (trace en rouge) Gtk+ (version 3.0) mettant en scène au moins 1 chat (mobile) et une souris (fixe). Le chat pourra utiliser les deux méthodes précédentes pour attraper la souris. (30% de la note finale) 3. le 20/04 vous devrez rendre un rapport écrit de maximum 10 pages et les codes sources de votre programme contenant une application graphique avec plusieurs chat poursuivant une souris mobile. Vous proposerez/implémenterez aussi des améliorations pour les méthodes de bases, prenant en compte par example les déplacements des bandits (les chats et la souris). (50% de la note finale) 3 Structurer le projet (à faire aujourd hui) Vous devez créer la structure de votre projet et générer les principaux fichiers *.c et *.h dont vous aurez besoin. Ces fichiers peuvent, pour le moment, être vides. Ils vous serviront à structurer votre projet et vous faciliter la tâche par la suite. Votre structure doit contenir au minimum une paire de fichier (.c.h) pour les fonctions et structures associées aux bandits à K-bras, une paire pour la gestion de l interface graphique, une paire pour la gestion de l environnement dans lequel les bandits se déplacent et enfin un fichier.c qui contiendra la fonction main(). 3.1 Rappel sur la compilation Voici les principales options de gcc que vous avez à utiliser. Pour plus d options : c.developpez.com/cours/mode-emploi-gcc/ Options utiles de gcc -o nom-cible : précise le nom du fichier de sortie -c si vous ne voulez pas générer d exécutable directement, ne crée que les objets "*.o" -llib pour inclure la bibliothèque lib $(pkg-config -cflags gtk+-3.0) vous sera aussi utile par la suite pour inclure toutes les options nécessaires à la compilation de l application graphique avec Gtk+ Inclusion conditionnelle Les fichiers d en tête (.h) doivent empêcher les doubles inclusions : 2
#i f n d e f HEADER_H #d e f i n e HEADER_H header.h / v o t r e code / #e n d i f 3.2 Rappel sur les "makefile" Le makefile contient un ensemble de règles que doit effectuer la machine pour compiler entièrement votre projet. Cet ensemble de règles permet de compiler séparément chaque fichier source et peut être ensuite exécuté par un simple appel à make depuis le shell. Voici quelques avantages de la compilation séparée : automatise la compilation du code modulaire qui est plus compréhensible ne recompile que le code modifié facilite la maintenance Les règles se présentent de la manière suivante : c i b l e : dependences commande cible est le fichier de sortie que va construire le makefile en exécutant cette règle. Ce fichier n est recompilé que s il a une de ses dépendances plus récente que lui. dependances est la liste des fichiers nécessaire pour construire la cible. commande est la commande shell à effectuer. Par example : f i c h i e r. o : f i c h i e r. c header. h gcc o f i c h i e r. o c f i c h i e r. c crée le fichier binaire fichier.o à partir de fichier.c et header.h. et : main : f i c h i e r. o f i c h i e r 2. o gcc o main f i c h i e r. o f i c h i e r 2. o crée un exécutable main à partir des fichiers binaires fichier.o et fichier2.o. L appel de make exécute uniquement la première règle, placez donc la règle qui génère l exécutable en premier. Pour exécuter une règle en particulier il vous suffit de taper dans le shell make cible. Variables On peut dans un makefile déclarer des variables qui seront utilisées par la suite. Par example : CC=gcc pour déclarer une variable pour le compilateur qui sera ensuite accessible par $(CC) dans le makefile. Variables Automatique Les variables automatiques sont des variables qui sont valables pour chaque règle séparément : $@ est le nom de la cible $ˆ est la liste des dépendances $< est la première dépendance de la liste 3
Ainsi notre premier exemple s écrit en pratique f i c h i e r. o : f i c h i e r. c header. h $ (CC) o $@ c $< et le deuxième main : f i c h i e r. o f i c h i e r 2. o $ (CC) o $@ $^ Conventions Certaines conventions doivent être respectées lors de la déclarations des variables pour plus de lisibilité dans les makefiles : CC # e s t l e c o m p i l a t e u r CFLAGS # s o n t l e s o p t i o n s de c o m p i l a t i o n C LIBS # s o n t l e s o p t i o n s d e d i t i o n de l i e n ( l e s b i b l i o t h e q u e s a i n c l u r e ) Ajouter au makefile la règle clean permet de nettoyer votre dossier de tous les fichiers.o inutiles après la création de l exécutable. Pour plus de détails sur les makefile, notamment la règle "clean" : http://gl.developpez.com/tutoriel/outil/makefile/ Vous pouvez poursuivre si vous avez créer les 7 fichiers demandés et que votre make compile. Chaque fichier doit inclure, avec #include, les fichiers d entête nécessaires et votre projet devra contenir la fonction main() qui, pour l instant, ne renvoie que 0. 4 Des structures et des fonctions pour les bandits Pour débuter, nous allons considérer des bandits à K bras très simples : ceux dont les bras ne peuvent prendre que deux valeurs (voir l exemple précédemment décrit). Dans cette situation, un bandit est donc constitué de K bras, donc chacun est lui-même associé avec 3 paramètres : la valeur d une perte, la valeur d un gain, et la probabilité associée à un gain. Une manière de représenter ces éléments, c est-à-dire un bandit et un jeu de paramètres, il est possible de définir 2 nouveaux types de données, l un appelé bandit et l autre appelé paramètres, de la manière suivante : #d e f i n e MAX_BRAS 10 / Des d e c l a r a t i o n s a n t i c i p e e s des t y p e s / / Ces renommages p e r m e t t e n t d a l l e g e r l e code / t y p e d e f s t r u c t b a n d i t b a n d i t ; t y p e d e f s t r u c t p a r a m e t r e s p a r a m e t r e s ; s t r u c t p a r a m e t r e s { / Le montant de l a p e r t e / double p e r t e ; / Le montant d un g a i n / double g a i n ; / La p r o b a b i l i t e d un g a i n / double proba_gain ; } ; 4
s t r u c t b a n d i t { / Nombre de b r a s / i n t K; / Un t a b l e a u de p a r a m e t r e s : l e n t r e e i c o r r e s p o n d / / aux p a r a m e t r e s du b r a s i / p a r a m e t r e s param [MAX_BRAS] } ; Ces types peuvent être utilisés de la manière suivante : b a n d i t b ; p a r a m e t r e s p1, p2 ; / l e s p a r a m e t r e s pour l e p r e m i e r b r a s / p1. p e r t e = 1.; p1. g a i n =10; p1. proba_gain = 0. 1 ; / l e s p a r a m e t r e s pour l e deuxieme b r a s / p2. p e r t e = 0. ; p2. g a i n =2; p2. proba_gain = 0. 5 ; / Les p a r a m e t r e s du b a n d i t / b.k =2; b. param [ 0 ] =p1 ; b. param [ 1 ] =p2 ; On peut noter que le nombre maximal de bras est fixé par MAX_BRAS, qui vaut ici 10. Lorsque les pointeurs seront introduits, nous verrons comment il est possible de ne pas limiter le nombre de bras à considérer a priori. Une manière d utiliser les structures de données que l on vient de définir, il est possible de définir deux fonctions, dont les fonctionnements sont décrits dans le commentaires qui les précèdent / F o n c t i o n q u i r e n v o i e un double compris e n t r e 0 e t 1 / double drand ( ) ; / Renvoie l a v a l e u r de l a recompense ( g a i n ou p e r t e ) / / l o r s q u on t i r e s u r l e b r a s i du b a n d i t b / double recompense ( b a n d i t b, i n t i ) ; 4.1 Travail à faire Un deuxième travail à effectuer pour se familiariser avec les bandits est le suivant : 1. Programmer les fonctions drand et recompense déclarées ci-dessus (faire des recherches sur internet pour comprendre leur implémentation, si besoin). 2. Faire un programme principal (fonction int main()) qui fait plusieurs tirages en choisissant aléatoirement les bras choisis. Vérifiez que la moyenne des récompenses pour chaque bras converge bien vers l espérance de chaque bras. 5