Python >> Tutoriel Python >  >> Python

Machine Learning from the Woods :Explorer des modèles d'ensemble arborescents en Python

Python est un sujet brûlant en ce moment. Tout comme l'apprentissage automatique. Et des modèles d'ensemble.

Mettez les trois ensemble et vous obtenez une puissante combinaison de technologies puissantes.

Cet article fournit un aperçu complet des modèles d'ensemble basés sur des arbres et des nombreuses applications de Python dans l'apprentissage automatique. Lisez la suite pour savoir pourquoi ces modèles sont la solution parfaite pour de nombreux problèmes d'apprentissage automatique.

Voici ce que nous allons couvrir :

  • Tout d'abord, je vais vous donner une théorie de base derrière trois modèles d'ensemble : forêts aléatoires, AdaBoost et amélioration des arbres dégradés.
  • Ensuite, je vais vous apprendre comment mettre en œuvre ces modèles d'ensemble en utilisant l'une des bibliothèques Python les plus populaires pour l'apprentissage automatique :scikit-learn.
  • Enfin, je vais vous montrer comment améliorer les performances prêtes à l'emploi de vos modèles d'ensemble.

1. Une courte présentation

Même s'ils sont relativement faciles à comprendre, les modèles d'ensemble sont les algorithmes gagnants dans de nombreux concours de science des données (organisés par Kaggle ou DrivenData, pour n'en nommer que quelques-uns). De plus, ils n'ont généralement pas besoin de grands clusters de machines accélérées par GPU pour obtenir des résultats raisonnables.

Vous n'avez pas besoin d'être un expert en apprentissage automatique ou un génie des mathématiques pour comprendre ce texte. L'idée générale derrière les modèles d'ensemble est vraiment simple - même les pigeons pourraient la comprendre !

Voici une recette :obtenez un ensemble de modèles, agrégez leurs prédictions, et le tour est joué !

Il existe, bien sûr, de nombreuses astuces pour obtenir les meilleurs résultats des ensembles, et nous en discuterons plus tard. De plus, je serai assez technique la plupart du temps, il serait donc plus facile si vous connaissiez au moins certains des principes fondamentaux de l'apprentissage supervisé. Mais pour l'instant, vous avez tout ce dont vous avez besoin.

Une dernière chose que vous vous demandez peut-être et que j'aimerais aborder avant de commencer :qu'en est-il de ce titre ?

"Apprentissage automatique des bois?" Pourquoi "bois" ? Question complémentaire :qu'est-ce que cela signifie que les modèles sont "arbres" ?

Eh bien, il s'avère que les algorithmes les plus couramment utilisés pour combiner en ensembles sont les arbres de décision. Compris ?

Remarque :cet article est disponible en deux versions :ici et sur le STX Next GitHub.

2. Contexte de l'apprentissage automatique

Je commencerai par présenter brièvement deux concepts cruciaux :biais et variance. Ils décrivent la dépendance entre la complexité du modèle et les données.

Un biais élevé signifie que votre modèle est trop général et ignore de nombreux modèles importants. À l'inverse, une variance élevée se produit lorsqu'un modèle « se concentre » trop sur les détails cosmétiques, perdant de vue la vue d'ensemble, en termes de données. Trouver un modèle optimal est toujours un compromis entre biais et variance.

Dans les sections suivantes, nous allons résoudre le problème de classification . Cela signifie que nous attribuerons une étiquette à une classe d'observation, en fonction de certaines caractéristiques qui la décrivent.

Dans notre cas, l'observation est une personne qui demande une carte de crédit. Cette personne est décrite par l'âge, le revenu et le nombre d'enfants, entre autres, ce sont toutes nos caractéristiques. Nous allons construire un modèle qui décide si cette personne doit recevoir une carte de crédit en fonction des caractéristiques spécifiées. En d'autres termes :nous attribuerons à la classe une étiquette " accepté" ou "refusé".

Sans plus tarder, avant de nous plonger dans le fonctionnement des forêts aléatoires, d'AdaBoost et de l'amélioration des arbres de gradient, passons une minute à parler de leurs éléments constitutifs :les arbres de décision.

a) Arbres de décision

En termes simples, les arbres de décision sont des modèles construits avec un ensemble de conditions booléennes, définies par des caractéristiques de données (par exemple, "si l'âge est inférieur à 18"). Ces conditions sont présentées sous la forme d'un arbre binaire.

Pour former un arbre de décision signifie localiser ces conditions (limites de décision) à l'aide de critères donnés. Regardons un exemple d'arbre pour montrer comment ces limites interagissent les unes avec les autres.

Nous allons créer un ensemble de données simple, où chacun des 100 échantillons est défini par seulement deux caractéristiques (dimensions) :X et Y. Il y aura trois classes :"rouge", "vert" et "bleu".

Le make_blobs La fonction de la bibliothèque scikit-learn nous facilite la tâche.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
import matplotlib.pyplot as plt

from sklearn.datasets import make_blobs

%matplotlib inline

RANDOM_STATE = 0  # Ensure repeatability when using randomness
COLORS = ["red", "green", "blue"]
FEATURES_NAMES = ["X", "Y"]

features, labels = make_blobs(
    centers=[(-4, 4), (0, -4), (4, 4)],
    random_state=RANDOM_STATE
)
labels_colors = [COLORS[label] for label in labels]

plt.scatter(features[:, 0], features[:, 1], c=labels_colors)
plt.xlabel(FEATURES_NAMES[0])
plt.ylabel(FEATURES_NAMES[1])
plt.show()

Ensuite, nous pouvons créer notre classificateur d'arbre.

Tous les modèles scikit-learn partagent la même API pour la formation :fit(features, labels) .

1 2 3 4
from sklearn.tree import DecisionTreeClassifier

decision_tree = DecisionTreeClassifier()
decision_tree.fit(features, labels)
1 2 3 4 5 6
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None,
            max_features=None, max_leaf_nodes=None,
            min_impurity_decrease=0.0, min_impurity_split=None,
            min_samples_leaf=1, min_samples_split=2,
            min_weight_fraction_leaf=0.0, presort=False, random_state=None,
            splitter='best')

Enfin, nous utilisons la bibliothèque graphviz pour étudier la structure de notre classificateur d'arbre.

1 2 3 4 5 6 7 8 9 10 11 12 13 14
import graphviz
from sklearn.tree import export_graphviz

tree_data = export_graphviz(
    decision_tree=decision_tree,
    out_file=None,
    feature_names=FEATURES_NAMES,
    class_names=COLORS,
    rounded=True,
    filled=True,
    impurity=False
)
tree_graph = graphviz.Source(tree_data)
tree_graph

La première ligne du nœud décrit la limite de décision et n'est pas présente dans les feuilles, car elles ne se divisent pas.

Le suivant est le nombre d'échantillons tombant dans ce nœud, suivi de la distribution des échantillons entre les classes.

La dernière est la classe la plus courante dans le nœud, qui dans les feuilles est interprétée comme l'étiquette de sortie de l'arbre.

Comme vous pouvez le constater, les arbres de décision ont bien géré ce petit exemple ; en fait, ils peuvent traiter de nombreux problèmes de la vie réelle. De plus, les modèles sont interprétables et facile à visualiser.

Les arbres de décision ne nécessitent pas non plus beaucoup de prétraitement des données. Par exemple, vous n'avez pas besoin de mettre à l'échelle les données, car les arbres ne se soucient pas de l'ampleur des caractéristiques pour trouver la limite de décision.

De plus, les arbres de décision peuvent gérer à la fois des données numériques et catégorielles, bien qu'ils soient assez sujets au surajustement, ce qui signifie qu'il s'agit de modèles à variance élevée.

Par conséquent, de petites modifications apportées à l'ensemble d'apprentissage peuvent entraîner des changements majeurs dans les limites de décision. L'un des moyens les plus efficaces de résoudre ce problème consiste à utiliser de nombreux arbres pour prendre des décisions.

Voici ce que vous voudriez retenir le plus de cette section :si vous répondez à de nombreuses petites questions par oui/non, l'arbre de décision vous dira la vérité.

b) Forêts aléatoires

Vous vous demandez peut-être :comment l'utilisation de nombreux modèles surajustés peut-elle réduire l'erreur de classificateur ? Eh bien, laissez-moi essayer de vous répondre.

Les arbres suréquipés prennent des décisions basées sur des observations généralement rares. Cependant, ils détiennent toujours des informations précieuses sur les données.

Lors de l'utilisation d'un grand nombre de ces arbres, les cas les plus rares et les plus étranges disparaissent. Les plus courants, cependant, restent et jouent un rôle important en rendant le classificateur plus "ouvert d'esprit".

Imaginez cela comme un groupe de personnes qui parviennent à un consensus en rejetant leurs opinions les plus extrêmes. Dans le monde humain, cela ne fonctionne pas toujours très bien, mais les arbres de décision sont des créatures plus accommodantes.

Nous avons déjà vu que la variété des classificateurs d'arbres est une caractéristique plutôt souhaitable. Si nous disposions de plusieurs ensembles de données provenant de plusieurs sources, nous pourrions rendre nos arbres encore plus asymétriques. Mais comment faire cela avec un seul jeu de données disponible ?

