Peut-on remplacer un tableau de pixels par une fonction ? Dans ce projet, vous entraînez un réseau qui reçoit les coordonnées (x, y) d’un pixel et prédit son intensité. On redessine ensuite l’image en interrogeant cette fonction. Le souci intéressant sera de représenter ses détails.

Ce TP long est un sujet au choix d’AP9, commencé avec l’enseignant puis poursuivi à la maison. Il prolonge Nx en AP4 et les réseaux d’AP5. Les modalités de notation, la charge attendue et l’échéance seront précisées avant le lancement en janvier ; les étapes ne sont pas un barème.

Téléchargez le squelette image-coordonnees. Son README contient le sujet autonome, les contrats, les indications Nx, les commandes et le dépannage. Trois images originales sont produites localement ; rien n’est à télécharger après les dépendances. L’entraînement, Adam, les sauvegardes et les exports SVG sont fournis. Le socle utilise le CPU et des niveaux de gris ; aucune connaissance HTML, CSS ou JavaScript n’est nécessaire.

Observer une image avant de construire le réseau

Avec Elixir 1.17 ou plus récent, ouvrez un terminal dans le dossier extrait :

mix deps.get
mix picture.preview
mix test --only fourni

Ouvrez results/preview/ondes.svg dans votre éditeur. Le dessin de droite montre les pixels d’apprentissage, de validation et de test. Les sept tests des briques fournies passent dès le départ ; ceux des quatre fonctions élèves signalent « À compléter » jusqu’à leur implémentation.

La fonction apprend une seule image par expérience. Chaque pixel est un exemple, avec deux coordonnées en entrée et une intensité en cible. Les 256 pixels d’une image 16 × 16 sont séparés en 153 pixels d’apprentissage, 51 de validation et 52 de test. Le mélange est fixe et indépendant des intensités ; un pixel n’appartient qu’à une partition.

Relier les coordonnées à une intensité

Un pixel d'une image rectangulaire est normalisé sur chaque axe, puis traverse deux couches ReLU et une sortie sigmoïde.

Le schéma se lit de gauche à droite : un pixel donne deux coordonnées, ramenées entre 0 et 1, qui traversent deux couches cachées puis une sortie qui produit son intensité. Vous écrivez normalize/3, predict/2 et mse/2 dans lib/coordinate_image/picture.ex. La première ramène chaque coordonnée dans [0, 1]. La deuxième applique deux couches denses ReLU puis une sigmoïde ; rappelons qu’une couche dense calcule entrées × poids + biais, que la ReLU remplace les valeurs négatives par zéro et que la sigmoïde ramène toute valeur dans l’intervalle ]0, 1[, ce qui en fait une intensité. La dernière fonction calcule l’erreur quadratique moyenne (MSE) utilisée pour l’apprentissage.

CalculEntréeRésultat
Normalisation{N, 2}, largeur et hauteur{N, 2} ; le bas à droite devient (1, 1).
Réseau{N, D} et les paramètres{N, 1} ; une intensité par pixel.
PertePrédictions et cibles de même formeUn scalaire, moyenne des erreurs au carré.

Le README rappelle les produits matriciels, le broadcasting et les fonctions Nx utiles. Testez notamment une image rectangulaire : échanger largeur et hauteur serait invisible sur un carré.

mix test --only coordonnees
mix test --only reseau
mix test --only perte
mix picture.train raw --steps 25 --out results/raw-essai
mix picture.train raw --steps 200 --out results/raw-200

Les fichiers comparison.svg et curve.svg montrent la reconstruction et les mesures pendant l’apprentissage. Les prédictions des pixels de test restent hachurées jusqu’au choix final du modèle. Les dossiers d’expérience ne sont pas écrasés ; donnez un nouveau nom à chaque essai.

Ajouter des sinus et des cosinus

Le réseau reçoit d’abord des coordonnées qui varient régulièrement. Pour l’aider à décrire des variations plus rapides, nous allons ajouter des fonctions périodiques à son entrée : des sinus et des cosinus des coordonnées, à plusieurs fréquences. Ce sont des caractéristiques calculées une fois pour toutes, pas des paramètres appris ; le réseau apprend ensuite à les combiner.

Des sinus de fréquences 1 et 2 font respectivement une et deux périodes sur l'intervalle de coordonnées. Les valeurs sont ajoutées à chaque ligne de l'entrée.

Complétez fourier/2. Pour chaque fréquence f, elle calcule les sinus et cosinus de 2πfx et 2πfy, puis les réunit avec les coordonnées brutes. Avec F fréquences, on obtient {N, 2 + 4F}. Le README précise l’ordre des colonnes et guide la manipulation des axes.

mix test --only fourier
mix test --include integration
mix picture.train fourier --steps 200 --out results/fourier-200

Les fréquences par défaut sont 1, 2, 4, soit 14 entrées par pixel. Le réseau possède alors 985 paramètres, contre 697 avec les coordonnées brutes. Comparez les mêmes pixels, les mêmes mini-lots (les petits paquets de pixels d’apprentissage traités à chaque pas) et le même nombre de pas, tout en signalant cette différence de capacité. La graine conserve une expérience reproductible, sans rendre identiques des matrices de formes différentes.

Sur le portable M3 de préparation, 200 pas ont pris environ 2 à 3 secondes par modèle sur CPU, hors compilation. Pour l’image ondes, la MSE de validation mesurée était d’environ 0,056 avec les coordonnées brutes et 0,0030 avec les fréquences. Ces valeurs décrivent une expérience du corrigé, pas un seuil à atteindre. Commencez avec 25 pas pour mesurer le coût sur votre poste.

Expliquer ce que l’expérience démontre

Comparez les courbes, les détails reproduits et les zones encore mal ajustées. Changez ensuite un seul choix, par ex. les fréquences, et annoncez votre hypothèse avant l’essai. Le tableau de COMPTE-RENDU.md, fourni dans le projet, conserve les paramètres, les mesures et les observations. Ajoutez également un cas de test personnel : un autre rectangle pour la normalisation, ou un petit réseau dont vous pouvez calculer la sortie à la main.

Après avoir justifié le modèle retenu sur la validation, lancez :

mix picture.evaluate results/fourier-200

La commande recharge les poids, mesure le test et écrit final.svg avec tous les pixels. Retenez bien que ce sont des pixels laissés de côté dans la même image. Leur corrélation spatiale et le fait de connaître l’image originale limitent ce qu’on peut conclure sur une autre image.

Un prolongement permet d’interroger le réseau sur une grille plus fine. Il faudra alors distinguer une interpolation visuellement agréable de la récupération de détails exacts. D’autres prolongements comparent à une image constante, équilibrent les nombres de paramètres ou passent à une image RGB. Le socle reste sur une grille fixe en niveau de gris.

Le dossier results/ est ignoré par Git : copiez les expériences retenues dans rendu/ et indiquez leurs chemins dans le compte rendu.

Le projet s’inspire des Fourier features de Tancik et al., avec des fréquences fixes sur les deux axes au lieu des projections aléatoires étudiées dans l’article. La lecture de cette référence est facultative. Pour toute question, vous pouvez me contacter par email avec la commande, l’erreur et le plus petit exemple qui reproduit le souci.

Pour les curieux : le fascicule

Un petit fascicule d’approfondissement accompagne ce sujet : les idées derrière le projet, leur histoire, ce qu’elles deviennent dans les systèmes réels et quoi lire ensuite. C’est un cadeau de fin de cours ; rien n’y est à rendre.

Ouvrir le fascicule (PDF)