main.py udpate

This commit is contained in:
Grégoire Mureau 2025-07-04 08:58:47 +02:00
parent 5d983fa9d2
commit 3dea82648c

View file

@ -1,77 +1,92 @@
import os import os
import dill
import pickle
from dotenv import load_dotenv from dotenv import load_dotenv
import re
import html
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, HTTPException
from pydantic import BaseModel from pydantic import BaseModel
import mlflow
from mlflow.tracking import MlflowClient
import mlflow.keras import mlflow.keras
import nltk from tensorflow.keras.preprocessing.sequence import pad_sequences
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
from nltk.stem import WordNetLemmatizer
# Téléchargements nécessaires NLTK # Charger les variables d'environnement
nltk.download('punkt')
nltk.download('stopwords')
nltk.download('wordnet')
# Charger les variables d'environnement depuis le fichier .env à la racine ou accessible
load_dotenv(dotenv_path='../../.env') load_dotenv(dotenv_path='../../.env')
# Configuration de MLflow avec les variables d'environnement #%%
mlflow_tracking_uri = os.getenv("MLFLOW_TRACKING_URI") mlflow_tracking_uri = os.getenv("MLFLOW_TRACKING_URI")
mlflow_s3_endpoint_url = os.getenv("MLFLOW_S3_ENDPOINT_URL") if mlflow_tracking_uri:
aws_access_key_id = os.getenv("AWS_ACCESS_KEY_ID") mlflow.set_tracking_uri(mlflow_tracking_uri)
aws_secret_access_key = os.getenv("AWS_SECRET_ACCESS_KEY") else:
print("⚠️ Attention : MLFLOW_TRACKING_URI non défini dans .env")
# Modèle et étape par défaut
MODEL_NAME = "SentimentAnalysisLSTM"
STAGE = "Production"
# Définition du modèle de données pour la requête
class TweetRequest(BaseModel): class TweetRequest(BaseModel):
tweet: str tweet: str
# Fonction de prétraitement pour les tweets def get_latest_version(client, model_name, stage):
def preprocess_tweet(tweet: str) -> str: versions = client.search_model_versions(f"name='{model_name}'")
""" filtered = [v for v in versions if v.current_stage == stage]
Prétraite un tweet en appliquant plusieurs transformations : if not filtered:
- Conversion en minuscules return None
- Remplacement des URLs, mentions et hashtags par des tokens spéciaux latest = max(filtered, key=lambda v: int(v.version))
- Suppression des caractères spéciaux return latest
- Tokenisation et lemmatisation
- Suppression des stopwords
"""
if not isinstance(tweet, str):
return ""
tweet = tweet.lower() def load_model_and_artifacts(model_name="SentimentAnalysisLSTM", stage="Production"):
tweet = re.sub(r'https?://\S+|www\.\S+', '<URL>', tweet) client = MlflowClient()
tweet = re.sub(r'@\w+', '<MENTION>', tweet) versions = client.get_latest_versions(model_name, [stage])
tweet = re.sub(r'#(\w+)', r'# \1', tweet) if not versions:
tweet = html.unescape(tweet) raise RuntimeError(f"Aucune version du modèle '{model_name}' en stage '{stage}'")
tweet = re.sub(r'[^\w\s<>@#!?]', '', tweet) version = versions[0]
run_id = version.run_id
if run_id is None:
raise RuntimeError("'run_id' introuvable.")
tokens = word_tokenize(tweet) # Récupérer les tags du modèle pour trouver embedding_type
model_version_info = client.get_model_version(model_name, version.version)
embedding_type = model_version_info.tags.get("embedding_type", "glove").lower()
lemmatizer = WordNetLemmatizer() local_dir = "./downloaded_artifacts"
tokens = [lemmatizer.lemmatize(token) for token in tokens] os.makedirs(local_dir, exist_ok=True)
stop_words = set(stopwords.words('english')) base_path = "local_artifacts/logs"
important_words = {'no', 'not', 'nor', 'neither', 'never', 'nobody', 'none', 'nothing', 'nowhere'}
stop_words = stop_words - important_words if embedding_type == "glove":
tokens = [token for token in tokens if token not in stop_words] keras_file = "final_model_LSTM_GloVe-300d-Fige.keras"
tokenizer_file = "tokenizer.pickle"
preprocess_file = "preprocess_function.dill"
elif embedding_type == "word2vec":
keras_file = "final_model_LSTM_Word2Vec.keras"
tokenizer_file = "tokenizer_word2vec.pickle"
preprocess_file = "preprocess_function_word2vec.dill"
else:
raise ValueError(f"Type d'embedding inconnu: {embedding_type}")
def dl(path):
return client.download_artifacts(run_id=run_id, path=f"{base_path}/{path}", dst_path=local_dir)
model_path = dl(keras_file)
tokenizer_path = dl(tokenizer_file)
preprocess_path = dl(preprocess_file)
model = mlflow.keras.load_model(model_path)
with open(tokenizer_path, "rb") as f:
tokenizer = pickle.load(f)
with open(preprocess_path, "rb") as f:
preprocess_fn = dill.load(f)
return model, tokenizer, preprocess_fn, embedding_type
return ' '.join(tokens)
# Initialisation FastAPI
app = FastAPI(title="API Prédiction Sentiment - Air Paradis") app = FastAPI(title="API Prédiction Sentiment - Air Paradis")
# Chargement du modèle MLflow (URI fixe du modèle en stage Production)
MODEL_URI = "models:/SentimentAnalysisLSTM/Production"
try: try:
model = mlflow.keras.load_model(MODEL_URI) model, tokenizer, preprocess_tweet = load_model_and_artifacts()
print("Modèle chargé depuis MLflow avec succès.")
except Exception as e: except Exception as e:
print(f"Erreur lors du chargement du modèle MLflow : {e}") print(f"❌ Erreur lors du chargement des artefacts : {e}")
model = None model, tokenizer, preprocess_tweet = None, None, None
@app.get("/") @app.get("/")
def root(): def root():
@ -79,27 +94,20 @@ def root():
@app.post("/predict") @app.post("/predict")
def predict_sentiment(request: TweetRequest): def predict_sentiment(request: TweetRequest):
if model is None: if model is None or tokenizer is None or preprocess_tweet is None:
raise HTTPException(status_code=503, detail="Modèle non disponible.") raise HTTPException(status_code=503, detail="Modèle ou artefacts non disponibles.")
processed_tweet = preprocess_tweet(request.tweet) processed_tweet = preprocess_tweet(request.tweet)
if processed_tweet == "": if not processed_tweet.strip():
raise HTTPException(status_code=400, detail="Tweet vide ou invalide après prétraitement.") raise HTTPException(status_code=400, detail="Tweet vide ou invalide après prétraitement.")
# Ici, il faudra adapter selon la manière dont ton modèle attend l'entrée sequence = tokenizer.texts_to_sequences([processed_tweet])
# Par exemple, si ton modèle attend une séquence d'indices, il faut intégrer la vectorisation / tokenisation sequence_padded = pad_sequences(sequence, maxlen=100) # Ajuster maxlen si besoin
# Pour l'exemple, on fait un dummy wrap en liste
input_data = [processed_tweet]
# Modèle Keras attend généralement un array numpy, ou séquence tokenisée
# À adapter selon ta pipeline exacte
import numpy as np
try: try:
# Exemple naïf : on prédit directement sur la liste textuelle (à adapter !) prediction_prob = model.predict(sequence_padded)[0][0]
prediction_prob = model.predict(input_data)[0][0] # supposons sortie [prob_neg]
sentiment = "positif" if prediction_prob >= 0.5 else "negatif" sentiment = "positif" if prediction_prob >= 0.5 else "negatif"
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"Erreur lors de l'inférence : {e}") raise HTTPException(status_code=500, detail=f"Erreur lors de l'inférence : {e}")
return {"sentiment": sentiment, "probability": float(prediction_prob)} return {"sentiment": sentiment, "probability": float(prediction_prob)}