C'est là que l'idée de mettre en sac (ou d'agréger par bootstrap) vient à la rescousse.

Disons que vous avez 1 000 échantillons dans votre ensemble d'apprentissage. Vous souhaitez créer 25 nouveaux ensembles de formation pour former 25 arbres de décision différents. Ce que vous faites, c'est simplement choisir 1 000 échantillons au hasard (avec remplacements) de votre kit d'entraînement (sac).

Dans le nouvel ensemble d'entraînement, certains échantillons de l'ensemble d'origine apparaîtront plus d'une fois, tandis que d'autres n'apparaîtront pas du tout. Si vous répétez cette procédure 25 fois, vous aurez 25 ensembles d'entraînement différents pour construire vos arbres. Pour obtenir la prédiction finale d'un ensemble, il vous suffit de sélectionner la sortie la plus courante de tous les arbres.

Vous pouvez également utiliser des probabilités de classes au lieu de simplement des étiquettes. Vous pouvez les prendre à partir d'une distribution de représentants de classe dans le nœud final de l'arbre (feuille). En utilisant cette approche, votre modèle favorisera des arbres plus fiables, ce qui n'est que raisonnable.

Mais la forêt aléatoire est plus qu'un simple ensachage d'arbres. Il existe une autre astuce pour rendre les arbres individuels encore plus diversifiés. Vous pouvez limiter au hasard l'ensemble des fonctionnalités disponibles lors de la division d'un nœud.

De cette façon, chaque fois qu'un arbre essaie de trouver une frontière de décision optimale, il "voit" un sous-ensemble de toutes les caractéristiques. Ce tirage a lieu à chaque fois qu'un nœud est divisé. Une mesure courante consiste à prendre au hasard sqrt(n) ou log2 (n) fonctionnalités pour faire une séparation où n est le nombre d'éléments d'origine.

Pour faire court :prenez beaucoup d'arbres différents et empilez leurs résultats ; les cas les moins populaires tomberont au bord du chemin, tandis que les plus courants gagneront en force.

c) AdaBoost

Il existe une autre famille de méthodes d'assemblage appelées boosting . Il résout le problème de l'empilement d'un grand nombre de classificateurs de l'autre côté.

Cette fois, nous utiliserons plusieurs apprenants faibles, ce qui n'est qu'une légère amélioration par rapport aux devinettes aléatoires. Encore une fois, la décision coopérative le rend puissant, car les classificateurs individuels sont souvent trop primitifs. Cependant, s'il y en a un nombre suffisant, des informations fréquentes peuvent être considérées comme précieuses. Une telle approche nous aide à réduire le biais général à l'aide de nombreux modèles à fort biais.

Les arbres composant l'ensemble AdaBoost sont créés séquentiellement. Une fois qu'un arbre est formé, il reste intact pour le reste de la formation de l'ensemble. De nouveaux classificateurs sont formés pour réduire le nombre d'erreurs commises dans les modèles précédents.

Les arbres utilisés dans AdaBoost sont peu profonds; ils sont appelés "souches de décision" et n'ont souvent que 2 feuilles. L'utilisation d'arbres comme apprenants de base n'est pas requise par l'algorithme AdaBoost, mais c'est la pratique la plus courante.

Lors de la création d'un modèle AdaBoost, tous les échantillons d'apprentissage sont pondérés. Initialement, tous les poids sont identiques et égaux 1/N , où N est le nombre d'échantillons.

Après avoir formé un arbre, les poids des échantillons mal classés sont augmentés. Un ensemble de données avec des poids mis à jour sert ensuite d'entrée à un autre arbre de l'ensemble.

Cette procédure est répétée jusqu'à ce que l'ajout d'un nouvel arbre réduise les erreurs d'apprentissage. Alternativement, la formation peut s'arrêter une fois que le nombre maximum d'arbres est atteint.

La prédiction de sortie est la moyenne pondérée des prédictions des arbres peu profonds ; les poids des classificateurs sont leurs précisions d'entraînement. Si vous voulez les formules exactes utilisées dans AdaBoost, je vous recommande vivement ce billet de blog de Jason Brownlee.

La conclusion pour vous est la suivante :construisez de petits arbres, un par un, et concentrez-vous sur les erreurs du passé.

d) Amélioration de l'arbre de dégradé

Gradient tree boosting est le prochain membre de la famille des algorithm boosting.

De même qu'AdaBoost, l'amélioration de l'arbre de gradient est construite à partir d'un ensemble de petits arbres, bien que généralement légèrement plus profond que les souches de décision . Les arbres sont entraînés séquentiellement, comme dans AdaBoost, mais l'entraînement des arbres individuels n'est pas le même.

Les arbres composant le gradient tree boosting sont des arbres de régression. Ils sont très similaires aux arbres de classification décrits précédemment, avec une différence essentielle :ils sont entraînés pour générer un nombre réel au lieu d'une étiquette pour chaque échantillon.

Ils mettent dans des feuilles des échantillons avec des valeurs réelles similaires (idéalement identiques) qui leur sont liées. La valeur de sortie d'une feuille donnée est la moyenne de tous les échantillons qui y arrivent pendant l'entraînement.

Dans le cas du gradient boosting, chaque classe a son propre arbre de régression. Un arbre entraîné génère une probabilité qu'un échantillon donné appartienne à cette classe. Les valeurs utilisées pour la formation sont bien sûr 1 et 0 uniquement, et un arbre parfaitement formé ne produira que ces valeurs.

Dans la vraie vie, lorsqu'un échantillon d'apprentissage tombe dans la mauvaise feuille, les sorties tomberont entre 1 et 0. Notez que pour la classification binaire, il n'est pas nécessaire d'avoir un arbre séparé pour chaque classe. Un arbre suffit, car les sorties à faible probabilité signifient que les échantillons appartiennent à l'autre classe.

