Utilizzo del Double Machine Learning e della Programmazione Lineare per ottimizzare le strategie di trattamento |  di Ryan O'Sullivan |  Aprile 2024

 | Intelligenza-Artificiale

IA causale, esplorando l'integrazione del ragionamento causale nell'apprendimento automatico

fotografato da Giordano McDonald SU Unsplash

Benvenuti nella mia serie sull'intelligenza artificiale causale, in cui esploreremo l'integrazione del ragionamento causale nei modelli di apprendimento automatico. Aspettatevi di esplorare una serie di applicazioni pratiche in diversi contesti aziendali.

Nell'ultimo articolo abbiamo esplorato effetti del trattamento de-biasing con Double Machine Learning. Questa volta approfondiremo ulteriormente il potenziale della copertura DML utilizzando il Double Machine Learning e la Programmazione Lineare per ottimizzare le strategie di trattamento.

Se ti sei perso l'ultimo articolo sul Double Machine Learning, dai un'occhiata qui:

Questo articolo mostrerà come utilizzare il Double Machine Learning e la Programmazione Lineare per ottimizzare le strategie di trattamento:

Aspettatevi di acquisire un'ampia conoscenza di:

  • Perché le aziende vogliono ottimizzare le strategie di trattamento.
  • In che modo gli effetti medi condizionali del trattamento (CATE) possono aiutare a personalizzare le strategie di trattamento (noto anche come modello Uplift).
  • Come la Programmazione Lineare può essere utilizzata per ottimizzare l'assegnazione del trattamento dati i vincoli di budget.
  • Un caso di studio elaborato in Python che illustra come possiamo utilizzare il Double Machine Learning per stimare CATE e la Programmazione Lineare per ottimizzare le strategie di trattamento.

Il quaderno completo lo trovate qui:

Nella maggior parte delle aziende si pone una domanda comune: “Qual è il trattamento ottimale per un cliente al fine di massimizzare le vendite future minimizzando i costi?”.

Analizziamo questa idea con un semplice esempio.

La tua attività vende calzini online. Non vendi un prodotto essenziale, quindi devi incoraggiare i clienti esistenti a ripetere l'acquisto. La tua leva principale per questo è l'invio di sconti. Quindi la strategia di trattamento in questo caso è l'invio di sconti:

  • Sconto del 10%.
  • Sconto del 20%.
  • Sconto del 50%.

Ogni sconto ha un diverso ritorno sull'investimento. Se ripensi all'ultimo articolo sugli effetti medi del trattamento, probabilmente vedrai come possiamo calcolare l'ATE per ciascuno di questi sconti e quindi selezionare quello con il rendimento più elevato.

Tuttavia, cosa succede se abbiamo effetti del trattamento eterogenei? L’effetto del trattamento varia tra i diversi sottogruppi della popolazione.

Questo è il momento in cui dobbiamo iniziare a considerare gli effetti medi condizionali del trattamento (CATE)!

CATE

CATE è l’impatto medio di un trattamento o intervento su diversi sottogruppi di una popolazione. ATE era incentrato sul “funziona questo trattamento?” mentre la CATE ci permette di cambiare la domanda in “chi dobbiamo trattare?”.

“Condizioniamo” le nostre funzionalità di controllo per consentire che gli effetti del trattamento varino a seconda delle caratteristiche del cliente.

Ripensa all'esempio in cui stiamo inviando sconti. Se i clienti con un numero maggiore di ordini precedenti rispondono meglio agli sconti, possiamo condizionare questa caratteristica del cliente.

Vale la pena sottolineare che nel marketing la stima del CATE viene spesso definita Uplift Modeling.

Stima del CATE con Double Machine Learning

Abbiamo trattato DML nell'ultimo articolo, ma nel caso avessi bisogno di un ripasso:

