diff --git a/MyIA.AI.Notebooks/GenAI/RAG-et-Memoire-Semantique/04-Tokenisation-From-Scratch.ipynb b/MyIA.AI.Notebooks/GenAI/RAG-et-Memoire-Semantique/04-Tokenisation-From-Scratch.ipynb index e19e34b436..487d50b7c9 100644 --- a/MyIA.AI.Notebooks/GenAI/RAG-et-Memoire-Semantique/04-Tokenisation-From-Scratch.ipynb +++ b/MyIA.AI.Notebooks/GenAI/RAG-et-Memoire-Semantique/04-Tokenisation-From-Scratch.ipynb @@ -164,6 +164,14 @@ "print(\"Vocabulaire caractères :\", sorted(vocab_chars))" ] }, + { + "cell_type": "markdown", + "id": "86f9854d", + "metadata": {}, + "source": [ + "**Lecture chiffree — les deux extremes en deux lignes de tableau.** `vocabulaire = 188 | tokens/texte ~ 299` contre `vocabulaire = 33 | tokens/texte ~ 1313 | en moyenne 4.39 token/mot`. Le tokenizer-mots coupe la sequence par 4.39 (1313/299) mais paie 188 symboles — et un vocabulaire OUVERT qui grandira a chaque mot nouveau du monde reel. Le tokenizer-caracteres paie 33 symboles fermes mais 1313 tokens : chaque mot devient 4.39 unites que le modele doit traiter une par une. Ces 33 symboles se lisent dans la liste triee : 24 lettres (pas de `k` ni `w` — aucun mot du corpus ne les contient), les 7 accents francais (`à`, `è`, `é`, `ê`, `î`, `ô`, `û`), l'apostrophe et le trait d'union — la fingerprint exacte d'un corpus francais en minuscules. BPE va se placer ENTRE ces deux bornes : quelques dizaines a quelques centaines de symboles, pour un cout par mot proche du tokenizer-mots.\n" + ] + }, { "cell_type": "markdown", "id": "01ae7f06", @@ -287,6 +295,14 @@ " print(f\" {i:>2}. {a!r} + {b!r} -> {a+b!r}\")" ] }, + { + "cell_type": "markdown", + "id": "6e402ba9", + "metadata": {}, + "source": [ + "**Lecture chiffree — l'ordre des 60 fusions EST la statistique du corpus.** Les trois premieres lignes : `1. 'e' + '' -> 'e'`, `2. 't' + '' -> 't'`, `3. 's' + '' -> 's'` — les terminaisons francaises les plus courantes partent en premier. Puis les digrammes (`'e' + 'n' -> 'en'`, `'o' + 'u' -> 'ou'`, `'a' + 'u' -> 'au'`), puis `12. 'e' + 't' -> 'et'` reconstruit un mot fonction. A l'autre bout de la liste : `57. 'ren' + 'ard' -> 'renard'` et `60. 'or' + 'beau' -> 'orbeau'` — des mots ENTIERS du corpus n'emergent qu'en toutes dernieres fusions, parce qu'un mot entier doit battre la somme des frequences de ses parties. La lecture : BPE apprend d'abord ce qui est TRANSVERSAL (terminaisons, digrammes — utile pour tous les mots), et seulement a la fin ce qui est SPECIFIQUE (les mots frequents de ce corpus precis). C'est un extracteur de morphologie gratuit, par simple comptage de paires.\n" + ] + }, { "cell_type": "markdown", "id": "c67bdd0d", @@ -385,6 +401,14 @@ " print(f\" {w!r:>14} -> {BPE_encode(w)}\")" ] }, + { + "cell_type": "markdown", + "id": "8785506f", + "metadata": {}, + "source": [ + "**Lecture chiffree — l'inversibilite prouvee, et le gradient frequent/rare en un echantillon.** `decode(encode(mot)) == mot sur 8 mots : True` — la propriete tient par construction (chaque fusion est une concatenation, le decodage recolle). Mais l'echantillon dessous est la vraie lecon : `'vous' -> ['vous']` et `'renard' -> ['renard']` tiennent en UN token (les mots les plus frequents du corpus), `'corbeau' -> ['c', 'orbeau']` en deux, `'fromage' -> ['f', 'ro', 'm', 'age']` en quatre, `'dépourvue' -> ['dé', 'p', 'our', 'v', 'ue']` en cinq (rare, vu une fois). Le cout par mot suit la frequence : c'est la propriete qui rend le BPE economique sur les vraies langues (loi de Zipf — peu de mots tres frequents couvrent beaucoup de texte) et prudent sur le rare : le mot inconnu retombe sur des fragments, jamais sur un echec.\n" + ] + }, { "cell_type": "markdown", "id": "1dab2e5a", @@ -449,6 +473,14 @@ " print(f\" {m:>3} | {v:.3f}\")" ] }, + { + "cell_type": "markdown", + "id": "dad7ebc0", + "metadata": {}, + "source": [ + "**Lecture chiffree — la courbe du compromis, rendements decroissants chiffres.** `0 | 5.391`, `10 | 4.264`, `60 | 2.997`, `80 | 2.739`. Le premier bloc de 10 fusions gagne 5.391 - 4.264 = 1.127 tokens/mot, soit 0.113 par fusion ; le dernier palier (60 -> 80) gagne 2.997 - 2.739 = 0.258, soit 0.013 par fusion — pres de NEUF fois moins par symbole ajoute au vocabulaire. Chaque fusion coute la meme chose (un symbole a stocker, une ligne d'embedding) mais rapporte de moins en moins : les paires frequentes sont fusionnees en premier, il ne reste que des paires rares. La courbe est concave, et le choix de 60 fusions dans ce notebook tombe au genou : 2.997 tokens/mot, moitie du tokenizer-caracteres (5.391), pour 60+33 symboles seulement. La taille du vocabulaire est une decision d'ingenierie, pas un reglage par defaut.\n" + ] + }, { "cell_type": "code", "execution_count": 6, @@ -564,6 +596,14 @@ "print(f\"{'Qwen (réel)':<22}{qwen.vocab_size:>8} | {tot_qwen}\")" ] }, + { + "cell_type": "markdown", + "id": "2d355f3e", + "metadata": {}, + "source": [ + "**Lecture chiffree — le jouet contre le reel : 46 contre 21 tokens, pour un vocabulaire 2 667 fois plus petit.** `notre BPE (60) 93 | 46` et `Qwen (réel) 248044 | 21`, avec `mots 188 | 18` comme borne basse mecanique. Trois lectures. (1) Sur ces trois phrases francaises courantes, le Qwen reel est a 3 tokens du decoupage-mots (21 contre 18) : son vocabulaire de 248 044 symboles contient quasi chaque mot francais courant entier. (2) Notre BPE a 93 symboles doit fragmenter les memes mots : 46 tokens, x2.2 le reel, pour un rapport de vocabulaire de 248 044 / 93 = 2 667. (3) La lecon d'echelle : la compacite s'achete en TAILLE de vocabulaire — le jouet montre la MECANIQUE (compter, fusionner), le reel montre le RESULTAT a l'echelle (des annees de corpus, des centaines de milliers de fusions). L'ecart se creuserait sur les mots rares et les noms propres, que le Qwen connait et que le jouet decompose en caracteres.\n" + ] + }, { "cell_type": "markdown", "id": "ce8205f3", @@ -629,6 +669,14 @@ " print(f\"{name:<22}{len(toks):>9}{n_chunks(toks, MAX_TOKENS):>12}\")" ] }, + { + "cell_type": "markdown", + "id": "51090ac3", + "metadata": {}, + "source": [ + "**Lecture chiffree — la facture RAG du choix de tokenizer : 9 chunks contre 4, a document identique.** `mots 299 3`, `BPE 20 1145 9`, `BPE 80 819 7`, `Qwen (réel) 419 4` (tokens, puis chunks a budget `MAX_TOKENS = 128` fixe). Le document ne change pas d'un mot — seul le tokenizer varie — et le nombre de chunks passe de 4 (Qwen) a 9 (BPE jouet a 20 fusions) : x2.25 sur TOUT le cout aval du RAG (stockage vectoriel, embeddings a calculer, retrieval, contexte consomme). Deuxieme lecture interne : passer le jouet de 20 a 80 fusions economise 1 145 - 819 = 326 tokens, soit exactement 2 chunks de moins (9 -> 7) — la courbe concave de la section 6, convertie en euros d'infrastructure. Et la ligne `mots 3` est un piege a lire honnetement : le plus compact ici, mais vocabulaire ouvert et hors-vocabulaire garanti sur tout texte reel — la borne basse n'est pas une option.\n" + ] + }, { "cell_type": "markdown", "id": "fe279c5e", @@ -694,6 +742,14 @@ "print(\"→ compter les lettres « à l'œil » (10) ne se retrouve PAS dans la découpe, car le mot est fragmenté.\")" ] }, + { + "cell_type": "markdown", + "id": "910f175e", + "metadata": {}, + "source": [ + "**Lecture chiffree — les cas pathologiques, token par token.** `'strawberry' | [485, 654, 14911] | 10` : trois tokens pour dix lettres, d'ou l'echec celebre du comptage de lettres — les lettres ne sont pas des unites de la representation, la question « combien de r » est mal posee pour un modele. `'élève' | [236940] | 5` : le francais accentue tient en UN token — le vocabulaire Qwen couvre la langue courante mieux que l'intuition ne le suggerre. `'1234' | [16, 17, 18, 19]` : chaque CHIFFRE est un token atomique ; et la coherence se verifie en croisant les lignes — dans `'aérodrome 42' -> [64, 933, 22771, 6439, 220, 19, 17]`, le `42` reprend exactement les ids `19` et `17` du `1234` — chaque chiffre porte un id fixe ('2' = 17, '4' = 19), et l'id d'un chiffre ne code pas sa valeur positionnelle. Consequence directe : l'arithmetique sur grands nombres est difficile pour un LLM non parce qu'il « sait mal compter » mais parce que ses nombres n'ont pas de representation positionnelle — 1234 est quatre symboles independants, pas mille-deux-cent-trente-quatre.\n" + ] + }, { "cell_type": "markdown", "id": "51c1fbf0",