惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
WordPress大学
WordPress大学
博客园 - 三生石上(FineUI控件)
H
Help Net Security
小众软件
小众软件
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
Apple Machine Learning Research
Apple Machine Learning Research
S
SegmentFault 最新的问题
Last Week in AI
Last Week in AI
爱范儿
爱范儿
量子位
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
IT之家
IT之家
博客园 - 【当耐特】
V
Visual Studio Blog
大猫的无限游戏
大猫的无限游戏
博客园_首页
Jina AI
Jina AI
D
Docker
博客园 - 司徒正美
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Microsoft Security Blog
Microsoft Security Blog
阮一峰的网络日志
阮一峰的网络日志

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Embarquer un modèle ML dans une app mobile offline-first ...
Marius · 2026-04-28 · via DEV Community

Le défi

Une application mobile qui communique avec un instrument de mesure terrain — forage, géotechnique, environnement — doit collecter des signaux en temps réel, les interpréter, et restituer une information exploitable à l'opérateur. Le tout sans connexion internet, sur un smartphone ou une tablette Android durcie.

Le cas concret : des capteurs envoient des flux de données mécaniques (pression, couple, avancement, profondeur, GPS) via liaison sans fil directe. L'application doit, à partir de ces signaux bruts, produire une classification en temps réel de la nature du terrain traversé — sans cloud, sans serveur, intégralement sur le device.

Le modèle de classification (LightGBM, entraîné sur données réelles annotées) existe, mais l'intégrer dans une app React Native soulève des questions d'architecture qui vont bien au-delà du simple appel à une librairie.


L'architecture en trois couches

Le système s'organise en trois grands domaines, strictement séparés :

Couche terrain — L'instrument de mesure communique en WiFi direct avec l'application. Pas de câble, pas d'infrastructure réseau, pas de cloud. Le flux est unidirectionnel : le capteur pousse ses mesures, l'application les reçoit. Un polling à fréquence fixe garantit la fraîcheur des données sans saturer le canal.

Couche applicative — C'est le cœur du système, lui-même divisé en trois responsabilités :

  • Acquisition et restitution : l'interface utilisateur reçoit les données, les affiche en temps réel sous forme de courbes et de représentations visuelles. L'opérateur peut interagir (lancer, suspendre, arrêter une mesure) et exporter les résultats.
  • Pipeline de transformation : les données brutes ne sont pas consommables directement par le modèle. Une chaîne de transformations successives (normalisation, calibration, fenêtrage, agrégation) les convertit en vecteurs de caractéristiques numériques.
  • Inférence et agrégation : le modèle ONNX s'exécute sur le CPU du device, produit des prédictions par point de mesure, qui sont ensuite consolidées par tranche pour donner un résultat stable et exploitable.

Couche stockage — Intégralement locale. Le modèle ML est embarqué dans l'application. Les données de mesure sont persistées en fichier CSV structuré, organisé par chantier. Les métadonnées utilisateur (préférences d'affichage, configuration) utilisent le stockage clé-valeur natif.

┌─────────────────────────────────────────────┐
│  Couche terrain                             │
│  Instrument → WiFi direct → Flux mesures    │
└─────────────────────┬───────────────────────┘
                      │
┌─────────────────────▼────────────────────────┐
│  Couche applicative                          │
│                                              │
│  ┌──────────┐  ┌──────────────┐  ┌─────────┐ │
│  │Interface │  │ Pipeline de  │  │Inférence│ │
│  │temps réel│  │transformation│  │ONNX     │ │
│  │+ export  │  │+ calibration │  │+ vote   │ │
│  └──────────┘  └──────────────┘  └─────────┘ │
└─────────────────────┬────────────────────────┘
                      │
┌─────────────────────▼───────────────────────┐
│  Couche stockage                            │
│  Modèle embarqué · CSV · Préférences        │
└─────────────────────────────────────────────┘

Enter fullscreen mode Exit fullscreen mode


Les décisions d'architecture structurantes

1. Un préprocesseur 100% JavaScript

Le modèle ML attend des vecteurs de caractéristiques normalisées, pas des données brutes. La tentation aurait été d'utiliser Python (numpy, pandas) pour cette étape — puisque c'est comme ça que le modèle a été entraîné — mais Python n'existe pas sur un smartphone React Native.

Deux options :

  • Écrire un binding natif (C++/Kotlin) qui réimplémente le preprocessing
  • Tout faire en JavaScript pur

Nous avons choisi la seconde. Pourquoi ? Parce que le preprocessing est exclusivement arithmétique — médianes, quantiles, tris, statistiques par fenêtre. Pas de calcul matriciel, pas de GPU. En JavaScript pur, ce code est :

  • Testable unitairement sans infrastructure (Jest)
  • Sans dépendance externe (pas de versionnage à synchroniser avec le runtime ONNX)
  • Performant (quelques dizaines de millisecondes pour des milliers de points)
  • Portable (le même code peut tourner sur iOS si besoin)

C'est un contre-pied intéressant à la tendance "tout en natif pour la perf". Ici, le JavaScript est parfaitement adapté.

2. Une calibration par référence embarquée

Un problème classique de la classification sur signaux de capteurs : deux instruments identiques, deux jours différents, deux opérateurs différents — les valeurs brutes peuvent varier significativement, même sur le même matériau. Un modèle entraîné naïvement sur ces valeurs serait fragile et dépendant du contexte.

