p1314558_NGUYEN-Thi-Tuong-Vy

6 vues

Publié le

0 commentaire
0 j’aime
Statistiques
Remarques
  • Soyez le premier à commenter

  • Soyez le premier à aimer ceci

Aucun téléchargement
Vues
Nombre de vues
6
Sur SlideShare
0
Issues des intégrations
0
Intégrations
1
Actions
Partages
0
Téléchargements
1
Commentaires
0
J’aime
0
Intégrations 0
Aucune incorporation

Aucune remarque pour cette diapositive

p1314558_NGUYEN-Thi-Tuong-Vy

  1. 1. Pôle Rhône-Alpes de Bioinformatique Bâtiment Gregor Mendel Université Claude Bernard Lyon 1 16, rue Raphaël Dubois 69622 Villeurbanne Cedex RAPPORT DE STAGE RECHERCHE DES MOTIFS EN MÉMOIRE CONSTANTE DANS UN GÉNOME NGUYEN Thi Tuong Vy Licence 3 Informatique Année universitaire 2015 – 2016 Maître de stage : Philippe VEBER Ingénieur de recherche
  2. 2. i Résumé Ce rapport résume mes travaux chez Pôle Rhône-Alpes de Bio-Informatique (PRABI) pendant la première moitié du stage et couvre mes missions prévues pendant le reste du stage. Dans un premier temps, je vous présente l’entreprise. Le sujet du stage et les missions réalisées seront étudiées dans la deuxième partie. À partir de là, je vais vous parler des résultats obtenus et vous citer les tâches précédentes pour résoudre le problème. Enfin et surtout, mon retour sur l’expérience sera montré. L’objectif principal du stage est d’implémenter un algorithme de recherche de motifs en mémoire constante dans un génome. Le langage principal utilisé est OCaml, un langage de programmation fonctionnelle. Les travaux sont réalisés essentiellement sur Debian GNU/Linux.
  3. 3. ii Remerciements Je voudrais tout d’abord exprimer mes sincères remerciements et ma profonde gratitude à Monsieur Philippe VEBER, ingénieur de recherche chez PRABI et en même temps mon maître de stage, de son enthousiasme et patience, de son enseignement, de son suivi régulier et de ses conseils portés sur mes travaux. Je tiens aussi mes sincères reconnaissances à Monsieur Guy PERRIERE – Directeur du PRABI, de son aide pour mon intégration dans la vie professionnelle et de m’avoir montré une vision concrète de l’environnement de recherche scientifique.
  4. 4. iii Sommaire Résumé................................................................................................................................................................i Remerciements...................................................................................................................................................ii Introduction....................................................................................................................................................... 1 1. Environnement professionnel ................................................................................................................... 2 1.1. Organisation du PRABI....................................................................................................................... 2 1.2. Mission du PRABI-AMSB.................................................................................................................... 3 2. Mission ...................................................................................................................................................... 4 2.1. Contexte et problématique du stage ................................................................................................ 4 2.1.1. Contexte .................................................................................................................................... 4 2.1.2. Problématique........................................................................................................................... 4 2.2. Approche ........................................................................................................................................... 6 2.3. Formats des données ........................................................................................................................ 6 2.3.1. Traitement de fichier FASTA...................................................................................................... 7 2.3.2. Traitement de fichier BED ......................................................................................................... 8 2.4. Algorithmes de recherche ................................................................................................................. 8 2.4.1. Algorithme naïf (algorithme de Brute Force) ............................................................................ 9 2.4.2. Lecture de chaque caractère du fichier..................................................................................... 9 2.4.3. Construction d’une table d’états............................................................................................... 9 2.4.4. Algorithme de KMP.................................................................................................................. 10 2.4.5. Recherche d’un motif de PWM ............................................................................................... 11 2.5. Représentation des flux................................................................................................................... 12 2.5.1. Le type «générateur» .............................................................................................................. 12 2.5.2. La continuation........................................................................................................................ 13 2.6. Application aux données ................................................................................................................. 14 2.7. Travaux réalisés et leurs résultats ................................................................................................... 14 2.7.1. Benchmark d’algorithme naïf et de KMP ................................................................................ 14 2.7.2. Construction la table d’états ................................................................................................... 15 2.7.3. Lecture de fichier FASTA.......................................................................................................... 16 2.8. Travaux prévus ................................................................................................................................ 16 3. Retour sur expérience ............................................................................................................................. 17 3.1. Apprentissage du langage OCaml et de la programmation fonctionnelle...................................... 17 3.1.1. Programmation fonctionnelle ................................................................................................. 17 3.1.2. Langage OCaml........................................................................................................................ 18 3.2. Compétences techniques acquises en OCaml................................................................................. 18 3.2.1. Variable non modifiable .......................................................................................................... 18
  5. 5. iv 3.2.2. Typage fort............................................................................................................................... 19 3.2.3. Inférence de type..................................................................................................................... 20 3.2.4. Fonction d’ordre supérieur...................................................................................................... 20 3.2.5. Fonction récursive et fonction récursive terminale ................................................................ 21 3.2.6. Programmation impérative dans OCaml................................................................................. 21 3.2.7. Type optionnel......................................................................................................................... 22 3.2.8. Application partielle ................................................................................................................ 22 3.3. Autres compétences techniques..................................................................................................... 23 3.4. Côté professionnel........................................................................................................................... 23 Conclusion ....................................................................................................................................................... 24 Bibliographie.................................................................................................................................................... 25
  6. 6. 1 Introduction Mon stage se déroule au Pôle Rhône-Alpes de Bio-Informatique (PRABI), qui est l’un des six centres régionaux membres du Réseau National des plateformes en Bioinformatique (ReNaBi). Le PRABI regroupe plusieurs structures de la région Rhône-Alpes, en particulier le PRABI-AMSB, plateforme Université Lyon 1, spécialisé dans l’analyse des données génomiques. Passionnée par la biologie, intéressée par la génomique et ayant l’intention de m’orienter vers la recherche, j’ai rejoint l’équipe du PRABI-AMSB pour découvrir les algorithmes spécifiques en bioinformatique. Mon sujet de stage porte sur la recherche de motifs dans un génome. Mon rôle est de modifier un algorithme connu pour qu’il réalise une recherche avec une complexité en espace ne dépendant pas de la taille de la séquence en entrée. L’implémentation doit suivre un style fonctionnel et être réalisée dans le langage OCaml. Pour réaliser cette mission de 3 mois, j’ai commencé par apprendre le langage OCaml pendant 3 semaines et je travaille depuis sur une implémentation. J’ai expérimenté plusieurs algorithmes en utilisant les différents styles de programmation en changeant la manière dont les données sont reçues et les résultats transmis. Aussi, j’évalue les performances pour illustrer les avantages de chaque approche. À la fin du stage, je vais faire l’intégration des fonctions produites à une bibliothèque et la documentation correspondante.
  7. 7. 2 1. Environnement professionnel Dans cette partie, je vais vous présenter ma structure d’accueil, son organisation et ses missions. 1.1. Organisation du PRABI Le PRABI est une structure régionale qui est le groupement des structures de bioinformatique en Rhône-Alpes. Parmi celles-ci, la composante Analyse et Modélisation des Systèmes Biologiques (AMSB) est une plateforme de l’Université Claude Bernard Lyon 1 (UCBL1). Dirigée par M. Guy PERRIERE, cette équipe est constituée d’ingénieurs autour d’un besoin technique (bioinformatique). Au sein de l’UCBL1, le PRABI-AMSB est décrit dans le figure 2 : Figure 1. Organigramme du PRABI Figure 2. Organigramme de UCBL1
  8. 8. 3 Sous l’encadrement de M. Philippe VEBER – un ingénieur du LBBE – je contribue au projet ANR ARREPRESS soutenu par l’Agence Nationale de la Recherche (ANR), et auquel le PRABI-AMSB participe. 1.2. Mission du PRABI-AMSB Le PRABI-AMSB est spécialisé dans l’analyse de données génomiques, notamment issues du séquençage haut-débit, et a pour but de : - réaliser des prestations d’analyse ou de développement logiciel en bioinformatique ; - proposer des formations ; - participer aux collaborations scientifiques sur appel à projet. Plus précisément, les applications du séquençage haut-débit comprennent : - l’assemblage de génome ; - la mesure de l’expression des gènes (RNA-seq) ; - la détection des liaisons entre l’ADN et les protéines (ChIP-seq). Figure 3. Organigramme du PRABI-AMSB
  9. 9. 4 2. Mission Dans cette partie, je parle d’abord de la problématique du stage et de l’approche que j’ai suivie pour y répondre. Je vais montrer ensuite le résultat que j’ai obtenu jusqu’ici. 2.1. Contexte et problématique du stage 2.1.1. Contexte L’équipe du PRABI et ses collaborateurs travaillent sur le projet ANR ARREPRESS. Le but de ce projet est de déterminer les mécanismes de répression de l’activité des gènes liés à une protéine appelée récepteur à l’acide rétinoïque (RAR). Ainsi, ce projet concerne principalement les sites de fixation de RAR à l’ADN. Le problème est abordé de 2 façons : détermination expérimentale (ChIP-seq) et prédiction «in silico» sur la séquence du génome. On souhaite comparer les résultats de la deuxième approche avec ceux de la première, qui est plus fiable mais aussi plus coûteuse. 2.1.2. Problématique Pour la prédiction, le modèle le plus utilisé pour les sites de liaison ADN- protéine sur le génome est appelé matrice poids-position (en anglais : position weight matrix - PWM). Dans le cadre du stage, je dois réaliser l’implémentation d’un algorithme de recherche de motif en complexité mémoire indépendante de la taille de l’entrée et de la sortie. En particulier, le motif cherché est un PWM et la recherche se fait sur un génome complet de souris. D’une part, l’information du génome occupe un espace mémoire de 2,8 Go ; d’autre part, les motifs recherchés sont courts et peu spécifiques : beaucoup d’occurrences seront trouvées et si l’on n’a pas un mécanisme d’écriture au fur et à mesure, cela conduira toujours à un problème d’espace mémoire. Cela cause donc un problème de chargement mémoire du génome et stockage des résultats. Ce projet a deux autres contraintes. La première est d’exprimer le calcul sous la forme des briques réutilisables. Deuxièmement, c’est d’utiliser un style fonctionnel (c’est-à-dire limitant l’usage d’effets de bord) pour un traitement impliquant des entrées/sorties. Pour la première contrainte, nous avons besoin de factoriser chaque étape du traitement : lecture depuis une source de données, analyse syntaxique, recherche du motif, écriture de résultats. Par exemple, un programme idéal doit pouvoir faire la recherche en utilisant la même implémentation de l’algorithme depuis n’importe quelle source de données. Donc, il faut
  10. 10. 5 certainement un mécanisme qui peut traiter un flux de chaines de caractères à partir d’une source quelconque. lecture flux réseau lecture fichier décompression parsing FASTA recherche motif autres algorithmes unparsing BED écriture fichier compression Figure 4. Les étapes du traitement type que l’on souhaite réaliser FASTA et BED sont des formats très utilisés en bioinformatique.
  11. 11. 6 2.2. Approche Pour chaque traitement, il faut formuler une implémentation incrémentale, c’est-à-dire capable de : - travailler sur une entrée partielle ; - s’interrompre quand il n’y a plus assez d’entrée ; - reprendre lorsque les nouvelles entrées sont disponibles. Pour rendre les algorithmes incrémentaux, on cherche à introduire un état explicite du traitement. En raison de la problématique et ses contraintes, mon approche se divise en 4 grandes étapes. Premièrement, je travaille sur les algorithmes de recherche. Pour rendre les algorithmes incrémentaux, il me faut introduire un état explicite par l’approche de la programmation fonctionnelle. En effet, à chaque réception de la chaine de caractères, un état sera utilisé pour donner un résultat et un nouvel état. Cela va représenter une étape de calcul qui est composable avec la suivante. Deuxièmement, on va faire le traitement du format FASTA. En fait, les fichiers du FASTA contiennent l’information du génome. Dans cette étape, au lieu de charger toute la séquence, celle-ci sera lue morceau par morceau. Par conséquent, il faut adapter le parsing fichier pour la rendre incrémentale. La troisième étape de l’approche est l’étude de la représentation des flux de données et comment ils permettent de réutilisation les briques de calcul. Enfin et surtout, nous terminerons par l’application de l’algorithme de recherche aux données réelles à l’aide du flux et de la communication avec le fichier. Les trois premières étapes peuvent être initiées indépendamment afin de contribuer à la dernière. En fait, les tâches seront réalisées des plus faciles aux plus difficiles. Ces détails seront abordés plus tard. 2.3. Formats des données Dans cette partie, le fichier FASTA est considéré comme le format d’entrée et le fichier BED est considéré comme le format sortie. En effet, la lecture et l’écriture au fur et à mesure seront mises à disposition.
  12. 12. 7 2.3.1. Traitement de fichier FASTA En fait, les séquences de génome sont distribuées sous format FASTA, qui permet de représenter un ensemble de séquences (une séquence par chromosome pour les génomes). Présentons tout d’abord le format du fichier FASTA. Un fichier FASTA est une liste d’items et chaque item a deux composants : description et séquence. Les descriptions sont courtes et commencent par le signe ">". Les séquences de données sont constituées des lettres représentant les acides nucléiques ou les acides aminés de la séquence. Par contre, chaque ligne contient au plus de 120 lettres. Donc, les séquences de taille plus grande doivent être découpée en plusieurs lignes. Voyons un exemple du fichier FASTA : Un type naturel pour représenter le fichier FASTA est décrit comme suit : type fasta = item list and item = { description : string ; sequence : string ; } Par contre, avec ce type-là, on a besoin de charger en mémoire tout le contenu du fichier. Or la séquence est parfois très longue. Donc, cette solution conduit aussi la surcharge de l’espace mémoire. On doit ainsi traiter le fichier en manière plus compliquée. L’idée est de construire un nouveau type de donnée appelé «fasta_item». Cette construction va stocker la séquence en plusieurs morceaux. Ce type peut se décrire comme suit : type fasta_item = | Description of string | Partial_sequence of string >SEQUENCE_1 MTEITAAMVKELRESTGAGMMVAKNDQFIASRKQLSDQLLREKGLGKAAKKADRLAAEG LVSVKVSDDFTIAAMRPSYLSYEDLDMTFVENEYLRESTGAGEKENEERRRLKDPNKPEHK IPQFASRKQLSDAILKLRESTGAGKEELKAQVAKNDVVIAAACDSAEVFIADNSQLDSKLTL MGQFYVMDDKKTVEQVIAEKEKEFGGKIKIVEFICFEVGEGLEKKTEDFAARLAAEG >SEQUENCE_2 SATVSEINSETDFVARPSYLSYRPSYLSYHIQSSAEVASKSRELHSKAGSTINGVKFEEYLKSQI ATIGENLVVRRFATLKAGANGVVNGYIHTNGRVGVVIAAACDSAEVASKSRDLLRQICMH
  13. 13. 8 Le fichier FASTA est complètement traité. Par contre, le résultat reste toujours les morceaux d’informations. C’est la raison pour laquelle, on a besoin de représenter les données sous forme des flux. 2.3.2. Traitement de fichier BED Pour sauvegarder le résultat de la recherche, nous utilisons le fichier BED. Le fichier BED est un format utilisé en bioinformatique pour stocker les positions dans un génome. Le fichier BED a plusieurs colonnes. Ces colonnes contiennent les types différents de données et sont séparées par les espaces ou les tabulations. Voyons un exemple du fichier BED : Dans le cadre du stage, les principales informations qu’on va stocker dans ce fichier sont : description (nom des chromosomes), première position d’occurrence, dernière position d’occurrence. Donc, un type représentant le fichier BED peut être décrit comme suit : type bed = bed_item list and bed_item = { chromosome : string ; first_pos : int ; last_pos: int; } Les items sont en petite taille. Donc, on n’a pas besoin de les décomposer dans les algorithmes incrémentaux. 2.4. Algorithmes de recherche Trois algorithmes seront abordés. L’implémentation de l’algorithme de Brute – Force se fait en premier en raison de sa simplicité. Un changement sera mis en place pour que cet algorithme s’adapte aux flux de données. Puis, l’algorithme de KMP sera réalisé. Enfin, un algorithme de recherche étudié sera appliqué sur le motif de PWM. Dans chaque implémentation, la complexité de l’algorithme sera considérée. L’impact du style fonctionnel sur les performances sera aussi étudié. Ce fait me permet de me familiariser avec le langage et m’habituer à l’évaluation des performances. chr4 117471120 117472332 chr7 127472380 127473430 chr9 157473570 157474625
  14. 14. 9 2.4.1. Algorithme naïf (algorithme de Brute Force) Dans un premier temps, j’ai commencé résoudre le problème avec l’algorithme naïf de recherche d’un motif dans une chaine de caractères entrée au clavier. Par rapport à l’idée originale du Brute Force, une petite différence est que la recherche va continuer même quand une occurrence est trouvée. C’est-à-dire, la recherche va terminer si et seulement si on est à la fin de la chaine de caractères. La valeur de retour est une liste des occurrences du motif dans la chaine donnée. Cet algorithme est réalisé en style fonctionnel même en style impératif pour qu’on puisse faire le benchmark. Est-ce que l’algorithme naïf peut jouer le rôle de l’algorithme de recherche qu’on est en train de chercher ? Est-ce que cet algorithme peut s’adapter aux données entrées morceau par morceau ? On va trouver la réponse dans les parties suivantes. 2.4.2. Lecture de chaque caractère du fichier En fait, l’opération élémentaire de l’algorithme de recherche est la comparaison entre les caractères. Donc, au lieu d’occuper un caractère par son indice dans une chaine, on va l’occuper par sa position dans le fichier. Maintenant, cette position ainsi joue le rôle de l’indice de l’algorithme original. Précisément, quand l’indice augmente, on fait augmenter la position ; quand l’indice saute en arrière, la position est diminuée. Cependant, quand on doit traiter les données venant d’un flux, par exemple : un pipeline, on ne peut pas faire sauter la position en arrière. On ne peut rien faire d’autre qu’avancer. Comment on le résout ? 2.4.3. Construction d’une table d’états Une table d’états peut résoudre notre problème. On a trouvé que quand on occupe un nouveau caractère, le résultat potentiel de la recherche – succès ou échec – dépend partiellement du résultat de la recherche au moment où on occupe le précédent caractère. En effet, la recherche est potentiellement réussie si les comparaisons des k couples de caractères (un du motif, un du fichier) sont tous vrais. Si k = n, où n est la taille du motif, la recherche est certainement réussie. Au contraire, si la comparaison au i-ième couple est échouée, la recherche est également échouée. Autrement dit, au moment où on reçoit un nouveau caractère, le
  15. 15. 10 résultat temporaire de la recherche est la conjonction du résultat temporaire précédent et la comparaison d’un nouveau couple de caractères. Par conséquent, la recherche est toujours temporairement échouée jusqu’au moment la n-ième couple de caractères se correspond. La théorie de l’automate peut décrire exactement cet algorithme : un état change à l’état suivant grâce au caractère donné ; un mot est accepté s’il accède à l’état final. Précisément, on considère l’exemple suivant. En choisissant le motif «ABC», on a le graphe de transition : Supposons une chaine «ABABC» envoyée caractère par caractère. Les tables sont donc : Ne recevoir rien vrai faux faux faux  échec Recevoir «A» vrai vrai faux faux  échec Recevoir «B» vrai vrai vrai faux  échec Recevoir «A» vrai vrai faux faux  échec Recevoir «B» vrai faux vrai faux  échec Recevoir «C» vrai faux vrai faux  succès La table peut également résoudre notre problème. Par contre, cet algorithme semble insatisfaisant en raison de sa complexité. En effet, on souhaite avoir une complexité plus optimiste. Heureusement, on a un autre algorithme qui a une meilleure complexité et ne fait rien d’autre qu’avancer. Cet algorithme sera présenté dans la partie suivante. 2.4.4. Algorithme de KMP L’algorithme de KMP profite des résultats des itérations précédentes pour faire diminuer le nombre d’itérations inutiles. Cet algorithme améliore la complexité du programme en cas moyen. En effet, si la duplication des caractères dans le motif est souvent, on n’a pas besoin de faire la recherche 0 1 2 3 3 * * * * A B C
  16. 16. 11 caractère par caractère. Comme l’algorithme naïf, celui de KMP est implémenté en style fonctionnel et en style impératif. 2.4.5. Recherche d’un motif de PWM En biologie, une matrice poids-position (PWM) est souvent utilisée pour représenter un motif, en particulier ceux qui représentent les sites de fixation des protéines à l’ADN. PWM permet de représenter la préférence pour certain nucléotide à chaque position du motif. Un PWM comprend 4 lignes pour 4 types de nucléotide. Chaque ligne a n colonnes, où n est la longueur des séquences de DNA. La création d’un PWM est décrite comme suit. À partir d’une collection de séquences d’ADN, on va créer une matrice de fréquence de position en calculant les occurrences de chaque nucléotide à chaque position. Puis, un PWM se crée par le calcul de la probabilité des occurrences de chaque nucléotide dans la séquence. Précisément, on va considérer l’exemple suivant : Supposons des séquences de DNA : GAGGTATAC TCCGTAAGC CAGGTTGGA ACAGTCAGT AAGGTTATT TAGGTACGG ATGCTGACG CTAGTATAG TGTCTGAGC AAGGTAAGT Supposons qu’on connait les instances du motif, on a ainsi la matrice de fréquence correspondante : A 4 5 2 0 0 5 6 2 1 M = C 2 2 1 2 0 1 1 1 3 G 1 1 6 8 0 2 1 6 3 T 3 2 1 0 10 2 2 1 3
  17. 17. 12 On a donc le PWM : A 0.4 0.5 0.2 0 0 0.5 0.6 0.2 0.1 M = C 0.2 0.2 0.1 0.2 0 0.1 0.1 0.1 0.3 G 0.1 0.1 0.6 0.8 0 0.2 0.1 0.6 0.3 T 0.3 0.2 0.1 0 1 0.2 0.2 0.1 0.3 Dans la réalité, les éléments du PWM sont souvent transformés en formule : Mj,k = ln (Mj,k/0.25) La recherche des occurrences des PWM est analogue à algorithme naïf sauf qu’au lieu de comparer un caractère de la séquence à un caractère du motif, on va regarder le score du caractère de la séquence dans la matrice. Ces scores sont additionnés et on cherche les positions où les sommes dépassent un certain seuil. 2.5. Représentation des flux Le traitement en espace mémoire constant fait partie aussi dans cette session. En fait, je vais créer une représentation des flux de données. Le flux soit se participe dans le progrès de recherche, soit consomme et produit les items de la recherche. 2.5.1. Le type «générateur» L’idée ici est de représenter un processus qui produit une nouvelle donnée quand on lui demande. En particulier, la définition du type est écrite : type α gen = unit -> α option Le type option est utilisé pour représenter naturellement le résultat de la recherche : soit rien, soit quelque chose d’un type quelconque. Plus précisément, on va obtenir None si la séquence est terminée, ou Some x (x est du type α) si le générateur a encore des éléments. En utilisant ce type, le parsing du fichier FASTA est décrit comme un processus qui prend un générateur des chaines de caractères et retourne un générateur des items fasta. Plus précisément, ce processus est décrit : val fasta_parser : string gen -> fasta_item gen
  18. 18. 13 Ce type nous permet de représenter la recherche du motif comme un processus qui prend un générateur des chaines de caractères et retourne un générateur des entiers. Ce processus peut être décrit : val search_motif : string -> string gen -> int gen La recherche du motif dans un fichier FASTA est en fait la composition de ces deux processus. Donc, cette composition est un processus qui prend un générateur des items fasta et retourne un générateur des chaines de caractères et des entiers. Chaque chaine de caractère représente le nom de chromosome où on trouve l’occurrence. La recherche du motif dans un fichier est décrite : val search_motif_in_fasta : string -> fasta_item gen -> (string * int) gen En fait, cette solution n’est pas une idéale. En effet, les fonctions doivent s’écrire partiellement impérativement. Donc, cela n’empêche pas totalement les effets de bord. 2.5.2. La continuation Ici on voit un flux comme un processus consommant et produisant des items et on représente son état et les états suivant à l’aide d’une continuation. Cette idée peut se décrire comme suit : type (‘i, ‘o) pipe = | Need_input of ‘i option -> (‘i, ‘o) pipe | Has_output of ‘o * (‘i, ‘o) pipe | Done Avec ce type-là, le parsing du fichier FASTA est décrit : val fasta_parser : (string, fasta_item) pipe Ensuite, la recherche du motif est représentée comme : val search_motif : string -> (string, int) pipe La composition de ces deux processus est: val string -> (string, string * int) pipe
  19. 19. 14 On aura aussi un opérateur de composition, qui peut être décrit: ($$): (α, β) pipe -> (β, γ) pipe -> (α, γ) pipe Cependant, cette idée est difficile à réaliser. Dans le cas où on ne peut pas l’implémenter, la première idée est acceptable. 2.6. Application aux données On va appliquer l’algorithme de recherche d’un PWM au génome de la souris. On va d’abord télécharger le génome de souris sous la forme d’un fichier FASTA, puis envoyer cette information sous forme d’un flux à la recherche du motif, ensuite obtenir un flux de résultats et enfin l’écrire dans un fichier BED. Il s’agira de comparer ces positions à celles déterminées expérimentalement et de calculer les performances de prédictions. 2.7. Travaux réalisés et leurs résultats Pendant 1.5 mois, j’ai terminé une moitié du projet. En fait, j’ai implémenté l’algorithme de Brute-Force et l’algorithme de KMP en deux styles : fonctionnel et impérative. J’ai fait le benchmark pour évaluer leur performance. En plus, une table d’états est implémentée pour optimiser la performance de l’algorithme naïf. Le traitement du format FASTA est aussi réalisé. J’ai fait la lecture du fichier FASTA et stocké les données morceau par morceau. Enfin, j’ai travaillé sur la représentation les flux de données. J’ai réussi avec le type «générateur». Maintenant, je suis en train de travailler sur la deuxième idée. 2.7.1. Benchmark d’algorithme naïf et de KMP Dans le premier temps, le motif cherché et la chaine de caractères sont générés à partir des caractères alphabétiques. Dans le résultat obtenu, l’algorithme naïf du style fonctionnel performe mieux que celui du style impératif. Ce fait se produit aussi dans le cas de l’algorithme de KMP. En comparant les deux algorithmes, le temps d’exécution de l’algorithme naïf est plus court que celui de l’autre algorithme. Ce résultat est donc raisonnable. En fait, en profitant des caractères analogues dans le motif,
  20. 20. 15 l’algorithme fait diminuer le nombre total d’itérations. Donc, sa complexité en moyen est baissée. Cependant, dans notre benchmark, les caractères du motif sont générés parmi vingtaine caractères alphabétiques. Donc, peu répétitions des caractères se produit. L’algorithme de KMP ne peut pas ainsi réduire les itérations mais couter plus de temps pour créer son tableau. Dans ce test, cet algorithme est en pire cas. Avec un motif biologique, l’alphabet est beaucoup réduit et l’algorithme peut mieux tirer parti de la table. J’ai fait un changement pour que le motif et la chaine de caractères soient générés à partir de deux caractères. L’algorithme de KMP est alors plus efficace que l’autre. 2.7.2. Construction la table d’états On va initialiser une table de booléens de taille n + 1, où n est la taille du motif. Tous les éléments sauf le premier de la table sont faux. Cela est la table d’états quand on ne reçoit pas encore de caractère. Les tables suivantes sont construites : procédure contruire_table (donnée-résultat tab : tableau[0…n+1] de booléens, donnée motif : chaine de caractères, donnée c: caractère) {précondition : la précédente table initialisée, le motif de recherche et un caractère données} {post condition : la table d’états quand on reçoit le i-ième caractère} variables j : entier n : entier début n <- taille_motif (motif) pour j de n à 0 faire tab.(j) <- tab.(j - 1) ^ (comparaison (c, motif.(j))) fin pour fin Certainement, la procédure ci-dessus s’écrit fonctionnellement. Ainsi, cette fonction marche bien avec l’algorithme naïf. Malheureusement, cette fonction est parfois inutile. Le problème se produit quand on veut imprimer exactement la chaine de caractères où on trouve le motif. En fait, dans la biologie, quelques fois, peu différence dans les deux chaines de caractères est acceptable. Dans ce cas, on veut savoir
  21. 21. 16 explicitement la sous-chaine qui conduit à la correspondance. Donc, une table contient seulement l’état précédent ne suffit pas. 2.7.3. Lecture de fichier FASTA Ici, je vais aborder la lecture du fichier FASTA. Le fichier va être lu morceau par morceau. À chaque lecture, chaque caractère doit être considéré si cela appartient d’une description ou d’une séquence. En effet, la plupart des «>» représente une description. En plus, j’ai un drapeau pour signaler l’état (description ou séquence) du flux de données à chaque moment du parcours. Je stocke aussi une position qui marque le commencement de la description ou de la séquence. Si l’état du flux va être changé, c’est-à-dire une description (ou une séquence) est terminé, le précédent caractère ainsi appartient d’une séquence (ou une description), je sauvegarde tout ce que j’ai déjà parcouru à partir de la position de commence, puis je réinitialise la valeur de la position marquée. Si la chaine de caractères est terminée mais aucun signal de changement l’état n’est trouvé, je sauvegarde la chaine de caractères et change la position marquée sans faire changer l’état du flux. En effet, on a accepté que la séquence de DNA peut être sauvegarde en plusieurs morceaux, parce qu’elle est très longue. Par contre, le commentaire découpé peut se concaténer. Parce qu’il est court, le cout de concaténation est bas. 2.8. Travaux prévus Dans la deuxième partie du stage, les tâches suivantes seront faites : - la deuxième représentation du flux ; - l’écriture dans le fichier BED ; - l’implémentation de l’algorithme de recherche sur le motif de PWM ; - l’intégration des fonctions produites ; - la documentation.
  22. 22. 17 3. Retour sur expérience Dans cette partie, je vais indiquer les expériences que j’ai acquises pendant le stage. J’aborde en premier mes connaissances sur la programmation fonctionnelle et sur le langage OCaml. Je vais parler ensuite les autres compétences techniques que j’ai renforcées. Cette partie sera terminée par mes expériences professionnelles. 3.1. Apprentissage du langage OCaml et de la programmation fonctionnelle Après l’apprentissage, je peux répondre aux questions suivantes : Qu’est-ce que la programmation fonctionnelle ? Pourquoi utilise-t-on ce paradigme ? Quelles sont ses propriétés ? Pourquoi utilise-t-on l’OCaml ? Quels sont les avantages du langage ? 3.1.1. Programmation fonctionnelle La programmation fonctionnelle est un paradigme moderne de programmation dont l’unité de base est la fonction. C’est-à-dire, ce paradigme de programmation se base sur l’application de fonctions mathématiques. En fait, l’utilisateur donne l’expression ou la déclaration. Ainsi, le programme en profite pour évaluer les fonctions pour retourner le résultat correspondant. La programmation fonctionnelle ne permet pas la modification des variables ni le changement d’états. C’est la différence principale entre ce paradigme avec les autres, qui utilisent souvent les phrases impératives pour guider le programme en un ou plusieurs étapes obtenir le résultat. La programmation fonctionnelle possède plusieurs atouts. D’abord, la gestion d’espace mémoire se lance automatiquement. L’utilisateur donc n’a pas soucis du tas, de la pile et d’allocation de l’espace mémoire. Ensuite, l’utilisateur peut facilement prendre une fonction comme argument sans l’utilisation d’un pointeur de fonction (comme d’autres paradigmes). Cet avantage sera représenté plus clair dans les parties suivantes. La programmation fonctionnelle est aussi très utile pour les callbacks (utilisés dans les IHMs pour les boucles d'évènements) et très excellente pour exprimer des algorithmes génériques. Beaucoup de fonctions peuvent être appliquées avec n'importe quel type de données. Par exemple, la fonction List.map peut s’utiliser avec les listes des entiers, des caractères, des listes, etc.
  23. 23. 18 Les contraintes du sujet sont la dernière raison pour laquelle on choisit la programmation fonctionnelle. En effet, la plupart des fonctions de ce paradigme n’a aucun effet de bord. Donc, on peut utiliser les fonctions sans soucis d’une sorte d'état caché gardée. 3.1.2. Langage OCaml OCaml est un langage de programmation fonctionnelle. Dans l’OCaml, les types de données sont synthétisés et gérés par l’ordinateur. Le logiciel OCaml est offert et utilisable sur presque tous plate-forme Unix et Windows. Outre les avantages d’un langage de programmation fonctionnelle, OCaml garde aussi ses propres atouts. D’abord, il autorise les effets de bord au travers des références et des tableaux. OCaml est donc plus pratique parce que les fonctions avec une sorte d’état caché gardée sont parfois utile. Un autre avantage de l’OCaml est son évaluation stricte. Cette caractéristique rend ainsi un résultat plus raisonnable. Par contre, si l’utilisateur veut faire quelques choses bizarres, l’OCaml ne l’empêche pas. Enfin et surtout, l’OCaml attire une base d’utilisateurs importante grâce aux ses services hors de la programmation fonctionnelle. En effet, l’OCaml est très apprécié grâce au service de la programmation orientée d’objet et du système de type. 3.2. Compétences techniques acquises en OCaml Dans la programmation fonctionnelle, particulièrement dans le langage OCaml, il y a des nouvelles notions que nous utilisons rarement dans d’autres paradigmes et langages. En fait, quelques notions sont déjà connues. Par contre, je vais les montrer aussi dans cette partie, parce qu’elles sont les plus importantes et très utiles. 3.2.1. Variable non modifiable Dans la programmation fonctionnelle, surtout l’OCaml, on ne peut pas modifier les variables. Elles sont donc gérées automatiquement. Cela nous semble un massif avantage. Par contre, pour profiter cet atout, nous devons donner une valeur à chaque fois une variable initialisée. Cette propriété est donc contraire avec notre habitude : initialiser une variable, puis affecter sa valeur.
  24. 24. 19 Par exemple, une variable peut être initialisée : # let x = 5;; val x : int = 5 ou # let y = String.length “ABCD”;; val y: int = 4 Par contre, on ne peut pas écrire comme suivant : # let z ;; 3.2.2. Typage fort L’OCaml est un langage de typage fort. C’est-à-dire, les variables utilisées a totalement et seulement un type. Donc, les conversions implicites de types sont formellement interdites. On a un exemple : # let double x = x * 2 ;; val double : int -> int Dans cet exemple, on va multiplier un entier avec 2. Dans d’autres langages, si l’on veut la multiplication d’un nombre réel, on peut également utiliser cette fonction. Par contre, dans l’OCaml, cela n’est permis pas. On doit ainsi écrire une autre fonction : # let double_reel x = x *. 2. ;; val double_reel : float -> float On va appliquer la fonction ci-dessus : # double_reel 1. ;; :- float = 2. Si l’on écrit : # double_reel 2 ;; cela va rend une erreur.
  25. 25. 20 3.2.3. Inférence de type Grâce au typage fort, on a un mécanisme de trouver le type de la fonction. Ainsi, le compilateur trouve simplement le type le plus général qui puisse prendre l'expression. Par conséquent, une fonction ne s’exécute pas une fois que son type soit inapproprié. Supposons l’exemple suivant : # let isvowel c = match c with ‘a’ -> true | ‘e’ -> true | ‘i’ -> true | ‘o’ -> true | ‘u’ -> true | _ -> false ;; val isvowel : char -> bool Cette fonction va prendre un caractère et retourner vrai si c’est une voyelle. Si l’on écrit : # isvowel ‘’a’’;; cela va rendre une erreur, parce que ‘’a’’ n’est pas du type char. Pour évaluer le caractère a, on doit le mettre entre les apostrophes. 3.2.4. Fonction d’ordre supérieur Dans programmation fonctionnelle, on se permet d’utiliser une fonction en tant qu’argument dans une autre fonction. La fonction qui prend une autre fonction comme son argument est la fonction d’ordre supérieur. On a un exemple suivant : # let double ls = List.map ( fun x -> x * 2 ) ls ;; val double : int list -> (int -> int) -> int list # double [1 ; 2 ; 3] ;; :- int list = [2 ; 4 ; 6] Dans cet exemple, la fonction (fun x -> x * 2) prend un argument x et retourne x * 2. J’ai appliqué cette fonction pour tous les éléments d’une liste d’entiers
  26. 26. 21 par l’appel de la fonction map. C’est-à-dire, la fonction map prend la fonction (fun x -> x * 2) comme argument. Donc, la fonction map s’appele une fonction d'ordre supérieur. 3.2.5. Fonction récursive et fonction récursive terminale Une fonction est récursive si cette fonction utilise lui-même pour obtenir le résultat. La fonction suivant est récursive : # let rec length l = match l with [] -> 0 |_ ::t -> 1 + length t Une fonction récursive est dit terminale si le résultat du dernier appel de cette fonction rend exactement la valeur de retour du programme. On a aussi la version récursive terminale de la fonction ci-dessus comme suivant : # let rec length_inner l n = match l with [] -> n | _ ::t -> length_inner t (n + 1) # let rec length l = length_inner l 0 Dans programmation fonctionnelle, on utilise souvent les fonctions récursives. La complexité de la fonction récursive est meilleure que celle non- récursive. Cependant, grâce à la comparaison ci-dessus, les fonctions récursives terminales sont les plus appréciées dans OCaml. 3.2.6. Programmation impérative dans OCaml Comme on le sait, la programmation fonctionnelle ne permet pas de changer les variables. Par contre, les utilisateurs d’OCaml peuvent également écrire impérativement leurs programmes. L’OCaml fournit une construction s’appelé référence qui est une boite dans laquelle on peut stocker une valeur. Nous avons un exemple suivant :
  27. 27. 22 # let x = ref 0 ;; val x : int ref = {contents = 0} Pour extraire le contenu courant d’une référence, on utilise l’opérateur «!», comme l’exemple suivant : # let p = !x ;; val p : int = 0 Pour mettre à jour le contenu d’un référence, on utilise l’opérateur «:=». # x := 50 ;; - : unit = () Une nouvelle valeur est mise dans la variable x, puis rien n’est retourné. Le type unit est ainsi pour but de représenter «rien». 3.2.7. Type optionnel Dans réalité, nous avons d’un type qui représente soit rien, soit quelque chose du type «α». Donc, l’OCaml nous permet de définir ce type comme suivant : # type ‘a option = None | Some of ‘a ;; type ‘a option = None | Some of ‘a Un exemple de l’utilisation de ce type: # let nothing = None ;; val nothing : ‘a option = None # let number = Some 50;; val number: int option = Some 50 3.2.8. Application partielle On a une fonction comme suivant : # let plus x y = x + y ;; val plus : int –> int -> int Du côté informatique, cette fonction est une fonction qui prend deux arguments x et y et retourne x + y.
  28. 28. 23 Du côté mathématique, cette fonction est une fonction qui prend un entier x et renvoie une fonction qui si l’on lui donne un entier y, retournera entier x + y. Cette fonction peut également être écrite comme suivant : # let plus x = fun y -> x + y ;; val plus : int -> (int -> int) La partie (int -> int) peut ainsi décrite comme une fonction qui prend un entier et retourne un entier. Donc, si l’on écrit : # let f = plus 1 ;; val f : int -> int on a surement une fonction qui prend un entier et retourne son successeur. La fonction f est appelé une application partielle de la fonction plus, parce f a appliqué la fonction plus avec 1 et retourne une fonction du type int -> int. 3.3. Autres compétences techniques Outre la technique de programmation fonctionnelle, j’améliore ma compétence d’analyse de la complexité. En effet, beaucoup d’éléments contribuent à la performance d’un programme : algorithme, codage, système d’exploitation, etc. Donc, pour évaluer un programme, il me faut remarquer plusieurs aspects. Je me renforce aussi en la communication avec le fichier. En effet, cette mission me donne une grande occasion pour faire la lecture et l’écriture dans les fichiers. Ce stage est aussi un cas concret dont j’utilise la connaissance de l’automate et des mathématiques. En fait, les fonctions mathématiques sont continument utilisées pendant ce stage. Beaucoup de problèmes peuvent donc être résolus de manière fonctionnelle. J’ai aussi l’occasion d’utiliser le GIT, un gestionnaire des versions du code. Outre la connaissance de base, mon tuteur m’aide beaucoup sur le mécanisme de diviser pour régner et me donne une vue générale du problème. Beaucoup de problèmes de grande taille peuvent se résoudre en plusieurs sous-problèmes analogues. Cette technique est très importante pour la construction des grands programmes. 3.4. Côté professionnel J’ai l’environnement pour pratiquer mon français. En plus, j’ai beaucoup de chances de discussion et d’explication avec mon tuteur sur le thème scientifique. Le niveau de mon sens critique est aussi meilleur.
  29. 29. 24 Conclusion Après une moitié du stage, quelques conclusions peuvent être tirées. D’abord, le stage au sein de PRABI me permet de découvrir cette organisation et son fonctionnement. En effet, les centres de recherche comme PRABI sont motivés par le développement de la science. En outre, je comprends bien la connexion étroite entre les composants de recherche. En effet, un projet de recherche doit être travaillé par plusieurs équipes. C’est la raison pour laquelle les grands changements se produisent. Ensuite, travailler dans un département de bioinformatique m’aide à trouver l’harmonie entre deux domaines scientifiques : biologie et informatique. En fait, l’informatique valorise des activités de recherche biologique. En reverse, la biologie crée l’environnement pratique pour que les innovations informatiques soient utilisées. Après, pendant 1.5 mois, j’ai appris un nouveau paradigme de programmation et un langage moderne. En outre, j’ai travaillé partiellement sur le sujet de recherche d’un motif en espace mémoire constant dans un génome. Mon travail contribue ainsi une petite partie à la recherche scientifique, surtout la recherche en bioinformatique. Ce stage m’aide aussi à renforcer mes savoirs théoriques. En effet, j’améliore ma compétence d’analyse de la complexité. En plus, je maitrise de la technique de lecture et d’écriture des fichiers. En outre, j’ai un nouveau point de vue sur l’usage de l’automate. Ainsi, je connais bien le rôle de la connaissance théorique et prends conscience des conditions réelles d'exercice. Dernièrement, en travaillant hors de l’environnement informatique, je trouve aussi l’importance de la technologie d’information dans la vie, particulièrement dans le domaine de recherche. Donc, l’informatique est vraiment une évolution du monde. Comme tous les jeunes, j’aimerais bien contribuer et m’attacher à ce domaine scientifique.
  30. 30. 25 Bibliographie 1. Livre WHITINGTON John. OCaml from the very beginning. Royaume-Uni. Coherent Press. 2013. 194 pages. 2. Sites internet consultés https://fr.wikipedia.org/wiki/Programmation_fonctionnelle https://ocaml.org/learn/tutorials/functional_programming.fr.html http://caml.inria.fr/about/history.en.html http://caml.inria.fr/resources/doc/faq/general.en.html http://caml.inria.fr/pub/docs/manual-ocaml/libref/index.html http://pbil.univ-lyon1.fr/members/veber/ocaml/fold.html https://fr.wikipedia.org/wiki/FASTA_(format_de_fichier) https://fr.wikipedia.org/wiki/BED_(format_de_fichier) https://en.wikipedia.org/wiki/Position_weight_matrix https://blogs.janestreet.com/core_bench-micro-benchmarking-for-ocaml/ https://github.com/c-cube/gen

×