Il y a 6 ans -
Temps de lecture 19 minutes
On-device Intelligence : intégrez du Deep Learning sur vos smartphones
Si l’innovation en matière d’usages commençait ces derniers temps à baisser de rythme pour les applications sur smartphones, les récentes avancées dans le domaine du Deep Learning ouvrent la porte à de nouvelles opportunités. De la reconnaissance d’images au traitement et à la génération automatique de son ou de texte, ces avancées techniques et technologiques viennent aujourd’hui révolutionner l’usage de vos smartphones : il devient possible d’embarquer de puissants algorithmes au sein même du smartphone et de les utiliser dans des applications novatrices. Au cours de cet article, nous allons vous montrer comment faire chauffer les neurones de votre smartphone avec du Deep Learning !
On-device intelligence, kezako ?
Avant de parler implémentation, définissons ensemble ce qu’est la notion de « on-device intelligence ». Un exemple qui illustre très bien le concept est le « Smart Reply » sur Android Wear 2.0 : grâce à un modèle de conversation déjà entraîné et enregistré sur le smartphone, ce dernier devient alors capable de prédire la réponse à apporter à un message reçu. Tout cela est fait « on-device », c’est à dire que toute l’intelligence est transmise sur le smartphone, sans avoir à faire aucun appel à une plateforme cloud hébergeant un algorithme ni en exposant sa donnée à l’extérieur.
Le terme « On-device intelligence » est apparu pour la première fois lors de la WWDC de 2016, lors de laquelle Apple a notamment annoncé une nouvelle fonctionnalité dans l’application de photographie de iOS, permettant par exemple de regrouper les photos prises selon les visages ou des thèmes. Le tout étant bien entendu fait directement sur le smartphone iOS.
Autre avancée récente, avec la sortie du Samsung S8, qui est le premier smartphone à incorporer un processeur SnapDragon 835 (CPU de 10 nanomètres) intégrant de plus un Hexagon 682 DSP (Digital Signal Processor). Le framework de Deep Learning Tensorflow, que nous allons grandement utiliser par la suite, est maintenant optimisé pour ces types de processeurs, donnant des temps de réponse jusqu’à 15 fois plus rapides que sur des CPU normaux.
Avec toutes ces révolutions en cours, il devient possible de traiter des données fondamentalement différentes pour les algorithmes de Machine Learning. L’objectif ultime du « on-device intelligence » est donc d’améliorer les capacités des smartphones mobiles à percevoir le monde physique. En résumé, nous devenons capables de donner des yeux et des oreilles à nos smartphones, pour qu’ils n’aient pas besoin de requêter des bases ou algorithmes externes pour traiter une information.
Tout cela nous a forcément donné envie de nous lancer dans cette aventure entre Xebians, et de cette réflexion est née la volonté de créer une application mobile ludique basée sur de la reconnaissance d’image, que nous avons joliment nommé Magritte.
Magritte : ceci n’est pas une pomme
Il est toujours difficile d’apprendre une langue étrangère, et même de prononcer le nom des objets les plus basiques lorsque l’on est dans un pays étranger. L’idée de l’application Magritte est de proposer à l’utilisateur de choisir sa langue désirée, et, par un système de niveaux à débloquer, d’apprendre à prononcer les objets qu’il voit dans son environnement quotidien. En prenant par exemple en photo une pomme, l’application fait appel à un algorithme de Deep Learning pour la reconnaissance d’images, et détecte alors que cette photo appartient à la catégorie « pomme ». Une fois cette détection faite, une dernière étape de « text to speech » est utilisée pour que le smartphone puisse prononcer la catégorie détectée dans la langue choisie.
L’application est composée de 4 écrans principaux :
-
L’écran d’accueil de l’application
-
Le choix de la langue à apprendre
-
Le choix du type de vocabulaire que l’utilisateur souhaite apprendre. Les niveaux supérieurs restent bloqués tant que le niveau d’avant n’a pas été complété
-
La reconnaissance d’images à proprement parler. Lorsqu’un objet est reconnu, son pictogramme apparaît dans la zone sombre dédiée.
La construction de cette application nous a permis de faire face aux différentes problématiques liées à l’intégration de modèles de Deep Learning sur un smartphone, que nous allons vous présenter dans la suite de cet article.
Tester la démonstration de TensorFlow sur Android
Nombreux sont les challenges qui nous font face lorsqu’il s’agit d’intégrer des modèles de Deep Learning sur un smartphone. Heureusement pour nous, une démonstration est disponible via le framework TensorFlow, qui nous permet de prototyper rapidement une application simpliste intégrant un modèle de reconnaissance d’images.
TensorFlow est un framework de programmation pour le calcul numérique qui a été rendu Open Source par Google en Novembre 2015. Depuis sa release, il n’a cessé de gagner en popularité, pour devenir très rapidement l’un des frameworks les plus utilisés pour le Deep Learning. En plus de toutes les optimisations derrière ce framework, sa documentation et ses tutoriels sont de vrais atouts pour une adoption simple. Des articles ont été rédigés sur notre blog pour apprendre à l’utiliser.
Nous avons utilisé le projet de classification d’images proposé parmis les trois exemples d’applications de TensorFlow sur Android (Classify, Detect & Stylize). L’application utilise un modèle déjà pré-entraîné, inception, permettant de reconnaître 1000 classes distinctes d’images, provenant du dataset ImageNet. Il est important de noter qu’aucune phase d’entraînement de modèle n’est faite sur le smartphone : ce dernier n’utilise que des modèles déjà entraînés sur des machines plus puissantes.
Le build de l’application nécessite l’utilisation de Bazel, l’outil utilisé en interne par Google. Après une vingtaine de minutes d’attente, notre première version de démonstration est disponible ! Elle nous permet de classifier quelques objets présents autour de nous (nous ne débattrons pas autour des objets présents autour de nous lors de cette séance de code…).
Maintenant que la version de démonstration est installée avec succès, nous souhaitons remplacer le modèle utilisé avec notre propre version pour répondre à notre cas d’utilisation. Vous pensez avoir fait le plus dur ? Attendez la suite !
Entraîner son propre modèle
Suite à cette mise en bouche grâce à l’application de démonstration, le champ des possibles nous semblait infini : nous voyons des neurones partout et pensions pouvoir reconnaître le monde entier !
Distinguer l’inférence de l’entraînement
Une petite explication s’impose. Au sein de l’application mobile, le réseau de neurones n’est utilisé que pour faire la prédiction (on lui fournit une image et le modèle prédit la classe associée) : c’est ce qu’on appelle l’étape d’inférence. Afin d’avoir un modèle capable de classifier les images qui vous intéresse, il faut être capable en amont de l’entraîner sur un dataset correspondant à votre cas d’utilisation.
L’entraînement d’un modèle se fait généralement via un algorithme nommé backpropagation. Le réseau de neurones est initialisé avec des poids aléatoires. On lui fournit alors en entrée des images et on applique l’étape d’inférence, comme si le modèle était déjà entraîné. Bien évidemment, les prédictions associées seront erronées. Mais dans la mesure où nous connaissons la vraie classe, nous pouvons avoir une mesure de l’erreur obtenue. A partir de cette erreur, les poids du réseau de neurones sont mis à jour de manière rétrograde : d’abord ceux de la dernière couche, puis ceux de la couche précédente et ainsi de suite. Au fur et à mesure, les prédictions faites convergeront vers les bonnes prédictions et l’on aura alors terminé la phase d’entraînement.
Jetons un oeil à ce fameux modèle inception, celui utilisé dans la démonstration de TensorFlow. Nous pourrons alors nous inspirer de son architecture pour construire notre propre réseau de neurones pour reconnaître nos fruits, légumes et autres objets du quotidien.
Wow ! Voilà un réseau de neurones profond ! Le modèle proposé par Google est un Deep Convolutional Neural Network. Il est constitué de nombreuses couches cachées composées d’étapes de convolution (très utilisées dans la reconnaissance d’images). Ce modèle a été entraîné durant de nombreuses heures sur des clusters de GPU ultra-performants sur plusieurs millions d’images labellisées.
À ce moment, on peut commencer à prendre peur devant la tâche qui nous attend pour entraîner notre propre réseau de neurones. Nous n’avons pas autant d’images sous la main, et, surtout, pas autant de puissance de calcul pour répondre à cette problématique ! Nous pourrions entraîner un modèle plus petit, mais au risque de dégrader les performances… Que faire alors ? C’est là que le Transfer Learning entre en scène.
Le Transfer Learning pour tirer parti des modèles déjà entraînés
L’idée du Transfer Learning est la suivante : j’ai à ma disposition un modèle profond qui a été entraîné durant des jours sur des millions d’images afin de répondre à un cas d’utilisation précis. Mon besoin ici est similaire à celui de ce modèle, mais ne traite pas les mêmes classes d’objets à détecter. Pourquoi ne pas tirer parti de ce qui a été appris par ce modèle, et le spécialiser sur mon propre cas d’utilisation ?
Sur des sujets traitant de reconnaissance d’images, les différentes couches intermédiaires des réseaux de neurones apprennent à reconnaître des traits et courbes, puis des formes de plus en plus évoluées. Ces formes se retrouvent sur la majorité des images. La couche finale prend en entrée la sortie de tous les filtres de formes qui ont été appris, et leur associe un poids permettant de révéler la présence ou l’absence de telle ou telle classe sur l’image. Étant donné que ces formes semblent universelles et que c’est bien l’association de ces formes apprises qui permet de faire des prédictions, on peut très bien s’en servir comme point d’entrée pour reconnaître nos propres classes.
Ainsi, grâce à du Transfer Learning, nous allons conserver les poids appris du réseau de neurones sur toutes ses couches, sauf la dernière (c’est ici que se passe le transfert de connaissances). Tout ce que nous aurons à entraîner, ce sont les poids de cette dernière couche du modèle, celle qui va permettre de reconnaître nos propres classes. La tâche est tout de suite beaucoup moins ardue. Pour une image donnée, le réseau de neurones est traversé dans sa quasi-totalité et seuls les poids de la dernière couche vont être mis à jour lors de l’apprentissage, les poids des couches intermédiaires restant constants. Nous pouvons ainsi tirer parti des filtres qui ont été appris après de longues heures par le réseau de neurones initial, pour enfin le spécialiser à notre tâche en ré-entraînant la dernière couche.
TensorFlow propose des tutoriels très ludiques pour se familiariser avec le Transfer Learning.
Une fois le modèle entraîné, il ne reste plus qu’à le sérialiser pour l’utiliser ultérieurement. Deux fichiers sont sauvegardés : un fichier au format protobuf contenant le modèle à proprement parler, ainsi qu’un fichier texte contenant le nom des classes qui sont à prédire afin de faire la correspondance.
Intégrer son modèle dans l’application
Le plus dur semble derrière nous. Il ne nous reste plus qu’à intégrer le modèle fraîchement entraîné dans l’application. Mais tout n’est pas si simple, et déjà notre première exception est levée :
java.lang.UnsupportedOperationException: OpBatchNormWithGlobalNormalization is not available in GraphDef version 21.
Ce type d’exception est révélateur de la présence d’opérations non supportées par la version de TensorFlow sur le smartphone. Mais ces opérations sont-elles vraiment utiles pour notre usage sur smartphone ? TensorFlow fonctionne via des graphes d’exécution : l’utilisateur instancie toutes les opérations nécessaires à la création et à l’entraînement de son réseau de neurones, puis, à l’ouverture d’une session et à l’appel des opérations avec des données d’entrée, il va générer un graphe d’exécution optimal.
Mais pour pouvoir entraîner notre réseau de neurones, il nous a fallu utiliser des opérations spécifiques permettant la mise à jour les poids du modèle pour minimiser les erreurs. Ces opérations relèvent uniquement de l’étape d’entraînement du modèle. Lors de l’étape d’inférence (donc de prédiction), seuls les poids permettant de passer des neurones d’entrée aux classes de sortie sont nécessaires. Nous pouvons donc nous permettre, une fois l’entraînement terminé, de supprimer les opérations inutiles pour l’inférence. Ce sont ces opérations qui ne sont pas supportées sur le smartphone.
Construire une application standalone
Nous avons à ce moment décidé de migrer vers notre propre application Android. Pour cela, nous avons utilisé les nightly builds de TensorFlow directement au sein d’une application développée from scratch. Ce changement d’organisation du code nous a aussi permis d’être plus flexibles sur le design de l’application.
Depuis Google I/O, l’interface inférence de TensorFlow pour Android est disponible sur JCenter. Les développeurs peuvent désormais inclure la dépendance directement dans le fichier build gradle comme pour toutes les autres applications Android et profiter de la puissance de TensorFlow dans leur application mobile.
L’architecture de notre application ressemble à ceci :
- L’application récupère les images depuis le preview de la caméra et les envoie vers TensorFlow
- Une fois le traitement terminé, l’application affiche les résultats obtenus
Quelques pré-traitements ont de plus été nécessaires afin de faire des prédictions performantes sur notre smartphone :
-
« Sampling » de l’image d’entrée : le modèle ayant été entraîné sur des images de taille fixe, il a fallu ajuster l’image obtenue par le flux de caméra du smartphone à la taille requise via des opérations de « cropping » et « resizing ».
-
Conversion de l’image YUV vers RGB : par défaut, le système d’encodage des couleurs sur Android est YUV, alors que le modèle de reconnaissance d’image a été entraîné sur du RGB. Il est alors indispensable de faire cette conversion en amont afin de ne pas avoir des prédictions complètement erronées.
-
Création du tenseur d’entrée : TensorFlow fonctionnant, comme son nom l’indique, avec la notion de tenseur (Tensor en anglais), il a fallu convertir l’image d’entrée en un tenseur exploitable par le modèle.
Nous voici maintenant avec une toute nouvelle application de reconnaissance d’images développée par nos soins ! Mais, même si les résultats sont déjà satisfaisants, les problématiques ne s’arrêtent pas là.
Une contrainte en mobilité : le poids de l’application
Une fois l’application installée, un « léger » détail s’est porté à notre attention : le poids de l’application était de plus de 80 MB !
Bien que pour un Data Scientist, cette remarque n’est en général qu’un détail, pour un développeur Android, c’est un élément de grande importance. Même si en soi, une application mobile peut faire ce poids, cela devient en revanche très contraignant pour faire des redéploiements ou pour inclure plusieurs modèles dans l’application. Réfléchissons donc à quoi est dû ce poids extravagant de l’application.
Si l’on reprend le schéma de l’architecture du modèle inception, on comprend très vite d’où vient le problème : plusieurs dizaines de couches intermédiaires, composées chacune de plusieurs centaines voir plusieurs milliers de paramètres, cela fait une quantité impressionnante de paramètres, qui devront tous être sauvegardés pour faire les prédictions. Quasiment chaque paramètre ayant une valeur unique, la quantité d’information à sauvegarder est considérable, d’où le poids du modèle et par conséquent de l’application. D’autant plus que le format de stockage est en float de 64 bits.
Que faire alors ? Il faut se poser la question suivante : est-ce vraiment nécessaire de sauvegarder les poids avec une telle précision ? Si je les arrondissais, les performances seraient-elles dégradées ? La réponse dans les deux cas est en géréral non, et c’est l’idée derrière la quantification des poids du modèle. Les poids de chaque couche vont être quantifiés afin d’avoir un nombre restreint de valeurs à sauvegarder. La bonne nouvelle est que cette technique ne dégrade que très peu voire pas du tout les performances du modèle, elle peut donc en général être utilisée sans crainte. Cette restriction du nombre de valeurs à sauvegarder a un effet énorme sur le poids du modèle : il passe de 80 MB à 20 MB, ce qui devient un poids tout à fait raisonnable pour une application mobile.
Il nous est alors possible d’entraîner notre propre modèle pour reconnaître des fruits et le déployer sur l’application. Nous avons notre premier niveau ! Mais que se passe-t-il si je souhaite créer un deuxième niveau, celui des légumes ? Vais-je devoir sauvegarder un deuxième modèle et donc doubler le poids de mon application ? Cette solution n’est bien évidemment pas scalable, et il faut réfléchir à une meilleure idée.
C’est là que la puissance de TensorFlow entre en jeu. Pour les deux niveaux, nous allons vouloir utiliser du Transfer Learning pour spécialiser le modèle inception à notre usage. Cela veut dire que, pour les deux niveaux, la quasi-totalité du réseau de neurones reste intacte, et seule la dernière couche est à retravailler. Le fonctionnement sous forme de graphe de TensorFlow va alors nous permettre de sauvegarder les opérations nécessaires à l’étape de prédiction du modèle de fruits ainsi que celles nécessaires du modèle de légumes au sein du même graphe. On ne sauvegarde ainsi qu’une seule fois les poids des étapes intermédiaires du modèle, associés aux poids spécifiques aux différents niveaux pour la prédiction, qui sont distincts.
Et le tour est joué ! L’ajout d’autres niveaux n’altère alors quasiment pas le poids de l’application, étant donné que ce ne sont que quelques centaines de poids supplémentaires à sauvegarder. Notre application est fin prête, il est grand temps d’en faire une démonstration !
De belles aventures sont encore à venir
Au cours de cet article, nous vous avons proposé un retour d’expérience sur l’intégration d’un modèle de Deep Learning sur un smartphone. Nous avons pu voir toutes les étapes nécessaires à la bonne réalisation d’un tel projet, ainsi que les difficultés que nous avons rencontré. Le résultat est plus que satisfaisant, et ouvre la porte à de nombreux sujets autour du « on-device intelligence ».
Voici quelques challenges sur lesquels nous allons nous pencher dans un futur proche pour améliorer l’application et investiguer plus en détails ce domaine :
-
Peut-on réduire encore plus le poids des modèles sérialisés ? L’utilisation de versions binaires du modèle semble laisser supposer que oui.
-
Google a récemment annoncé la création de l’API Android NN ainsi que de TensorFlow Lite afin de faciliter encore plus l’intégration et l’utilisation de modèles TensorFlow sur les smartphones mobiles
-
Mise à disposition d’une nouvelle version d’un modèle sur les applications des utilisateurs via un pull depuis une plateforme cloud, où comment gérer le déploiement de nouvelle versions du modèle entraîné de manière transparente pour les utilisateurs.
-
Federated Learning : cette idée issue d’un papier de recherche de Google a pour but de permettre d’utiliser les smartphones mobiles des utilisateurs pour continuer d’entraîner les modèles utilisés. L’utilisateur, via son smartphone, va permettre au modèle de s’améliorer, et ce sont uniquement les updates des poids du réseau de neurones qui seront communiqués à l’extérieur (les données personnelles de l’utilisateur restent donc protégées). La centralisation de tous ces updates permettrait alors de mettre à jour plus efficacement un modèle global.
De beaux projets sont encore à venir sur le sujet, et nous n’avons pas fini d’entendre parler de ces applications plus intelligentes que jamais qui utilisent des modèles sans utiliser de requêtes vers des plateformes extérieures et qui respectent la privacité de nos données.
Le code source du projet Magritte est disponible sur le github de Xebia France.
Commentaire