“Primo stadio:

  • Modello di trattamento (de-biasing): Modello di machine learning utilizzato per stimare la probabilità di assegnazione del trattamento (spesso indicato come punteggio di propensione). Vengono quindi calcolati i residui del modello di trattamento.
  • Modello di risultato (de-noising): Modello di apprendimento automatico utilizzato per stimare il risultato utilizzando solo le funzionalità di controllo. Vengono quindi calcolati i residui del modello di risultato.

Seconda fase:

  • I residui del modello di trattamento vengono utilizzati per prevedere i residui del modello di risultato.

Possiamo utilizzare il Double Machine Learning per stimare la CATE interagendo con le nostre funzionalità di controllo (X) con l'effetto del trattamento nel modello della seconda fase.

Immagine generata dall'utente

Questo può essere davvero potente poiché ora siamo in grado di ottenere effetti di trattamento a livello del cliente!

Che cos'è?

La programmazione lineare è un metodo di ottimizzazione che può essere utilizzato per trovare la soluzione ottima di una funzione lineare dati alcuni vincoli. Viene spesso utilizzato per risolvere problemi di trasporto, programmazione e allocazione delle risorse. Un termine più generico che potresti vedere utilizzato è ricerca operativa.

Analizziamo la programmazione lineare con un semplice esempio:

  • Variabili decisionali: Queste sono le quantità incognite per le quali vogliamo stimare i valori ottimali: la spesa di marketing su social media, TV e ricerca a pagamento.
  • Funzione obiettivi: L'equazione lineare che stiamo cercando di minimizzare o massimizzare: il ritorno sull'investimento (ROI) del marketing.
  • Vincoli: Alcune restrizioni sulle variabili decisionali, solitamente rappresentate da disuguaglianze lineari: spesa di marketing totale compresa tra £ 100.000 e £ 500.000.

L'intersezione di tutti i vincoli forma una regione ammissibile, che è l'insieme di tutte le possibili soluzioni che soddisfano i vincoli dati. L'obiettivo della programmazione lineare è trovare il punto all'interno della regione ammissibile che ottimizza la funzione obiettivo.

Problemi di assegnazione

I problemi di assegnazione sono un tipo specifico di problema di programmazione lineare in cui l'obiettivo è assegnare un insieme di “compiti” a un insieme di “agenti”. Usiamo un esempio per dargli vita:

Esegui un esperimento in cui invii sconti diversi a 4 gruppi casuali di clienti esistenti (del quarto dei quali in realtà non invii alcuno sconto). Costruisci 2 modelli CATE: (1) Stima di come il valore dell'offerta influisce sul valore dell'ordine e (2) Stima di come il valore dell'offerta influisce sul costo.

  • Agenti: la tua base clienti esistente
  • Compiti: se invii loro uno sconto del 10%, 20% o 50%.
  • Variabili decisionali: Variabile decisionale binaria
  • Funzione obiettivo: il valore totale dell'ordine meno i costi
  • Vincolo 1: a ciascun agente viene assegnato al massimo 1 attività
  • Vincolo 2: Il costo ≥ £ 10.000
  • Vincolo 3: Il costo ≤ £ 100.000
Immagine generata dall'utente

Fondamentalmente vogliamo scoprire il trattamento ottimale per ciascun cliente dati alcuni vincoli di costo complessivi. E la programmazione lineare può aiutarci a farlo!

Vale la pena notare che questo problema è “NP difficile”, una classificazione di problemi che sono difficili almeno quanto i problemi più difficili in NP (tempo polinomiale non deterministico).

La programmazione lineare è un argomento davvero complicato ma gratificante. Ho provato a introdurre l'idea per iniziare: se vuoi saperne di più, ti consiglio questa risorsa:

O Strumenti

Gli strumenti OR sono un pacchetto open source sviluppato da Google in grado di risolvere una serie di problemi di programmazione lineare, inclusi problemi di assegnazione. Lo dimostreremo in azione più avanti nell'articolo.

Sfondo