L'idée principale derrière l'algorithme est de trouver de manière itérative de nouveaux arbres qui minimisent la fonction de perte, une mesure indiquant à quel point le modèle est mauvais. Cette fonction doit être différentiable, et elle est sélectionnée en fonction du problème à résoudre.

Pour les problèmes de classification, nous utilisons généralement la fonction de perte de log, qui est simplement la moyenne négative des log-probabilités où l'échantillon xi est classé comme son étiquette yi ;

Connaissant la valeur de la perte, nous pouvons calculer ce que l'on appelle les "pseudo-résidus". Ce sont des gradients de perte par rapport aux prédictions des arbres précédents. Plus le résidu est grand, plus l'erreur est importante. Les pseudo-résidus sont alors utilisés à la place des étiquettes lors de la formation d'un nouvel arbre.

C'est parfaitement logique, puisque le nouvel arbre accordera plus d'attention aux échantillons qui ont été complètement mal classés dans les arbres précédents.

De plus, l'arborescence est paramétrée. Ces paramètres de structure sont également optimisés pendant l'entraînement afin de réduire les pertes. L'impact des nouveaux arbres est progressivement réduit, afin de ne pas écarter les prédictions précédentes.

Si vous avez faim de plus de mathématiques qui expliquent tout le processus, je vous recommande de lire cet article de Wikipedia. Un vrai geek du ML peut également jeter un œil à ce livre blanc rédigé par l'un des pères de cette méthode.

TL; DR :l'amélioration de l'arbre de gradient est similaire à AdaBoost, mais au lieu de regarder uniquement il a échoué, il attire également votre attention sur combien il a échoué.

3. Ensemble de données

Pour tester nos algorithmes, nous utiliserons l'ancien ensemble de données d'Australian Credit Approval.

Basé sur les caractéristiques du client, notre modèle essaiera de prédire s'il faut accepter ou rejeter une demande de carte de crédit. Les noms des fonctionnalités d'origine ont été supprimés, par souci de confidentialité.

Puisqu'il n'y a que deux sorties possibles, nous avons affaire ici à une classification binaire. Vous pouvez télécharger l'ensemble de données original ici.

Pour garder les choses simples, nous allons utiliser une fonction utilitaire de scikit-learn, fetch_mldata . C'est une ligne pratique pour télécharger des ensembles de données à partir de mldata.org. Nous mélangeons également les données et les divisons pour former et tester les ensembles dans le but d'évaluer notre modèle sur des données invisibles.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
from sklearn.datasets import fetch_mldata
from sklearn.model_selection import train_test_split
from sklearn.utils import shuffle

DATASET_NAME = "australian"
TEST_SIZE = 0.2

data_bunch = fetch_mldata(DATASET_NAME)
features, labels = shuffle(
    data_bunch.data,
    data_bunch.target,
    random_state=RANDOM_STATE
)
features_train, features_test, labels_train, labels_test = train_test_split(
    features, labels, test_size=TEST_SIZE, random_state=RANDOM_STATE)

4. Modèles d'ensemble dans scikit-learn

L'un des principes fondamentaux de Python est le suivant :"Simple vaut mieux que complexe". Les créateurs de scikit-learn l'ont certainement pris à cœur.

Même si les méthodes d'ensemble sont généralement plus compliquées que les arbres simples, ce n'est pas le cas dans scikit-learn. La bibliothèque fournit une API identique pour les former. Vous importez un module approprié, créez un objet et entraînez-le à l'aide du fit méthode. Simple, mais puissant !

Tous les modèles que nous allons tester ici vivent à l'intérieur du sklearn.ensemble module. Afin d'obtenir toujours les mêmes résultats avec les mêmes données, nous définissons random_state après les avoir créés.

1 2 3 4 5 6 7 8 9 10 11
from sklearn.ensemble import (
    AdaBoostClassifier,
    GradientBoostingClassifier,
    RandomForestClassifier
)

models = [
    RandomForestClassifier(random_state=RANDOM_STATE),
    AdaBoostClassifier(random_state=RANDOM_STATE),
    GradientBoostingClassifier(random_state=RANDOM_STATE)
]

5. Résultats

Vérifions maintenant la précision des trois modèles. Par "précision", je fais référence au taux d'échantillons correctement étiquetés. Nous le mettrons ensuite dans le DataFrame de Panda et découvrez les performances de nos modèles.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
import pandas as pd

TRAIN_SCORES_ROW = "Train accuracy"
TEST_SCORES_ROW = "Test accuracy"

def evaluate_models(models, model_names=None):
    if not model_names:
        model_names = [type(model).__name__ for model in models]
        
    scores = pd.DataFrame(index=[TRAIN_SCORES_ROW, TEST_SCORES_ROW])
    for model, model_name in zip(models, model_names):
        model.fit(features_train, labels_train)
        scores.at[TRAIN_SCORES_ROW, model_name] = \
            model.score(features_train, labels_train)
        scores.at[TEST_SCORES_ROW, model_name] = \
            model.score(features_test, labels_test)
    return scores

