Självstudie: Skapa, träna och utvärdera en upplyftningsmodell

I den här självstudien visas ett exempel från slutpunkt till slutpunkt på ett Synapse-Datavetenskap arbetsflöde i Microsoft Fabric. Du lär dig hur du skapar, tränar och utvärderar upplyftningsmodeller och tillämpar upplyftningsmodelleringstekniker.

Förutsättningar

Följ med i en notebook-fil

Du kan följa med i en notebook-fil på något av två sätt:

  • Öppna och kör den inbyggda notebook-filen i Synapse Datavetenskap-upplevelsen
  • Ladda upp din notebook-fil från GitHub till Synapse Datavetenskap-upplevelsen

Öppna den inbyggda notebook-filen

Exempelanteckningsboken för Uplift-modellering medföljer den här självstudien. Besök För att öppna självstudiekursens inbyggda exempelanteckningsbok i Synapse Datavetenskap experience:1. Gå till startsidan för Synapse Datavetenskap. 1. Välj Använd ett exempel. 1. Välj motsvarande exempel:* På standardfliken för arbetsflöden från slutpunkt till slutpunkt (Python) om exemplet är för en Python-självstudiekurs. * Från fliken Arbetsflöden från slutpunkt till slutpunkt (R) om exemplet är för en R-självstudie. * Från fliken Snabb självstudier , om exemplet är för en snabb självstudie.1. Koppla ett lakehouse till notebook-filen innan du börjar köra kod. för mer information om åtkomst till inbyggda exempelanteckningsböcker för självstudier.

Så här öppnar du självstudiekursens inbyggda exempelanteckningsbok i Synapse Datavetenskap upplevelse:

  1. Gå till startsidan för Synapse Datavetenskap

  2. Välj Använd ett exempel

  3. Välj motsvarande exempel:

    1. Från standardfliken för arbetsflöden från slutpunkt till slutpunkt (Python) om exemplet är för en Python-självstudie
    2. Från fliken Arbetsflöden från slutpunkt till slutpunkt (R) om exemplet är för en R-självstudie
    3. Om exemplet är till för en snabb självstudiekurs på fliken Snabbstudier
  4. Koppla ett lakehouse till notebook-filen innan du börjar köra kod

Importera anteckningsboken från GitHub

Notebook-filen AIsample – Uplift Modeling.ipynb medföljer den här självstudien.

Om du vill öppna den medföljande notebook-filen för den här självstudien följer du anvisningarna i Förbereda systemet för självstudier för datavetenskap för att importera anteckningsboken till din arbetsyta.

Du kan skapa en ny notebook-fil om du hellre kopierar och klistrar in koden från den här sidan.

Se till att bifoga ett lakehouse i notebook-filen innan du börjar köra kod.

Steg 1: Läs in data

Datamängd

Criteo AI Lab skapade datauppsättningen. Datamängden har 13 miljoner rader. Varje rad representerar en användare. Varje rad har 12 funktioner, en behandlingsindikator och två binära etiketter som inkluderar besök och konvertering.

f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 behandling konvertering

  • f0 – f11: funktionsvärden (täta, flytande värden)
  • behandling: om en användare slumpmässigt var mål för behandling (till exempel reklam) (1 = behandling, 0 = kontroll)
  • konvertering: om en konvertering har inträffat (till exempel gjort ett köp) för en användare (binär, etikett)
  • besök: om en konvertering har inträffat (till exempel gjort ett köp) för en användare (binär, etikett)

Hänvisning

Den datauppsättning som används för den här notebook-filen kräver denna BibTex-källhänvisning:

@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}
}

Dricks

Genom att definiera följande parametrar kan du enkelt använda den här notebook-filen på olika datauppsättningar.

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

Importera bibliotek

Innan du bearbetar måste du importera nödvändiga Spark- och SynapseML-bibliotek. Du måste också importera ett datavisualiseringsbibliotek – till exempel Seaborn, ett Python-datavisualiseringsbibliotek. Ett datavisualiseringsbibliotek tillhandahåller ett högnivågränssnitt för att skapa visuella resurser på DataFrames och matriser. Läs mer om Spark, SynapseML och 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

Ladda ned en datauppsättning och ladda upp till lakehouse

Den här koden laddar ned en offentligt tillgänglig version av datauppsättningen och lagrar sedan den dataresursen i en Infrastruktursjöhus.

Viktigt!

Se till att du lägger till ett sjöhus i anteckningsboken innan du kör det. Om du inte gör det uppstår ett fel.

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.")

Börja spela in körningen av den här notebook-filen.

# Record the notebook running time
import time

ts = time.time()

Konfigurera MLflow-experimentspårning

För att utöka MLflow-loggningsfunktionerna samlar automatisk loggning automatiskt in värdena för indataparametrar och utdatamått för en maskininlärningsmodell under träningen. Den här informationen loggas sedan till arbetsytan, där MLflow-API:erna eller motsvarande experiment på arbetsytan kan komma åt och visualisera den. Besök den här resursen om du vill ha mer information om automatisk loggning.

# Set up the MLflow experiment
import mlflow

mlflow.set_experiment(EXPERIMENT_NAME)
mlflow.autolog(disable=True)  # Disable MLflow autologging

Kommentar

Om du vill inaktivera automatisk loggning av Microsoft Fabric i en notebook-session anropar mlflow.autolog() du och anger disable=True.

Läsa data från lakehouse

Läs rådata från avsnittet Lakehouse Files och lägg till fler kolumner för olika datumdelar. Samma information används för att skapa en partitionerad deltatabell.

raw_df = spark.read.csv(f"{DATA_FOLDER}/raw/{DATA_FILE}", header=True, inferSchema=True).cache()

Steg 2: Undersökande dataanalys

display Använd kommandot för att visa statistik på hög nivå om datauppsättningen. Du kan också visa diagramvyerna för att enkelt visualisera delmängder av datamängden.

display(raw_df.limit(20))

Granska procentandelen av de användare som besöker, procentandelen användare som konverterar och procentandelen besökare som konverterar.

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()

Analysen visar att 4,9% av användarna från behandlingsgruppen - användare som fick behandlingen eller reklam - besökte onlinebutiken. Endast 3,8% av användarna från kontrollgruppen - användare som aldrig fick behandlingen, eller aldrig erbjöds eller exponerades för reklam - gjorde detsamma. Dessutom konverterade eller gjorde 0,31 % av alla användare från behandlingsgruppen ett köp , medan endast 0,19 % av användarna från kontrollgruppen gjorde det. Som ett resultat är konverteringsgraden för besökare som gjorde ett köp, som också var medlemmar i behandlingsgruppen, 6,36%, jämfört med endast 5,07%** för användare av kontrollgruppen. Baserat på dessa resultat kan behandlingen potentiellt förbättra besöksfrekvensen med cirka 1%, och omräkningsfrekvensen för besökare med cirka 1,3%. Behandlingen leder till en betydande förbättring.

Steg 3: Definiera modellen för träning

Förbereda träningen och testa datauppsättningarna

Här kan du anpassa en Featurize-transformator till raw_df DataFrame för att extrahera funktioner från de angivna indatakolumnerna och mata ut dessa funktioner till en ny kolumn med namnet features.

Resulterande DataFrame lagras i en ny DataFrame med namnet 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()

Förbereda datauppsättningarna för behandling och kontroll

När du har skapat tränings- och testdatauppsättningarna måste du också bilda datauppsättningarna för behandling och kontroll för att träna maskininlärningsmodellerna för att mäta upplyftningen.

# 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")

Nu när du har förberett dina data kan du fortsätta att träna en modell med LightGBM.

Upplyftningsmodellering: T-Learner med LightGBM

Metainlärare är en uppsättning algoritmer som bygger på maskininlärningsalgoritmer som LightGBM, Xgboost osv. De hjälper till att beräkna villkorsstyrd genomsnittlig behandlingseffekt, eller CATE. T-learner är en metainlärare som inte använder en enda modell. I stället använder T-learner en modell per behandlingsvariabel. Därför utvecklas två modeller och vi refererar till meta-learner som T-learner. T-learner använder flera maskininlärningsmodeller för att lösa problemet med att helt ta bort behandlingen, genom att tvinga eleven att först dela på den.

mlflow.autolog(exclusive=False)
classifier = (
    LightGBMClassifier()
    .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
     

Använda testdatauppsättningen för en förutsägelse

Här använder treatment_model du och control_model, som båda definierades tidigare, för att transformera testdatauppsättningen test_df . Sedan beräknar du den förväntade upplyftningen. Du definierar den förutsagda upplyftningen som skillnaden mellan det förväntade behandlingsresultatet och det förutsagda kontrollresultatet. Ju större denna förutsagda ökningsskillnad är, desto större blir behandlingens effektivitet (till exempel reklam) på en individ eller en undergrupp.

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))

Utföra modellutvärdering

Eftersom faktisk upplyftning inte kan observeras för varje individ måste du mäta upplyftningen över en grupp individer. Du använder en upplyftningskurva som ritar den verkliga, kumulativa upplyftningen i hela populationen.

Skärmbild av ett diagram som visar en normaliserad upplyftningsmodellkurva jämfört med slumpmässig behandling.