Continueremo con l'esempio del problema di assegnazione e illustreremo come possiamo risolverlo in Python.

Processo di generazione dei dati

Impostiamo un processo di generazione dei dati con le seguenti caratteristiche:

  • Parametri di disturbo difficili (b)
  • Eterogeneità dell’effetto del trattamento (tau)

Le caratteristiche X sono caratteristiche del cliente rilevate prima del trattamento:

Immagine generata dall'utente

T è un flag binario che indica se il cliente ha ricevuto l'offerta. Creiamo tre diverse interazioni di trattamento per permetterci di simulare diversi effetti del trattamento.

Immagine generata dall'utente
def data_generator(tau_weight, interaction_num):

# Set number of observations
n=10000

# Set number of features
p=10

# Create features
X = np.random.uniform(size=n * p).reshape((n, -1))

# Nuisance parameters
b = (
np.sin(np.pi * X(:, 0) * X(:, 1))
+ 2 * (X(:, 2) - 0.5) ** 2
+ X(:, 3)
+ 0.5 * X(:, 4)
+ X(:, 5) * X(:, 6)
+ X(:, 7) ** 3
+ np.sin(np.pi * X(:, 8) * X(:, 9))
)

# Create binary treatment
T = np.random.binomial(1, expit(b))

# treatment interactions
interaction_1 = X(:, 0) * X(:, 1) + X(:, 2)
interaction_2 = X(:, 3) * X(:, 4) + X(:, 5)
interaction_3 = X(:, 6) * X(:, 7) + X(:, 9)

# Set treatment effect
if interaction_num==1:
tau = tau_weight * interaction_1
elif interaction_num==2:
tau = tau_weight * interaction_2
elif interaction_num==3:
tau = tau_weight * interaction_3

# Calculate outcome
y = b + T * tau + np.random.normal(size=n)

return X, T, tau, y

Possiamo utilizzare il generatore di dati per simulare tre trattamenti, ciascuno con un effetto terapeutico diverso.

Immagine generata dall'utente
np.random.seed(123)

# Generate samples for 3 different treatments
X1, T1, tau1, y1 = data_generator(0.75, 1)
X2, T2, tau2, y2 = data_generator(0.50, 2)
X3, T3, tau3, y3 = data_generator(0.90, 3)

Come nell'ultimo articolo, il codice Python del processo di generazione dei dati si basa sul creatore di dati sintetici del pacchetto Ubers Causal ML:

Stima del CATE con DML

Quindi addestriamo tre modelli DML utilizzando LightGBM come modelli flessibili di prima fase. Ciò dovrebbe consentirci di catturare i difficili parametri di disturbo calcolando correttamente l’effetto del trattamento.

