TB Logo
NeuralNetworkCPP — Réseau de neurones en C++ à partir de zéro

NeuralNetworkCPP — Réseau de neurones en C++ à partir de zéro

mai 2023
Temps passé: ~30 h
Voir sur GitHub
Tags:
Deep LearningRéseaux de neurones
Compétences:
C++
C++

NeuralNetworkCPP

Ce projet est une petite bibliothèque de réseau de neurones écrite en C++ à partir de zéro, faite surtout pour apprendre.
À ce moment-là, j'étais en train d'apprendre les bases du deep learning, et je voulais aussi un projet C++ concret qui m'oblige à implémenter moi-même les maths et toute la boucle d'entraînement.

Pourquoi je l'ai fait

La plupart du temps, on peut entraîner un réseau de neurones sans trop se demander ce qui se passe vraiment “sous le capot”.
Ici, le but était l'inverse : implémenter tout le pipeline (forward pass → loss → backprop → mise à jour des paramètres), et voir comment des choix assez simples (fonctions d'activation, learning rate, batch size, etc.) impactent l'entraînement.

C'est volontairement une “learning library”, pas un framework de production. L'objectif est la clarté et l'expérimentation.

Comment ça marche (tech + algo)

La bibliothèque implémente un réseau feedforward classique avec plusieurs couches fully-connected, avec des poids et des biais initialisés aléatoirement.
Elle supporte des fonctions d'activation courantes comme ReLU, Sigmoid et TanH, configurables couche par couche.

L'entraînement est une descente de gradient “classique” :

  • La propagation avant met en cache les tenseurs intermédiaires (Z / A) pour les réutiliser pendant la backprop.
  • La backprop calcule les gradients couche par couche puis applique une mise à jour : W=WαdWW = W - \alpha \cdot dW et b=bαdbb = b - \alpha \cdot db, où α\alpha est le learning rate.
  • La loss utilisée dans la boucle d'entraînement est une log loss calculée à partir des sorties/targets (avec un petit epsilon pour la stabilité numérique).

Il y a aussi un mode dropout optionnel (avec une probabilité de conservation) pendant l'entraînement pour limiter l'overfitting.
Et pour le côté fun / perf, le projet inclut un chemin CUDA optionnel pour accélérer les calculs matriciels sur GPU NVIDIA.

Démo de reconnaissance de chiffres (MNIST)

Pour valider que la bibliothèque apprend réellement quelque chose, j'ai construit un exemple de reconnaissance de chiffres avec MNIST.
La démo charge 55 000 images d'entraînement et 5 000 images de test, les normalise, et construit des targets one-hot pour les chiffres de 0 à 9.

Pour ce run, l'architecture est un MLP simple : 784 → 128 → 128 → 128 → 10, entraîné avec un learning rate de 0.1 pendant 10 epochs, et un batch size de 512.
Les couches cachées utilisent ReLU, et la couche de sortie utilise Sigmoid dans cette implémentation.
Pendant l'entraînement, j'ai activé le dropout avec keepProb = 0.7.

UI d'entraînement (terminal)

J'ai aussi passé du temps sur l'UX terminal, parce que ça rend le projet plus “vivant” :

  • Une barre de progression custom est utilisée pour le chargement du dataset et pour chaque epoch.
  • Les valeurs de loss sont affichées régulièrement pendant l'entraînement.

Barre de progression d'entraînement
Barre de progression d'entraînement

Graphiques de précision (terminal)

À la fin de l'entraînement, la démo affiche :

  • Une accuracy globale sur le test set et sur le training set.
  • Un graphique par chiffre, où chaque classe a une barre verte pour les prédictions correctes et une barre rouge pour les erreurs, avec les compteurs à côté.

Graphique de précision du test set
Graphique de précision du test set
Graphique de précision du training set
Graphique de précision du training set

Résultats (10 epochs)

Sur cette expérience, j'ai obtenu 94.32% d'accuracy sur le test set et 95.65% sur le training set.
Un petit écart train/test comme ça suggère souvent un overfitting léger, mais rien d'extrême (le modèle apprend des features utiles et généralise plutôt bien).
Et vu que c'est un réseau fully-connected simple (pas de convolutions) avec une boucle d'entraînement à partir de zéro, c'était franchement un résultat très satisfaisant pour seulement 10 epochs.

Ce que j'ai appris

Ce projet m'a forcé à comprendre la backprop de façon très concrète (dimensions, erreurs de broadcasting, flux des gradients, et à quel point on peut “casser” l'entraînement sans s'en rendre compte tout de suite).
Il m'a aussi fait réaliser la partie perf du ML : organisation mémoire, coût des multiplications matricielles, batching, et pourquoi les librairies investissent autant dans des kernels optimisés (et pourquoi CUDA est un monde à part).

Côté tooling, construire une CLI avec un feedback visuel (progress bars + graphs) m'a rappelé à quel point l'ergonomie compte, même pour “juste un programme en terminal”.

Ce que j'améliorerais ensuite

Si je devais y revenir aujourd'hui, quelques améliorations seraient en haut de la liste :

  • Passer la sortie sur un setup plus standard Softmax + cross-entropy pour de la classification multi-classes.
  • Ajouter de meilleurs optimiseurs (Adam / RMSProp) et éventuellement un schedule de learning rate.
  • Ajouter plus d'options de régularisation et un meilleur suivi (export des métriques, matrice de confusion, etc.).
  • Étendre la librairie au-delà des couches denses (en restant simple), ou comparer avec un petit CNN pour visualiser clairement l'écart.

Si vous voulez explorer le code, le repo est ici : NeuralNetworkCPP on GitHub.