evaluate_models(models)
RandomForestClassifier AdaBoostClassifier GradientBoostingClassifier
Précision de l'entraînement 0,992754 0.900362 0.969203
Tester la précision 0,876812 0,847826 0,898551

Pas mal !

Comme vous pouvez le voir, la forêt aléatoire et l'amplification du gradient ont fonctionné presque parfaitement sur l'ensemble d'entraînement. Les résultats des toutes nouvelles données sont légèrement moins bons, ce qui est à prévoir.

Mais il y a plus de paramètres à vérifier que la simple précision. Regardons la matrice de confusion pour notre meilleur modèle :

1 2 3
best_model = models[2]
predictions_train = best_model.predict(features_train)
predictions_test = best_model.predict(features_test)
1 2 3 4 5 6 7 8
from sklearn.metrics import confusion_matrix

def confusion_matrix_as_data_frame(labels, predictions):
    return pd.DataFrame(
        data=confusion_matrix(labels, predictions),
        index=["Actual rejected", "Actual accepted"],
        columns=["Predicted rejected", "Predicted accepted"]
    )
1
confusion_matrix_as_data_frameconfusi (labels_train, predictions_train)
Prévision
rejeté
Prévision
accepté
Réel
rejeté
295 10
Réel
accepté
7 240
1
confusion_matrix_as_data_frame(labels_test, predictions_test)
Prévision
rejeté
Prévision
accepté
Réel
rejeté
70 8
Réel
accepté
6 54

La matrice de confusion illustre où notre modèle a échoué.

Par exemple, nous pouvons voir que le modèle de gradient boosting a décidé d'accepter 8 demandes de carte de crédit qui ne devraient pas ont été acceptés.

En utilisant les valeurs de la matrice de confusion, nous pouvons calculer le nombre de mesures utiles, telles que la précision ou rappel . Je vous encourage à vous familiariser avec eux, si vous ne l'êtes pas déjà.

6. Réglage du modèle

a) Présentation des hyperparamètres

Les hyperparamètres sont des parties du modèle choisies explicitement au lieu d'être définies lors de la formation. Ils diffèrent selon les modèles et doivent être utilisés avec précaution. Les valeurs par défaut de Scikit-learn sont raisonnables, mais il reste souvent une marge d'amélioration.

Pour tirer le meilleur parti des modèles de ML, ils ne doivent pas être traités comme des boîtes noires magiques. Ils ont de nombreux "boutons" que vous pouvez modifier pour les améliorer. Une compréhension de haut niveau de leur fonctionnement vous permet de le faire.

Décrivons les hyperparamètres les plus importants pour nos trois modèles :

Nombre d'arbres

Sans aucun doute crucial, sinon l'hyperparamètre le plus important. Il est disponible pour les trois algorithmes. Généralement, plus il y a d'arbres dans un ensemble, mieux c'est, mais ce n'est pas une solution unique.

Les algorithmes de boosting peuvent sur-ajuster lorsqu'un trop grand nombre de modèles sont utilisés. L'utilisation de plus d'arbres entraîne également un allongement du temps d'entraînement et de prédiction.

Le nombre d'arbres peut être défini via le constructeur (comme tous les autres hyperparamètres) en utilisant le n_estimators mot-clé.

Taille de l'arbre

Nous ne voulons pas que nos arbres soient trop compliqués. Naturellement, les forêts aléatoires reposent sur des arbres à variance élevée, mais quand même, assez est aussi bon qu'un festin (par exemple en raison de contraintes de temps).

D'un autre côté, les algorithmes de boost nécessitent des arbres peu profonds, peut-être même des souches. Il semble que nous devrions avoir le pouvoir de le contrôler. Heureusement, scikit-learn nous permet de le faire de différentes manières :

Tout d'abord, nous pouvons définir la profondeur maximale de l'arbre (max_depth ).

Deuxièmement, le nombre maximum de nœuds feuilles (max_leaf_nodes ).

Enfin, le nombre minimum d'échantillons nécessaires pour effectuer un découpage dans un nœud (min_samples_split ).

Les deux premiers sont recommandés pour une utilisation avec le boost, tandis que le dernier est particulièrement utile avec les forêts aléatoires. Lorsque vous utilisez AdaBoost, ils ne peuvent pas être définis directement via le constructeur, vous devez donc les spécifier dans le constructeur du base_estimator objet.

Taux d'apprentissage

Il n'est présent que dans les méthodes de boosting et définit le taux de réduction de chaque arbre nouvellement ajouté. En d'autres termes, c'est le facteur par lequel la contribution de chaque arbre est multipliée.

Le taux d'apprentissage a une forte corrélation avec le nombre d'arbres. L'utilisation d'un taux d'apprentissage plus faible vous oblige à utiliser un plus grand nombre d'arbres. Dans les deux méthodes de boost que nous avons décrites, sa valeur est définie via le learning_rate  mot-clé. La valeur de ce petit bonhomme doit être comprise entre 0 et 1.

Sous-échantillonnage

