Formation des modèles d'arbres
Let's go !

3.2 AdaBoost from scratch

Maintenant vous le savez, implémenter un algorithme from scratch est pour moi la meilleure façon d’être sûr d’avoir bien compris son fonctionnement. 

Dans cette partie, nous allons implémenter l’algorithme AdaBoost from scratch en Python.

Pour plus de simplicité, nous n’allons pas implémenter l’algorithme d’arbre de décision mais utiliser ce qui est proposé par le package Sklearn. 

Si vous voulez avoir plus d’informations sur l’implémentation de l’arbre de décision, n’hésitez pas à revenir dans le premier chapitre.

A/ L'initialisation de la classe AdaBoost

Nous allons coder la classe du modèle qui va nous permettre d’entraîner nos modèles AdaBoost.

On va commencer par créer la fonction d’initialisation de la classe.

C’est à cette étape que l’on va demander à l’utilisateur de spécifier toutes les informations dont on a besoin pour entraîner notre modèle.

class adaBoostClassifier():
  def __init__(self, n_estimators=100, max_depth=1, random_state=None):
    self._n_estimators = n_estimators
    self._max_depth = max_depth
    self._random_state = random_state
    self._sample_weight = np.array([])
    self._weight_model = dict()
    self._model = dict()
    self._features = dict()

B/ La fonction d'entraînement

La classe fit, entraîne un nombre d’arbres de notre forêt égal au nombre spécifié dans n_estimators_.

Nous allons aléatoirement choisir un nombre d’exemples et un nombre de colonnes, cet échantillon est assigné à  x_samp.

Nous allons entraîner un arbre en utilisant x_samp comme données d’entraînement. Cet arbre sera sauvé dans le dictionnaire model et les variable à utiliser pour cette arbre seront sauvées dans le dictionnaire features.

A chaque arbre est associé un poids rangé dans le dictionnaire weight_model.

Les poids des exemples contenus dans le dictionnaire sample_weight sont mis-à-jour après chaque entraînement d’arbre.

  def fit(self, x, y):

    # Initialisation des poids
    x_length = len(x)
    self._sample_weight = np.ones(x_length)/x_length


    np.random.seed(seed=9999)
    for est in range(self._n_estimators) :

      # Sélection aléatoire des indices colonnes et des indices exemples
      rand_index = np.random.randint(low=0, high=x.shape[0], size=round(x.shape[0]*0.8))
      rand_column = np.random.randint(low=0, high=x.shape[1], size=round(x.shape[1]*0.8))

      # Sélection aléatoire des colonnes et des exemples
      x_samp = x[rand_index, :]
      x_samp = x_samp[:, rand_column]
      y_samp = y[rand_index]
      weight_samp = self._sample_weight[rand_index]

      # Initialisation du modèle d'arbre de décision
      decision_tree_model = DecisionTreeClassifier(max_depth=self._max_depth, random_state=self._random_state)
      
      # Entraînement du modèle d'arbre de décision
      self._model[est] = decision_tree_model.fit(x_samp, y_samp, sample_weight=weight_samp)

      # Sauvegarde des colonnes utlisées pour l'arbre
      self._features[est] = rand_column

      # Prédition des données d'entraînement 
      y_pred = self._model[est].predict(x_samp)

      # Calcul de l'erreur total pondérée par le poids de chaque exemple
      total_error = np.sum((y_pred!=y_samp) * weight_samp)

      # Calcul du poids de l'arbre
      self._weight_model[est] = (1/2) * np.log((1-total_error)/(total_error))

      # Mise à jour des poids de l'arbre
      self._sample_weight[rand_index] = (y_pred!=y_samp) * self._sample_weight[rand_index] * np.exp(self._weight_model[est]) + (y_pred==y_samp) * self._sample_weight[rand_index] * np.exp(-self._weight_model[est])
  
      # Normalisation des poids  
      self._sample_weight /= np.sum(self._sample_weight)

C/ La fonction de prédiction

La classe predict utilise les données passées en paramètres dans et boucle sur les arbres de notre forêts pour obtenir une prédiction pour chaque arbre.

  def predict(self, x):

    # Initialiser le vecteur de prédiction
    pred = np.zeros(x.shape[0])

    # On ajoute à pred la prédiction de chaque arbre
    for i in range(self._n_estimators):
      pred += self._model[i].predict(x[:, self._features[i]]) * self._weight_model[i]

    # Normalisation des prédictions
    pred /= self._n_estimators

    # Passage d'une probabilité à une prédiction
    pred = np.where(pred >= 0.5, 1, 0)

    return pred