Découvrez FastAPI pour le Machine Learning

Vous l’avez compris avec le chapitre précédent, l’objectif de ce cours est de progressivement industrialiser un projet ML, brique par brique, chapitre par chapitre. Dans celui-ci, nous allons préparer ensemble l’industrialisation de la brique motrice du projet ML : le modèle de ML en lui-même.

L’une des solutions les plus répandues pour ce faire, est d’exposer le modèle via une API (dans le jargon, on parle aussi “d’API-ser” le modèle). À l’heure d’écriture de ce cours, FastAPI est le package open source incontournablepour “API-ser” un modèle de ML. C’est ce que nous allons utiliser pour ce qui suit !

Si le mot API vous semble être un buzz word et que vous ne comprenez pas exactement ce qu’on entend par là, le chapitre "Initiez-vous au fonctionnement des API" du cours d’initiation aux APIs vulgarise avec brio les notions rudimentaires !

Comprenez les intéractions entre le modèle ML et l’API

L'interaction avec un modèle ML via une API n'est, dans l'ensemble, pas très différente d'une interaction avec Google pour faire vos recherches. Si vous comprenez ce qui se passe sous le capot dans le dernier scénario, vous allez comprendre simultanément le premier.

Si vous cherchez dans Google le terme "machine learning", vous allez vous retrouver avec l'URL suivante dans votre barre de recherche :  https://www.google.com/search?q=machine+learning&source=web&hl=fr

C'est cela que nous devons décomposer pour comprendre ce qui se passe :

  • L'endpoint :https://www.google.com/searchoù "search" est le Path Parameter

  • Les Query Parameters :q=machine+learning&source=web&hl=fr

Quand vous avez cliqué sur "Rechercher", votre navigateur envoie une requête HTTP de type GET avec ces paramètres, et le serveur de Google vous renvoie une page de résultats.

C'est assez similaire avec votre modèle de ML. S'il est déjà en production et que vous voulez réaliser une prédiction sur la valeur d'un bien avec les caractéristiques X, directement via votre navigateur web (on verra exactement comment faire), vous verrez une URL qui peut très bien ressembler à quelque chose comme ceci :  https://mon-api-ml.com/predict?surface_habitable=35&taux_interet=2.5&departement_Paris=1&type_batiment_Appartement=1.

Là où Google vous retourne une page HTML, cette requête retourne souvent un JSON, avec une structure qui peut ressembler à ceci :  { "prediction": "350000", "model_version": "v2.1" } .

FastAPI va justement venir transformer votre script Python de prédiction en unservice web accessible par URL, que vous allez pouvoir requêter à la main, ou de manière automatisée, via des requêtes HTTP.

Ce qui signifie que l'utilisateur final n'a pas à télécharger votre code, installer Python, les packages et tout le tralala pour bénéficier des prédictions pertinentes de votre modèle ! Celui-ci devient accessible à distance, par le web !

Plus concrètement, une fonction en Python qui réaliserait une inférence basée sur mon exemple plus haut, ressemblerait probablement à ceci.

def predire_valeur_bien(surface_habitable, taux_interet, departement_Paris, type_batiment_Appartement):
    # Votre logique de prédiction

    prediction = model.predict([[surface_habitable, taux_interet, departement_Paris, type_batiment_Appartement]])

    return prediction[0]

Avec FastAPI, cette même fonction devient accessible via HTTP :

from fastapi import FastAPI

app = FastAPI()

@app.get("/predict")
def predire_valeur_bien(
    surface_habitable: float, 
    taux_interet: float, 
    departement_Paris: int, 
    type_batiment_Appartement: int
):

    prediction = model.predict([[surface_habitable, taux_interet, departement_Paris, type_batiment_Appartement]])
    return {
        "prediction": prediction[0],
        "model_version": "v2.1"
    }

C'est la structure FastAPI la plus basique :

  • La ligne juste avant la définition de la fonction précise quel endpoint il faut appeler avec une requête HTTP pour exécuter la fonction, tout en précisant quel type de requête HTTP il faut (GET, POST etc.)

  • Les entrées de la fonction sont alimentées par les Query Parameters de la requête HTTP !

  • Le return, tout comme dans n'importe quelle fonction Python, est sous forme de dictionnaire, car c'esttypiquement un format JSON qui est renvoyé par une API !

Assainissez vos appels API avec Pydantic 

En réalité, l'exemple d'implémentation de FastAPI présenté juste avant est simpliste, voire irréaliste. L'intérêt de l'exemple était de comprendre l'analogie avec un simple appel HTML.

Dans le vrai monde, les features en input de la prédiction du modèle ne sont jamais transférées en tant que Query Parameters. Ce n'est pas fait pour ! Ce sont des Body Parameters qui sont utilisés.

Avec les Body Parameters, vous envoyez vos données sous forme de JSON structuré directement dans le corps de la requête. En plus d'être plus lisible, cette solution est beaucoup plus sécurisée, dans le cas où vos features encodent des données sensibles.

