DavidJyes commited on
Commit
d5b0b0e
·
verified ·
1 Parent(s): af5af59

upload files

Browse files
Dockerfile ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ EXPOSE 7860
11
+
12
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Fraud Detection API
3
+ emoji: 🛡️
4
+ colorFrom: red
5
+ colorTo: blue
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ ---
10
+
11
+ # 🛡️ API de Détection de Fraude
12
+
13
+ API de détection de fraude dans les transactions bancaires utilisant un modèle Random Forest.
14
+
15
+ ## 🚀 Utilisation
16
+
17
+ ### Endpoint principal: `/predict`
18
+ ```bash
19
+ curl -X POST "https://votre-space.hf.space/predict" \
20
+ -H "Content-Type: application/json" \
21
+ -d '{
22
+ "amt": 125.50,
23
+ "category": "personal_care",
24
+ "merchant": "fraud_Kirlin and Sons",
25
+ "trans_date_trans_time": "2020-06-21 12:14:25",
26
+ "gender": "M",
27
+ "state": "SC",
28
+ "lat": 33.9659,
29
+ "long": -80.9355,
30
+ "city_pop": 333497,
31
+ "dob": "1968-03-19",
32
+ "merch_lat": 33.986391,
33
+ "merch_long": -81.200714
34
+ }'
35
+ ```
36
+
37
+ ### Réponse
38
+ ```json
39
+ {
40
+ "is_fraud": false,
41
+ "fraud_probability": 0.23,
42
+ "risk_level": "Faible",
43
+ "details": {
44
+ "montant": 125.50,
45
+ "categorie": "personal_care",
46
+ "heure": 12,
47
+ "age_client": 52,
48
+ "distance_km": 25.4
49
+ }
50
+ }
51
+ ```
52
+
53
+ ## 📊 Endpoints disponibles
54
+
55
+ - `GET /` - Informations sur l'API
56
+ - `GET /health` - État de santé
57
+ - `GET /categories` - Liste des catégories valides
58
+ - `POST /predict` - Prédiction de fraude
59
+ - `GET /docs` - Documentation interactive (Swagger)
60
+
61
+ ## 🔧 Modèle
62
+
63
+ - **Algorithme**: Random Forest Classifier
64
+ - **Features**: 16 caractéristiques (montant, heure, distance, historique client...)
65
+ - **Performance**: AUC-ROC > 0.95
66
+
67
+ ## 📝 Licence
68
+
69
+ MIT
app.py ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from fastapi import FastAPI, HTTPException
3
+ from pydantic import BaseModel, Field
4
+ import joblib
5
+ import json
6
+ import numpy as np
7
+ from datetime import datetime
8
+ from typing import Optional
9
+ import os
10
+
11
+ # Initialisation
12
+ app = FastAPI(
13
+ title="API Détection de Fraude",
14
+ description="API de détection de fraude dans les transactions bancaires",
15
+ version="1.0.0"
16
+ )
17
+
18
+ # Chargement du modèle et des encodeurs
19
+ model = joblib.load('fraud_model.pkl')
20
+ le_category = joblib.load('le_category.pkl')
21
+ le_gender = joblib.load('le_gender.pkl')
22
+ le_state = joblib.load('le_state.pkl')
23
+
24
+ with open('mappings.json', 'r') as f:
25
+ mappings = json.load(f)
26
+
27
+ # Modèle de données
28
+ class Transaction(BaseModel):
29
+ amt: float = Field(..., description="Montant de la transaction", example=125.50)
30
+ category: str = Field(..., description="Catégorie du marchand", example="personal_care")
31
+ merchant: str = Field(..., description="Nom du marchand", example="fraud_Kirlin and Sons")
32
+ trans_date_trans_time: str = Field(..., description="Date et heure", example="2020-06-21 12:14:25")
33
+ gender: str = Field(..., description="Genre (M/F)", example="M")
34
+ state: str = Field(..., description="État (code à 2 lettres)", example="SC")
35
+ lat: float = Field(..., description="Latitude du client", example=33.9659)
36
+ long: float = Field(..., description="Longitude du client", example=-80.9355)
37
+ city_pop: int = Field(..., description="Population de la ville", example=333497)
38
+ dob: str = Field(..., description="Date de naissance", example="1968-03-19")
39
+ merch_lat: float = Field(..., description="Latitude du marchand", example=33.986391)
40
+ merch_long: float = Field(..., description="Longitude du marchand", example=-81.200714)
41
+ cc_num: Optional[str] = Field(None, description="Numéro de carte (optionnel)")
42
+ avg_amt: Optional[float] = Field(50.0, description="Montant moyen historique")
43
+ std_amt: Optional[float] = Field(30.0, description="Écart-type historique")
44
+ nb_trans: Optional[int] = Field(10, description="Nombre de transactions historiques")
45
+
46
+ class PredictionResponse(BaseModel):
47
+ is_fraud: bool
48
+ fraud_probability: float
49
+ risk_level: str
50
+ details: dict
51
+
52
+ # Routes
53
+ @app.get("/")
54
+ def read_root():
55
+ return {
56
+ "message": "API de Détection de Fraude",
57
+ "version": "1.0.0",
58
+ "endpoints": {
59
+ "/predict": "POST - Prédire une transaction",
60
+ "/health": "GET - Statut de l'API",
61
+ "/categories": "GET - Liste des catégories",
62
+ "/docs": "GET - Documentation interactive"
63
+ }
64
+ }
65
+
66
+ @app.get("/health")
67
+ def health_check():
68
+ return {
69
+ "status": "healthy",
70
+ "model_loaded": model is not None,
71
+ "timestamp": datetime.now().isoformat()
72
+ }
73
+
74
+ @app.get("/categories")
75
+ def get_categories():
76
+ return {
77
+ "categories": list(mappings['categories'].keys()),
78
+ "states": list(mappings['states'].keys()),
79
+ "genders": list(mappings['genders'].keys())
80
+ }
81
+
82
+ @app.post("/predict", response_model=PredictionResponse)
83
+ def predict_fraud(transaction: Transaction):
84
+ try:
85
+ # Parsing des dates
86
+ trans_dt = datetime.strptime(transaction.trans_date_trans_time, "%Y-%m-%d %H:%M:%S")
87
+ dob_dt = datetime.strptime(transaction.dob, "%Y-%m-%d")
88
+
89
+ # Features temporelles
90
+ hour = trans_dt.hour
91
+ day_of_week = trans_dt.weekday()
92
+ day = trans_dt.day
93
+ month = trans_dt.month
94
+ age = (trans_dt - dob_dt).days // 365
95
+
96
+ # Distance
97
+ distance = np.sqrt(
98
+ (transaction.lat - transaction.merch_lat)**2 +
99
+ (transaction.long - transaction.merch_long)**2
100
+ ) * 111
101
+
102
+ # Encodage
103
+ if transaction.category not in mappings['categories']:
104
+ raise HTTPException(400, f"Catégorie inconnue: {transaction.category}")
105
+ if transaction.gender not in mappings['genders']:
106
+ raise HTTPException(400, f"Genre inconnu: {transaction.gender}")
107
+ if transaction.state not in mappings['states']:
108
+ raise HTTPException(400, f"État inconnu: {transaction.state}")
109
+
110
+ category_encoded = mappings['categories'][transaction.category]
111
+ gender_encoded = mappings['genders'][transaction.gender]
112
+ state_encoded = mappings['states'][transaction.state]
113
+
114
+ # Construction du vecteur de features
115
+ features = np.array([[
116
+ transaction.amt,
117
+ hour,
118
+ day_of_week,
119
+ day,
120
+ month,
121
+ age,
122
+ category_encoded,
123
+ gender_encoded,
124
+ state_encoded,
125
+ transaction.lat,
126
+ transaction.long,
127
+ transaction.city_pop,
128
+ distance,
129
+ transaction.avg_amt,
130
+ transaction.std_amt,
131
+ transaction.nb_trans
132
+ ]])
133
+
134
+ # Prédiction
135
+ fraud_proba = model.predict_proba(features)[0][1]
136
+ is_fraud = fraud_proba > 0.5
137
+
138
+ # Niveau de risque
139
+ if fraud_proba < 0.3:
140
+ risk_level = "Faible"
141
+ elif fraud_proba < 0.7:
142
+ risk_level = "Moyen"
143
+ else:
144
+ risk_level = "Élevé"
145
+
146
+ return PredictionResponse(
147
+ is_fraud=bool(is_fraud),
148
+ fraud_probability=float(fraud_proba),
149
+ risk_level=risk_level,
150
+ details={
151
+ "montant": transaction.amt,
152
+ "categorie": transaction.category,
153
+ "heure": hour,
154
+ "age_client": age,
155
+ "distance_km": round(distance, 2)
156
+ }
157
+ )
158
+
159
+ except ValueError as e:
160
+ raise HTTPException(status_code=400, detail=f"Erreur de format: {str(e)}")
161
+ except Exception as e:
162
+ raise HTTPException(status_code=500, detail=f"Erreur interne: {str(e)}")
163
+
164
+ if __name__ == "__main__":
165
+ import uvicorn
166
+ uvicorn.run(app, host="0.0.0.0", port=7860)
mappings.json ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "categories": {
3
+ "entertainment": 0,
4
+ "food_dining": 1,
5
+ "gas_transport": 2,
6
+ "grocery_net": 3,
7
+ "grocery_pos": 4,
8
+ "health_fitness": 5,
9
+ "home": 6,
10
+ "kids_pets": 7,
11
+ "misc_net": 8,
12
+ "misc_pos": 9,
13
+ "personal_care": 10,
14
+ "shopping_net": 11,
15
+ "shopping_pos": 12,
16
+ "travel": 13
17
+ },
18
+ "genders": {
19
+ "F": 0,
20
+ "M": 1
21
+ },
22
+ "states": {
23
+ "AK": 0,
24
+ "AL": 1,
25
+ "AR": 2,
26
+ "AZ": 3,
27
+ "CA": 4,
28
+ "CO": 5,
29
+ "CT": 6,
30
+ "DC": 7,
31
+ "FL": 8,
32
+ "GA": 9,
33
+ "HI": 10,
34
+ "IA": 11,
35
+ "ID": 12,
36
+ "IL": 13,
37
+ "IN": 14,
38
+ "KS": 15,
39
+ "KY": 16,
40
+ "LA": 17,
41
+ "MA": 18,
42
+ "MD": 19,
43
+ "ME": 20,
44
+ "MI": 21,
45
+ "MN": 22,
46
+ "MO": 23,
47
+ "MS": 24,
48
+ "MT": 25,
49
+ "NC": 26,
50
+ "ND": 27,
51
+ "NE": 28,
52
+ "NH": 29,
53
+ "NJ": 30,
54
+ "NM": 31,
55
+ "NV": 32,
56
+ "NY": 33,
57
+ "OH": 34,
58
+ "OK": 35,
59
+ "OR": 36,
60
+ "PA": 37,
61
+ "RI": 38,
62
+ "SC": 39,
63
+ "SD": 40,
64
+ "TN": 41,
65
+ "TX": 42,
66
+ "UT": 43,
67
+ "VA": 44,
68
+ "VT": 45,
69
+ "WA": 46,
70
+ "WI": 47,
71
+ "WV": 48,
72
+ "WY": 49
73
+ },
74
+ "features": [
75
+ "amt",
76
+ "hour",
77
+ "day_of_week",
78
+ "day",
79
+ "month",
80
+ "age",
81
+ "category_encoded",
82
+ "gender_encoded",
83
+ "state_encoded",
84
+ "lat",
85
+ "long",
86
+ "city_pop",
87
+ "distance",
88
+ "avg_amt",
89
+ "std_amt",
90
+ "nb_trans"
91
+ ]
92
+ }
model_pkl/fraud_model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:42d2fa79a92b4b2268e5b4c8816df246c5ff7e9310bc36ad09780ddad495f420
3
+ size 3633545
model_pkl/le_category.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:109f0b498d8a270af6195f17d6e9116fe3bfe8fe7af72497162807f4dfa01f94
3
+ size 660
model_pkl/le_gender.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1b8403e7d7f991dc10fe2aa945dfaad26b29d26bd103f081f1c6086deb036368
3
+ size 481
model_pkl/le_state.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c7f746d39c9ad90215b3ac088e59c6b4a04e58ef602131ab6fb45216e64a1245
3
+ size 723
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn[standard]==0.24.0
3
+ pydantic==2.5.0
4
+ scikit-learn==1.3.2
5
+ joblib==1.3.2
6
+ numpy==1.24.3
7
+ pandas==2.1.3
train_and_save_model.py ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import numpy as np
3
+ from sklearn.model_selection import train_test_split
4
+ from sklearn.preprocessing import StandardScaler, LabelEncoder
5
+ from sklearn.ensemble import RandomForestClassifier
6
+ import joblib
7
+ import json
8
+
9
+ print("Chargement et préparation des données...")
10
+
11
+ # Chargement des données
12
+ df = pd.read_csv('/mnt/c/Users/david/Desktop/AIA 2025/BLOC_3_AUTOMATIC_FRAUD_DETECTION/fraudTest.csv', index_col=0)
13
+
14
+ # Feature Engineering
15
+ data = df.copy()
16
+ data['trans_date_trans_time'] = pd.to_datetime(data['trans_date_trans_time'])
17
+ data['hour'] = data['trans_date_trans_time'].dt.hour
18
+ data['day_of_week'] = data['trans_date_trans_time'].dt.dayofweek
19
+ data['day'] = data['trans_date_trans_time'].dt.day
20
+ data['month'] = data['trans_date_trans_time'].dt.month
21
+
22
+ data['dob'] = pd.to_datetime(data['dob'])
23
+ data['age'] = (data['trans_date_trans_time'] - data['dob']).dt.days // 365
24
+
25
+ data['distance'] = np.sqrt(
26
+ (data['lat'] - data['merch_lat'])**2 +
27
+ (data['long'] - data['merch_long'])**2
28
+ ) * 111
29
+
30
+ # Encodage
31
+ le_category = LabelEncoder()
32
+ le_gender = LabelEncoder()
33
+ le_state = LabelEncoder()
34
+
35
+ data['category_encoded'] = le_category.fit_transform(data['category'])
36
+ data['gender_encoded'] = le_gender.fit_transform(data['gender'])
37
+ data['state_encoded'] = le_state.fit_transform(data['state'])
38
+
39
+ # Statistiques par client
40
+ client_stats = data.groupby('cc_num').agg({
41
+ 'amt': ['mean', 'std', 'count'],
42
+ 'is_fraud': 'sum'
43
+ }).reset_index()
44
+ client_stats.columns = ['cc_num', 'avg_amt', 'std_amt', 'nb_trans', 'nb_fraud']
45
+ client_stats['std_amt'] = client_stats['std_amt'].fillna(0)
46
+ data = data.merge(client_stats, on='cc_num', how='left')
47
+
48
+ # Préparation des features
49
+ features = [
50
+ 'amt', 'hour', 'day_of_week', 'day', 'month', 'age',
51
+ 'category_encoded', 'gender_encoded', 'state_encoded',
52
+ 'lat', 'long', 'city_pop', 'distance',
53
+ 'avg_amt', 'std_amt', 'nb_trans'
54
+ ]
55
+
56
+ X = data[features].fillna(0)
57
+ y = data['is_fraud']
58
+
59
+ # Split et entraînement
60
+ X_train, X_test, y_train, y_test = train_test_split(
61
+ X, y, test_size=0.3, random_state=42, stratify=y
62
+ )
63
+
64
+ print("Entraînement du Random Forest...")
65
+ model = RandomForestClassifier(
66
+ n_estimators=100,
67
+ max_depth=10,
68
+ random_state=42,
69
+ n_jobs=-1
70
+ )
71
+ model.fit(X_train, y_train)
72
+
73
+ # Évaluation
74
+ from sklearn.metrics import roc_auc_score, classification_report
75
+ y_pred_proba = model.predict_proba(X_test)[:, 1]
76
+ auc = roc_auc_score(y_test, y_pred_proba)
77
+ print(f"\nAUC-ROC: {auc:.4f}")
78
+
79
+ # Sauvegarde du modèle et des encodeurs
80
+ print("\nSauvegarde du modèle et des encodeurs...")
81
+ joblib.dump(model, 'fraud_model.pkl')
82
+ joblib.dump(le_category, 'le_category.pkl')
83
+ joblib.dump(le_gender, 'le_gender.pkl')
84
+ joblib.dump(le_state, 'le_state.pkl')
85
+
86
+ # Sauvegarde des mappings pour l'API
87
+ category_mapping = dict(zip(le_category.classes_, le_category.transform(le_category.classes_)))
88
+ gender_mapping = dict(zip(le_gender.classes_, le_gender.transform(le_gender.classes_)))
89
+ state_mapping = dict(zip(le_state.classes_, le_state.transform(le_state.classes_)))
90
+
91
+ mappings = {
92
+ 'categories': category_mapping,
93
+ 'genders': gender_mapping,
94
+ 'states': state_mapping,
95
+ 'features': features
96
+ }
97
+
98
+ def convert(o):
99
+ if isinstance(o, np.integer):
100
+ return int(o)
101
+ elif isinstance(o, np.floating):
102
+ return float(o)
103
+ elif isinstance(o, np.ndarray):
104
+ return o.tolist()
105
+ else:
106
+ return o
107
+
108
+ with open('mappings.json', 'w') as f:
109
+ json.dump(mappings, f, indent=2, default=convert)
110
+
111
+ print("\n✅ Modèle sauvegardé avec succès!")
112
+ print("Fichiers créés:")
113
+ print(" - fraud_model.pkl")
114
+ print(" - le_category.pkl, le_gender.pkl, le_state.pkl")
115
+ print(" - mappings.json")