Pourquoi plusieurs neurones?
En Phase 2, un seul neurone pouvait seulement tracer une ligne droite pour séparer les classes.
Mais regardons le graphique de Phase 1 : Versicolor et Virginica ne sont pas séparables par une droite. Elles se chevauchent.
Un neurone seul ne peut pas résoudre ce problème.
C'est ici qu'intervient la couche cachée.
L'idée : des détecteurs en cascade
Première couche (cachée) : 3 neurones qui apprennent chacun à détecter quelque chose de différent.
- Neurone A : « Détecte les pétales courts »
- Neurone B : « Détecte les pétales larges »
- Neurone C : « Détecte les pétales carrés »
Chacun donne une sortie entre 0 et 1.
Deuxième couche (sortie) : 1 neurone qui prend ces 3 signaux et fait un vote pondéré.
- « Si A et C s'activent fortement, c'est Setosa »
- « Si B s'active seul, c'est probablement Versicolor »
- etc.
Architecture concrète
Entrées (x1, x2)
↓
├─→ [Neurone A] ─→ sortie a (0 à 1)
├─→ [Neurone B] ─→ sortie b (0 à 1)
└─→ [Neurone C] ─→ sortie c (0 à 1)
↓
Tous les trois vont vers...
↓
[Neurone Final] ─→ sortie finale (0 à 1)
Ça ressemble à ça :
- Les neurones A, B, C forment la « couche cachée »
- Le neurone Final forme la « couche de sortie »
- Entre eux : des poids différents pour chaque connexion
Explication détaillée
📖 Vocabulaire - français / anglais
- couche cachée ↔ *hidden layer*
- propagation avant ↔ *forward propagation* (calculer la sortie, de gauche à droite)
- neurone caché ↔ *hidden unit / hidden neuron*
Exercice 2.5.1 - Calculer un réseau complet à la main
Cet exercice est important. Tu vas tracer, manuelle ment, le chemin d'une donnée à travers un réseau entier.
Une fois que tu l'as fait une fois, tu comprendras pourquoi NumPy est magique (il fait la même chose, mais pour 1000 fleurs à la fois).
Les données
Fleur : Virginica avec longueur pétale x1 = 5.1, largeur pétale x2 = 1.8.
Architecture du réseau :
- Couche cachée : 3 neurones (A, B, C)
- Couche de sortie : 1 neurone (Final)
Les poids (fixés d'avance)
Neurone A (détecteur de longueur):
- w1 = 0.5 (longueur a un poids positif modéré)
- w2 = -0.2 (largeur a un léger effet négatif)
- biais = 0.1
Neurone B (détecteur de largeur):
- w1 = 0.3 (longueur a peu d'effet)
- w2 = 0.8 (largeur a un fort poids positif)
- biais = -0.3
Neurone C (détecteur mixte):
- w1 = -0.1 (longueur a un faible poids négatif)
- w2 = 0.4 (largeur a un poids positif)
- biais = 0.2
Neurone Final (vote final):
- Poids pour A : 0.4
- Poids pour B : 0.6
- Poids pour C : -0.3
- Biais : 0.1
Ta tâche
✍️ À toi de jouer
Calcule complètement à la main, étape par étape :
Étape 1 : Couche cachée (les 3 détecteurs)
- Neurone A:
- Somme_A = 5.1 * 0.5 + 1.8 * (-0.2) + 0.1 = ?
- Sortie_A = sigmoide(Somme_A) = ? (utilise une calculatrice ou Python juste pour la sigmoïde)
- Neurone B:
- Somme_B = 5.1 * 0.3 + 1.8 * 0.8 + (-0.3) = ?
- Sortie_B = sigmoide(Somme_B) = ?
- Neurone C:
- Somme_C = 5.1 * (-0.1) + 1.8 * 0.4 + 0.2 = ?
- Sortie_C = sigmoide(Somme_C) = ?
Étape 2 : Couche de sortie (le vote final)
- Neurone Final:
- Somme_Finale = Sortie_A * 0.4 + Sortie_B * 0.6 + Sortie_C * (-0.3) + 0.1 = ?
- Prédiction finale = sigmoide(Somme_Finale) = ?
Écris tous tes calculs intermédiaires. Ensuite, vérifie avec Python.
Besoin d'un indice ?
Pour les calculs à la main :
- Commence par Neurone A :
- 5.1 × 0.5 = 2.55
- 1.8 × (-0.2) = -0.36
- 2.55 + (-0.36) + 0.1 = 2.29
- Maintenant, utilise Python pour sigmoide(2.29)
- Fais pareil pour B et C
- Quand tu as les trois sorties, utilise-les pour le neurone Final
Les valeurs de sigmoïde :
- sigmoide(2.29) ≈ 0.908
- sigmoide(2.37) ≈ 0.914
- sigmoide(0.42) ≈ 0.603
(Ces valeurs sont approximatives; calcule avec Python pour la précision.)
Voir la réponse
Solution complète :
import math
def sigmoide(x):
return 1 / (1 + math.exp(-x))
# Données d'entrée
x1, x2 = 5.1, 1.8
# ============ COUCHE CACHÉE ============
# Neurone A
somme_a = x1*0.5 + x2*(-0.2) + 0.1
sortie_a = sigmoide(somme_a)
print(f"Neurone A:")
print(f" Somme: {x1}*0.5 + {x2}*(-0.2) + 0.1 = {somme_a:.3f}")
print(f" Sortie: sigmoide({somme_a:.3f}) = {sortie_a:.3f}")
# Neurone B
somme_b = x1*0.3 + x2*0.8 + (-0.3)
sortie_b = sigmoide(somme_b)
print(f"Neurone B:")
print(f" Somme: {x1}*0.3 + {x2}*0.8 + (-0.3) = {somme_b:.3f}")
print(f" Sortie: sigmoide({somme_b:.3f}) = {sortie_b:.3f}")
# Neurone C
somme_c = x1*(-0.1) + x2*0.4 + 0.2
sortie_c = sigmoide(somme_c)
print(f"Neurone C:")
print(f" Somme: {x1}*(-0.1) + {x2}*0.4 + 0.2 = {somme_c:.3f}")
print(f" Sortie: sigmoide({somme_c:.3f}) = {sortie_c:.3f}")
# ============ COUCHE DE SORTIE ============
somme_finale = sortie_a*0.4 + sortie_b*0.6 + sortie_c*(-0.3) + 0.1
prediction = sigmoide(somme_finale)
print(f"\nNeurone Final:")
print(f" Somme: {sortie_a:.3f}*0.4 + {sortie_b:.3f}*0.6 + {sortie_c:.3f}*(-0.3) + 0.1")
print(f" = {somme_finale:.3f}")
print(f" Prédiction finale: sigmoide({somme_finale:.3f}) = {prediction:.3f}")
print(f"\nRésultat : {prediction:.3f} (attendu ≈ 1 pour une Virginica)")
Résultat attendu :
- Sortie_A ≈ 0.908
- Sortie_B ≈ 0.914
- Sortie_C ≈ 0.603
- Prédiction finale ≈ 0.71
(Légèrement moins que 1, mais raisonnablement confiant : 71% = « c'est probablement une Virginica ».)
Ce que tu viens de faire : Tu as tracé, à la main, ce qu'on appelle la propagation avant (forward pass) à travers un réseau complet. C'est exactement ce que NumPy fera pour 150 fleurs en une seule ligne.
Exercice 2.5.2 - Interpréter les détecteurs
Maintenant que tu as les sorties (A ≈ 0.908, B ≈ 0.914, C ≈ 0.603), regarde ce qu'elles signifient.
Les poids révèlent le rôle de chaque neurone.
✍️ À toi de jouer
Regarde les poids et les sorties :
- Neurone A (w1=0.5, w2=−0.2) : Pourquoi sort-il 0.908?
- Neurone B (w1=0.3, w2=0.8) : Pourquoi sort-il 0.914?
- Neurone C (w1=−0.1, w2=0.4) : Pourquoi sort-il 0.603?
- Le vote final (A:0.4, B:0.6, C:−0.3) : Qui vote le plus?
Voir la réponse
Neurone A : w1=0.5 (amplifie la longueur), w2=-0.2 (réduit la largeur)
- Rôle : Détecteur de « pétale long »
- Pour x1=5.1 (long), il sort 0.908 (enthousiaste)
Neurone B : w1=0.3 (faible), w2=0.8 (amplifie fortement la largeur)
- Rôle : Détecteur de « pétale large »
- Pour x2=1.8 (large), il sort 0.914 (très enthousiaste)
Neurone C : w1=-0.1, w2=0.4 (poids faibles)
- Rôle : Modulateur nuancé
- Sort 0.603 (ni enthousias-te, ni sceptique)
Le vote final : B a le poids 0.6 (le plus élevé). Son signal domine. A apporte 0.4. C apporte -0.3 (modère un peu).
Ce que tu as appris :
- Chaque neurone caché détecte un motif différent. Ensemble, ils créent une limite courbe (pas une droite comme en Phase 0).
- Les poids cachés + le vote final = une équation complexe que un seul neurone ne pourrait pas exprimer.
- C'est ça, apprendre : ajuster ces poids pour que les détecteurs reconnaissent les bons motifs.
Conclusion : du manuel au NumPy
Tu viens de faire à la main :
- 3 neurones cachés (15 multiplications)
- 1 neurone final (4 multiplications)
- 4 sigmoïdes
- 25 opérations pour 1 fleur
Pour 150 fleurs : 3,750 opérations. Long.
NumPy le fait en 1 ligne :
hidden = sigmoid(np.dot(X, W1) + b1) # Toutes les 150 fleurs, 3 neurones
output = sigmoid(np.dot(hidden, W2) + b2) # Vote final
Ce n'est pas de la magie. Les mêmes 3,750 opérations, juste :
- En parallèle (GPU)
- En une seule ligne (matrices)
- Automatiquement scalable (1000 fleurs? Même ligne. 1000 neurones cachés? Même ligne.)
Tu comprends maintenant ce que NumPy, et plus tard PyTorch, automatisent.