Comparaison de modèles, apprentissage des LPC et vraisemblance

Table des matières

bank_ml.png

Figure 1 : Dall-E 3: machine learning digital marketing bank loan ; white background ; colorful

1. Objectif

L'objectif de cet exercice est de comparer l'influence du choix de la structure d'un RB sur la représentativité de données observées. Autrement dit, il s'agit d'un problème de sélection de modèles.

Pour ce faire, nous nous intéresserons à des données de retour marketing (fictives) sur la vente de livret A. Dans ces données, les individus sont décrits par les variables suivantes :

Age :
classe d'âges de l'individu - valeurs possibles : [18,25], [26,59], 60+;
Epargne :
l'individu a t-il de l'épargne - valeurs possibles : non, oui;
Vente_livret_A :
vente d'un livret A à l'individu - valeurs possibles : échec, succès.

Une des applications classiques de ce type de modèles est le ciblage client. Pour un produit donné, l'idée est de trouver les caractéristiques de la population qui maximisent les chances de vente.

2. Apprentissage avec pyAGRuM

2.1. Modèle 1 : \(A \rightarrow V \leftarrow E\)

  1. Importez le module pyagrum dans votre environnement Python.

    import pyAgrum as gum # La librairie pyAgrum
    
    # Vérification des versions
    {
        "pyagrum": gum.__version__, 
    }
    
    pyagrum : 1.12.1
  2. Construire la structure du modèle 1.

    # Création du RB   
    bn1 = gum.BayesNet("Vente livret")
    
    # Création de la variable Vente
    va_V = gum.LabelizedVariable("Vente_livret_A", "Vente livret", 2)
    va_V.changeLabel(0, "echec")
    va_V.changeLabel(1, "succes")
    
    # Création de la variable Âge
    va_A = gum.LabelizedVariable("Age", "Âge", 3)
    va_A.changeLabel(0, "c18_25")
    va_A.changeLabel(1, "c26_59")
    va_A.changeLabel(2, "c60")
    
    # Création de la variable Épargne
    va_E = gum.LabelizedVariable("Epargne", "Épargne", 2)
    va_E.changeLabel(0, "non")
    va_E.changeLabel(1, "oui")
    
    # TODO : ajout des variable dans le RB (méthode .add)
    
    # TODO : ajout des arcs (méthode .addArc)
    
    
    # SOLUTION
    # --------
    # Ajout des variables dans le RB
    bn1.add(va_V)
    bn1.add(va_A)
    bn1.add(va_E)
    
    # Ajout des arcs
    bn1.addArc("Age", "Vente_livret_A")
    bn1.addArc("Epargne", "Vente_livret_A")
    
  3. Téléchargez le fichier de données vente_livret_A_donnees_10000.csv et lisez ces données dans un DataFrame Pandas avec la fonction read_csv.

    # Lecture des données
    import pandas as pd
    
    data_df = pd.read_csv("vente_livret_A_donnees_10000.csv")
    
  4. Utilisez la classe gum.BNLearner et en particulier la méthode fitParameters pour réaliser l'apprentissage des LPC de votre modèle.

    learner = gum.BNLearner(data_df)
    
    learner.fitParameters(bn1)
    
    print(bn1.cpt("Vente_livret_A"))
    print(bn1.cpt("Age"))
    print(bn1.cpt("Epargne"))
    
  5. Créez une fonction loglikelihood prenant en entrées un RB et un DataFrame représentant les mêmes variables et retournant la vraisemblance du RB par rapport au données. Il vous faudra utiliser un moteur d'inférence (ex: gum.LazyPropagation) et ses méthodes .setEvidence et .evidenceProbability.

    import math # Pour utiliser math.log
    
    def loglikelihood(bn, data_df):
    
        # TODO: Code à écrire ;)
     
        return loglike
    
    # SOLUTION
    # --------
    import math # Pour utiliser math.log
    
    def loglikelihood(bn, data_df):
    
        loglike = 0
        
        # Création d'un moteur d'inférence
        inf_engine = gum.LazyPropagation(bn)
    
        for idx, obs in data_df.iterrows():
            # Projection de la données courante dans le RB
            inf_engine.setEvidence(obs.to_dict())
            # Calcul de la probabilité (vraisemblance) de la données dans le RB
            data_loglike = inf_engine.evidenceProbability()
            # Passage au log pour obtenir la log-vraisemblance de la données
            # et somme pour calculer la vraisemblance sur toutes les données
            loglike += math.log(data_loglike)
     
        return loglike
    
  6. Utilisez votre fonction pour calculer la vraisemble du modèle 1 par rapport aux données.

    loglike = loglikelihood(bn1, data_df)
    print(loglike)
    
    -22879.905249672032
    

2.2. Autres modèles

Reprendre les étapes précédentes sur les modèles suivants :

  • Modèle 2 : \(A \leftarrow V \rightarrow E\).
  • Modèle 3 : \(A \leftarrow V \rightarrow E\) et \(A \rightarrow E\).
  • Modèle 4 : \(A \leftarrow V \rightarrow E\) et \(A \leftarrow E\).
  • Modèle 5 : \(A\), \(V\), \(E\) indépendantes.

Comparer les log-vraisemblance obtenues et expliquer les résultats.

