Skip to content
The loss curve

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 :

  1. Choisir une position de départ dans train.bin.
  2. Lire les block_size tokens suivants comme contexte.
  3. 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/activate
cd my-llm; .\\.venv\\Scripts\\Activate.ps1
cd my-llm && source .venv/bin/activate

Installe ensuite les librairies nécessaires :

  • numpy pour la sérialisation binaire
  • tiktoken pour la tokenization BPE GPT
  • requests pour télécharger le corpus
pip install numpy tiktoken requests
pip install numpy tiktoken requests
pip install numpy tiktoken requests

Si 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.txt
Invoke-WebRequest -Uri https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt -OutFile data\\input.txt
curl -L -o data/input.txt https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt

Inspecte-le :

head -n 20 data/input.txt
Get-Content data\\input.txt -TotalCount 20
head -n 20 data/input.txt

Voilà 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.uint16 stocke chaque token sur deux octets.
  • [6] tofile écrit les ids binaires.

Lance-le :

python -m scripts.prepare
python -m scripts.prepare
python -m scripts.prepare

Tu 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.py a 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 print n’apparaissent que si toutes les assertions passent. Tu vois partout = chemin de données sain.

Lance :

python -m scripts.verify_data
python -m scripts.verify_data
python -m scripts.verify_data

Sortie 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.bin et data/val.bin sont 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.