Piloter un robot
avec une Tsetlin Machine,
sans GPU et hors ligne

Moondream labellise le sol praticable, la Tsetlin Machine apprend et délimite les zones où un robot peut rouler — un pipeline sans cloud à l'inférence : la Tsetlin Machine tourne sur un PC local sans GPU et pilote un robot ESP32 + Raspberry Pi.

Projet : Moondream • GroundTM • Tsetlin.jl • ESP32 • Raspberry Pi


Un robot qui doit voir où il roule

Un robot roulant — une plateforme ESP32 avec un Raspberry Pi et une caméra — doit savoir où il peut avancer. Le capteur principal, c'est la caméra. Depuis le châssis, elle capture des photos en 320×240 pixels de la zone devant le robot. Le problème revient à classer chaque portion de l'image en deux catégories : roulable ou bloqué.

Une Tsetlin Machine est bien adaptée à ce besoin : frugale en mémoire, déterministe, interprétable. Mais il faut d'abord l'entraîner — et pour ça, il faut des données labellisées. C'est là que Moondream entre en jeu.

Frugale

Modèle de 59 Ko, une image classée en 6 ms (médiane) sur un PC de bureau, sans GPU.

Déterministe

Mêmes entrées → mêmes décisions, contrairement aux VLM à formulations variables.

Interprétable

Règles booléennes lisibles : la décision se décompose en ET/NON.


Moondream labellise le sol roulable

Moondream est un VLM (Vision-Language Model) capable de segmenter une image à partir d'une description en langage naturel. On l'utilise via l'API cloud (api.moondream.ai/v1/segment, modèle moondream3.1-9B-A2B) — une seule fois, en amont, pour labelliser un jeu de photos de terrain.

Le prompt qui définit ce qu'est une surface roulable :

"floor surface, walkable ground"