La solution : avant chaque campagne, une mesure de référence à vide est effectuée. Le modèle ne travaille jamais sur les valeurs brutes, mais sur les écarts à cette référence. Cette approche différentielle rend le système invariant au matériel, aux conditions climatiques, à l'usure mécanique — seul ce qui change par rapport à la référence du jour porte l'information utile.

C'est un pattern qui dépasse la géotechnique : toute application qui classifie à partir de signaux de capteurs en environnement réel devrait envisager une forme de normalisation contextuelle.

3. Une inférence qui ne bloque jamais l'interface

Le runtime ONNX (onnxruntime-react-native) présente une caractéristique d'architecture méconnue : l'appel session.run() est asynchrone côté JavaScript, mais le travail réel s'exécute sur un thread C++ natif dédié, indépendant du thread JS principal.

Concrètement :

  • Le thread JS prépare les données et déclenche l'appel
  • Le runtime C++ crée un thread secondaire, y exécute l'inférence, et retourne le résultat via une Promise
  • Pendant ce temps, le thread JS continue de gérer l'interface, le polling, les animations

C'est exactement ce qu'on veut pour une application temps réel : l'inférence — même sur des lots de plusieurs milliers de points — ne provoque aucune latence perceptible sur l'interface.

4. Une agrégation pour lisser le bruit

Le modèle a été entraîné sur des points de mesure individuels. Inférer point par point produirait des prédictions bruttées et instables. La solution : inférer sur tous les points d'une tranche de profondeur, puis voter.

Le mécanisme est simple : chaque point vote pour une classe, la classe majoritaire l'emporte. En cas d'égalité, c'est la confiance moyenne qui départage. Ce vote majoritaire lisse les points aberrants et ne retient que le comportement dominant de la tranche.

C'est un pattern qu'on retrouve dans beaucoup de systèmes de classification embarquée : plutôt que de chercher à améliorer la précision point-par-point (ce qui serait coûteux en données d'entraînement), on exploite la redondance naturelle des mesures pour gagner en robustesse.

5. Un pipeline temps réel idempotent

En mode temps réel, les données arrivent par lots successifs (polling). Le défi : ne pas re-inférer des tranches déjà traitées, et détecter le moment où une tranche est complète.

L'architecture retenue est un singleton avec état qui :

  • S'initialise avec les données de calibration (une fois)
  • Ingère les nouveaux points à chaque cycle de polling
  • Déclenche l'inférence uniquement quand une tranche est complète (dès qu'un point de la tranche suivante est reçu)
  • Garantit l'idempotence : si les mêmes données sont passées deux fois, elles sont ignorées

Le cycle de vie est simple : init()process() (en boucle) → reset(). Pas de gestion d'état complexe, pas de machine à états. Un singleton, un Set pour l'idempotence, et une règle de complétion de tranche.


Ce qu'on a appris

L'embarqué a ses propres règles

Ce qui marche en backend (inférer par lots, tout charger en mémoire, tolérer 500ms de latence) ne marche pas sur un appareil mobile terrain. Les contraintes sont différentes :

  • RAM limitée : pas question de charger tout le dataset en mémoire
  • CPU partagé : le thread JS fait tourner le polling, le rendu, les animations ET le preprocessing
  • Pas de fallback cloud : si le modèle est corrompu ou absent, l'app doit le détecter et le signaler sans crasher

Le format de sortie du modèle est un maillon faible

Les modèles exportés en ONNX peuvent avoir des formats de sortie différents selon la version de l'exportateur : int64, Int32, string, dictionnaire — parfois selon l'humeur du pipeline. Si l'application suppose un format et reçoit l'autre, le résultat est une erreur silencieuse ou un crash.

La leçon : toujours parser les sorties de manière défensive. Vérifier le type, prévoir un fallback, logger l'écart. Une prédiction fausse vaut mieux qu'un crash — surtout sur un chantier où redémarrer l'app signifie perdre du temps.

La validation croisée Python/JS est indispensable

Le preprocessing a été développé et testé en Python (numpy, pandas) pour l'entraînement, puis réécrit en JavaScript pour l'embarqué. Sans une validation que les deux implémentations produisent exactement les mêmes résultats — bit pour bit — sur les mêmes données, on ne peut pas avoir confiance dans les prédictions.

La méthode : un jeu de données de référence, deux exécutables standalone (Python et JS), et une batterie de tests qui compare les sorties avec une tolérance au bruit Float32. Cette approche devrait être systématique dès qu'on porte un pipeline ML d'un environnement de recherche vers un environnement de production.

La calibration, pas la donnée brute

La décision la plus importante du projet n'est pas technique mais méthodologique : ne jamais travailler sur les valeurs brutes, toujours sur des écarts normalisés à une référence contextuelle. C'est ce qui rend le système robuste en conditions réelles, sur du matériel variable, dans des environnements non contrôlés.


Pour conclure

Ce projet illustre un constat qui devient difficile à ignorer : les modèles ML ne sont plus cantonnés aux serveurs. On peut — et on doit — les embarquer sur les appareils terrain, là où les données sont produites, là où la décision doit être prise.

Mais embarquer un modèle ne se résume pas à appeler une librairie. C'est repenser toute l'architecture : comment normaliser les entrées sans Python, comment garantir la fiabilité sans serveur, comment valider la cohérence entre l'entraînement et l'inférence, comment faire tourner le tout sur un smartphone sous la pluie, avec des gants, à côté d'une foreuse qui vibre.

L'edge AI, en 2025, ce n'est plus un laboratoire. C'est un chantier.