Presta attenzione a come passiamo le funzionalità X tramite X anziché W (a differenza dell'ultimo articolo in cui abbiamo passato le funzionalità X tramite W). Le funzionalità passate attraverso X verranno utilizzate sia nel modello di prima che in quella di seconda fase — Nel modello di seconda fase le funzionalità vengono utilizzate per creare termini di interazione con il residuo del trattamento.

np.random.seed(123)

# Train DML model using flexible stage 1 models
dml1 = LinearDML(model_y=LGBMRegressor(), model_t=LGBMClassifier(), discrete_treatment=True)
dml1.fit(y1, T=T1, X=X1, W=None)

# Train DML model using flexible stage 1 models
dml2 = LinearDML(model_y=LGBMRegressor(), model_t=LGBMClassifier(), discrete_treatment=True)
dml2.fit(y2, T=T2, X=X2, W=None)

# Train DML model using flexible stage 1 models
dml3 = LinearDML(model_y=LGBMRegressor(), model_t=LGBMClassifier(), discrete_treatment=True)
dml3.fit(y3, T=T3, X=X3, W=None)

Quando tracciamo il CATE effettivo rispetto a quello stimato, vediamo che il modello svolge un lavoro ragionevole.

# Create a figure and subplots
fig, axes = plt.subplots(1, 3, figsize=(15, 5))

# Plot scatter plots on each subplot
sns.scatterplot(x=dml1.effect(X1), y=tau1, ax=axes(0))
axes(0).set_title('Treatment 1')
axes(0).set_xlabel('Estimated CATE')
axes(0).set_ylabel('Actual CATE')

sns.scatterplot(x=dml2.effect(X2), y=tau2, ax=axes(1))
axes(1).set_title('Treatment 2')
axes(1).set_xlabel('Estimated CATE')
axes(1).set_ylabel('Actual CATE')

sns.scatterplot(x=dml3.effect(X3), y=tau3, ax=axes(2))
axes(2).set_title('Treatment 3')
axes(2).set_xlabel('Estimated CATE')
axes(2).set_ylabel('Actual CATE')

# Add labels to the entire figure
fig.suptitle('Actual vs Estimated')

# Show plots
plt.show()

Immagine generata dall'utente

Ottimizzazione ingenua

Inizieremo esplorando questo problema come problema di ottimizzazione. Abbiamo tre trattamenti che un cliente può ricevere. Di seguito creiamo una mappatura per il costo di ciascun trattamento e impostiamo un vincolo di costo complessivo.

# Create mapping for cost of each treatment
cost_dict = {'T1': 0.1, 'T2': 0.2, 'T3': 0.3}

# Set constraints
max_cost = 3000

Possiamo quindi stimare il CATE per ciascun cliente e quindi selezionare inizialmente il miglior trattamento per ciascun cliente. Tuttavia, la scelta del trattamento migliore non ci mantiene entro il vincolo di costo massimo. Seleziona quindi i clienti con il CATE più alto fino a raggiungere il nostro vincolo di costo massimo.

# Concatenate features
X = np.concatenate((X1, X2, X3), axis=0)

# Estimate CATE for each treatment using DML models
Treatment_1 = dml1.effect(X)
Treatment_2 = dml2.effect(X)
Treatment_3 = dml3.effect(X)
cate = pd.DataFrame({"T1": Treatment_1, "T2": Treatment_2, "T3": Treatment_3})

# Select the best treatment for each customer
best_treatment = cate.idxmax(axis=1)
best_value = cate.max(axis=1)

# Map cost for each treatment
best_cost = pd.Series((cost_dict(value) for value in best_treatment))

# Create dataframe with each customers best treatment and associated cost
best_df = pd.concat((best_value, best_cost), axis=1)
best_df.columns = ("value", "cost")
best_df = best_df.sort_values(by=('value'), ascending=False).reset_index(drop=True)

# Naive optimisation
best_df_cum = best_df.cumsum()
opt_index = best_df_cum('cost').searchsorted(max_cost)
naive_order_value = round(best_df_cum.iloc(opt_index)('value'), 0)
naive_cost_check = round(best_df_cum.iloc(opt_index)('cost'), 0)

print(f'The total order value from the naive treatment strategy is {naive_order_value} with a cost of {naive_cost_check}')

Immagine generata dall'utente

Ottimizzazione delle strategie di trattamento con la Programmazione Lineare

Iniziamo creando un dataframe con il costo di ciascun trattamento per ciascun cliente.

# Cost mapping for all treatments
cost_mapping = {'T1': (cost_dict("T1")) * 30000,
'T2': (cost_dict("T2")) * 30000,
'T3': (cost_dict("T3")) * 30000}

# Create DataFrame
df_costs = pd.DataFrame(cost_mapping)

Ora è il momento di utilizzare il pacchetto OR Tools per risolvere questo problema di assegnazione! Il codice accetta i seguenti input:

  • Vincoli di costo
  • Matrice contenente il costo di ciascun trattamento per ciascun cliente
  • Matrice contenente il valore stimato dell'ordine per ciascun trattamento per ciascun cliente

Il codice genera un dataframe con il potenziale trattamento di ciascun cliente e una colonna che indica quale è l'assegnazione ottimale.

solver = pywraplp.Solver.CreateSolver('SCIP')

# Set constraints
max_cost = 3000
min_cost = 3000

# Create input arrays
costs = df_costs.to_numpy()
order_value = cate.to_numpy()

num_custs = len(costs)
num_treatments = len(costs(0))

# x(i, j) is an array of 0-1 variables, which will be 1 if customer i is assigned to treatment j.
x = {}
for i in range(num_custs):
for j in range(num_treatments):
x(i, j) = solver.IntVar(0, 1, '')

# Each customer is assigned to at most 1 treatment.
for i in range(num_custs):
solver.Add(solver.Sum((x(i, j) for j in range(num_treatments))) <= 1)

# Cost constraints
solver.Add(sum((costs(i)(j) * x(i, j) for j in range(num_treatments) for i in range(num_custs))) <= max_cost)
solver.Add(sum((costs(i)(j) * x(i, j) for j in range(num_treatments) for i in range(num_custs))) >= min_cost)

# Objective
objective_terms = ()
for i in range(num_custs):
for j in range(num_treatments):
objective_terms.append((order_value(i)(j) * x(i, j) - costs(i)(j) * x(i, j) ))
solver.Maximize(solver.Sum(objective_terms))

# Solve
status = solver.Solve()

assignments = ()
values = ()

if status == pywraplp.Solver.OPTIMAL or status == pywraplp.Solver.FEASIBLE:
for i in range(num_custs):
for j in range(num_treatments):
# Test if x(i,j) is 1 (with tolerance for floating point arithmetic).
if x(i, j).solution_value() > -0.5:
assignments.append((i, j))
values.append((x(i, j).solution_value(), costs(i)(j) * x(i, j).solution_value(), order_value(i)(j)))

# Create a DataFrame from the collected data
df = pd.DataFrame(assignments, columns=('customer', 'treatment'))
df('assigned') = (x(0) for x in values)
df('cost') = (x(1) for x in values)
df('order_value') = (x(2) for x in values)

df

Immagine generata dall'utente

Pur rispettando il vincolo di costo di £ 3.000, possiamo generare £ 18.000 in valore dell'ordine utilizzando la strategia di trattamento ottimizzata. Questo è superiore del 36% rispetto all’approccio ingenuo!

opt_order_value = round(df('order_value')(df('assigned') == 1).sum(), 0)
opt_cost_check = round(df('cost')(df('assigned') == 1).sum(), 0)

print(f'The total order value from the optimised treatment strategy is {opt_order_value} with a cost of {opt_cost_check}')

Immagine generata dall'utente

Oggi abbiamo parlato dell'utilizzo del Double Machine Learning e della Programmazione Lineare per ottimizzare le strategie di trattamento. Ecco alcuni pensieri conclusivi:

  • Abbiamo trattato il DML lineare, potresti voler esplorare approcci alternativi più adatti a gestire effetti di interazione complessi nel modello della seconda fase:
  • Ma ricorda anche che non è necessario utilizzare DML, potrebbero essere utilizzati altri metodi come T-Learner o DR-Learner.
  • Per mantenere questo articolo in una lettura veloce non ho ottimizzato gli iperparametri: poiché aumentiamo la complessità del problema e dell'approccio utilizzato, dobbiamo prestare maggiore attenzione a questa parte.
  • I problemi di programmazione/assegnazione lineare sono NP difficili, quindi se hai una vasta base di clienti e/o diversi trattamenti questa parte del codice potrebbe richiedere molto tempo per essere eseguita.
  • Può essere difficile rendere operativa una pipeline giornaliera con problemi di programmazione/assegnazione lineare: un'alternativa è eseguire periodicamente l'ottimizzazione e apprendere la politica ottimale in base ai risultati al fine di creare una segmentazione da utilizzare in una pipeline giornaliera.

Fonte: towardsdatascience.com

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *