Nous allons optimiser un Random Forest avec les hyperparametres suivants:
# Espace de recherche pour Random Forestparam_space = {'n_estimators': (10, 500), # Nombre d'arbres (entier)'max_depth': (3, 30), # Profondeur max (entier)'min_samples_split': (2, 20), # Min samples pour split (entier)'min_samples_leaf': (1, 10), # Min samples par feuille (entier)'max_features': (0.1, 1.0), # Ratio de features (float)}def evaluate_rf(params: Dict[str, Any], cv_folds: int=5) ->float:""" Evalue une configuration d'hyperparametres. Retourne la moyenne de cross-validation (accuracy). """# Convertir les parametres entiers rf_params = {'n_estimators': int(params['n_estimators']),'max_depth': int(params['max_depth']),'min_samples_split': int(params['min_samples_split']),'min_samples_leaf': int(params['min_samples_leaf']),'max_features': params['max_features'],'random_state': 42,'n_jobs': -1 } model = RandomForestClassifier(**rf_params) scores = cross_val_score(model, X_train, y_train, cv=cv_folds, scoring='accuracy')return scores.mean()print(f"Espace de recherche defini : {len(param_space)} hyperparametres, evaluate_rf pret")
Espace de recherche defini : 5 hyperparametres, evaluate_rf pret
2. Méthodes de Reference
2.1 Grid Search (Recherche en Grille)
Principe: Explorer systematiquement toutes les combinaisons d’une grille predefinie.
Avantages: - Simple a implementer et comprendre - Garantit de trouver le meilleur point sur la grille - Très parallele
Inconvenients: - Explosion combinatoire: \(O(n^d)\) ou \(n\) = points par dimension, \(d\) = dimensions - Ne s’adapte pas a l’importance des dimensions
Principe: Echantillonner uniformement dans l’espace de recherche.
Insight cle (Bergstra & Bengio, 2012): Si certaines dimensions sont plus importantes que d’autres, le random search explore plus efficacement ces dimensions.
Avantages: - Echelle mieux avec la dimensionalite - Peut etre arrete a tout moment - Très parallele
La cellule ci-dessus a execute un Random Search avec 50 itérations. Ecrivez une fonction iterations_to_threshold qui, etant donne un historique de recherche et un seuil (ex: 95% du meilleur score), retourne le nombre minimal d’evaluations necessaires pour atteindre ce seuil.
Cet indicateur mesure l’efficacite d’une méthode d’optimisation : moins d’evaluations = méthode plus efficiente.
Indices : - Calculez le score cumulatif maximum : np.maximum.accumulate([h['score'] for h in history]) - Le seuil cible = threshold_fraction * best_final_score - Utilisez np.argmax(cumulative >= target) pour trouver la première itération qui atteint le seuil - Testez avec threshold_fraction=0.99 (atteindre 99% du meilleur score)
def iterations_to_threshold(history: List[Dict], threshold_fraction: float=0.99) ->int:"""Retourne le nombre minimal d'evaluations pour atteindre threshold_fraction du meilleur score. Args: history: Liste de dicts avec cle 'score' (resultats d'une methode d'optimisation) threshold_fraction: Fraction du meilleur score a atteindre (ex: 0.99 = 99%) Returns: Nombre d'evaluations necessaires (0-indexed). Retourne len(history) si jamais atteint. """# TODO etudiant : implementer le calcul de vitesse de convergence# Etape 1 : extraire les scores depuis l'historique# Etape 2 : calculer le score cumulatif maximum (best-so-far)# Etape 3 : determiner le seuil cible = threshold_fraction * meilleur score final# Etape 4 : trouver la premiere iteration ou cumulative_best >= seuilreturn0# TODO etudiant : remplacer par le calculprint("Exercice a completer : analyse de vitesse de convergence")
Exercice a completer : analyse de vitesse de convergence
3. Optimisation Bayesienne
Principe: Utiliser un modèle probabiliste (Gaussian Process) pour modeler la fonction objectif et guider l’exploration.
Algorithme
Initialisation: Evaluer quelques points aleatoires
Modèle: Entrainer un GP sur les observations
Acquisition: Sélectionner le prochain point maximisant l’acquisition
Evaluation: Evaluer le point selectionne
Repetition: Retourner a 2 jusqu’a budget epuise
Fonctions d’acquisition
EI (Expected Improvement): Balance exploration/exploitation
Le BayesianOptimizer ci-dessus utilise l’Expected Improvement (EI) comme fonction d’acquisition. Implementez une alternative : UCB (Upper Confidence Bound).
La formule UCB est : \(\alpha(x) = \mu(x) + \kappa \cdot \sigma(x)\)
ou \(\mu(x)\) est la prediction moyenne du modèle, \(\sigma(x)\) l’incertitude, et \(\kappa\) un paramètre controlant le trade-off exploration/exploitation.
Indices : - Reprenez la structure de expected_improvement dans la classe BayesianOptimizer - Le calcul de mean et std est identique (distance-weighted) - UCB = mean + kappa * std (pas besoin de norm.cdf ni norm.pdf) - Plus kappa est grand, plus l’exploration est favorisee (essayer kappa=2.0)
def ucb_acquisition(x: np.ndarray, observations: List, kappa: float=2.0) ->float:"""Calcule l'Upper Confidence Bound pour un point x. Args: x: Point a evaluer (array normalise [0,1]) observations: Liste de tuples (params_normalized, score) deja observes kappa: Parametre exploration/exploitation (plus grand = plus exploratoire) Returns: Valeur UCB (plus grand = plus prometteur) """# TODO etudiant : implementer la fonction d'acquisition UCB# Etape 1 : si moins de 2 observations, retourner une valeur aleatoire# Etape 2 : calculer les distances entre x et les observations observees# Etape 3 : estimer mu(x) par moyenne ponderee par les distances# Etape 4 : estimer sigma(x) par variance ponderee# Etape 5 : retourner mu + kappa * sigmareturn0.0# TODO etudiant : remplacer par le calcul UCBprint("Exercice a completer : fonction d'acquisition UCB")
Exercice a completer : fonction d'acquisition UCB
Avec Optuna (framework professionnel)
Optuna est la librairie de reference pour l’optimisation bayesienne en Python.
Exercice : Mesurer la diversite d’une population génétique
Une population trop homogene signifie que l’algorithme génétique a converge prematurement. Implementez une fonction population_diversity qui calcule la diversite moyenne d’une population en mesurant la distance moyenne entre paires d’individus.
Indices : - Convertissez chaque individu (dict de paramètres) en vecteur numérique normalise [0, 1] - Pour chaque paire d’individus, calculez la distance euclidienne np.linalg.norm(a - b) - La diversite = moyenne de toutes les distances par paires - Une diversite proche de 0 indique une convergence prematuree - Pour N individus, il y a N*(N-1)/2 paires distinctes
def population_diversity(population: List[Dict], param_space: Dict[str, Tuple]) ->float:"""Calcule la diversite moyenne d'une population (distance par paires normalisee). Args: population: Liste d'individus (chaque individu = dict de parametres) param_space: Dictionnaire {nom: (min, max)} pour la normalisation Returns: Diversite moyenne (0 = population identique, ~1 = population tres diversifiee) """# TODO etudiant : implementer la metrique de diversite# Etape 1 : normaliser chaque individu en vecteur [0, 1]# Pour chaque param : (value - min) / (max - min)# Etape 2 : calculer les distances par paires entre tous les individus# Utiliser deux boucles imbriquees ou itertools.combinations# Etape 3 : retourner la distance moyennereturn0.0# TODO etudiant : remplacer par le calculprint("Exercice a completer : metrique de diversite de population")
Exercice a completer : metrique de diversite de population
5. Particle Swarm Optimization (PSO)
Principe: Une nuée de particules explore l’espace, guidee par: - pBest: Meilleure position personnelle - gBest: Meilleure position globale
Visualisons comment chaque méthode explore l’espace des hyperparametres.
# Visualisation 2D (n_estimators vs max_depth)fig, axes = plt.subplots(2, 2, figsize=(12, 10))histories = [history_random, history_bayes, history_ga, history_pso]titles = ['Random Search', 'Bayesian (custom)', 'Genetic Algorithm', 'PSO']colors = ['blue', 'green', 'red', 'purple']for ax, history, title, color inzip(axes.flat, histories, titles, colors):# Extraire les points explores n_estimators = [h['params']['n_estimators'] for h in history] max_depth = [h['params']['max_depth'] for h in history] scores = [h['score'] for h in history]# Scatter plot avec couleur = score scatter = ax.scatter(n_estimators, max_depth, c=scores, cmap='viridis', s=50, alpha=0.7, edgecolors='black')# Meilleur point best_idx = np.argmax(scores) ax.scatter([n_estimators[best_idx]], [max_depth[best_idx]], c='red', s=200, marker='*', edgecolors='black', linewidths=2, label=f'Best: {max(scores):.4f}') ax.set_xlabel('n_estimators') ax.set_ylabel('max_depth') ax.set_title(title) ax.legend() plt.colorbar(scatter, ax=ax, label='Accuracy')plt.suptitle('Exploration de l\'Espace (n_estimators vs max_depth)', fontsize=14, y=1.02)plt.tight_layout()plt.show()
Interpretation des résultats
La visualisation montre comment chaque méthode explore l’espace 2D des paramètres (n_estimators vs max_depth) : - Random Search explore large et uniformement - Bayesian se concentre sur les regions prometteuses - GA explore a travers tout l’espace - PSO montre une exploration similaire a Random Search - Meilleurs points : les méthodes à recherche structurée (PSO, GA, Bayesian) convergent vers des configurations de régularisation fortes (max_depth modéré, min_samples_leaf élevé), adaptées au bruit d’étiquette du dataset - Convergence : sur ce paysage rugueux, le tableau comparatif (idx précédent) distingue nettement les méthodes — PSO (0,788) et GA (0,785) dominent, Bayesian (custom) (0,783) suit de près avec deux fois moins d’évaluations (50 vs 100), tandis que Grid (0,769) et Random (0,767) restent en retrait d’environ 0,02. L’écart total (0,021) est environ dix fois celui qu’on observerait sur un dataset saturant (paysage lisse) : la régularisation devenant discriminante, l’échantillonnage naïf (Grid/Random) laisse de la précision que les méthodes dirigées récupèrent - Recommandation : utiliser la visualisation pour identifier quelle méthode convient a votre problème spécifique
Poursuite : testez d’autres combinaisons de paramètres (n_estimators, max_depth) pour voir comment la performance varie a travers l’espace.
8. Recommandations et Bonnes Pratiques
Quand utiliser chaque méthode?
Méthode
Quand l’utiliser
Random Search
Baseline, premier passage, parallelisation facile
Bayesienne
Peu d’evaluations possibles, fonction couteuse, mono-objectif