X-axeln representerar förhållandet mellan den population som valts för behandlingen. Ett värde på 0 tyder på att ingen behandlingsgrupp - ingen exponeras för, eller erbjuds, behandlingen. Ett värde på 1 tyder på en fullständig behandlingsgrupp - alla utsätts för, eller erbjuds, behandlingen. Y-axeln visar upplyftningsmåttet. Syftet är att hitta storleken på behandlingsgruppen, eller den procentandel av befolkningen som skulle erbjudas eller exponeras för behandlingen (till exempel reklam). Den här metoden optimerar målvalet för att optimera resultatet.

Rangordna först dataramens testordning efter den förutsagda upplyftningen. Den förutsagda upplyftningen är skillnaden mellan det förväntade behandlingsresultatet och det förväntade kontrollresultatet.

# 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))

Beräkna sedan den kumulativa procentandelen besök i både behandlings- och kontrollgrupperna.

# 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))

Vid varje procent beräknar du slutligen upplyftningen av gruppen som skillnaden mellan den kumulativa procentandelen besök mellan behandlings- och kontrollgrupperna.

test_ranked_df = test_ranked_df.withColumn("group_uplift", F.col("treatment_cumsum") - F.col("control_cumsum")).cache()
display(test_ranked_df.limit(20))

Rita nu upplyftningskurvan för förutsägelsen av testdatauppsättningen. Du måste konvertera PySpark DataFrame till en Pandas DataFrame innan du ritar.

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")

Skärmbild av ett diagram som visar en normaliserad upplyftningsmodellkurva jämfört med slumpmässig behandling.

X-axeln representerar förhållandet mellan den population som valts för behandlingen. Ett värde på 0 tyder på att ingen behandlingsgrupp - ingen exponeras för, eller erbjuds, behandlingen. Ett värde på 1 tyder på en fullständig behandlingsgrupp - alla utsätts för, eller erbjuds, behandlingen. Y-axeln visar upplyftningsmåttet. Syftet är att hitta storleken på behandlingsgruppen, eller den procentandel av befolkningen som skulle erbjudas eller exponeras för behandlingen (till exempel reklam). Den här metoden optimerar målvalet för att optimera resultatet.

Rangordna först dataramens testordning efter den förutsagda upplyftningen. Den förutsagda upplyftningen är skillnaden mellan det förväntade behandlingsresultatet och det förväntade kontrollresultatet.

# 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))

Beräkna sedan den kumulativa procentandelen besök i både behandlings- och kontrollgrupperna.

# 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))

Vid varje procent beräknar du slutligen upplyftningen av gruppen som skillnaden mellan den kumulativa procentandelen besök mellan behandlings- och kontrollgrupperna.

test_ranked_df = test_ranked_df.withColumn("group_uplift", F.col("treatment_cumsum") - F.col("control_cumsum")).cache()
display(test_ranked_df.limit(20))

Rita nu upplyftningskurvan för förutsägelsen av testdatauppsättningen. Du måste konvertera PySpark DataFrame till en Pandas DataFrame innan du ritar.

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")

Skärmbild av ett diagram som visar en normaliserad upplyftningsmodellkurva jämfört med slumpmässig behandling.

Analysen och upplyftningskurvan visar båda att den översta 20% populationen, enligt förutsägelsen, skulle ha en stor vinst om de fick behandlingen. Det innebär att de översta 20 % av populationen representerar gruppen med övertalningsbara objekt. Därför kan du sedan ange brytpoängen för den önskade storleken på behandlingsgruppen till 20 %, för att identifiera målvalskunderna för den största effekten.

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}
)

Steg 4: Registrera den slutliga ML-modellen

Du använder MLflow för att spåra och logga alla experiment för både behandlings- och kontrollgrupper. Den här spårningen och loggningen omfattar motsvarande parametrar, mått och modeller. Den här informationen loggas under experimentnamnet på arbetsytan för senare användning.

# 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()

Så här visar du dina experiment:

  1. Välj din arbetsyta i den vänstra panelen.
  2. Leta upp och välj experimentnamnet, i det här fallet aisample-upliftmodelling.

Skärmbild som visar experimentresultatet för upplyftningsmodellering.

Steg 5: Spara förutsägelseresultatet

Microsoft Fabric erbjuder PREDICT – en skalbar funktion som stöder batchbedömning i alla beräkningsmotorer. Det gör det möjligt för kunder att operationalisera maskininlärningsmodeller. Användare kan skapa batchförutsägelser direkt från en notebook-fil eller objektsidan för en specifik modell. Besök den här resursen om du vill veta mer om PREDICT och lära dig hur du använder PREDICT i 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.")