Le sous-échantillonnage contrôle la taille du sous-ensemble d'entités à sélectionner lors de la division d'un nœud . C'est un hyperparamètre essentiel pour les forêts aléatoires qui peut également jouer un rôle régulateur important dans l'amplification des arbres de gradient. Le mot-clé de cet hyperparamètre est max_features .

Vous pouvez utiliser un flottant (fraction) ou un entier (valeur exacte) pour décider du nombre d'entités à sélectionner. Il peut également s'agir d'une chaîne :"sqrt" , "log2" , ou "auto" . Les deux premiers sont explicites, tandis que "auto" est identique à "sqrt" .

De plus, GradientBoostingClassifier prend en charge le subsample mot-clé. C'est un flotteur qui nous dit quelle fraction de tous les échantillons sera utilisée pour former un seul arbre. Le définir sur une valeur inférieure à 1,0 peut réduire la variance et conduire à de meilleurs résultats.

b) Recherche d'hyperparamètres

Il existe de nombreuses approches différentes pour rechercher les meilleurs hyperparamètres. Puisque le nombre d'arbres est le facteur décisif, fortement lié au taux d'apprentissage, ce sont les deux par lesquels nous commencerons.

Nous entraînerons des algorithmes de boosting avec un grand nombre d'arbres. Dans cet exemple, 300 est considéré comme grand, mais pour des tâches plus difficiles, cela pourrait être des milliers. Nous essaierons différentes valeurs de taux d'apprentissage pendant l'entraînement pour choisir la meilleure paire.

Avant de commencer la formation, nous allons extraire un soi-disant "ensemble de validation" de notre ensemble de formation.

À quoi sert un ensemble de validation ?

Nous voulons continuer à ajouter de nouveaux arbres tant que cela améliore la qualité principalement sur l'ensemble de test et pas seulement sur l'ensemble d'apprentissage. Pour éviter de sur-adapter l'ensemble de test réel, nous extrayons un ensemble de validation de notre ensemble d'apprentissage. De cette façon, nous pouvons observer quand notre algorithme (formé sur la version coupée de l'ensemble de formation) commence à sur-adapter.

Le surapprentissage commence au point où la précision de l'entraînement augmente, mais la précision de la validation ne s'améliore plus. Nous pouvons alors supposer qu'un comportement similaire se produira sur des ensembles d'entraînement et de test complets.

Vous verrez par vous-même de quoi je parle bien assez tôt. Pour l'instant, commençons par extraire l'ensemble de validation de notre ensemble d'entraînement :

1 2
features_train_, features_valid, labels_train_, labels_valid = train_test_split(
    features_train, labels_train, test_size=TEST_SIZE, random_state=RANDOM_STATE)

Ensuite, nous définissons une fonction qui prendra un modèle de boosting et listera les taux d'apprentissage à tester.

Il s'adapte à un ensemble composé de nombreux arbres avec des taux d'apprentissage différents et calcule la précision à chaque étape de l'ajout d'un arbre à un ensemble.

Le processus est simple, puisque les algorithmes de boosting utilisent le staged_predict méthode. Cette méthode produit des sorties données par des modèles intermédiaires.

N -nième prédiction étagée sera la sortie après la première utilisation de N des arbres. En sortie, on obtient le DataFrame de Panda . Ses lignes correspondent à des taux d'apprentissage donnés, tandis que les colonnes représentent la formation et l'ensemble valide. Chaque cellule contient une liste avec les précisions des modèles intermédiaires.

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
from sklearn.metrics import accuracy_score

N_TREES = 300
TRAINING_COLUMN = "training"
VALIDATION_COLUMN = "validation"


def get_staged_accuracies(model_class, learning_rates):
    staged_accuracies = pd.DataFrame(
        columns=[TRAINING_COLUMN, VALIDATION_COLUMN]
    )
    for learning_rate, color in zip(learning_rates, COLORS):
        model = model_class(
            n_estimators=N_TREES,
            learning_rate=learning_rate,
            random_state=RANDOM_STATE
        )
        model.fit(features_train_, labels_train_)
        predictions_train = model.staged_predict(features_train_)
        predictions_valid = model.staged_predict(features_valid)
        staged_accuracies.at[learning_rate, TRAINING_COLUMN] = [
            accuracy_score(labels_train_, prediction)
            for prediction in predictions_train
        ]
        staged_accuracies.at[learning_rate, VALIDATION_COLUMN] = [
            accuracy_score(labels_valid, prediction)
            for prediction in predictions_valid
        ]
    return staged_accuracies

La fonction ci-dessous est assez explicite.

Compte tenu des précisions DataFrame à partir de la fonction ci-dessus, elle renvoie un tuple de la plus grande précision, ainsi que le nombre d'arbres et le taux d'apprentissage correspondants.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
def get_best_params(staged_accuracies):
    best_params_all_lrs = [] 
    for learning_rate, accuracies in staged_accuracies.iterrows():
        accuracies_valid = accuracies[VALIDATION_COLUMN]
        best_n_trees, best_accuracy = max(
            enumerate(accuracies_valid, start=1),
            key=lambda x: x[1]
        )
        best_params_all_lrs.append(
            (best_accuracy, best_n_trees, learning_rate)
        )
    
    # Get set of params with highest accuracy and pick one
    # with less trees when draw occurs
    best_params = max(best_params_all_lrs, key=lambda x: (x[0], -x[1]))
    return best_params

Enfin, nous définissons une fonction pour tracer les résultats de notre recherche d'hyperparamètres.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
def plot_accuracies(staged_accuracies, title, colors=("r", "g", "b")):
    for (learning_rate, accuracies), color in zip(staged_accuracies.iterrows(), colors):
        plt.plot(
            staged_accuracies.at[learning_rate, TRAINING_COLUMN],
            linestyle="--",
            color=color,
            label="Train accuracy, LR: {}".format(learning_rate)
        )
        plt.plot(
            staged_accuracies.at[learning_rate, VALIDATION_COLUMN],
            color=color,
            label="Valid accuracy, LR: {}".format(learning_rate)
        )
        plt.title(title)
        plt.xlabel("Number of trees")
        plt.ylabel("Accuracy")
        plt.legend()
    plt.show()

Habituellement, les taux d'apprentissage qui valent la peine d'être essayés tombent en dessous de 0,1. Cependant, puisque nous avons affaire à un petit ensemble de données, nous n'aurons peut-être pas besoin de centaines d'arbres.

Un petit nombre d'arbres nécessite souvent un taux d'apprentissage plus élevé, nous allons donc en essayer un plus grand (0,5).

De plus, élargissons un peu les tracés de notre matplotlib pour plus de lisibilité.

1 2 3 4 5 6 7 8 9 10 11 12 13 14
wide_fig_size = (16, 4)
plt.rcParams["figure.figsize"] = wide_fig_size

staged_accuracies_gb = get_staged_accuracies(
    GradientBoostingClassifier,
    learning_rates=[0.01, 0.1, 0.5]
)
accuracy_gb, n_trees_gb, lr_gb = get_best_params(staged_accuracies_gb)
print(
    "Gradient Boosting: best valid accuracy={}"
    "with {} trees and learning rate={}"
    .format(accuracy_gb, n_trees_gb, lr_gb)
)
plot_accuracies(staged_accuracies_gb, title="Gradient Boosting")

Comme on le soupçonnait, nous n'aurons pas besoin des 300 arbres pour bien performer. 152 modèles de base avec un taux d'apprentissage de 0,1 feront l'affaire.

Nous appellerons également cette fonction sur un modèle AdaBoost. N'oubliez pas qu'il favorise généralement des taux d'apprentissage légèrement plus élevés.

1 2 3 4 5 6 7 8 9 10 11
staged_accuracies_ab = get_staged_accuracies(
    AdaBoostClassifier,
    learning_rates=[0.5, 0.8, 1.0]
)
accuracy_ab, n_trees_ab, lr_ab = get_best_params(staged_accuracies_ab)
print(
    "AdaBoost: best valid accuracy={}"
    "with {} trees and learning rate={}"
    .format(accuracy_ab, n_trees_ab, lr_ab)
)
plot_accuracies(staged_accuracies_ab, title="AdaBoost")

Nous avons un nombre d'arbres et un taux d'apprentissage optimaux, il est donc grand temps d'ajuster les autres hyperparamètres. Tournons-nous encore une fois vers notre formidable tandem Python et scikit-learn pour faire l'affaire.

Il existe de nombreuses recommandations différentes pour choisir les bons paramètres. Nous utiliserons un ensemble de paramètres potentiellement bons du tableau disponible ici.

Nous effectuerons une soi-disant "recherche de grille", ce qui signifie que nous essaierons toutes les combinaisons possibles de ces paramètres et choisirons celle qui fonctionne le mieux.

Lors de la recherche dans la grille, une validation croisée est effectuée. Au début, un ensemble d'apprentissage est divisé en n parties paires (définies par le cv mot-clé). Ensuite, il y a n tourne lorsqu'une partie est utilisée comme ensemble de validation et le reste est utilisé comme ensemble d'apprentissage.

Après n tours de validation, un score moyen (par exemple, la précision) est calculé, de sorte que le meilleur modèle peut être choisi. Scikit-learn fournit une API pratique pour le faire. Nous pouvons facilement recycler le modèle le plus performant sur un ensemble de formation complet.

Remarque :ce code peut prendre un peu plus de temps (jusqu'à 90 secondes). Si vous êtes impatient, supprimez certaines valeurs pour minimiser le nombre de combinaisons (par exemple, les valeurs maximales pour max_depth et max_features ).

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
from sklearn.model_selection import GridSearchCV

NUM_FOLDS = 5

gradient_boosting_params = {
    "n_estimators": [152],
    "learning_rate": [0.1],
    "max_depth": [4, 6, 8, 10],
    "subsample": [0.4, 0.6, 0.8, 1.0],
    "max_features": [0.5, 0.75, 1.0]
}

random_search = GridSearchCV(
    estimator=GradientBoostingClassifier(
        random_state=RANDOM_STATE
    ),
    param_grid=gradient_boosting_params,
    cv=NUM_FOLDS
)
random_search.fit(features_train, labels_train)
best_gb_model = random_search.best_estimator_
best_gb_params = random_search.best_params_
best_gb_params
learning_rate 0,1
max_depth 0,6
max_features 0,75
n_estimators 152
subsample 1.0

Pour augmenter la mise, nous laisserons l'évaluation de la précision de notre modèle réglé pour plus tard. Tout d'abord, réglons la forêt aléatoire et AdaBoost.

Si vous ne disposez pas d'une telle table avec des hyperparamètres, ou si elle échoue complètement sur vos données, il existe une alternative :recherche aléatoire.

Cet algorithme est trivial. Vous définissez simplement des plages de recherche, puis choisissez au hasard plusieurs ensembles de paramètres. De plus, cela peut même vous donner de meilleurs résultats que la recherche par grille !

Au début, cela peut sembler étrange, mais regardez simplement l'image ci-dessous, tirée de cet article. La courbe verte en haut et la jaune à gauche sont des fonctions de précision dans chaque cas.

La recherche par grille assure une couverture uniforme de cet espace à deux dimensions, mais elle ne couvre que trois points sur chaque espace d'hyperparamètres. Lorsque nous utilisons la recherche aléatoire, nous obtenons une bien meilleure couverture de recherche de chaque hyperparamètre individuel. Il est particulièrement utile lorsqu'une fonction métrique donnée présente des pics évidents par rapport à certains hyperparamètres.

Maintenant, définissons les plages d'intérêt pour les classificateurs restants et le nombre d'essais aléatoires (égal aux combinaisons de la recherche de grille précédente).

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
from functools import reduce
from operator import mul

random_trials = reduce(
    mul,
    (
        len(values)
        for _, values in gradient_boosting_params.items()
    )
)

random_forest_params = {
    "n_estimators": range(5, 500),
    "min_samples_split": range(2, 50),
    "max_features": [x / 10 for x in range(1, 11)]
}

adaboost_params = {
    "n_estimators": range(25, 50),
    "learning_rate": [x / 100 for x in range(80, 101)],
    "base_estimator__max_depth": range(1, 3),   
}

Définissons également une fonction pour récupérer le meilleur modèle basé sur une recherche aléatoire.

1 2 3 4 5 6 7 8 9 10 11 12
from sklearn.model_selection import RandomizedSearchCV

def find_best_model_random_search(model, params):
    random_search = RandomizedSearchCV(
        estimator=model,
        param_distributions=params,
        n_iter=random_trials,
        cv=NUM_FOLDS,
        random_state=RANDOM_STATE
    )
    random_search.fit(features_train, labels_train)
    return random_search.best_estimator_

Enfin, trouvons les meilleurs hyperparamètres pour la forêt aléatoire et AdaBoost. Ensuite, nous comparerons ceux avec le gradient boosting.

Note that we have to define the AdaBoost base model in order to tinker with its parameters.

Again, the hyperparameter search can take some time (up to 170 seconds), so be patient when you run it.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
best_rf_model = find_best_model_random_search(
    model=RandomForestClassifier(
        random_state=RANDOM_STATE
    ),
    params=random_forest_params
)
best_ab_model = find_best_model_random_search(
    model=AdaBoostClassifier(
        DecisionTreeClassifier(),
        random_state=RANDOM_STATE
    ),
    params=adaboost_params
)
                         
evaluate_models([best_rf_model, best_ab_model, best_gb_model])
RandomForestClassifier AdaBoostClassifier GradientBoostingClassifier
Train accuracy 0.956522 0.896739 1.000000
Test accuracy 0.884058 0.869565 0.905797

Hourra ! We have slight improvements on each of our classifiers.

You can, of course, try different hyperameter ranges or use random search with gradient boosting. Plus, let’s not forget that there are many more parameters you can tune.

But I’d argue that these are the ones you should start with. They represent different aspects of our models and are rather easy to interpret.

There is also a somewhat smarter way to do hyperparameter search. It uses Bayesian optimization, and as an added bonus allows you to use scikit-learn for implementation. The topic is outside the scope of this text, so I’ll leave it at that, but it’s definitely something worth exploring.

7. Summary

After reading this article, you should have some high-level understanding of ensembling methods. Also, I hope you’ll agree that using scikit-learn for implementation is quite fun!

Here’s a short recap of what we’ve learned:

  1. Decision trees are high-variance models, but we can fix this issue by using ensembles.
  2. Two main groups of ensembling methods are bagging (random forests) and boosting (AdaBoost and gradient tree boosting).
  3. Scikit-learn provides an easy API to train ensemble models with reasonable out-of-the-box quality.
  4. A little bit of digging into model hyperparameters can help us improve our models.

Should this post inspire you to take a deep dive into the vast world of machine learning in Python, make sure to check out the links I’ve left for you throughout the text. I found them very useful and helpful while I was putting this piece together.

But if you’re new to the machine learning game and looking for a place to start, look no further than this tutorial by two of my colleagues and ML experts at STX Next. They cover everything you need to begin your ML adventure in Python.