Tutoriel : Créer, entraîner et évaluer un modèle d’élévation
Ce tutoriel présente un exemple de bout en bout d’un flux de travail Synapse Data Science, dans Microsoft Fabric. Vous apprenez à créer, entraîner et évaluer des modèles de élévation et appliquer des techniques de modélisation de élévation.
Conditions préalables
Souscrivez à un abonnement Microsoft Fabric . Vous pouvez également vous inscrire à une version d’évaluation gratuite de Microsoft Fabric .
Connectez-vous à Microsoft Fabric.
Utilisez le sélecteur d’expérience en bas à gauche de votre page d’accueil pour basculer vers Fabric.
- Une connaissance des notebooks Microsoft Fabric
- Un lakehouse pour ce notebook afin de stocker des données pour cet exemple. Pour plus d’informations, consultez Ajouter un lakehouse à votre notebook
Suivre dans un notebook
Vous pouvez suivre dans un bloc-notes de l’une des deux manières suivantes :
- Ouvrez et exécutez le notebook intégré.
- Chargez votre bloc-notes à partir de GitHub.
Ouvrir le notebook intégré
L’exemple de notebook Modélisation de l’élévation accompagne ce tutoriel.
Pour ouvrir le carnet d'exemples pour ce didacticiel, suivez les instructions de Préparer votre système pour les didacticiels des sciences des données.
Assurez-vous d’attacher un lakehouse au notebook avant de commencer à exécuter du code.
Importer le notebook à partir de GitHub
Le bloc-notes AIsample - Uplift Modeling.ipynb accompagne ce didacticiel.
Pour ouvrir le bloc-notes associé pour ce didacticiel, suivez les instructions de Préparer votre système pour les didacticiels de science des données, pour importer le bloc-notes dans votre espace de travail.
Vous pouvez créer un bloc-notes si vous préférez copier et coller le code à partir de cette page.
Assurez-vous d’attacher un lakehouse au notebook avant de commencer à exécuter du code.
Étape 1 : Charger les données
Jeu de données
Criteo AI Lab a créé le jeu de données. Ce jeu de données comporte 13 M de lignes. Chaque ligne représente un utilisateur. Chaque ligne a 12 attributs, un indicateur de traitement et deux étiquettes binaires comprenant la visite et la conversion.
- f0 - f11: valeurs de caractéristiques (denses, valeurs flottantes)
- traitement : que ce soit ou non un utilisateur ciblé aléatoirement pour un traitement (par exemple, publicité) (1 = traitement, 0 = contrôle)
- conversion: si une conversion s’est produite (par exemple, effectué un achat) pour un utilisateur (binaire, étiquette)
- visite : pour savoir si une conversion s’est produite (par exemple, un achat) pour un utilisateur (binaire, étiquette)
Citation
- Page d’accueil du jeu de données : https://ailab.criteo.com/criteo-uplift-prediction-dataset/
Le jeu de données utilisé pour ce notebook nécessite cette citation BibTex :
@inproceedings{Diemert2018,
author = {{Diemert Eustache, Betlei Artem} and Renaudin, Christophe and Massih-Reza, Amini},
title={A Large Scale Benchmark for Uplift Modeling},
publisher = {ACM},
booktitle = {Proceedings of the AdKDD and TargetAd Workshop, KDD, London,United Kingdom, August, 20, 2018},
year = {2018}
}
Conseil
En définissant les paramètres suivants, vous pouvez facilement appliquer ce notebook à différents jeux de données.
IS_CUSTOM_DATA = False # If True, the user must upload the dataset manually
DATA_FOLDER = "Files/uplift-modelling"
DATA_FILE = "criteo-research-uplift-v2.1.csv"
# Data schema
FEATURE_COLUMNS = [f"f{i}" for i in range(12)]
TREATMENT_COLUMN = "treatment"
LABEL_COLUMN = "visit"
EXPERIMENT_NAME = "aisample-upliftmodelling" # MLflow experiment name
Importer des bibliothèques
Avant de traiter, vous devez importer les bibliothèques Spark et SynapseML requises. Vous devez également importer une bibliothèque de visualisation de données , par exemple Seaborn, une bibliothèque de visualisation de données Python. Une bibliothèque de visualisation de données fournit une interface de haut niveau pour créer des ressources visuelles sur des DataFrames et des tableaux. En savoir plus sur Spark, SynapseMLet Seaborn.
import os
import gzip
import pyspark.sql.functions as F
from pyspark.sql.window import Window
from pyspark.sql.types import *
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.style as style
import seaborn as sns
%matplotlib inline
from synapse.ml.featurize import Featurize
from synapse.ml.core.spark import FluentAPI
from synapse.ml.lightgbm import *
from synapse.ml.train import ComputeModelStatistics
import mlflow
Télécharger un jeu de données et charger dans lakehouse
Ce code télécharge une version publique du jeu de données, puis stocke cette ressource de données dans un lac Fabric.
Important
Assurez-vous d’Ajouter un lakehouse au notebook avant de l’exécuter. L’échec de cette opération entraîne une erreur.
if not IS_CUSTOM_DATA:
# Download demo data files into lakehouse if not exist
import os, requests
remote_url = "http://go.criteo.net/criteo-research-uplift-v2.1.csv.gz"
download_file = "criteo-research-uplift-v2.1.csv.gz"
download_path = f"/lakehouse/default/{DATA_FOLDER}/raw"
if not os.path.exists("/lakehouse/default"):
raise FileNotFoundError("Default lakehouse not found, please add a lakehouse and restart the session.")
os.makedirs(download_path, exist_ok=True)
if not os.path.exists(f"{download_path}/{DATA_FILE}"):
r = requests.get(f"{remote_url}", timeout=30)
with open(f"{download_path}/{download_file}", "wb") as f:
f.write(r.content)
with gzip.open(f"{download_path}/{download_file}", "rb") as fin:
with open(f"{download_path}/{DATA_FILE}", "wb") as fout:
fout.write(fin.read())
print("Downloaded demo data files into lakehouse.")
Démarrez l’enregistrement du runtime de ce notebook.
# Record the notebook running time
import time
ts = time.time()
Configurer le suivi des expériences MLflow
Pour étendre les fonctionnalités de journalisation MLflow, l'autologging capture automatiquement les valeurs des paramètres d’entrée et des métriques de sortie d’un modèle de machine learning pendant son entraînement. Ces informations sont ensuite consignées dans l’espace de travail, où les API MLflow ou l’expérience correspondante dans l’espace de travail peuvent y accéder et les visualiser. Pour plus d'informations sur la journalisation automatique, consultez cette ressource.
# Set up the MLflow experiment
import mlflow
mlflow.set_experiment(EXPERIMENT_NAME)
mlflow.autolog(disable=True) # Disable MLflow autologging
Note
Pour désactiver la journalisation automatique de Microsoft Fabric dans une session de notebook, appelez mlflow.autolog()
et configurez disable=True
.
Lire des données à partir du lakehouse
Lisez les données brutes de la section Fichiers du lakehouse et ajoutez d’autres colonnes pour différentes parties de date. Les mêmes informations sont utilisées pour créer une table delta partitionnée.
raw_df = spark.read.csv(f"{DATA_FOLDER}/raw/{DATA_FILE}", header=True, inferSchema=True).cache()
Étape 2 : Analyse exploratoire des données
Utilisez la commande display
pour afficher des statistiques générales sur le jeu de données. Vous pouvez également afficher les vues graphiques pour visualiser facilement les sous-ensembles du jeu de données.
display(raw_df.limit(20))
Examinez le pourcentage des utilisateurs qui visitent, le pourcentage d’utilisateurs qui convertissent et le pourcentage des visiteurs qui se convertissent.
raw_df.select(
F.mean("visit").alias("Percentage of users that visit"),
F.mean("conversion").alias("Percentage of users that convert"),
(F.sum("conversion") / F.sum("visit")).alias("Percentage of visitors that convert"),
).show()
L’analyse indique que 4,9 % des utilisateurs du groupe de traitement (les utilisateurs qui ont reçu le traitement ou la publicité) ont visité la boutique en ligne. Seulement 3,8% d’utilisateurs du groupe de contrôle, c’est-à-dire les utilisateurs auxquels le traitement n'a jamais été proposé ou qui n'ont jamais été exposés à la publicité, ont fait de même. En outre, 0,31% des utilisateurs du groupe de traitement ont converti, c'est-à-dire effectué un achat, alors que seulement 0,19% des utilisateurs du groupe de contrôle l'ont fait. Par conséquent, le taux de conversion des visiteurs qui ont effectué un achat, qui étaient également membres du groupe de traitement, est 6,36%, comparativement à seulement 5,07%** pour les utilisateurs du groupe de contrôle. En fonction de ces résultats, le traitement peut potentiellement améliorer le taux de visite d’environ 1%, et le taux de conversion des visiteurs d’environ 1,3%. Le traitement entraîne une amélioration significative.
Étape 3 : Définir le modèle pour l’entraînement
Préparer l’entraînement et tester les jeux de données
Ici, ajustez un transformateur Featurize au dataframe raw_df
, pour extraire les caractéristiques des colonnes d’entrée spécifiées et les enregistrez dans une nouvelle colonne nommée features
.
Le DataFrame résultant est stocké dans un nouveau DataFrame nommé df
.
transformer = Featurize().setOutputCol("features").setInputCols(FEATURE_COLUMNS).fit(raw_df)
df = transformer.transform(raw_df)
# Split the DataFrame into training and test sets, with a 80/20 ratio and a seed of 42
train_df, test_df = df.randomSplit([0.8, 0.2], seed=42)
# Print the training and test dataset sizes
print("Size of train dataset: %d" % train_df.count())
print("Size of test dataset: %d" % test_df.count())
# Group the training dataset by the treatment column, and count the number of occurrences of each value
train_df.groupby(TREATMENT_COLUMN).count().show()
Préparer les jeux de données de traitement et de contrôle
Après avoir créé les jeux de données d’entraînement et de test, vous devez également former les jeux de données de traitement et de contrôle pour entraîner les modèles Machine Learning pour mesurer l’élévation.
# Extract the treatment and control DataFrames
treatment_train_df = train_df.where(f"{TREATMENT_COLUMN} > 0")
control_train_df = train_df.where(f"{TREATMENT_COLUMN} = 0")
Maintenant que vous avez préparé vos données, vous pouvez continuer à entraîner un modèle avec LightGBM.
Modélisation de l’élévation : T-Learner avec LightGBM
Les méta-apprenants sont un ensemble d’algorithmes, basés sur des algorithmes de Machine Learning comme LightGBM, Xgboost, etc. Ils aident à estimer l’effet moyen conditionnel du traitement, ou CATE. T-learner est un méta-apprenant qui n’utilise pas de modèle unique. Au lieu de cela, T-learner utilise un modèle par variable de traitement. Par conséquent, deux modèles sont développés et nous faisons référence au méta-apprenant en tant qu’apprenant T-learner. T-learner utilise plusieurs modèles de Machine Learning pour surmonter le problème de l’abandon complet du traitement, en obligeant l’apprenant à d’abord le diviser selon cet aspect.
mlflow.autolog(exclusive=False)
classifier = (
LightGBMClassifier(dataTransferMode="bulk")
.setFeaturesCol("features") # Set the column name for features
.setNumLeaves(10) # Set the number of leaves in each decision tree
.setNumIterations(100) # Set the number of boosting iterations
.setObjective("binary") # Set the objective function for binary classification
.setLabelCol(LABEL_COLUMN) # Set the column name for the label
)
# Start a new MLflow run with the name "uplift"
active_run = mlflow.start_run(run_name="uplift")
# Start a new nested MLflow run with the name "treatment"
with mlflow.start_run(run_name="treatment", nested=True) as treatment_run:
treatment_run_id = treatment_run.info.run_id # Get the ID of the treatment run
treatment_model = classifier.fit(treatment_train_df) # Fit the classifier on the treatment training data
# Start a new nested MLflow run with the name "control"
with mlflow.start_run(run_name="control", nested=True) as control_run:
control_run_id = control_run.info.run_id # Get the ID of the control run
control_model = classifier.fit(control_train_df) # Fit the classifier on the control training data
Utiliser le jeu de données de test pour une prédiction
Ici, vous utilisez les treatment_model
et les control_model
, définis précédemment, pour transformer le jeu de données de test test_df
. Ensuite, vous calculez l’élévation prédite. Vous définissez l’élévation prédite comme la différence entre le résultat du traitement prédit et le résultat de contrôle prédit. Plus la différence prédite d'augmentation est grande, plus l'efficacité du traitement (par exemple, la publicité) sur un individu ou un sous-groupe est élevée.
getPred = F.udf(lambda v: float(v[1]), FloatType())
# Cache the resulting DataFrame for easier access
test_pred_df = (
test_df.mlTransform(treatment_model)
.withColumn("treatment_pred", getPred("probability"))
.drop("rawPrediction", "probability", "prediction")
.mlTransform(control_model)
.withColumn("control_pred", getPred("probability"))
.drop("rawPrediction", "probability", "prediction")
.withColumn("pred_uplift", F.col("treatment_pred") - F.col("control_pred"))
.select(TREATMENT_COLUMN, LABEL_COLUMN, "treatment_pred", "control_pred", "pred_uplift")
.cache()
)
# Display the first twenty rows of the resulting DataFrame
display(test_pred_df.limit(20))
Effectuer une évaluation de modèle
Étant donné que l’élévation réelle ne peut pas être observée pour chaque individu, vous devez mesurer l’élévation sur un groupe d’individus. Vous utilisez une courbe d’élévation qui trace l’élévation cumulative réelle de la population.
L’axe des x représente le ratio de la population sélectionnée pour le traitement. Une valeur de 0 suggère qu'aucun groupe de traitement n'existe – personne n'est exposé au traitement, ni ne se voit proposer le traitement. Une valeur de 1 suggère un groupe de traitement complet : tout le monde est exposé ou se voit offrir le traitement. L’axe y montre la mesure d’élévation. L’objectif est de trouver la taille du groupe de traitement, ou le pourcentage de la population qui serait proposée ou exposée au traitement (par exemple, la publicité). Cette approche optimise la sélection cible pour optimiser le résultat.
Commencez par classer l’ordre du dataFrame de test en fonction de l’élévation prédite. L’élévation prédite est la différence entre le résultat du traitement prédit et le résultat de contrôle prédit.
# Compute the percentage rank of the predicted uplift values in descending order, and display the top twenty rows
test_ranked_df = test_pred_df.withColumn("percent_rank", F.percent_rank().over(Window.orderBy(F.desc("pred_uplift"))))
display(test_ranked_df.limit(20))
Ensuite, calculez le pourcentage cumulé de visites dans les groupes de traitement et de contrôle.
# Calculate the number of control and treatment samples
C = test_ranked_df.where(f"{TREATMENT_COLUMN} == 0").count()
T = test_ranked_df.where(f"{TREATMENT_COLUMN} != 0").count()
# Add columns to the DataFrame to calculate the control and treatment cumulative sum
test_ranked_df = (
test_ranked_df.withColumn(
"control_label",
F.when(F.col(TREATMENT_COLUMN) == 0, F.col(LABEL_COLUMN)).otherwise(0),
)
.withColumn(
"treatment_label",
F.when(F.col(TREATMENT_COLUMN) != 0, F.col(LABEL_COLUMN)).otherwise(0),
)
.withColumn(
"control_cumsum",
F.sum("control_label").over(Window.orderBy("percent_rank")) / C,
)
.withColumn(
"treatment_cumsum",
F.sum("treatment_label").over(Window.orderBy("percent_rank")) / T,
)
)
# Display the first 20 rows of the dataframe
display(test_ranked_df.limit(20))
Enfin, à chaque pourcentage, calculez l’élévation du groupe comme la différence entre le pourcentage cumulé de visites entre le traitement et les groupes de contrôle.
test_ranked_df = test_ranked_df.withColumn("group_uplift", F.col("treatment_cumsum") - F.col("control_cumsum")).cache()
display(test_ranked_df.limit(20))
À présent, tracez la courbe d’élévation pour la prédiction du jeu de données de test. Vous devez convertir le DataFrame PySpark en dataFrame Pandas avant de tracer.
def uplift_plot(uplift_df):
"""
Plot the uplift curve
"""
gain_x = uplift_df.percent_rank
gain_y = uplift_df.group_uplift
# Plot the data
fig = plt.figure(figsize=(10, 6))
mpl.rcParams["font.size"] = 8
ax = plt.plot(gain_x, gain_y, color="#2077B4", label="Normalized Uplift Model")
plt.plot(
[0, gain_x.max()],
[0, gain_y.max()],
"--",
color="tab:orange",
label="Random Treatment",
)
plt.legend()
plt.xlabel("Porportion Targeted")
plt.ylabel("Uplift")
plt.grid()
return fig, ax
test_ranked_pd_df = test_ranked_df.select(["pred_uplift", "percent_rank", "group_uplift"]).toPandas()
fig, ax = uplift_plot(test_ranked_pd_df)
mlflow.log_figure(fig, "UpliftCurve.png")
L’axe des x représente le ratio de la population sélectionnée pour le traitement. Une valeur de 0 suggère qu'aucun groupe de traitement n'existe, et que personne n'est exposé ni proposé au traitement. Une valeur de 1 suggère un groupe de traitement complet : tout le monde est exposé au traitement ou se voit offrir le traitement. L’axe y montre la mesure d’élévation. L’objectif est de trouver la taille du groupe de traitement, ou le pourcentage de la population qui serait proposée ou exposée au traitement (par exemple, la publicité). Cette approche optimise la sélection cible pour optimiser le résultat.
Commencez par classer l’ordre du dataFrame de test en fonction de l’élévation prédite. L’élévation prédite est la différence entre le résultat du traitement prédit et le résultat de contrôle prédit.
# Compute the percentage rank of the predicted uplift values in descending order, and display the top twenty rows
test_ranked_df = test_pred_df.withColumn("percent_rank", F.percent_rank().over(Window.orderBy(F.desc("pred_uplift"))))
display(test_ranked_df.limit(20))
Ensuite, calculez le pourcentage cumulé de visites dans les groupes de traitement et de contrôle.
# Calculate the number of control and treatment samples
C = test_ranked_df.where(f"{TREATMENT_COLUMN} == 0").count()
T = test_ranked_df.where(f"{TREATMENT_COLUMN} != 0").count()
# Add columns to the DataFrame to calculate the control and treatment cumulative sum
test_ranked_df = (
test_ranked_df.withColumn(
"control_label",
F.when(F.col(TREATMENT_COLUMN) == 0, F.col(LABEL_COLUMN)).otherwise(0),
)
.withColumn(
"treatment_label",
F.when(F.col(TREATMENT_COLUMN) != 0, F.col(LABEL_COLUMN)).otherwise(0),
)
.withColumn(
"control_cumsum",
F.sum("control_label").over(Window.orderBy("percent_rank")) / C,
)
.withColumn(
"treatment_cumsum",
F.sum("treatment_label").over(Window.orderBy("percent_rank")) / T,
)
)
# Display the first 20 rows of the dataframe
display(test_ranked_df.limit(20))
Enfin, à chaque pourcentage, calculez l’élévation du groupe comme la différence entre le pourcentage cumulé de visites entre le traitement et les groupes de contrôle.
test_ranked_df = test_ranked_df.withColumn("group_uplift", F.col("treatment_cumsum") - F.col("control_cumsum")).cache()
display(test_ranked_df.limit(20))
À présent, tracez la courbe d’élévation pour la prédiction du jeu de données de test. Vous devez convertir le DataFrame PySpark en dataFrame Pandas avant de tracer.
def uplift_plot(uplift_df):
"""
Plot the uplift curve
"""
gain_x = uplift_df.percent_rank
gain_y = uplift_df.group_uplift
# Plot the data
fig = plt.figure(figsize=(10, 6))
mpl.rcParams["font.size"] = 8
ax = plt.plot(gain_x, gain_y, color="#2077B4", label="Normalized Uplift Model")
plt.plot(
[0, gain_x.max()],
[0, gain_y.max()],
"--",
color="tab:orange",
label="Random Treatment",
)
plt.legend()
plt.xlabel("Porportion Targeted")
plt.ylabel("Uplift")
plt.grid()
return fig, ax
test_ranked_pd_df = test_ranked_df.select(["pred_uplift", "percent_rank", "group_uplift"]).toPandas()
fig, ax = uplift_plot(test_ranked_pd_df)
mlflow.log_figure(fig, "UpliftCurve.png")
L’analyse et la courbe d’élévation montrent toutes deux que les 20 % de personnes les mieux classées, selon la prédiction, bénéficieraient d’un gain important si elles recevaient le traitement. Cela signifie que les 20 % supérieurs de la population représentent le groupe de personnes persuadables. Vous pouvez donc définir la note seuil pour la taille souhaitée du groupe de traitement à 20 %, afin d’identifier les clients cibles de la sélection qui auront le plus d’impact.
cutoff_percentage = 0.2
cutoff_score = test_ranked_pd_df.iloc[int(len(test_ranked_pd_df) * cutoff_percentage)][
"pred_uplift"
]
print("Uplift scores that exceed {:.4f} map to Persuadables.".format(cutoff_score))
mlflow.log_metrics(
{"cutoff_score": cutoff_score, "cutoff_percentage": cutoff_percentage}
)
Étape 4 : Inscrire le modèle ML final
Vous utilisez MLflow pour suivre et consigner toutes les expériences pour les groupes de traitement et de contrôle. Ce suivi et cette journalisation incluent les paramètres, les métriques et les modèles correspondants. Ces informations sont enregistrées sous le nom de l’expérience, dans l’espace de travail, pour une utilisation ultérieure.
# Register the model
treatment_model_uri = "runs:/{}/model".format(treatment_run_id)
mlflow.register_model(treatment_model_uri, f"{EXPERIMENT_NAME}-treatmentmodel")
control_model_uri = "runs:/{}/model".format(control_run_id)
mlflow.register_model(control_model_uri, f"{EXPERIMENT_NAME}-controlmodel")
mlflow.end_run()
Pour afficher vos expériences :
- Dans le volet gauche, sélectionnez votre espace de travail.
- Recherchez et sélectionnez le nom de l’expérience. Dans ce cas aisample-upliftmodelling.
Étape 5 : Enregistrer les résultats de prédiction
Microsoft Fabric offre PREDICT - une fonction évolutive qui prend en charge le scoring par lots dans n’importe quel moteur de calcul. Il permet aux clients d’opérationnaliser des modèles Machine Learning. Les utilisateurs peuvent créer des prédictions par lots directement à partir d’un bloc-notes ou de la page d’élément d’un modèle spécifique. Visitez cette ressource pour en savoir plus sur PREDICT et découvrir comment utiliser PREDICT dans Microsoft Fabric.
# Load the model back
loaded_treatmentmodel = mlflow.spark.load_model(treatment_model_uri, dfs_tmpdir="Files/spark")
loaded_controlmodel = mlflow.spark.load_model(control_model_uri, dfs_tmpdir="Files/spark")
# Make predictions
batch_predictions_treatment = loaded_treatmentmodel.transform(test_df)
batch_predictions_control = loaded_controlmodel.transform(test_df)
batch_predictions_treatment.show(5)
# Save the predictions in the lakehouse
batch_predictions_treatment.write.format("delta").mode("overwrite").save(
f"{DATA_FOLDER}/predictions/batch_predictions_treatment"
)
batch_predictions_control.write.format("delta").mode("overwrite").save(
f"{DATA_FOLDER}/predictions/batch_predictions_control"
)
# Determine the entire runtime
print(f"Full run cost {int(time.time() - ts)} seconds.")