├── photos ├── photo_0.jpg ├── photo_1.jpg ├── photo_2.jpg ├── photo_3.jpg ├── photo_4.jpg └── photo_5.jpg ├── images ├── arborescence.png ├── histogramme0.pdf ├── histogramme1.pdf ├── hubble_flou.jpeg ├── hubble_vision.png └── pixels_adjacents.png ├── environment.yml ├── README.md ├── Exponentiation_Rapide_Eleve.ipynb ├── Synthese_Algo_S5.ipynb ├── Chronometrage_Eleve.ipynb ├── Algorithmes_De_Tri_Eleve.ipynb ├── Compression_Sans_Perte_Eleve.ipynb ├── BPE_Tokenization_Eleve.ipynb ├── Chronometrage_Prof.ipynb └── BPE_Tokenization_Prof.ipynb /photos/photo_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpaviot/Algo/master/photos/photo_0.jpg -------------------------------------------------------------------------------- /photos/photo_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpaviot/Algo/master/photos/photo_1.jpg -------------------------------------------------------------------------------- /photos/photo_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpaviot/Algo/master/photos/photo_2.jpg -------------------------------------------------------------------------------- /photos/photo_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpaviot/Algo/master/photos/photo_3.jpg -------------------------------------------------------------------------------- /photos/photo_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpaviot/Algo/master/photos/photo_4.jpg -------------------------------------------------------------------------------- /photos/photo_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpaviot/Algo/master/photos/photo_5.jpg -------------------------------------------------------------------------------- /images/arborescence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpaviot/Algo/master/images/arborescence.png -------------------------------------------------------------------------------- /images/histogramme0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpaviot/Algo/master/images/histogramme0.pdf -------------------------------------------------------------------------------- /images/histogramme1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpaviot/Algo/master/images/histogramme1.pdf -------------------------------------------------------------------------------- /images/hubble_flou.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpaviot/Algo/master/images/hubble_flou.jpeg -------------------------------------------------------------------------------- /images/hubble_vision.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpaviot/Algo/master/images/hubble_vision.png -------------------------------------------------------------------------------- /images/pixels_adjacents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpaviot/Algo/master/images/pixels_adjacents.png -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - pip 3 | - pip: 4 | - matplotlib 5 | - scikit-learn 6 | - lazypredict 7 | - statsmodels 8 | - pandas 9 | - seaborn 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Si la machine hôte ne dispose pas des bibliothèques requises, possibilité d'exécuter une instance de JupyterLab exécutée sur un container docker en ligne: 2 | 3 | https://mybinder.org/v2/gh/tpaviot/binderenv/HEAD?filepath= 4 | 5 | ### Extrait du référentiel 6 | 7 | 3.3.4. Ressource R5.Real.04 : Qualité algorithmique 8 | 9 | Compétences ciblées : 10 | – Développer — c’est-à-dire concevoir, coder, tester et intégrer — une solution informatique pour un client. 11 | – Proposer des applications informatiques optimisées en fonction de critères spécifiques : temps d’exécution, précision, consommation de ressources.. 12 | 13 | SAÉ au sein desquelles la ressource peut être mobilisée et combinée : 14 | – SAÉ 5.Real.01 | Développement avancé 15 | 16 | Descriptif : 17 | L’objectif de cette ressource est de permettre aux développeurs d’évaluer la qualité algorithmique de leur code à travers l’utilisation d’outils et de métriques. 18 | 19 | Savoirs de référence étudiés 20 | – Complexité des algorithmes 21 | – Métriques (par ex. : temps d’exécution, occupation mémoire, montée en charge...) 22 | – Utilisation d’outil d’audit 23 | 24 | Prolongements suggérés 25 | – Revue de code 26 | 27 | Apprentissages critiques ciblés : 28 | – AC31.01 | Choisir et implémenter les architectures adaptées 29 | – AC32.01 | Anticiper les résultats de diverses métriques (temps d’exécution, occupation mémoire, montée en charge...) 30 | – AC32.02 | Profiler, analyser et justifier le comportement d’un code existant 31 | 32 | Mots clés : 33 | Complexité algorithmique – Métriques – Profiling 34 | Volume horaire : 35 | Volume horaire défini nationalement : 12 heures dont 3 heures de TP 36 | -------------------------------------------------------------------------------- /Exponentiation_Rapide_Eleve.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "1ea51db2", 6 | "metadata": {}, 7 | "source": [ 8 | "## Complexité des algorithmes - Exponentiation\n", 9 | "On propose, dans ce TP, de comparer deux algorithmes permettant de calculer $n^k$ avec $(n,k) \\in (\\N \\dot \\N)$. Ces deux algorithmes sont présentés ci-dessous (tiré de http://www.bibmath.net/dico/index.php?action=affiche&quoi=./e/exponentiationrapide.html).\n", 10 | "\n", 11 | "#### Algorithme 1\n", 12 | "```bash\n", 13 | "ENTREE : n un entier strictement positif, k un entier naturel\n", 14 | "SORTIE : n puissance k\n", 15 | "DEBUT\n", 16 | " i ← 0\n", 17 | " resultat ← 1\n", 18 | " TANT QUE i != k FAIRE\n", 19 | " resultat ← resultat * n\n", 20 | " i ← i + 1\n", 21 | " FIN TANT QUE\n", 22 | " RENVOYER résultat\n", 23 | "FIN\n", 24 | "```\n", 25 | "\n", 26 | "#### Algorithme 2\n", 27 | "```bash\n", 28 | "ENTREE : n un entier strictement positif, k un entier naturel\n", 29 | "SORTIE : n puissance k\n", 30 | "DEBUT\n", 31 | " puissance ← n\n", 32 | " resultat ← 1\n", 33 | " TANT QUE k != 0\n", 34 | " SI k est impair FAIRE\n", 35 | " resultat ← resultat * puissance\n", 36 | " FIN SI\n", 37 | " puissance ← puissance * puissance\n", 38 | " k ← partie entière de la division euclidienne de k par 2\n", 39 | " FIN TANT QUE\n", 40 | " RENVOYER resultat\n", 41 | "FIN\n", 42 | "```\n", 43 | "\n", 44 | "On appelle $T_1 (k)$ et $T_2 (k)$ le nombre d’opérations nécessaires pour réaliser le calcul de $n^k$ avec l’algorithme 1 et 2 respectivement." 45 | ] 46 | }, 47 | { 48 | "cell_type": "markdown", 49 | "id": "c980a740", 50 | "metadata": {}, 51 | "source": [ 52 | "#### Question 1.\n", 53 | "Programmer ces deux algorithmes dans deux fonctions Python différentes. Vérifier qu’elles renvoient les mêmes résultats." 54 | ] 55 | }, 56 | { 57 | "cell_type": "markdown", 58 | "id": "4e55fef9", 59 | "metadata": {}, 60 | "source": [ 61 | "#### Question 2.\n", 62 | "Modifier ces deux fonctions pour qu’elles renvoient le nombre d’opérations nécessaires (additions/soustractions/multiplications/divisions/affectations/comparaisons etc.) à la réalisation de l’algorithme." 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "id": "602dbd51", 68 | "metadata": {}, 69 | "source": [ 70 | "#### Question 3.\n", 71 | "Sur un graphique maptloblib, pour $n=2$, tracer $T_1 (k)$ et $T_2 (k)$ pour $k \\in ⟦1,40⟧$. Déterminer la complexité algorithmique des deux algorithmes. Vous tracerez la fonction dominante permettant de justifier la complexité en $O$ (notation Landau)." 72 | ] 73 | } 74 | ], 75 | "metadata": { 76 | "kernelspec": { 77 | "display_name": "Python 3 (ipykernel)", 78 | "language": "python", 79 | "name": "python3" 80 | }, 81 | "language_info": { 82 | "codemirror_mode": { 83 | "name": "ipython", 84 | "version": 3 85 | }, 86 | "file_extension": ".py", 87 | "mimetype": "text/x-python", 88 | "name": "python", 89 | "nbconvert_exporter": "python", 90 | "pygments_lexer": "ipython3", 91 | "version": "3.9.0" 92 | } 93 | }, 94 | "nbformat": 4, 95 | "nbformat_minor": 5 96 | } 97 | -------------------------------------------------------------------------------- /Synthese_Algo_S5.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "84b53e56-ca3b-496b-a647-554d903e896d", 6 | "metadata": {}, 7 | "source": [ 8 | "# Synthèse Algorithmique S5\n", 9 | "\n", 10 | "Le module de Qualité Algorithmique a abordé, suivant différents points de vue liés au cas d'utilisation, les questions liées à l'algorithmique de manière générale : adéquation du résutat fourni par l'algorithme avec le résultat attendu, mesure du temps d'exécution, consommation mémoire pour l'exécution de l'algorithme.\n", 11 | "\n", 12 | "Les cas d'utilisation ont été les suivants : traitement d'images, compression de données, tri, encodage de texte, data mining.\n", 13 | "\n", 14 | "A chaque fois, le travail proposé dans les activités pratiques était formulé de la manière suivante : une introduction à la problématique, un questionnement de difficulté progressive abordant différentes possibilités pour accomplir un même objectf (i.e. différents algorihtmes) et comparaison de leurs performances en terme de qualité et de consommation de ressources (principalement le temps CPU).\n", 15 | "\n", 16 | "## Travail attendu\n", 17 | "\n", 18 | "Vous devez montrer votre capacité à :\n", 19 | "\n", 20 | "* identifier un problème d'algorithmique à l'origine d'un problème ou d'une performance à améliorer (soit sous l'angle de la qualité, soit sous celui de la consommation de resources) ;\n", 21 | "\n", 22 | "* identifier et mettre en oeuvre plusieurs techniques pour illustrer et/ou résoudre le problème ;\n", 23 | "\n", 24 | "* évaluer les performances (qualité, resources) et comparer ;\n", 25 | "\n", 26 | "* synthétiser et communiquer votre travail.\n", 27 | "\n", 28 | "## Contexte :\n", 29 | "\n", 30 | "Le contexte est celui de votre projet SAé en cours. Vous devez identifier et choisir un problème d'algorithmique que vous rencontrez dans votre SAé, ou qui est présent dans votre SAé sans que vous l'ayez a priori identifié. Par exemple, si vous travaillez sur la conception d'une interface utilisateur, vous n'êtes pas nécessairement confronté directement à un problème d'algorithmique, mais vous utilisez des solutions développées par d'autres (placement automatique des fenêtres sur l'écran, gestion des événements souris dans des files etc.).\n", 31 | "\n", 32 | "Le sujet que vous choisissez doit être en lien direct avec votre SAé.\n", 33 | "\n", 34 | "## Modalités pratiques :\n", 35 | "\n", 36 | "En terme de contenu, vous devez:\n", 37 | "\n", 38 | "* présenter un sujet d'algorithmique en lien avec votre SAé, exposer le lien avec votre projet que vous aurez rappelé au préalable en 10 lignes de texte maximum ;\n", 39 | "\n", 40 | "* dresser une description du problème en une dizaine de lignes maximum ;\n", 41 | "\n", 42 | "* proposer trois algorithmes qui permettent d'atteindre l'objectif (résoudre le problème) de manière différente ;\n", 43 | "\n", 44 | "* **mesurer**/présenter les mesures (avec des tableaux, des courbes etc.) ;\n", 45 | "\n", 46 | "* **comparer**/conclure quant au problème qui se pose et au choix à faire par rapport à l'étude précédente.\n", 47 | "\n", 48 | "C'est la **mesure**/la **comparaison** qui fait l'objet de ce travail et qui doit être au coeur de votre communication.\n", 49 | "\n", 50 | "## Contraintes:\n", 51 | "\n", 52 | "* vous pouvez choisir le langage que vous voulez ;\n", 53 | "\n", 54 | "* le code doit pouvoir être compilé et/ou exécuté sur machine Linux distribution Ubunbu 22.04. Vous pouvez choisir les bibliothèques que vous souhaitez, à condition qu'une information soit donnée concernant l'installation s'il s'agit de bibliothèques de développement qui ne font pas partie de la distribution standard.\n", 55 | "\n", 56 | "* vous pouvez travailler seul ou à deux, pas plus ;\n", 57 | "\n", 58 | "* vous indiquerez votre nom dans l'en-tête du fichier ;\n", 59 | "\n", 60 | "* le travail doit être original (non produit dans un autre cours, ou non vu autre part) ;\n", 61 | "\n", 62 | "* sujets d'algorithmiques déjà vus à ne pas traiter : cryptage, algorithmes de tri. Je veux apprendre des choses en lisant votre travail.\n", 63 | "\n", 64 | "* aucune faute d'orthographe dans votre compte-rendu. Phrases simples rédigées au présent de l'indicatif.\n", 65 | "\n", 66 | "## Livrable :\n", 67 | "* **un seul** fichier source, dans le langage que vous voulez, incluant des commentaires éventuels. Pas de limite de taille ;\n", 68 | "\n", 69 | "* **un seul** document de description annexe de **exactement 4 pages au format A4** au format PDF seulement (pas de word ou dot ou txt) ;\n", 70 | "\n", 71 | "(si vous utilisez python, vous pouvez condenser les deux premiers points dans un notebook jupyter) ;\n", 72 | "\n", 73 | "* les deux fichiers remis dans Teams dans une archive zip : NOM1_NOM_2_Algo_S5_Final.zip\n", 74 | "\n", 75 | "## Calendrier/jalons :\n", 76 | "* un jalon intermédiaire pour valider les deux premiers points (description du contexte et de l'algorithme choisi) pour le XX/12/2023\n", 77 | "\n", 78 | "* un jalon final pour la remise du document pour le XX/12/2023.\n", 79 | " " 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": null, 85 | "id": "758ca9c6-65f4-4505-a353-2f2092d20a04", 86 | "metadata": {}, 87 | "outputs": [], 88 | "source": [] 89 | } 90 | ], 91 | "metadata": { 92 | "kernelspec": { 93 | "display_name": "Python 3 (ipykernel)", 94 | "language": "python", 95 | "name": "python3" 96 | }, 97 | "language_info": { 98 | "codemirror_mode": { 99 | "name": "ipython", 100 | "version": 3 101 | }, 102 | "file_extension": ".py", 103 | "mimetype": "text/x-python", 104 | "name": "python", 105 | "nbconvert_exporter": "python", 106 | "pygments_lexer": "ipython3", 107 | "version": "3.9.0" 108 | } 109 | }, 110 | "nbformat": 4, 111 | "nbformat_minor": 5 112 | } 113 | -------------------------------------------------------------------------------- /Chronometrage_Eleve.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "df55d0a1-aa03-418f-9e4e-d51c332fe075", 6 | "metadata": { 7 | "jp-MarkdownHeadingCollapsed": true 8 | }, 9 | "source": [ 10 | "## Mesure du temps d'exécution : la méthode naïve avec un chronomètre\n", 11 | "L'objectif de cette courte activité est de mesurer le temps d'exécution de quelques fonctions qui implémentent des algorithmes sur des chaînes de caractères ou des nombres.\n", 12 | "\n", 13 | "La technique utilisée est la suivante : on relève la valeur d'un chronomètre juste avant et juste après l'appel à la fonction et on czalcule la différece, appelée `duree`.\n", 14 | "\n", 15 | "Si le temps d'exécution est très court, on effectue $N$ fois l'appel à la fonction à l'aide d'une boucle, et on divise la durée totale par N :\n", 16 | "\n", 17 | "$ duree = \\frac{N}{t_f - t_i}$" 18 | ] 19 | }, 20 | { 21 | "cell_type": "markdown", 22 | "id": "2ace14b4-160a-4712-9c32-b1ac4034ca2a", 23 | "metadata": {}, 24 | "source": [ 25 | "## Utilisation d'un chronomètre avec Python\n", 26 | "On utilisera le modèle suivant pour mesurer la durée d'une opération :\n", 27 | "```python\n", 28 | "from time import perf_counter\n", 29 | "\n", 30 | "t_init = perf_counter()\n", 31 | "\n", 32 | "# ce dont on souhaite mesurer la durée\n", 33 | "\n", 34 | "t_final = perf_counter()\n", 35 | "duree = t_final - t_init\n", 36 | "\n", 37 | "print(f\"Durée : {duree} secondes\")\n", 38 | "```\n", 39 | "\n", 40 | "\n", 41 | "## Utilisation d'un chronomètre avec c++\n", 42 | "On utilisera le modèle suivant pour mesurer la durée d'une opération :\n", 43 | "```c++\n", 44 | "#include \n", 45 | "#include \n", 46 | "#include \n", 47 | "#include \n", 48 | "\n", 49 | "int main() {\n", 50 | "\tusing namespace std::chrono;\n", 51 | "\n", 52 | "\thigh_resolution_clock::time_point t_init = high_resolution_clock::now();\n", 53 | "\t\n", 54 | " // ce dont on veut mesurer la durée\n", 55 | " \n", 56 | "\thigh_resolution_clock::time_point t_final = high_resolution_clock::now();\n", 57 | "\tduration duree = duration_cast>(t_final - t_init);\n", 58 | "\tstd::cout << \"Duree =\" << duree.count() << \" secondes\" << std::endl ;\n", 59 | "\n", 60 | "return 0;\n", 61 | "}\n", 62 | "```" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "id": "08380ecd-0ca1-4075-9a87-049419ae8a1b", 68 | "metadata": {}, 69 | "source": [ 70 | "## Objectif du TP\n", 71 | "L'objectif de cette activité est de comparer les implémentations python et c++ pour des algorithmes classiques, avec un criètre de temps d'exécution." 72 | ] 73 | }, 74 | { 75 | "cell_type": "markdown", 76 | "id": "8e5b0e46-6adc-42f2-8057-66ce37b80a3f", 77 | "metadata": {}, 78 | "source": [ 79 | "## Rappel : Exécution d'un programme c++\n", 80 | "On rappelle qu'on utilise le compilateur `g++` pour produire les exécutables, dont la syntaxe est la suivante :\n", 81 | "\n", 82 | "```bash\n", 83 | "$ g++ fichier_source.cpp -o executable\n", 84 | "```\n", 85 | "\n", 86 | "vérifier que l'exécutable a été correctement généré avec la commande suivante :\n", 87 | "```bash\n", 88 | "$ ls\n", 89 | "```\n", 90 | "puis exécuter le programme avec la commande :\n", 91 | "```\n", 92 | "$ ./executable\n", 93 | "```" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": null, 99 | "id": "bbd16673-06bb-4e11-897d-995c03bdd298", 100 | "metadata": {}, 101 | "outputs": [], 102 | "source": [ 103 | "import matplotlib.pyplot as plt" 104 | ] 105 | }, 106 | { 107 | "cell_type": "markdown", 108 | "id": "defa556f-602c-45f8-9e13-f46e2904835a", 109 | "metadata": {}, 110 | "source": [ 111 | "### Activité 1. Mesure du temps nécessaire pour comparer deux chaines de 10 caractères avec Python - Méthode `perf_counter`\n", 112 | "* En utilisant un chronomètre comme expliqué plus haut, effectuer 10000 (dix mille) mesures du temps nécessaire pour comparer deux chaînes de 10 caractères dont seul le dernier diffère\n", 113 | "* déterminer le temps mini, le temps maxi, le temps moyen\n", 114 | "* représenter sur un graphique l'ensemble de ces temps de calcul\n", 115 | "* que constatez-vous ?" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": null, 121 | "id": "059a3497-ccbb-403c-a710-68467baeeaed", 122 | "metadata": { 123 | "scrolled": true 124 | }, 125 | "outputs": [], 126 | "source": [ 127 | "# votre code ici" 128 | ] 129 | }, 130 | { 131 | "cell_type": "markdown", 132 | "id": "894f103b-86c3-47ee-a88e-087d20aaffbe", 133 | "metadata": {}, 134 | "source": [ 135 | "### Activité 2. Mesure du temps nécessaire pour calculer le sinus d'un nombre flottant avec Python - Méthode `time_it`\n", 136 | "\n", 137 | "En utilisant le module `timeit`, \n", 138 | "```python\n", 139 | "timer = timeit.Timer(lambda : \"abcdefghij\" == \"abcdefghik\")\n", 140 | "elapsed = timer.timeit(N)\n", 141 | "```\n", 142 | "effectuer la mesure et trouver le temps moyen" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": null, 148 | "id": "7b782733-73d5-4e8f-a6b1-2c15585ca5fb", 149 | "metadata": {}, 150 | "outputs": [], 151 | "source": [ 152 | "# votre code ici" 153 | ] 154 | }, 155 | { 156 | "cell_type": "markdown", 157 | "id": "80f188cb-1d7a-40a6-8890-95b05c8c7855", 158 | "metadata": {}, 159 | "source": [ 160 | "### Activité 3. Mesure du temps nécessaire pour calculer un sinus avec la méthode cProfile\n", 161 | "\n", 162 | "cProfile est le profiler intégré à Python.\n", 163 | "```python\n", 164 | "import cProfile\n", 165 | "\n", 166 | "def ff():\n", 167 | " for i in range(N):\n", 168 | " \"abcdefghij\" == \"abcdefghik\"\n", 169 | "cProfile.run('ff()')\n", 170 | "```" 171 | ] 172 | }, 173 | { 174 | "cell_type": "code", 175 | "execution_count": null, 176 | "id": "81529a8c-e767-4949-9bb1-e0bff35e8286", 177 | "metadata": {}, 178 | "outputs": [], 179 | "source": [ 180 | "# votre code ici" 181 | ] 182 | }, 183 | { 184 | "cell_type": "markdown", 185 | "id": "1bb9c099-c26d-44e9-a7f6-db67ebd44512", 186 | "metadata": {}, 187 | "source": [ 188 | "### Décorateur `time_it`" 189 | ] 190 | }, 191 | { 192 | "cell_type": "code", 193 | "execution_count": null, 194 | "id": "9ac01229-bc31-46fe-9597-8c700e92a533", 195 | "metadata": {}, 196 | "outputs": [], 197 | "source": [ 198 | "import time\n", 199 | "import random\n", 200 | "\n", 201 | "def timeit(func):\n", 202 | " def wrapper(*args, **kwargs):\n", 203 | " start = time.perf_counter()\n", 204 | " result = func(*args, **kwargs)\n", 205 | " end = time.perf_counter()\n", 206 | " elapsed = end - start\n", 207 | " print(f'Time taken: {elapsed:.6f} seconds')\n", 208 | " return result\n", 209 | " return wrapper\n", 210 | "\n", 211 | "@timeit\n", 212 | "def calculate_pi(n):\n", 213 | " \"\"\"Calculate and return an approximation of pi using the Monte Carlo method.\"\"\"\n", 214 | " inside = 0\n", 215 | " for i in range(n):\n", 216 | " x = random.uniform(-1, 1)\n", 217 | " y = random.uniform(-1, 1)\n", 218 | " if x ** 2 + y ** 2 <= 1:\n", 219 | " inside += 1\n", 220 | " pi = (inside / n) * 4\n", 221 | " return pi\n", 222 | "\n", 223 | "pi = calculate_pi(1000000)\n", 224 | "print(pi)" 225 | ] 226 | }, 227 | { 228 | "cell_type": "markdown", 229 | "id": "77bcd7a0-3e30-4dce-b9e7-728d5cf59b17", 230 | "metadata": {}, 231 | "source": [ 232 | "### Activité 4. Comparaison c++/python\n", 233 | "En utilisant un chronomètre c++, comparer les résultats obtenus avec Pythoni pour l'activité 1. Note: Vous pourrez augmenter N." 234 | ] 235 | } 236 | ], 237 | "metadata": { 238 | "kernelspec": { 239 | "display_name": "Python 3 (ipykernel)", 240 | "language": "python", 241 | "name": "python3" 242 | }, 243 | "language_info": { 244 | "codemirror_mode": { 245 | "name": "ipython", 246 | "version": 3 247 | }, 248 | "file_extension": ".py", 249 | "mimetype": "text/x-python", 250 | "name": "python", 251 | "nbconvert_exporter": "python", 252 | "pygments_lexer": "ipython3", 253 | "version": "3.9.0" 254 | } 255 | }, 256 | "nbformat": 4, 257 | "nbformat_minor": 5 258 | } 259 | -------------------------------------------------------------------------------- /Algorithmes_De_Tri_Eleve.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "09534300", 6 | "metadata": {}, 7 | "source": [ 8 | "## Algorithmique - Comparaison d'algorithmes de Tri" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "c3822aac", 14 | "metadata": {}, 15 | "source": [ 16 | "Un algorithme de tri permet d'organiser une collection d'objets selon une relation d'ordre déterminée. Les objets à trier sont des éléments d'un ensemble muni d'un ordre total, c'est-à-dire qu'il est possible de comparer n'importe lesquels de deux éléments de l'ensemble avec l'opérateur \"supérieur à\" ou \"inférieur à\".\n", 17 | "\n", 18 | "D'ensemble muni d'une relation d'ordre : les entiers, les les réels, les dates, les heures, les chaînes de caractère (ordre \"alphabétique\"). Il existe bien d'autres relations d'ordre qui peuvent être très spécifiques (par exemple, dans un jeu de 32 cartes, le classement de la carte la plus faible à la plus forte dépend de la règle du jeu, et différera suivant que l'on joue à la belote ou à la bataille).\n", 19 | "\n", 20 | "Un algorithme de tri a pour objectif de classer un ensemble d'éléments suivant cette relation d'ordre, par exemple : du plus petit au plus grand pour un ensemble d'entiers, du plus ancien au plus récent pour un ensemble de dates, classement dans l'ordre alphabétique pour un ensemble de chaînes de caractères.\n", 21 | "\n", 22 | "Les algorithmes de tris sont massivement utilisés en informatique. L'amélioration de leurs performances fait l'objet de travaux de développements incessants.\n", 23 | "\n", 24 | "Dans ce TP/TD, on cherche à classer dans l'ordre croissant une liste aléatoire d'entiers, en utilisant 3 algorithmes de tri différents : tri à bulle, tri par insertion, tri rapide. L'objectif est de comparer les performances de ces trois algorithmes. " 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "id": "49289eee", 30 | "metadata": {}, 31 | "source": [ 32 | "### Préparation des données\n", 33 | "Dans cette cellule préliminaire, on créée une liste aléatoire de $N$ nombres entiers choisis entre $0$ et $N-1$." 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "id": "3d6e4b4c-646c-40f6-b194-4082f3e5965c", 39 | "metadata": {}, 40 | "source": [ 41 | "### Question préliminaire\n", 42 | "Expliquer, en insérant les commentaires pertinents, ce que fait le code de la cellule suivante." 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 1, 48 | "id": "9e5f426a", 49 | "metadata": {}, 50 | "outputs": [ 51 | { 52 | "name": "stdout", 53 | "output_type": "stream", 54 | "text": [ 55 | "Liste de 20 entiers à trier: [16, 10, 13, 14, 6, 0, 19, 2, 17, 18, 11, 1, 5, 8, 7, 4, 9, 15, 3, 12]\n" 56 | ] 57 | } 58 | ], 59 | "source": [ 60 | "N = 20\n", 61 | "import random\n", 62 | "\n", 63 | "def creer_liste_aleatoire(n):\n", 64 | " lst = list(range(n))\n", 65 | " random.shuffle(lst)\n", 66 | " return lst\n", 67 | "\n", 68 | "list_to_sort = creer_liste_aleatoire(N)\n", 69 | "print(f\"Liste de {N} entiers à trier: {list_to_sort}\")" 70 | ] 71 | }, 72 | { 73 | "cell_type": "markdown", 74 | "id": "1e5c436f", 75 | "metadata": {}, 76 | "source": [ 77 | "## Tri par insertion\n", 78 | "On donne ci-dessous, en pseudo-code, l'algorithme du tri par insertion.\n", 79 | "```bash\n", 80 | "POUR (i allant de 0 à n-1) FAIRE\n", 81 | "\tvaleur ← liste[i]\n", 82 | "\tj ← i\n", 83 | "\tTANTQUE ( j > 0 et liste[j-1] > valeur) FAIRE\n", 84 | "\t\tliste[j] ← liste[j-1]\n", 85 | "\t\tj ← j-1\n", 86 | "\tFIN TANTQUE\n", 87 | "\tliste[j] ← valeur\n", 88 | "FIN POUR\n", 89 | "```" 90 | ] 91 | }, 92 | { 93 | "cell_type": "markdown", 94 | "id": "d425a8b4", 95 | "metadata": {}, 96 | "source": [ 97 | "### Question 1. \n", 98 | "Implémenter avec Python l'algorithme précédent dans une fonction `tri_insertion` qui retourne la liste triée. " 99 | ] 100 | }, 101 | { 102 | "cell_type": "code", 103 | "execution_count": 2, 104 | "id": "48cea5b3", 105 | "metadata": {}, 106 | "outputs": [ 107 | { 108 | "data": { 109 | "text/plain": [ 110 | "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]" 111 | ] 112 | }, 113 | "execution_count": 2, 114 | "metadata": {}, 115 | "output_type": "execute_result" 116 | } 117 | ], 118 | "source": [ 119 | "def tri_insertion(liste):\n", 120 | " # Votre code ici\n", 121 | "\n", 122 | "# vérification\n", 123 | "tri_insertion(list_to_sort)[0]" 124 | ] 125 | }, 126 | { 127 | "cell_type": "markdown", 128 | "id": "914d5f9d", 129 | "metadata": {}, 130 | "source": [ 131 | "### Question 2.\n", 132 | "Programmer une fonction `test_tri_insertion` qui vérifie que le tri a bien été effectué, c'est-à-dire que chaque élément de la liste est inférieur ou égal à son successeur." 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": 3, 138 | "id": "fbb7e132", 139 | "metadata": {}, 140 | "outputs": [], 141 | "source": [ 142 | "def test_tri_insertion():\n", 143 | " # votre code ici\n", 144 | "test_tri_insertion()" 145 | ] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "id": "772abfca", 150 | "metadata": {}, 151 | "source": [ 152 | "### Question 3.\n", 153 | "Modifier la fonction `tri_insertion` pour qu'elle retourne, en plus de la liste triée, le nombre d'opérations qui ont été nécessaires (additions, soustractions, multplications, divisions, affectations)." 154 | ] 155 | }, 156 | { 157 | "cell_type": "markdown", 158 | "id": "fa564287", 159 | "metadata": {}, 160 | "source": [ 161 | "### Question 4.\n", 162 | "Tracer sur un graphique l'évolution du nombre d'opérations nécessaires pour faire le tri par insertion d'une liste de N entiers pour N allant de 100 à 1000 par pas de 10." 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": 1, 168 | "id": "32db3e5a", 169 | "metadata": {}, 170 | "outputs": [], 171 | "source": [ 172 | "import matplotlib.pyplot as plt\n", 173 | "\n", 174 | "liste_N = range(100, 1000, 10)\n", 175 | "nb_ope_insertion = []\n", 176 | "\n", 177 | "# votre code ici" 178 | ] 179 | }, 180 | { 181 | "cell_type": "markdown", 182 | "id": "1b1ac937", 183 | "metadata": {}, 184 | "source": [ 185 | "## Tri à bulles\n", 186 | "On donne ci-dessous, en pseudo-code, l'algorithme du tri à bulles.\n", 187 | "```bash\n", 188 | "echange_en_cours ← VRAI\n", 189 | "TANTQUE (echange_en_cours = VRAI) FAIRE\n", 190 | "\techange_en_cours ← FAUX\n", 191 | "\tPOUR j de 0 à n-2 FAIRE\n", 192 | "\t\tSI liste[j] > liste[j + 1] ALORS\n", 193 | "\t\t\téchanger liste[j] et liste[j + 1]\n", 194 | "\t\t\techange_en_cours ← VRAI\n", 195 | "\t\tFIN SI\n", 196 | "\tFIN POUR\n", 197 | "FIN TANTQUE\n", 198 | "```" 199 | ] 200 | }, 201 | { 202 | "cell_type": "markdown", 203 | "id": "7b559fb1", 204 | "metadata": {}, 205 | "source": [ 206 | "### Question 5.\n", 207 | "Effectuer le même travail que pour le tri par insertion, jusqu'au tracé du graphique." 208 | ] 209 | }, 210 | { 211 | "cell_type": "code", 212 | "execution_count": 5, 213 | "id": "4d62dc39", 214 | "metadata": {}, 215 | "outputs": [ 216 | { 217 | "data": { 218 | "text/plain": [ 219 | "[1, 2, 3, 5, 6, 7]" 220 | ] 221 | }, 222 | "execution_count": 5, 223 | "metadata": {}, 224 | "output_type": "execute_result" 225 | } 226 | ], 227 | "source": [ 228 | "# tout d'abord, je programme la fonction qui permet d'effectuer le tri conformément à l'algorithme ci-dessus.\n", 229 | "def tri_a_bulles(liste):\n", 230 | " # votre code ici\n", 231 | "\n", 232 | "# vérification\n", 233 | "tri_a_bulles([6, 5, 7, 3, 1, 2])[0]" 234 | ] 235 | }, 236 | { 237 | "cell_type": "code", 238 | "execution_count": 6, 239 | "id": "dd670459", 240 | "metadata": {}, 241 | "outputs": [], 242 | "source": [ 243 | "# fonction de test\n", 244 | "def test_tri_a_bulles():\n", 245 | " # votre code ici\n", 246 | "test_tri_a_bulles()" 247 | ] 248 | }, 249 | { 250 | "cell_type": "code", 251 | "execution_count": 2, 252 | "id": "fce93143", 253 | "metadata": {}, 254 | "outputs": [], 255 | "source": [ 256 | "# le graphique pour les mêmes valeurs de N, en traçant aussi le tri par insertion\n", 257 | "\n", 258 | "liste_N = range(100, 1000, 10)\n", 259 | "nb_ope_bulles = []\n", 260 | "\n", 261 | "# votre code ici" 262 | ] 263 | }, 264 | { 265 | "cell_type": "markdown", 266 | "id": "1ec8ea39", 267 | "metadata": {}, 268 | "source": [ 269 | "## Tri rapide\n", 270 | "Enfin, on donne l'algorithme en pseudo-code d'un troisième algorithme de tri, connu sous le nom de tri rapide ou quick sort.\n", 271 | "```bash\n", 272 | "\n", 273 | "SI nombre_d_element_de_liste = 0 FAIRE:\n", 274 | " RENVOYER LISTE\n", 275 | "\n", 276 | "pivot ← liste[0]\n", 277 | "\n", 278 | "POUR i de 0 à n-1 FAIRE\n", 279 | " liste_plus_petit ← []\n", 280 | " liste_plus_grand ← []\n", 281 | " POUR i de 1 à n:\n", 282 | " SI liste[i] <= pivot FAIRE\n", 283 | " AJOUTER liste[i] à liste_plus_petit\n", 284 | " SINON FAIRE\n", 285 | " AJOUTER liste[i] à liste_plus_grand\n", 286 | " FIN_SI\n", 287 | " APPLIQUER RECURSIVEMENT TRI_RAPIDE sur liste_plus_petit\n", 288 | " APPLIQUER RECURSIVEMENT TRI_RAPIDE sur liste_plus_grand\n", 289 | " CONCATENER les deux listes\n", 290 | " RENVOYER CETTE LISTE\n", 291 | "FIN POUR\n", 292 | "```\n", 293 | "\n", 294 | "### Question 6.\n", 295 | "Reprendre le travail précédent pour ce nouvel algorithme de tri." 296 | ] 297 | }, 298 | { 299 | "cell_type": "code", 300 | "execution_count": 8, 301 | "id": "1095389b", 302 | "metadata": {}, 303 | "outputs": [ 304 | { 305 | "data": { 306 | "text/plain": [ 307 | "([7, 8, 8, 9, 10, 11, 12], 19)" 308 | ] 309 | }, 310 | "execution_count": 8, 311 | "metadata": {}, 312 | "output_type": "execute_result" 313 | } 314 | ], 315 | "source": [ 316 | "def tri_rapide(input_list):\n", 317 | " # votre code ici\n", 318 | "\n", 319 | "# vérification\n", 320 | "tri_rapide([11, 12, 8, 7, 10, 8, 9])" 321 | ] 322 | }, 323 | { 324 | "cell_type": "code", 325 | "execution_count": null, 326 | "id": "bb2860bc", 327 | "metadata": {}, 328 | "outputs": [], 329 | "source": [ 330 | "# fonction de test\n", 331 | "def test_tri_rapide():\n", 332 | " # votre code ici\n", 333 | "test_tri_rapide()" 334 | ] 335 | }, 336 | { 337 | "cell_type": "code", 338 | "execution_count": 4, 339 | "id": "b0d2ff1f", 340 | "metadata": {}, 341 | "outputs": [], 342 | "source": [ 343 | "# finalement, on trace le graphique avec la comparaison des 3 algorithmes\n", 344 | "# le graphique pour les mêmes valeurs de N, en traçant aussi le tri par insertion\n", 345 | "\n", 346 | "# votre code ici" 347 | ] 348 | }, 349 | { 350 | "cell_type": "markdown", 351 | "id": "074b22e9", 352 | "metadata": {}, 353 | "source": [ 354 | "## Complexité algorithmique\n", 355 | "\n", 356 | "### Question 8.\n", 357 | "Déterminer la complexité algorithmique des 3 algorithmes précédents. Vous porterez, sur le graphique, un majorant pour justifier votre réponse.\n", 358 | "\n" 359 | ] 360 | }, 361 | { 362 | "cell_type": "markdown", 363 | "id": "e14a3eb3", 364 | "metadata": {}, 365 | "source": [ 366 | "La complexité algorithmique du tri par insertion et du tri à bulle est une complexité quadratique $O(n^2)$. C'est-à-dire que, si l'on considère que $T_{insertion}$ est le nombre d'opérations requis pour le tri par insertion d'une liste de $n$ éléments, alors :\n", 367 | "\n", 368 | "$ \\exists n_0 \\in N, \\exists c \\in R, \\forall b \\in R, n>=n_0 \\implies |T(n)| <= |c \\times n^2|$\n", 369 | "\n", 370 | "Pour démontrer la complexité quadratique, trouver par essai/erreur, en traçant le graphique associé, un réel $c$ tel que la fonction $cn²$ soit un majorant asymptotique de $T_{insertion}$. Sur le même graphique, faire le même travail pour $T_{bulle}$.\n", 371 | "\n", 372 | "Enfin, sur un autre graphique, démontrer de la même manière que la complexité algorithmique du tri rapide est $O(nlog(n))$." 373 | ] 374 | }, 375 | { 376 | "cell_type": "code", 377 | "execution_count": 5, 378 | "id": "48e92199", 379 | "metadata": {}, 380 | "outputs": [], 381 | "source": [ 382 | "# le graphique pour les mêmes valeurs de N, en traçant aussi le tri par insertion\n", 383 | "import numpy as np\n", 384 | "\n", 385 | "# votre code ici" 386 | ] 387 | }, 388 | { 389 | "cell_type": "markdown", 390 | "id": "2d7055ca", 391 | "metadata": {}, 392 | "source": [ 393 | "## Prévision du temps de calcul\n", 394 | "On doit trier une d'un milliard d'entiers (c'est-à-dire $10^9$ entiers). Avant de se lancer dans cette tâche, nous allons prévoir le temps requis pour le faire, avec chacun des algorithmes.\n", 395 | "\n", 396 | "### Question 9.\n", 397 | "Pour chacun des algorithmes, mesurer le temps moyen nécessaire pour trier une liste de 100000 ($10^4$) entiers." 398 | ] 399 | }, 400 | { 401 | "cell_type": "code", 402 | "execution_count": null, 403 | "id": "8b90d28c", 404 | "metadata": {}, 405 | "outputs": [], 406 | "source": [ 407 | "liste_a_trier = creer_liste_aleatoire(10 ** 4)\n", 408 | "\n", 409 | "# votre code ici" 410 | ] 411 | }, 412 | { 413 | "cell_type": "markdown", 414 | "id": "1a7d8d3d", 415 | "metadata": {}, 416 | "source": [ 417 | "### Question 10.\n", 418 | "Compte tenu de la complexité de chaque algorithme, déterminer, par extrapolation à partir des résultats précédents, le temps nécessaire pour effectuer le tri d'une liste de $10^{9}$ entiers. On demande un ordre de grandeur." 419 | ] 420 | }, 421 | { 422 | "cell_type": "code", 423 | "execution_count": 7, 424 | "id": "4999fab3", 425 | "metadata": {}, 426 | "outputs": [], 427 | "source": [ 428 | "# votre code ici" 429 | ] 430 | }, 431 | { 432 | "cell_type": "markdown", 433 | "id": "c7fa20e1", 434 | "metadata": {}, 435 | "source": [ 436 | "### Conclusion.\n", 437 | "Quel est l'algorihtme le plus efficace parmi ceux que nous avons expérimentés ?" 438 | ] 439 | }, 440 | { 441 | "cell_type": "markdown", 442 | "id": "89fa4f72", 443 | "metadata": {}, 444 | "source": [ 445 | "## Pour aller plus loin..." 446 | ] 447 | }, 448 | { 449 | "cell_type": "markdown", 450 | "id": "fd7e2f62", 451 | "metadata": {}, 452 | "source": [ 453 | "* implémenter chacun des trois algorithmes dans un autre langage de programmation de votre choix. Comparer alors les temps de traitement avec ceux que vous ave fait en Python.\n", 454 | "* utiliser la fonction python pré-programmée `sort` et comparer, en temps de traitement, avec l'algorithme que vous avez implémenté avec Python\n", 455 | "* tester d'autres algorithmes de tri : tri fusion, tri par tas" 456 | ] 457 | } 458 | ], 459 | "metadata": { 460 | "kernelspec": { 461 | "display_name": "Python 3 (ipykernel)", 462 | "language": "python", 463 | "name": "python3" 464 | }, 465 | "language_info": { 466 | "codemirror_mode": { 467 | "name": "ipython", 468 | "version": 3 469 | }, 470 | "file_extension": ".py", 471 | "mimetype": "text/x-python", 472 | "name": "python", 473 | "nbconvert_exporter": "python", 474 | "pygments_lexer": "ipython3", 475 | "version": "3.9.0" 476 | } 477 | }, 478 | "nbformat": 4, 479 | "nbformat_minor": 5 480 | } 481 | -------------------------------------------------------------------------------- /Compression_Sans_Perte_Eleve.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "f5397e9c-7894-4e16-98de-aa45aa1cecb9", 6 | "metadata": {}, 7 | "source": [ 8 | "### Compression de données sans perte. Algorithmes RLE et Huffmann.\n", 9 | "\n", 10 | "L'objectif de ce TP est de comparer deux algorithmes de compression de données en terme de :\n", 11 | "* complexité algorithmique\n", 12 | "* temps de calcul\n", 13 | "* performance (taux de compression). Le taux de compression, en pourcents, est défini par :\n", 14 | "\n", 15 | " $ taux\\_compression = 100 \\times \\frac{taille\\_texte\\_comprimé}{texte\\_original}$\n", 16 | "\n", 17 | "Le texte à compresser est extrait des Misérables de Victor Hugo :" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": null, 23 | "id": "e2732fe2-6844-44f5-ae6f-2b23d6772016", 24 | "metadata": {}, 25 | "outputs": [], 26 | "source": [ 27 | "texte_miserables = \"\"\"Il entendit un jour conter dans un salon un procès criminel qu'on\n", 28 | "instruisait et qu'on allait juger. Un misérable homme, par amour pour\n", 29 | "une femme et pour l'enfant qu'il avait d'elle, à bout de ressources,\n", 30 | "avait fait de la fausse monnaie. La fausse monnaie était encore punie de\n", 31 | "mort à cette époque. La femme avait été arrêtée émettant la première\n", 32 | "pièce fausse fabriquée par l'homme. On la tenait, mais on n'avait de\n", 33 | "preuves que contre elle. Elle seule pouvait charger son amant et le\n", 34 | "perdre en avouant. Elle nia. On insista. Elle s'obstina à nier. Sur ce,\n", 35 | "le procureur du roi avait eu une idée. Il avait supposé une infidélité\n", 36 | "de l'amant, et était parvenu, avec des fragments de lettres savamment\n", 37 | "présentés, à persuader à la malheureuse qu'elle avait une rivale et que\n", 38 | "cet homme la trompait. Alors, exaspérée de jalousie, elle avait dénoncé\n", 39 | "son amant, tout avoué, tout prouvé. L'homme était perdu. Il allait être\n", 40 | "prochainement jugé à Aix avec sa complice. On racontait le fait, et\n", 41 | "chacun s'extasiait sur l'habileté du magistrat. En mettant la jalousie\n", 42 | "en jeu, il avait fait jaillir la vérité par la colère, il avait fait\n", 43 | "sortir la justice de la vengeance. L'évêque écoutait tout cela en\n", 44 | "silence. Quand ce fut fini, il demanda:\n", 45 | "\n", 46 | "--Où jugera-t-on cet homme et cette femme?\n", 47 | "\n", 48 | "--À la cour d'assises.\n", 49 | "\n", 50 | "Il reprit:\n", 51 | "\n", 52 | "--Et où jugera-t-on monsieur le procureur du roi?\n", 53 | "\n", 54 | "Il arriva à Digne une aventure tragique. Un homme fut condamné à mort\n", 55 | "pour meurtre. C'était un malheureux pas tout à fait lettré, pas tout à\n", 56 | "fait ignorant, qui avait été bateleur dans les foires et écrivain\n", 57 | "public. Le procès occupa beaucoup la ville. La veille du jour fixé pour\n", 58 | "l'exécution du condamné, l'aumônier de la prison tomba malade. Il\n", 59 | "fallait un prêtre pour assister le patient à ses derniers moments. On\n", 60 | "alla chercher le curé. Il paraît qu'il refusa en disant: Cela ne me\n", 61 | "regarde pas. Je n'ai que faire de cette corvée et de ce saltimbanque;\n", 62 | "moi aussi, je suis malade; d'ailleurs ce n'est pas là ma place. On\n", 63 | "rapporta cette réponse à l'évêque qui dit:\n", 64 | "\n", 65 | "--Monsieur le curé a raison. Ce n'est pas sa place, c'est la mienne.\"\"\"" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "id": "030a2712-af40-4da5-9c03-25ab19be5a60", 71 | "metadata": {}, 72 | "source": [ 73 | "## Travail préliminaire\n", 74 | "\n", 75 | "* Compter le nombre de caractères qui composent ce texte." 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": null, 81 | "id": "0590bcb9-76cd-4615-bfc0-8096181eb1f5", 82 | "metadata": {}, 83 | "outputs": [], 84 | "source": [ 85 | "# votre code ici" 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "id": "b38da28a-0159-42e9-93c5-2f443a0f8b8d", 91 | "metadata": {}, 92 | "source": [ 93 | "### Algorithme RLE\n", 94 | "\n", 95 | "L'un des algorithmes de compression sans perte les plus simples est l'algorithme Run-Length Encoding (RLE), également connu sous le nom de codage par longueurs de runs. Il est particulièrement efficace pour compresser des données contenant des répétitions consécutives de caractères ou de symboles.\n", 96 | "\n", 97 | "Le principe de base de l'algorithme RLE est le suivant :\n", 98 | "\n", 99 | "* Parcourez la séquence de données d'entrée de gauche à droite.\n", 100 | "* Lorsque vous trouvez une série de caractères identiques consécutifs, comptez leur nombre.\n", 101 | "* Écrivez le caractère suivi du nombre de fois qu'il apparaît dans la sortie compressée.\n", 102 | "* Répétez les étapes 2 et 3 jusqu'à ce que vous ayez parcouru toute la séquence d'entrée.\n", 103 | "\n", 104 | "Voici un exemple simple :\n", 105 | "\n", 106 | "Données d'entrée : \"AAAABBBCCDAA\"\n", 107 | "\n", 108 | "Données compressées avec RLE : \"4A3B2C1D2A\"\n", 109 | "\n", 110 | "### A noter : on considérera une chaîne de caractères comme une liste de caractères :\n", 111 | "```python\n", 112 | "chaine = \"Bonjour\"\n", 113 | "print(chaine[1])\n", 114 | ">>> 'o'\n", 115 | "```" 116 | ] 117 | }, 118 | { 119 | "cell_type": "markdown", 120 | "id": "3f28cd76-555f-4637-ab3b-477e16a08f18", 121 | "metadata": {}, 122 | "source": [ 123 | "### Implémentation de l'algorithme RLE pour la compression et la décompression\n", 124 | "\n", 125 | "En utilisant l'algorithme RLE, compresser le texte des Misérables. Vous pourrez par exemple code une fonction `compress_RLE` et `uncompress_RLE`.\n", 126 | "Déterminer le taux de compression obtenu ainsi que la complexité de l'algorithme (dénombrez les opérations et mesurez le temps d'exécution)." 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": null, 132 | "id": "56a8289a-e413-4105-8fe2-012314ec9b7e", 133 | "metadata": {}, 134 | "outputs": [], 135 | "source": [ 136 | "# votre code ici\n", 137 | "\n", 138 | "# Exemple de compression\n", 139 | "assert compress_RLE(\"AAAABBBCCDAA\") == \"4A3B2C1D2A\"" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": null, 145 | "id": "390a990a-fe48-4638-97c9-cec6896c863e", 146 | "metadata": {}, 147 | "outputs": [], 148 | "source": [ 149 | "# votre code ici\n", 150 | "\n", 151 | "assert uncompress_RLE(\"4A3B2C1D2A\") == \"AAAABBBCCDAA\"" 152 | ] 153 | }, 154 | { 155 | "cell_type": "markdown", 156 | "id": "a918e6f1-1fcf-4eb1-b080-be4a3424c52d", 157 | "metadata": {}, 158 | "source": [ 159 | "### Qualification de l'algorithme RLE sur une chaîne aléatoire\n", 160 | "Compresser une chaîne de caractères composée de 1000 caractères choisis parmi \"A\", \"B\", \"C\" ou \"D\". Pour cela, on utilisera la fonction `random.choice`. Déterminer le taux de compression moyen." 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": null, 166 | "id": "af6ae443-d53c-4276-8ea0-7ea9fa2054ca", 167 | "metadata": {}, 168 | "outputs": [], 169 | "source": [ 170 | "# votre code ici" 171 | ] 172 | }, 173 | { 174 | "cell_type": "markdown", 175 | "id": "e530b35d-9f9a-4c88-8b4b-1d9b1bd2365d", 176 | "metadata": {}, 177 | "source": [ 178 | "### Qualification de l'algorithme RLE sur une chaîne de caractères en langue française\n", 179 | "* A l'aide de la fonction `compress_RLE`, comprimer le texte extrait des Misérables\n", 180 | "* utiliser la fonction `uncompress_RLE` pour le décompresser, et vérifier qu'on retrouve bien le texte original\n", 181 | "* calcul le taux de compression pour ce texte\n", 182 | "* que pouvez-vous conclure quant à cet algorithme de compression sans perte, en comparant les performances sur une chaîne aléatoire et sur une chaîne en langue française ?" 183 | ] 184 | }, 185 | { 186 | "cell_type": "code", 187 | "execution_count": null, 188 | "id": "19d69b26-4c00-42e0-b351-bb3d24a6fbbf", 189 | "metadata": {}, 190 | "outputs": [], 191 | "source": [ 192 | "# votre code ici" 193 | ] 194 | }, 195 | { 196 | "cell_type": "markdown", 197 | "id": "873aaacf-c0d5-4c60-a434-ab1d078723fd", 198 | "metadata": {}, 199 | "source": [ 200 | "### Codage Huffman\n", 201 | "Le codage Huffman est un algorithme de compression qui se base sur le nombre d'occurrences de chaque lette dans un texte. L'objectif est de compresser efficacement un texte en exploitant le fait que les caractères aient une fréquence d'apparition loin d'être uniforme. (Par exemple, le \"e\" est beaucoup plus fréquent que le \"w\", donc on va faire en sorte qu'il prenne moins de place en mémoire).\n", 202 | "\n", 203 | "En voici une implémentation python ci-dessous." 204 | ] 205 | }, 206 | { 207 | "cell_type": "code", 208 | "execution_count": null, 209 | "id": "796d25e7-965a-4dfb-a92f-bbd6410e305a", 210 | "metadata": {}, 211 | "outputs": [], 212 | "source": [ 213 | "def compterOccurences(texte):\n", 214 | " \"\"\"\n", 215 | " Renvoie une liste qui associe à chaque caractère son nombre d'apparitions.\n", 216 | "\n", 217 | " Chaque lettre est donc dotée d'un poids (son nombre d'occurences), et\n", 218 | " plus son poids est élevé, plus elle sera légère en mémoire.\n", 219 | " (L'objectif étant ici la compression, donc d'échanger de la puissance\n", 220 | " de calcul contre de l'espace de stockage.)\n", 221 | " \"\"\"\n", 222 | " lettres = [[0, chr(i)] for i in range(256)]\n", 223 | " for i in texte:\n", 224 | " lettres[ord(i)][0] += 1\n", 225 | " return lettres\n", 226 | "\n", 227 | "def creerArbre(lettres):\n", 228 | " \"\"\"\n", 229 | " Crée un arbre binaire à partir des lettres et de leur poids.\n", 230 | "\n", 231 | " On choisit de représenter un arbre de la façon suivante :\n", 232 | " * Une feuille est un 2-uplet : le nombre d'occurences et la lettre\n", 233 | " On notera que compterOccurences renvoie en fait une liste de feuilles.\n", 234 | " * Un noeud est un 3-uplet : la somme des occurences de toutes\n", 235 | " les feuilles descendantes, le fils gauche et le fils droit.\n", 236 | " Ensuite, on construit l'arbre en piochant les deux noeuds de poids\n", 237 | " le plus faible, on en fait un nouveau noeud que l'on remet dans le tas.\n", 238 | " On s'arrête dès qu'il reste un unique noeud (qui est l'arbre voulu)\n", 239 | " \"\"\"\n", 240 | " # On commence par enlever les lettres qui ne sont pas présentes\n", 241 | " noeuds = [(k, v) for (k, v) in lettres if k > 0]\n", 242 | " # Puis on récupère les deux noeuds (ou feuilles) de poids le plus faible,\n", 243 | " # et on en fait un noeud, de poids la somme des deux petits poids\n", 244 | " # On boucle tant qu'il y a reste au moins deux noeuds\n", 245 | " l = len(noeuds)\n", 246 | " while l >= 2:\n", 247 | " # Indice et noeud des minima des poids\n", 248 | " # (on initialise avec les deux premières valeurs)\n", 249 | " petitMin = (0, noeuds[0])\n", 250 | " grandMin = (1, noeuds[1])\n", 251 | " for i in range(2, l):\n", 252 | " if noeuds[i][0] <= petitMin[1][0]: # poids < petitMin < grandMin\n", 253 | " grandMin = petitMin\n", 254 | " petitMin = (i, noeuds[i])\n", 255 | " elif noeuds[i][0] <= grandMin[1][0]: # petitMin < poids < grandMin\n", 256 | " grandMin = (i, noeuds[i])\n", 257 | " nouveauNoeud = (\n", 258 | " petitMin[1][0] + grandMin[1][0],\n", 259 | " noeuds[petitMin[0]],\n", 260 | " noeuds[grandMin[0]]\n", 261 | " )\n", 262 | " # On enlève les deux noeuds (ou feuilles) précedentes\n", 263 | " # et on ajoute le nouveau noeud\n", 264 | " noeuds[petitMin[0]] = nouveauNoeud\n", 265 | " noeuds.pop(grandMin[0])\n", 266 | " # On a au final un noeud de moins (-2 +1)\n", 267 | " l -= 1\n", 268 | " # À cet instant il ne reste plus qu'un noeud, qui est la racine de\n", 269 | " # l'arbre de Huffman\n", 270 | " return noeuds[0]\n", 271 | "\n", 272 | "def creerDico(arbre):\n", 273 | " \"\"\"\n", 274 | " Renvoie un dictionnaire {lettre: code binaire}.\n", 275 | "\n", 276 | " On va explorer l'arbre à l'aide d'une file : si on rencontre une feuille,\n", 277 | " on la traite, si on rencontre un noeud, on ajoute les deux branches à la file.\n", 278 | " Le premier composant d'un élément de la file est le code binaire jusqu'à cet élément,\n", 279 | " le second est un noeud ou une feuille.\n", 280 | " \"\"\"\n", 281 | " fileExploration = [(\"\", arbre)]\n", 282 | " dico = {}\n", 283 | " l = 1\n", 284 | " # On boucle tant que la file n'est pas vide\n", 285 | " while l >= 1:\n", 286 | " code, truc = fileExploration.pop(0) # On défile le premier élément\n", 287 | " l -= 1\n", 288 | " if len(truc) == 2: # C'est une feuille\n", 289 | " dico[truc[1]] = code # On ajoute la lettre et son code au dico\n", 290 | " elif len(truc) == 3: # C'est un noeud\n", 291 | " # On continue l'exploration en respectant la règle pour obtenir le code :\n", 292 | " # Gauche -> 0, droite -> 1\n", 293 | " fileExploration.append((code + \"0\", truc[1]))\n", 294 | " fileExploration.append((code + \"1\", truc[2]))\n", 295 | " l += 2\n", 296 | " return dico\n", 297 | "\n", 298 | "def compress_Huffman(texte):\n", 299 | " \"\"\"\n", 300 | " On se contente de remplacer les lettres du texte par le code binaire\n", 301 | " obtenu à l'aide de la fonction creerDico.\n", 302 | " \"\"\"\n", 303 | " lettres = compterOccurences(texte)\n", 304 | " arbre = creerArbre(lettres)\n", 305 | " dico = creerDico(arbre)\n", 306 | " texteCompresse = \"\"\n", 307 | " for i in texte:\n", 308 | " texteCompresse += dico[i]\n", 309 | " # On n'oublie pas de renvoyer aussi le dictionnaire,\n", 310 | " # sinon il sera impossible de décompresser le texte\n", 311 | " return texteCompresse, dico\n", 312 | "\n", 313 | "def uncompress_Huffman(texteCompresse, dicoRetourne):\n", 314 | " \"\"\"\n", 315 | " Décompresse un texte à l'aide de son dico.\n", 316 | "\n", 317 | " Une fois encore, on utilise une file. C'est un outil très puissant\n", 318 | " qui permet de ne jamais écrire de fonction récursive. Chaque élément\n", 319 | " de la file est un 2-uplet, le premier élément est le texte décompressé\n", 320 | " jusque là, le second est le code binaire restant à décompresser.\n", 321 | " \"\"\"\n", 322 | " # On retourne le dico\n", 323 | " dico = {v: k for (k, v) in dicoRetourne.items()}\n", 324 | " # Nombre maximum de bits d'un caractère compressé\n", 325 | " limite = max(len(k) for k in dico.keys())\n", 326 | " fileExploration = [(\"\", texteCompresse)]\n", 327 | " l = 1\n", 328 | " while l >= 1:\n", 329 | " fait, restant = fileExploration.pop(0) # On défile le premier élément\n", 330 | " l -= 1\n", 331 | " # On regarde si la décompression est terminée\n", 332 | " if restant == \"\":\n", 333 | " return fait\n", 334 | " # Sinon, on tente de remplacer les i premiers bits de restant par un caractère\n", 335 | " i = 0\n", 336 | " bits = \"\"\n", 337 | " for bit in restant:\n", 338 | " bits += bit\n", 339 | " i += 1\n", 340 | " if i > limite:\n", 341 | " # C'est pas la peine de continuer, bits est trop long\n", 342 | " # pour correspondre à un caractère\n", 343 | " break\n", 344 | " elif bits in dico:\n", 345 | " # On a la possibilité de remplacer quelques 0 et 1 par un caractère\n", 346 | " # alors on le fait, sans pour autant considérer que l'on a choisi\n", 347 | " # le bon remplacement\n", 348 | " fileExploration.append((fait + dico[bits], restant[i:]))\n", 349 | " l += 1\n", 350 | " # Puis on continue à explorer les possibilités\n", 351 | " # Aucune décompression n'a fonctionné, on ne renvoie rien\n", 352 | " return None" 353 | ] 354 | }, 355 | { 356 | "cell_type": "code", 357 | "execution_count": null, 358 | "id": "92f5358c-a441-49fe-b191-3d6d695c1cf9", 359 | "metadata": {}, 360 | "outputs": [], 361 | "source": [ 362 | "# Par exemple, pour ce texte en \"latin\" :\n", 363 | "texte = \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent fermentum aliquam ipsum non vehicula. Sed placerat aliquam aliquet. Donec magna mauris, gravida sed volutpat vitae, molestie at massa. Pellentesque et metus quis lacus tempor placerat. Aliquam erat volutpat. Vivamus dapibus mi nec nisi aliquam, et euismod augue molestie. Nunc interdum.\"\n", 364 | "# On le compresse\n", 365 | "texteCompresse, dico = compress_Huffman(texte)\n", 366 | "print(\"Avant : {} bits / Après : {} bits\".format(len(texte) * 8, len(texteCompresse)))\n", 367 | "# Avant : 2816 bits / Après : 1522 bits\n", 368 | "# (On rappelle que 1 octet = 8 bits)" 369 | ] 370 | }, 371 | { 372 | "cell_type": "markdown", 373 | "id": "b72c19cf-a05f-456e-b668-944857a77342", 374 | "metadata": {}, 375 | "source": [ 376 | "### Travail demandé pour l'algorithme de Huffman :\n", 377 | "* vérifier que la compression/décompression fonctionne correctement avec quelques chaînes de vérification ;\n", 378 | "* compresser la chaine du texte des Misérables et déterminer le taux de compression." 379 | ] 380 | }, 381 | { 382 | "cell_type": "code", 383 | "execution_count": null, 384 | "id": "70d79931-0876-459e-aa63-dabfecc84a42", 385 | "metadata": {}, 386 | "outputs": [], 387 | "source": [ 388 | "# votre code ici" 389 | ] 390 | }, 391 | { 392 | "cell_type": "markdown", 393 | "id": "16c1798b-5206-48c1-a0cd-3487133fc092", 394 | "metadata": {}, 395 | "source": [ 396 | "### Complexité de l'algorithme de Huffman\n", 397 | "Déterminer la complexité algorithmique de l'algo de Huffman à la fois pour la compression et la décompression. Pour cela :\n", 398 | "* vous mesurerez soit le nombre d'oéprations, soit le temps requis, pour compresser une chaîne de longueur N caractères choisis au hasard dans l'ensemble de la table ASCII.\n", 399 | "* vous ferez varier N entre 10 et 20000 (dix mille) par pas de 100 et tracerez un graphique\n", 400 | "* vous chercherez un dominant puis en déduirez une complexité algorithmique dans la notation $O$.\n", 401 | "\n", 402 | "Pour information, pour choisir au hasard des éléments dans une liste, on utilise la fonction `random.choice`, et python donne la liste de tous les caractères ascii avec les instructions :\n", 403 | "\n", 404 | "```python\n", 405 | "import string\n", 406 | "print(string.printable)\n", 407 | "```" 408 | ] 409 | }, 410 | { 411 | "cell_type": "code", 412 | "execution_count": null, 413 | "id": "3d86d70c-a50d-4572-99f0-919ecc18626a", 414 | "metadata": {}, 415 | "outputs": [], 416 | "source": [ 417 | "# votre code ici" 418 | ] 419 | }, 420 | { 421 | "cell_type": "code", 422 | "execution_count": null, 423 | "id": "2fa1a096-bbb1-4639-af2c-90aec725cb6a", 424 | "metadata": {}, 425 | "outputs": [], 426 | "source": [ 427 | "# votre code ici" 428 | ] 429 | }, 430 | { 431 | "cell_type": "code", 432 | "execution_count": null, 433 | "id": "abc7a04c-031c-4451-90b7-9f27b7f84e4d", 434 | "metadata": {}, 435 | "outputs": [], 436 | "source": [ 437 | "# votre code ici" 438 | ] 439 | }, 440 | { 441 | "cell_type": "markdown", 442 | "id": "dfee153a-7e7b-44c8-8d81-d6c9ec84195b", 443 | "metadata": {}, 444 | "source": [ 445 | "Je constate que la compression, $O(nlogn)$ est beaucoup plus efficace que la décompression $O(n^2)$." 446 | ] 447 | }, 448 | { 449 | "cell_type": "markdown", 450 | "id": "cedc06ad-6830-4b97-a2d6-3c638617f896", 451 | "metadata": {}, 452 | "source": [ 453 | "### Anticipation du temps requis pour la compression/décompression du texte complet des Misérables\n", 454 | "* ouvrir le texte des misérables et déterminer la nombre de caractères\n", 455 | "* par extrapolation de l'étude précédente, déduire le temps approximatif de compression et décompression\n", 456 | "* effectuer la compression/décompression puis vérifier votre prédiction.\n", 457 | "\n", 458 | "Pour charger le texte, vous utiliserez l'encodage `latin-1`:\n", 459 | "```python\n", 460 | "with open('LesMiserables.txt', 'r', encoding='latin-1') as fp:\n", 461 | " les_miserables_complet = fp.read()\n", 462 | "```" 463 | ] 464 | }, 465 | { 466 | "cell_type": "code", 467 | "execution_count": null, 468 | "id": "df97bce6-ddcd-441b-9e51-e9cc0aeb1a7b", 469 | "metadata": {}, 470 | "outputs": [], 471 | "source": [ 472 | "# votre code ici" 473 | ] 474 | }, 475 | { 476 | "cell_type": "code", 477 | "execution_count": null, 478 | "id": "2065e145-ee2c-48b2-9971-2d2a71610fb6", 479 | "metadata": {}, 480 | "outputs": [], 481 | "source": [ 482 | "# votre code ici" 483 | ] 484 | }, 485 | { 486 | "cell_type": "markdown", 487 | "id": "4800821d-0e38-4e5e-a1b6-4901cdf79a69", 488 | "metadata": {}, 489 | "source": [ 490 | "### Réduction du temps requis : diviser pour mieux régner\n", 491 | "Afin d'accélérer le processus, on propose de diviser le texte en 10 parties égales, de compresser/décompresser chaque chaîne.\n", 492 | "* implémenter cette technique\n", 493 | "* quels sont les impacts (gains ? pertes ?) en terme de taux de compression et de temps de compression/décompression." 494 | ] 495 | } 496 | ], 497 | "metadata": { 498 | "kernelspec": { 499 | "display_name": "Python 3 (ipykernel)", 500 | "language": "python", 501 | "name": "python3" 502 | }, 503 | "language_info": { 504 | "codemirror_mode": { 505 | "name": "ipython", 506 | "version": 3 507 | }, 508 | "file_extension": ".py", 509 | "mimetype": "text/x-python", 510 | "name": "python", 511 | "nbconvert_exporter": "python", 512 | "pygments_lexer": "ipython3", 513 | "version": "3.9.0" 514 | } 515 | }, 516 | "nbformat": 4, 517 | "nbformat_minor": 5 518 | } 519 | -------------------------------------------------------------------------------- /BPE_Tokenization_Eleve.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "f31cd2b1-d221-4be0-a791-08b71ff1c42f", 6 | "metadata": { 7 | "jp-MarkdownHeadingCollapsed": true 8 | }, 9 | "source": [ 10 | "## Algorithme d'encodage de texte pour Large Language Models : Byte Pair Encoding (BPE) et Tokenization\n", 11 | "\n", 12 | "Pour celles et ceux d'entre vous qui ne disposent pas des droits d'accès pour utiliser les bibliothèques tierces requises pour ce TP (tiktoken et transformers), un environnement en ligne est disponible à l'URL https://mybinder.org/v2/gh/tpaviot/binderenv/HEAD?filepath=\n", 13 | "\n", 14 | "Nous allons travailler à diviser un texte en briques de base connues sous le nom de \"vocabulaire\" et d'associer ainsi à une chapine de caractères une succession d'entiers. La tokenisation la plus élémentaire est celle consistant à associer à un mot l'ensemble des valeurs de la table ASCII (le vocabulaire contient $2^7=128$ briques de base) :\n", 15 | "```\n", 16 | "print([ord(c) for c in \"Salut\"])\n", 17 | "[83, 97, 108, 117, 116]\n", 18 | "```\n", 19 | "\n", 20 | "\n", 21 | "Mais ceci n'est pas suffisant pour travailler avec les grands modèles de langage type ChatGPT.\n", 22 | "\n", 23 | "Le **corpus** de texte est le jeu de données connu sous le nom de \"Tiny shakespeare\" accessible à l'url : https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt\n", 24 | "\n", 25 | "Ce TP a été construit à partir, entre autres, des ressources suivantes disponibles en ligne, que vous êtes invité.e.s à prendre le temps de consulter (en particulier les vidéos de Karpathy) :\n", 26 | "\n", 27 | "* La documenation HuggingFace (https://huggingface.co/learn/nlp-course/chapter6/5?fw=pt)\n", 28 | "\n", 29 | "* La chaîne vidéo YouTube d'Andrej Karpathy (https://www.youtube.com/@AndrejKarpathy/)\n", 30 | "\n", 31 | "Ce **TP comporte trois parties** :\n", 32 | "* dans une première partie, nous nous intéressons à un algorithme qui associe à chaque lettre un entier\n", 33 | "\n", 34 | "* dans une deuxième partie, nous expérimentons un algorithme qui découpe les mots en token de 2 caractères\n", 35 | "\n", 36 | "* dans une troisième partie, nous implémentons un algorithmes plus avancé appelé BPE qui permet d'encoder n'importe quelle chaine de caractères dans une liste d'entiers.\n", 37 | "\n", 38 | "Pour chacun de ces trois algorithmes, nous comparons :\n", 39 | "* la qualité, c'est-à-dire le nombre d'entiers requis pour encoder la chaîne. Plus ce nombre est petit, plus l'aglo est efficient\n", 40 | "\n", 41 | "* le temps de calcul nécessaire pour encoder/décoder, étant entendu que, dans le domaine des modèles de langage, les texte à encoder peuvent être de plus giga octets. Plus le temps est court, plus l'algorithme est efficient.\n", 42 | "\n", 43 | "Dans la suite, on appellera `token` un de ces motifs de base et `tokenization` le processus consistant à découper une chaîne de caractères en éléments de base disponibles dans un vocabulaire. On utilisera le terme *token* pour désigner sans distinction l'élément de base du vocabulaire ou l'entier associé, qui sont en bijection. L'`encoding` est le processus permettant de passer de la chaîne à la liste de tokens et donc d'entiers, le `decodage` le processus réciproque (passage d'une liste d'entiers à une chaîne de caractères)." 44 | ] 45 | }, 46 | { 47 | "cell_type": "markdown", 48 | "id": "2fc1ed68-cf1b-4fb0-8c5b-c52c70cbdbe2", 49 | "metadata": {}, 50 | "source": [ 51 | "## Question 1 - Chargement du jeu de données\n", 52 | "\n", 53 | "Avec la commande `wget` directement dans ce notebook, télécharger le contenu du fichier `tinyshakespeare` et le stocker dans le répertoire courant." 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "id": "5ddaa536-439e-4bcb-8080-a9c1c0a18246", 60 | "metadata": {}, 61 | "outputs": [], 62 | "source": [ 63 | "# votre code ici" 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "id": "558e273e-6c31-450b-a61c-943e8c304b58", 69 | "metadata": {}, 70 | "source": [ 71 | "# Première partie - Character Level Tokenization\n", 72 | "\n", 73 | "## Question 2\n", 74 | "\n", 75 | "* Charger le contenu du fichier dans une variable nommée `text`. Vous spécifierez un encodage de type utf-8 ;\n", 76 | "\n", 77 | "* afficher les 200 premiers caractères du texte ;\n", 78 | "\n", 79 | "* afficher le nombre total de caractères du texte." 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": null, 85 | "id": "fdb7ff22-25fc-4d82-8639-4eca14c81944", 86 | "metadata": {}, 87 | "outputs": [], 88 | "source": [ 89 | "# votre code ici" 90 | ] 91 | }, 92 | { 93 | "cell_type": "markdown", 94 | "id": "aa25f64a-6804-4d2d-9f48-666dc97836ea", 95 | "metadata": {}, 96 | "source": [ 97 | "## Question 3\n", 98 | "\n", 99 | "Créer une fonction `build_vocab` qui prend comme paramètre une chaîne de carcatères `input_str` et qui renvoie :\n", 100 | "\n", 101 | "* la liste `chars` de tous les caractères utilisés, sans doublon, classée dans l'ordre des codes ASCII des caractères\n", 102 | "\n", 103 | "* la taille `vocab_size` de cette liste\n", 104 | "\n", 105 | "* vérifier avec un `assert` que pour la chaîne `\"Andrej Karpathy, né le 23 octobre 1986, est un informaticien slovaco-canadien qui a été directeur de l'intelligence artificielle et du pilotage automatique chez Tesla. Il travaille actuellement pour OpenAI\"` le résultat concaténé retourné est `\" ',-.123689AIKOTabcdefghijlmnopqrstuvyzé\"` et la longueur `40`." 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": null, 111 | "id": "d69c2acc-b261-4e85-a736-71f2eca407c3", 112 | "metadata": {}, 113 | "outputs": [], 114 | "source": [ 115 | "# votre code ici" 116 | ] 117 | }, 118 | { 119 | "cell_type": "markdown", 120 | "id": "19286932-7d2c-44dd-8585-58661951df14", 121 | "metadata": {}, 122 | "source": [ 123 | "## Question 4. Character Level Tokenization\n", 124 | "Il s'agit de convertir ce texte en une séquence d'entiers à partir du vocabulaire défini précédemment.\n", 125 | "\n", 126 | "* créer une fonction `encode` qui prend en paramètre une liste de caractères et renvoie la liste des indices des caractères correspondant dans la liste `vocab`\n", 127 | "\n", 128 | "* créer une fonction `decode` qui est la fonction réciproque\n", 129 | "\n", 130 | "* vérifier que `encode(\"hii there\")` renvoie `[46, 47, 47, 1, 58, 46, 43, 56, 43]`\n", 131 | "\n", 132 | "* vérifier que `decode([46, 47, 47, 1, 58, 46, 43, 56, 43])` renvoie `\"hii_there\"`\n", 133 | "\n", 134 | "Dans cette question, nous avons associé, dans la fonction `encode`, un entier à chaque caractère, ce qui s'appelle `Character Level Tokenization`." 135 | ] 136 | }, 137 | { 138 | "cell_type": "code", 139 | "execution_count": null, 140 | "id": "d551465d-e257-42bf-a26d-d595492dad67", 141 | "metadata": {}, 142 | "outputs": [], 143 | "source": [ 144 | "# votre code ici" 145 | ] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "id": "86050951-b96f-499d-aee5-2979bd7b20c5", 150 | "metadata": {}, 151 | "source": [ 152 | "## Question 5. Performances de notre Character Encoder\n", 153 | "\n", 154 | "* Mesurer le temps total nécessaire pour encoder le texte complet du texte tiny_shakespeare avec la fonction précédente ;\n", 155 | "\n", 156 | "* Afficher la vitesse d'encodage en octets/secondes ;\n", 157 | "\n", 158 | "* Mesurer et afficher le nombre d'éléments du texte encodé ;\n", 159 | "\n", 160 | "* Mesurer le temps total de décodage pour le texte, et exprimer de la même manière la vitesse de décodage en octets/s." 161 | ] 162 | }, 163 | { 164 | "cell_type": "code", 165 | "execution_count": null, 166 | "id": "8dc1c137-e2bc-4451-a87d-2973e69c35b8", 167 | "metadata": {}, 168 | "outputs": [], 169 | "source": [ 170 | "# votre code ici" 171 | ] 172 | }, 173 | { 174 | "cell_type": "markdown", 175 | "id": "8fab7918-42dc-4fa4-ae5a-0ad765c31921", 176 | "metadata": {}, 177 | "source": [ 178 | "## Question 6. Qualité de notre Character Encoder\n", 179 | "\n", 180 | "* encoder le texte suivant: \"Napoleon is a spectacle-filled action epic that details the checkered rise and fall of the iconic French Emperor Napoleon Bonaparte\". Quelle est la taille de la liste obtenue ?\n", 181 | " \n", 182 | "* encoder le texte suivant: \"Napoléon est un film réalisé par Ridley Scott avec Joaquin Phoenix, Vanessa Kirby.\" Que constatez-vous ? quelle solution pouvez-vous apporter ?" 183 | ] 184 | }, 185 | { 186 | "cell_type": "code", 187 | "execution_count": null, 188 | "id": "239ace76-0c5b-40f0-9956-612a4c3a45f5", 189 | "metadata": {}, 190 | "outputs": [], 191 | "source": [ 192 | "# votre code ici" 193 | ] 194 | }, 195 | { 196 | "cell_type": "markdown", 197 | "id": "b3c6c44d-909e-4052-8a79-89d6a6afffdb", 198 | "metadata": {}, 199 | "source": [ 200 | "# Partie 2\n", 201 | "\n", 202 | "## Question 7. Introduction à la problématique de Subword tokenization\n", 203 | "Nous pouvons travailler à partir d'un vocabulaire qui n'est pas constitué que de caractères simples mais de séquences de 2 caractères, ce qui permettra d'avoir des encodages plus courts.\n", 204 | "\n", 205 | "Par exemple, si le vocabulaire est constitué des éléments: `vocab = ['ch', 'ien', 'at']` alors l'encodage du mot `chien` sera `[0, 1]` et celui du mot `chat` sera `[0, 2]`, ne prenant dans les deux cas que deux entiers alors qu'il en aurait fallu 4 avec la méthode des questions précédentes. Il s'agit dans ce cas d'un algorithme de \"SubWord encoding\", plus performant de toute évidence puisqu'il divise dans ce cas le nombre d'entiers par 2.\n", 206 | "\n", 207 | "* définir une fonction `split_pair` qui prend une chaine de caractères et scinde la chaîne de caractères en groupes de deux caractères. Si la longueur de la chaîne de caractères est impaire alors la dernière lettre sera un caractère seul.\n", 208 | "\n", 209 | "* vérifier que la fonction `split_pair` appliquée à la chaîne `\"Napoleon\"` renvoie `['Na', 'po', 'le', 'on']`\n", 210 | "\n", 211 | "* vérifier que la fonction `split_pair` appliquée à la chaîne `\"Napoleon3\"`renvoie `['Na', 'po', 'le', 'on', '3']`\n", 212 | "\n", 213 | "* comme dans la question 3, construire ensuite un vocabulaire à partir de cette liste de paires de caractères, sans doublons. Vérifier que pour la chaîne `\"un chien et un chat rigolent, ha ha\"` vous obtenez le vocabulaire :\n", 214 | "`[' c', ' e', ' h', ', ', 'a', 'en', 'go', 'ha', 'hi', 'le', 'nt', 'ri', 't ', 'un']`\n", 215 | "et une taille de vocabulaire de `14` éléments.\n", 216 | " " 217 | ] 218 | }, 219 | { 220 | "cell_type": "code", 221 | "execution_count": null, 222 | "id": "9cb42201-58e1-49eb-a5f4-562b29747833", 223 | "metadata": {}, 224 | "outputs": [], 225 | "source": [ 226 | "# votre code ici" 227 | ] 228 | }, 229 | { 230 | "cell_type": "markdown", 231 | "id": "dea15e77-c584-4b2e-b75d-adb8f3fac420", 232 | "metadata": {}, 233 | "source": [ 234 | "## Question 8. Un pair encoder simplifié\n", 235 | "\n", 236 | "* créer le vocabulaire correspondant au jeu de données `tiny_shakespeare`. Vérifier que la taille du vocabulaire est de `1334` ;\n", 237 | "\n", 238 | "* créer une fonction `encode_pair` et `decode_pair` qui s'appuient sur le vocabulaire précédent ;\n", 239 | "\n", 240 | "* vérifier que l'encoder renvoie pour la chaine `'I say unto you, what he hath done famously'` est `[391, 1165, 1296, 1237, 1208, 104, 1085, 156, 1267, 710, 88, 794, 887, 1203, 84, 1078, 794, 839, 1012, 1241, 993]`\n", 241 | "\n", 242 | "* vérifier que le décodage renvoie la bonne chaine" 243 | ] 244 | }, 245 | { 246 | "cell_type": "code", 247 | "execution_count": null, 248 | "id": "9bbf2cbe-4b61-4c5a-9c05-86edf7bac23f", 249 | "metadata": {}, 250 | "outputs": [], 251 | "source": [ 252 | "# votre code ici" 253 | ] 254 | }, 255 | { 256 | "cell_type": "markdown", 257 | "id": "513c91e1-c6cc-4249-9393-bf36a81032fe", 258 | "metadata": {}, 259 | "source": [ 260 | "## Question 9. Performances de ce pair encoder basique\n", 261 | "\n", 262 | "* reprendre les mêmes questions que la question 5 pour mesurer les performances de l'encoder et du decoder en octets/s pour le texte complet tiney_shakespeare.\n", 263 | "\n", 264 | "* conclure quant à la comparaison entre les deux encoders." 265 | ] 266 | }, 267 | { 268 | "cell_type": "code", 269 | "execution_count": null, 270 | "id": "a27180c9-c11e-42e8-9a66-c9c5af3bc511", 271 | "metadata": {}, 272 | "outputs": [], 273 | "source": [ 274 | "# votre code ici" 275 | ] 276 | }, 277 | { 278 | "cell_type": "markdown", 279 | "id": "2a7618b4-91ba-4b59-a6d6-9db923256d3c", 280 | "metadata": {}, 281 | "source": [ 282 | "## Question 10. Limites de notre pair encoder\n", 283 | "\n", 284 | "* Encoder la chaine \"BUT Informatique de Nevers\" avec le pair encoder précédent.\n", 285 | "\n", 286 | "* Proposer et implémenter une solution pour corriger précédent pour le cas où les paires ne sont pas trouvées dans le vocabulaire.\n", 287 | "\n", 288 | "* L'encoder modifié devra permettre d'obtenir la même longueur pour le tiny_shakespeare encodé, et proposer une solution pour n'importe quelle chaîne de caractères pour les caractères présents dans le texte original tiny_shakespeare.\n", 289 | "\n", 290 | "* Vérifier l'impact de votre modification en termes de performances." 291 | ] 292 | }, 293 | { 294 | "cell_type": "code", 295 | "execution_count": null, 296 | "id": "67dc4309-1a26-4451-a67a-f641be96d307", 297 | "metadata": {}, 298 | "outputs": [], 299 | "source": [ 300 | "# votre code ici" 301 | ] 302 | }, 303 | { 304 | "cell_type": "markdown", 305 | "id": "70eaba4c-ebb5-4daf-91fc-2795bad681d4", 306 | "metadata": {}, 307 | "source": [ 308 | "## Partie 3. Algorithme Byte Pair Encoding (BPE)\n" 309 | ] 310 | }, 311 | { 312 | "cell_type": "markdown", 313 | "id": "a7e892ba-c8d2-4910-b10c-8be1e3cba3ea", 314 | "metadata": {}, 315 | "source": [ 316 | "Dans l'exemple précédent, nous avons construit des paires de lettres de manière irréfléchie, simplement en stockant les paires au fur et à mesure qu'elles se présentent. L'algorithme Byte Pair Encoding permet de construire un vocabulaire de **token** (groupes de 2 ou plus lettres formant le vocabulaire de base) à partir de l'**analyse de la fréquence d'occurrence** dans un **corpus** (dans notre cas, le corpus est le fichier \"tiny shakespeare\"). Byte Pair Encoding (BPE) est un des algorithmes de **tokenization** les plus populaires, utilisé notamment dans les grands modèles de langage type ChatGPT.\n", 317 | "\n", 318 | "Nous allons, dans les questions suivantes, implémenter un algorithme BPE à partir de zéro, puis ensuite nous le confronterons à des implémentations industrielles libres (celles d'OpenAI et HuggingFace)." 319 | ] 320 | }, 321 | { 322 | "cell_type": "markdown", 323 | "id": "13de5fa8-9dc5-4cd0-8396-6c5a1d3fa51c", 324 | "metadata": {}, 325 | "source": [ 326 | "## Question 11. Fréquence de mots\n", 327 | "\n", 328 | "Ecrire une fonction `frequence_mots` qui prend comme paramètre une chaine de caractères `input_str` et qui renvoie un dictionnaire dont les clés sont les mots et les valeurs sont le nombre d'occurrences de ces mots dans la chaîne.\n", 329 | "\n", 330 | "Par exemple, dans la chaîne \"le chien Pluto et le chien Milou\", le mot \"chien\" est présent 2 fois, le mot 'le' aussi, on obtiendra donc: \n", 331 | "\n", 332 | "{'le': 2, 'chien': 2, 'Pluto': 1, 'et': 1, 'Milou': 1)}\n", 333 | "\n", 334 | "Nous allons travailler, dans ce qui suit, avec la chaîne de caractères suivante:\n", 335 | "```python\n", 336 | "corpus = \"This is the Hugging Face Course. This chapter is about tokenization. This section shows several tokenizer algorithms. Hopefully, you will be able to understand how they are trained and generate tokens.\"\n", 337 | "```\n", 338 | "\n", 339 | "Vérifier que\n", 340 | "```python\n", 341 | "mots_freqs = frequence_mots(corpus)\n", 342 | "print(mots_freqs)\n", 343 | "```\n", 344 | "\n", 345 | "renvoie bien\n", 346 | "\n", 347 | "```python\n", 348 | "{'This': 3, 'is': 2, 'the': 1, 'Hugging': 1, 'Face': 1, 'Course.': 1, 'chapter': 1, 'about': 1, 'tokenization.': 1, 'section': 1, 'shows': 1, 'several': 1, 'tokenizer': 1, 'algorithms.': 1, 'Hopefully,': 1, 'you': 1, 'will': 1, 'be': 1, 'able': 1, 'to': 1, 'understand': 1, 'how': 1, 'they': 1, 'are': 1, 'trained': 1, 'and': 1, 'generate': 1, 'tokens.': 1}\n", 349 | "```" 350 | ] 351 | }, 352 | { 353 | "cell_type": "code", 354 | "execution_count": null, 355 | "id": "bbead76c-e064-4baa-b5d9-5cd49d414822", 356 | "metadata": {}, 357 | "outputs": [], 358 | "source": [ 359 | "# votre code ici" 360 | ] 361 | }, 362 | { 363 | "cell_type": "markdown", 364 | "id": "ceaaa44c-8d77-4be1-b21e-3e44da6f3e3c", 365 | "metadata": {}, 366 | "source": [ 367 | "## Question 12. Alphabet et vocabulaire\n", 368 | "\n", 369 | "L'étape suivante est de déterminer le vocabulaire de base, formé par l'ensemble des caractères utilisés dans le corpus.\n", 370 | "\n", 371 | "* Ecrire une fonction `calcule_alphabet` qui prend comme paramètre un dictionnaire de fréquences de mots `dict_freq` et qui renvoie la liste des lettres utilisées ;\n", 372 | "\n", 373 | "* ensuite, créer le `vocabulaire` en ajoutant le token spécial `<|endoftext|>`:\n", 374 | "```python\n", 375 | "vocabulaire = [\"<|endoftext|>\"] + alphabet.copy()\n", 376 | "```\n", 377 | "\n", 378 | "Vous vérifierez que vous obtenez le vocabulaire suivant :\n", 379 | "```python\n", 380 | "['<|endoftext|>', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', 't', 'u', 'v', 'w', 'y', 'z']\n", 381 | "```\n" 382 | ] 383 | }, 384 | { 385 | "cell_type": "code", 386 | "execution_count": null, 387 | "id": "2f03f0c3-3d36-4cab-bd70-4613445fb9a2", 388 | "metadata": {}, 389 | "outputs": [], 390 | "source": [ 391 | "# votre code ici" 392 | ] 393 | }, 394 | { 395 | "cell_type": "markdown", 396 | "id": "cf5ea284-e593-4172-b3ea-fbd7ddaf8565", 397 | "metadata": {}, 398 | "source": [ 399 | "## Question 13. Splits\n", 400 | "\n", 401 | "Ecrire une fonction `calcule_splits` qui prend comme paramètre une liste de mots `liste_mots`et qui renvoie un dictionnaire qui associe à chaque mot la liste des lettres qui le composent. Par exemple :\n", 402 | "```python\n", 403 | "{'This': ['T', 'h', 'i', 's'], 'is': ['i', 's'], ...\n", 404 | "```\n", 405 | "Appliquer cette fonction aux mots servant de clé dans la dictionnaire `freqs`" 406 | ] 407 | }, 408 | { 409 | "cell_type": "code", 410 | "execution_count": null, 411 | "id": "62272ef6-77d3-4c1a-92d0-988d52c2709d", 412 | "metadata": {}, 413 | "outputs": [], 414 | "source": [ 415 | "# votre code ici" 416 | ] 417 | }, 418 | { 419 | "cell_type": "markdown", 420 | "id": "3bb0121c-385d-4933-8243-6a95d8712e24", 421 | "metadata": {}, 422 | "source": [ 423 | "## Question 14 - Fréquence des paires de lettres ou groupes de lettres\n", 424 | "\n", 425 | "Il s'agit ensuite de déterminer la fréquence de l'occurrence de chaque paire de lettres. Par exemple, le mot 'This' est associé aux lettres 'T', 'h', 'i et 's'. Il faut chercher, dans tous les mots, le nombre d'occurrences de 'T', 'h', puis de 'h','i', et de 'i','s'. Et ainsi de suite pour chaque mot.\n", 426 | "\n", 427 | "Ecrire une fonction `calcule_pair_freqs` qui retourne un dictionnaire avec comme clé un couple de lettres et comme valeur le nombre d'occurrences trouvées dans tous les mots. \n", 428 | "\n", 429 | "On aura par exemple en sortie:\n", 430 | "\n", 431 | "```python\n", 432 | "{('T', 'h'): 3,\n", 433 | " ('h', 'i'): 3,\n", 434 | " ('i', 's'): 5,\n", 435 | " ('t', 'h'): 3,\n", 436 | " ('h', 'e'): 2,\n", 437 | " ('H', 'u'): 1,\n", 438 | " ('u', 'g'): 1,\n", 439 | " ('g', 'g'): 1,\n", 440 | " ('g', 'i'): 1,\n", 441 | " ('i', 'n'): 2,\n", 442 | " ('n', 'g'): 1,\n", 443 | " ...}\n", 444 | "```" 445 | ] 446 | }, 447 | { 448 | "cell_type": "code", 449 | "execution_count": null, 450 | "id": "75ffee04-49d5-481e-b10d-46b93870484f", 451 | "metadata": {}, 452 | "outputs": [], 453 | "source": [ 454 | "# votre code ici" 455 | ] 456 | }, 457 | { 458 | "cell_type": "markdown", 459 | "id": "ddf3a797-8b5e-47e7-a0c7-8d9451d5301a", 460 | "metadata": {}, 461 | "source": [ 462 | "## Question 15. Paire la plus fréquente\n", 463 | "\n", 464 | "Créer une fonction `paire_la_plus_frequente` qui prend comme paramètre le dictionnaire issu de la fonction précédente (celui contenant la fréquence de chaque paire) et qui retourne la paire la plus fréquente du corpus ainsi que le nombre correspondant à la fréquence. Si deux paires présentent le même nombre d'occurrences, la fonction renvoie la première paire rencontrée dans le parcours de l'ensemble des paires.\n", 465 | "\n", 466 | "Vérifier que `paire_la_plus_frequente(pair_freqs)` renvoie `(('i', 's'), 5)`" 467 | ] 468 | }, 469 | { 470 | "cell_type": "code", 471 | "execution_count": null, 472 | "id": "8b6077c5-8bd5-49a8-a01d-65b8072cea6f", 473 | "metadata": {}, 474 | "outputs": [], 475 | "source": [ 476 | "# votre code ici" 477 | ] 478 | }, 479 | { 480 | "cell_type": "markdown", 481 | "id": "6bd64286-6c3c-44b8-9bbf-2e53b74b4f5b", 482 | "metadata": {}, 483 | "source": [ 484 | "## Question 16. Extension du vocabulaire\n", 485 | "\n", 486 | "On va maintenant rajouter au vocabulaire les combinaisons de lettres les plus fréquentes dans le texte.\n", 487 | "\n", 488 | "* créer un dictionnaire `fusions` qui associe, à la paire précédente `('i', 's')` la paire concaténée `'is'`\n", 489 | "\n", 490 | "* ajouter cette chaîne concaténée à la liste `vocabulaire`.\n", 491 | "\n", 492 | "Remarque : cette question est très facile, inutile de créer une fonction.\n", 493 | "\n", 494 | "Vous vérifierez que\n", 495 | "\n", 496 | "```python\n", 497 | "print(fusions)\n", 498 | "print(vocabulaire)\n", 499 | "```\n", 500 | "renvoie\n", 501 | "```\n", 502 | "{('i', 's'): 'is'}\n", 503 | "['<|endoftext|>', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', 't', 'u', 'v', 'w', 'y', 'z', 'is']\n", 504 | "```" 505 | ] 506 | }, 507 | { 508 | "cell_type": "code", 509 | "execution_count": null, 510 | "id": "10b91e46-4718-40df-b4aa-febbb8e5d541", 511 | "metadata": {}, 512 | "outputs": [], 513 | "source": [ 514 | "# votre code ici" 515 | ] 516 | }, 517 | { 518 | "cell_type": "markdown", 519 | "id": "53fb801b-d118-489b-898b-30ba79e73fe3", 520 | "metadata": {}, 521 | "source": [ 522 | "## Question 16. Fusion des paires\n", 523 | "\n", 524 | "C'est la dernière étape de l'algorithme de création du vocabulaire. On se donne une taille maximale du vocabulaire `vocab_size` que l'on fixe à `50`. Reproduire l'étape précédente (reherche de la paire la plus fréquence, fusion, ajout au vocabulaire) jusqu'à ce que la taille maximale du vocabulaire soit atteinte.\n", 525 | "\n", 526 | "Pour cette valeur de `vocab_size`, vérifier que vous obtenez le vocabulaire suivant :\n", 527 | "\n", 528 | "```python\n", 529 | "['<|endoftext|>', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', 't', 'u', 'v', 'w', 'y', 'z', 'is', 'is', 'er', 'to', 'en', 'Th', 'This', 'th', 'ou', 'se', 'tok', 'token', 'nd', 'the', 'in', 'ab', 'tokeni', 'tokeniz', 'at', 'io']\n", 530 | "```" 531 | ] 532 | }, 533 | { 534 | "cell_type": "code", 535 | "execution_count": null, 536 | "id": "9900bfce-772d-41ea-94de-a4ec05cdbdac", 537 | "metadata": {}, 538 | "outputs": [], 539 | "source": [ 540 | "# votre code ici" 541 | ] 542 | }, 543 | { 544 | "cell_type": "markdown", 545 | "id": "439f54eb-bc03-4e98-bca3-080a5c8fc347", 546 | "metadata": {}, 547 | "source": [ 548 | "## Question 17 - Tokenization\n", 549 | "\n", 550 | "La dernière étape de ce voyage vers les tokens consiste à créer une fonction `tokenize` qui prend une chaine de caractères et, comme dans le début de ce TP, contient l'ensemble des entiers faisant référence au vocabulaire de 50 termes construit précédemment.\n", 551 | "\n", 552 | "Vérifier que la tokenization :\n", 553 | "\n", 554 | "```python\n", 555 | "print(tokenize('This is not a token.'))\n", 556 | "```\n", 557 | "\n", 558 | "Donne bien\n", 559 | "\n", 560 | "```python\n", 561 | "['This', 'is', 'n', 'o', 't', 'a', 'token', '.']\n", 562 | "```" 563 | ] 564 | }, 565 | { 566 | "cell_type": "code", 567 | "execution_count": null, 568 | "id": "04f2625f-094f-4cf9-bcb4-f5ead7a8f4bf", 569 | "metadata": {}, 570 | "outputs": [], 571 | "source": [ 572 | "# votre code ici" 573 | ] 574 | }, 575 | { 576 | "cell_type": "markdown", 577 | "id": "01c50540-7976-483a-93e9-9965fea46600", 578 | "metadata": {}, 579 | "source": [ 580 | "## Question 18 - Bilan de la qualité algorithmique\n", 581 | "\n", 582 | "Pour la châine de caractères `This is not a token`, comparer la taille de la liste d'entiers obtenue pour chacun des 3 tokenizers étudiés.\n" 583 | ] 584 | }, 585 | { 586 | "cell_type": "markdown", 587 | "id": "63da73d1-9e8e-4e3e-a09a-9b06c457478c", 588 | "metadata": {}, 589 | "source": [ 590 | "## Question 19 - Performances de notre BPE\n", 591 | "\n", 592 | "Reprendre les questions précédentes en travaillant à partir du corpus `tinyshakespeare`." 593 | ] 594 | }, 595 | { 596 | "cell_type": "markdown", 597 | "id": "c4e647ec-3ad1-4bba-8218-aada2429221c", 598 | "metadata": {}, 599 | "source": [ 600 | "## Question 20. Implémentations industrielles de tokenizers GPT2 - OpenAI, HuggingFace\n", 601 | "\n", 602 | "* BPE est utilisé par OpenAI pour ses gpt depuis gpt2. C'est la bibliothèque `tiktoken` (https://github.com/openai/tiktoken) qui implémente cet algorithme\n", 603 | "\n", 604 | "* BPE est aussi utilisé par un autre grand acteur de l'IA générative : HuggingFace, dans sa bibliothèque `transformers` (https://github.com/huggingface/transformers)\n", 605 | "\n", 606 | "Pour utiliser la bibliothèque **tiktoken**, encoder/décoder :\n", 607 | "```python\n", 608 | "enc = tiktoken.get_encoding('gpt2')\n", 609 | "print(f\"Nombre d'élements dans le vocabulaire : {enc.n_vocab}\")\n", 610 | "enc.encode('This is not a token')\n", 611 | "```\n", 612 | "\n", 613 | "Pour utiliser la bibliothèque **transformers**, encoder/décoder :\n", 614 | "```python\n", 615 | "from transformers import GPT2Tokenizer\n", 616 | "tokenizer = GPT2Tokenizer.from_pretrained(\"gpt2\")\n", 617 | "tokenizer(\"This is not a token\")['input_ids']\n", 618 | "```\n", 619 | "\n", 620 | "* Quelle est la taille du vocabulaire `gpt2`?\n", 621 | "\n", 622 | "* Vérifier que les deux implémentations renvoient la même liste d'entiers pour l'encodage de la chaîne `This is not a token`.\n", 623 | "\n", 624 | "* Comparer ces implémentations de BPE avec celle que nous avons faite précédemment.\n", 625 | "\n", 626 | "* Comparer ces deux bibliothèques en encodage/décodage par rapport à la vitesse en octets/seconde.\n", 627 | "\n", 628 | "* Conclure." 629 | ] 630 | }, 631 | { 632 | "cell_type": "code", 633 | "execution_count": null, 634 | "id": "801cf09c-ecc5-4d0b-a9fb-3facc42079a7", 635 | "metadata": {}, 636 | "outputs": [], 637 | "source": [ 638 | "# votre code ici" 639 | ] 640 | } 641 | ], 642 | "metadata": { 643 | "kernelspec": { 644 | "display_name": "Python 3 (ipykernel)", 645 | "language": "python", 646 | "name": "python3" 647 | }, 648 | "language_info": { 649 | "codemirror_mode": { 650 | "name": "ipython", 651 | "version": 3 652 | }, 653 | "file_extension": ".py", 654 | "mimetype": "text/x-python", 655 | "name": "python", 656 | "nbconvert_exporter": "python", 657 | "pygments_lexer": "ipython3", 658 | "version": "3.9.0" 659 | } 660 | }, 661 | "nbformat": 4, 662 | "nbformat_minor": 5 663 | } 664 | -------------------------------------------------------------------------------- /Chronometrage_Prof.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "df55d0a1-aa03-418f-9e4e-d51c332fe075", 6 | "metadata": { 7 | "jp-MarkdownHeadingCollapsed": true 8 | }, 9 | "source": [ 10 | "## Mesure du temps d'exécution : la méthode naïve avec un chronomètre\n", 11 | "L'objectif de cette courte activité est de mesurer le temps d'exécution de quelques fonctions qui implémentent des algorithmes sur des chaînes de caractères ou des nombres.\n", 12 | "\n", 13 | "La technique utilisée est la suivante : on relève la valeur d'un chronomètre juste avant et juste après l'appel à la fonction et on czalcule la différece, appelée `duree`.\n", 14 | "\n", 15 | "Si le temps d'exécution est très court, on effectue $N$ fois l'appel à la fonction à l'aide d'une boucle, et on divise la durée totale par N :\n", 16 | "\n", 17 | "$ duree = \\frac{N}{t_f - t_i}$" 18 | ] 19 | }, 20 | { 21 | "cell_type": "markdown", 22 | "id": "2ace14b4-160a-4712-9c32-b1ac4034ca2a", 23 | "metadata": {}, 24 | "source": [ 25 | "## Utilisation d'un chronomètre avec Python\n", 26 | "On utilisera le modèle suivant pour mesurer la durée d'une opération :\n", 27 | "```python\n", 28 | "from time import perf_counter\n", 29 | "\n", 30 | "t_init = perf_counter()\n", 31 | "\n", 32 | "# ce dont on souhaite mesurer la durée\n", 33 | "\n", 34 | "t_final = perf_counter()\n", 35 | "duree = t_final - t_init\n", 36 | "\n", 37 | "print(f\"Durée : {duree} secondes\")\n", 38 | "```\n", 39 | "\n", 40 | "\n", 41 | "## Utilisation d'un chronomètre avec c++\n", 42 | "On utilisera le modèle suivant pour mesurer la durée d'une opération :\n", 43 | "```c++\n", 44 | "#include \n", 45 | "#include \n", 46 | "#include \n", 47 | "#include \n", 48 | "\n", 49 | "int main() {\n", 50 | "\tusing namespace std::chrono;\n", 51 | "\n", 52 | "\thigh_resolution_clock::time_point t_init = high_resolution_clock::now();\n", 53 | "\t\n", 54 | " // ce dont on veut mesurer la durée\n", 55 | " \n", 56 | "\thigh_resolution_clock::time_point t_final = high_resolution_clock::now();\n", 57 | "\tduration duree = duration_cast>(t_final - t_init);\n", 58 | "\tstd::cout << \"Duree =\" << duree.count() << \" secondes\" << std::endl ;\n", 59 | "\n", 60 | "return 0;\n", 61 | "}\n", 62 | "```" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "id": "08380ecd-0ca1-4075-9a87-049419ae8a1b", 68 | "metadata": {}, 69 | "source": [ 70 | "## Objectif du TP\n", 71 | "L'objectif de cette activité est de comparer les implémentations python et c++ pour des algorithmes classiques, avec un criètre de temps d'exécution." 72 | ] 73 | }, 74 | { 75 | "cell_type": "markdown", 76 | "id": "8e5b0e46-6adc-42f2-8057-66ce37b80a3f", 77 | "metadata": {}, 78 | "source": [ 79 | "## Rappel : Exécution d'un programme c++\n", 80 | "On rappelle qu'on utilise le compilateur `g++` pour produire les exécutables, dont la syntaxe est la suivante :\n", 81 | "\n", 82 | "```bash\n", 83 | "$ g++ fichier_source.cpp -o executable\n", 84 | "```\n", 85 | "\n", 86 | "vérifier que l'exécutable a été correctement généré avec la commande suivante :\n", 87 | "```bash\n", 88 | "$ ls\n", 89 | "```\n", 90 | "puis exécuter le programme avec la commande :\n", 91 | "```\n", 92 | "$ ./executable\n", 93 | "```" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": 2, 99 | "id": "bbd16673-06bb-4e11-897d-995c03bdd298", 100 | "metadata": {}, 101 | "outputs": [], 102 | "source": [ 103 | "import matplotlib.pyplot as plt" 104 | ] 105 | }, 106 | { 107 | "cell_type": "markdown", 108 | "id": "defa556f-602c-45f8-9e13-f46e2904835a", 109 | "metadata": {}, 110 | "source": [ 111 | "### Activité 1. Mesure du temps nécessaire pour comparer deux chaines de 10 caractères avec Python - Méthode `perf_counter`\n", 112 | "* En utilisant un chronomètre comme expliqué plus haut, effectuer 10000 (dix mille) mesures du temps nécessaire pour comparer deux chaînes de 10 caractères dont seul le dernier diffère\n", 113 | "* déterminer le temps mini, le temps maxi, le temps moyen\n", 114 | "* représenter sur un graphique l'ensemble de ces temps de calcul\n", 115 | "* que constatez-vous ?" 116 | ] 117 | }, 118 | { 119 | "cell_type": "code", 120 | "execution_count": 4, 121 | "id": "059a3497-ccbb-403c-a710-68467baeeaed", 122 | "metadata": { 123 | "scrolled": true 124 | }, 125 | "outputs": [ 126 | { 127 | "name": "stdout", 128 | "output_type": "stream", 129 | "text": [ 130 | "Temps mini : 7.399648893624544e-08\n", 131 | "Temps maxi : 0.00019766799960052595\n", 132 | "Temps moyen : 9.995644650189206e-08\n" 133 | ] 134 | }, 135 | { 136 | "data": { 137 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAmYAAAGwCAYAAAADo6klAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAABgUUlEQVR4nO3de1xUdRo/8M8MtwGUmwgDhUpFaoqiss5imm2OgritmGvisnlZVqufbLpsWpjiJQu1bJWyWO2i9fO+tdSaEoSSqRMoXvGWlrfUwQvCCAgMzPf3hz+OHhmRQRjOxuf9es1L5nue8z3PeQh4OrdRCSEEiIiIiKjFqVs6ASIiIiK6iY0ZERERkUKwMSMiIiJSCDZmRERERArBxoyIiIhIIdiYERERESkEGzMiIiIihXBs6QSofhaLBRcuXEDbtm2hUqlaOh0iIiJqACEErl+/jsDAQKjVDT8OxsZM4S5cuICgoKCWToOIiIga4dy5c3jwwQcbHM/GTOHatm0L4OY31sPDo0nnNpvNyMzMxJAhQ+Dk5NSkc9MtrLN9sM72wTrbB+tsH81ZZ5PJhKCgIOnveEOxMVO42tOXHh4ezdKYubm5wcPDgz/4zYh1tg/W2T5YZ/tgne3DHnW29TIkXvxPREREpBBszIiIiIgUgo0ZERERkUKwMSMiIiJSCDZmRERERArBxoyIiIhIIRTRmC1btgydOnWCRqOBTqdDXl5evfEbN25Ely5doNFoEBoais2bN8uWCyGQnJyMgIAAuLq6Qq/X48SJE7KYoqIixMXFwcPDA15eXoiPj0dpaam0PCcnB8OHD0dAQADc3d0RFhaG1atXN0suRERERIACGrP169cjMTERs2fPxt69e9GzZ09ERkbi0qVLVuN37dqFMWPGID4+Hvv27UNMTAxiYmJQUFAgxSxatAipqalIS0tDbm4u3N3dERkZiYqKCikmLi4Ohw8fRlZWFjZt2oTt27dj0qRJsu306NEDn3/+OQ4ePIgJEyZg7Nix2LRpU5PnQkRERAQAEC2sb9++YvLkydL7mpoaERgYKFJSUqzGP/vss2LYsGGyMZ1OJ55//nkhhBAWi0VotVrx1ltvScuLi4uFi4uLWLt2rRBCiCNHjggAYvfu3VLMli1bhEqlEufPn79rrtHR0WLChAlNmsu9lJSUCACipKSkQfG2qKqqEunp6aKqqqrJ56ZbWGf7YJ3tg3W2D9bZPpqzzo39+92iT/6vqqpCfn4+kpKSpDG1Wg29Xg+DwWB1HYPBgMTERNlYZGQk0tPTAQCnTp2C0WiEXq+Xlnt6ekKn08FgMCA2NhYGgwFeXl4IDw+XYvR6PdRqNXJzczFixAir2y4pKUHXrl2bNJc7VVZWorKyUnpvMpkA3Hw6sdlstppXY9XO19TzkhzrbB+ss32wzvbBOttHc9a5sXO2aGN25coV1NTUwN/fXzbu7++PY8eOWV3HaDRajTcajdLy2rH6Yvz8/GTLHR0d4ePjI8XcacOGDdi9ezf+9a9/NWkud0pJScHcuXPrjGdmZsLNzc3qOvcrKyurWeYlOdbZPlhn+2Cd7YN1to/mqHN5eXmj1uNnZTbAtm3bMGHCBKxYsQLdunVr1m0lJSXJjsLVfgjqkCFDmuWzMrOysjB48GB+FlszYp3tg3W2D9bZPlhn+2jOOtee8bJVizZmvr6+cHBwQGFhoWy8sLAQWq3W6jparbbe+Np/CwsLERAQIIsJCwuTYu68uaC6uhpFRUV1tvvdd9/h6aefxj//+U+MHTu2yXO5k4uLC1xcXOqMOzk5NdsPZ3POTbewzvbBOlt3o6oGrs4OTTYf62wfrLN9NEedGztfi96V6ezsjD59+iA7O1sas1gsyM7ORkREhNV1IiIiZPHAzUOQtfHBwcHQarWyGJPJhNzcXCkmIiICxcXFyM/Pl2K2bt0Ki8UCnU4njeXk5GDYsGFYuHCh7I7NpsyFiKi5rdx5Cl2TM/DVgQstnQoR3UOLn8pMTEzEuHHjEB4ejr59+2LJkiUoKyvDhAkTAABjx47FAw88gJSUFADAlClTMHDgQCxevBjDhg3DunXrsGfPHixfvhwAoFKpMHXqVMyfPx8hISEIDg7GrFmzEBgYiJiYGABA165dERUVhYkTJyItLQ1msxkJCQmIjY1FYGAggJunL3//+99jypQpGDlypHRNmLOzM3x8fJosFyKi5jbnv0cAAC+t3Yc/9Axs4WyIqD4t3piNHj0aly9fRnJyMoxGI8LCwpCRkSFdMH/27Fmo1bcO7PXr1w9r1qzBzJkzMWPGDISEhCA9PR3du3eXYqZPn46ysjJMmjQJxcXF6N+/PzIyMqDRaKSY1atXIyEhAYMGDYJarcbIkSORmpoqLV+1ahXKy8uRkpIiNYUAMHDgQOTk5DRpLkREREQAoBJCiJZOgu7OZDLB09MTJSUlzXLx/+bNmxEdHc1rGJoR62wfrPPddXr1a+nr0wuG3ddcrLN9sM720Zx1buzf7xZ/8j8RERER3cTGjIiIiEgh2JgRERERKQQbMyIiIiKFYGNGREREpBBszIiIiIgUgo0ZERERkUKwMSMiIiJSCDZmRERERArBxoyIiIhIIdiYERERESkEGzMiIiIihWBjRkRERKQQbMyIiIiIFIKNGREREZFCsDEjIiIiUgg2ZkREREQKwcaMiIiISCHYmBEREREpBBszIiIiIoVgY0ZERESkEGzMiIiIiBSCjRkRERGRQrAxIyIiIlIINmZERERECsHGjIiIiEgh2JgRERERKQQbMyIiIiKFYGNGREREpBBszIiIiIgUgo0ZERERkUKwMSMiIiJSiBZvzJYtW4ZOnTpBo9FAp9MhLy+v3viNGzeiS5cu0Gg0CA0NxebNm2XLhRBITk5GQEAAXF1dodfrceLECVlMUVER4uLi4OHhAS8vL8THx6O0tFRaXlFRgfHjxyM0NBSOjo6IiYmpk8f48eOhUqnqvLp16ybFzJkzp87yLl26NKJKRERE1Bq0aGO2fv16JCYmYvbs2di7dy969uyJyMhIXLp0yWr8rl27MGbMGMTHx2Pfvn2IiYlBTEwMCgoKpJhFixYhNTUVaWlpyM3Nhbu7OyIjI1FRUSHFxMXF4fDhw8jKysKmTZuwfft2TJo0SVpeU1MDV1dXvPTSS9Dr9VZzWbp0KS5evCi9zp07Bx8fH4waNUoW161bN1ncjh077qdkRERE9Cvm2JIbf+eddzBx4kRMmDABAJCWloavv/4aH3/8MV599dU68UuXLkVUVBSmTZsGAHj99deRlZWF9957D2lpaRBCYMmSJZg5cyaGDx8OAPj000/h7++P9PR0xMbG4ujRo8jIyMDu3bsRHh4OAHj33XcRHR2Nt99+G4GBgXB3d8cHH3wAANi5cyeKi4vr5OLp6QlPT0/pfXp6Oq5duybtSy1HR0dotdoG16SyshKVlZXSe5PJBAAwm80wm80Nnqchaudr6nlJjnW2D9a5Ye63PqyzfbDO9tGcdW7snC3WmFVVVSE/Px9JSUnSmFqthl6vh8FgsLqOwWBAYmKibCwyMhLp6ekAgFOnTsFoNMqOcnl6ekKn08FgMCA2NhYGgwFeXl5SUwYAer0earUaubm5GDFiRKP256OPPoJer0fHjh1l4ydOnEBgYCA0Gg0iIiKQkpKCDh063HWelJQUzJ07t854ZmYm3NzcGpXbvWRlZTXLvCTHOtsH62zNrV/1d17+0Viss32wzvbRHHUuLy9v1Hot1phduXIFNTU18Pf3l437+/vj2LFjVtcxGo1W441Go7S8dqy+GD8/P9lyR0dH+Pj4SDG2unDhArZs2YI1a9bIxnU6HVauXInOnTvj4sWLmDt3LgYMGICCggK0bdvW6lxJSUmy5tNkMiEoKAhDhgyBh4dHo/K7G7PZjKysLAwePBhOTk5NOjfdwjrbB+t8d1MMmdLX0dHR9zUX62wfrLN9NGeda8942apFT2X+WqxatQpeXl51bhIYOnSo9HWPHj2g0+nQsWNHbNiwAfHx8VbncnFxgYuLS51xJyenZvvhbM656RbW2T5Y5/o1VW1YZ/tgne2jOerc2Pla7OJ/X19fODg4oLCwUDZeWFh412uytFptvfG1/94r5s6bC6qrq1FUVGTTtWC1hBD4+OOP8dxzz8HZ2bneWC8vLzz66KM4efKkzdshIiKiX78Wa8ycnZ3Rp08fZGdnS2MWiwXZ2dmIiIiwuk5ERIQsHrh5Xrg2Pjg4GFqtVhZjMpmQm5srxURERKC4uBj5+flSzNatW2GxWKDT6Wzej++++w4nT5686xGw25WWluKnn35CQECAzdshIiKiX78WPZWZmJiIcePGITw8HH379sWSJUtQVlYm3dk4duxYPPDAA0hJSQEATJkyBQMHDsTixYsxbNgwrFu3Dnv27MHy5csBACqVClOnTsX8+fMREhKC4OBgzJo1C4GBgdJpxq5duyIqKgoTJ05EWloazGYzEhISEBsbi8DAQCm3I0eOoKqqCkVFRbh+/Tr2798PAAgLC5Ptw0cffQSdTofu3bvX2b+XX34ZTz/9NDp27IgLFy5g9uzZcHBwwJgxY5q4kkRERPRr0KKN2ejRo3H58mUkJyfDaDQiLCwMGRkZ0sX7Z8+ehVp966Bev379sGbNGsycORMzZsxASEgI0tPTZU3R9OnTUVZWhkmTJqG4uBj9+/dHRkYGNBqNFLN69WokJCRg0KBBUKvVGDlyJFJTU2W5RUdH48yZM9L7Xr16Abh56rJWSUkJPv/8cyxdutTq/v3yyy8YM2YMrl69ivbt26N///744Ycf0L59+/uoGhEREf1atfjF/wkJCUhISLC6LCcnp87YqFGj6jzE9XYqlQrz5s3DvHnz7hrj4+NT5w7KO50+fbre5cDNR3HUdzvsunXr7jkHERERUa0W/0gmIiIiIrqJjRkRERGRQrAxIyIiIlIINmZERERECsHGjIiIiEgh2JgRERERKQQbMyIiIiKFYGNGREREpBBszIiIiIgUgo0ZERERkUKwMSMiIiJSCDZmRERERArBxoyIiIhIIdiYERERESkEGzMiIiIihWBjRkRERKQQbMyIiIiIFIKNGREREZFCsDEjIiIiUgg2ZkREREQKwcaMiIiISCHYmBEREREpBBszIiIiIoVgY0ZERESkEGzMiIiIiBSCjRkRERGRQrAxIyIiIlIINmZERERECsHGjIiIiEgh2JgRERERKQQbMyIiIiKFaPHGbNmyZejUqRM0Gg10Oh3y8vLqjd+4cSO6dOkCjUaD0NBQbN68WbZcCIHk5GQEBATA1dUVer0eJ06ckMUUFRUhLi4OHh4e8PLyQnx8PEpLS6XlFRUVGD9+PEJDQ+Ho6IiYmJg6eeTk5EClUtV5GY3G+9o/IiIiar1atDFbv349EhMTMXv2bOzduxc9e/ZEZGQkLl26ZDV+165dGDNmDOLj47Fv3z7ExMQgJiYGBQUFUsyiRYuQmpqKtLQ05Obmwt3dHZGRkaioqJBi4uLicPjwYWRlZWHTpk3Yvn07Jk2aJC2vqamBq6srXnrpJej1+nr34fjx47h48aL08vPza/T+ERERUevWoo3ZO++8g4kTJ2LChAl47LHHkJaWBjc3N3z88cdW45cuXYqoqChMmzYNXbt2xeuvv47evXvjvffeA3DzaNmSJUswc+ZMDB8+HD169MCnn36KCxcuID09HQBw9OhRZGRk4MMPP4ROp0P//v3x7rvvYt26dbhw4QIAwN3dHR988AEmTpwIrVZb7z74+flBq9VKL7X6Vklt3T8iIiJq3RxbasNVVVXIz89HUlKSNKZWq6HX62EwGKyuYzAYkJiYKBuLjIyUmq5Tp07BaDTKjnJ5enpCp9PBYDAgNjYWBoMBXl5eCA8Pl2L0ej3UajVyc3MxYsQIm/YjLCwMlZWV6N69O+bMmYPHH3+80fsHAJWVlaisrJTem0wmAIDZbIbZbLYpt3upna+p5yU51tk+WOeGud/6sM72wTrbR3PWubFztlhjduXKFdTU1MDf31827u/vj2PHjlldx2g0Wo2vva6r9t97xdx+uhEAHB0d4ePjU+f6sPoEBAQgLS0N4eHhqKysxIcffognn3wSubm56N27d6P2DwBSUlIwd+7cOuOZmZlwc3NrcH62yMrKapZ5SY51tg/W2Zpbv+rvvC63sVhn+2Cd7aM56lxeXt6o9VqsMftf17lzZ3Tu3Fl6369fP/z000/45z//ic8++6zR8yYlJcmOCppMJgQFBWHIkCHw8PC4r5zvZDabkZWVhcGDB8PJyalJ56ZbWGf7YJ3vboohU/o6Ojr6vuZine2DdbaP5qxz7RkvW7VYY+br6wsHBwcUFhbKxgsLC+96XZdWq603vvbfwsJCBAQEyGLCwsKkmDsvvq+urkZRUdE9rye7l759+2LHjh0AGrd/AODi4gIXF5c6405OTs32w9mcc9MtrLN9sM71a6rasM72wTrbR3PUubHztdjF/87OzujTpw+ys7OlMYvFguzsbERERFhdJyIiQhYP3Dz8WBsfHBwMrVYrizGZTMjNzZViIiIiUFxcjPz8fClm69atsFgs0Ol097VP+/fvlxrCxuwfERERtW4teiozMTER48aNQ3h4OPr27YslS5agrKwMEyZMAACMHTsWDzzwAFJSUgAAU6ZMwcCBA7F48WIMGzYM69atw549e7B8+XIAgEqlwtSpUzF//nyEhIQgODgYs2bNQmBgoPQssq5duyIqKgoTJ05EWloazGYzEhISEBsbi8DAQCm3I0eOoKqqCkVFRbh+/Tr2798PANKRtyVLliA4OBjdunVDRUUFPvzwQ2zduhWZmbdOGdxr/4iIiIhu16KN2ejRo3H58mUkJyfDaDQiLCwMGRkZ0gXzZ8+elT1+ol+/flizZg1mzpyJGTNmICQkBOnp6ejevbsUM336dJSVlWHSpEkoLi5G//79kZGRAY1GI8WsXr0aCQkJGDRoENRqNUaOHInU1FRZbtHR0Thz5oz0vlevXgBuPpIDuHnX5T/+8Q+cP38ebm5u6NGjB7799lv87ne/a/D+EREREd1OJWo7DVIkk8kET09PlJSUNMvF/5s3b0Z0dDSvYWhGrLN9sM531+nVr6WvTy8Ydl9zsc72wTrbR3PWubF/v1v8I5mIiIiI6CY2ZkREREQKwcaMiIiISCHYmBEREREpBBszIiIiIoVgY0ZERESkEGzMiIiIiBSCjRkRERGRQrAxIyIiIlIINmZERERECsHGjIiIiEgh2JgRERERKQQbMyIiIiKFYGNGREREpBBszIiIiIgUgo0ZERERkUKwMSMiIiJSCDZmRERERArBxoyIiIhIIdiYERERESkEGzMiIiIihWBjRkRERKQQbMyIiIiIFIKNGREREZFCsDEjIiIiUgg2ZkREREQKwcaMiIiISCEcGxL01VdfNXjCP/zhD41OhoiIiKg1a1BjFhMT06DJVCoVampq7icfIiIiolarQY2ZxWJp7jyIiIiIWj1eY0ZERESkEA06Yna7efPm1bs8OTm50ckQERERtWY2HzH7z3/+I3tt2LABCxcuxOLFi5Genm5zAsuWLUOnTp2g0Wig0+mQl5dXb/zGjRvRpUsXaDQahIaGYvPmzbLlQggkJycjICAArq6u0Ov1OHHihCymqKgIcXFx8PDwgJeXF+Lj41FaWiotr6iowPjx4xEaGgpHR0er19h98cUXGDx4MNq3bw8PDw9ERETgm2++kcXMmTMHKpVK9urSpYuNFSIiIqLWwubGbN++fbJXQUEBLl68iEGDBuHvf/+7TXOtX78eiYmJmD17Nvbu3YuePXsiMjISly5dshq/a9cujBkzBvHx8di3bx9iYmIQExODgoICKWbRokVITU1FWloacnNz4e7ujsjISFRUVEgxcXFxOHz4MLKysrBp0yZs374dkyZNkpbX1NTA1dUVL730EvR6vdVctm/fjsGDB2Pz5s3Iz8/H7373Ozz99NPYt2+fLK5bt264ePGi9NqxY4dNNSIiIqLWw+ZTmdZ4eHhg7ty5ePrpp/Hcc881eL133nkHEydOxIQJEwAAaWlp+Prrr/Hxxx/j1VdfrRO/dOlSREVFYdq0aQCA119/HVlZWXjvvfeQlpYGIQSWLFmCmTNnYvjw4QCATz/9FP7+/khPT0dsbCyOHj2KjIwM7N69G+Hh4QCAd999F9HR0Xj77bcRGBgId3d3fPDBBwCAnTt3ori4uE4uS5Yskb1/88038eWXX+K///0vevXqJY07OjpCq9U2uCaVlZWorKyU3ptMJgCA2WyG2Wxu8DwNUTtfU89LcqyzfbDODXO/9WGd7YN1to/mrHNj52ySxgwASkpKUFJS0uD4qqoq5OfnIykpSRpTq9XQ6/UwGAxW1zEYDEhMTJSNRUZGSqdQT506BaPRKDvK5enpCZ1OB4PBgNjYWBgMBnh5eUlNGQDo9Xqo1Wrk5uZixIgRDd6H21ksFly/fh0+Pj6y8RMnTiAwMBAajQYRERFISUlBhw4d7jpPSkoK5s6dW2c8MzMTbm5ujcrtXrKyspplXpJjne2Ddbbm1q/6Oy//aCzW2T5YZ/tojjqXl5c3aj2bG7PU1FTZeyEELl68iM8++wxDhw5t8DxXrlxBTU0N/P39ZeP+/v44duyY1XWMRqPVeKPRKC2vHasvxs/PT7bc0dERPj4+UkxjvP322ygtLcWzzz4rjel0OqxcuRKdO3fGxYsXMXfuXAwYMAAFBQVo27at1XmSkpJkzafJZEJQUBCGDBkCDw+PRudnjdlsRlZWFgYPHgwnJ6cmnZtuYZ3tg3W+uymGTOnr6Ojo+5qLdbYP1tk+mrPOtWe8bGVzY/bPf/5T9l6tVqN9+/YYN26c7OhXa7JmzRrMnTsXX375pazpu71R7dGjB3Q6HTp27IgNGzYgPj7e6lwuLi5wcXGpM+7k5NRsP5zNOTfdwjrbB+tcv6aqDetsH6yzfTRHnRs7n82N2alTpxq1oTv5+vrCwcEBhYWFsvHCwsK7XpOl1Wrrja/9t7CwEAEBAbKYsLAwKebOmwuqq6tRVFRk07VgtdatW4e//vWv2Lhx411vFKjl5eWFRx99FCdPnrR5O0RERPTrZ/NdmSUlJSgqKqozXlRUZNNhO2dnZ/Tp0wfZ2dnSmMViQXZ2NiIiIqyuExERIYsHbp4Xro0PDg6GVquVxZhMJuTm5koxERERKC4uRn5+vhSzdetWWCwW6HS6BucPAGvXrsWECROwdu1aDBs27J7xpaWl+Omnn2RNIxEREVEtmxuz2NhYrFu3rs74hg0bEBsba9NciYmJWLFiBVatWoWjR4/ixRdfRFlZmXSX5tixY2WnR6dMmYKMjAwsXrwYx44dw5w5c7Bnzx4kJCQAuPlZnVOnTsX8+fPx1Vdf4dChQxg7diwCAwOlZ5F17doVUVFRmDhxIvLy8rBz504kJCQgNjYWgYGB0raOHDmC/fv3o6ioCCUlJdi/fz/2798vLV+zZg3Gjh2LxYsXQ6fTwWg0wmg0ym6AePnll/Hdd9/h9OnT2LVrF0aMGAEHBweMGTPGpjoRERFR62Dzqczc3Fy88847dcaffPJJvPbaazbNNXr0aFy+fBnJyckwGo0ICwtDRkaGdPH+2bNnoVbf6h379euHNWvWYObMmZgxYwZCQkKQnp6O7t27SzHTp09HWVkZJk2ahOLiYvTv3x8ZGRnQaDRSzOrVq5GQkIBBgwZBrVZj5MiRdW5qiI6OxpkzZ6T3tY/AEEIAAJYvX47q6mpMnjwZkydPluLGjRuHlStXAgB++eUXjBkzBlevXkX79u3Rv39//PDDD2jfvr1NdSIiIqLWwebGrLKyEtXV1XXGzWYzbty4YXMCCQkJ0hGvO+Xk5NQZGzVqFEaNGnXX+VQqFebNm1fvR0f5+PhgzZo19eZ1+vTpepdby+1O1o4sEhEREd2Nzacy+/bti+XLl9cZT0tLQ58+fZokKSIiIqLWyOYjZvPnz4der8eBAwcwaNAgAEB2djZ2796NzMzMe6xNRERERHdj8xGzxx9/HAaDAUFBQdiwYQP++9//4pFHHsHBgwcxYMCA5siRiIiIqFVo1EcyhYWFYfXq1U2dCxEREVGr1qDGzJbnkzX1xwYRERERtRYNasy8vLygUqnqjRFCQKVSoaampkkSIyIiImptGtSYbdu2rbnzICIiImr1GtSYDRw4sLnzICIiImr1GnXxPwCUl5fj7NmzqKqqko336NHjvpMiIiIiao1sbswuX76MCRMmYMuWLVaX8xozIiIiosax+TlmU6dORXFxMXJzc+Hq6oqMjAysWrUKISEh+Oqrr5ojRyIiIqJWweYjZlu3bsWXX36J8PBwqNVqdOzYEYMHD4aHhwdSUlIwbNiw5siTiIiI6FfP5iNmZWVl8PPzAwB4e3vj8uXLAIDQ0FDs3bu3abMjIiIiakVsbsw6d+6M48ePAwB69uyJf/3rXzh//jzS0tIQEBDQ5AkSERERtRY2n8qcMmUKLl68CACYPXs2oqKisHr1ajg7O2PlypVNnR8RERFRq2FzY/bnP/9Z+rpPnz44c+YMjh07hg4dOsDX17dJkyMiIiJqTRr9HLNabm5u6N27d1PkQkRERNSq2XyN2ciRI7Fw4cI644sWLcKoUaOaJCkiIiKi1sjmxmz79u2Ijo6uMz506FBs3769SZIiIiIiao1sbsxKS0vh7OxcZ9zJyQkmk6lJkiIiIiJqjWxuzEJDQ7F+/fo64+vWrcNjjz3WJEkRERERtUY2X/w/a9YsPPPMM/jpp5/w1FNPAQCys7Oxdu1abNy4sckTJCIiImotbG7Mnn76aaSnp+PNN9/Ev//9b7i6uqJHjx749ttvMXDgwObIkYiIiKhVaNTjMoYNG8bPxCQiIiJqYjZfY0ZEREREzYONGREREZFCsDEjIiIiUgg2ZkREREQK0ejGrKqqCsePH0d1dXVT5kNERETUatncmJWXlyM+Ph5ubm7o1q0bzp49CwD429/+hgULFjR5gkRERESthc2NWVJSEg4cOICcnBxoNBppXK/XW/1EACIiIiJqGJsbs/T0dLz33nvo378/VCqVNN6tWzf89NNPNiewbNkydOrUCRqNBjqdDnl5efXGb9y4EV26dIFGo0FoaCg2b94sWy6EQHJyMgICAuDq6gq9Xo8TJ07IYoqKihAXFwcPDw94eXkhPj4epaWl0vKKigqMHz8eoaGhcHR0RExMjNVccnJy0Lt3b7i4uOCRRx7BypUr73v/iIiIqPWyuTG7fPky/Pz86oyXlZXJGrWGWL9+PRITEzF79mzs3bsXPXv2RGRkJC5dumQ1fteuXRgzZgzi4+Oxb98+xMTEICYmBgUFBVLMokWLkJqairS0NOTm5sLd3R2RkZGoqKiQYuLi4nD48GFkZWVh06ZN2L59OyZNmiQtr6mpgaurK1566SXo9XqruZw6dQrDhg3D7373O+zfvx9Tp07FX//6V3zzzTeN3j8iIiJq5YSNBgwYIFJTU4UQQrRp00b8/PPPQgghEhISRGRkpE1z9e3bV0yePFl6X1NTIwIDA0VKSorV+GeffVYMGzZMNqbT6cTzzz8vhBDCYrEIrVYr3nrrLWl5cXGxcHFxEWvXrhVCCHHkyBEBQOzevVuK2bJli1CpVOL8+fN1tjlu3DgxfPjwOuPTp08X3bp1k42NHj1aVgNb98+akpISAUCUlJQ0eJ2GqqqqEunp6aKqqqrJ56ZbWGf7YJ3vruMrm6TX/WKd7YN1to/mrHNj/37b/JFMb775JoYOHYojR46guroaS5cuxZEjR7Br1y589913DZ6nqqoK+fn5SEpKksbUajX0ej0MBoPVdQwGAxITE2VjkZGRSE9PB3DzKJbRaJQd5fL09IROp4PBYEBsbCwMBgO8vLwQHh4uxej1eqjVauTm5mLEiBENyt9gMNQ5mhYZGYmpU6c2ev8AoLKyEpWVldJ7k8kEADCbzTCbzQ3KraFq52vqeUmOdbYP1rlh7rc+rLN9sM720Zx1buycNjdm/fv3x/79+7FgwQKEhoYiMzMTvXv3hsFgQGhoaIPnuXLlCmpqauDv7y8b9/f3x7Fjx6yuYzQarcYbjUZpee1YfTF3nop1dHSEj4+PFNMQd8vFZDLhxo0buHbtms37BwApKSmYO3dunfHMzEy4ubk1OD9bZGVlNcu8JMc62wfrbM2tX/V3XpfbWKyzfbDO9tEcdS4vL2/Ueo36EPOHH34YK1asaNQGqX5JSUmyo4ImkwlBQUEYMmQIPDw8mnRbZrMZWVlZGDx4MJycnJp0brqFdbYP1vnuphgypa+jo6Pvay7W2T5YZ/tozjrXnvGyVYMaM1smb2jz4OvrCwcHBxQWFsrGCwsLodVqra6j1Wrrja/9t7CwEAEBAbKYsLAwKebOi++rq6tRVFR01+3akouHhwdcXV3h4OBg8/4BgIuLC1xcXOqMOzk5NdsPZ3POTbewzvbBOtevqWrDOtsH62wfzVHnxs7XoLsyvby84O3t3aBXQzk7O6NPnz7Izs6WxiwWC7KzsxEREWF1nYiICFk8cPPwY218cHAwtFqtLMZkMiE3N1eKiYiIQHFxMfLz86WYrVu3wmKxQKfTNTj/e+XSmP0jIiKi1q1BR8y2bdsmfX369Gm8+uqrGD9+vNRgGAwGrFq1CikpKTZtPDExEePGjUN4eDj69u2LJUuWoKysDBMmTAAAjB07Fg888IA075QpUzBw4EAsXrwYw4YNw7p167Bnzx4sX74cAKBSqTB16lTMnz8fISEhCA4OxqxZsxAYGCg9i6xr166IiorCxIkTkZaWBrPZjISEBMTGxiIwMFDK7ciRI6iqqkJRURGuX7+O/fv3A4B05O2FF17Ae++9h+nTp+Mvf/kLtm7dig0bNuDrr79u8P4RERERydh6++dTTz0l1qxZU2d89erVYuDAgbZOJ959913RoUMH4ezsLPr27St++OEHadnAgQPFuHHjZPEbNmwQjz76qHB2dhbdunUTX3/9tWy5xWIRs2bNEv7+/sLFxUUMGjRIHD9+XBZz9epVMWbMGNGmTRvh4eEhJkyYIK5fvy6L6dixowBQ53W7bdu2ibCwMOHs7Cweeugh8cknn9i0fw3Bx2X872Od7YN1vjs+LuN/D+tsH0p8XIZKCCFsaeTc3Nxw4MABhISEyMZ//PFHhIWFNfouBLLOZDLB09MTJSUlzXLx/+bNmxEdHc1rGJoR62wfrPPddXr11pH80wuG3ddcrLN9sM720Zx1buzfb5uf/B8UFGT1jswPP/wQQUFBtk5HRERERP+fzY/L+Oc//4mRI0diy5Yt0sXyeXl5OHHiBD7//PMmT5CIiIiotbD5iFl0dDROnDiBP/zhDygqKkJRURGefvpp/Pjjj/f9fBwiIiKi1qxRD5h98MEH8cYbbzR1LkREREStms1HzIiIiIioebAxIyIiIlIINmZERERECsHGjIiIiEghbG7Mbty4IXuI7JkzZ7BkyRJkZmY2aWJERERErY3Njdnw4cPx6aefAgCKi4uh0+mwePFiDB8+HB988EGTJ0hERETUWtjcmO3duxcDBgwAAPz73/+Gv78/zpw5g08//RSpqalNniARERFRa2FzY1ZeXo62bdsCADIzM/HMM89ArVbjt7/9Lc6cOdPkCRIRERG1FjY3Zo888gjS09Nx7tw5fPPNNxgyZAgA4NKlS03+IdtERERErYnNjVlycjJefvlldOrUCX379kVERASAm0fPevXq1eQJEhEREbUWNn8k0x//+Ef0798fFy9eRM+ePaXxQYMGYcSIEU2aHBEREVFr0qjPytRqtdBqtTh37hwAICgoCH379m3SxIiIiIhaG5tPZVZXV2PWrFnw9PREp06d0KlTJ3h6emLmzJkwm83NkSMRERFRq2DzEbO//e1v+OKLL7Bo0SLp+jKDwYA5c+bg6tWrfJYZERERUSPZ3JitWbMG69atw9ChQ6WxHj16ICgoCGPGjGFjRkRERNRINp/KdHFxQadOneqMBwcHw9nZuSlyIiIiImqVbG7MEhIS8Prrr6OyslIaq6ysxBtvvIGEhIQmTY6IiIioNbH5VOa+ffuQnZ2NBx98UHpcxoEDB1BVVYVBgwbhmWeekWK/+OKLpsuUiIiI6FfO5sbMy8sLI0eOlI0FBQU1WUJERERErZXNjdknn3zSHHkQERERtXo2X2NGRERERM3D5iNmV69eRXJyMrZt24ZLly7BYrHIlhcVFTVZckREREStic2N2XPPPYeTJ08iPj4e/v7+UKlUzZEXERERUatjc2P2/fffY8eOHbIPMCciIiKi+2fzNWZdunTBjRs3miMXIiIiolbN5sbs/fffx2uvvYbvvvsOV69ehclkkr2IiIiIqHEa9Rwzk8mEp556SjYuhIBKpUJNTU2TJUdERETUmtjcmMXFxcHJyQlr1qzhxf9ERERETcjmU5kFBQX45JNPMHr0aDz55JMYOHCg7NUYy5YtQ6dOnaDRaKDT6ZCXl1dv/MaNG9GlSxdoNBqEhoZi8+bNsuVCCCQnJyMgIACurq7Q6/U4ceKELKaoqAhxcXHw8PCAl5cX4uPjUVpaKos5ePAgBgwYAI1Gg6CgICxatEi2/Mknn4RKparzGjZsmBQzfvz4OsujoqIaUyYiIiL6lbO5MQsPD8e5c+eaLIH169cjMTERs2fPxt69e9GzZ09ERkbi0qVLVuN37dqFMWPGID4+Hvv27UNMTAxiYmJQUFAgxSxatAipqalIS0tDbm4u3N3dERkZiYqKCikmLi4Ohw8fRlZWFjZt2oTt27dj0qRJ0nKTyYQhQ4agY8eOyM/Px1tvvYU5c+Zg+fLlUswXX3yBixcvSq+CggI4ODhg1KhRspyjoqJkcWvXrm2q8hEREdGviM2nMv/2t79hypQpmDZtGkJDQ+Hk5CRb3qNHD5vme+eddzBx4kRMmDABAJCWloavv/4aH3/8MV599dU68UuXLkVUVBSmTZsGAHj99deRlZWF9957D2lpaRBCYMmSJZg5cyaGDx8OAPj000/h7++P9PR0xMbG4ujRo8jIyMDu3bsRHh4OAHj33XcRHR2Nt99+G4GBgVi9ejWqqqrw8ccfw9nZGd26dcP+/fvxzjvvSA2cj4+PLLd169bBzc2tTmPm4uICrVZrU12IiIio9bG5MRs9ejQA4C9/+Ys0plKpGnXxf1VVFfLz85GUlCSNqdVq6PV6GAwGq+sYDAYkJibKxiIjI5Geng4AOHXqFIxGI/R6vbTc09MTOp0OBoMBsbGxMBgM8PLykpoyANDr9VCr1cjNzcWIESNgMBjwxBNPwNnZWbadhQsX4tq1a/D29q6T20cffYTY2Fi4u7vLxnNycuDn5wdvb2889dRTmD9/Ptq1a2d1/yorK1FZWSm9r73T1Ww2w2w2W12nsWrna+p5SY51tg/WuWHutz6ss32wzvbRnHVu7Jw2N2anTp1q1IasuXLlCmpqauDv7y8b9/f3x7Fjx6yuYzQarcYbjUZpee1YfTF+fn6y5Y6OjvDx8ZHFBAcH15mjdtmdjVleXh4KCgrw0UcfycajoqLwzDPPIDg4GD/99BNmzJiBoUOHwmAwwMHBoc7+paSkYO7cuXXGMzMz4ebmZqUi9y8rK6tZ5iU51tk+WGdrbv2qv/Oa3MZine2DdbaP5qhzeXl5o9azuTHr2LFjozb0a/fRRx8hNDQUffv2lY3HxsZKX4eGhqJHjx54+OGHkZOTg0GDBtWZJykpSXZE0GQyISgoCEOGDIGHh0eT5mw2m5GVlYXBgwfXOSVNTYd1tg/W+e6mGDKlr6Ojo+9rLtbZPlhn+2jOOjf22a42N2YA8NlnnyEtLQ2nTp2CwWBAx44dsWTJEgQHB0vXdTWEr68vHBwcUFhYKBsvLCy86zVZWq223vjafwsLCxEQECCLCQsLk2LuvLmguroaRUVFsnmsbef2bdQqKyvDunXrMG/evHvu80MPPQRfX1+cPHnSamPm4uICFxeXOuNOTk7N9sPZnHPTLayzfbDO9Wuq2rDO9sE620dz1Lmx89l8V+YHH3yAxMREREdHo7i4WLqmzMvLC0uWLLFpLmdnZ/Tp0wfZ2dnSmMViQXZ2NiIiIqyuExERIYsHbh6CrI0PDg6GVquVxZhMJuTm5koxERERKC4uRn5+vhSzdetWWCwW6HQ6KWb79u2yc8RZWVno3LlzndOYGzduRGVlJf785z/fc59/+eUXXL16VdY0EhEREQGNaMzeffddrFixAq+99prsGqnw8HAcOnTI5gQSExOxYsUKrFq1CkePHsWLL76IsrIy6S7NsWPHym4OmDJlCjIyMrB48WIcO3YMc+bMwZ49e5CQkADg5o0IU6dOxfz58/HVV1/h0KFDGDt2LAIDAxETEwMA6Nq1K6KiojBx4kTk5eVh586dSEhIQGxsLAIDAwEAf/rTn+Ds7Iz4+HgcPnwY69evx9KlS+vceADcPI0ZExNT54L+0tJSTJs2DT/88ANOnz6N7OxsDB8+HI888ggiIyNtrhURERH9ujXq4v9evXrVGXdxcUFZWZnNCYwePRqXL19GcnIyjEYjwsLCkJGRIV1of/bsWajVt/rHfv36Yc2aNZg5cyZmzJiBkJAQpKeno3v37lLM9OnTUVZWhkmTJqG4uBj9+/dHRkYGNBqNFLN69WokJCRg0KBBUKvVGDlyJFJTU6Xlnp6eyMzMxOTJk9GnTx/4+voiOTlZ9qwzADh+/Dh27NiBzMxM3MnBwQEHDx7EqlWrUFxcjMDAQAwZMgSvv/661dOVRERE1LrZ3JgFBwdj//79dW4CyMjIQNeuXRuVREJCgnTE6045OTl1xkaNGlXnWWG3U6lUmDdvXr3XfPn4+GDNmjX15tWjRw98//339cZ07twZQgiry1xdXfHNN9/Uuz4RERFRrQY3ZvPmzcPLL7+MxMRETJ48GRUVFRBCIC8vD2vXrkVKSgo+/PDD5syViIiI6FetwY3Z3Llz8cILL+Cvf/0rXF1dMXPmTJSXl+NPf/oTAgMDsXTpUtmjIYiIiIjINg1uzG4/XRcXF4e4uDiUl5ejtLS0zsNaiYiIiMh2Nl1jplKpZO/d3Nya7Wn0RERERK2NTY3Zo48+Wqc5u1NRUdF9JURERETUWtnUmM2dOxeenp7NlQsRERFRq2ZTYxYbG8vryYiIiIiaSYOf/H+vU5hEREREdH8a3Jjd7SGqRERERNQ0Gnwq02KxNGceRERERK2ezR9iTkRERETNg40ZERERkUKwMSMiIiJSCDZmRERERArBxoyIiIhIIdiYERERESkEGzMiIiIihWBjRkRERKQQbMyIiIiIFIKNGREREZFCsDEjIiIiUgg2ZkREREQKwcaMiIiISCHYmBEREREpBBszIiIiIoVgY0ZERESkEGzMiIiIiBSCjRkRERGRQrAxIyIiIlIINmZERERECsHGjIiIiEghFNGYLVu2DJ06dYJGo4FOp0NeXl698Rs3bkSXLl2g0WgQGhqKzZs3y5YLIZCcnIyAgAC4urpCr9fjxIkTspiioiLExcXBw8MDXl5eiI+PR2lpqSzm4MGDGDBgADQaDYKCgrBo0SLZ8pUrV0KlUsleGo3G5lyIiIiIAAU0ZuvXr0diYiJmz56NvXv3omfPnoiMjMSlS5esxu/atQtjxoxBfHw89u3bh5iYGMTExKCgoECKWbRoEVJTU5GWlobc3Fy4u7sjMjISFRUVUkxcXBwOHz6MrKwsbNq0Cdu3b8ekSZOk5SaTCUOGDEHHjh2Rn5+Pt956C3PmzMHy5ctl+Xh4eODixYvS68yZM7LlDcmFiIiICAAgWljfvn3F5MmTpfc1NTUiMDBQpKSkWI1/9tlnxbBhw2RjOp1OPP/880IIISwWi9BqteKtt96SlhcXFwsXFxexdu1aIYQQR44cEQDE7t27pZgtW7YIlUolzp8/L4QQ4v333xfe3t6isrJSinnllVdE586dpfeffPKJ8PT0vOu+NSSXeykpKREARElJSYPibVFVVSXS09NFVVVVk89Nt7DO9sE6313HVzZJr/vFOtsH62wfzVnnxv79dmzJprCqqgr5+flISkqSxtRqNfR6PQwGg9V1DAYDEhMTZWORkZFIT08HAJw6dQpGoxF6vV5a7unpCZ1OB4PBgNjYWBgMBnh5eSE8PFyK0ev1UKvVyM3NxYgRI2AwGPDEE0/A2dlZtp2FCxfi2rVr8Pb2BgCUlpaiY8eOsFgs6N27N958801069atwbncqbKyEpWVldJ7k8kEADCbzTCbzfUX1Ea18zX1vCTHOtsH69ww91sf1tk+WGf7aM46N3bOFm3Mrly5gpqaGvj7+8vG/f39cezYMavrGI1Gq/FGo1FaXjtWX4yfn59suaOjI3x8fGQxwcHBdeaoXebt7Y3OnTvj448/Ro8ePVBSUoK3334b/fr1w+HDh/Hggw82KJc7paSkYO7cuXXGMzMz4ebmZnWd+5WVldUs85Ic62wfrLM1t37V33lNbmOxzvbBOttHc9S5vLy8Ueu1aGP2vy4iIgIRERHS+379+qFr167417/+hddff71RcyYlJcmOCJpMJgQFBWHIkCHw8PC475xvZzabkZWVhcGDB8PJyalJ56ZbWGf7YJ3vboohU/o6Ojr6vuZine2DdbaP5qxz7RkvW7VoY+br6wsHBwcUFhbKxgsLC6HVaq2uo9Vq642v/bewsBABAQGymLCwMCnmzpsLqqurUVRUJJvH2nZu38adnJyc0KtXL5w8ebLBudzJxcUFLi4uVudurh/O5pybbmGd7YN1rl9T1YZ1tg/W2T6ao86Nna9F78p0dnZGnz59kJ2dLY1ZLBZkZ2fLjkTdLiIiQhYP3DwEWRsfHBwMrVYrizGZTMjNzZViIiIiUFxcjPz8fClm69atsFgs0Ol0Usz27dtl54izsrLQuXNn6fqyO9XU1ODQoUNSE9aQXIiIiIhqtfjjMhITE7FixQqsWrUKR48exYsvvoiysjJMmDABADB27FjZzQFTpkxBRkYGFi9ejGPHjmHOnDnYs2cPEhISAAAqlQpTp07F/Pnz8dVXX+HQoUMYO3YsAgMDERMTAwDo2rUroqKiMHHiROTl5WHnzp1ISEhAbGwsAgMDAQB/+tOf4OzsjPj4eBw+fBjr16/H0qVLZacZ582bh8zMTPz888/Yu3cv/vznP+PMmTP461//2uBciIiIiGq1+DVmo0ePxuXLl5GcnAyj0YiwsDBkZGRIF8yfPXsWavWt/rFfv35Ys2YNZs6ciRkzZiAkJATp6eno3r27FDN9+nSUlZVh0qRJKC4uRv/+/ZGRkSF7+Ovq1auRkJCAQYMGQa1WY+TIkUhNTZWWe3p6IjMzE5MnT0afPn3g6+uL5ORk2bPOrl27hokTJ0o3A/Tp0we7du3CY489ZlMuRERERACgEkKIlk6C7s5kMsHT0xMlJSXNcvH/5s2bER0dzWsYmhHrbB+s8911evVr6evTC4bd11yss32wzvbRnHVu7N/vFj+VSUREREQ3sTEjIiIiUgg2ZkREREQKwcaMiIiISCHYmBEREREpBBszIiIiIoVgY0ZERESkEGzMiIiIiBSCjRkRERGRQrAxIyIiIlIINmZERERECsHGjIiIiEgh2JgRERERKQQbMyIiIiKFYGNGREREpBBszIiIiIgUgo0ZERERkUKwMSMiIiJSCDZmRERERArBxoyIiIhIIdiYERERESkEGzMiIiIihWBjRkRERKQQbMyIiIiIFIKNGREREZFCsDEjIiIiUgg2ZkREREQKwcaMiIiISCHYmBEREREpBBszIiIiIoVgY0ZERESkEGzMiIiIiBRCEY3ZsmXL0KlTJ2g0Guh0OuTl5dUbv3HjRnTp0gUajQahoaHYvHmzbLkQAsnJyQgICICrqyv0ej1OnDghiykqKkJcXBw8PDzg5eWF+Ph4lJaWymIOHjyIAQMGQKPRICgoCIsWLZItX7FiBQYMGABvb294e3tDr9fXyX38+PFQqVSyV1RUlK0lIiIiolagxRuz9evXIzExEbNnz8bevXvRs2dPREZG4tKlS1bjd+3ahTFjxiA+Ph779u1DTEwMYmJiUFBQIMUsWrQIqampSEtLQ25uLtzd3REZGYmKigopJi4uDocPH0ZWVhY2bdqE7du3Y9KkSdJyk8mEIUOGoGPHjsjPz8dbb72FOXPmYPny5VJMTk4OxowZg23btsFgMCAoKAhDhgzB+fPnZTlHRUXh4sWL0mvt2rVNVT4iIptsPVYIi0W0dBpEdDeihfXt21dMnjxZel9TUyMCAwNFSkqK1fhnn31WDBs2TDam0+nE888/L4QQwmKxCK1WK9566y1peXFxsXBxcRFr164VQghx5MgRAUDs3r1bitmyZYtQqVTi/PnzQggh3n//feHt7S0qKyulmFdeeUV07tz5rvtSXV0t2rZtK1atWiWNjRs3TgwfPvxeZbirkpISAUCUlJQ0eo67qaqqEunp6aKqqqrJ56ZbWGf7YJ3vruMrm2SvdXlnGj0X62wfrLN9NGedG/v327Elm8Kqqirk5+cjKSlJGlOr1dDr9TAYDFbXMRgMSExMlI1FRkYiPT0dAHDq1CkYjUbo9XppuaenJ3Q6HQwGA2JjY2EwGODl5YXw8HApRq/XQ61WIzc3FyNGjIDBYMATTzwBZ2dn2XYWLlyIa9euwdvbu05u5eXlMJvN8PHxkY3n5OTAz88P3t7eeOqppzB//ny0a9fO6v5VVlaisrJSem8ymQAAZrMZZrPZ6jqNVTtfU89LcqyzfbDODfftkUI8ExbQqHVZZ/tgne2jOevc2DlbtDG7cuUKampq4O/vLxv39/fHsWPHrK5jNBqtxhuNRml57Vh9MX5+frLljo6O8PHxkcUEBwfXmaN2mbXG7JVXXkFgYKCsKYyKisIzzzyD4OBg/PTTT5gxYwaGDh0Kg8EABweHOnOkpKRg7ty5dcYzMzPh5uZmpSL3Lysrq1nmJTnW2T5YZ2vkv+qNRmOda3NtxTrbB+tsH81R5/Ly8kat16KN2a/JggULsG7dOuTk5ECj0UjjsbGx0tehoaHo0aMHHn74YeTk5GDQoEF15klKSpIdETSZTNK1ax4eHk2as9lsRlZWFgYPHgwnJ6cmnZtuYZ3tg3W+uymGTNl7f39/REf3atRcrLN9sM720Zx1rj3jZasWbcx8fX3h4OCAwsJC2XhhYSG0Wq3VdbRabb3xtf8WFhYiICBAFhMWFibF3HlzQXV1NYqKimTzWNvO7duo9fbbb2PBggX49ttv0aNHj3r3+aGHHoKvry9OnjxptTFzcXGBi4tLnXEnJ6dm++FszrnpFtbZPljne1Op1fddI9bZPlhn+2iOOjd2vha9K9PZ2Rl9+vRBdna2NGaxWJCdnY2IiAir60RERMjigZuHIGvjg4ODodVqZTEmkwm5ublSTEREBIqLi5Gfny/FbN26FRaLBTqdTorZvn277BxxVlYWOnfuLDuNuWjRIrz++uvIyMiQXbN2N7/88guuXr0qaxqJiIiIAAU8LiMxMRErVqzAqlWrcPToUbz44osoKyvDhAkTAABjx46V3RwwZcoUZGRkYPHixTh27BjmzJmDPXv2ICEhAQCgUqkwdepUzJ8/H1999RUOHTqEsWPHIjAwEDExMQCArl27IioqChMnTkReXh527tyJhIQExMbGIjAwEADwpz/9Cc7OzoiPj8fhw4exfv16LF26VHaaceHChZg1axY+/vhjdOrUCUajEUajUXoeWmlpKaZNm4YffvgBp0+fRnZ2NoYPH45HHnkEkZGR9igvEVEdgk/LIFKsFr/GbPTo0bh8+TKSk5NhNBoRFhaGjIwM6UL7s2fPQq2+1T/269cPa9aswcyZMzFjxgyEhIQgPT0d3bt3l2KmT5+OsrIyTJo0CcXFxejfvz8yMjJk136tXr0aCQkJGDRoENRqNUaOHInU1FRpuaenJzIzMzF58mT06dMHvr6+SE5Olj3r7IMPPkBVVRX++Mc/yvZp9uzZmDNnDhwcHHDw4EGsWrUKxcXFCAwMxJAhQ/D6669bPV1JRERErVuLN2YAkJCQIB3xulNOTk6dsVGjRmHUqFF3nU+lUmHevHmYN2/eXWN8fHywZs2aevPq0aMHvv/++7suP336dL3ru7q64ptvvqk3hojI/njIjEipWvxUJhERERHdxMaMiIiISCHYmBERtTK8+J9IudiYERERESkEGzMiIiIihWBjRkTUyvBMJpFysTEjIiIiUgg2ZkRErYzg1f9EisXGjIiIiEgh2JgRERERKQQbMyKiVoYnMomUi40ZERERkUKwMSMiIiJSCDZmREStDG/KJFIuNmZERERECsHGjIioleEBMyLlYmNGRL961TUWnL5S1tJpECmaEAIV5pqWTqPVY2NGRL96Ez/dgyffzsF/D1xo6VSIFOuVzw+iy6wM/Hy5tKVTadXYmBHRr96245cBAB/tONXCmSgDP5KJrNmw5xcAwIrvf27hTFo3NmZE1GqwHSG6N/btLYuNGRG1GjxSRHRvFv6ctCg2ZkTUavDvDdG98eekZbExI6JWg0cCiO7Nwh+TFsXGjIhaDfZlRPcmeDVmi2JjRkStBo+Y3cQyUH3430fLYmNGREREEt4k07LYmBFRq8EjZjfxVBXVh/91tCw2ZkTUarAvI7o3/py0LDZmRNRq8O8N0b3xyHLLYmNGRK0G/+DcxDJQffjfR8tiY0ZErQf/4BDdE69BbFmKaMyWLVuGTp06QaPRQKfTIS8vr974jRs3okuXLtBoNAgNDcXmzZtly4UQSE5ORkBAAFxdXaHX63HixAlZTFFREeLi4uDh4QEvLy/Ex8ejtLRUFnPw4EEMGDAAGo0GQUFBWLRoUbPkQs3DWFKBknJzS6dBCvK/csTskqkCa/PO4kZVTUunQq2QxdLSGbRuLd6YrV+/HomJiZg9ezb27t2Lnj17IjIyEpcuXbIav2vXLowZMwbx8fHYt28fYmJiEBMTg4KCAilm0aJFSE1NRVpaGnJzc+Hu7o7IyEhUVFRIMXFxcTh8+DCysrKwadMmbN++HZMmTZKWm0wmDBkyBB07dkR+fj7eeustzJkzB8uXL2/yXFqKxSLQkN/7p6+U4WppZfMn1IRKys34bUo2es7LbOlUfvWUdmu9EALmGut/WeydaUm5GaYK2/7n4KfLpej7ZjaSvjiEBVuONkteCvuWtShjSQUKTfLfxxaLQIW5cU2xxSJw9mq5bOz9nJP4aMcp2ZjSfm5u979yxMxYUoFrZVXYcugizhWV33uF/xEq0cL/deh0OvzmN7/Be++9BwCwWCwICgrC3/72N7z66qt14kePHo2ysjJs2rRJGvvtb3+LsLAwpKWlQQiBwMBA/OMf/8DLL78MACgpKYG/vz9WrlyJ2NhYHD16FI899hh2796N8PBwAEBGRgaio6Pxyy+/IDAwEB988AFee+01GI1GODs7AwBeffVVpKen49ixY02Wy72YTCZ4enqipKQEHh4ejSmxVcu2ncRb3xwHAPymkzd2n76GD+J6o43GEULc/AMmhMD//eEsvj1aeLNGUwdI69/+X825onKkffcTXhj4MAI8XVFtscDw81UcN15Hzwe94OKkxqYDF3G88DoCvTR4pteD2HOmCJsPGbHgmVCcvFSKg+dLMFUfgm3HLiHIxw3zNx1FVY0Fzg5qjOkbhFHhQQAAlerWdlVQwWi6gZfW7kdpZbU0HuLXBoWmCpgqbo510baF6YYZgV6ueGVoF/i4O2PnySu4XlGNIY/5w9FBjbe+OYbvf7yCeTHd4OnqhOsV1Wjj4oh1u88h60ghPhwbjtNXyzD/67p/KJOGdoG7iyP6PdwOapVK+pUmhEDmkUJ8U3ARAz2vYuhTA+Hk5AiLEPhi73lcKa2ELrgdfrl2AzfMNeiibYuwIC9cKLmBdXnn0LGdGzq1c8fxwusQQiDIxw3XK6rh7uyA0Ac94e1287/LiyUVuFpWhapqCx70doXWQ3Nz+wC2Hrv5PzjXK8wIC/LC+E92AwBGhwdhwKO+ePub4zh9tRzDwwIR+5sO8PNwQfTS79HjQU8kDu4MD1dHHLt4HQGeGtww12DZtpPYe7a4Tg1cnRwwPCwQ63afk8bGRXSEs6Ma5VU16NTOHcG+7vjywAU8/nA7vPrFoTpzeLo64Z+je6K8qgbnr91AkI8bHm7fBkVlVfjsh9O4UFyBcf06oueDXtL+/Xy5DKYbZvxj44E6892vmcO6Iu9UETKPFN415v243rhRVYNr5VWoMNfgQkkFugd6Yt/Za9iY/0ud+E8m/AZbDl3Ehj23lj3k646fr5Qh+feP4cSlUqzNO2t1WxlTB0Dj6IBTV8rw3Y+X8VB7dwT5uOHw+RK8nfkjfvuQDxzValwovoE/6Trg1JUydGznhscCPPHnj3Jlc3m5OeHzF/tBCAGLAEorq/HM+7vwxz4PIrKbFloPDU5dLcNLa/fddd8ff7gdfNq44A89A/HNYSP0Xf1x5moZUrYck8V18HHD2aJydNG2xYtPPowNe87h0C8lMFVUI8SvDU5cunmmQt/VH7/r0h6v/acAc55+DOXmGrg7O6LCXIMl357Ajf/fKD3xaHts//EyAOD14d2Qf+Ya0vdfqJPf/JjuuF5RDS83J6z4/mf8fLlMWvZwe3dMi+yCTr5uiFryvTT+u87tEd7JR/rdOHNYV7Rr44xjxuto38YFg7r6A7h55DX35yK89c0xVFVbUFZVgz/pOmBody2e++jWGZ+/6x+FSgW8k/UjAOCbqU9A46TGf/adx5Jvb545mTIoBGEdvBDczh3XK6qRsuUoJj3xEB7wdMZnX29HZqEb3nk2DFpPjTTvceN1vLh6L2ZEd8Hgx7Sy/U764iAe9W+LsREdYa4RqLHc/B+Va+VV+MvKPXji0faY8/RjAIAfC6/jalkVUrNPoLjcjMpq+f/QvDGiO3TB7QDc/O/EIgS+//EKzBYLLpkqsXLXaQDAi08+jN/3CMBfVu7GjaoaTBzwEE5fLYeTg0r2OwEA/vJ4ML7Y9wuKy83Qd/VHTK9AtHN3gZ+HixQz4ZPdOPv/G62p+hCEPuCJf377I4Z2D5C+N7/vEYBNBy/K5t728pN4eeMB5J+5hpiwQKTvv4CH27uj54NeCO/kg0f82qB925vbqW19qqur8d133+EPUXr4e7nX+e/ofjT273eLNmZVVVVwc3PDv//9b8TExEjj48aNQ3FxMb788ss663To0AGJiYmYOnWqNDZ79mykp6fjwIED+Pnnn/Hwww9j3759CAsLk2IGDhyIsLAwLF26FB9//DH+8Y9/4Nq1a9Ly6upqaDQabNy4ESNGjMDYsWNhMpmQnp4uxWzbtg1PPfUUioqK4O3t3SS53KmyshKVlbeOTplMJgQFBeHKlStN2pj9ZVU+vj95tcnmIyIi+l818fEOmB7VpUnnNJlM8PX1tbkxc2zSLGx05coV1NTUwN/fXzbu7+8vHZW6k9FotBpvNBql5bVj9cX4+fnJljs6OsLHx0cWExwcXGeO2mXe3t5NksudUlJSMHfu3DrjmZmZcHNzs7pOY/RzA7638u0PdBOoPSilUgG/lN06RNXWyXoPf918K8bLWaC4SmU17n543rbt27Mwme9/W64OAjdqmiZnF4eb9VPh5gcBV1puzevqcDPzptqWq8PNEw4VVuZzcRCobKLtKJGrg0CVBagRv959tEbjIKx+vxvDzUFIR6DLqltXHRtLDQFnB+s/cw11t5/NO8fv/F7X/v4A5L9DNLeN3x7v5iDgoAYcVIBaBRRVNi5nFwcBB9XN32nN/d+J2//fl/Jm/N11ex1v98uZU9i8+ecm3VZ5eeNOr7ZoY0Z1JSUlITExUXpfe8RsyJAhTXrEDADGxZiRlZWFwYMHw8nJqUnnplvMZtbZHlhn+2Cd7YN1to/mrLPJZGrUei3amPn6+sLBwQGFhfJrOAoLC6HVaq2uo9Vq642v/bewsBABAQGymNrTiVqtts7NBdXV1SgqKpLNY207t2+jKXK5k4uLC1xcXOqMOzk5NdsPZ3POTbewzvbBOtsH62wfrLN9NEedGztfi96V6ezsjD59+iA7O1sas1gsyM7ORkREhNV1IiIiZPEAkJWVJcUHBwdDq9XKYkwmE3Jzc6WYiIgIFBcXIz8/X4rZunUrLBYLdDqdFLN9+3aYzWbZdjp37gxvb+8my4WIiIhIIlrYunXrhIuLi1i5cqU4cuSImDRpkvDy8hJGo1EIIcRzzz0nXn31VSl+586dwtHRUbz99tvi6NGjYvbs2cLJyUkcOnRIilmwYIHw8vISX375pTh48KAYPny4CA4OFjdu3JBioqKiRK9evURubq7YsWOHCAkJEWPGjJGWFxcXC39/f/Hcc8+JgoICsW7dOuHm5ib+9a9/NXku9SkpKREARElJie3FvYeqqiqRnp4uqqqqmnxuuoV1tg/W2T5YZ/tgne2jOevc2L/fLX6N2ejRo3H58mUkJyfDaDQiLCwMGRkZ0gXzZ8+ehVp968Bev379sGbNGsycORMzZsxASEgI0tPT0b17dylm+vTpKCsrw6RJk1BcXIz+/fsjIyMDGs2t241Xr16NhIQEDBo0CGq1GiNHjkRqaqq03NPTE5mZmZg8eTL69OkDX19fJCcny5511lS5EBEREQEKeI4Z1a+5nmMG3LzocfPmzYiOjuY1DM2IdbYP1tk+WGf7YJ3toznr3Ni/3y3+5H8iIiIiuomNGREREZFCsDEjIiIiUgg2ZkREREQKwcaMiIiISCHYmBEREREpBBszIiIiIoVgY0ZERESkEGzMiIiIiBSixT+SiepX+8EMJpOpyec2m80oLy+HyWTik6WbEetsH6yzfbDO9sE620dz1rn277atH7DExkzhrl+/DgAICgpq4UyIiIjIVtevX4enp2eD4/lZmQpnsVhw4cIFtG3bFiqVqknnNplMCAoKwrlz55r8czjpFtbZPlhn+2Cd7YN1to/mrLMQAtevX0dgYCDU6oZfOcYjZgqnVqvx4IMPNus2PDw8+INvB6yzfbDO9sE62wfrbB/NVWdbjpTV4sX/RERERArBxoyIiIhIIdiYtWIuLi6YPXs2XFxcWjqVXzXW2T5YZ/tgne2DdbYPJdaZF/8TERERKQSPmBEREREpBBszIiIiIoVgY0ZERESkEGzMiIiIiBSCjVkrtWzZMnTq1AkajQY6nQ55eXktnZJipKSk4De/+Q3atm0LPz8/xMTE4Pjx47KYiooKTJ48Ge3atUObNm0wcuRIFBYWymLOnj2LYcOGwc3NDX5+fpg2bRqqq6tlMTk5OejduzdcXFzwyCOPYOXKlXXyaS3fqwULFkClUmHq1KnSGOvcNM6fP48///nPaNeuHVxdXREaGoo9e/ZIy4UQSE5ORkBAAFxdXaHX63HixAnZHEVFRYiLi4OHhwe8vLwQHx+P0tJSWczBgwcxYMAAaDQaBAUFYdGiRXVy2bhxI7p06QKNRoPQ0FBs3ry5eXbazmpqajBr1iwEBwfD1dUVDz/8MF5//XXZ5ySyzo2zfft2PP300wgMDIRKpUJ6erpsuZLq2pBc7klQq7Nu3Trh7OwsPv74Y3H48GExceJE4eXlJQoLC1s6NUWIjIwUn3zyiSgoKBD79+8X0dHRokOHDqK0tFSKeeGFF0RQUJDIzs4We/bsEb/97W9Fv379pOXV1dWie/fuQq/Xi3379onNmzcLX19fkZSUJMX8/PPPws3NTSQmJoojR46Id999Vzg4OIiMjAwpprV8r/Ly8kSnTp1Ejx49xJQpU6Rx1vn+FRUViY4dO4rx48eL3Nxc8fPPP4tvvvlGnDx5UopZsGCB8PT0FOnp6eLAgQPiD3/4gwgODhY3btyQYqKiokTPnj3FDz/8IL7//nvxyCOPiDFjxkjLS0pKhL+/v4iLixMFBQVi7dq1wtXVVfzrX/+SYnbu3CkcHBzEokWLxJEjR8TMmTOFk5OTOHTokH2K0YzeeOMN0a5dO7Fp0yZx6tQpsXHjRtGmTRuxdOlSKYZ1bpzNmzeL1157TXzxxRcCgPjPf/4jW66kujYkl3thY9YK9e3bV0yePFl6X1NTIwIDA0VKSkoLZqVcly5dEgDEd999J4QQori4WDg5OYmNGzdKMUePHhUAhMFgEELc/EWiVquF0WiUYj744APh4eEhKisrhRBCTJ8+XXTr1k22rdGjR4vIyEjpfWv4Xl2/fl2EhISIrKwsMXDgQKkxY52bxiuvvCL69+9/1+UWi0VotVrx1ltvSWPFxcXCxcVFrF27VgghxJEjRwQAsXv3bilmy5YtQqVSifPnzwshhHj//feFt7e3VPfabXfu3Fl6/+yzz4phw4bJtq/T6cTzzz9/fzupAMOGDRN/+ctfZGPPPPOMiIuLE0Kwzk3lzsZMSXVtSC4NwVOZrUxVVRXy8/Oh1+ulMbVaDb1eD4PB0IKZKVdJSQkAwMfHBwCQn58Ps9ksq2GXLl3QoUMHqYYGgwGhoaHw9/eXYiIjI2EymXD48GEp5vY5amNq52gt36vJkydj2LBhdWrBOjeNr776CuHh4Rg1ahT8/PzQq1cvrFixQlp+6tQpGI1G2f57enpCp9PJ6uzl5YXw8HApRq/XQ61WIzc3V4p54okn4OzsLMVERkbi+PHjuHbtmhRT3/fif1m/fv2QnZ2NH3/8EQBw4MAB7NixA0OHDgXAOjcXJdW1Ibk0BBuzVubKlSuoqamR/SEDAH9/fxiNxhbKSrksFgumTp2Kxx9/HN27dwcAGI1GODs7w8vLSxZ7ew2NRqPVGtcuqy/GZDLhxo0breJ7tW7dOuzduxcpKSl1lrHOTePnn3/GBx98gJCQEHzzzTd48cUX8dJLL2HVqlUAbtWpvv03Go3w8/OTLXd0dISPj0+TfC9+DXV+9dVXERsbiy5dusDJyQm9evXC1KlTERcXB4B1bi5KqmtDcmkIxwZHErVCkydPRkFBAXbs2NHSqfzqnDt3DlOmTEFWVhY0Gk1Lp/OrZbFYEB4ejjfffBMA0KtXLxQUFCAtLQ3jxo1r4ex+PTZs2IDVq1djzZo16NatG/bv34+pU6ciMDCQdSab8IhZK+Pr6wsHB4c6d7YVFhZCq9W2UFbKlJCQgE2bNmHbtm148MEHpXGtVouqqioUFxfL4m+voVartVrj2mX1xXh4eMDV1fVX/73Kz8/HpUuX0Lt3bzg6OsLR0RHfffcdUlNT4ejoCH9/f9a5CQQEBOCxxx6TjXXt2hVnz54FcKtO9e2/VqvFpUuXZMurq6tRVFTUJN+LX0Odp02bJh01Cw0NxXPPPYe///3v0tFg1rl5KKmuDcmlIdiYtTLOzs7o06cPsrOzpTGLxYLs7GxERES0YGbKIYRAQkIC/vOf/2Dr1q0IDg6WLe/Tpw+cnJxkNTx+/DjOnj0r1TAiIgKHDh2S/TLIysqCh4eH9EcyIiJCNkdtTO0cv/bv1aBBg3Do0CHs379feoWHhyMuLk76mnW+f48//nidx738+OOP6NixIwAgODgYWq1Wtv8mkwm5ubmyOhcXFyM/P1+K2bp1KywWC3Q6nRSzfft2mM1mKSYrKwudO3eGt7e3FFPf9+J/WXl5OdRq+Z9UBwcHWCwWAKxzc1FSXRuSS4M0+DYB+tVYt26dcHFxEStXrhRHjhwRkyZNEl5eXrI721qzF198UXh6eoqcnBxx8eJF6VVeXi7FvPDCC6JDhw5i69atYs+ePSIiIkJERERIy2sf4zBkyBCxf/9+kZGRIdq3b2/1MQ7Tpk0TR48eFcuWLbP6GIfW9L26/a5MIVjnppCXlyccHR3FG2+8IU6cOCFWr14t3NzcxP/9v/9XilmwYIHw8vISX375pTh48KAYPny41ccN9OrVS+Tm5oodO3aIkJAQ2eMGiouLhb+/v3juuedEQUGBWLdunXBzc6vzuAFHR0fx9ttvi6NHj4rZs2f/Tz/G4Xbjxo0TDzzwgPS4jC+++EL4+vqK6dOnSzGsc+Ncv35d7Nu3T+zbt08AEO+8847Yt2+fOHPmjBBCWXVtSC73wsaslXr33XdFhw4dhLOzs+jbt6/44YcfWjolxQBg9fXJJ59IMTdu3BD/5//8H+Ht7S3c3NzEiBEjxMWLF2XznD59WgwdOlS4uroKX19f8Y9//EOYzWZZzLZt20RYWJhwdnYWDz30kGwbtVrT9+rOxox1bhr//e9/Rffu3YWLi4vo0qWLWL58uWy5xWIRs2bNEv7+/sLFxUUMGjRIHD9+XBZz9epVMWbMGNGmTRvh4eEhJkyYIK5fvy6LOXDggOjfv79wcXERDzzwgFiwYEGdXDZs2CAeffRR4ezsLLp16ya+/vrrpt/hFmAymcSUKVNEhw4dhEajEQ899JB47bXXZI9fYJ0bZ9u2bVZ/J48bN04Ioay6NiSXe1EJcdtjiYmIiIioxfAaMyIiIiKFYGNGREREpBBszIiIiIgUgo0ZERERkUKwMSMiIiJSCDZmRERERArBxoyIiIhIIdiYERHV4/Tp05g/fz5KS0tbOhUiagXYmBER3UVlZSVGjRoFX19ftGnTxi7bzMnJgUqlqvPh7famUqmQnp7eZPM9+eSTmDp1apPNR/RrxcaMiBRl/PjxUKlUWLBggWw8PT0dKpXKrrn8/e9/x5AhQ/DCCy/YdbtE1Ho5tnQCRER30mg0WLhwIZ5//nl4e3u3WB7vv/9+g+Kqqqrg7OzczNkQUWvAI2ZEpDh6vR5arRYpKSl3jZkzZw7CwsJkY0uWLEGnTp2k9+PHj0dMTAzefPNN+Pv7w8vLC/PmzUN1dTWmTZsGHx8fPPjgg/jkk09k85w7dw7PPvssvLy84OPjg+HDh+P06dN15n3jjTcQGBiIzp07AwAOHTqEp556Cq6urmjXrh0mTZp0z2vTNm/ejEcffRSurq743e9+J9tOQ/fTmsOHD+P3v/89PDw80LZtWwwYMAA//fQTAGD37t0YPHgwfH194enpiYEDB2Lv3r31zvfLL79gzJgx8PHxgbu7O8LDw5Gbmyurx+2mTp2KJ598st45iaguNmZEpDgODg5488038e677+KXX365r7m2bt2KCxcuYPv27XjnnXcwe/Zs/P73v4e3tzdyc3Pxwgsv4Pnnn5e2YzabERkZibZt2+L777/Hzp070aZNG0RFRaGqqkqaNzs7G8ePH0dWVhY2bdqEsrIyREZGwtvbG7t378bGjRvx7bffIiEh4a65nTt3Ds888wyefvpp7N+/H3/961/x6quv3tf+AsD58+fxxBNPwMXFBVu3bkV+fj7+8pe/oLq6GgBw/fp1jBs3Djt27MAPP/yAkJAQREdH4/r161bnKy0txcCBA3H+/Hl89dVXOHDgAKZPnw6LxXLfuRKRHE9lEpEijRgxAmFhYZg9ezY++uijRs/j4+OD1NRUqNVqdO7cGYsWLUJ5eTlmzJgBAEhKSsKCBQuwY8cOxMbGYv369bBYLPjwww+la9o++eQTeHl5IScnB0OGDAEAuLu748MPP5ROYa5YsQIVFRX49NNP4e7uDgB477338PTTT2PhwoXw9/evk9sHH3yAhx9+GIsXLwYAdO7cGYcOHcLChQsbvb8AsGzZMnh6emLdunVwcnICADz66KPS8qeeekoWv3z5cnh5eeG7777D73//+zrzrVmzBpcvX8bu3bvh4+MDAHjkkUfuK0ciso5HzIhIsRYuXIhVq1bh6NGjjZ6jW7duUKtv/arz9/dHaGio9N7BwQHt2rXDpUuXAAAHDhzAyZMn0bZtW7Rp0wZt2rSBj48PKioqpFOBABAaGiq7ruzo0aPo2bOn1JQBwOOPPw6LxYLjx49bze3o0aPQ6XSysYiIiEbva639+/djwIABUlN2p8LCQkycOBEhISHw9PSEh4cHSktLcfbs2bvO16tXL6kpI6LmwyNmRKRYTzzxBCIjI5GUlITx48fLlqnVagghZGNms7nOHHc2JyqVyupY7Wm50tJS9OnTB6tXr64zV/v27aWvb2/AmlND9/N2rq6u9S4fN24crl69iqVLl6Jjx45wcXFBRESE7FStLfM1Jkciso5HzIhI0RYsWID//ve/MBgMsvH27dvDaDTKGoL9+/ff9/Z69+6NEydOwM/PD4888ojs5enpedf1unbtigMHDqCsrEwa27lzp3QK9W7r5OXlycZ++OEH2fvG7GePHj3w/fff37U52rlzJ1566SVER0ejW7ducHFxwZUrV+qdb//+/SgqKrK6vH379rh48aJsrCm+F0StERszIlK00NBQxMXFITU1VTb+5JNP4vLly1i0aBF++uknLFu2DFu2bLnv7cXFxcHX1xfDhw/H999/j1OnTiEnJwcvvfRSvTcixMXFQaPRYNy4cSgoKMC2bdvwt7/9Dc8995zV68sA4IUXXsCJEycwbdo0HD9+HGvWrMHKlSvvez8TEhJgMpkQGxuLPXv24MSJE/jss8+kU6ohISH47LPPcPToUeTm5iIuLq7eo2JjxoyBVqtFTEwMdu7ciZ9//hmff/651Cw/9dRT2LNnDz799FOcOHECs2fPRkFBQb05EpF1bMyISPHmzZtX5w7Arl274v3338eyZcvQs2dP5OXl4eWXX77vbbm5uWH79u3o0KEDnnnmGXTt2hXx8fGoqKiAh4dHvet98803KCoqwm9+8xv88Y9/xKBBg/Dee+/ddZ0OHTrg888/R3p6Onr27Im0tDS8+eab972f7dq1w9atW6W7Kfv06YMVK1ZIp3A/+ugjXLt2Db1798Zzzz2Hl156CX5+fnedz9nZGZmZmfDz80N0dDRCQ0OxYMECODg4AAAiIyMxa9YsTJ8+Hb/5zW9w/fp1jB07tt4cicg6lbjzwgAiIiIiahE8YkZERESkEGzMiIiIiBSCjRkRERGRQrAxIyIiIlIINmZERERECsHGjIiIiEgh2JgRERERKQQbMyIiIiKFYGNGREREpBBszIiIiIgUgo0ZERERkUL8P5ETpUl9OiznAAAAAElFTkSuQmCC", 138 | "text/plain": [ 139 | "
" 140 | ] 141 | }, 142 | "metadata": {}, 143 | "output_type": "display_data" 144 | } 145 | ], 146 | "source": [ 147 | "import time\n", 148 | "import matplotlib.pyplot as plt\n", 149 | "\n", 150 | "temps_de_calcul = []\n", 151 | "\n", 152 | "N = 100000\n", 153 | "\n", 154 | "for i in range(N):\n", 155 | " t_init = time.perf_counter()\n", 156 | " \"abcdefghij\" == \"abcdefghik\"\n", 157 | " t_final = time.perf_counter()\n", 158 | " temps_de_calcul.append(t_final - t_init)\n", 159 | "\n", 160 | "temps_mini = min(temps_de_calcul)\n", 161 | "temps_maxi = max(temps_de_calcul)\n", 162 | "temps_moyen = sum(temps_de_calcul) / len(temps_de_calcul)\n", 163 | "\n", 164 | "print(\"Temps mini :\", temps_mini)\n", 165 | "print(\"Temps maxi :\", temps_maxi)\n", 166 | "print(\"Temps moyen :\", temps_moyen)\n", 167 | "\n", 168 | "plt.plot(range(N), temps_de_calcul)\n", 169 | "plt.ylabel(\"Temps de calcul\")\n", 170 | "plt.xlabel(\"Numéro du calcul\")\n", 171 | "plt.grid()\n", 172 | "plt.show()" 173 | ] 174 | }, 175 | { 176 | "cell_type": "markdown", 177 | "id": "894f103b-86c3-47ee-a88e-087d20aaffbe", 178 | "metadata": {}, 179 | "source": [ 180 | "### Activité 2. Mesure du temps nécessaire pour calculer le sinus d'un nombre flottant avec Python - Méthode `time_it`\n", 181 | "\n", 182 | "En utilisant le module `timeit`, \n", 183 | "```python\n", 184 | "timer = timeit.Timer(lambda : \"abcdefghij\" == \"abcdefghik\")\n", 185 | "elapsed = timer.timeit(N)\n", 186 | "```\n", 187 | "effectuer la mesure et trouver le temps moyen" 188 | ] 189 | }, 190 | { 191 | "cell_type": "code", 192 | "execution_count": 5, 193 | "id": "7b782733-73d5-4e8f-a6b1-2c15585ca5fb", 194 | "metadata": {}, 195 | "outputs": [ 196 | { 197 | "name": "stdout", 198 | "output_type": "stream", 199 | "text": [ 200 | "Temps moyen : 5.925598001340404e-08\n" 201 | ] 202 | } 203 | ], 204 | "source": [ 205 | "import timeit\n", 206 | "\n", 207 | "timer = timeit.Timer(lambda : \"abcdefghij\" == \"abcdefghik\")\n", 208 | "\n", 209 | "elapsed = timer.timeit(N)\n", 210 | "\n", 211 | "print(\"Temps moyen :\", elapsed / N)" 212 | ] 213 | }, 214 | { 215 | "cell_type": "markdown", 216 | "id": "80f188cb-1d7a-40a6-8890-95b05c8c7855", 217 | "metadata": {}, 218 | "source": [ 219 | "### Activité 3. Mesure du temps nécessaire pour calculer un sinus avec la méthode cProfile\n", 220 | "\n", 221 | "cProfile est le profiler intégré à Python.\n", 222 | "```python\n", 223 | "import cProfile\n", 224 | "\n", 225 | "def ff():\n", 226 | " for i in range(N):\n", 227 | " \"abcdefghij\" == \"abcdefghik\"\n", 228 | "cProfile.run('ff()')\n", 229 | "```" 230 | ] 231 | }, 232 | { 233 | "cell_type": "code", 234 | "execution_count": 7, 235 | "id": "81529a8c-e767-4949-9bb1-e0bff35e8286", 236 | "metadata": {}, 237 | "outputs": [ 238 | { 239 | "name": "stdout", 240 | "output_type": "stream", 241 | "text": [ 242 | " 100004 function calls in 0.022 seconds\n", 243 | "\n", 244 | " Ordered by: standard name\n", 245 | "\n", 246 | " ncalls tottime percall cumtime percall filename:lineno(function)\n", 247 | " 1 0.014 0.014 0.022 0.022 2454247613.py:3(ff)\n", 248 | " 1 0.000 0.000 0.022 0.022 :1()\n", 249 | " 1 0.000 0.000 0.022 0.022 {built-in method builtins.exec}\n", 250 | " 100000 0.009 0.000 0.009 0.000 {built-in method math.sin}\n", 251 | " 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}\n", 252 | "\n", 253 | "\n" 254 | ] 255 | } 256 | ], 257 | "source": [ 258 | "import cProfile\n", 259 | "import math\n", 260 | "def ff():\n", 261 | " for i in range(N):\n", 262 | " math.sin(i)\n", 263 | "cProfile.run('ff()')" 264 | ] 265 | }, 266 | { 267 | "cell_type": "markdown", 268 | "id": "1bb9c099-c26d-44e9-a7f6-db67ebd44512", 269 | "metadata": {}, 270 | "source": [ 271 | "### Décorateur `time_it`" 272 | ] 273 | }, 274 | { 275 | "cell_type": "code", 276 | "execution_count": 51, 277 | "id": "9ac01229-bc31-46fe-9597-8c700e92a533", 278 | "metadata": {}, 279 | "outputs": [ 280 | { 281 | "name": "stdout", 282 | "output_type": "stream", 283 | "text": [ 284 | "Time taken: 1.310766 seconds\n", 285 | "3.141596\n" 286 | ] 287 | } 288 | ], 289 | "source": [ 290 | "import time\n", 291 | "import random\n", 292 | "\n", 293 | "def timeit(func):\n", 294 | " def wrapper(*args, **kwargs):\n", 295 | " start = time.perf_counter()\n", 296 | " result = func(*args, **kwargs)\n", 297 | " end = time.perf_counter()\n", 298 | " elapsed = end - start\n", 299 | " print(f'Time taken: {elapsed:.6f} seconds')\n", 300 | " return result\n", 301 | " return wrapper\n", 302 | "\n", 303 | "@timeit\n", 304 | "def calculate_pi(n):\n", 305 | " \"\"\"Calculate and return an approximation of pi using the Monte Carlo method.\"\"\"\n", 306 | " inside = 0\n", 307 | " for i in range(n):\n", 308 | " x = random.uniform(-1, 1)\n", 309 | " y = random.uniform(-1, 1)\n", 310 | " if x ** 2 + y ** 2 <= 1:\n", 311 | " inside += 1\n", 312 | " pi = (inside / n) * 4\n", 313 | " return pi\n", 314 | "\n", 315 | "pi = calculate_pi(1000000)\n", 316 | "print(pi)" 317 | ] 318 | }, 319 | { 320 | "cell_type": "markdown", 321 | "id": "77bcd7a0-3e30-4dce-b9e7-728d5cf59b17", 322 | "metadata": {}, 323 | "source": [ 324 | "### Activité 4. Comparaison c++/python\n", 325 | "En utilisant un chronomètre c++, comparer les résultats obtenus avec Pythoni pour l'activité 1. Note: Vous pourrez augmenter N." 326 | ] 327 | } 328 | ], 329 | "metadata": { 330 | "kernelspec": { 331 | "display_name": "Python 3 (ipykernel)", 332 | "language": "python", 333 | "name": "python3" 334 | }, 335 | "language_info": { 336 | "codemirror_mode": { 337 | "name": "ipython", 338 | "version": 3 339 | }, 340 | "file_extension": ".py", 341 | "mimetype": "text/x-python", 342 | "name": "python", 343 | "nbconvert_exporter": "python", 344 | "pygments_lexer": "ipython3", 345 | "version": "3.9.0" 346 | } 347 | }, 348 | "nbformat": 4, 349 | "nbformat_minor": 5 350 | } 351 | -------------------------------------------------------------------------------- /BPE_Tokenization_Prof.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "f31cd2b1-d221-4be0-a791-08b71ff1c42f", 6 | "metadata": { 7 | "jp-MarkdownHeadingCollapsed": true 8 | }, 9 | "source": [ 10 | "## Algorithme d'encodage de texte pour Large Language Models : Byte Pair Encoding (BPE) et Tokenization\n", 11 | "\n", 12 | "Pour celles et ceux d'entre vous qui ne disposent pas des droits d'accès pour utiliser les bibliothèques tierces requises pour ce TP (tiktoken et transformers), un environnement en ligne est disponible à l'URL https://mybinder.org/v2/gh/tpaviot/binderenv/HEAD?filepath=\n", 13 | "\n", 14 | "Nous allons travailler à diviser un texte en briques de base connues sous le nom de \"vocabulaire\" et d'associer ainsi à une chapine de caractères une succession d'entiers. La tokenisation la plus élémentaire est celle consistant à associer à un mot l'ensemble des valeurs de la table ASCII (le vocabulaire contient $2^7=128$ briques de base) :\n", 15 | "```\n", 16 | "print([ord(c) for c in \"Salut\"])\n", 17 | "[83, 97, 108, 117, 116]\n", 18 | "```\n", 19 | "\n", 20 | "\n", 21 | "Mais ceci n'est pas suffisant pour travailler avec les grands modèles de langage type ChatGPT.\n", 22 | "\n", 23 | "Le **corpus** de texte est le jeu de données connu sous le nom de \"Tiny shakespeare\" accessible à l'url : https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt\n", 24 | "\n", 25 | "Ce TP a été construit à partir, entre autres, des ressources suivantes disponibles en ligne, que vous êtes invité.e.s à prendre le temps de consulter (en particulier les vidéos de Karpathy) :\n", 26 | "\n", 27 | "* La documenation HuggingFace (https://huggingface.co/learn/nlp-course/chapter6/5?fw=pt)\n", 28 | "\n", 29 | "* La chaîne vidéo YouTube d'Andrej Karpathy (https://www.youtube.com/@AndrejKarpathy/)\n", 30 | "\n", 31 | "Ce **TP comporte trois parties** :\n", 32 | "* dans une première partie, nous nous intéressons à un algorithme qui associe à chaque lettre un entier\n", 33 | "\n", 34 | "* dans une deuxième partie, nous expérimentons un algorithme qui découpe les mots en token de 2 caractères\n", 35 | "\n", 36 | "* dans une troisième partie, nous implémentons un algorithmes plus avancé appelé BPE qui permet d'encoder n'importe quelle chaine de caractères dans une liste d'entiers.\n", 37 | "\n", 38 | "Pour chacun de ces trois algorithmes, nous comparons :\n", 39 | "* la qualité, c'est-à-dire le nombre d'entiers requis pour encoder la chaîne. Plus ce nombre est petit, plus l'aglo est efficient\n", 40 | "\n", 41 | "* le temps de calcul nécessaire pour encoder/décoder, étant entendu que, dans le domaine des modèles de langage, les texte à encoder peuvent être de plus giga octets. Plus le temps est court, plus l'algorithme est efficient.\n", 42 | "\n", 43 | "Dans la suite, on appellera `token` un de ces motifs de base et `tokenization` le processus consistant à découper une chaîne de caractères en éléments de base disponibles dans un vocabulaire. On utilisera le terme *token* pour désigner sans distinction l'élément de base du vocabulaire ou l'entier associé, qui sont en bijection. L'`encoding` est le processus permettant de passer de la chaîne à la liste de tokens et donc d'entiers, le `decodage` le processus réciproque (passage d'une liste d'entiers à une chaîne de caractères)." 44 | ] 45 | }, 46 | { 47 | "cell_type": "markdown", 48 | "id": "2fc1ed68-cf1b-4fb0-8c5b-c52c70cbdbe2", 49 | "metadata": {}, 50 | "source": [ 51 | "## Question 1 - Chargement du jeu de données\n", 52 | "\n", 53 | "Avec la commande `wget` directement dans ce notebook, télécharger le contenu du fichier `tinyshakespeare` et le stocker dans le répertoire courant." 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": 26, 59 | "id": "5ddaa536-439e-4bcb-8080-a9c1c0a18246", 60 | "metadata": {}, 61 | "outputs": [ 62 | { 63 | "name": "stdout", 64 | "output_type": "stream", 65 | "text": [ 66 | "--2023-11-29 08:46:53-- https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt\n", 67 | "Résolution de raw.githubusercontent.com (raw.githubusercontent.com)… 2606:50c0:8000::154, 2606:50c0:8002::154, 2606:50c0:8001::154, ...\n", 68 | "Connexion à raw.githubusercontent.com (raw.githubusercontent.com)|2606:50c0:8000::154|:443… connecté.\n", 69 | "requête HTTP transmise, en attente de la réponse… 200 OK\n", 70 | "Taille : 1115394 (1,1M) [text/plain]\n", 71 | "Enregistre : ‘input.txt.5’\n", 72 | "\n", 73 | "input.txt.5 100%[===================>] 1,06M --.-KB/s ds 0,08s \n", 74 | "\n", 75 | "2023-11-29 08:46:54 (13,5 MB/s) - ‘input.txt.5’ enregistré [1115394/1115394]\n", 76 | "\n" 77 | ] 78 | }, 79 | { 80 | "name": "stderr", 81 | "output_type": "stream", 82 | "text": [ 83 | "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", 84 | "To disable this warning, you can either:\n", 85 | "\t- Avoid using `tokenizers` before the fork if possible\n", 86 | "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n" 87 | ] 88 | } 89 | ], 90 | "source": [ 91 | "!wget https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt" 92 | ] 93 | }, 94 | { 95 | "cell_type": "markdown", 96 | "id": "558e273e-6c31-450b-a61c-943e8c304b58", 97 | "metadata": {}, 98 | "source": [ 99 | "# Première partie - Character Level Tokenization\n", 100 | "\n", 101 | "## Question 2\n", 102 | "\n", 103 | "* Charger le contenu du fichier dans une variable nommée `text`. Vous spécifierez un encodage de type utf-8 ;\n", 104 | "\n", 105 | "* afficher les 200 premiers caractères du texte ;\n", 106 | "\n", 107 | "* afficher le nombre total de caractères du texte." 108 | ] 109 | }, 110 | { 111 | "cell_type": "code", 112 | "execution_count": 27, 113 | "id": "fdb7ff22-25fc-4d82-8639-4eca14c81944", 114 | "metadata": {}, 115 | "outputs": [ 116 | { 117 | "name": "stdout", 118 | "output_type": "stream", 119 | "text": [ 120 | "First Citizen:\n", 121 | "Before we proceed any further, hear me speak.\n", 122 | "\n", 123 | "All:\n", 124 | "Speak, speak.\n", 125 | "\n", 126 | "First Citizen:\n", 127 | "You are all resolved rather to die than to famish?\n", 128 | "\n", 129 | "All:\n", 130 | "Resolved. resolved.\n", 131 | "\n", 132 | "First Citizen:\n", 133 | "First, you\n", 134 | "Nombre de caractères : 1115394\n" 135 | ] 136 | } 137 | ], 138 | "source": [ 139 | "with open('input.txt', 'r', encoding='utf-8') as f:\n", 140 | " text = f.read()\n", 141 | "print(text[:200])\n", 142 | "print(f\"Nombre de caractères : {len(text)}\")" 143 | ] 144 | }, 145 | { 146 | "cell_type": "markdown", 147 | "id": "aa25f64a-6804-4d2d-9f48-666dc97836ea", 148 | "metadata": {}, 149 | "source": [ 150 | "## Question 3\n", 151 | "\n", 152 | "Créer une fonction `build_vocab` qui prend comme paramètre une chaîne de carcatères `input_str` et qui renvoie :\n", 153 | "\n", 154 | "* la liste `chars` de tous les caractères utilisés, sans doublon, classée dans l'ordre des codes ASCII des caractères\n", 155 | "\n", 156 | "* la taille `vocab_size` de cette liste\n", 157 | "\n", 158 | "* vérifier avec un `assert` que pour la chaîne `\"Andrej Karpathy, né le 23 octobre 1986, est un informaticien slovaco-canadien qui a été directeur de l'intelligence artificielle et du pilotage automatique chez Tesla. Il travaille actuellement pour OpenAI\"` le résultat concaténé retourné est `\" ',-.123689AIKOTabcdefghijlmnopqrstuvyzé\"` et la longueur `40`." 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": 28, 164 | "id": "d69c2acc-b261-4e85-a736-71f2eca407c3", 165 | "metadata": {}, 166 | "outputs": [], 167 | "source": [ 168 | "def build_vocab(input_str):\n", 169 | " chars = sorted(set(input_str))\n", 170 | " vocab_size = len(chars)\n", 171 | " return chars, vocab_size\n", 172 | "\n", 173 | "l, s = build_vocab(\"Andrej Karpathy, né le 23 octobre 1986, est un informaticien slovaco-canadien qui a été directeur de l'intelligence artificielle et du pilotage automatique chez Tesla. Il travaille actuellement pour OpenAI\")\n", 174 | "assert ''.join(l) == \" ',-.123689AIKOTabcdefghijlmnopqrstuvyzé\"\n", 175 | "assert s == 40\n", 176 | "\n", 177 | "chars, vocab_size = build_vocab(text)" 178 | ] 179 | }, 180 | { 181 | "cell_type": "markdown", 182 | "id": "19286932-7d2c-44dd-8585-58661951df14", 183 | "metadata": {}, 184 | "source": [ 185 | "## Question 4. Character Level Tokenization\n", 186 | "Il s'agit de convertir ce texte en une séquence d'entiers à partir du vocabulaire défini précédemment.\n", 187 | "\n", 188 | "* créer une fonction `encode` qui prend en paramètre une liste de caractères et renvoie la liste des indices des caractères correspondant dans la liste `vocab`\n", 189 | "\n", 190 | "* créer une fonction `decode` qui est la fonction réciproque\n", 191 | "\n", 192 | "* vérifier que `encode(\"hii there\")` renvoie `[46, 47, 47, 1, 58, 46, 43, 56, 43]`\n", 193 | "\n", 194 | "* vérifier que `decode([46, 47, 47, 1, 58, 46, 43, 56, 43])` renvoie `\"hii_there\"`\n", 195 | "\n", 196 | "Dans cette question, nous avons associé, dans la fonction `encode`, un entier à chaque caractère, ce qui s'appelle `Character Level Tokenization`." 197 | ] 198 | }, 199 | { 200 | "cell_type": "code", 201 | "execution_count": 29, 202 | "id": "d551465d-e257-42bf-a26d-d595492dad67", 203 | "metadata": {}, 204 | "outputs": [], 205 | "source": [ 206 | "def encode(str_input):\n", 207 | " return [chars.index(c) for c in str_input]\n", 208 | "\n", 209 | "def decode(l):\n", 210 | " return ''.join([chars[entier] for entier in l])\n", 211 | "\n", 212 | "assert encode('hii there') == [46, 47, 47, 1, 58, 46, 43, 56, 43]\n", 213 | "assert decode([46, 47, 47, 1, 58, 46, 43, 56, 43]) == 'hii there'" 214 | ] 215 | }, 216 | { 217 | "cell_type": "markdown", 218 | "id": "86050951-b96f-499d-aee5-2979bd7b20c5", 219 | "metadata": {}, 220 | "source": [ 221 | "## Question 5. Performances de notre Character Encoder\n", 222 | "\n", 223 | "* Mesurer le temps total nécessaire pour encoder le texte complet du texte tiny_shakespeare avec la fonction précédente ;\n", 224 | "\n", 225 | "* Afficher la vitesse d'encodage en octets/secondes ;\n", 226 | "\n", 227 | "* Mesurer et afficher le nombre d'éléments du texte encodé ;\n", 228 | "\n", 229 | "* Mesurer le temps total de décodage pour le texte, et exprimer de la même manière la vitesse de décodage en octets/s." 230 | ] 231 | }, 232 | { 233 | "cell_type": "code", 234 | "execution_count": 30, 235 | "id": "8dc1c137-e2bc-4451-a87d-2973e69c35b8", 236 | "metadata": {}, 237 | "outputs": [ 238 | { 239 | "name": "stdout", 240 | "output_type": "stream", 241 | "text": [ 242 | "Nombre d'éléments de la liste encodée: 1115394 entiers.\n", 243 | "Temps d'encodage en octets/seconde: 2662282\n", 244 | "Temps de décodage en octets/seconde: 30516042\n" 245 | ] 246 | } 247 | ], 248 | "source": [ 249 | "import time\n", 250 | "t1 = time.perf_counter()\n", 251 | "shakespeare_encoded = encode(text)\n", 252 | "t2 = time.perf_counter()\n", 253 | "shakespeare_decoded = decode(shakespeare_encoded)\n", 254 | "t3 = time.perf_counter()\n", 255 | "print(f\"Nombre d'éléments de la liste encodée: {len(shakespeare_encoded)} entiers.\")\n", 256 | "print(f\"Temps d'encodage en octets/seconde: {int(len(shakespeare_encoded)/(t2-t1))}\")\n", 257 | "print(f\"Temps de décodage en octets/seconde: {int(len(shakespeare_decoded)/(t3-t2))}\")" 258 | ] 259 | }, 260 | { 261 | "cell_type": "markdown", 262 | "id": "8fab7918-42dc-4fa4-ae5a-0ad765c31921", 263 | "metadata": {}, 264 | "source": [ 265 | "## Question 6. Qualité de notre Character Encoder\n", 266 | "\n", 267 | "* encoder le texte suivant: \"Napoleon is a spectacle-filled action epic that details the checkered rise and fall of the iconic French Emperor Napoleon Bonaparte\". Quelle est la taille de la liste obtenue ?\n", 268 | " \n", 269 | "* encoder le texte suivant: \"Napoléon est un film réalisé par Ridley Scott avec Joaquin Phoenix, Vanessa Kirby.\" Que constatez-vous ? quelle solution pouvez-vous apporter ?" 270 | ] 271 | }, 272 | { 273 | "cell_type": "code", 274 | "execution_count": 31, 275 | "id": "239ace76-0c5b-40f0-9956-612a4c3a45f5", 276 | "metadata": {}, 277 | "outputs": [ 278 | { 279 | "name": "stdout", 280 | "output_type": "stream", 281 | "text": [ 282 | "131\n" 283 | ] 284 | }, 285 | { 286 | "ename": "ValueError", 287 | "evalue": "'é' is not in list", 288 | "output_type": "error", 289 | "traceback": [ 290 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 291 | "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", 292 | "Cell \u001b[0;32mIn[31], line 3\u001b[0m\n\u001b[1;32m 1\u001b[0m nap_eng \u001b[38;5;241m=\u001b[39m encode(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mNapoleon is a spectacle-filled action epic that details the checkered rise and fall of the iconic French Emperor Napoleon Bonaparte\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 2\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;28mlen\u001b[39m(nap_eng))\n\u001b[0;32m----> 3\u001b[0m np_fr \u001b[38;5;241m=\u001b[39m \u001b[43mencode\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mNapoléon est un film réalisé par Ridley Scott avec Joaquin Phoenix, Vanessa Kirby.\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", 293 | "Cell \u001b[0;32mIn[29], line 2\u001b[0m, in \u001b[0;36mencode\u001b[0;34m(str_input)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mencode\u001b[39m(str_input):\n\u001b[0;32m----> 2\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m [chars\u001b[38;5;241m.\u001b[39mindex(c) \u001b[38;5;28;01mfor\u001b[39;00m c \u001b[38;5;129;01min\u001b[39;00m str_input]\n", 294 | "Cell \u001b[0;32mIn[29], line 2\u001b[0m, in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mencode\u001b[39m(str_input):\n\u001b[0;32m----> 2\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m [\u001b[43mchars\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mindex\u001b[49m\u001b[43m(\u001b[49m\u001b[43mc\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;28;01mfor\u001b[39;00m c \u001b[38;5;129;01min\u001b[39;00m str_input]\n", 295 | "\u001b[0;31mValueError\u001b[0m: 'é' is not in list" 296 | ] 297 | } 298 | ], 299 | "source": [ 300 | "nap_eng = encode(\"Napoleon is a spectacle-filled action epic that details the checkered rise and fall of the iconic French Emperor Napoleon Bonaparte\")\n", 301 | "print(len(nap_eng))\n", 302 | "np_fr = encode(\"Napoléon est un film réalisé par Ridley Scott avec Joaquin Phoenix, Vanessa Kirby.\")" 303 | ] 304 | }, 305 | { 306 | "cell_type": "code", 307 | "execution_count": 32, 308 | "id": "62de907f-9ad2-4a94-8d1e-07c65f7756aa", 309 | "metadata": {}, 310 | "outputs": [], 311 | "source": [ 312 | "# visiblement, une exception est levée du fait de la présence du caractère \"è\" qui n'est pas dans le vocabulaire.\n", 313 | "# Je propose d'étendre le jeu de données avec un texte en français comprenant des accents." 314 | ] 315 | }, 316 | { 317 | "cell_type": "markdown", 318 | "id": "b3c6c44d-909e-4052-8a79-89d6a6afffdb", 319 | "metadata": {}, 320 | "source": [ 321 | "# Partie 2\n", 322 | "\n", 323 | "## Question 7. Introduction à la problématique de Subword tokenization\n", 324 | "Nous pouvons travailler à partir d'un vocabulaire qui n'est pas constitué que de caractères simples mais de séquences de 2 caractères, ce qui permettra d'avoir des encodages plus courts.\n", 325 | "\n", 326 | "Par exemple, si le vocabulaire est constitué des éléments: `vocab = ['ch', 'ien', 'at']` alors l'encodage du mot `chien` sera `[0, 1]` et celui du mot `chat` sera `[0, 2]`, ne prenant dans les deux cas que deux entiers alors qu'il en aurait fallu 4 avec la méthode des questions précédentes. Il s'agit dans ce cas d'un algorithme de \"SubWord encoding\", plus performant de toute évidence puisqu'il divise dans ce cas le nombre d'entiers par 2.\n", 327 | "\n", 328 | "* définir une fonction `split_pair` qui prend une chaine de caractères et scinde la chaîne de caractères en groupes de deux caractères. Si la longueur de la chaîne de caractères est impaire alors la dernière lettre sera un caractère seul.\n", 329 | "\n", 330 | "* vérifier que la fonction `split_pair` appliquée à la chaîne `\"Napoleon\"` renvoie `['Na', 'po', 'le', 'on']`\n", 331 | "\n", 332 | "* vérifier que la fonction `split_pair` appliquée à la chaîne `\"Napoleon3\"`renvoie `['Na', 'po', 'le', 'on', '3']`\n", 333 | "\n", 334 | "* comme dans la question 3, construire ensuite un vocabulaire à partir de cette liste de paires de caractères, sans doublons. Vérifier que pour la chaîne `\"un chien et un chat rigolent, ha ha\"` vous obtenez le vocabulaire :\n", 335 | "`[' c', ' e', ' h', ', ', 'a', 'en', 'go', 'ha', 'hi', 'le', 'nt', 'ri', 't ', 'un']`\n", 336 | "et une taille de vocabulaire de `14` éléments.\n", 337 | " " 338 | ] 339 | }, 340 | { 341 | "cell_type": "code", 342 | "execution_count": 33, 343 | "id": "9cb42201-58e1-49eb-a5f4-562b29747833", 344 | "metadata": {}, 345 | "outputs": [], 346 | "source": [ 347 | "def split_pair(input_str):\n", 348 | " pair = True\n", 349 | " if len(input_str) % 2 == 1: #impair\n", 350 | " pair = False\n", 351 | " if not pair:\n", 352 | " last_character = input_str[-1]\n", 353 | " input_str = input_str[0:len(input_str)-1]\n", 354 | " splitted_pair = [input_str[i:i+2] for i in range(0, len(input_str), 2)]\n", 355 | " if not pair:\n", 356 | " splitted_pair += last_character\n", 357 | " return splitted_pair\n", 358 | "\n", 359 | "# vérification\n", 360 | "assert split_pair(\"Napoleon\") == ['Na', 'po', 'le', 'on']\n", 361 | "assert split_pair(\"Napoleon3\") == ['Na', 'po', 'le', 'on', '3']\n", 362 | "\n", 363 | "def build_vocab_pair(input_str):\n", 364 | " pairs = split_pair(input_str)\n", 365 | " vocab = sorted(set(pairs))\n", 366 | " vocab_size = len(vocab)\n", 367 | " return vocab, vocab_size\n", 368 | "\n", 369 | "v, s = build_vocab_pair(\"un chien et un chat rigolent, ha ha\")\n", 370 | "assert v == [' c', ' e', ' h', ', ', 'a', 'en', 'go', 'ha', 'hi', 'le', 'nt', 'ri', 't ', 'un']\n", 371 | "assert s == 14" 372 | ] 373 | }, 374 | { 375 | "cell_type": "markdown", 376 | "id": "dea15e77-c584-4b2e-b75d-adb8f3fac420", 377 | "metadata": {}, 378 | "source": [ 379 | "## Question 8. Un pair encoder simplifié\n", 380 | "\n", 381 | "* créer le vocabulaire correspondant au jeu de données `tiny_shakespeare`. Vérifier que la taille du vocabulaire est de `1334` ;\n", 382 | "\n", 383 | "* créer une fonction `encode_pair` et `decode_pair` qui s'appuient sur le vocabulaire précédent ;\n", 384 | "\n", 385 | "* vérifier que l'encoder renvoie pour la chaine `'I say unto you, what he hath done famously'` est `[391, 1165, 1296, 1237, 1208, 104, 1085, 156, 1267, 710, 88, 794, 887, 1203, 84, 1078, 794, 839, 1012, 1241, 993]`\n", 386 | "\n", 387 | "* vérifier que le décodage renvoie la bonne chaine" 388 | ] 389 | }, 390 | { 391 | "cell_type": "code", 392 | "execution_count": 34, 393 | "id": "9bbf2cbe-4b61-4c5a-9c05-86edf7bac23f", 394 | "metadata": {}, 395 | "outputs": [ 396 | { 397 | "name": "stdout", 398 | "output_type": "stream", 399 | "text": [ 400 | "La taille du vocabulaire est: 1334\n" 401 | ] 402 | } 403 | ], 404 | "source": [ 405 | "vocab, siz = build_vocab_pair(text)\n", 406 | "print(f\"La taille du vocabulaire est: {siz}\")\n", 407 | "\n", 408 | "def encode_pair(str_input):\n", 409 | " lis = split_pair(str_input)\n", 410 | " return [vocab.index(c) for c in lis]\n", 411 | "\n", 412 | "def decode_pair(l):\n", 413 | " return ''.join([vocab[entier] for entier in l])\n", 414 | "\n", 415 | "assert encode_pair('I say unto you, what he hath done famously') == [391, 1165, 1296, 1237, 1208, 104, 1085, 156, 1267, 710, 88, 794, 887, 1203, 84, 1078, 794, 839, 1012, 1241, 993]\n", 416 | "assert decode_pair([391, 1165, 1296, 1237, 1208, 104, 1085, 156, 1267, 710, 88, 794, 887, 1203, 84, 1078, 794, 839, 1012, 1241, 993]) == 'I say unto you, what he hath done famously'" 417 | ] 418 | }, 419 | { 420 | "cell_type": "markdown", 421 | "id": "513c91e1-c6cc-4249-9393-bf36a81032fe", 422 | "metadata": {}, 423 | "source": [ 424 | "## Question 9. Performances de ce pair encoder basique\n", 425 | "\n", 426 | "* reprendre les mêmes questions que la question 5 pour mesurer les performances de l'encoder et du decoder en octets/s pour le texte complet tiney_shakespeare.\n", 427 | "\n", 428 | "* conclure quant à la comparaison entre les deux encoders." 429 | ] 430 | }, 431 | { 432 | "cell_type": "code", 433 | "execution_count": 35, 434 | "id": "a27180c9-c11e-42e8-9a66-c9c5af3bc511", 435 | "metadata": {}, 436 | "outputs": [ 437 | { 438 | "name": "stdout", 439 | "output_type": "stream", 440 | "text": [ 441 | "Nombre d'éléments de la liste encodée: 557697 entiers.\n", 442 | "Temps d'encodage en octets/secondes: 128282\n", 443 | "Temps de décodage en octets/secondes: 60039143\n" 444 | ] 445 | } 446 | ], 447 | "source": [ 448 | "import time\n", 449 | "t1 = time.perf_counter()\n", 450 | "shakespear_encoded = encode_pair(text)\n", 451 | "t2 = time.perf_counter()\n", 452 | "shakespear_decoded = decode_pair(shakespear_encoded)\n", 453 | "t3 = time.perf_counter()\n", 454 | "print(f\"Nombre d'éléments de la liste encodée: {len(shakespear_encoded)} entiers.\")\n", 455 | "print(f\"Temps d'encodage en octets/secondes: {int(len(shakespear_encoded)/(t2-t1))}\")\n", 456 | "print(f\"Temps de décodage en octets/secondes: {int(len(shakespear_decoded)/(t3-t2))}\")" 457 | ] 458 | }, 459 | { 460 | "cell_type": "markdown", 461 | "id": "2a7618b4-91ba-4b59-a6d6-9db923256d3c", 462 | "metadata": {}, 463 | "source": [ 464 | "## Question 10. Limites de notre pair encoder\n", 465 | "\n", 466 | "* Encoder la chaine \"BUT Informatique de Nevers\" avec le pair encoder précédent.\n", 467 | "\n", 468 | "* Proposer et implémenter une solution pour corriger précédent pour le cas où les paires ne sont pas trouvées dans le vocabulaire.\n", 469 | "\n", 470 | "* L'encoder modifié devra permettre d'obtenir la même longueur pour le tiny_shakespeare encodé, et proposer une solution pour n'importe quelle chaîne de caractères pour les caractères présents dans le texte original tiny_shakespeare.\n", 471 | "\n", 472 | "* Vérifier l'impact de votre modification en termes de performances." 473 | ] 474 | }, 475 | { 476 | "cell_type": "code", 477 | "execution_count": 36, 478 | "id": "67dc4309-1a26-4451-a67a-f641be96d307", 479 | "metadata": {}, 480 | "outputs": [ 481 | { 482 | "name": "stdout", 483 | "output_type": "stream", 484 | "text": [ 485 | "Une paire n'est pas trouvée. Erreur : 'T ' is not in list\n" 486 | ] 487 | } 488 | ], 489 | "source": [ 490 | "try:\n", 491 | " encode_pair(\"BUT Informatique de Nevers\")\n", 492 | "except ValueError as e:\n", 493 | " print(\"Une paire n'est pas trouvée. Erreur :\", e)\n", 494 | "# cela ne fonctionne pas, car la paire \"T \" n'est pas dans le texte d'origine.\n", 495 | "\n", 496 | "# solution proposée: si une paire n'est pas trouvée, je la coupe en deux et j'encode chaque caractère.\n", 497 | "# mon vocabulaire est donc la fusion des deux vocabulaires précédents : celui par caractères, et celui par paires.\n" 498 | ] 499 | }, 500 | { 501 | "cell_type": "markdown", 502 | "id": "70eaba4c-ebb5-4daf-91fc-2795bad681d4", 503 | "metadata": {}, 504 | "source": [ 505 | "## Partie 3. Algorithme Byte Pair Encoding (BPE)\n" 506 | ] 507 | }, 508 | { 509 | "cell_type": "markdown", 510 | "id": "a7e892ba-c8d2-4910-b10c-8be1e3cba3ea", 511 | "metadata": {}, 512 | "source": [ 513 | "Dans l'exemple précédent, nous avons construit des paires de lettres de manière irréfléchie, simplement en stockant les paires au fur et à mesure qu'elles se présentent. L'algorithme Byte Pair Encoding permet de construire un vocabulaire de **token** (groupes de 2 ou plus lettres formant le vocabulaire de base) à partir de l'**analyse de la fréquence d'occurrence** dans un **corpus** (dans notre cas, le corpus est le fichier \"tiny shakespeare\"). Byte Pair Encoding (BPE) est un des algorithmes de **tokenization** les plus populaires, utilisé notamment dans les grands modèles de langage type ChatGPT.\n", 514 | "\n", 515 | "Nous allons, dans les questions suivantes, implémenter un algorithme BPE à partir de zéro, puis ensuite nous le confronterons à des implémentations industrielles libres (celles d'OpenAI et HuggingFace)." 516 | ] 517 | }, 518 | { 519 | "cell_type": "markdown", 520 | "id": "13de5fa8-9dc5-4cd0-8396-6c5a1d3fa51c", 521 | "metadata": {}, 522 | "source": [ 523 | "## Question 11. Fréquence de mots\n", 524 | "\n", 525 | "Ecrire une fonction `frequence_mots` qui prend comme paramètre une chaine de caractères `input_str` et qui renvoie un dictionnaire dont les clés sont les mots et les valeurs sont le nombre d'occurrences de ces mots dans la chaîne.\n", 526 | "\n", 527 | "Par exemple, dans la chaîne \"le chien Pluto et le chien Milou\", le mot \"chien\" est présent 2 fois, le mot 'le' aussi, on obtiendra donc: \n", 528 | "\n", 529 | "{'le': 2, 'chien': 2, 'Pluto': 1, 'et': 1, 'Milou': 1)}\n", 530 | "\n", 531 | "Nous allons travailler, dans ce qui suit, avec la chaîne de caractères suivante:\n", 532 | "```python\n", 533 | "corpus = \"This is the Hugging Face Course. This chapter is about tokenization. This section shows several tokenizer algorithms. Hopefully, you will be able to understand how they are trained and generate tokens.\"\n", 534 | "```\n", 535 | "\n", 536 | "Vérifier que\n", 537 | "```python\n", 538 | "mots_freqs = frequence_mots(corpus)\n", 539 | "print(mots_freqs)\n", 540 | "```\n", 541 | "\n", 542 | "renvoie bien\n", 543 | "\n", 544 | "```python\n", 545 | "{'This': 3, 'is': 2, 'the': 1, 'Hugging': 1, 'Face': 1, 'Course.': 1, 'chapter': 1, 'about': 1, 'tokenization.': 1, 'section': 1, 'shows': 1, 'several': 1, 'tokenizer': 1, 'algorithms.': 1, 'Hopefully,': 1, 'you': 1, 'will': 1, 'be': 1, 'able': 1, 'to': 1, 'understand': 1, 'how': 1, 'they': 1, 'are': 1, 'trained': 1, 'and': 1, 'generate': 1, 'tokens.': 1}\n", 546 | "```" 547 | ] 548 | }, 549 | { 550 | "cell_type": "code", 551 | "execution_count": 37, 552 | "id": "bbead76c-e064-4baa-b5d9-5cd49d414822", 553 | "metadata": {}, 554 | "outputs": [ 555 | { 556 | "name": "stdout", 557 | "output_type": "stream", 558 | "text": [ 559 | "{'This': 3, 'is': 2, 'the': 1, 'Hugging': 1, 'Face': 1, 'Course.': 1, 'chapter': 1, 'about': 1, 'tokenization.': 1, 'section': 1, 'shows': 1, 'several': 1, 'tokenizer': 1, 'algorithms.': 1, 'Hopefully,': 1, 'you': 1, 'will': 1, 'be': 1, 'able': 1, 'to': 1, 'understand': 1, 'how': 1, 'they': 1, 'are': 1, 'trained': 1, 'and': 1, 'generate': 1, 'tokens.': 1}\n" 560 | ] 561 | } 562 | ], 563 | "source": [ 564 | "def frequence_mots(input_str):\n", 565 | " # renvoie une liste de mots\n", 566 | " mots = input_str.split()\n", 567 | "\n", 568 | " freqs = {}\n", 569 | " for mot in mots:\n", 570 | " # on compte le nombre d'occurrences\n", 571 | " freqs[mot] = mots.count(mot)\n", 572 | " return freqs\n", 573 | "\n", 574 | "corpus = \"This is the Hugging Face Course. This chapter is about tokenization. This section shows several tokenizer algorithms. Hopefully, you will be able to understand how they are trained and generate tokens.\"\n", 575 | "mots_freqs = frequence_mots(corpus)\n", 576 | "print(mots_freqs)" 577 | ] 578 | }, 579 | { 580 | "cell_type": "markdown", 581 | "id": "ceaaa44c-8d77-4be1-b21e-3e44da6f3e3c", 582 | "metadata": {}, 583 | "source": [ 584 | "## Question 12. Alphabet et vocabulaire\n", 585 | "\n", 586 | "L'étape suivante est de déterminer le vocabulaire de base, formé par l'ensemble des caractères utilisés dans le corpus.\n", 587 | "\n", 588 | "* Ecrire une fonction `calcule_alphabet` qui prend comme paramètre un dictionnaire de fréquences de mots `dict_freq` et qui renvoie la liste des lettres utilisées ;\n", 589 | "\n", 590 | "* ensuite, créer le `vocabulaire` en ajoutant le token spécial `<|endoftext|>`:\n", 591 | "```python\n", 592 | "vocabulaire = [\"<|endoftext|>\"] + alphabet.copy()\n", 593 | "```\n", 594 | "\n", 595 | "Vous vérifierez que vous obtenez le vocabulaire suivant :\n", 596 | "```python\n", 597 | "['<|endoftext|>', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', 't', 'u', 'v', 'w', 'y', 'z']\n", 598 | "```\n" 599 | ] 600 | }, 601 | { 602 | "cell_type": "code", 603 | "execution_count": 38, 604 | "id": "2f03f0c3-3d36-4cab-bd70-4613445fb9a2", 605 | "metadata": {}, 606 | "outputs": [ 607 | { 608 | "name": "stdout", 609 | "output_type": "stream", 610 | "text": [ 611 | "['<|endoftext|>', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', 't', 'u', 'v', 'w', 'y', 'z']\n" 612 | ] 613 | } 614 | ], 615 | "source": [ 616 | "def calcule_alphabet(dict_freq):\n", 617 | " alphabet = []\n", 618 | "\n", 619 | " for word in dict_freq.keys():\n", 620 | " for letter in word:\n", 621 | " if letter not in alphabet:\n", 622 | " alphabet.append(letter)\n", 623 | " alphabet.sort()\n", 624 | " return alphabet\n", 625 | "\n", 626 | "alphabet = calcule_alphabet(mots_freqs)\n", 627 | "vocabulaire = [\"<|endoftext|>\"] + alphabet.copy()\n", 628 | "print(vocabulaire)" 629 | ] 630 | }, 631 | { 632 | "cell_type": "markdown", 633 | "id": "cf5ea284-e593-4172-b3ea-fbd7ddaf8565", 634 | "metadata": {}, 635 | "source": [ 636 | "## Question 13. Splits\n", 637 | "\n", 638 | "Ecrire une fonction `calcule_splits` qui prend comme paramètre une liste de mots `liste_mots`et qui renvoie un dictionnaire qui associe à chaque mot la liste des lettres qui le composent. Par exemple :\n", 639 | "```python\n", 640 | "{'This': ['T', 'h', 'i', 's'], 'is': ['i', 's'], ...\n", 641 | "```\n", 642 | "Appliquer cette fonction aux mots servant de clé dans la dictionnaire `freqs`" 643 | ] 644 | }, 645 | { 646 | "cell_type": "code", 647 | "execution_count": 39, 648 | "id": "62272ef6-77d3-4c1a-92d0-988d52c2709d", 649 | "metadata": {}, 650 | "outputs": [ 651 | { 652 | "name": "stdout", 653 | "output_type": "stream", 654 | "text": [ 655 | "{'This': ['T', 'h', 'i', 's'], 'is': ['i', 's'], 'the': ['t', 'h', 'e'], 'Hugging': ['H', 'u', 'g', 'g', 'i', 'n', 'g'], 'Face': ['F', 'a', 'c', 'e'], 'Course.': ['C', 'o', 'u', 'r', 's', 'e', '.'], 'chapter': ['c', 'h', 'a', 'p', 't', 'e', 'r'], 'about': ['a', 'b', 'o', 'u', 't'], 'tokenization.': ['t', 'o', 'k', 'e', 'n', 'i', 'z', 'a', 't', 'i', 'o', 'n', '.'], 'section': ['s', 'e', 'c', 't', 'i', 'o', 'n'], 'shows': ['s', 'h', 'o', 'w', 's'], 'several': ['s', 'e', 'v', 'e', 'r', 'a', 'l'], 'tokenizer': ['t', 'o', 'k', 'e', 'n', 'i', 'z', 'e', 'r'], 'algorithms.': ['a', 'l', 'g', 'o', 'r', 'i', 't', 'h', 'm', 's', '.'], 'Hopefully,': ['H', 'o', 'p', 'e', 'f', 'u', 'l', 'l', 'y', ','], 'you': ['y', 'o', 'u'], 'will': ['w', 'i', 'l', 'l'], 'be': ['b', 'e'], 'able': ['a', 'b', 'l', 'e'], 'to': ['t', 'o'], 'understand': ['u', 'n', 'd', 'e', 'r', 's', 't', 'a', 'n', 'd'], 'how': ['h', 'o', 'w'], 'they': ['t', 'h', 'e', 'y'], 'are': ['a', 'r', 'e'], 'trained': ['t', 'r', 'a', 'i', 'n', 'e', 'd'], 'and': ['a', 'n', 'd'], 'generate': ['g', 'e', 'n', 'e', 'r', 'a', 't', 'e'], 'tokens.': ['t', 'o', 'k', 'e', 'n', 's', '.']}\n" 656 | ] 657 | } 658 | ], 659 | "source": [ 660 | "def calcule_splits(liste_mots):\n", 661 | " d = {}\n", 662 | " for mot in liste_mots:\n", 663 | " d[mot] = list(mot)\n", 664 | " return d\n", 665 | "\n", 666 | "splits = calcule_splits(mots_freqs.keys())\n", 667 | "print(splits)" 668 | ] 669 | }, 670 | { 671 | "cell_type": "markdown", 672 | "id": "3bb0121c-385d-4933-8243-6a95d8712e24", 673 | "metadata": {}, 674 | "source": [ 675 | "## Question 14 - Fréquence de paires de lettres ou groupes de lettres\n", 676 | "\n", 677 | "Il s'agit ensuite de déterminer la fréquence de l'occurrence de chaque paire de lettres. Par exemple, le mot 'This' est associé aux lettres 'T', 'h', 'i et 's'. Il faut chercher, dans tous les mots, le nombre d'occurrences de 'T', 'h', puis de 'h','i', et de 'i','s'. Et ainsi de suite pour chaque mot.\n", 678 | "\n", 679 | "Ecrire une fonction `calcule_pair_freqs` qui retourne un dictionnaire avec comme clé un couple de lettres et comme valeur le nombre d'occurrences trouvées dans tous les mots. \n", 680 | "\n", 681 | "On aura par exemple en sortie:\n", 682 | "\n", 683 | "```python\n", 684 | "{('T', 'h'): 3,\n", 685 | " ('h', 'i'): 3,\n", 686 | " ('i', 's'): 5,\n", 687 | " ('t', 'h'): 3,\n", 688 | " ('h', 'e'): 2,\n", 689 | " ('H', 'u'): 1,\n", 690 | " ('u', 'g'): 1,\n", 691 | " ('g', 'g'): 1,\n", 692 | " ('g', 'i'): 1,\n", 693 | " ('i', 'n'): 2,\n", 694 | " ('n', 'g'): 1,\n", 695 | " ...}\n", 696 | "```" 697 | ] 698 | }, 699 | { 700 | "cell_type": "code", 701 | "execution_count": 40, 702 | "id": "75ffee04-49d5-481e-b10d-46b93870484f", 703 | "metadata": {}, 704 | "outputs": [ 705 | { 706 | "name": "stdout", 707 | "output_type": "stream", 708 | "text": [ 709 | "{('T', 'h'): 3, ('h', 'i'): 3, ('i', 's'): 5, ('t', 'h'): 3, ('h', 'e'): 2, ('H', 'u'): 1, ('u', 'g'): 1, ('g', 'g'): 1, ('g', 'i'): 1, ('i', 'n'): 2, ('n', 'g'): 1, ('F', 'a'): 1, ('a', 'c'): 1, ('c', 'e'): 1, ('C', 'o'): 1, ('o', 'u'): 3, ('u', 'r'): 1, ('r', 's'): 2, ('s', 'e'): 3, ('e', '.'): 1, ('c', 'h'): 1, ('h', 'a'): 1, ('a', 'p'): 1, ('p', 't'): 1, ('t', 'e'): 2, ('e', 'r'): 5, ('a', 'b'): 2, ('b', 'o'): 1, ('u', 't'): 1, ('t', 'o'): 4, ('o', 'k'): 3, ('k', 'e'): 3, ('e', 'n'): 4, ('n', 'i'): 2, ('i', 'z'): 2, ('z', 'a'): 1, ('a', 't'): 2, ('t', 'i'): 2, ('i', 'o'): 2, ('o', 'n'): 2, ('n', '.'): 1, ('e', 'c'): 1, ('c', 't'): 1, ('s', 'h'): 1, ('h', 'o'): 2, ('o', 'w'): 2, ('w', 's'): 1, ('e', 'v'): 1, ('v', 'e'): 1, ('r', 'a'): 3, ('a', 'l'): 2, ('z', 'e'): 1, ('l', 'g'): 1, ('g', 'o'): 1, ('o', 'r'): 1, ('r', 'i'): 1, ('i', 't'): 1, ('h', 'm'): 1, ('m', 's'): 1, ('s', '.'): 2, ('H', 'o'): 1, ('o', 'p'): 1, ('p', 'e'): 1, ('e', 'f'): 1, ('f', 'u'): 1, ('u', 'l'): 1, ('l', 'l'): 2, ('l', 'y'): 1, ('y', ','): 1, ('y', 'o'): 1, ('w', 'i'): 1, ('i', 'l'): 1, ('b', 'e'): 1, ('b', 'l'): 1, ('l', 'e'): 1, ('u', 'n'): 1, ('n', 'd'): 3, ('d', 'e'): 1, ('s', 't'): 1, ('t', 'a'): 1, ('a', 'n'): 2, ('e', 'y'): 1, ('a', 'r'): 1, ('r', 'e'): 1, ('t', 'r'): 1, ('a', 'i'): 1, ('n', 'e'): 2, ('e', 'd'): 1, ('g', 'e'): 1, ('n', 's'): 1}\n" 710 | ] 711 | } 712 | ], 713 | "source": [ 714 | "def calcule_pair_freqs(splits, word_freqs):\n", 715 | " pair_freqs = {}\n", 716 | " for word, freq in word_freqs.items():\n", 717 | " split = splits[word]\n", 718 | " if len(split) == 1:\n", 719 | " continue\n", 720 | " for i in range(len(split) - 1):\n", 721 | " pair = (split[i], split[i + 1])\n", 722 | " if pair in pair_freqs:\n", 723 | " pair_freqs[pair] += freq\n", 724 | " else:\n", 725 | " pair_freqs[pair] = freq # la première fois qu'on la trouve\n", 726 | " return pair_freqs\n", 727 | "\n", 728 | "pair_freqs = calcule_pair_freqs(splits, mots_freqs)\n", 729 | "print(pair_freqs)" 730 | ] 731 | }, 732 | { 733 | "cell_type": "markdown", 734 | "id": "ddf3a797-8b5e-47e7-a0c7-8d9451d5301a", 735 | "metadata": {}, 736 | "source": [ 737 | "## Question 15. Paire la plus fréquente\n", 738 | "\n", 739 | "Créer une fonction `paire_la_plus_frequente` qui prend comme paramètre le dictionnaire issu de la fonction précédente (celui contenant la fréquence de chaque paire) et qui retourne la paire la plus fréquente du corpus ainsi que le nombre correspondant à la fréquence. Si deux paires présentent le même nombre d'occurrences, la fonction renvoie la première paire rencontrée dans le parcours de l'ensemble des paires.\n", 740 | "\n", 741 | "Vérifier que `paire_la_plus_frequente(pair_freqs)` renvoie `(('i', 's'), 5)`" 742 | ] 743 | }, 744 | { 745 | "cell_type": "code", 746 | "execution_count": 41, 747 | "id": "8b6077c5-8bd5-49a8-a01d-65b8072cea6f", 748 | "metadata": {}, 749 | "outputs": [ 750 | { 751 | "data": { 752 | "text/plain": [ 753 | "(('i', 's'), 5)" 754 | ] 755 | }, 756 | "execution_count": 41, 757 | "metadata": {}, 758 | "output_type": "execute_result" 759 | } 760 | ], 761 | "source": [ 762 | "def paire_la_plus_frequente(pair_freq_dict):\n", 763 | " best_pair = \"\"\n", 764 | " max_freq = None\n", 765 | " \n", 766 | " for pair, freq in pair_freq_dict.items():\n", 767 | " if max_freq is None or max_freq < freq:\n", 768 | " best_pair = pair\n", 769 | " max_freq = freq\n", 770 | " \n", 771 | " return best_pair, max_freq\n", 772 | "\n", 773 | "paire_la_plus_frequente(pair_freqs)" 774 | ] 775 | }, 776 | { 777 | "cell_type": "markdown", 778 | "id": "6bd64286-6c3c-44b8-9bbf-2e53b74b4f5b", 779 | "metadata": {}, 780 | "source": [ 781 | "## Question 16. Extension du vocabulaire\n", 782 | "\n", 783 | "On va maintenant rajouter au vocabulaire les combinaisons de lettres les plus fréquentes dans le texte.\n", 784 | "\n", 785 | "* créer un dictionnaire `fusions` qui associe, à la paire précédente `('i', 's')` la paire concaténée `'is'`\n", 786 | "\n", 787 | "* ajouter cette chaîne concaténée à la liste `vocabulaire`.\n", 788 | "\n", 789 | "Remarque : cette question est très facile, inutile de créer une fonction.\n", 790 | "\n", 791 | "Vous vérifierez que\n", 792 | "\n", 793 | "```python\n", 794 | "print(fusions)\n", 795 | "print(vocabulaire)\n", 796 | "```\n", 797 | "renvoie\n", 798 | "```\n", 799 | "{('i', 's'): 'is'}\n", 800 | "['<|endoftext|>', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', 't', 'u', 'v', 'w', 'y', 'z', 'is']\n", 801 | "```" 802 | ] 803 | }, 804 | { 805 | "cell_type": "code", 806 | "execution_count": 42, 807 | "id": "10b91e46-4718-40df-b4aa-febbb8e5d541", 808 | "metadata": {}, 809 | "outputs": [ 810 | { 811 | "name": "stdout", 812 | "output_type": "stream", 813 | "text": [ 814 | "{('i', 's'): 'is'}\n", 815 | "['<|endoftext|>', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', 't', 'u', 'v', 'w', 'y', 'z', 'is']\n" 816 | ] 817 | } 818 | ], 819 | "source": [ 820 | "fusions = {(\"i\", \"s\"): \"is\"}\n", 821 | "vocabulaire.append(\"is\")\n", 822 | "print(fusions)\n", 823 | "print(vocabulaire)" 824 | ] 825 | }, 826 | { 827 | "cell_type": "markdown", 828 | "id": "53fb801b-d118-489b-898b-30ba79e73fe3", 829 | "metadata": {}, 830 | "source": [ 831 | "## Question 16. Fusion des paires\n", 832 | "\n", 833 | "C'est la dernière étape de l'algorithme de création du vocabulaire. On se donne une taille maximale du vocabulaire `vocab_size` que l'on fixe à `50`. Reproduire l'étape précédente (reherche de la paire la plus fréquence, fusion, ajout au vocabulaire) jusqu'à ce que la taille maximale du vocabulaire soit atteinte.\n", 834 | "\n", 835 | "Pour cette valeur de `vocab_size`, vérifier que vous obtenez le vocabulaire suivant :\n", 836 | "\n", 837 | "```python\n", 838 | "['<|endoftext|>', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', 't', 'u', 'v', 'w', 'y', 'z', 'is', 'is', 'er', 'to', 'en', 'Th', 'This', 'th', 'ou', 'se', 'tok', 'token', 'nd', 'the', 'in', 'ab', 'tokeni', 'tokeniz', 'at', 'io']\n", 839 | "```" 840 | ] 841 | }, 842 | { 843 | "cell_type": "code", 844 | "execution_count": 43, 845 | "id": "9900bfce-772d-41ea-94de-a4ec05cdbdac", 846 | "metadata": {}, 847 | "outputs": [ 848 | { 849 | "name": "stdout", 850 | "output_type": "stream", 851 | "text": [ 852 | "['<|endoftext|>', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', 't', 'u', 'v', 'w', 'y', 'z', 'is', 'is', 'er', 'to', 'en', 'Th', 'This', 'th', 'ou', 'se', 'tok', 'token', 'nd', 'the', 'in', 'ab', 'tokeni', 'tokeniz', 'at', 'io']\n" 853 | ] 854 | } 855 | ], 856 | "source": [ 857 | "def merge_pair(a, b, splits):\n", 858 | " for mot in mots_freqs:\n", 859 | " split = splits[mot]\n", 860 | " if len(split) == 1:\n", 861 | " continue\n", 862 | "\n", 863 | " i = 0\n", 864 | " while i < len(split) - 1:\n", 865 | " if split[i] == a and split[i + 1] == b:\n", 866 | " split = split[:i] + [a + b] + split[i + 2 :]\n", 867 | " else:\n", 868 | " i += 1\n", 869 | " splits[mot] = split\n", 870 | " return splits\n", 871 | "\n", 872 | "vocab_size = 50\n", 873 | "\n", 874 | "while len(vocabulaire) < vocab_size:\n", 875 | " pair_freqs = calcule_pair_freqs(splits, mots_freqs)\n", 876 | " best_pair, max_freq = paire_la_plus_frequente(pair_freqs)\n", 877 | " splits = merge_pair(best_pair[0], best_pair[1], splits)\n", 878 | " fusions[best_pair] = best_pair[0] + best_pair[1]\n", 879 | " vocabulaire.append(best_pair[0] + best_pair[1])\n", 880 | "\n", 881 | "print(vocabulaire)" 882 | ] 883 | }, 884 | { 885 | "cell_type": "markdown", 886 | "id": "439f54eb-bc03-4e98-bca3-080a5c8fc347", 887 | "metadata": {}, 888 | "source": [ 889 | "## Question 17 - Tokenization\n", 890 | "\n", 891 | "La dernière étape de ce voyage vers les tokens consiste à créer une fonction `tokenize` qui prend une chaine de caractères et, comme dans le début de ce TP, contient l'ensemble des entiers faisant référence au vocabulaire de 50 termes construit précédemment.\n", 892 | "\n", 893 | "Vérifier que la tokenization :\n", 894 | "\n", 895 | "```python\n", 896 | "print(tokenize('This is not a token.'))\n", 897 | "```\n", 898 | "\n", 899 | "Donne bien\n", 900 | "\n", 901 | "```python\n", 902 | "['This', 'is', 'n', 'o', 't', 'a', 'token', '.']\n", 903 | "```" 904 | ] 905 | }, 906 | { 907 | "cell_type": "code", 908 | "execution_count": 44, 909 | "id": "04f2625f-094f-4cf9-bcb4-f5ead7a8f4bf", 910 | "metadata": {}, 911 | "outputs": [ 912 | { 913 | "name": "stdout", 914 | "output_type": "stream", 915 | "text": [ 916 | "['This', 'is', 'n', 'o', 't', 'a', 'token', '.']\n" 917 | ] 918 | } 919 | ], 920 | "source": [ 921 | "def tokenize(text):\n", 922 | " # première étape : découpage du mot en liste\n", 923 | " splits = [list(word) for word in text.split()]\n", 924 | " \n", 925 | " for pair, merge in fusions.items():\n", 926 | " for idx, split in enumerate(splits):\n", 927 | " i = 0\n", 928 | " while i < len(split) - 1:\n", 929 | " if split[i] == pair[0] and split[i + 1] == pair[1]:\n", 930 | " split = split[:i] + [merge] + split[i + 2 :]\n", 931 | " else:\n", 932 | " i += 1\n", 933 | " splits[idx] = split\n", 934 | "\n", 935 | " return sum(splits, [])\n", 936 | "\n", 937 | "print(tokenize('This is not a token.'))" 938 | ] 939 | }, 940 | { 941 | "cell_type": "markdown", 942 | "id": "01c50540-7976-483a-93e9-9965fea46600", 943 | "metadata": {}, 944 | "source": [ 945 | "## Question 18 - Bilan de la qualité algorithmique\n", 946 | "\n", 947 | "Pour la châine de caractères `This is not a token`, comparer la taille de la liste d'entiers obtenue pour chacun des 3 tokenizers étudiés.\n" 948 | ] 949 | }, 950 | { 951 | "cell_type": "markdown", 952 | "id": "63da73d1-9e8e-4e3e-a09a-9b06c457478c", 953 | "metadata": {}, 954 | "source": [ 955 | "## Question 19 - Performances de notre BPE\n", 956 | "\n", 957 | "Reprendre les questions précédentes en travaillant à partir du corpus `tinyshakespeare`." 958 | ] 959 | }, 960 | { 961 | "cell_type": "markdown", 962 | "id": "c4e647ec-3ad1-4bba-8218-aada2429221c", 963 | "metadata": {}, 964 | "source": [ 965 | "## Question 20. Implémentations industrielles de tokenizers GPT2 - OpenAI, HuggingFace\n", 966 | "\n", 967 | "* BPE est utilisé par OpenAI pour ses gpt depuis gpt2. C'est la bibliothèque `tiktoken` (https://github.com/openai/tiktoken) qui implémente cet algorithme\n", 968 | "\n", 969 | "* BPE est aussi utilisé par un autre grand acteur de l'IA générative : HuggingFace, dans sa bibliothèque `transformers` (https://github.com/huggingface/transformers)\n", 970 | "\n", 971 | "Pour utiliser la bibliothèque **tiktoken**, encoder/décoder :\n", 972 | "```python\n", 973 | "enc = tiktoken.get_encoding('gpt2')\n", 974 | "print(f\"Nombre d'élements dans le vocabulaire : {enc.n_vocab}\")\n", 975 | "enc.encode('This is not a token')\n", 976 | "```\n", 977 | "\n", 978 | "Pour utiliser la bibliothèque **transformers**, encoder/décoder :\n", 979 | "```python\n", 980 | "from transformers import GPT2Tokenizer\n", 981 | "tokenizer = GPT2Tokenizer.from_pretrained(\"gpt2\")\n", 982 | "tokenizer(\"This is not a token\")['input_ids']\n", 983 | "```\n", 984 | "\n", 985 | "* Quelle est la taille du vocabulaire `gpt2`?\n", 986 | "\n", 987 | "* Vérifier que les deux implémentations renvoient la même liste d'entiers pour l'encodage de la chaîne `This is not a token`.\n", 988 | "\n", 989 | "* Comparer ces implémentations de BPE avec celle que nous avons faite précédemment.\n", 990 | "\n", 991 | "* Comparer ces deux bibliothèques en encodage/décodage par rapport à la vitesse en octets/seconde.\n", 992 | "\n", 993 | "* Conclure." 994 | ] 995 | }, 996 | { 997 | "cell_type": "code", 998 | "execution_count": 45, 999 | "id": "801cf09c-ecc5-4d0b-a9fb-3facc42079a7", 1000 | "metadata": {}, 1001 | "outputs": [ 1002 | { 1003 | "name": "stdout", 1004 | "output_type": "stream", 1005 | "text": [ 1006 | "Nombre d'élements dans le vocabulaire : 50257\n" 1007 | ] 1008 | }, 1009 | { 1010 | "data": { 1011 | "text/plain": [ 1012 | "[1212, 318, 407, 257, 11241]" 1013 | ] 1014 | }, 1015 | "execution_count": 45, 1016 | "metadata": {}, 1017 | "output_type": "execute_result" 1018 | } 1019 | ], 1020 | "source": [ 1021 | "import tiktoken\n", 1022 | "enc = tiktoken.get_encoding('gpt2')\n", 1023 | "print(f\"Nombre d'élements dans le vocabulaire : {enc.n_vocab}\")\n", 1024 | "enc.encode(\"This is not a token\")" 1025 | ] 1026 | }, 1027 | { 1028 | "cell_type": "code", 1029 | "execution_count": 46, 1030 | "id": "c8669485-e613-4f4c-a4f9-36edd1cf9805", 1031 | "metadata": {}, 1032 | "outputs": [ 1033 | { 1034 | "data": { 1035 | "text/plain": [ 1036 | "[1212, 318, 407, 257, 11241]" 1037 | ] 1038 | }, 1039 | "execution_count": 46, 1040 | "metadata": {}, 1041 | "output_type": "execute_result" 1042 | } 1043 | ], 1044 | "source": [ 1045 | "from transformers import GPT2TokenizerFast\n", 1046 | "tokenizer = GPT2TokenizerFast.from_pretrained('gpt2')\n", 1047 | "tokenizer(\"This is not a token\")['input_ids']" 1048 | ] 1049 | }, 1050 | { 1051 | "cell_type": "code", 1052 | "execution_count": 47, 1053 | "id": "adb94557-3395-448e-8fef-7ee74a16cf60", 1054 | "metadata": {}, 1055 | "outputs": [ 1056 | { 1057 | "name": "stderr", 1058 | "output_type": "stream", 1059 | "text": [ 1060 | "Token indices sequence length is longer than the specified maximum sequence length for this model (338025 > 1024). Running this sequence through the model will result in indexing errors\n" 1061 | ] 1062 | }, 1063 | { 1064 | "name": "stdout", 1065 | "output_type": "stream", 1066 | "text": [ 1067 | "Temps total pour encodage avec tiktoken en secondes : 0.13s\n", 1068 | "Temps total pour encodage avec transformers en secondes: 0.59s\n", 1069 | "Temps total pour décodage avec tiktoken en secondes : 0.01s\n", 1070 | "Temps total pour décodage avec transformers en secondes: 1.13s\n" 1071 | ] 1072 | } 1073 | ], 1074 | "source": [ 1075 | "# comparaison des performances en encodage\n", 1076 | "t0 = time.perf_counter()\n", 1077 | "openai_encoded = enc.encode(text) # encodage de tinyshakespeare avec OpenAI tiktoken\n", 1078 | "t1 = time.perf_counter()\n", 1079 | "hf_encoded = tokenizer(text)['input_ids'] # tokenization avec HuggingFace transformers\n", 1080 | "t2 = time.perf_counter()\n", 1081 | "#assert openai_encoded == hf_encoded['input_ids'] # on vérifie que le résultat est le même\n", 1082 | "# affichage des performances comparées\n", 1083 | "print(f\"Temps total pour encodage avec tiktoken en secondes : {t1-t0:.2f}s\")\n", 1084 | "print(f\"Temps total pour encodage avec transformers en secondes: {t2-t1:.2f}s\")\n", 1085 | "\n", 1086 | "# et pour le décodage\n", 1087 | "t0_bis = time.perf_counter()\n", 1088 | "openai_decoded = enc.decode(openai_encoded) # encodage de tinyshakespeare avec OpenAI tiktoken\n", 1089 | "t1_bis = time.perf_counter()\n", 1090 | "hf_decoded = tokenizer.decode(hf_encoded)\n", 1091 | "t2_bis = time.perf_counter()\n", 1092 | "\n", 1093 | "print(f\"Temps total pour décodage avec tiktoken en secondes : {t1_bis-t0_bis:.2f}s\")\n", 1094 | "print(f\"Temps total pour décodage avec transformers en secondes: {t2_bis-t1_bis:.2f}s\")" 1095 | ] 1096 | } 1097 | ], 1098 | "metadata": { 1099 | "kernelspec": { 1100 | "display_name": "Python 3 (ipykernel)", 1101 | "language": "python", 1102 | "name": "python3" 1103 | }, 1104 | "language_info": { 1105 | "codemirror_mode": { 1106 | "name": "ipython", 1107 | "version": 3 1108 | }, 1109 | "file_extension": ".py", 1110 | "mimetype": "text/x-python", 1111 | "name": "python", 1112 | "nbconvert_exporter": "python", 1113 | "pygments_lexer": "ipython3", 1114 | "version": "3.9.0" 1115 | } 1116 | }, 1117 | "nbformat": 4, 1118 | "nbformat_minor": 5 1119 | } 1120 | --------------------------------------------------------------------------------