Mettre à disposition un modèle par le biais d’une API
Les API sont un moyen très pratique pour simplifier la mise à disposition des prédictions d’un modèle de machine learning.
Ce chapitre présente la manière dont FastAPI simplifie l’intégration d’un modèle de machine learning dans une application.
Pour essayer les exemples présents dans ce tutoriel :
Ce chapitre présente la deuxième application
d’une journée de cours que j’ai
donné à l’Université Dauphine dans le cadre
des PSL Data Week.
L’objectif de ce chapitre est d’amener à développer
une API du type de celle-ci.
Dérouler les slides associées ci-dessous ou cliquer ici
pour les afficher en plein écran.
Le chapitre précédent constituait une introduction à la création
de pipelines de machine learning.
Ce chapitre va aller plus loin en montrant la démarche pour le rendre
disponible à plus grande échelle par le biais d’une API pouvant
être consommée avec de nouvelles données. L’objectif de celle-ci est
de ne pas contraindre les réutilisateurs d’un modèle
à disposer d’un environnement technique complexe
pour pouvoir utiliser le même modèle que celui entraîné précédemment.
1 Exemple de réutilisation d’un modèle sous forme d’API
Néanmoins, l’un des intérêts de proposer
une API est que les utilisateurs du modèle
ne sont pas obligés d’être des pythonistes.
Cela accroît grandement la cible des ré-utilisateurs
potentiels.
Cette approche ouvre notamment la possibilité de
faire des applications interactives qui utilisent,
en arrière plan, notre modèle entraîné avec Python.
Voici un exemple, minimaliste, d’une réutilisation
de notre modèle avec deux sélecteurs Javascript
qui mettent à jour le prix estimé du bien.
html`<div>Nombre de pièces</div><div>${viewof pieces_principales}</div>`
html`<div>Surface de l'appartement</div><div>${surface}</div>`
value = d3.json(url_api_dvf).then(data => {// Access the 'value' property from the objectlet originalNumber = data;// Convert it to a floating-point numberlet numericValue =parseFloat(originalNumber);// Round the numberlet roundedNumber =Math.round(numericValue).toLocaleString();return roundedNumber;}).catch(error =>console.error('Error:', error));
return_message =`Valeur estimée de l'appartement: <span class="blue2">__${value} €__</span>`
2 Etape 1: créer une application en local
Mettre en place une API consiste à gravir une marche
dans l’échelle de la reproductibilité par rapport
à fournir un notebook. Ces derniers
ne sont pas les outils les plus adaptés
pour partager autre chose que du code, à faire tourner
de son côté.
Il est donc naturel de sortir des notebooks
lorsqu’on commence à aller vers ce niveau de mise à
disposition.
Par le biais de
scripts Python lancés en ligne de commande,
construits en exportant le code du chapitre précédent
de nos notebooks, on pourra
créer une base de départ propre.
Il est plus naturel de privilégier une interface de développement
généraliste comme VSCode à Jupyter lorsqu’on franchit
ce rubicon. L’exercice suivant permettra donc
de créer cette première application minimale, à
exécuter en ligne de commande.
Exercice 1: créer des scripts pour entraîner le modèle
Le dépôt Github qui permet de construire l’API from scratch
est disponible ici.
Nous allons emprunter quelques éléments, par-ci par-là,
pour faire notre application en local.
Créer un nouveau service VSCode sur le SSPCloud en paramétrant dans l’onglet
Networking le port 5000 ;
Utiliser la commande suivante depuis le terminal:
mkdir app
cd app
Depuis le menu des fichiers, créer quatre fichiers dont le contenu
suit:
requirements.txt: récupérer le contenu sur cette page ;
Exécuter getdvf.py puis train.py pour stocker en local le modèle entraîné
Ajouter model.joblib au .gitignore(si vous utilisez Git)
Créer un script test.py qui contient la fonction suivante et la teste après avoir importé votre modèle (load('pipe.joblib') en n’oubliant pas from joblib import load):
Le script précédent constitue déjà un progrès dans
la reproductibilité. Il rend plus facile le réentraînement
d’un modèle sur le même jeu de données. Néanmoins,
il reste tributaire du fait que la personne désirant
utiliser du modèle utilise Python et sache réentrainer
le modèle dans les mêmes conditions que vous.
Avec FastAPI, nous allons très facilement pouvoir
transformer cette application Python en une API.
Exercice 2: créer des scripts pour entraîner le modèle
La ligne ci-dessous du script api.py récupère un modèle pré-entraîné enregistré sur un espace de stockage
A partir du README du service VSCode,
se rendre sur l’URL de déploiement,
ajouter /docs/ à celui-ci et observer la documentation de l’API
Se servir de la documentation pour tester les requêtes /predict
Récupérer l’URL d’une des requêtes proposées. La tester dans le navigateur
et depuis Python avec Requests (requests.get(url).json())
Optionnel: faire tourner le même code dans un autre environnement que le SSPCloud (par exemple une installation de Python en local) pour voir que ça fonctionne de manière identique.
4 Aller plus loin: mettre à disposition cette API de manière pérenne
L’étape précédente permettait de créer un point d’accès
à votre modèle depuis n’importe quel type de client. A chaque
requête de l’API, le script api.py était exécuté et
renvoyait son output.
Ceci est déjà un saut de géant dans l’échelle de la
reproductibilité. Néanmoins, cela reste artisanal: si votre
serveur local connait un problème (par exemple, vous killez l’application), les clients ne recevront plus de réponse,
sans comprendre pourquoi.
Il est donc plus fiable de mettre en production sur des
serveurs dédiés, qui tournent 24h/24 et qui peuvent
également se répartir la charge de travail s’il y a
beaucoup de demandes instantanées.
Ceci dépasse néanmoins
le cadre de ce cours et sera l’objet
d’un cours dédié en 3e année de l’ENSAE: “Mise en production de projets data science” donné par Romain Avouac et moi.
md`Ce fichier a été modifié __${table_commit.length}__ fois depuis sa création le ${creation_string} (dernière modification le ${last_modification_string})`
functionreplacePullRequestPattern(inputString, githubRepo) {// Use a regular expression to match the pattern #digitvar pattern =/#(\d+)/g;// Replace the pattern with ${github_repo}/pull/#digitvar replacedString = inputString.replace(pattern,'[#$1]('+ githubRepo +'/pull/$1)');return replacedString;}
table_commit = {// Get the HTML table by its class namevar table =document.querySelector('.commit-table');// Check if the table existsif (table) {// Initialize an array to store the table datavar dataArray = [];// Extract headers from the first rowvar headers = [];for (var i =0; i < table.rows[0].cells.length; i++) { headers.push(table.rows[0].cells[i].textContent.trim()); }// Iterate through the rows, starting from the second rowfor (var i =1; i < table.rows.length; i++) {var row = table.rows[i];var rowData = {};// Iterate through the cells in the rowfor (var j =0; j < row.cells.length; j++) {// Use headers as keys and cell content as values rowData[headers[j]] = row.cells[j].textContent.trim(); }// Push the rowData object to the dataArray dataArray.push(rowData); } }return dataArray}
// Get the element with class 'git-details'{var gitDetails =document.querySelector('.commit-table');// Check if the element existsif (gitDetails) {// Hide the element gitDetails.style.display='none'; }}
@book{galiana2023,
author = {Galiana, Lino},
title = {Python pour la data science},
date = {2023},
url = {https://pythonds.linogaliana.fr/},
doi = {10.5281/zenodo.8229676},
langid = {fr}
}