main.py udpate
This commit is contained in:
parent
5d983fa9d2
commit
3dea82648c
1 changed files with 72 additions and 64 deletions
|
|
@ -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)}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue