aurelien commited on
Commit
cb1109f
·
1 Parent(s): 135616e

1st commit

Browse files
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ __pycache__
2
+ data
3
+ venv
app.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from pydantic import BaseModel
3
+ from typing import List
4
+ import joblib
5
+ import pandas as pd
6
+ import numpy as np
7
+ from sentence_transformers import SentenceTransformer
8
+ from transformers import pipeline
9
+ import torch
10
+ from validate_comment_sentiment_tags import analyze_comment # ton code ci-dessus, tu peux aussi le copier ici
11
+
12
+ app = FastAPI(title="Comment Validator API")
13
+
14
+ # =====================================
15
+ # 🔹 Chargement des modèles
16
+ # =====================================
17
+
18
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
19
+ print(f"🧠 Using device: {device}")
20
+
21
+ text_model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2", device=device)
22
+ clf = joblib.load("models/classifier.joblib")
23
+ encoder = joblib.load("models/encoder.joblib")
24
+ sentiment_analyzer = pipeline("sentiment-analysis", model="nlptown/bert-base-multilingual-uncased-sentiment", device=-1)
25
+ toxicity_analyzer = pipeline("text-classification", model="unitary/toxic-bert", return_all_scores=True, device=-1)
26
+
27
+ # =====================================
28
+ # 🔸 Modèles de requête/réponse
29
+ # =====================================
30
+
31
+ class CommentRequest(BaseModel):
32
+ comment: str
33
+ category: str
34
+ country: str
35
+
36
+ class BatchRequest(BaseModel):
37
+ items: List[CommentRequest]
38
+
39
+ # =====================================
40
+ # 🔹 Routes
41
+ # =====================================
42
+
43
+ @app.post("/predict")
44
+ def predict(item: CommentRequest):
45
+ """Analyse un seul commentaire"""
46
+ result = analyze_comment(item.comment, item.category, item.country)
47
+ return result
48
+
49
+
50
+ @app.post("/batch_predict")
51
+ def batch_predict(request: BatchRequest):
52
+ """Analyse plusieurs commentaires à la fois"""
53
+ results = []
54
+ for item in request.items:
55
+ results.append(analyze_comment(item.comment, item.category, item.country))
56
+ return {"results": results}
models/classifier.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:83787fadf944c3d5d149a3b7dc251e4ffa934ead146080c6b7ed62f732f2a8a2
3
+ size 490140969
models/encoder.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0fb07d0e6818f85b30c63b9f33363b36bc77a7ef410e4a4007716dd552d9e283
3
+ size 2002
requirements.txt ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ certifi==2025.10.5
2
+ charset-normalizer==3.4.4
3
+ filelock==3.20.0
4
+ fsspec==2025.9.0
5
+ hf-xet==1.2.0
6
+ huggingface-hub==0.36.0
7
+ idna==3.11
8
+ Jinja2==3.1.6
9
+ joblib==1.5.2
10
+ MarkupSafe==3.0.3
11
+ mpmath==1.3.0
12
+ networkx==3.5
13
+ numpy==1.26.4
14
+ packaging==25.0
15
+ pandas==2.3.3
16
+ pillow==12.0.0
17
+ protobuf==6.33.0
18
+ python-dateutil==2.9.0.post0
19
+ pytz==2025.2
20
+ PyYAML==6.0.3
21
+ regex==2025.10.23
22
+ requests==2.32.5
23
+ safetensors==0.6.2
24
+ scikit-learn==1.7.2
25
+ scipy==1.16.2
26
+ sentence-transformers==5.1.2
27
+ sentencepiece==0.2.1
28
+ six==1.17.0
29
+ sympy==1.14.0
30
+ threadpoolctl==3.6.0
31
+ tiktoken==0.12.0
32
+ tokenizers==0.22.1
33
+ torch==2.2.2
34
+ torchaudio==2.2.2
35
+ torchvision==0.17.2
36
+ tqdm==4.67.1
37
+ transformers==4.57.1
38
+ typing_extensions==4.15.0
39
+ tzdata==2025.2
40
+ urllib3==2.5.0
validate_comment_sentiment_tags.py ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import joblib
3
+ import numpy as np
4
+ from sentence_transformers import SentenceTransformer
5
+ from transformers import pipeline
6
+ import torch
7
+ from tqdm import tqdm
8
+ import os
9
+
10
+ # =====================================
11
+ # 🔹 Initialisation
12
+ # =====================================
13
+
14
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
15
+ print(f"🧠 Using device: {device}")
16
+
17
+ # Modèle d'embedding
18
+ text_model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2", device=device)
19
+
20
+ # Modèle de classification & encodeur
21
+ clf = joblib.load("models/classifier.joblib")
22
+ encoder = joblib.load("models/encoder.joblib")
23
+
24
+ # Modèle de sentiment
25
+ sentiment_analyzer = pipeline(
26
+ "sentiment-analysis",
27
+ model="nlptown/bert-base-multilingual-uncased-sentiment",
28
+ device=-1
29
+ )
30
+
31
+ # Modèle de toxicité
32
+ toxicity_analyzer = pipeline(
33
+ "text-classification",
34
+ model="unitary/toxic-bert",
35
+ return_all_scores=True,
36
+ device=-1
37
+ )
38
+
39
+ # =====================================
40
+ # 🔸 Fonction d’analyse d’un commentaire
41
+ # =====================================
42
+
43
+ def analyze_comment(comment: str, category: str, country: str) -> dict:
44
+ reasons = []
45
+
46
+ # --- Analyse du sentiment ---
47
+ try:
48
+ sentiment = sentiment_analyzer(comment[:512])[0]
49
+ label = sentiment["label"]
50
+ score = sentiment["score"]
51
+ except Exception:
52
+ label, score = "unknown", 0.0
53
+
54
+ if "1" in label or "2" in label:
55
+ sentiment_score = -1
56
+ reasons.append("Le ton semble négatif ou insatisfait.")
57
+ elif "4" in label or "5" in label:
58
+ sentiment_score = 1
59
+ else:
60
+ sentiment_score = 0
61
+
62
+ # --- Encodage du texte ---
63
+ X_text = text_model.encode([comment])
64
+
65
+ # --- Encodage catégorie/pays ---
66
+ df_cat = pd.DataFrame([[category, country]], columns=["category", "country"])
67
+ try:
68
+ X_cat = encoder.transform(df_cat)
69
+ except ValueError:
70
+ reasons.append(f"Catégorie ou pays inconnus : {category}, {country}")
71
+ n_features = sum(len(cats) for cats in encoder.categories_)
72
+ X_cat = np.zeros((1, n_features))
73
+
74
+ # --- Concaténation ---
75
+ X = np.concatenate([X_text, X_cat], axis=1)
76
+
77
+ # --- Prédiction validité ---
78
+ proba = clf.predict_proba(X)[0][1]
79
+ prediction = proba >= 0.5
80
+
81
+ if len(comment.split()) < 3:
82
+ reasons.append("Le commentaire est trop court.")
83
+ if sentiment_score < 0:
84
+ reasons.append("Le ton global est négatif.")
85
+ if proba < 0.4:
86
+ reasons.append("Le modèle estime une faible probabilité de validité.")
87
+
88
+ # --- Analyse toxicité ---
89
+ try:
90
+ tox_scores = toxicity_analyzer(comment[:512])[0] # tronquer pour sécurité
91
+ tags = {f"tag_{item['label']}": round(item['score'], 3) for item in tox_scores}
92
+ except Exception:
93
+ tags = {f"tag_{label}": 0.0 for label in ["toxicity","severe_toxicity","obscene","identity_attack","insult","threat"]}
94
+
95
+ # --- Résultat final ---
96
+ result = {
97
+ "is_valid": bool(prediction),
98
+ "confidence": round(float(proba), 3),
99
+ "sentiment": label,
100
+ "sentiment_score": round(float(score), 3),
101
+ "reasons": "; ".join(reasons) if reasons else "Aucune anomalie détectée."
102
+ }
103
+
104
+ result.update(tags)
105
+ return result
106
+
107
+ # =====================================
108
+ # 🔹 Batch prediction sur CSV
109
+ # =====================================
110
+
111
+ def batch_predict(input_csv: str, output_csv: str):
112
+ if not os.path.exists(input_csv):
113
+ raise FileNotFoundError(f"Le fichier '{input_csv}' est introuvable.")
114
+
115
+ print(f"🔹 Lecture du fichier : {input_csv}")
116
+ df = pd.read_csv(input_csv)
117
+
118
+ required_cols = {"comment", "category", "country"}
119
+ if not required_cols.issubset(df.columns):
120
+ raise ValueError(f"Le fichier CSV doit contenir les colonnes : {required_cols}")
121
+
122
+ print(f"🚀 Analyse de {len(df)} commentaires...")
123
+ results = []
124
+
125
+ for _, row in tqdm(df.iterrows(), total=len(df)):
126
+ analysis = analyze_comment(
127
+ str(row["comment"]),
128
+ str(row["category"]),
129
+ str(row["country"])
130
+ )
131
+ results.append(analysis)
132
+
133
+ df_results = pd.concat([df, pd.DataFrame(results)], axis=1)
134
+ os.makedirs(os.path.dirname(output_csv), exist_ok=True)
135
+ df_results.to_csv(output_csv, index=False)
136
+ print(f"✅ Résultats sauvegardés dans : {output_csv}")
137
+
138
+ # =====================================
139
+ # 🔸 Exemple d’exécution
140
+ # =====================================
141
+
142
+ if __name__ == "__main__":
143
+ INPUT_FILE = "data/new_comments.csv"
144
+ OUTPUT_FILE = "data/predictions_with_tags.csv"
145
+ batch_predict(INPUT_FILE, OUTPUT_FILE)