Généraliser en profondeur : un réseau à N couches
Du cas particulier au cas général
Section titled “Du cas particulier au cas général”La leçon précédente codait en dur deux couches (W1/b1, W2/b2). Pour généraliser à un nombre arbitraire de couches, on stocke les paramètres dans un dictionnaire indexé par couche, et on boucle dessus :
class DeepNeuralNetwork: def __init__(self, layer_sizes, n_features): sizes = [n_features] + list(layer_sizes) + [1] self.C = len(sizes) - 1 # nombre de couches (hors entree) self.parametres = {} for c in range(1, self.C + 1): self.parametres[f"W{c}"] = np.random.randn(sizes[c], sizes[c-1]) * np.sqrt(2 / sizes[c-1]) self.parametres[f"b{c}"] = np.zeros((sizes[c], 1))layer_sizes=[16, 16, 8] construit un réseau à 3 couches cachées (16, 16 puis 8 neurones) ; layer_sizes=[4] construit un simple réseau à 1 couche cachée de 4 neurones. C’est la même classe qui couvre les deux cas.
Propagation avant et arrière génériques
Section titled “Propagation avant et arrière génériques”def _forward(self, X): activations = {"A0": X} for c in range(1, self.C + 1): Z = self.parametres[f"W{c}"] @ activations[f"A{c-1}"] + self.parametres[f"b{c}"] activations[f"A{c}"] = self._sigmoid(Z) return activationsLa boucle avant accumule les activations de chaque couche (A0, A1, ..., AC) dans un dictionnaire — elles seront réutilisées telles quelles pendant la rétropropagation, qui parcourt les couches en sens inverse :
for c in reversed(range(1, self.C + 1)): A_prev = activations[f"A{c-1}"] gradients[f"dW{c}"] = (1 / m) * dZ @ A_prev.T gradients[f"db{c}"] = (1 / m) * np.sum(dZ, axis=1, keepdims=True) if c > 1: dA_prev = self.parametres[f"W{c}"].T @ dZ dZ = dA_prev * A_prev * (1 - A_prev)Un problème où la profondeur fait la différence
Section titled “Un problème où la profondeur fait la différence”Sur une spirale à deux branches — plus difficile que les anneaux concentriques —, un réseau peu profond (4 neurones sur une seule couche) est mis en difficulté :
shallow = DeepNeuralNetwork(layer_sizes=[4], n_features=2)shallow.fit(X_train, y_train, learning_rate=1.5, n_iterations=6000)print("accuracy test:", acc_shallow)
deep = DeepNeuralNetwork(layer_sizes=[16, 16, 8], n_features=2)deep.fit(X_train, y_train, learning_rate=1.5, n_iterations=6000)print("accuracy test:", acc_deep)=== Reseau peu profond (1 couche cachee de 4 neurones) ===perte finale: 0.4593322016426831accuracy test: 0.7833333333333333
=== Reseau profond (3 couches cachees : 16, 16, 8 neurones) ===perte finale: 0.0018499924550939052accuracy test: 1.0Le réseau peu profond plafonne à 78 % : ses 4 neurones ne suffisent pas à capturer l’enroulement de la spirale. Le réseau profond, avec davantage de couches et de neurones, atteint 100 % — chaque couche supplémentaire peut recombiner les frontières apprises par la précédente en des formes de plus en plus complexes, une capacité qu’aucune augmentation du nombre d’itérations ne peut compenser pour le réseau peu profond.
Ce que ce résultat ne prouve pas
Section titled “Ce que ce résultat ne prouve pas”Cette comparaison change à la fois la profondeur (1 → 3 couches) et le nombre total de neurones (4 → 40) : elle démontre qu’un réseau plus profond et plus large résout un problème que l’autre ne résout pas, pas que la profondeur seule, à nombre de neurones égal, serait toujours supérieure à la largeur seule — une question plus subtile, activement étudiée en recherche, qui dépasse le cadre de ce cours.
Prochaine étape
Section titled “Prochaine étape”👉 Passer à un framework de production : Keras
Junior TSAFACK – 12/09/2026