De Python à C#: le transfert de modèles sous format ONNX

Python s’est imposé comme le langage de référence dans la communauté des chercheurs, ingénieurs, et praticiens de l’Intelligence Artificielle et les implémentations les plus complètes des frameworks de ML les plus populaires, comme PyTorch ou Tensorflow, sont aujourd’hui écrites dans ce langage. Comment faire pour pouvoir utiliser nativement vos modèles d’IA dans une application écrite dans un autre langage ? Le format ONNX répond à cette problématique.

Voyons comment avec ce tutoriel !

Qu’est-ce qu’ONNX ?

ONNX signifie Open Neural Network Exchange. Il s’agit d’un projet open-source dont le but est de faciliter l’interopérabilité entre des systèmes de machine learning, quel que soit le langage dans lequel ils ont été écrits. Vous pouvez trouver la page GitHub du projet ici. Le format est progressivement devenu un standard dans l’industrie.

Les entreprises soutenant le projet ONNX

En plus de permettre la migration d’un modèle d’IA d’une plateforme à une autre, ONNX facilite l’optimisation de ces mêmes modèles grâce à son convertisseur qui transforme les modèles en un format standardisé, tout en intégrant des optimisations de performance. Ces optimisations peuvent inclure la fusion de couches de neurones, l’élimination de noeuds redondants, ou encore la simplification des opérations mathématiques

Ces optimisations permettent d’accélérer les temps d’exécution et de réduire l’empreinte mémoire des modèles. Cela est particulièrement bénéfique lors du déploiement de modèles sur des appareils à ressources limitées, comme les mobiles ou les dispositifs IoT. Ainsi, ONNX contribue à rendre les modèles d’apprentissage automatique plus légers et plus rapides, sans perte d’information et de fonctionnalité avec l’original.

Pré requis

L’objectif de ce tutoriel est de vous guider à travers un cas pratique de transfert de modèle de Python à C#. Nous ne passerons pas beaucoup de temps à expliquer comment entraîner un modèle, comment fonctionnent les réseaux de neurones, etc. car nous souhaitons nos focaliser ici sur le transfert ONNX.

Il présuppose que vous avez les pré requis suivants:

  • un environnement Python pour le marchine learning fonctionnel
  • un environnement .NET Core (j’ai utilisé la version 7) fonctionnel

Le code que vous allez écrire devrait normalement run sous toute plateforme, n’hésitez pas à me le faire savoir en commentaire si vous rencontrez des soucis !

Ce que vous allez construire

Vous allez:

  • Entraîner un classificateur d’images basé sur resnet18 en utilisant fastai, une librairie wrapper autour de PyTorch, permettant de réaliser très facilement des pipelines d’entraînement de réseaux de neurones.
  • Nous allons spécialiser ce modèle en l’entraînant sur le MNIST, le dataset de référence du deep learning.
  • Ce classificateur aura pour mission de reconnaître les chiffres 0 à 9 avec une image en input issue du training set du MNIST.
  • Nous l’exporterons ensuite en ONNX pour pouvoir le consommer avec une application .NET Core 7 afin qu’il puisse effectuer des classifications dans ce langage!

Votre point de référence sera ce dépôt git. N’hésitez pas à le consulter pour voir le code complet. Je m’attarderai ici sur les points qui m’ont semblé le plus important pour comprendre comment ce type d’exports fonctionne.

Les points importants avant export

Lister de manière exhaustive les étapes de pré processing de l’input à classifier

La plupart des frameworks et outils populaires d’IA vont opérer plusieurs transformations sur la donnée en entrée même si vous ne l’avez pas spécifié dans votre code, c’est le cas de fastai. Par exemple, j’ai passé pas mal de temps à comprendre qu’en réalité le visual_learner de fastai opérait plusieurs transformations par défaut sur mes images d’entraînement au moment de l’entraînement alors que je voulais créer le data loader le plus simple possible:

un data loader très simple pour loader le MNIST dans fastai
le data loader en question

On peut vérifier cela après l’entraînement du modèle de cette manière =>

les étapes de transformations par défaut qu'opère `fastai` lorsqu'on utilise un visual learner

En résumé, 2 transformations sont appliquées par défaut par fastai au moment de l’entraînement:

  1. Le tensor de nombres entiers de l’image importée est transformée en tensor de float et chaque pixel est divisé par 255. à fins de normalisation.
  2. Les pixels sont de nouveau normalisés en utilisant l’écart moyen et standard afin de s’assurer que toutes les valeurs des pixels sont distribuées dans la bonne échelle.

