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 )