Ces dernières années, les modèles de Deep Learning se sont avérés surperformer les techniques de Machine Learning à l’état de l’art et d’autres approches plus traditionnelles dans de nombreux domaines dont, notablement, la vision par ordinateur (computer vision). Un nouveau défi est né à mesure que progressent les cas d’usage du Deep Learning et ses performances grandissantes : appliquer le Deep Learning directement sur les terminaux finaux (edge devices). Les algorithmes de Deep Learning tournent généralement sur des serveurs, qui traitent des requêtes des terminaux et y répondent au moyen des sorties de l’algorithme. Cette interaction terminal-serveur implique une grande latence pour l’obtention des résultats, puisqu’elle nécessite des allers-retours dans leurs communications. Pour plusieurs cas d’usage – par exemple, les véhicules autonomes – ce délai n’est pas acceptable. Pour que les données soient traitées rapidement, les algorithmes de Deep Learning peuvent être appliqués directement sur les terminaux.
Cette solution apporte son lot de problèmes, au nombre desquels la taille des librairies et des modèles requis pour faire fonctionner des algorithmes de Deep Learning complexes. Chez HarfangLab, nous avons abordé cette problématique au prisme de deux principes : 1) utiliser des librairies réduites qui ne contiennent que le code dont nous avons besoin, et 2) réduire la taille du modèle tout en conservant ses performances. Dans notre cas, nous avons dû créer tflite-runtime
(version réduite de tensorflow
pour la prédiction) pour les plateformes souhaitées (Windows 32 et 64 bits), réduire la taille de numpy
en supprimant du code non nécessaire, et diviser la taille de notre modèle par 10.
Voici comment nous avons accompli ces étapes et comment nous parvenons désormais à faire fonctionner des réseaux de neurones de Deep Learning complexes sur des machines Windows et Linux, avec des wheels Python qui pèsent moins de 5 Mo. Les différentes wheels sur-mesure que nous avons construites et qui sont requises pour faire fonctionner ces algorithmes seront aussi disponibles depuis les dépôts suivants.
Faire fonctionner du Deep Learning avec de dépendances réduites
Tensorflow et tensorflow-lite
Utilisation de tensorflow et tensorflow-lite
Tensorflow
est l’une des principales librairies de Deep Learning fournissant un framework simple et efficace pour mettre en œuvre et entraîner des modèles perfectionnés. Une fois le modèle entraîné avec ce framework, il présente le sérieux avantage de permettre de simplement le compresser en un modèle tensorflow-lite
. L’inférence peut donc fonctionner en n’utilisant que ce modèle et l’interpréteur tensorflow-lite
(tflite-runtime
), en laissant de côté toutes les dépendances non nécessaires qui viendraient avec les bibliothèques tensorflow
et tensorflow-lite
. Aussi, en utilisant tflite-runtime
, nous évitons la bibliothèque tensorflow
(1,2 Go) et la bibliothèque tensorflow-lite
(50 MB). Au lieu de cela, nous récupérons seulement l’interpréteur qui pèse 1 Mo pour charger et lancer le moteur en inférence.
Installer et utiliser tflite-runtime
est très simple. Il est possible de l’installer via une wheel avec pip install tflite-runtime
. Malheureusement, il n’existe pas de wheel Windows 64-bits pour tensorflow
après la version 2.5.0 (et aucune pour Windows 32-bits). Il existe quelques lignes directrices sur la manière de les construire, publiées par tensorflow
: https://www.tensorflow.org/lite/guide/build_cmake_pip and https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/tools/pip_package
Ces wheels pour tensorflow 2.7.0
sont disponibles depuis notre dépôt GitHub. Voici quelques conseils supplémentaires pour les construire.
Construire les wheels
Pour Linux, vous trouverez les wheels tflite-runtime
sur PyPI.
Pour construire tflite-runtime
pour Windows x86
et Windows x64
, en plus d’une bonne dose de courage, il vous faudra une machine virtuelle Windows x64
(ou votre ordinateur s’il est en Windows x64
). Le constructeur CMake
officiel doit être utilisé pour cette tâche. Pour lancer la construction, vous devez avoir installé Visual Studio Build Tools 2019
et Python 64-bits
(python -V
et python3 –V
ne devrait pas produire d’erreurs) et Python 32-bits
(qui doit être dans la même version que celle choisie pour Python 64-bits
). Il vous faut aussi installer numpy
, wheel
et pybind11
.
Voici la commande pour x64
et x86
: tensorflow/lite/tools/pip_package/build_pip_package_with_cmake.sh windows
Nous avons rencontré de nombreux problèmes avec cette commande. Voici comment nous avons modifié les lignes critiques du fichier thetensorflow/lite/tools/pip_package/build_pip_package_with_cmake.sh
:
Copy
PYTHON_LIB=$(${PYTHON} -c "import distutils.sysconfig as sysconfig; print(sysconfig.PREFIX + '\libs')")
PYTHON_INCLUDE=$(${PYTHON} -c "from sysconfig import get_paths as gp; print(gp()['include'])")
PYBIND11_INCLUDE=$(${PYTHON} -c "import pybind11; print (pybind11.get_include())")
NUMPY_INCLUDE=$(${PYTHON} -c "import numpy; print (numpy.get_include())")
BUILD_FLAGS=${BUILD_FLAGS:-"-I${PYTHON_INCLUDE} -I${PYBIND11_INCLUDE} -I${NUMPY_INCLUDE} /EHsc"}
cmake -A Win32 -DCMAKE_C_FLAGS="${BUILD_FLAGS}" -DCMAKE_CXX_FLAGS="${BUILD_FLAGS}" -DCMAKE_SHARED_LINKER_FLAGS="/libpath:$PYTHON_LIB" "${TENSORFLOW_LITE_DIR}" -DTFLITE_ENABLE_XNNPACK=OFF -DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=True
cmake --build . --verbose -j ${BUILD_NUM_JOBS} -t _pywrap_tensorflow_interpreter_wrapper --config Release
cd "${BUILD_DIR}"
cp "${BUILD_DIR}/cmake_build/Release/_pywrap_tensorflow_interpreter_wrapper.dll ${BUILD_DIR}/tflite_runtime/_pywrap_tensorflow_interpreter_wrapper${LIBRARY_EXTENSION}"
Numpy, taille des wheels et des dépendances
Pour x86, nous avons ajouté -A Win32
pour préciser que la construction doit être pour Windows 32-bit.
Maintenant que les wheels tflite-runtime
sont construites, nous pouvons aisément les installer sur une machine Linux ou Windows et faire fonctionner un modèle avec cette wheel de 1 Mo. Toutefois, la wheel tflite-runtime
requiert numpy
comme dépendance. Selon votre machine, numpy
devrait représenter entre 10 et 16 Mo. Il est possible d’optimiser la taille des dépendances requises pour faire fonctionner le modèle en réduisant la taille de numpy
. Pour ce faire, vous pouvez construire numpy
avec des exigences précises et réduire manuellement la taille de la wheel, afin de ne conserver que les fonctionnalités essentielles. Pour Windows x86
, Windows x64
, et Linux
, suivez les étapes ci-dessous :
CFLAGS="-g0 -Wl,--strip-all -I/usr/include:/usr/local/include -L/usr/lib:/usr/local/lib" pip install --cache-dir . --compile --global-option=build_ext --global-option="-j 4" numpy
- Construire la wheel et lancer wheel unpack
name-of-wheel.whl
- Trouver et supprimer le fichier
RECORD
dans le dossier extrait - Supprimer aléatoirement du dossier extrait quelques fichiers ou répertoires qui ne semblent pas nécessaires (
numpy
fournit des tests, de la documentation, des données, etc. qui augmentent grandement sa taille) - Packager le fichier à nouveau avec
wheel pack name-of-unpacked-folder
- Installer la
wheel
avecpip
- Installer
tflite-runtime
- Vérifier que votre modèle effectue une inférence sans échouer
- Supprimer d’autres fichiers tant que votre modèle peut toujours faire fonctionner l’inférence
Avec cette méthode manuelle, nous avons pu faire passer la taille de la wheel numpy-1.22.3
à moins de 3 Mo.
Réduire la taille des modèles de Deep Learning
Après les étapes précédentes, les dépendances nécessaires à l’exécution des algorithmes de Deep Learning ne devraient plus représenter que 4 Mo environ. Le seul objet manquant pour exécuter un algorithme est le modèle lui-même. La taille du fichier tflite
contenant le modèle augmente rapidement avec le nombre de paramètres du modèle. Voici les techniques que nous avons utilisées pour réduire la taille nécessaire à l’enregistrement du modèle.
Quantification du modèle
Un moyen simple et courant de réduire la taille d’un modèle est la quantification post-entraînement. Cette méthode consiste à réduire le nombre de bits utilisés par les paramètres et opérations du modèle. En général, les paramètres du modèle sont encodés sur 32 bits dans tensorflow
, mais ils peuvent être réduits à des flottants de 16 bits avec peu de pertes de performance. De manière plus radicale, les flottants en 32 bits peuvent aussi être convertis en entiers de 8 bits avec un risque de perte de performance plus important. D’autres variations de ces techniques sont disponibles et faciles à mettre en œuvre avec tensorflow-lite
, comme décrit dans la documentation : https://www.tensorflow.org/lite/performance/post_training_quantization#dynamic_range_quantization.
Les couches denses et GlobalAveraging
Dans de nombreux réseaux de neurones (en particulier dans les réseaux de neurones convolutifs), les couches denses situées à l’extrémité du réseau et produisant les prédictions représentent plus de 80 % des paramètres du modèle. Ces couches critiques peuvent être nécessaires à la discrimination des entrées de la tâche, bien que les couches convolutives précédentes soient souvent suffisantes. Le simple fait de supprimer partiellement les couches denses (ou de les remplacer entièrement par une couche de global average pooling) peut réduire considérablement la taille du modèle sans trop modifier ses prédictions.
Le Pruning
Cette dernière méthode consiste à supprimer les neurones et synapses qui contribuent le moins aux outputs du modèle. Cette méthode est rapide à mettre en œuvre avec tensorflow
, mais elle nécessite plus d’expérimentations et de réglages fins que les deux précédentes méthodes présentées pour optimiser le rapport taille/performance.
Faire fonctionner des algorithmes de Deep Learning de moins de 5 Mo sur des terminaux
En construisant l’interpréteur tensorflow-lite
, en supprimant les parties inutiles de numpy
, en personnalisant nos modèles et en limitant leur taille grâce à la quantification et au pruning, nous exécutons des algorithmes de Deep Learning sur des machines sous Windows et Linux, avec des wheels Python de moins de 5 Mo !