# SOLUTION
# --------
# Spécification des structures de chaque modèle
bn_struct_specs = {
    "M1": [("Age", "Vente_livret_A"), ("Epargne", "Vente_livret_A")],
    "M2": [("Vente_livret_A", "Age"), ("Vente_livret_A", "Epargne")],
    "M3": [("Vente_livret_A", "Age"), ("Vente_livret_A", "Epargne"), ("Age", "Epargne")],
    "M4": [("Vente_livret_A", "Age"), ("Vente_livret_A", "Epargne"), ("Epargne", "Age")],
    "M5": [],
}

# Boucle de construction de chaque modèle
bn_models = {}
for bn_name, struct in bn_struct_specs.items():
    # Construction du RB courant
    bn = gum.BayesNet(bn_name)
    bn.add(va_V)
    bn.add(va_A)
    bn.add(va_E)
    [bn.addArc(*arc) for arc in struct]

    # Apprentissage des LPC
    learner.fitParameters(bn)
    
    bn_models[bn_name] = bn

# Boucle de calcul des vraisemblances de chaque modèle
bn_loglike = {}
for bn_name, bn in bn_models.items():
    bn_loglike[bn_name] = loglikelihood(bn, data_df)

print(bn_loglike)
{'M1': -22879.905249672032, 'M2': -23296.642978057353, 'M3': -21958.960794571823, 'M4': -21958.960794571823, 'M5': -24196.13497634752}

3. Évaluation des modèles avec pyAGRuM

L'objectif est à présent de tester les performances de nos modèles pour effectuer des ciblages de clientèle. Les clients à cibler se trouvent dans le fichier vente_livret_A_donnees_100_test.csv contenant ce que nous appellerons dans la suite les données de test. Ces données contiennent à la fois les caractéristiques des clients à cibler (âge, épargne) mais aussi le résultat réel de la vente. La performance d'un modèle correspondra donc au taux de bonnes prédictions du modèle sur l'issue des ventes.

Pour ce faire, vous pouvez utiliser la fonction predict suivante permettant de calculer les probabilités a posteriori d'une variable à partir de l'observation d'autres variables.

import numpy as np
import sys

def predict(bn, data, var_target, show_progress=False):
    """
    This function is used to predict the posterior probability of a target variable from observations  
    using a bayesian network model. 

    Inputs:
    - =bn=: the predictive model given as a =pyAgrum.BayesNet= object
    - =data=: the data containing the observations used to predict the target variable 
    as a =pandas.DataFrame= object
    - =var_target=: the name of the target variable as a =str= object

    Returns:
    - a =DataFrame= containing the posterior probability distribution of the target 
    variable given each observation in =data=.
    """
    # Initialize the inference engine
    inf_bn = gum.LazyPropagation(bn)
    inf_bn.setTargets({var_target})
    nb_data = len(data)
    target_size = bn.variable(var_target).domainSize()
    target_dom = np.array([bn.variable(var_target).label(i)
                           for i in range(target_size)])
    data_records = data.to_dict("records")
    post_prob = np.zeros((nb_data, target_size))
    for i in range(nb_data):
        # Set the evidence
        inf_bn.setEvidence(data_records[i])
        # Run inference
        inf_bn.makeInference()
        # Compute posterior probability of target variable
        post_prob[i, :] = inf_bn.posterior(var_target).toarray()
        # Erase evidence
        inf_bn.eraseAllEvidence()
        if show_progress:
            sys.stdout.write("predict progress: {0:3.0%}\r".format(i/nb_data))

    post_prob_df = pd.DataFrame(post_prob,
                                index=data.index,
                                columns=bn.variable(var_target).labels())
    post_prob_df.columns.name = var_target
    return post_prob_df

Réaliser les étapes suivantes pour chacun des modèles construits à la phase précédente :

  1. Chargez le fichier de données de test vente_livret_A_donnees_100_test.csv dans un DataFrame nommé data_test_df.

    # SOLUTION
    # --------
    data_test_df = pd.read_csv("vente_livret_A_donnees_100_test.csv")
    
  2. Utilisez la fonction predict avec le modèle 1 de manière à prédire les lois a posteriori de la variable Vente_livret_A conditionnellement à chaque observation des variables Age et Epargne dans les données de test.

    m1_prob_test = predict(bn1, data=data_test_df[["Age", "Epargne"]], var_target="Vente_livret_A")
    
  3. Calculez, pour chaque prédiction, la modalité (echec ou succes) qui maximise la probabilité a posteriori.

    m1_pred_test = m1_prob_test.idxmax(axis=1)
    
  4. Calculez le taux de bonnes prédictions du modèle 1 sur les données de test.

    # SOLUTION
    # --------
    m1_accuracy_test = (data_test_df["Vente_livret_A"] == m1_pred_test).mean()
    
  5. Comparer les performances observées de ce chaque modèle et expliquer les résultats.
# SOLUTION
# --------
# Boucle de calcul des prédictions et des performances
bn_accuracy = {}
for bn_name, bn in bn_models.items():
    prob_test = predict(bn, data=data_test_df[["Age", "Epargne"]],
                        var_target="Vente_livret_A")
    pred_test = prob_test.idxmax(axis=1)
    accuracy_test = (data_test_df["Vente_livret_A"] == pred_test).mean()

    bn_accuracy[bn_name] = accuracy_test

print(bn_accuracy)
{'M1': 0.74, 'M2': 0.67, 'M3': 0.74, 'M4': 0.74, 'M5': 0.53}