Aller au contenu
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 sur disque. Même un petit 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 , le , puis le sauvegarder en fichier binaire contigu que la boucle d’ 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 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' et de validation.

1. Voir à quoi ressemble un dataset tokenizé

La cellule utilise un fragment de conte et un volontairement simple pour montrer la forme du résultat. Le vrai preprocessing utilisera un , mais la sortie a la même forme : un flux d’ids de . Le transforme le texte en .

À toi de jouer · JavaScript

Observe le ratio de compression. Un vrai anglais tourne souvent autour de 0.25-0.3 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’ fera sans cesse :

  1. Choisir une position de départ dans train.bin.
  2. Lire les block_size suivants comme contexte.
  3. Utiliser le 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.

À toi de jouer · 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 GPT
  • requests pour télécharger le
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 brut.
  • [3] encode en ids GPT-2.
  • [4] garde 10 % pour validation.
  • [5] np.uint16 stocke chaque 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 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 et le 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 , mauvais , 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 est , comme au chapitre 3, ici avec GPT-2 pour éviter de réentraîner.
  • La boucle d’ é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.