File size: 7,226 Bytes
ad86b94
 
 
15ea647
ad86b94
 
 
 
 
15ea647
 
 
ad86b94
 
 
 
 
 
 
 
 
 
 
 
 
 
15ea647
 
 
 
ad86b94
15ea647
 
 
 
 
 
 
 
 
 
 
 
 
 
ad86b94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15ea647
ad86b94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15ea647
ad86b94
 
 
 
15ea647
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ad86b94
15ea647
ad86b94
 
 
 
 
 
15ea647
ad86b94
 
 
 
15ea647
 
 
 
 
 
 
 
ad86b94
 
 
 
 
 
15ea647
 
 
 
ad86b94
 
 
 
 
 
15ea647
 
 
ad86b94
15ea647
ad86b94
 
15ea647
 
 
ad86b94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# src/scripts/profile_predict.py
"""

Script de profiling pour identifier les goulots d'étranglement

lors de l'inférence (prédiction), simulant le comportement de l'API FastAPI.



Usage:

    poetry run python src/scripts/profile_predict.py



Prérequis:

    - Fichiers de test dans ./.data/extract/:

        * test_sample_application.csv

        * test_sample_bureau.csv

    - Variables d'environnement HF_REPOSITORY, HF_MODEL, HF_PIPELINE

"""

import cProfile
import pstats
import io
from pathlib import Path
from datetime import datetime
import os
import joblib
import pandas as pd
import numpy as np
from huggingface_hub import hf_hub_download

# Variables globales (comme dans l'API)
model = None
pipeline = None
_model_loaded = False


def load_model():
    """

    Charge le modèle et le pipeline depuis Hugging Face (lazy loading).

    Simule exactement le comportement de l'API.

    """
    global model, pipeline, _model_loaded

    if _model_loaded:
        print("/////////////////////////////// Modèle déjà chargé !! //////////")
        return  # déjà chargé

    print("#######################################################")
    print("Chargement du modèle et du pipeline depuis Hugging Face (lazy)...")

    hf_repository = os.getenv("HF_REPOSITORY")
    hf_model = os.getenv("HF_MODEL")
    hf_pipeline = os.getenv("HF_PIPELINE")

    if not all([hf_repository, hf_model, hf_pipeline]):
        raise RuntimeError(
            "Variables d'environnement HF manquantes (HF_REPOSITORY, HF_MODEL, HF_PIPELINE)"
        )

    # Téléchargement et chargement du modèle
    model_path = hf_hub_download(
        repo_id=hf_repository,
        filename=hf_model
    )
    model = joblib.load(model_path)
    print(f"Modèle chargé depuis: {model_path}")

    # Téléchargement et chargement de la pipeline
    pipeline_path = hf_hub_download(
        repo_id=hf_repository,
        filename=hf_pipeline
    )
    pipeline = joblib.load(pipeline_path)
    print(f"Pipeline chargée depuis: {pipeline_path}")

    _model_loaded = True


def load_test_data():
    """Charge les données de test pour le profiling."""
    data_dir = Path('./.data/extract')

    app_path = data_dir / 'test_sample_application.csv'
    bureau_path = data_dir / 'test_sample_bureau.csv'

    if not app_path.exists():
        raise FileNotFoundError(
            f"Fichier {app_path} introuvable. "
            f"Placez vos données de test dans {data_dir}/"
        )

    df_application = pd.read_csv(app_path)
    print(f"Données application chargées: {len(df_application)} lignes")

    # Bureau peut être vide
    if bureau_path.exists():
        df_bureau = pd.read_csv(bureau_path)
        print(f"Données bureau chargées: {len(df_bureau)} lignes")
    else:
        df_bureau = pd.DataFrame()
        print("⚠ Pas de données bureau (fichier absent)")

    return df_application, df_bureau


