Oxyb50410 commited on
Commit
0406bd2
·
verified ·
1 Parent(s): 88cd200

Upload 8 files

Browse files
Files changed (8) hide show
  1. README.md +246 -6
  2. app.py +139 -0
  3. build_dataframe.py +114 -0
  4. data_processing.py +102 -0
  5. generate_response.py +135 -0
  6. main.py +84 -0
  7. requirements.txt +6 -0
  8. test_app.py +132 -0
README.md CHANGED
@@ -1,13 +1,253 @@
1
  ---
2
- title: Amazon Sentiment Analysis Shirin
3
- emoji: 🐠
4
- colorFrom: red
5
- colorTo: pink
6
  sdk: gradio
7
- sdk_version: 6.0.2
8
  app_file: app.py
9
  pinned: false
10
  license: apache-2.0
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Amazon Sentiment Analysis
3
+ emoji: 🛍️
4
+ colorFrom: blue
5
+ colorTo: green
6
  sdk: gradio
7
+ sdk_version: 4.0.0
8
  app_file: app.py
9
  pinned: false
10
  license: apache-2.0
11
  ---
12
 
13
+ # 🛍️ Analyse de Sentiment d'Avis Amazon + Réponses Automatiques
14
+
15
+ ## 📋 Description du Projet
16
+
17
+ Pipeline IA complet pour l'analyse d'avis clients Amazon en français avec :
18
+ 1. **Nettoyage des textes** (suppression stopwords, ponctuation, normalisation)
19
+ 2. **Analyse de sentiment** (positif/négatif)
20
+ 3. **Génération automatique de réponses** pour les avis négatifs avec Qwen2.5-3B
21
+
22
+ ## 🎯 Objectifs Pédagogiques
23
+
24
+ Projet développé dans le cadre d'un **Master en AI Project Management** au Collège de Paris :
25
+
26
+ - Maîtrise d'un **pipeline NLP complet** (preprocessing → analyse → génération)
27
+ - Utilisation de **modèles de langage open-source** (Qwen2.5-3B-Instruct)
28
+ - **Déploiement avec CI/CD automatique** sur Hugging Face Spaces
29
+ - Application de l'**éthique IA** (transparence, explicabilité)
30
+
31
+ ## 🛠️ Technologies Utilisées
32
+
33
+ - **Modèle** : [Qwen/Qwen2.5-3B-Instruct](https://huggingface.co/Qwen/Qwen2.5-3B-Instruct)
34
+ - **Dataset** : [SetFit/amazon_reviews_multi_fr](https://huggingface.co/datasets/SetFit/amazon_reviews_multi_fr)
35
+ - **Framework UI** : Gradio 4.0+
36
+ - **Librairies** : Transformers, PyTorch, Pandas, Datasets
37
+
38
+ ## 🏗️ Architecture du Projet
39
+
40
+ ### Structure des fichiers
41
+
42
+ ```
43
+ 📦 amazon-sentiment-analysis-shirin/
44
+ ├── app.py # Interface Gradio principale
45
+ ├── data_processing.py # Nettoyage et traitement des données
46
+ ├── generate_response.py # Génération de réponses (module Qwen)
47
+ ├── build_dataframe.py # Construction du DataFrame complet
48
+ ├── main.py # Point d'entrée principal
49
+ ├── test_app.py # Tests unitaires
50
+ ├── requirements.txt # Dépendances Python
51
+ └── README.md # Documentation
52
+ ```
53
+
54
+ ### Modules
55
+
56
+ #### `data_processing.py`
57
+ Fonctions de nettoyage et traitement :
58
+ - `clean_text()` : Nettoyage des avis (stopwords, ponctuation)
59
+ - `label_to_sentiment()` : Conversion label → sentiment
60
+ - `make_fake_email()` : Génération d'emails factices
61
+
62
+ #### `generate_response.py`
63
+ Génération de réponses automatiques :
64
+ - `load_model()` : Chargement du modèle Qwen
65
+ - `generer_reponse()` : Génération de réponse pour avis négatif
66
+ - `build_reply_prompt()` : Construction du prompt
67
+
68
+ #### `build_dataframe.py`
69
+ Construction du DataFrame final :
70
+ - Chargement dataset Amazon
71
+ - Nettoyage et analyse de sentiment
72
+ - Génération de réponses pour avis négatifs
73
+ - Export CSV
74
+
75
+ #### `app.py`
76
+ Interface utilisateur Gradio :
77
+ - Saisie d'avis client
78
+ - Analyse de sentiment
79
+ - Génération de réponse (si négatif)
80
+ - Affichage des résultats
81
+
82
+ #### `main.py`
83
+ Point d'entrée avec options :
84
+ - `python main.py app` → Lance l'interface Gradio
85
+ - `python main.py build` → Construit le DataFrame
86
+ - `python main.py test` → Lance les tests unitaires
87
+
88
+ #### `test_app.py`
89
+ Tests unitaires :
90
+ - Tests de nettoyage de texte
91
+ - Tests de conversion sentiment
92
+ - Tests de génération d'emails
93
+
94
+ ## 🚀 Utilisation
95
+
96
+ ### Interface Web
97
+
98
+ 1. Entrez un avis client dans le champ de texte
99
+ 2. (Optionnel) Sélectionnez manuellement le sentiment
100
+ 3. Cliquez sur "🚀 Analyser l'avis"
101
+ 4. Consultez les résultats :
102
+ - Texte nettoyé
103
+ - Sentiment détecté
104
+ - Réponse générée (si négatif)
105
+
106
+ ### Ligne de commande
107
+
108
+ ```bash
109
+ # Lancer l'interface Gradio
110
+ python main.py app
111
+
112
+ # Construire le DataFrame complet
113
+ python main.py build
114
+
115
+ # Lancer les tests
116
+ python main.py test
117
+ ```
118
+
119
+ ### Exemples d'avis à tester
120
+
121
+ **Avis négatif :**
122
+ ```
123
+ Le produit est arrivé cassé et le service client ne répond pas. Très déçu !
124
+ ```
125
+
126
+ **Avis positif :**
127
+ ```
128
+ Excellent produit, livraison rapide et conforme à la description. Je recommande !
129
+ ```
130
+
131
+ ## 🔄 CI/CD (Intégration Continue / Déploiement Continu)
132
+
133
+ ### Pipeline CI/CD Automatique
134
+
135
+ Ce projet utilise **Hugging Face Spaces** qui intègre nativement un pipeline CI/CD complet :
136
+
137
+ ```
138
+ Code modifié → Push → Build Auto (CI) → Deploy Auto (CD) → App en ligne
139
+ ```
140
+
141
+ ### Processus d'Intégration Continue (CI)
142
+
143
+ À chaque modification du code (push Git ou upload), Hugging Face Spaces exécute automatiquement :
144
+
145
+ 1. ✅ **Détection des changements** : Trigger automatique
146
+ 2. ✅ **Parsing de requirements.txt** : Identification des dépendances
147
+ 3. ✅ **Installation des packages** : pip install automatique
148
+ 4. ✅ **Téléchargement du modèle** : Qwen2.5-3B depuis Hugging Face Hub
149
+ 5. ✅ **Vérification syntaxique** : Tests Python
150
+ 6. ✅ **Build de l'environnement** : Container Docker
151
+
152
+ ### Processus de Déploiement Continu (CD)
153
+
154
+ Si le build CI réussit :
155
+
156
+ 1. ✅ **Déploiement automatique** : Lancement de l'app Gradio
157
+ 2. ✅ **Mise à jour URL** : Application accessible immédiatement
158
+ 3. ✅ **Rolling update** : Pas de downtime
159
+ 4. ✅ **Monitoring** : Logs disponibles en temps réel
160
+
161
+ ### Avantages vs Azure App Service + GitHub Actions
162
+
163
+ | Caractéristique | Azure + GitHub Actions | Hugging Face Spaces |
164
+ |----------------|----------------------|---------------------|
165
+ | **CI/CD automatique** | ✅ Oui (via .yml) | ✅ Oui (natif) |
166
+ | **Configuration** | ⚠️ Fichiers workflow | ✅ Aucune config |
167
+ | **Coût** | ❌ CB requise | ✅ 100% gratuit |
168
+ | **Spécialisation IA** | ⚠️ Généraliste | ✅ Optimisé ML |
169
+ | **Complexité** | ⚠️ Moyenne | ✅ Simple |
170
+
171
+ **Pourquoi pas de dossier `.github/workflows/` ?**
172
+
173
+ Hugging Face Spaces intègre la CI/CD **nativement**, sans nécessiter de fichiers de configuration YAML. Le processus est entièrement automatisé et transparent.
174
+
175
+ ## ⚙️ Configuration Technique
176
+
177
+ - **Hardware** : CPU Basic (gratuit)
178
+ - **Modèle** : Chargé en FP32 pour compatibilité CPU
179
+ - **Temps de réponse** : ~10-30 secondes selon la charge
180
+ - **Build initial** : ~10-15 minutes (téléchargement modèle)
181
+ - **Builds suivants** : ~3-5 minutes (modèle en cache)
182
+
183
+ ## 🧪 Tests
184
+
185
+ Le projet inclut des tests unitaires dans `test_app.py` :
186
+
187
+ ```bash
188
+ # Lancer les tests
189
+ python test_app.py
190
+
191
+ # Ou via main.py
192
+ python main.py test
193
+ ```
194
+
195
+ Tests couverts :
196
+ - Nettoyage de texte (ponctuation, chiffres, stopwords)
197
+ - Conversion label → sentiment
198
+ - Génération d'emails factices
199
+ - Construction de prompts
200
+
201
+ ## 🎓 Contexte Académique
202
+
203
+ **Projet réalisé par** : Coralie
204
+ **Formation** : Master AI Project Management - Collège de Paris (2024-2026)
205
+ **Alternance** : AI Project Manager - IRFA Formation
206
+ **Sujet** : Pipeline NLP complet avec déploiement CI/CD
207
+
208
+ ### Choix Techniques Justifiés
209
+
210
+ **Pourquoi Hugging Face Spaces plutôt qu'Azure ?**
211
+ - ✅ Gratuit et sans carte bancaire
212
+ - ✅ Spécialisé pour les modèles IA
213
+ - ✅ CI/CD native simplifiée
214
+ - ✅ Crédibilité académique reconnue
215
+ - ✅ Cohérence (dataset et modèle déjà sur HF)
216
+
217
+ **Pourquoi Qwen2.5-3B-Instruct ?**
218
+ - ✅ Performances excellentes en français
219
+ - ✅ Taille raisonnable (3B paramètres)
220
+ - ✅ Compatible CPU
221
+ - ✅ Licence open-source
222
+
223
+ ## 🔮 Évolutions Possibles
224
+
225
+ - [ ] Migration vers ZeroGPU pour accélération
226
+ - [ ] Fine-tuning du modèle sur avis Amazon FR
227
+ - [ ] Classification multi-classes (1-5 étoiles)
228
+ - [ ] API REST pour intégration externe
229
+ - [ ] Batch processing pour volume élevé
230
+ - [ ] Dashboard de monitoring
231
+
232
+ ## 📄 Licence
233
+
234
+ Apache 2.0 - Libre d'utilisation et de modification
235
+
236
+ ## 🙏 Remerciements
237
+
238
+ - **Hugging Face** pour l'infrastructure Spaces
239
+ - **Qwen Team** pour le modèle open-source
240
+ - **SetFit** pour le dataset Amazon Reviews
241
+
242
+ ---
243
+
244
+ ## 📚 Références
245
+
246
+ - [Documentation Gradio](https://www.gradio.app/docs)
247
+ - [Qwen2.5 Model Card](https://huggingface.co/Qwen/Qwen2.5-3B-Instruct)
248
+ - [Hugging Face Spaces Guide](https://huggingface.co/docs/hub/spaces)
249
+ - [SetFit Amazon Reviews](https://huggingface.co/datasets/SetFit/amazon_reviews_multi_fr)
250
+
251
+ ---
252
+
253
+ **🔗 URL du projet** : https://huggingface.co/spaces/Oxyb50410/amazon-sentiment-analysis-shirin
app.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Interface Gradio pour l'analyse de sentiment d'avis Amazon
3
+ et la génération automatique de réponses
4
+ """
5
+
6
+ import gradio as gr
7
+ import time
8
+ from data_processing import clean_text
9
+ from generate_response import generer_reponse, load_model
10
+
11
+
12
+ # Préchargement du modèle au démarrage
13
+ print("🔄 Préchargement du modèle...")
14
+ load_model()
15
+ print("✅ Application prête !")
16
+
17
+
18
+ def analyze_review(review_text: str, manual_sentiment: str = None):
19
+ """
20
+ Analyse un avis client et génère une réponse si négatif
21
+
22
+ Args:
23
+ review_text (str): Texte de l'avis client
24
+ manual_sentiment (str): Sentiment manuel (optionnel)
25
+
26
+ Returns:
27
+ tuple: (résultat, réponse, texte_nettoyé)
28
+ """
29
+
30
+ if not review_text or review_text.strip() == "":
31
+ return "⚠️ Veuillez entrer un avis client.", "", ""
32
+
33
+ start_time = time.time()
34
+
35
+ # 1. Nettoyage du texte
36
+ texte_clean = clean_text(review_text)
37
+
38
+ # 2. Analyse de sentiment
39
+ if manual_sentiment:
40
+ sentiment = manual_sentiment.lower()
41
+ else:
42
+ # Détection automatique basique par mots-clés
43
+ negative_words = ["nul", "mauvais", "horrible", "déçu", "décevant", "pas", "rien", "cassé"]
44
+ sentiment = "negatif" if any(word in texte_clean for word in negative_words) else "positif"
45
+
46
+ # 3. Génération de réponse (uniquement si négatif)
47
+ if sentiment == "negatif":
48
+ try:
49
+ response = generer_reponse(review_text, max_tokens=100, temperature=0.7)
50
+ response_display = f"📧 **Réponse générée :**\n\n{response}"
51
+ except Exception as e:
52
+ response = f"[Erreur : {e}]"
53
+ response_display = f"❌ Erreur lors de la génération : {e}"
54
+ else:
55
+ response = ""
56
+ response_display = "✅ Avis positif - Aucune réponse nécessaire"
57
+
58
+ elapsed = time.time() - start_time
59
+
60
+ # Résultat formaté
61
+ result = f"""
62
+ ### 📊 Analyse terminée en {elapsed:.2f}s
63
+
64
+ **Texte nettoyé :** {texte_clean}
65
+
66
+ **Sentiment détecté :** {"🔴 NÉGATIF" if sentiment == "negatif" else "🟢 POSITIF"}
67
+ """
68
+
69
+ return result, response_display, texte_clean
70
+
71
+
72
+ # Interface Gradio
73
+ with gr.Blocks(title="Analyse de Sentiment Amazon + Réponses Auto", theme=gr.themes.Soft()) as demo:
74
+
75
+ gr.Markdown(
76
+ """
77
+ # 🛍️ Analyse de Sentiment d'Avis Amazon
78
+ ### Pipeline IA complet : Nettoyage + Sentiment + Génération de réponses
79
+
80
+ **Projet Master IA** - Coralie | Modèle : Qwen2.5-3B-Instruct
81
+
82
+ Ce projet utilise un **pipeline CI/CD automatique** via Hugging Face Spaces.
83
+ """
84
+ )
85
+
86
+ with gr.Row():
87
+ with gr.Column(scale=1):
88
+ review_input = gr.Textbox(
89
+ label="📝 Avis client Amazon",
90
+ placeholder="Entrez un avis client ici...",
91
+ lines=6
92
+ )
93
+
94
+ sentiment_choice = gr.Radio(
95
+ label="🎯 Sentiment (optionnel - sinon détection auto)",
96
+ choices=["Positif", "Negatif"],
97
+ value=None
98
+ )
99
+
100
+ analyze_btn = gr.Button("🚀 Analyser l'avis", variant="primary")
101
+
102
+ with gr.Column(scale=1):
103
+ result_output = gr.Markdown(label="Résultat de l'analyse")
104
+ response_output = gr.Markdown(label="Réponse générée")
105
+
106
+ with gr.Accordion("📋 Texte nettoyé (debug)", open=False):
107
+ clean_output = gr.Textbox(label="Texte après nettoyage", lines=3)
108
+
109
+ gr.Markdown(
110
+ """
111
+ ---
112
+ ### 🔍 Exemples d'avis à tester :
113
+
114
+ **Avis négatif :** "Le produit est arrivé cassé et le service client ne répond pas. Très déçu de cet achat !"
115
+
116
+ **Avis positif :** "Excellent produit, livraison rapide et conforme à la description. Je recommande !"
117
+
118
+ ---
119
+
120
+ ### 🔄 CI/CD Automatique
121
+
122
+ Cette application est déployée via **Hugging Face Spaces** avec CI/CD intégrée :
123
+ - ✅ Build automatique à chaque modification
124
+ - ✅ Tests de syntaxe et dépendances
125
+ - ✅ Déploiement continu si build OK
126
+ """
127
+ )
128
+
129
+ # Événement
130
+ analyze_btn.click(
131
+ fn=analyze_review,
132
+ inputs=[review_input, sentiment_choice],
133
+ outputs=[result_output, response_output, clean_output]
134
+ )
135
+
136
+
137
+ # Lancement
138
+ if __name__ == "__main__":
139
+ demo.launch()
build_dataframe.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Construction du DataFrame final pour le projet
3
+ Charge les données Amazon, nettoie, analyse sentiment, génère réponses
4
+ """
5
+
6
+ import pandas as pd
7
+ from datasets import load_dataset
8
+ from data_processing import clean_text, label_to_sentiment, make_fake_email
9
+ from generate_response import generer_reponse
10
+
11
+
12
+ def build_dataframe(n_samples: int = 1000, n_negative_responses: int = 100) -> pd.DataFrame:
13
+ """
14
+ Construit le DataFrame final avec sentiment et réponses
15
+
16
+ Args:
17
+ n_samples (int): Nombre d'avis à charger du dataset
18
+ n_negative_responses (int): Nombre d'avis négatifs pour lesquels générer une réponse
19
+
20
+ Returns:
21
+ pd.DataFrame: DataFrame avec colonnes texte_clean, sentiment, response, email_client
22
+ """
23
+ print(f"📊 Construction du DataFrame avec {n_samples} avis...")
24
+
25
+ # 1. Chargement du dataset
26
+ print("1️⃣ Chargement du dataset Amazon Reviews...")
27
+ dataset = load_dataset("SetFit/amazon_reviews_multi_fr", split=f"train[:{n_samples}]")
28
+ df = pd.DataFrame(dataset)[["text", "label"]].copy()
29
+ df = df.rename(columns={"text": "texte_original"})
30
+ print(f" ✅ {len(df)} avis chargés")
31
+
32
+ # 2. Nettoyage des textes
33
+ print("2️⃣ Nettoyage des textes...")
34
+ df["texte_clean"] = df["texte_original"].apply(clean_text)
35
+ print(" ✅ Textes nettoyés")
36
+
37
+ # 3. Analyse de sentiment
38
+ print("3️⃣ Analyse de sentiment...")
39
+ df["sentiment"] = df["label"].apply(label_to_sentiment)
40
+ n_positif = (df["sentiment"] == "positif").sum()
41
+ n_negatif = (df["sentiment"] == "negatif").sum()
42
+ print(f" ✅ Sentiments : {n_positif} positifs, {n_negatif} négatifs")
43
+
44
+ # 4. Génération de réponses pour avis négatifs
45
+ print(f"4️⃣ Génération de réponses pour {n_negative_responses} avis négatifs...")
46
+ df["response"] = ""
47
+
48
+ neg_df = df[df["sentiment"] == "negatif"].head(n_negative_responses)
49
+
50
+ for idx, row in neg_df.iterrows():
51
+ try:
52
+ review_text = row["texte_original"]
53
+ response = generer_reponse(review_text, max_tokens=80, temperature=0.7)
54
+ df.at[idx, "response"] = response
55
+
56
+ if (idx + 1) % 10 == 0:
57
+ print(f" ... {idx + 1}/{len(neg_df)} réponses générées")
58
+ except Exception as e:
59
+ print(f" ⚠️ Erreur pour l'avis {idx}: {e}")
60
+ df.at[idx, "response"] = "[Erreur de génération]"
61
+
62
+ print(" ✅ Réponses générées")
63
+
64
+ # 5. Ajout des emails factices
65
+ print("5️⃣ Ajout des emails clients...")
66
+ df["email_client"] = [make_fake_email(i) for i in range(1, len(df) + 1)]
67
+ print(" ✅ Emails ajoutés")
68
+
69
+ # 6. DataFrame final
70
+ df_final = df[["texte_clean", "sentiment", "response", "email_client"]].copy()
71
+
72
+ print(f"\n✅ DataFrame final construit : {len(df_final)} lignes")
73
+ print(f" - Colonnes : {list(df_final.columns)}")
74
+ print(f" - Réponses générées : {(df_final['response'] != '').sum()}")
75
+
76
+ return df_final
77
+
78
+
79
+ def save_dataframe(df: pd.DataFrame, output_path: str = "dataframe_final.csv"):
80
+ """
81
+ Sauvegarde le DataFrame en CSV
82
+
83
+ Args:
84
+ df (pd.DataFrame): DataFrame à sauvegarder
85
+ output_path (str): Chemin du fichier de sortie
86
+ """
87
+ df.to_csv(output_path, index=False)
88
+ print(f"💾 DataFrame sauvegardé : {output_path}")
89
+
90
+
91
+ if __name__ == "__main__":
92
+ print("=" * 80)
93
+ print("🚀 CONSTRUCTION DU DATAFRAME AMAZON SENTIMENT ANALYSIS")
94
+ print("=" * 80)
95
+
96
+ # Construction avec paramètres réduits pour test rapide
97
+ df = build_dataframe(n_samples=100, n_negative_responses=10)
98
+
99
+ # Affichage d'exemples
100
+ print("\n📋 Exemples d'avis négatifs avec réponses :")
101
+ print("-" * 80)
102
+ neg_with_response = df[(df["sentiment"] == "negatif") & (df["response"] != "")]
103
+ for i, row in neg_with_response.head(3).iterrows():
104
+ print(f"\nAvis {i+1}:")
105
+ print(f"Texte nettoyé : {row['texte_clean'][:100]}...")
106
+ print(f"Réponse : {row['response'][:150]}...")
107
+ print(f"Email : {row['email_client']}")
108
+
109
+ # Sauvegarde
110
+ save_dataframe(df, "dataframe_amazon_test.csv")
111
+
112
+ print("\n" + "=" * 80)
113
+ print("✅ TERMINÉ !")
114
+ print("=" * 80)
data_processing.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Module de traitement des données pour l'analyse de sentiment
3
+ Nettoyage des textes et labellisation
4
+ """
5
+
6
+ import re
7
+ import string
8
+
9
+ # Liste des stopwords français
10
+ FRENCH_STOPWORDS = {
11
+ "a", "à", "ai", "aie", "aient", "aies", "ait", "alors", "as", "au", "aucun", "aura",
12
+ "aurai", "auraient", "aurais", "aurait", "auve", "avec", "avez", "aviez", "avions",
13
+ "avoir", "avons", "bon", "car", "ce", "cela", "ces", "cet", "cette", "ceux", "chaque",
14
+ "comme", "d", "dans", "de", "des", "du", "elle", "en", "encore", "est", "et", "eu",
15
+ "fait", "faites", "fois", "ici", "il", "ils", "je", "la", "le", "les", "leur", "lui",
16
+ "mais", "me", "mes", "moi", "mon", "ne", "nos", "notre", "nous", "on", "ou", "par",
17
+ "pas", "pour", "plus", "qu", "que", "qui", "sa", "se", "ses", "son", "sur",
18
+ "ta", "te", "tes", "toi", "ton", "toujours", "tout", "tous", "très", "tu",
19
+ "un", "une", "vos", "votre", "vous", "y"
20
+ }
21
+
22
+ # Table de traduction pour remplacer la ponctuation par des espaces
23
+ PUNCT_TABLE = str.maketrans({c: " " for c in string.punctuation})
24
+
25
+
26
+ def clean_text(text: str) -> str:
27
+ """
28
+ Nettoie un texte d'avis client :
29
+ - Conversion en minuscules
30
+ - Suppression de la ponctuation
31
+ - Suppression des chiffres
32
+ - Suppression des stopwords français
33
+ - Normalisation des espaces
34
+
35
+ Args:
36
+ text (str): Texte brut à nettoyer
37
+
38
+ Returns:
39
+ str: Texte nettoyé
40
+ """
41
+ if not isinstance(text, str):
42
+ return ""
43
+
44
+ # 1. Minuscules
45
+ text = text.lower()
46
+
47
+ # 2. Suppression de la ponctuation
48
+ text = text.translate(PUNCT_TABLE)
49
+
50
+ # 3. Suppression des chiffres
51
+ text = re.sub(r"\d+", " ", text)
52
+
53
+ # 4. Normalisation des espaces
54
+ text = re.sub(r"\s+", " ", text).strip()
55
+
56
+ # 5. Suppression des stopwords
57
+ tokens = [tok for tok in text.split() if tok not in FRENCH_STOPWORDS]
58
+
59
+ return " ".join(tokens)
60
+
61
+
62
+ def label_to_sentiment(label_value: int) -> str:
63
+ """
64
+ Convertit un label numérique (1-5 étoiles) en sentiment positif/négatif
65
+
66
+ Args:
67
+ label_value (int): Note de 1 à 5 étoiles
68
+
69
+ Returns:
70
+ str: "positif" si >= 3 étoiles, "negatif" sinon
71
+ """
72
+ try:
73
+ v = int(label_value)
74
+ except Exception:
75
+ v = 0
76
+
77
+ return "positif" if v >= 3 else "negatif"
78
+
79
+
80
+ def make_fake_email(index: int) -> str:
81
+ """
82
+ Génère un email factice pour un client
83
+
84
+ Args:
85
+ index (int): Numéro du client
86
+
87
+ Returns:
88
+ str: Email au format [email protected]
89
+ """
90
+ return f"client{index:05d}@example.com"
91
+
92
+
93
+ if __name__ == "__main__":
94
+ # Tests
95
+ test_text = "Je suis TRÈS déçu de ce produit ! Il est arrivé cassé et le service client ne répond pas..."
96
+ print(f"Original : {test_text}")
97
+ print(f"Nettoyé : {clean_text(test_text)}")
98
+
99
+ print(f"\nLabel 1 → {label_to_sentiment(1)}")
100
+ print(f"Label 5 → {label_to_sentiment(5)}")
101
+
102
+ print(f"\nEmail test : {make_fake_email(42)}")
generate_response.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Module de génération de réponses automatiques pour avis négatifs
3
+ Utilise le modèle Qwen2.5-3B-Instruct
4
+ """
5
+
6
+ from transformers import AutoTokenizer, AutoModelForCausalLM
7
+ import torch
8
+
9
+ # Configuration du modèle
10
+ MODEL_NAME = "Qwen/Qwen2.5-3B-Instruct"
11
+
12
+ # Variables globales pour le modèle (chargé une seule fois)
13
+ _tokenizer = None
14
+ _model = None
15
+
16
+
17
+ def load_model():
18
+ """
19
+ Charge le modèle Qwen2.5-3B-Instruct
20
+ Appelé une seule fois au démarrage
21
+ """
22
+ global _tokenizer, _model
23
+
24
+ if _tokenizer is None or _model is None:
25
+ print(f"🔄 Chargement du modèle {MODEL_NAME}...")
26
+
27
+ _tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
28
+ _model = AutoModelForCausalLM.from_pretrained(
29
+ MODEL_NAME,
30
+ torch_dtype=torch.float32, # CPU compatible
31
+ device_map="cpu"
32
+ )
33
+
34
+ # Configuration du token de padding
35
+ if _tokenizer.pad_token is None:
36
+ _tokenizer.pad_token = _tokenizer.eos_token
37
+ _model.config.pad_token_id = _tokenizer.eos_token_id
38
+
39
+ print("✅ Modèle chargé avec succès !")
40
+
41
+ return _tokenizer, _model
42
+
43
+
44
+ def build_reply_prompt(review_text: str) -> str:
45
+ """
46
+ Construit le prompt pour générer une réponse au service client
47
+
48
+ Args:
49
+ review_text (str): Texte de l'avis client négatif
50
+
51
+ Returns:
52
+ str: Prompt formaté pour le modèle
53
+ """
54
+ prompt = (
55
+ "Tu es un agent du service client Amazon. "
56
+ "Tu dois répondre en français à un avis client NEGATIF.\n\n"
57
+ "Consignes pour ta réponse :\n"
58
+ "- rester poli et professionnel,\n"
59
+ "- reconnaître le problème,\n"
60
+ "- proposer une solution ou un contact,\n"
61
+ "- utiliser un ton empathique.\n\n"
62
+ "Avis du client :\n"
63
+ f"{review_text}\n\n"
64
+ "Réponse du service client :"
65
+ )
66
+ return prompt
67
+
68
+
69
+ def generer_reponse(review_text: str, max_tokens: int = 100, temperature: float = 0.7) -> str:
70
+ """
71
+ Génère une réponse automatique pour un avis négatif
72
+
73
+ Args:
74
+ review_text (str): Texte de l'avis client négatif
75
+ max_tokens (int): Nombre maximum de tokens à générer
76
+ temperature (float): Température pour la génération (0.0 à 1.0)
77
+
78
+ Returns:
79
+ str: Réponse générée par le modèle
80
+ """
81
+ # Charger le modèle si nécessaire
82
+ tokenizer, model = load_model()
83
+
84
+ # Construire le prompt
85
+ prompt = build_reply_prompt(review_text)
86
+
87
+ # Tokeniser
88
+ inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=512)
89
+
90
+ # Générer la réponse
91
+ with torch.no_grad():
92
+ outputs = model.generate(
93
+ inputs.input_ids,
94
+ max_new_tokens=max_tokens,
95
+ temperature=temperature,
96
+ top_p=0.9,
97
+ do_sample=True,
98
+ pad_token_id=tokenizer.pad_token_id,
99
+ eos_token_id=tokenizer.eos_token_id,
100
+ )
101
+
102
+ # Décoder la réponse complète
103
+ full_response = tokenizer.decode(outputs[0], skip_special_tokens=True)
104
+
105
+ # Extraire uniquement la réponse générée (après le prompt)
106
+ split_token = "Réponse du service client :"
107
+ if split_token in full_response:
108
+ answer = full_response.split(split_token, 1)[1].strip()
109
+ else:
110
+ answer = full_response.strip()
111
+
112
+ return answer
113
+
114
+
115
+ # Fonction alternative avec nom anglais (pour compatibilité)
116
+ def generate_response(review_text: str, max_tokens: int = 100, temperature: float = 0.7) -> str:
117
+ """Alias anglais de generer_reponse"""
118
+ return generer_reponse(review_text, max_tokens, temperature)
119
+
120
+
121
+ if __name__ == "__main__":
122
+ # Test du module
123
+ print("=== TEST DU MODULE generate_response.py ===\n")
124
+
125
+ test_reviews = [
126
+ "Le produit est arrivé cassé et le service client ne répond pas. Très déçu !",
127
+ "Horrible expérience, produit défectueux et remboursement refusé."
128
+ ]
129
+
130
+ for i, review in enumerate(test_reviews, 1):
131
+ print(f"Test {i}:")
132
+ print(f"Avis: {review}")
133
+ response = generer_reponse(review, max_tokens=80)
134
+ print(f"Réponse: {response}")
135
+ print("-" * 80)
main.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Point d'entrée principal du projet Amazon Sentiment Analysis
3
+ Peut lancer soit l'interface Gradio, soit le build du DataFrame
4
+ """
5
+
6
+ import sys
7
+ import argparse
8
+
9
+
10
+ def launch_gradio_app():
11
+ """Lance l'application Gradio"""
12
+ print("🚀 Lancement de l'interface Gradio...")
13
+ from app import demo
14
+ demo.launch()
15
+
16
+
17
+ def build_dataset():
18
+ """Construit le DataFrame complet"""
19
+ print("📊 Construction du DataFrame...")
20
+ from build_dataframe import build_dataframe, save_dataframe
21
+
22
+ # Paramètres personnalisables
23
+ n_samples = 1000
24
+ n_responses = 100
25
+
26
+ print(f"Configuration : {n_samples} avis, {n_responses} réponses générées")
27
+
28
+ df = build_dataframe(n_samples=n_samples, n_negative_responses=n_responses)
29
+ save_dataframe(df, "dataframe_final_amazon.csv")
30
+
31
+ print("✅ DataFrame construit et sauvegardé !")
32
+
33
+
34
+ def run_tests():
35
+ """Lance les tests unitaires"""
36
+ print("🧪 Lancement des tests...")
37
+ from test_app import run_all_tests
38
+
39
+ success = run_all_tests()
40
+ if success:
41
+ print("✅ Tests réussis !")
42
+ sys.exit(0)
43
+ else:
44
+ print("❌ Tests échoués")
45
+ sys.exit(1)
46
+
47
+
48
+ def main():
49
+ """Point d'entrée principal avec options en ligne de commande"""
50
+ parser = argparse.ArgumentParser(
51
+ description="Amazon Sentiment Analysis - Projet Master IA"
52
+ )
53
+
54
+ parser.add_argument(
55
+ "mode",
56
+ choices=["app", "build", "test"],
57
+ help="Mode d'exécution : app (Gradio), build (DataFrame), test (tests)"
58
+ )
59
+
60
+ args = parser.parse_args()
61
+
62
+ print("=" * 80)
63
+ print("🛍️ AMAZON SENTIMENT ANALYSIS")
64
+ print("Projet Master AI Project Management - Coralie")
65
+ print("=" * 80)
66
+ print()
67
+
68
+ if args.mode == "app":
69
+ launch_gradio_app()
70
+ elif args.mode == "build":
71
+ build_dataset()
72
+ elif args.mode == "test":
73
+ run_tests()
74
+
75
+
76
+ if __name__ == "__main__":
77
+ # Si appelé sans arguments, lance l'app Gradio par défaut
78
+ if len(sys.argv) == 1:
79
+ print("ℹ️ Aucun argument fourni, lancement de l'app Gradio...")
80
+ print(" Usage: python main.py [app|build|test]")
81
+ print()
82
+ launch_gradio_app()
83
+ else:
84
+ main()
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ gradio>=4.0.0
2
+ transformers>=4.35.0
3
+ torch>=2.0.0
4
+ accelerate>=0.24.0
5
+ datasets>=2.14.0
6
+ pandas>=2.0.0
test_app.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Tests unitaires pour le projet Amazon Sentiment Analysis
3
+ Teste les fonctions de traitement et de génération
4
+ """
5
+
6
+ import sys
7
+ import os
8
+
9
+ # Import des modules à tester
10
+ from data_processing import clean_text, label_to_sentiment, make_fake_email
11
+
12
+
13
+ def test_clean_text():
14
+ """Test de la fonction de nettoyage de texte"""
15
+ print("\n=== TEST : clean_text ===")
16
+
17
+ # Test 1 : Texte avec ponctuation
18
+ text1 = "Super produit !!! Je recommande."
19
+ result1 = clean_text(text1)
20
+ assert "super" in result1
21
+ assert "produit" in result1
22
+ assert "!" not in result1
23
+ print(f"✅ Test 1 réussi : '{text1}' → '{result1}'")
24
+
25
+ # Test 2 : Texte avec chiffres
26
+ text2 = "Livraison en 24h, excellent !"
27
+ result2 = clean_text(text2)
28
+ assert "24" not in result2
29
+ print(f"✅ Test 2 réussi : '{text2}' → '{result2}'")
30
+
31
+ # Test 3 : Stopwords supprimés
32
+ text3 = "Je suis très content de cet achat"
33
+ result3 = clean_text(text3)
34
+ assert "je" not in result3 # stopword supprimé
35
+ assert "content" in result3
36
+ print(f"✅ Test 3 réussi : '{text3}' → '{result3}'")
37
+
38
+ print("✅ Tous les tests clean_text réussis !")
39
+
40
+
41
+ def test_label_to_sentiment():
42
+ """Test de la conversion label → sentiment"""
43
+ print("\n=== TEST : label_to_sentiment ===")
44
+
45
+ # Test labels négatifs (1-2 étoiles)
46
+ assert label_to_sentiment(1) == "negatif"
47
+ assert label_to_sentiment(2) == "negatif"
48
+ print("✅ Labels 1-2 → négatif")
49
+
50
+ # Test labels positifs (3-5 étoiles)
51
+ assert label_to_sentiment(3) == "positif"
52
+ assert label_to_sentiment(4) == "positif"
53
+ assert label_to_sentiment(5) == "positif"
54
+ print("✅ Labels 3-5 → positif")
55
+
56
+ # Test valeur invalide
57
+ assert label_to_sentiment("invalid") == "negatif" # Par défaut
58
+ print("✅ Valeur invalide gérée")
59
+
60
+ print("✅ Tous les tests label_to_sentiment réussis !")
61
+
62
+
63
+ def test_make_fake_email():
64
+ """Test de la génération d'emails factices"""
65
+ print("\n=== TEST : make_fake_email ===")
66
+
67
+ # Test format
68
+ email1 = make_fake_email(1)
69
+ assert email1 == "[email protected]"
70
+ print(f"✅ Email 1 : {email1}")
71
+
72
+ email42 = make_fake_email(42)
73
+ assert email42 == "[email protected]"
74
+ assert "@example.com" in email42
75
+ print(f"✅ Email 42 : {email42}")
76
+
77
+ print("✅ Tous les tests make_fake_email réussis !")
78
+
79
+
80
+ def test_generer_response_mock():
81
+ """
82
+ Test simulé de la génération de réponse (sans charger le modèle)
83
+ Vérifie que le prompt est bien construit
84
+ """
85
+ print("\n=== TEST : build_reply_prompt ===")
86
+
87
+ from generate_response import build_reply_prompt
88
+
89
+ review = "Produit cassé"
90
+ prompt = build_reply_prompt(review)
91
+
92
+ # Vérifications
93
+ assert "service client" in prompt.lower()
94
+ assert "Produit cassé" in prompt
95
+ assert "poli et professionnel" in prompt
96
+
97
+ print("✅ Prompt correctement construit")
98
+ print(f"Extrait : {prompt[:100]}...")
99
+
100
+
101
+ def run_all_tests():
102
+ """Lance tous les tests"""
103
+ print("=" * 80)
104
+ print("🧪 LANCEMENT DES TESTS UNITAIRES")
105
+ print("=" * 80)
106
+
107
+ try:
108
+ test_clean_text()
109
+ test_label_to_sentiment()
110
+ test_make_fake_email()
111
+ test_generer_response_mock()
112
+
113
+ print("\n" + "=" * 80)
114
+ print("🎉 TOUS LES TESTS ONT RÉUSSI !")
115
+ print("=" * 80)
116
+ return True
117
+
118
+ except AssertionError as e:
119
+ print("\n" + "=" * 80)
120
+ print(f"❌ ÉCHEC DES TESTS : {e}")
121
+ print("=" * 80)
122
+ return False
123
+ except Exception as e:
124
+ print("\n" + "=" * 80)
125
+ print(f"❌ ERREUR LORS DES TESTS : {e}")
126
+ print("=" * 80)
127
+ return False
128
+
129
+
130
+ if __name__ == "__main__":
131
+ success = run_all_tests()
132
+ sys.exit(0 if success else 1)