Reprenons notre cas d'usage des prédictions sur les données immobilières. Que ce soit pour un modèle de régression ou de classification, les features en entrée sont les mêmes. Parmi ces features, nous retrouvons par exemple le nombre de transactions du mois précédent dans la région. Si jamais cette feature se retrouve malencontreusement avec une valeur décimale (comme 9,3) ou pire, une valeur négative, elle serait complètement incohérente et elle fausserait complètement la prédiction du modèle.

Il ne faut pas oublier que le modèle de ML n'est pas réellement intelligent ! Ce n'est qu'une machine à cruncher des chiffres. Si le modèle a été entraîné sur 7 features numériques et 3 catégorielles, il réalisera sa prédiction quels que soient les chiffres reçus du moment qu'il en reçoit 7 numériques et 3 catégorielles, dans le même ordre que ce qu'il a vu en entraînement.

Pydantic permet justement d'implémenter des règles, garantes de la cohérence des entrées et des sorties du modèle ML. Pour ce faire, Pydantic utilise un système de classe, comme le suivant :

from fastapi import FastAPI
from pydantic import BaseModel, Field

class DonneesImmobilieres(BaseModel):
    surface_habitable: float = Field(gt=0, description="Surface en m²")
    taux_interet: float = Field(ge=0, le=100, description="Taux en %")
    nb_transactions_mois_precedent: int = Field(ge=0, description="Nombre entier positif")
    departement_Paris: int = Field(ge=0, le=1, description="0 ou 1")
    type_batiment_Appartement: int = Field(ge=0, le=1, description="0 ou 1")

app = FastAPI()

@app.post("/predict")
def predire_valeur_bien(donnees: DonneesImmobilieres):
    # Extraction des features validées
    features = [
        donnees.surface_habitable,
        donnees.taux_interet,
        donnees.nb_transactions_mois_precedent,
        donnees.departement_Paris,
        donnees.type_batiment_Appartement
    ]
    prediction = model.predict([features])
    return {"prediction": prediction[0], "model_version": "v2.1"}

Cette classe Pydantic  DonneesImmobilieres  est non seulement le miroir de ce que les Body Parameters doivent contenir, mais elle définit aussi le schéma exact des données attendues, avec des contraintes strictes de qualité de données. Grâce aux contraintes  Field , si quelqu'un essaie d'envoyer une valeur incohérente comme  nb_transactions_mois_precedent: -5  ou  9.3, FastAPI rejettera automatiquement la requête avec un message d'erreur clair avant même que votre modèle ML ne soit sollicité !

Qu'est-ce qu'on peut mettre comme Path Parameter ou Query Parameter alors si toutes les features vont dans le Body Parameter ?

Tout dépend de votre projet et de ses besoins ! Dans notre cas d'usage immobilier, on avait entraîné des modèles de régression et de classification. On pourrait imaginer avoir un endpoint et un Path Parameter pour la régression et un autre pour la classification.

Concernant les Query Parameters, ils servent à proposer des informations optionnelles à l'utilisateur de l'API. Par exemple, s'il veut le score de probabilité associé à l'inférence du modèle de classification, ou bien s'il souhaite les valeurs de Shapley de la prédiction afin de comprendre l'importance des features dans la prédiction qu'il a demandée. Autre exemple plus technique : renvoyer l'identifiant du run MLflow associé au modèle ML ! Cela permet de facilement identifier un modèle API-sé dont la qualité se dégrade, surtout dans un contexte où plusieurs modèles ML sont en production.

Découvrez un démo d’une app FastAPI simple

On va maintenant mettre tous ces concepts en musique en présentant un modèle simple API-sé via FastAPI.