Moondream répond avec un masque de segmentation : un chemin SVG qui délimite les zones praticables dans l'image. Le script label_ground_moondream.py prend ce masque, le rastérise, et le fusionne en une grille de labels :

  • 1 → traversable (sol roulable)
  • 2 → bloqué (tout ce qui ne l'est pas — mur, rocher, plante, ciel)

L'image source (320×240 px) est redimensionnée par box-average en une grille de 64×24, soit 1 536 cellules, et chaque cellule reçoit un label. Une seule requête segment par photo, une seule clé API.

Script de labellisation
python scripts/label_ground_moondream.py viz/pipeline/robot_cam.jpg
→ viz/moondream/robot_cam_32x24.ppm      # features visuelles 32×24
→ viz/moondream/robot_cam_labels.pgm     # labels 1 (roulable) / 2 (bloqué)

Entraînement de la Tsetlin Machine

Les labels Moondream deviennent les règles de la TM.

Les labels Moondream alimentent le pipeline d'entraînement sur un serveur Julia. Le module GroundTM implémente la version de la Tsetlin Machine spécialisée pour la classification pixel par pixel :

  1. Binarisation — Chaque pixel RVB (et ses voisins, définis par un radius) est transformé en un vecteur de bits. Le module GroundBinarization traduit intensité, différence et motifs locaux en booléens.
  2. Entraînement — Le classifieur TMClassifier (de Tsetlin.jl) apprend à associer chaque vecteur de bits à son label. Hyperparamètres : 160 clauses (80 par classe, 40 positives et 40 négatives), marge de vote T = 80, valeurs de mémorisation S = 20 et d'oubli L = 80.
  3. Export — Une fois entraîné, le modèle est compilé avec export_tm en un fichier .tmbin portable de 59 Ko.

La boucle itérative

Corriger les erreurs de l'un par l'autre.

Le premier entraînement produit rarement un modèle parfait. On fait inférer la TM sur les photos d'entraînement, on isole les désaccords, on re-soumet ces cas ambigus à Moondream pour vérifier ou corriger le label — et on ré-entraîne.

Après quelques itérations, la TM atteint 85,5 % de précision sur les photos de validation (meilleure époque ; détail dans la section Performances), avec l'avantage du déterminisme pur — pas de variation entre deux formulations possibles.


Inférence et navigation

Le robot voit la route.

À l'inférence, le Raspberry Pi du robot capture une image et l'envoie par WiFi local à un PC (AMD Ryzen 5 5600G, sans GPU), où la Tsetlin Machine tourne en Julia. Le PC renvoie la grille au robot. Le pipeline est le même qu'à l'entraînement : décodage JPEG → redimensionnement → binarisation → classification TM. Le résultat est une grille de décision — chaque cellule reçoit la classe traversable (1) ou blocked (2).

L'image 320×240 est classée en une grille de cellules. Pour la navigation temps réel, le module AutoPilot.Policy ne considère que la moitié inférieure — celle qui correspond au sol devant le robot jusqu'à l'horizon.

Pipeline inférence : image JPEG → décodage → redimensionnement box-average → binarisation GroundTM → TM predict → grille de décision.

Architecture actuelle : Raspberry Pi = caméra et pilotage ; ESP32 = moteurs et centrale inertielle (IMU) ; PC local = inférence Julia, sans GPU ni connexion internet. Rien ne tourne encore en embarqué sur le Pi : y faire tourner le modèle de 59 Ko est une prochaine étape possible.



Performances

En clair : l'entraînement prend quelques minutes sur un PC de bureau sans GPU, une décision prend quelques millisecondes, et le modèle pèse 59 Ko.

Mesures du projet : données, entraînement, précision, taille du modèle et temps de classification
Modèle160 clauses (80 par classe : 40 positives, 40 négatives)T = 80, S = 20, L = 80
Photos étiquetées876, dont 841 utilisables
Cellules d'entraînement1 244 950rééquilibrées : 622 475 par classe
Cellules d'évaluation247 29620 % des photos
Préparation des données11 s115 s avant optimisation
Une époque d'entraînement36 s51 s avec l'ancienne version de Tsetlin.jl (−29 %)
Entraînement complet (10 époques)environ 6 minutes467 s mesurées, étiquetage Moondream de 47 nouvelles photos compris ; l'apprentissage domine : ~360 s sur ~390
Précision sur les photos de validation85,5 %meilleure époque ; 78 à 85 % selon les époques
« Faux libre » (obstacle vu comme du sol)4,24 ± 0,43 %quand les « faux bloqué » sont fixés à 8 %
Taille du modèle59 Ko (.tmbin).tm avec états d'apprentissage : 524 Ko
Classer une image sur le PC6 ms en médiane, 7 ms au 90e centileun seul cœur ; JPEG → grille de 1 536 cellules
Raspberry Pi Zero (ancien essai)226 ms par imageNIF Rust (tsetlin_runner), ancienne grille 32×24 et ancien modèle ; non remesuré, le Pi ne classe plus

Méthode : le taux de « faux libre » est mesuré sur 5 entraînements, avec des jeux de photos séparés (630 pour l'entraînement, 98 pour la validation, 113 pour le test).

Source des chiffres : journal de mesures du projet (docs/journal.md, tableau de bilan et sections 4, 7 et 11). PC de mesure : AMD Ryzen 5 5600G, sans GPU.


Résultats

Moondream n'intervient qu'à l'entraînement. À l'inférence, la Tsetlin Machine classe chaque image en 6 ms (médiane) sur le PC local — sans cloud, sans GPU, sans appel API.

Taille

Modèle .tmbin : 59 Ko (524 Ko avec les états d'apprentissage).

Latence

6 ms par image en médiane, 7 ms au 90e centile, sur un seul cœur du PC.

Matériel

Raspberry Pi (caméra, pilotage) + ESP32 (moteurs, IMU), inférence sur un PC local sans GPU, sans cloud à l'inférence.

Déterministe

Pas de variation de formulation — la TM prend toujours la même décision pour la même entrée.

Le corridor le plus praticable devient la prochaine commande moteur, et le robot roule.

Vous voulez en savoir plus ?

Ce pipeline fait partie de mon travail sur l'IA locale et frugale. La bibliothèque Tsetlin.jl utilisée ici est open source sur GitHub.