Savoir exactement quelle étape de pré processing a été appliquée est primordial, comme vous le verrez dans la partie C#.

Savoir quelle est la forme de l’input à classifier

Cela vaut pour les programmes classiques et pour les réseaux de neurones (jusque là): un modèle attend un input d’une certaine forme pour pouvoir réaliser sa tâche.

Lorsque vous aurez exporté votre modèle pour usage dans votre langage de programmation cible, il faudra vous assurer que l’input à prédire a exactement la même forme que celui qui a été utilisé durant l’entraînement:

les dimensions et canaux d'une image représentée par un `tensor`

On voit ci-dessus que notre input est une image de 28*28 pixels avec trois canaux: R, G, et B. On peut donc parler d’un rank-3 tensor.

Connaître la forme de l’input à classifier nous aidera à définir un example d’input lors de l’export de l’ONNX:

example d'input à définir pour l'export du modèle

Les quatre dimensions que vous voyez ci-dessus sont, de gauche à droite:

  • la taille du batch à donner au modèle au moment de la classification (ici 1, mais à l’entraînement on lui chargeait des images par batchs de 64)
  • les canaux de l’image (RGB)
  • les pixels en largeur et hauteur

Passer le modèle en mode évaluation avant l’export

Cette petite ligne de code permet de s’assurer que son modèle est prêt pour la production:

préparation du modèle pour la production

Cela va supprimer des couches de neurones redondantes, certaines opérations de normalisation dans les couches couchées, et éliminer tout dropout parce que nous ne sommes plus en phase d’entraînement.

Exporter le modèle

export du modèle en ONNX côté Python

Le code ci-dessus est amplement commenté dans le notebook.

Toutefois certaines points intéressants sont à soulever:

  • Si vous n’êtes pas certain que les machines cibles qui accueilleront votre modèle ONNX pour exécuter des inférences ont une GPU, privilégiez la sécurité et passez en paramètre d’export le fait que le modèle sera exécuté sur CPU; de même, assurez-vous que l’input de test est un tenseur CPU
  • N’oubliez pas de spécifier que vous souhaitez appliquer les optimisations du compilateur au moment de l’export, c’est une bonne pratique.
  • Pour une application en production, il faut inclure les poids du modèle dans l’export.
  • Spécifiez des axes dynamiques pour l’I/O: cela vous permettra d’exécuter des inférences de masse avec votre modèle exporté (classifier des images en batchs dans notre cas).

Validez votre export en Python

Avant de switcher sur une autre plateforme pour exploiter votre export ONNX et d’ajouter de la complexité à votre travail en faisant cela, épargnez-vous quelques heures de souffrance 🤒: validez que votre export fonctionne dans la même plateforme que vous avez utilisé pour l’export en ré important tout simplement votre modèle ONNX et en vérifiant qu’il fonctionne comme attendu =>

Vérification technique

vérification technique que l'export a fonctionné

Reproduire le pré processing

pré processing de l'image

Comme indiqué plus haut, pour que la classification réussisse, il faut que le modèle reçoive en input la même chose qu’il a apprise à l’entraînement (un principe ô combien classique mais si facilement oubliable dans le monde du deep learning).

Tester l’inférence avec le modèle exporté

tester l'inférence côté Python

Ici, le test est très simple, mais pour tester rigoureusement que votre modèle ne perd pas en performance d’une plateforme à une autre, vous effectuerez des inférences de masse sur un jeu de données représentatif.

Faisons cela en C# 😉

Vous êtes prêt à run votre modèle en C# !

Le code C# du programme « mirror » en quelque sorte ce que nous avons fait en Python:

pré processing côté C#
… le pré processing initial
ONNX inference in C#
l’exécution de l’inférence

Conclusion

A travers cet exemple (et quelques heures avant de comprendre comment ça marche), je suis ravi de partager avec vous comment consommer un modèle d’IA développé en Python dans un autre langage de programmation (ici le C#).

Cet apprentissage a été pour moi très excitant dans la mesure où je réalise d’avantage le champ des possibles dans le fait de pouvoir embed des applications boostées à l’IA dans n’importe quelle codebase, même legacy!

J’ajoute que, si vous avez besoin de moderniser et d’ajouter un sel d’IA votre transformation digitale sans devoir tout casser dans votre système existant, vous pouvez faire appel à l’IA Squad 🦸 !

Pour cela, rien de plus simple: contactez https://reboot-conseil.com/ !

Partager l'article:

Autres articles