Petit rappel sur le cas d'usage des prédictions de prix immobilières :

  • Les différents modèles ML sont entraînés par région, mais nous allons exclusivement nous concentrer sur la région Nouvelle-Aquitaine pour ce cours

  • Le modèle de classification va prédire si un bien immobilier aura une valeur en dessous de la moyenne de la région ou non (classe 1 signifie en dessous de la moyenne, classe 0 signifie l'inverse)

Les plus curieux d'entre vous se demanderont d'où viennent les différents textes décrivant les entrées et les sorties de l'endpoint predict qu'on a vu ensemble. Eh bien c'est très simple, tout est défini sous forme de docstring dans la fonction en Python !

Ce fonctionnement est très pratique car nous pouvons centraliser dans le script Python, la logique de l'API en plus de toute la documentation nécessaire pour en expliquer le fonctionnement. En clair, si vous voyez quelque chose dans le Swagger UI, vous trouverez son équivalent en code nécessairement dans le script qui définit l'app FastAPI !

To API or not to API ?

On pourrait se dire que c’est toujours une bonne idée d’API-ser notre modèle, pour faciliter les intéractions avec l’utilisateur final. Sauf qu’en entreprise, on ne met pas en place un système juste pour le plaisir. En plus de répondre à un besoin, il faut que le choix de mise à disposition du modèle soit adapté. Autrement dit : Il faut le minimum de complexité nécessaire.

Tout est dans le mot nécessaire !

Reprenons notre cas d’usage immobilier par exemple : Imaginons que notre utilisateur final soit un agent immobilier, qui souhaite prédire l’évolution des prix d’une certaine catégorie spécifique de biens (par exemple les T3) dans un département en particulier (par exemple le Bas-Rhin). Ensuite, imaginons que nos données sources ont une fréquence de rafraîchissement hebdomadaires.

Pour adresser le besoin de l’agent immobilier, il suffit de réaliser une prédiction par semaine pour chaque bien de la catégorie et du département qu’il intéresse. Une fois ces prédictions réalisées par le modèle, on n’a plus besoin de celui-ci jusqu’à la semaine suivante ! Il est alors largement suffisant de stocker ces prédictions hebdo dans une base de données et de permettre à l’agent d'interagir directement avec cette BDD.

Maintenant, prenons un deuxième exemple, dans le même contexte, mais qui milite pour l’implémentation d’une API : le même agent immobilier maintenant souhaite simuler l’impact de divers types de travaux de rénovations sur le prix d’un bien immobilier en particulier, parmi ceux du département et de la catégorie souhaitée. On appelle cela du counterfactual analysis, vous pouvez en savoir plus dans cet article : "Local Model-Agnostic Methods: 15 Counterfactual Explanations" (ressource en anglais).

Dans ce cas, cela devient compliqué de se baser uniquement sur une BDD. En effet, cette approche nécessiterait de précalculer à l’avance toutes les simulations possibles pour tous les biens possibles du périmètre de données qu’il a sélectionné. Cette solution est très difficile à implémenter et elle est tout sauf scalable. On est très loin du minimum de complexité nécessaire évoqué tout à l’heure !

L’API est en revanche particulièrement adaptée pour notre situation : l’agent immobilier pourra directement requêter le modèle avec des demandes de simulations (qui se traduisent par des valeurs de features qu’il aura sélectionné) et obtenir en live une réponse (car il s’agit d’inférence, sans ré-entraînement du modèle). On commence à comprendre que l’API est une piste intéressante quand les demandes de l’utilisateur final sont potentiellement trop diverses pour pouvoir être calculées à l’avance.

Maintenant qu’on a dit ça, FastAPI propose une pléthore d’options pour API-ser votre modèle. Pour n’en citer que quelques-uns :

  • Possibilités de mettre en place une API asynchrone (qui réponds en décalé à la demande de l’utilisateur) : Utile pour situations avec une forte demande sur l’API (typique du B2C) et sans contraintes de temps réel ou quasi-temps réel.

  • Possibilité de faire cohabiter plusieurs versions de votre modèle simultanément (par exemple v1 et v2) et de basculer progressivement vos utilisateurs d'une version à l'autre. Particulièrement utile quand vous entraînez régulièrement votre modèle et que vous souhaitez tester une nouvelle version sans perturber les utilisateurs existants. Pour plus de détails sur ces stratégies de déploiement, le chapitre "Structurez vos modélisations futures" du cours Maîtrisez l'apprentissage supervisé vous donnera quelques billes.

  • Possibilité d'envoyer des fichiers à votre modèle pour réaliser une inférence. Cela est particulièrement adapté aux données non structurées comme des cas d’usage de Computer Vision. Vous trouverez dans cet article de PyCharm un exemple de modèle API-sé qui utilise cette fonctionnalité : "How to Use FastAPI for Machine Learning".

Les possibilités sont très nombreuses et on peut rapidement s’y perdre. Nous ne regardons que le bout de l’Iceberg d’un nouveau domaine de connaissance.

Bienvenue dans le monde du ML System Design (aussi dénommé System Engineering) ! Autrement dit, comment penser et implémenter l'interaction entre un modèle ML, l’utilisateur final et tous les systèmes IT pertinents pour le projet !

Au prochain chapitre, nous allons examiner ce genre de considération dans un cas pratique, mais cela reste un vaste domaine qui mériterait son propre cours. On revoit vers cette ressource si vous voulez en savoir plus : "Machine Learning System Design — Part 1: Introduction".

Quoi qu’il en soit, vous avez désormais les bases de FastAPI et une première idée des situations où API-ser un modèle est intéressant !

En résumé 

  • Exposez un modèle ML via une API pour le rendre accessible à distance sans installation locale.

  • Utilisez FastAPI pour transformer une fonction Python de prédiction en service web accessible par HTTP.

  • Structurez les données entrantes avec Pydantic pour garantir leur cohérence et sécurité.

  • Privilégiez les Body Parameters pour transmettre les features d’un modèle, surtout s’ils sont nombreux.

  • API-sez un modèle uniquement si les besoins de l’utilisateur final justifient cette complexité.

Passons au prochain chapitre pour explorer les choix d’architecture possibles autour d’un modèle ML en production !

 

Ever considered an OpenClassrooms diploma?
  • Up to 100% of your training program funded
  • Flexible start date
  • Career-focused projects
  • Individual mentoring
Find the training program and funding option that suits you best