- Vue d'ensemble du projet : une chaîne complète reproductible pour les services d'IA
Ce projet ne se limite pas à une démo de type "Kaggle" pour détecter les hot-dogs. Il s'agit d'une implémentation complète d'un service d'IA utilisant FastAPI pour l'API web, MobileNetV2 pour le modèle de vision par ordinateur, et une chaîne de déploiement pratique. L'objectif est de fournir un point de référence minimale mais complet pour sortir un modèle de Jupyter Notebook et le rendre accessible via une API HTTP, intégrable par des applications front-end et monitorable par les équipes ops.
Ce guide s'adresse aux ingénieurs full-stack, aux débutants en MLOps et aux praticiens de l'IA confrontés aux défis du déploiement en production. Il met l'accent sur les aspects pratiques : traitement des requêtes d'image, structuration des réponses, gestion des erreurs et optimisation des performances sans GPU.
- Conception de l'architecture et choix techniques
2.1 Pourquoi FastAPI ?
FastAPI est choisi pour sa compatibilité native avec l'asynchrone (ASGI) et sa validation automatique des requêtes via Pydantic. Contrairement à Flask, qui bloque les threads sur les opérations I/O, FastAPI gère efficacement les téléchargements d'images. Par rapport à Django, il est plus léger et adapté aux API pures. FastAPI génère également automatiquement la documentation OpenAPI, réduisant ainsi les coûts d'intégration.
Exemple de requête avec validation :
from pydantic import BaseModel, Field
class RequetePrediction(BaseModel):
image: UploadFile = File(...)
seuil_confiance: float = Field(0.5, ge=0.1, le=0.99)
2.2 Pourquoi MobileNetV2 ?
MobileNetV2 offre un bon compromis entre précision et efficacité. Ses convolutions séparables en profondeur réduisent la taille du modèle et le temps d'inférence, ce qui est crucial pour le déploiement sur des serveurs à ressources limitées. Pour une tâche de détection de hot-dogs, il atteint une précision suffisante après un fine-tuning rapide.
2.3 Pourquoi Docker + Uvicorn + Nginx ?
Cette pile garantit la cohérence de l'environnement, la performance et la sécurité. Docker encapsule les dépendances, Uvicorn gère les requêtes ASGI, et Nginx sert de proxy inverse pour le routage, la limitation de débit et la terminaison SSL. Contrairement aux solutions serverless, elle offre un temps de réponse plus stable et évite les contraintes de taille de paquet.
- Détails d'implémentation : de la préparation des données à la sérialisation du modèle
3.1 Préparation du jeu de données
Un jeu de données minimal de 200 images est utilisé. Les images sont nettoyées en plusieurs étapes : détection des bords pour filtrer les images floues, utilisation de modèles sémantiques pour vérifier la cohérence, et révision manuelle pour les cas ambigus. L'augmentation des données est limitée aux retournements horizontaux et aux ajustements de couleur pour éviter les artefacts.
3.2 Entraînement et export du modèle
L'entraînement contrôle la reproductibilité via des graines aléatoires fixes. Le modèle est exporté au format ONNX pour une interopérabilité maximale, avec des axes dynamiques pour gérer les tailles de batch variables.
Exemple d'export ONNX :
entree_factice = torch.randn(1, 3, 224, 224)
torch.onnx.export(
modele, entree_factice, "hotdog.onnx",
noms_entrees=["entree"],
noms_sorties=["sortie"],
axes_dynamiques={"entree": {0: "taille_batch"}, "sortie": {0: "taille_batch"}}
)
3.3 Service FastAPI
Le service inclut des middlewares pour mesurer le temps de traitement et gérer les exceptions globalement. La fonction de prétraitement redimensionne et normalice les images selon les standards d'ImageNet.
Exemple de fonction de prétraitement :
def traiter_image(fichier_image: UploadFile) -> torch.Tensor:
octets_image = fichier_image.file.read()
image = Image.open(io.BytesIO(octets_image)).convert("RGB")
image = transforms.Resize((256, 256))(image)
image = transforms.CenterCrop(224)(image)
image = transforms.ToTensor()(image)
image = transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])(image)
return image.unsqueeze(0)
- Processus de déploiement
4.1 Environnement de développement
VS Code avec DevContainers est utilisé pour standardiser l'environnement. Un fichier .devcontainer/devcontainer.json configure un conteneur Docker avec les dépendances Python nécessaires, garantissant la cohérence entre les machines de développement.
4.2 Construction de l'image Docker
L'image est construite en plusieurs étapes pour minimiser la taille : une phase de construction compile les roues Python, et une phase d'exécution les installe. L'application s'exécute sous un utilisateur non root pour des raisons de sécurité.
# Dockerfile multi-étapes
FROM python:3.9-slim AS constructeur
WORKDIR /app
COPY requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt
FROM python:3.9-slim
WORKDIR /app
COPY --from=constructeur /app/wheels /wheels
RUN pip install --no-cache /wheels/*.whl
COPY . .
RUN adduser --disabled-password --gecos "" appuser && chown -R appuser:appuser /app
USER appuser
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
4.3 Déploiement sur un serveur cloud
Le déploiement implique l'installation de Docker et Nginx, la configuration d'un proxy inverse, et l'automatisation HTTPS avec Certbot. Le conteneur Docker est exécuté avec des options de redémarrage automatique et de montage de volumes pour la persistance.
- Dépannage courant
- Erreur 413 (entité trop grande) : augmenter
client_max_body_sizedans Nginx. - Temps de chargement initial lent : préchauffer le modèle ONNX au démarrage de l'application.
- Échecs en concurrence : ajuster le nombre de workers Uvicorn et les limites de ressources.
- Problèmes CORS : configurer le middleware CORS dans FastAPI avec des origines spécifiques.
- Faux positifs : ajouter des règles post-traitement basées sur des caractéristiques géométriques.
- Tests de performence et surveillance
Des tests de charge avec Locust simulent un trafic réaliste. Des métriques sont exposées via Prometheus et visualisées avec Grafana pour une observabilité complète. Les résultats montrent que le service peut gérer une charge moyenne avec des temps de réponse acceptables.
- Extensibilité et évolution
Le code est réorganisé pour supporter plusieurs modèles via un registre dynamique. Un mécanisme de mise à jour à chaud permet de changer le modèle sans redémarrer le service. Des optimisations comme TensorRT peuvant être appliquées pour des performances GPU accrues.
Exemple de registre de modèles :
REGISTRE_MODELES = {
"hotdog": {
"classe": MobileNetV2,
"chemin_poids": "modeles/hotdog.pth",
"pretraitement": pretraitement_hotdog,
"posttraitement": posttraitement_hotdog
},
"chat_chien": {
"classe": ResNet18,
"chemin_poids": "modeles/chat_chien.pth",
"pretraitement": pretraitement_chat_chien,
"posttraitement": posttraitement_chat_chien
}
}