Chapitre 11 · 16 min
Préparer un dataset
Remplace les chaînes jouets par un vrai corpus, tokenize-le et écris train.bin / val.bin dans le projet local démarré au chapitre 1.
Jusqu’ici, ton projet local restait assez petit pour être inspecté à la main : texte jouet, maths en listes, minuscules matrices, pas de dépendances lourdes. C’était utile : tout restait visible.
Maintenant, on change d’échelle. On garde le même dossier, mais on remplace les chaînes jouets par un vrai corpus sur disque. Même un petit modèle de langage a besoin de fichiers, d’un preprocessing répétable et d’un split train/validation fiable.
Ce chapitre fait la partie ennuyeuse mais structurante : récupérer un corpus, le tokenizer, puis le sauvegarder en fichier binaire contigu que la boucle d’entraînement pourra lire vite.
Important : le sélecteur d’OS en haut de page compte. Les commandes changent entre macOS/Linux et Windows selon ton choix.
Ce qu’on vise
Le livrable est train.bin et val.bin sur ton disque : deux fichiers binaires contenant le corpus tokenizé sous forme de flux d’entiers non signés 16 bits. C’est le format attendu par nanoGPT et beaucoup de trainers pédagogiques. Lire des .bin est bien plus rapide que retokenizer à chaque epoch. Le est divisé en jeux d'entraînement et de validation.
1. Voir à quoi ressemble un dataset tokenizé
La cellule utilise un fragment de conte et un tokenizer volontairement simple pour montrer la forme du résultat. Le vrai preprocessing utilisera un tokenizer BPE, mais la sortie a la même forme : un flux d’ids de tokens. Le transforme le texte en tokens.
Code · JavaScript
Observe le ratio de compression. Un vrai BPE anglais tourne souvent autour de 0.25-0.3 token par caractère. La taille finale de train.bin vaut 2 × num_tokens octets, car on utilise uint16.
2. Du flux de tokens aux paires d’entraînement
Au chapitre 13, la boucle d’entraînement fera sans cesse :
- Choisir une position de départ dans
train.bin. - Lire les
block_sizetokens suivants comme contexte. - Utiliser le token suivant comme cible. Le détermine la longueur du contexte vu par le modèle.
C’est la fenêtre glissante (contexte, cible) dont on parle depuis le début.
Code · JavaScript
En pratique, on ne pré-calcule pas toutes les paires : on échantillonne des positions au hasard à la volée. Le modèle conceptuel reste le même.
3. Installer les librairies de données
Active le virtualenv créé au chapitre 1 :
cd my-llm && source .venv/bin/activatecd my-llm; .\\.venv\\Scripts\\Activate.ps1cd my-llm && source .venv/bin/activateInstalle ensuite les librairies nécessaires :
numpypour la sérialisation binairetiktokenpour la tokenization BPE GPTrequestspour télécharger le corpus
pip install numpy tiktoken requestspip install numpy tiktoken requestspip install numpy tiktoken requestsSi tout est propre, python -c "import numpy, tiktoken, requests; print('ok')" doit afficher ok.
4. Télécharger un petit corpus dans data/
Le dataset classique à cette échelle est TinyShakespeare : 1.1 MB, tout Shakespeare, assez petit pour tenir en mémoire et s’entraîner vite.
curl -L -o data/input.txt https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txtInvoke-WebRequest -Uri https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt -OutFile data\\input.txtcurl -L -o data/input.txt https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txtInspecte-le :
head -n 20 data/input.txtGet-Content data\\input.txt -TotalCount 20head -n 20 data/input.txtVoilà les données : environ 40 000 lignes de Shakespeare.
5. Le script de preprocessing
Sauvegarde ceci dans scripts/prepare.py :
"""prepare.py — tokenize input.txt and save train.bin / val.bin."""
import numpy as np
import tiktoken
from pathlib import Path
# [1]
data_dir = Path("data")
with open(data_dir / "input.txt", "r", encoding="utf-8") as f:
# [2]
text = f.read()
# GPT-2 tokenizer: 50_257 entries, well-tested, no training needed.
# [3]
enc = tiktoken.get_encoding("gpt2")
ids = enc.encode_ordinary(text)
print(f"corpus: {len(text):,} characters → {len(ids):,} tokens")
print(f"compression: {len(ids) / len(text):.3f} tokens/char")
print(f"vocab size: {enc.n_vocab}")
# 90/10 train/val split.
# [4]
split = int(0.9 * len(ids))
# [5]
train_ids = np.array(ids[:split], dtype=np.uint16)
val_ids = np.array(ids[split:], dtype=np.uint16)
# [6]
train_ids.tofile(data_dir / "train.bin")
val_ids.tofile(data_dir / "val.bin")
print(f"wrote data/train.bin ({train_ids.nbytes:,} bytes) and data/val.bin ({val_ids.nbytes:,} bytes)")Lis-le comme un pipeline :
- [1]
Path("data")évite les soucis de séparateurs entre OS. - [2] charge le corpus brut.
- [3] encode en ids GPT-2.
- [4] garde 10 % pour validation.
- [5]
np.uint16stocke chaque token sur deux octets. - [6]
tofileécrit les ids binaires.
Lance-le :
python -m scripts.preparepython -m scripts.preparepython -m scripts.prepareTu devrais obtenir des logs du genre :
corpus: 1,115,394 characters → 301,966 tokens
compression: 0.271 tokens/char
vocab size: 50257
wrote data/train.bin (543,538 bytes) and data/val.bin (60,394 bytes)6. Vérifier train.bin
Sauvegarde scripts/verify_data.py :
"""verify_data.py — confirm train.bin / val.bin are on the rails."""
import numpy as np
import tiktoken
ids = np.fromfile("data/train.bin", dtype=np.uint16)
val_ids = np.fromfile("data/val.bin", dtype=np.uint16)
enc = tiktoken.get_encoding("gpt2")
# [1]
total = ids.size + val_ids.size
assert 280_000 < total < 330_000, f"expected ~302k total tokens, got {total:,}"
val_ratio = val_ids.size / total
assert 0.05 < val_ratio < 0.15, f"expected ~10% val split, got {val_ratio:.1%}"
# [2]
decoded = enc.decode(ids[:50].tolist())
assert "First" in decoded or "Citizen" in decoded, (
f"decoded prefix does not look like TinyShakespeare: {decoded!r}"
)
print(f"✓ {ids.size:,} train tokens, {val_ids.size:,} val tokens ({val_ratio:.1%} val)")
print(f"✓ first 20 ids: {ids[:20].tolist()}")
print(f"✓ decoded prefix: {enc.decode(ids[:20].tolist())!r}")- [1] asserte que le nombre total de et le split train/val correspondent à ce que
prepare.pya produit. Si tu as relancé sur un autre corpus par erreur, ça se voit ici. - [2] asserte que le préfixe décodé contient un mot qu’on attend du début de TinyShakespeare. Attrape le cas où le tokenizer et le corpus ont divergé.
- Les
printn’apparaissent que si toutes les assertions passent. Tu vois✓partout = chemin de données sain.
Lance :
python -m scripts.verify_datapython -m scripts.verify_datapython -m scripts.verify_dataSortie attendue :
✓ 271,769 train tokens, 30,197 val tokens (10.0% val)
✓ first 20 ids: [5962, 22307, 25, 198, 8421, ...]
✓ decoded prefix: 'First Citizen:\nBefore we proceed any further, ...'Si une assertion se déclenche, le message dit quelle étape est cassée — mauvais corpus, mauvais tokenizer, ou mauvaise mise en forme du fichier.
Recap
data/train.binetdata/val.binsont le format attendu par les trainers pédagogiques. - Le tokenizer est BPE, comme au chapitre 3, ici avec GPT-2 pour éviter de réentraîner. - La boucle d’entraînement échantillonne des fenêtres aléatoires dans le fichier. - Un seul script Python prépare les données. - Ton projet local a maintenant de vraies données au lieu de chaînes jouets.
Pour aller plus loin
Prochaine étape : le code minimal — le modèle lui-même en moins de 150 lignes de PyTorch.