Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import joblib | |
| import numpy as np | |
| import pandas as pd | |
| import re | |
| import pickle | |
| import nltk | |
| from nltk.corpus import stopwords | |
| from nltk.stem import PorterStemmer | |
| # Télécharger les ressources NLTK nécessaires | |
| try: | |
| nltk.data.find('corpora/stopwords') | |
| except LookupError: | |
| nltk.download('stopwords') | |
| # ============================================================================= | |
| # CHARGEMENT DES MODÈLES ET CONFIGURATION | |
| # ============================================================================= | |
| def load_models(): | |
| """Charger tous les modèles et configurations""" | |
| try: | |
| print("🔄 Chargement des modèles...") | |
| # Charger le modèle et le vectorizer | |
| model = joblib.load('spam_classifier.pkl') | |
| vectorizer = joblib.load('tfidf_vectorizer.pkl') | |
| # Charger la configuration de preprocessing | |
| with open('preprocessing_config.pkl', 'rb') as f: | |
| config = pickle.load(f) | |
| # Recréer les outils NLTK | |
| stop_words = set(config['stop_words']) | |
| stemmer = PorterStemmer() | |
| print("✅ Modèles chargés avec succès!") | |
| return model, vectorizer, stop_words, stemmer | |
| except FileNotFoundError as e: | |
| print(f"❌ Fichier manquant: {e}") | |
| print("🔧 Utilisation des paramètres par défaut...") | |
| # Paramètres par défaut si les fichiers sont manquants | |
| model = None | |
| vectorizer = None | |
| stop_words = set(stopwords.words('english')) | |
| stemmer = PorterStemmer() | |
| return model, vectorizer, stop_words, stemmer | |
| except Exception as e: | |
| print(f"❌ Erreur lors du chargement: {e}") | |
| return None, None, None, None | |
| # Charger tout au démarrage | |
| MODEL, VECTORIZER, STOP_WORDS, STEMMER = load_models() | |
| # ============================================================================= | |
| # FONCTIONS DE PREPROCESSING (IDENTIQUES À VOTRE NOTEBOOK) | |
| # ============================================================================= | |
| def preprocess_spam(text): | |
| """ | |
| Préprocessing spécifique au spam - EXACTEMENT comme dans votre notebook | |
| Garde les ponctuations importantes : ! / + > | |
| """ | |
| if pd.isna(text) or not text: | |
| return "" | |
| text = str(text).lower() | |
| # Supprimer URLs, emails, téléphones | |
| text = re.sub(r'http\S+|www\S+', '', text) | |
| text = re.sub(r'\S+@\S+', '', text) | |
| text = re.sub(r'\+?\d[\d -]{8,}\d', '', text) | |
| # Garder lettres et ponctuations importantes pour spam | |
| text = re.sub(r'[^a-z\s!/+>]', '', text) | |
| # Stemming et suppression stopwords | |
| if STEMMER and STOP_WORDS: | |
| words = [STEMMER.stem(word) for word in text.split() if word not in STOP_WORDS and word.strip()] | |
| else: | |
| words = text.split() | |
| return " ".join(words) | |
| def preprocess_ham(text): | |
| """ | |
| Préprocessing spécifique au ham - EXACTEMENT comme dans votre notebook | |
| Supprime toute ponctuation | |
| """ | |
| if pd.isna(text) or not text: | |
| return "" | |
| text = str(text).lower() | |
| # Supprimer URLs, emails, téléphones | |
| text = re.sub(r'http\S+|www\S+', '', text) | |
| text = re.sub(r'\S+@\S+', '', text) | |
| text = re.sub(r'\+?\d[\d -]{8,}\d', '', text) | |
| # Supprimer toute ponctuation | |
| text = re.sub(r'[^\w\s]', '', text) | |
| # Stemming et suppression stopwords | |
| if STEMMER and STOP_WORDS: | |
| words = [STEMMER.stem(word) for word in text.split() if word not in STOP_WORDS and word.strip()] | |
| else: | |
| words = text.split() | |
| return " ".join(words) | |
| # ============================================================================= | |
| # FONCTION DE CLASSIFICATION PRINCIPALE | |
| # ============================================================================= | |
| def classify_message(message, threshold=0.4): | |
| """ | |
| Classification d'un message - BASÉE sur votre fonction test_message() | |
| """ | |
| if not MODEL or not VECTORIZER: | |
| return "❌ Modèle non disponible. Vérifiez que les fichiers .pkl sont uploadés." | |
| try: | |
| if not message or not message.strip(): | |
| return "⚠️ Veuillez entrer un message à analyser." | |
| # Preprocessing (utiliser preprocess_spam pour tous comme dans votre notebook) | |
| cleaned_message = preprocess_spam(message) | |
| if not cleaned_message.strip(): | |
| return "⚠️ Le message ne contient pas de mots valides après nettoyage." | |
| # Vectorisation TF-IDF | |
| message_vector = VECTORIZER.transform([cleaned_message]) | |
| # Prédiction des probabilités | |
| probabilities = MODEL.predict_proba(message_vector)[0] | |
| prob_ham = probabilities[0] | |
| prob_spam = probabilities[1] | |
| # Classification selon le seuil (comme votre fonction) | |
| prediction = 'spam' if prob_spam >= threshold else 'ham' | |
| confidence = prob_spam if prediction == 'spam' else prob_ham | |
| # Formatage du résultat | |
| if prediction == 'spam': | |
| result = "🚨 **SPAM DÉTECTÉ**\n" | |
| icon = "🔴" | |
| else: | |
| result = "✅ **MESSAGE LÉGITIME**\n" | |
| icon = "🟢" | |
| result += f"Confiance: **{confidence:.1%}**\n\n" | |
| result += f"📊 **Détails des probabilités:**\n" | |
| result += f"• {icon} Ham (légitime): {prob_ham:.1%}\n" | |
| result += f"• 🔴 Spam: {prob_spam:.1%}\n\n" | |
| result += f"🧹 **Message nettoyé:** `{cleaned_message}`\n" | |
| result += f"⚙️ **Seuil utilisé:** {threshold}" | |
| return result | |
| except Exception as e: | |
| return f"❌ Erreur lors de l'analyse: {str(e)}" | |
| def classify_csv_batch(file, threshold=0.4): | |
| """ | |
| Classification par lot depuis un fichier CSV | |
| """ | |
| if not MODEL or not VECTORIZER: | |
| return "❌ Modèle non disponible." | |
| try: | |
| # Lire le fichier CSV | |
| df = pd.read_csv(file.name) | |
| # Vérifications | |
| if 'message' not in df.columns: | |
| return "❌ Le fichier CSV doit contenir une colonne 'message'" | |
| if df.empty: | |
| return "❌ Le fichier CSV est vide" | |
| # Preprocessing de tous les messages | |
| df['cleaned'] = df['message'].astype(str).apply(preprocess_spam) | |
| # Supprimer les messages vides après nettoyage | |
| df_filtered = df[df['cleaned'].str.strip() != ""].copy() | |
| if df_filtered.empty: | |
| return "❌ Aucun message valide après nettoyage" | |
| # Vectorisation | |
| messages_vector = VECTORIZER.transform(df_filtered['cleaned']) | |
| # Prédictions | |
| probabilities = MODEL.predict_proba(messages_vector) | |
| spam_probs = probabilities[:, 1] | |
| ham_probs = probabilities[:, 0] | |
| # Classification avec seuil | |
| predictions = ['spam' if prob >= threshold else 'ham' for prob in spam_probs] | |
| confidences = [max(spam_probs[i], ham_probs[i]) for i in range(len(spam_probs))] | |
| # Ajouter les résultats | |
| df_filtered['prediction'] = predictions | |
| df_filtered['confidence'] = confidences | |
| df_filtered['prob_ham'] = ham_probs | |
| df_filtered['prob_spam'] = spam_probs | |
| df_filtered['threshold_used'] = threshold | |
| # Statistiques | |
| total_messages = len(df_filtered) | |
| spam_count = sum(1 for pred in predictions if pred == 'spam') | |
| ham_count = total_messages - spam_count | |
| print(f"📊 Résultats: {spam_count} spam, {ham_count} ham sur {total_messages} messages") | |
| # Sauvegarder | |
| output_filename = "spam_classification_results.csv" | |
| df_filtered.to_csv(output_filename, index=False) | |
| return output_filename | |
| except Exception as e: | |
| return f"❌ Erreur lors du traitement: {str(e)}" | |
| # ============================================================================= | |
| # INTERFACE GRADIO | |
| # ============================================================================= | |
| def create_interface(): | |
| """Créer l'interface Gradio""" | |
| with gr.Blocks( | |
| title="🔍 Détecteur de Spam - ML", | |
| theme=gr.themes.Soft(), | |
| css=""" | |
| .gradio-container { | |
| max-width: 1200px; | |
| margin: auto; | |
| } | |
| """ | |
| ) as demo: | |
| gr.Markdown(""" | |
| # 🔍 Détecteur de Spam Intelligent | |
| ### Classification automatique de messages avec Machine Learning | |
| Modèle basé sur **régression logistique** + **TF-IDF** entraîné sur des données SMS/emails réelles. | |
| """) | |
| # Tab 1: Classification individuelle | |
| with gr.Tab("📝 Analyse individuelle"): | |
| gr.Markdown("### Analysez un message texte") | |
| with gr.Row(): | |
| with gr.Column(scale=3): | |
| message_input = gr.Textbox( | |
| label="📨 Message à analyser", | |
| placeholder="Tapez ou collez votre message ici...", | |
| lines=4, | |
| max_lines=10 | |
| ) | |
| threshold_slider = gr.Slider( | |
| minimum=0.1, | |
| maximum=0.9, | |
| value=0.4, | |
| step=0.05, | |
| label="🎯 Seuil de détection spam", | |
| info="Plus bas = plus sensible aux spams" | |
| ) | |
| analyze_btn = gr.Button( | |
| "🔍 Analyser le message", | |
| variant="primary", | |
| size="lg" | |
| ) | |
| with gr.Column(scale=3): | |
| result_output = gr.Textbox( | |
| label="📋 Résultat de l'analyse", | |
| lines=12, | |
| max_lines=20 | |
| ) | |
| # Exemples de test | |
| gr.Markdown("### 💡 Exemples à tester") | |
| examples_data = [ | |
| ["Salut ! Comment ça va ? On se voit ce soir pour le dîner ?"], | |
| ["FÉLICITATIONS! Vous avez gagné 1000€! Cliquez MAINTENANT: http://win-money.fake"], | |
| ["Rappel: RDV médecin demain 14h30. Merci de confirmer."], | |
| ["URGENT! Your account will be closed! Click here: http://bank-fake.com"], | |
| ["FREE iPhone 15 PRO!!! 🎉 Limited offer! Call +1-800-FAKE now!!!"], | |
| ["Merci pour la réunion. Voici le compte-rendu en pièce jointe."], | |
| ["WINNER! You've been selected! Claim $5000 prize NOW! Text STOP to opt out"] | |
| ] | |
| gr.Examples( | |
| examples=examples_data, | |
| inputs=message_input, | |
| outputs=result_output, | |
| fn=lambda msg: classify_message(msg, 0.4), | |
| cache_examples=False | |
| ) | |
| # Connecter les actions | |
| analyze_btn.click( | |
| fn=classify_message, | |
| inputs=[message_input, threshold_slider], | |
| outputs=result_output | |
| ) | |
| # Tab 2: Classification par lot | |
| with gr.Tab("📊 Analyse par lot (CSV)"): | |
| gr.Markdown(""" | |
| ### Analysez plusieurs messages en une fois | |
| **📋 Format CSV requis:** | |
| - Colonne `message` avec les textes à analyser | |
| - Encodage UTF-8 recommandé | |
| - Une ligne par message | |
| """) | |
| with gr.Row(): | |
| with gr.Column(): | |
| file_input = gr.File( | |
| label="📁 Fichier CSV à analyser", | |
| file_types=[".csv"], | |
| type="filepath" | |
| ) | |
| threshold_csv = gr.Slider( | |
| minimum=0.1, | |
| maximum=0.9, | |
| value=0.4, | |
| step=0.05, | |
| label="🎯 Seuil de détection spam", | |
| info="Appliqué à tous les messages" | |
| ) | |
| process_btn = gr.Button( | |
| "🔄 Traiter le fichier", | |
| variant="primary" | |
| ) | |
| with gr.Column(): | |
| file_output = gr.File( | |
| label="📥 Résultats (CSV à télécharger)", | |
| type="filepath" | |
| ) | |
| gr.Markdown(""" | |
| **📊 Le fichier de résultat contiendra:** | |
| - Vos messages originaux | |
| - Prédictions (spam/ham) | |
| - Probabilités détaillées | |
| - Messages nettoyés | |
| - Niveau de confiance | |
| """) | |
| process_btn.click( | |
| fn=classify_csv_batch, | |
| inputs=[file_input, threshold_csv], | |
| outputs=file_output | |
| ) | |
| # Tab 3: Informations | |
| with gr.Tab("ℹ️ À propos"): | |
| gr.Markdown(""" | |
| ## 🤖 Informations sur le modèle | |
| ### **Algorithme** | |
| - **Régression Logistique** avec régularisation | |
| - **Vectorisation TF-IDF** (1-gram et 2-grams) | |
| - **Équilibrage SMOTE** pour gérer le déséquilibre des classes | |
| - **5000 features** maximum avec filtrage intelligent | |
| ### **Performance** | |
| - Entraîné sur dataset SMS/emails réels | |
| - Validation croisée 5-fold | |
| - Métriques: Accuracy, F1-score, AUC-ROC | |
| - Gestion de l'overfitting | |
| ### **Preprocessing intelligent** | |
| - **Spam**: Conservation ponctuations importantes (!+>) | |
| - **Ham**: Nettoyage standard | |
| - Suppression URLs, emails, téléphones | |
| - Stemming + suppression stopwords anglais | |
| ### **Classes** | |
| - 🟢 **Ham**: Messages légitimes (conversations, notifications...) | |
| - 🔴 **Spam**: Messages indésirables (pub, arnaques, phishing...) | |
| ### **Utilisation du seuil** | |
| - **0.1-0.3**: Très sensible (capture plus de spams, plus de faux positifs) | |
| - **0.4**: Équilibré (recommandé) | |
| - **0.5-0.7**: Conservateur (moins de faux positifs) | |
| - **0.8-0.9**: Très conservateur (risque de manquer des spams) | |
| ### **Conseils** | |
| - Messages courts: classification plus difficile | |
| - Langues: optimisé pour français/anglais | |
| - Fautes d'orthographe: peuvent affecter la précision | |
| - Emojis: traités comme ponctuation | |
| --- | |
| *Développé avec scikit-learn, NLTK et Gradio* | |
| """) | |
| return demo | |
| # ============================================================================= | |
| # LANCEMENT DE L'APPLICATION | |
| # ============================================================================= | |
| if __name__ == "__main__": | |
| # Créer et lancer l'interface | |
| demo = create_interface() | |
| # Afficher l'état des modèles au démarrage | |
| if MODEL and VECTORIZER: | |
| print("✅ Application prête! Modèles chargés avec succès.") | |
| else: | |
| print("⚠️ Application en mode dégradé. Vérifiez les fichiers .pkl") | |
| demo.launch( | |
| share=False, | |
| inbrowser=True, | |
| show_error=True | |
| ) |