def simulate_api_prediction(df_application, df_bureau):
    """

    Simule une requête API : lazy loading + prédiction.



    Args:

        df_application: DataFrame avec les données application (1 ligne)

        df_bureau: DataFrame avec les données bureau



    Returns:

        dict: Résultat de la prédiction avec temps d'inférence

    """
    start = datetime.now()

    # Lazy loading (comme dans l'API)
    if model is None or pipeline is None:
        load_model()
    else:
        print("############################ modèle déjà chargé !!!! ##########")

    # Transformation via la pipeline (comme dans l'API)
    X_processed = pipeline.transform(df_application, df_bureau)

    # Prédiction
    pred = int(model.predict(X_processed)[0])

    # Calcul de la probabilité
    if hasattr(model, "predict_proba"):
        proba = float(model.predict_proba(X_processed)[0][1])
    elif hasattr(model, "decision_function"):
        decision = model.decision_function(X_processed)[0]
        proba = float(1 / (1 + np.exp(-decision)))
    else:
        proba = float(pred)

    elapsed_ms = (datetime.now() - start).total_seconds() * 1000

    return {
        'prediction': pred,
        'probability': round(proba, 4),
        'inference_time_ms': round(elapsed_ms, 2)
    }


def run_profiling(df_application, df_bureau, n_iterations=100):
    """

    Exécute n prédictions pour profiler les performances.



    Args:

        df_application: DataFrame avec les données application

        df_bureau: DataFrame avec les données bureau

        n_iterations: Nombre d'itérations pour le profiling

    """
    print(f"\n🔄 Exécution de {n_iterations} prédictions pour profiling...")

    results = []

    for i in range(n_iterations):
        result = simulate_api_prediction(df_application, df_bureau)
        result['iteration'] = i + 1
        results.append(result)

    # Statistiques (en excluant la première itération qui inclut le chargement)
    times = [r['inference_time_ms'] for r in results[1:]]  # Skip première itération

    print(f"\n Statistiques sur {len(times)} prédictions (hors chargement initial):")
    print(f"   - Temps moyen: {np.mean(times):.2f} ms")
    print(f"   - Temps médian: {np.median(times):.2f} ms")
    print(f"   - Temps min: {np.min(times):.2f} ms")
    print(f"   - Temps max: {np.max(times):.2f} ms")
    print(f"   - Écart-type: {np.std(times):.2f} ms")

    # Temps de la première prédiction (avec chargement)
    first_time = results[0]['inference_time_ms']
    print(f"\n⏱  Temps première prédiction (avec lazy loading): {first_time:.2f} ms")

    return results


def main():
    """Fonction principale encapsulant tout le profiling."""

    print("=" * 80)
    print("PROFILING DE L'INFÉRENCE - Simulation du comportement de l'API")
    print("=" * 80)

    # 1. Chargement des données de test
    df_application, df_bureau = load_test_data()

    # 2. Exécution des prédictions (à profiler)
    # Note: Le lazy loading se fera à la première itération
    results = run_profiling(
        df_application=df_application,
        df_bureau=df_bureau,
        n_iterations=100
    )

    print("\n Profiling terminé avec succès")


if __name__ == "__main__":
    # Créer le dossier de sortie pour les résultats de profiling
    Path('.data/profiling').mkdir(parents=True, exist_ok=True)

    # Activer le profiler
    profiler = cProfile.Profile()
    profiler.enable()

    # Exécuter le profiling
    main()

    # Désactiver le profiler
    profiler.disable()

    # Afficher les résultats dans la console
    s = io.StringIO()
    ps = pstats.Stats(profiler, stream=s).sort_stats("cumulative")
    ps.print_stats(50)  # Top 50 fonctions les plus coûteuses

    print("\n" + "=" * 80)
    print("PROFILING RESULTS - Top 50 fonctions par temps cumulé")
    print("=" * 80)
    print(s.getvalue())

    # Sauvegarder le fichier .prof
    prof_file = '.data/profiling/predict_inference.prof'
    profiler.dump_stats(prof_file)
    print(f"\n Fichier de profiling sauvegardé : {prof_file}")