Spaces:
Running
Running
| import os | |
| from typing import Dict, Tuple | |
| import gradio as gr | |
| import torch | |
| from fastapi import FastAPI | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from gradio.routes import mount_gradio_app | |
| from transformers import AutoModelForSequenceClassification, AutoTokenizer | |
| # --- Model setup --- | |
| # Fine-tuned model (ton modèle entraîné) | |
| MODEL_ID = os.getenv("MODEL_ID", "TayebBou/sentiment-fr-allocine") | |
| # Model de base (corps pré-entraîné + tête random) pour comparaison | |
| BASE_MODEL = os.getenv("BASE_MODEL", "distilbert-base-multilingual-cased") | |
| LABELS = ["neg", "pos"] | |
| # Tokenizer (on peut réutiliser le même tokenizer si compatible) | |
| tok = AutoTokenizer.from_pretrained(MODEL_ID) | |
| # Chargement du modèle fine-tuné (celui que tu déploies) | |
| mdl = AutoModelForSequenceClassification.from_pretrained(MODEL_ID) | |
| mdl.eval() | |
| # Chargement du modèle "non entraîné" : corps pré-entraîné + tête aléatoire | |
| # On utilise from_pretrained(BASE_MODEL, num_labels=2) — la tête sera initialisée aléatoirement | |
| mdl_head_random = AutoModelForSequenceClassification.from_pretrained( | |
| BASE_MODEL, num_labels=2 | |
| ) | |
| mdl_head_random.eval() | |
| # --- Prediction utilities --- | |
| def predict_proba_from_model( | |
| model: AutoModelForSequenceClassification, text: str | |
| ) -> Dict[str, float]: | |
| """Return probability distribution over labels for a given text and model.""" | |
| inputs = tok(text, return_tensors="pt", truncation=True) | |
| # Si tu veux forcer CPU (par ex. sur un HF Space sans GPU), pas de .to(device) ici | |
| with torch.no_grad(): | |
| logits = model(**inputs).logits | |
| probs = torch.softmax(logits, dim=-1).squeeze().tolist() | |
| # Si modèle renvoie scalaire pour un seul exemple, ensure list | |
| if isinstance(probs, float): | |
| probs = [probs] | |
| return {LABELS[i]: float(probs[i]) for i in range(len(LABELS))} | |
| def top_label_phrase(probs: Dict[str, float]) -> str: | |
| """ | |
| Transforme les probabilités en phrase demandée. | |
| Exemple de sortie : | |
| "Avis positif à 95.00% de probabilité" | |
| """ | |
| pos_prob = probs.get("pos", 0.0) | |
| neg_prob = probs.get("neg", 0.0) | |
| if pos_prob >= neg_prob: | |
| return f"Avis positif à {pos_prob * 100:.2f}% de probabilité" | |
| else: | |
| return f"Avis négatif à {neg_prob * 100:.2f}% de probabilité" | |
| # Fonctions exposées | |
| def predict_label_only(text: str) -> str: | |
| """Fonction legacy qui renvoie juste le label du modèle fine-tuné (compatibilité).""" | |
| probs = predict_proba_from_model(mdl, text) | |
| return max(probs.keys(), key=lambda k: probs[k]) | |
| def predict_both_phrases(text: str) -> Tuple[str, str]: | |
| """ | |
| Renvoie deux phrases formatées : | |
| - phrase pour le modèle fine-tuné (MODEL_ID) | |
| - phrase pour le modèle non-entraîné (BASE_MODEL avec tête random) | |
| """ | |
| probs_ft = predict_proba_from_model(mdl, text) | |
| probs_head_random = predict_proba_from_model(mdl_head_random, text) | |
| phrase_ft = top_label_phrase(probs_ft) | |
| phrase_head_random = top_label_phrase(probs_head_random) | |
| return phrase_ft, phrase_head_random | |
| # --- Gradio interface --- | |
| demo = gr.Interface( | |
| fn=predict_both_phrases, | |
| inputs=gr.Textbox(label="Texte (FR)", lines=4, value="Ce film est bon"), | |
| outputs=[ | |
| gr.Textbox( | |
| label=f"Modèle {BASE_MODEL} fine-tuné ({MODEL_ID})", interactive=False | |
| ), | |
| gr.Textbox( | |
| label=f"Modèle {BASE_MODEL} non-entrainé (tête random)", interactive=False | |
| ), | |
| ], | |
| examples=[ | |
| ["Ce film est une merveille, j'ai adoré !"], | |
| ["Vraiment décevant, perte de temps."], | |
| ], | |
| title="Comparaison : modèle fine-tuné vs modèle non-entrainé", | |
| description="Affiche pour chaque modèle la prédiction principale sous forme 'Avis positif/negatif à X% de probabilité'.", | |
| ) | |
| # --- FastAPI app --- | |
| app = FastAPI(title="Sentiment FR API") | |
| # Allow CORS for demo/testing purposes | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| def healthz(): | |
| return {"status": "ok", "model": MODEL_ID, "base_model_for_compare": BASE_MODEL} | |
| def predict_api(item: dict): | |
| """ | |
| Endpoint qui renvoie les probabilités pour les deux modèles. | |
| JSON attendu: {"text": "..." } | |
| Réponse: | |
| { | |
| "fine_tuned": {"neg": 0.12, "pos": 0.88}, | |
| "head_random": {"neg": 0.51, "pos": 0.49} | |
| } | |
| """ | |
| text = item.get("text", "") | |
| probs_ft = predict_proba_from_model(mdl, text) | |
| probs_head_random = predict_proba_from_model(mdl_head_random, text) | |
| return {"fine_tuned": probs_ft, "head_random": probs_head_random} | |
| # --- Mount Gradio in HF Space friendly way --- | |
| IS_HF_SPACE = os.getenv("SYSTEM") == "spaces" | |
| if IS_HF_SPACE: | |
| # In HF Space → launch Gradio only | |
| if __name__ == "__main__": | |
| demo.launch() | |
| else: | |
| # In local or else → FastAPI + Gradio mounted together | |
| mount_gradio_app(app, demo, path="/") | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=True) | |