Chapitre 6 · 14 min
Empiler les couches
Assemble des neurones en couches, écris une passe avant de MLP et étends ton module de réseau de neurones local au-delà d’une seule ligne.
Au chapitre 5, tu as entraîné un unique et vu une droite glisser entre deux nuages de points. Cette droite était la seule chose qu’il pouvait faire. Sur des données comme XOR, un damier ou un cercle dans un cercle, il échoue.
La correction est simple : empiler des . Une couche est plusieurs en parallèle sur la même entrée. Chacun découpe sa propre ligne. Combine leurs sorties, et tu obtiens des courbes, des coins, des régions.
On va inspecter un () à deux couches, l’entraîner sur XOR, puis sauvegarder une passe avant locale réutilisable.
1. La couche linéaire
Une couche linéaire est y = Wx + b, où W est une matrice et b un vecteur de biais. Si x a longueur n et que la couche a out , W a la forme [out, n].
Chaque ligne de W est le vecteur de d’un .
Code · JavaScript
C’est le cheval de trait des réseaux denses. Lis-le une fois, habitue-toi aux shapes, puis réutilise-le partout.
2. La passe avant du MLP
On chaîne deux couches linéaires avec une non-linéarité entre les deux. Sans non-linéarité, deux couches linéaires s’effondrent en une seule couche linéaire. Ici, la non-linéarité est : relu(x) = max(0, x).
Écris cette fonction. Le chapitre compare ta sortie à une référence sur quatre points de test.
Code · JavaScript
Si les barres correspondent, la passe avant est correcte. Non entraîné, le réseau prédit surtout du bruit. C’est normal : l’ donne du sens aux couches.
3. Entraîner sur XOR
XOR est le cas classique où un seul échoue : deux quadrants opposés valent 0, les deux autres valent 1. Aucune droite ne les sépare.
Entraîne un avec hidden size 8 sur un XOR bruité.
Code · JavaScript
Le panneau gauche montre les régions de décision : chaque pixel est coloré selon la probabilité prédite. Après , tu devrais voir un motif de damier. La à droite doit descendre, parfois avec des plateaux.
Si l’ échoue, augmente hiddenSize, iterations ou ajuste lr. La plupart des échecs de réseaux neuronaux se résument à capacité, temps ou taille de pas.
4. Étendre llm/nn.py
Ajoute ces helpers sous le code du :
# [1]
Vector = list[float]
Matrix = list[Vector]
# [2]
def relu(x: Vector) -> Vector:
return [max(0.0, value) for value in x]
# [3]
def linear(x: Vector, weight: Matrix, bias: Vector) -> Vector:
return [
sum(xi * wi for xi, wi in zip(x, row)) + b
for row, b in zip(weight, bias)
]
def mlp_forward(
x: Vector,
w1: Matrix,
b1: Vector,
w2: Matrix,
b2: Vector,
) -> Vector:
# [4]
hidden = relu(linear(x, w1, b1))
# [5]
return linear(hidden, w2, b2)Lis d’abord les shapes :
- [1] nomme les formes : vecteur et matrice.
- [2]
relugarde les signaux positifs et coupe les négatifs à zéro. - [3]
linearapplique plusieurs à un même vecteur. - [4] crée des features cachées apprises.
- [5] mélange ces features vers la sortie.
Le code reste en listes pour rendre chaque shape visible. Au chapitre 12, linear deviendra torch.nn.Linear, mais le contrat ne changera pas.
Ce que ça corrige (et ce que ça coûte)
Corrige : n’importe quelle frontière imaginable. Le théorème d’approximation universelle garantit qu’un avec assez de cachés peut approcher n’importe quelle fonction continue sur un domaine compact, avec une précision arbitraire. XOR, spirales, cercles concentriques, tout y passe.
Coûte :
- Beaucoup plus de . Hidden size 8 avec 2 entrées, c’est 8×2 + 8 + 8 + 1 = 33 . Les vrais réseaux en ont des millions ou des milliards.
- Minima locaux. La sur une non convexe peut rester coincée. Les modernes s’en sortent presque toujours parce que leurs paysages de sont indulgents en haute dimension, mais la théorie reste largement empirique.
- . Hidden size, , initialisation, choix d’optimiseur — tout ça décide si l’ marche. Le chapitre 7 ouvre cette conversation.
Recap
- Une couche linéaire est
y = Wx + b. Une ligne de W par de sortie. - Un est au moins deux couches linéaires avec des non-linéarités (souvent ) entre elles. Sans non-linéarité, le réseau s’effondre en une seule fonction linéaire. - L’ est le même algorithme que pour un — sur la — mais le traverse toutes les couches (règle de chaîne). - Les résolvent XOR. C’est l’exemple canonique, mais le principe se généralise : toute frontière descriptible, un avec assez de capacité peut l’apprendre. - Tonllm/nn.pycontient maintenantlinear,reluetmlp_forward, les mêmes briques utilisées dans les couches feed-forward des . - Le prochain chapitre regarde comment la trouve réellement ces — et pourquoi la naïve ne suffit en général pas.
Pour aller plus loin
- Karpathy, « Building micrograd » — dérive la règle de chaîne visuellement et construit un autograd qui gère le calcul de sur des expressions arbitraires.
- 3Blue1Brown, « What is backpropagation, really doing? » — explication visuelle douce du .
- TensorFlow Playground — glisse les d’un réseau et regarde-le s’entraîner sur des datasets prédéfinis. Bon pour bâtir l’intuition.
- L’implémentation de référence vit dans
lib/ml/nn/.
Prochaine étape : la en direct — on a appelé mlpStep comme une boîte noire. Il est temps de regarder dedans, de voir pourquoi la vanilla cale souvent, et de rencontrer et .