Anwaree commited on
Commit
d7ef0ee
·
verified ·
1 Parent(s): 2dafd09

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +436 -0
app.py ADDED
@@ -0,0 +1,436 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import joblib
3
+ import numpy as np
4
+ import pandas as pd
5
+ import re
6
+ import pickle
7
+ import nltk
8
+ from nltk.corpus import stopwords
9
+ from nltk.stem import PorterStemmer
10
+
11
+ # Télécharger les ressources NLTK nécessaires
12
+ try:
13
+ nltk.data.find('corpora/stopwords')
14
+ except LookupError:
15
+ nltk.download('stopwords')
16
+
17
+ # =============================================================================
18
+ # CHARGEMENT DES MODÈLES ET CONFIGURATION
19
+ # =============================================================================
20
+
21
+ def load_models():
22
+ """Charger tous les modèles et configurations"""
23
+ try:
24
+ print("🔄 Chargement des modèles...")
25
+
26
+ # Charger le modèle et le vectorizer
27
+ model = joblib.load('spam_classifier.pkl')
28
+ vectorizer = joblib.load('tfidf_vectorizer.pkl')
29
+
30
+ # Charger la configuration de preprocessing
31
+ with open('preprocessing_config.pkl', 'rb') as f:
32
+ config = pickle.load(f)
33
+
34
+ # Recréer les outils NLTK
35
+ stop_words = set(config['stop_words'])
36
+ stemmer = PorterStemmer()
37
+
38
+ print("✅ Modèles chargés avec succès!")
39
+ return model, vectorizer, stop_words, stemmer
40
+
41
+ except FileNotFoundError as e:
42
+ print(f"❌ Fichier manquant: {e}")
43
+ print("🔧 Utilisation des paramètres par défaut...")
44
+
45
+ # Paramètres par défaut si les fichiers sont manquants
46
+ model = None
47
+ vectorizer = None
48
+ stop_words = set(stopwords.words('english'))
49
+ stemmer = PorterStemmer()
50
+
51
+ return model, vectorizer, stop_words, stemmer
52
+
53
+ except Exception as e:
54
+ print(f"❌ Erreur lors du chargement: {e}")
55
+ return None, None, None, None
56
+
57
+ # Charger tout au démarrage
58
+ MODEL, VECTORIZER, STOP_WORDS, STEMMER = load_models()
59
+
60
+ # =============================================================================
61
+ # FONCTIONS DE PREPROCESSING (IDENTIQUES À VOTRE NOTEBOOK)
62
+ # =============================================================================
63
+
64
+ def preprocess_spam(text):
65
+ """
66
+ Préprocessing spécifique au spam - EXACTEMENT comme dans votre notebook
67
+ Garde les ponctuations importantes : ! / + >
68
+ """
69
+ if pd.isna(text) or not text:
70
+ return ""
71
+
72
+ text = str(text).lower()
73
+
74
+ # Supprimer URLs, emails, téléphones
75
+ text = re.sub(r'http\S+|www\S+', '', text)
76
+ text = re.sub(r'\S+@\S+', '', text)
77
+ text = re.sub(r'\+?\d[\d -]{8,}\d', '', text)
78
+
79
+ # Garder lettres et ponctuations importantes pour spam
80
+ text = re.sub(r'[^a-z\s!/+>]', '', text)
81
+
82
+ # Stemming et suppression stopwords
83
+ if STEMMER and STOP_WORDS:
84
+ words = [STEMMER.stem(word) for word in text.split() if word not in STOP_WORDS and word.strip()]
85
+ else:
86
+ words = text.split()
87
+
88
+ return " ".join(words)
89
+
90
+ def preprocess_ham(text):
91
+ """
92
+ Préprocessing spécifique au ham - EXACTEMENT comme dans votre notebook
93
+ Supprime toute ponctuation
94
+ """
95
+ if pd.isna(text) or not text:
96
+ return ""
97
+
98
+ text = str(text).lower()
99
+
100
+ # Supprimer URLs, emails, téléphones
101
+ text = re.sub(r'http\S+|www\S+', '', text)
102
+ text = re.sub(r'\S+@\S+', '', text)
103
+ text = re.sub(r'\+?\d[\d -]{8,}\d', '', text)
104
+
105
+ # Supprimer toute ponctuation
106
+ text = re.sub(r'[^\w\s]', '', text)
107
+
108
+ # Stemming et suppression stopwords
109
+ if STEMMER and STOP_WORDS:
110
+ words = [STEMMER.stem(word) for word in text.split() if word not in STOP_WORDS and word.strip()]
111
+ else:
112
+ words = text.split()
113
+
114
+ return " ".join(words)
115
+
116
+ # =============================================================================
117
+ # FONCTION DE CLASSIFICATION PRINCIPALE
118
+ # =============================================================================
119
+
120
+ def classify_message(message, threshold=0.4):
121
+ """
122
+ Classification d'un message - BASÉE sur votre fonction test_message()
123
+ """
124
+ if not MODEL or not VECTORIZER:
125
+ return "❌ Modèle non disponible. Vérifiez que les fichiers .pkl sont uploadés."
126
+
127
+ try:
128
+ if not message or not message.strip():
129
+ return "⚠️ Veuillez entrer un message à analyser."
130
+
131
+ # Preprocessing (utiliser preprocess_spam pour tous comme dans votre notebook)
132
+ cleaned_message = preprocess_spam(message)
133
+
134
+ if not cleaned_message.strip():
135
+ return "⚠️ Le message ne contient pas de mots valides après nettoyage."
136
+
137
+ # Vectorisation TF-IDF
138
+ message_vector = VECTORIZER.transform([cleaned_message])
139
+
140
+ # Prédiction des probabilités
141
+ probabilities = MODEL.predict_proba(message_vector)[0]
142
+ prob_ham = probabilities[0]
143
+ prob_spam = probabilities[1]
144
+
145
+ # Classification selon le seuil (comme votre fonction)
146
+ prediction = 'spam' if prob_spam >= threshold else 'ham'
147
+ confidence = prob_spam if prediction == 'spam' else prob_ham
148
+
149
+ # Formatage du résultat
150
+ if prediction == 'spam':
151
+ result = "🚨 **SPAM DÉTECTÉ**\n"
152
+ icon = "🔴"
153
+ else:
154
+ result = "✅ **MESSAGE LÉGITIME**\n"
155
+ icon = "🟢"
156
+
157
+ result += f"Confiance: **{confidence:.1%}**\n\n"
158
+ result += f"📊 **Détails des probabilités:**\n"
159
+ result += f"• {icon} Ham (légitime): {prob_ham:.1%}\n"
160
+ result += f"• 🔴 Spam: {prob_spam:.1%}\n\n"
161
+ result += f"🧹 **Message nettoyé:** `{cleaned_message}`\n"
162
+ result += f"⚙️ **Seuil utilisé:** {threshold}"
163
+
164
+ return result
165
+
166
+ except Exception as e:
167
+ return f"❌ Erreur lors de l'analyse: {str(e)}"
168
+
169
+ def classify_csv_batch(file, threshold=0.4):
170
+ """
171
+ Classification par lot depuis un fichier CSV
172
+ """
173
+ if not MODEL or not VECTORIZER:
174
+ return "❌ Modèle non disponible."
175
+
176
+ try:
177
+ # Lire le fichier CSV
178
+ df = pd.read_csv(file.name)
179
+
180
+ # Vérifications
181
+ if 'message' not in df.columns:
182
+ return "❌ Le fichier CSV doit contenir une colonne 'message'"
183
+
184
+ if df.empty:
185
+ return "❌ Le fichier CSV est vide"
186
+
187
+ # Preprocessing de tous les messages
188
+ df['cleaned'] = df['message'].astype(str).apply(preprocess_spam)
189
+
190
+ # Supprimer les messages vides après nettoyage
191
+ df_filtered = df[df['cleaned'].str.strip() != ""].copy()
192
+
193
+ if df_filtered.empty:
194
+ return "❌ Aucun message valide après nettoyage"
195
+
196
+ # Vectorisation
197
+ messages_vector = VECTORIZER.transform(df_filtered['cleaned'])
198
+
199
+ # Prédictions
200
+ probabilities = MODEL.predict_proba(messages_vector)
201
+ spam_probs = probabilities[:, 1]
202
+ ham_probs = probabilities[:, 0]
203
+
204
+ # Classification avec seuil
205
+ predictions = ['spam' if prob >= threshold else 'ham' for prob in spam_probs]
206
+ confidences = [max(spam_probs[i], ham_probs[i]) for i in range(len(spam_probs))]
207
+
208
+ # Ajouter les résultats
209
+ df_filtered['prediction'] = predictions
210
+ df_filtered['confidence'] = confidences
211
+ df_filtered['prob_ham'] = ham_probs
212
+ df_filtered['prob_spam'] = spam_probs
213
+ df_filtered['threshold_used'] = threshold
214
+
215
+ # Statistiques
216
+ total_messages = len(df_filtered)
217
+ spam_count = sum(1 for pred in predictions if pred == 'spam')
218
+ ham_count = total_messages - spam_count
219
+
220
+ print(f"📊 Résultats: {spam_count} spam, {ham_count} ham sur {total_messages} messages")
221
+
222
+ # Sauvegarder
223
+ output_filename = "spam_classification_results.csv"
224
+ df_filtered.to_csv(output_filename, index=False)
225
+
226
+ return output_filename
227
+
228
+ except Exception as e:
229
+ return f"❌ Erreur lors du traitement: {str(e)}"
230
+
231
+ # =============================================================================
232
+ # INTERFACE GRADIO
233
+ # =============================================================================
234
+
235
+ def create_interface():
236
+ """Créer l'interface Gradio"""
237
+
238
+ with gr.Blocks(
239
+ title="🔍 Détecteur de Spam - ML",
240
+ theme=gr.themes.Soft(),
241
+ css="""
242
+ .gradio-container {
243
+ max-width: 1200px;
244
+ margin: auto;
245
+ }
246
+ """
247
+ ) as demo:
248
+
249
+ gr.Markdown("""
250
+ # 🔍 Détecteur de Spam Intelligent
251
+ ### Classification automatique de messages avec Machine Learning
252
+
253
+ Modèle basé sur **régression logistique** + **TF-IDF** entraîné sur des données SMS/emails réelles.
254
+ """)
255
+
256
+ # Tab 1: Classification individuelle
257
+ with gr.Tab("📝 Analyse individuelle"):
258
+ gr.Markdown("### Analysez un message texte")
259
+
260
+ with gr.Row():
261
+ with gr.Column(scale=3):
262
+ message_input = gr.Textbox(
263
+ label="📨 Message à analyser",
264
+ placeholder="Tapez ou collez votre message ici...",
265
+ lines=4,
266
+ max_lines=10
267
+ )
268
+
269
+ threshold_slider = gr.Slider(
270
+ minimum=0.1,
271
+ maximum=0.9,
272
+ value=0.4,
273
+ step=0.05,
274
+ label="🎯 Seuil de détection spam",
275
+ info="Plus bas = plus sensible aux spams"
276
+ )
277
+
278
+ analyze_btn = gr.Button(
279
+ "🔍 Analyser le message",
280
+ variant="primary",
281
+ size="lg"
282
+ )
283
+
284
+ with gr.Column(scale=3):
285
+ result_output = gr.Textbox(
286
+ label="📋 Résultat de l'analyse",
287
+ lines=12,
288
+ max_lines=20
289
+ )
290
+
291
+ # Exemples de test
292
+ gr.Markdown("### 💡 Exemples à tester")
293
+
294
+ examples_data = [
295
+ ["Salut ! Comment ça va ? On se voit ce soir pour le dîner ?"],
296
+ ["FÉLICITATIONS! Vous avez gagné 1000€! Cliquez MAINTENANT: http://win-money.fake"],
297
+ ["Rappel: RDV médecin demain 14h30. Merci de confirmer."],
298
+ ["URGENT! Your account will be closed! Click here: http://bank-fake.com"],
299
+ ["FREE iPhone 15 PRO!!! 🎉 Limited offer! Call +1-800-FAKE now!!!"],
300
+ ["Merci pour la réunion. Voici le compte-rendu en pièce jointe."],
301
+ ["WINNER! You've been selected! Claim $5000 prize NOW! Text STOP to opt out"]
302
+ ]
303
+
304
+ gr.Examples(
305
+ examples=examples_data,
306
+ inputs=message_input,
307
+ outputs=result_output,
308
+ fn=lambda msg: classify_message(msg, 0.4),
309
+ cache_examples=False
310
+ )
311
+
312
+ # Connecter les actions
313
+ analyze_btn.click(
314
+ fn=classify_message,
315
+ inputs=[message_input, threshold_slider],
316
+ outputs=result_output
317
+ )
318
+
319
+ # Tab 2: Classification par lot
320
+ with gr.Tab("📊 Analyse par lot (CSV)"):
321
+ gr.Markdown("""
322
+ ### Analysez plusieurs messages en une fois
323
+
324
+ **📋 Format CSV requis:**
325
+ - Colonne `message` avec les textes à analyser
326
+ - Encodage UTF-8 recommandé
327
+ - Une ligne par message
328
+ """)
329
+
330
+ with gr.Row():
331
+ with gr.Column():
332
+ file_input = gr.File(
333
+ label="📁 Fichier CSV à analyser",
334
+ file_types=[".csv"],
335
+ type="filepath"
336
+ )
337
+
338
+ threshold_csv = gr.Slider(
339
+ minimum=0.1,
340
+ maximum=0.9,
341
+ value=0.4,
342
+ step=0.05,
343
+ label="🎯 Seuil de détection spam",
344
+ info="Appliqué à tous les messages"
345
+ )
346
+
347
+ process_btn = gr.Button(
348
+ "🔄 Traiter le fichier",
349
+ variant="primary"
350
+ )
351
+
352
+ with gr.Column():
353
+ file_output = gr.File(
354
+ label="📥 Résultats (CSV à télécharger)",
355
+ type="filepath"
356
+ )
357
+
358
+ gr.Markdown("""
359
+ **📊 Le fichier de résultat contiendra:**
360
+ - Vos messages originaux
361
+ - Prédictions (spam/ham)
362
+ - Probabilités détaillées
363
+ - Messages nettoyés
364
+ - Niveau de confiance
365
+ """)
366
+
367
+ process_btn.click(
368
+ fn=classify_csv_batch,
369
+ inputs=[file_input, threshold_csv],
370
+ outputs=file_output
371
+ )
372
+
373
+ # Tab 3: Informations
374
+ with gr.Tab("ℹ️ À propos"):
375
+ gr.Markdown("""
376
+ ## 🤖 Informations sur le modèle
377
+
378
+ ### **Algorithme**
379
+ - **Régression Logistique** avec régularisation
380
+ - **Vectorisation TF-IDF** (1-gram et 2-grams)
381
+ - **Équilibrage SMOTE** pour gérer le déséquilibre des classes
382
+ - **5000 features** maximum avec filtrage intelligent
383
+
384
+ ### **Performance**
385
+ - Entraîné sur dataset SMS/emails réels
386
+ - Validation croisée 5-fold
387
+ - Métriques: Accuracy, F1-score, AUC-ROC
388
+ - Gestion de l'overfitting
389
+
390
+ ### **Preprocessing intelligent**
391
+ - **Spam**: Conservation ponctuations importantes (!+>)
392
+ - **Ham**: Nettoyage standard
393
+ - Suppression URLs, emails, téléphones
394
+ - Stemming + suppression stopwords anglais
395
+
396
+ ### **Classes**
397
+ - 🟢 **Ham**: Messages légitimes (conversations, notifications...)
398
+ - 🔴 **Spam**: Messages indésirables (pub, arnaques, phishing...)
399
+
400
+ ### **Utilisation du seuil**
401
+ - **0.1-0.3**: Très sensible (capture plus de spams, plus de faux positifs)
402
+ - **0.4**: Équilibré (recommandé)
403
+ - **0.5-0.7**: Conservateur (moins de faux positifs)
404
+ - **0.8-0.9**: Très conservateur (risque de manquer des spams)
405
+
406
+ ### **Conseils**
407
+ - Messages courts: classification plus difficile
408
+ - Langues: optimisé pour français/anglais
409
+ - Fautes d'orthographe: peuvent affecter la précision
410
+ - Emojis: traités comme ponctuation
411
+
412
+ ---
413
+ *Développé avec scikit-learn, NLTK et Gradio*
414
+ """)
415
+
416
+ return demo
417
+
418
+ # =============================================================================
419
+ # LANCEMENT DE L'APPLICATION
420
+ # =============================================================================
421
+
422
+ if __name__ == "__main__":
423
+ # Créer et lancer l'interface
424
+ demo = create_interface()
425
+
426
+ # Afficher l'état des modèles au démarrage
427
+ if MODEL and VECTORIZER:
428
+ print("✅ Application prête! Modèles chargés avec succès.")
429
+ else:
430
+ print("⚠️ Application en mode dégradé. Vérifiez les fichiers .pkl")
431
+
432
+ demo.launch(
433
+ share=False,
434
+ inbrowser=True,
435
+ show_error=True
436
+ )