diff --git a/MyIA.AI.Notebooks/ML/DataScienceWithAgents/03-DeepLearning/3.9c-Pruning-From-Scratch.ipynb b/MyIA.AI.Notebooks/ML/DataScienceWithAgents/03-DeepLearning/3.9c-Pruning-From-Scratch.ipynb index 6ffebf3718..c3391dd369 100644 --- a/MyIA.AI.Notebooks/ML/DataScienceWithAgents/03-DeepLearning/3.9c-Pruning-From-Scratch.ipynb +++ b/MyIA.AI.Notebooks/ML/DataScienceWithAgents/03-DeepLearning/3.9c-Pruning-From-Scratch.ipynb @@ -157,6 +157,16 @@ " print(f' sparsite {s:.2f} : conserve {int(mask.sum() / mask.size * 100)}% des poids')" ] }, + { + "cell_type": "markdown", + "id": "a3b1799d", + "metadata": {}, + "source": [ + "### Lecture du résultat\n", + "\n", + "La conservation suit exactement la règle `round((1 - s) × n)` : sur les 10 000 poids du tensor, `s = 0.50` en garde 5 000 (50 %), `s = 0.90` en garde 1 000 (10 %), `s = 0.99` en garde 100 (1 %). Deux détails d'implémentation comptent ici : `np.partition` extrait le seuil (le k-ième plus grand `|w|`) en O(n) sans trier tout le tensor ; et le garde-fou `k = max(1, ...)` garantit qu'au moins un poids survit, même si l'on poussait la sparsité vers 1. Le masque obtenu est *unstructured* : les poids conservés sont éparpillés — le stockage peut être compressé, mais un matmul dense classique n'y gagne rien.\n" + ] + }, { "cell_type": "markdown", "id": "c623ccdb", @@ -245,6 +255,16 @@ " print(f' sparsite {s:.2f} : garde {keep.sum()}/{len(keep)} neurones de sortie')" ] }, + { + "cell_type": "markdown", + "id": "6895cea8", + "metadata": {}, + "source": [ + "### Lecture du résultat\n", + "\n", + "Deux enseignements dans cette sortie. D'abord le niveau des norms : sur un kernel gaussien i.i.d., les 16 L1-norms tiennent dans l'étroit intervalle **43.82–68.50** (facteur 1,56 entre extrêmes seulement) — sur du bruit, le critère « plus petite norme » discrimine peu. C'est l'entraînement qui écarte les norms (les filtres utiles grossissent), et c'est là que ce critère devient significatif. Ensuite l'effet du pruning structured : `s = 0.25` supprime 4 filtres entiers (12/16 conservés), `s = 0.50` la moitié (8/16) — chaque filtre retiré disparaît **du calcul** de la convolution, et sur le `Linear` 64×128 chaque colonne supprimée retire une unité cachée (48/64 puis 32/64). C'est la différence économique avec le unstructured : ici l'accélération est réelle, sans noyau sparse dédié.\n" + ] + }, { "cell_type": "markdown", "id": "3e4ff705", @@ -518,6 +538,16 @@ "print(f'Train : {len(train_ds)} images, Test : {len(test_ds)} images.')" ] }, + { + "cell_type": "markdown", + "id": "5a1298a1", + "metadata": {}, + "source": [ + "### Lecture du résultat\n", + "\n", + "L'information utile est la dernière ligne : **20 000 images d'entraînement, 10 000 de test**. Le reste de la sortie n'est que les barres de progression du premier téléchargement de MNIST (9.91 Mo d'images d'entraînement, etc.) — il disparaît des exécutions suivantes une fois le cache `./data` en place. Le sous-ensemble de 20 000 est tiré sans remise par `default_rng(42)` : le tirage est reproductible, et les quatre protocoles de la section 5 comparent leurs accuracies **sur les mêmes données**. Dernier réglage visible : les batchs de test (512) sont 4× plus gros que ceux d'entraînement (128) — sans rétropropagation, le seul coût est mémoire.\n" + ] + }, { "cell_type": "code", "execution_count": 6, @@ -777,6 +807,16 @@ "print(f'Dense final test acc = {dense_test_acc:.4f}')" ] }, + { + "cell_type": "markdown", + "id": "fa7576c5", + "metadata": {}, + "source": [ + "### Lecture du résultat\n", + "\n", + "La baseline dense converge en 5 epochs : la perte passe de 0.4529 à 0.0671, l'accuracy de test de 0.9298 à 0.9675 — mais le gain de la dernière epoch n'est que de +0.34 point (0.9641 → 0.9675) : la courbe s'aplatit. L'écart final train/test (0.9800 contre 0.9675, soit 1.25 point) signe un léger surapprentissage, attendu pour un MLP de ~235K paramètres nourri de 20K exemples. Ces deux chiffres — **0.9675 en 47.4 s** — sont l'étalon auquel les trois variantes pruning vont se mesurer : l'enjeu de la section 5 est de voir ce qu'il en reste avec 20 % des poids.\n" + ] + }, { "cell_type": "code", "execution_count": 9, @@ -859,6 +899,16 @@ "print(f'LTH final test acc = {lth_test_acc:.4f}')\n" ] }, + { + "cell_type": "markdown", + "id": "9dfcfbd4", + "metadata": {}, + "source": [ + "### Lecture du résultat\n", + "\n", + "La signature du **reset à l'init** est visible dès la première epoch : la loss repart à 0.5314, au même niveau que celle du dense à son premier passage (0.4529) — le réseau a tout oublié de l'entraînement dense, seul le masque a survécu. Pourtant cinq epochs suffisent à rattraper presque tout : 0.9678 contre 0.9675 au dense, avec 20 % des poids. C'est le comportement qualitatif annoncé par la LTH. Le temps (41.8 s contre 47.4 s au dense) n'est pas une accélération : le masque multiplie les poids par zéro mais les tenseurs restent denses en mémoire — sans noyau sparse dédié, la sparsité ne s'échange pas contre du calcul.\n" + ] + }, { "cell_type": "code", "execution_count": 10, @@ -955,6 +1005,16 @@ "print(f'Pruned final test acc = {pruned_test_acc:.4f}')\n" ] }, + { + "cell_type": "markdown", + "id": "e9e4817a", + "metadata": {}, + "source": [ + "### Lecture du résultat\n", + "\n", + "Le contraste avec la cellule précédente est immédiat : sans reset, la loss de la première epoch démarre à 0.1133 — environ 4.7× plus bas que le 0.5314 du LTH. Le modèle garde ses poids entraînés, l'entraînement **continue** au lieu de repartir. Conséquence mesurée : 0.9704 en finale, mieux que le LTH (0.9678) **et** que le dense (0.9675). Sur ce terrain léger, conserver les poids entraînés bat le retour à l'init — l'inversion par rapport à la prédiction de la section 5, analysée en synthèse (§6).\n" + ] + }, { "cell_type": "code", "execution_count": 11, @@ -1053,6 +1113,16 @@ "print(f'torch.prune final test acc = {torchprune_test_acc:.4f}')" ] }, + { + "cell_type": "markdown", + "id": "17208331", + "metadata": {}, + "source": [ + "### Lecture du résultat\n", + "\n", + "Les modules éligibles sont les trois couches `Linear` du MLP (784→256, 256→128, 128→10). Deux choses distinguent cette variante du from-scratch. D'abord le mécanisme : `global_unstructured` traite les poids des trois couches comme **un seul pool** pour fixer le seuil — la sparsité se répartit là où elle coûte le moins, là où le from-scratch l'appliquait couche par couche. Ensuite le résultat : 0.9719, le meilleur des quatre protocoles, avec une loss de départ à 0.0516 (comme le pruning simple, cette variante conserve les poids entraînés). L'écart reste mince — 0.15 point sur le pruning simple (0.9704) — mais il va dans le sens du pool global.\n" + ] + }, { "cell_type": "code", "execution_count": 12,