7.2. Ce que le ML a changé

Le module image embarque une poignée de méthodes de détection héritéesfind_features() pour la détection de visages par cascade de Haar, find_eye() pour le détecteur de pupille fixe, find_hog() pour les résumés de direction de gradient, ainsi que les chemins find_keypoints() et find_lbp() pour les points clés arbitraires. Toutes fonctionnent encore ; toutes ont été supplantées par le pipeline d’apprentissage automatique.

7.2.1. La séparation classique : résumés conçus à la main, décisions apprises

Un pipeline de vision classique était une chose en deux étapes. La première étape transformait les pixels bruts en un ensemble compact de nombres choisis pour résumer ce que contenait l’image – non pas les valeurs de pixels elles-mêmes, mais une description plus courte indiquant quels motifs apparaissaient où. La deuxième étape prenait ce résumé et prenait une décision : visage ou non, tel objet ou tel autre, même cible ou différente.

La séparation importait parce que les deux étapes avaient des auteurs différents. La première étape était écrite par un humain. Quelqu’un s’asseyait et décidait que la différence de luminosité entre deux rectangles spécifiques était un bon résumé d’une région d’œil, que la direction de contour (arête) dominante dans chaque cellule d’une grille était un bon résumé de la silhouette d’une personne debout, que le motif clair-ou-sombre autour de chaque pixel était un bon résumé de la texture locale. Chacun de ces choix était un algorithme écrit à la main – écrit, débogué et publié. Les méthodes héritées ci-dessus étaient toutes des résumés de ce genre devenus des outils standard :

  • find_features() résume une fenêtre de l’image en additionnant la luminosité à l’intérieur de plusieurs rectangles et en comparant les totaux. Les dispositions de rectangles ont été choisies parce que les visages humains présentent des contrastes clair-contre-sombre fiables : les sourcils contre les joues, les orbites contre le front, le nez contre la peau environnante.

  • find_hog() résume une image en parcourant une grille de petites cellules et en enregistrant quelle direction de contour (arête) domine dans chaque cellule. La grille a été choisie parce que la silhouette d’une personne debout produit un motif reconnaissable de directions de contour quel que soit l’habillement ou l’éclairage.

  • find_lbp() résume le voisinage de chaque pixel en encodant lesquels des pixels environnants sont plus clairs et lesquels sont plus sombres. L’encodage a été choisi parce que ces motifs plus-clair-que / plus-sombre-que capturent la texture d’une surface indépendamment de l’éclairage global.

  • find_keypoints() trouve des points de coin dans l’image et décrit la zone autour de chaque coin d’une manière qui reste identique lorsque le coin est tourné. Le schéma coin-et-rotation a été choisi parce que les mêmes coins réapparaissent lorsqu’une scène est vue sous un angle différent.

Une fois un résumé écrit à la main, une petite étape d”apprentissage par-dessus pouvait combiner les nombres en une décision. L’algorithme de détection de visages greffait une étape d’apprentissage sur le résumé de différence de rectangles, en l’entraînant sur des images étiquetées de visage et de non-visage pour apprendre quelles combinaisons de différences signalent un visage. Le résumé de direction de contour alimentait une étape d’apprentissage entraînée sur des images étiquetées de personne et de non-personne. Les descripteurs de coin alimentaient une étape de mise en correspondance qui apprenait quel poids accorder à chaque coin. Chacune de ces deuxièmes étapes est un algorithme d’apprentissage – petit selon les normes modernes, mais un algorithme d’apprentissage.

La répartition des contributions est ce qui importait. L’humain apportait le résumé. La machine apprenait la combinaison. Ajouter une nouvelle cible signifiait écrire un nouveau résumé.

7.2.2. Ce que les réseaux de neurones ont changé

Un réseau de neurones efface la séparation. Les premières couches du réseau effectuent le travail de résumé que les algorithmes écrits à la main faisaient autrefois – détecter des contours (arêtes), des coins, des barres orientées, des textures, exactement les choses que chacune des méthodes héritées énumérées ci-dessus était réglée pour détecter – mais elles ne sont pas écrites à la main. Elles sont apprises à partir des mêmes données d’entraînement que l’étape de décision, en une seule passe d’entraînement qui ajuste les deux moitiés du réseau à la fois. Les couches plus profondes effectuent la combinaison que la petite étape d’apprentissage par-dessus les résumés écrits à la main faisait autrefois, également apprise, dans la même passe.

Le changement dans qui conçoit quoi est total :

  • L’humain conçoit l”entrée – des trames capturées d’une taille et d’un format donnés.

  • L’humain conçoit la sortie – la disposition du tenseur de résultat (un score par classe pour la classification, une liste de boîtes pour la détection, une grille de points clés pour les repères).

  • L’humain fournit les données d’entraînement étiquetées – suffisamment d’exemples de la cible et suffisamment d’exemples de non-cibles pour que le processus d’entraînement ait de quoi apprendre.

Tout ce qui se trouve entre l’entrée et la sortie est généré par le processus d’entraînement. Il n’y a pas d’étape distincte d’écriture de résumé. Les premières couches se transforment en détecteurs de contours et de textures non pas parce que quelqu’un les a écrites ainsi, mais parce que la détection de contours et de textures est ce qui fait correspondre les prédictions du réseau aux étiquettes. Les couches plus profondes se transforment en détecteurs de formes et d’objets pour la même raison. Les deux moitiés sont entraînées ensemble, ce qui permet aux résumés produits par chaque couche d’être exactement les résumés dont la couche suivante a besoin – et non pas les résumés génériques auxquels un pipeline écrit à la main devait se contenter.

7.2.3. Composer avec le module image

Les pipelines de réseaux de neurones capturent toujours via les mêmes API de capteur, dessinent les résultats via les mêmes primitives draw_rectangle() et draw_circle(), et délimitent le travail via les mêmes ROI (x, y, w, h). Un pipeline typique capture une trame, trouve éventuellement une cible grossière avec un détecteur classique comme find_blobs() et transmet sa boîte englobante à l’inférence en tant que ROI, exécute l’inférence et annote les détections renvoyées dans la trame d’origine. Les primitives classiques sont le substrat ; le réseau est la nouvelle étape au milieu.