Sortir de la séparation linéaire : la couche cachée
Une limite fondamentale du neurone unique
Section titled “Une limite fondamentale du neurone unique”Un neurone (régression logistique) ne peut tracer qu’une frontière de décision linéaire — une droite en 2D. Certains problèmes ne sont tout simplement pas séparables par une droite, quel que soit l’entraînement.
Un jeu de données qui le prouve
Section titled “Un jeu de données qui le prouve”Deux anneaux concentriques : une classe au centre, une classe en périphérie. Aucune droite ne peut les séparer.
def make_concentric_rings(n_per_class=150, noise=0.08): theta1 = np.random.uniform(0, 2 * np.pi, n_per_class) r1 = 1.0 + np.random.normal(0, noise, n_per_class) X1 = np.column_stack([r1 * np.cos(theta1), r1 * np.sin(theta1)])
theta2 = np.random.uniform(0, 2 * np.pi, n_per_class) r2 = 2.5 + np.random.normal(0, noise, n_per_class) X2 = np.column_stack([r2 * np.cos(theta2), r2 * np.sin(theta2)])
X = np.vstack([X1, X2]) y = np.concatenate([np.zeros(n_per_class), np.ones(n_per_class)]) return X, y.reshape(-1, 1)Le neurone unique échoue, comme prévu
Section titled “Le neurone unique échoue, comme prévu”print("Accuracy test (neurone seul):", single_acc)Accuracy test (neurone seul): 0.48333333333333334Une accuracy proche de 50 % sur un problème à deux classes équilibrées : le neurone unique ne fait pas mieux qu’un tirage au hasard — la confirmation expérimentale de sa limite théorique.
Ajouter une couche cachée
Section titled “Ajouter une couche cachée”class ShallowNeuralNetwork: def __init__(self, n_features, n_hidden): self.W1 = np.random.randn(n_hidden, n_features) * 0.1 self.b1 = np.zeros((n_hidden, 1)) self.W2 = np.random.randn(1, n_hidden) * 0.1 self.b2 = np.zeros((1, 1))
def _forward(self, X): Z1 = self.W1 @ X + self.b1 A1 = self._sigmoid(Z1) Z2 = self.W2 @ A1 + self.b2 A2 = self._sigmoid(Z2) return A1, A2Chaque neurone de la couche cachée (A1) apprend sa propre frontière linéaire ; la couche de sortie (A2) combine ces frontières linéaires en une frontière globale bien plus riche, capable d’entourer une région circulaire.
Rétropropagation à deux couches
Section titled “Rétropropagation à deux couches”def fit(self, X, y, learning_rate=0.5, n_iterations=5000): X, y = X.T, y.T m = X.shape[1] for i in range(n_iterations): A1, A2 = self._forward(X)
dZ2 = A2 - y dW2 = (1 / m) * dZ2 @ A1.T db2 = (1 / m) * np.sum(dZ2, axis=1, keepdims=True)
dZ1 = (self.W2.T @ dZ2) * A1 * (1 - A1) dW1 = (1 / m) * dZ1 @ X.T db1 = (1 / m) * np.sum(dZ1, axis=1, keepdims=True)
self.W1 -= learning_rate * dW1 self.b1 -= learning_rate * db1 self.W2 -= learning_rate * dW2 self.b2 -= learning_rate * db2Le gradient se propage de la sortie vers l’entrée : dZ2 (l’erreur en sortie) sert à calculer dZ1 (l’erreur de la couche cachée), via la dérivée de la sigmoïde (A1 * (1 - A1)) — c’est la rétropropagation, appliquée ici dans son cas le plus simple (2 couches).
Le résultat
Section titled “Le résultat”print("Accuracy test (reseau a 2 couches):", nn_acc)Accuracy test (reseau a 2 couches): 1.0Avec seulement 6 neurones cachés, le réseau sépare parfaitement les deux anneaux — précisément là où le neurone unique échouait totalement.
Prochaine étape
Section titled “Prochaine étape”👉 Généraliser en profondeur : un réseau à N couches
Junior TSAFACK – 12/09/2026