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 Studying. Questa volta approfondiremo ulteriormente il potenziale della copertura DML utilizzando il Double Machine Studying e la Programmazione Lineare per ottimizzare le strategie di trattamento.
Se ti sei perso l'ultimo articolo sul Double Machine Studying, dai un'occhiata qui:
Questo articolo mostrerà come utilizzare il Double Machine Studying 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 funds.
- Un caso di studio elaborato in Python che illustra come possiamo utilizzare il Double Machine Studying 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 tremendous di massimizzare le vendite future minimizzando i costi?”.
Analizziamo questa concept con un semplice esempio.
La tua attività vende calzini on-line. 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 period 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 advertising la stima del CATE viene spesso definita Uplift Modeling.
Stima del CATE con Double Machine Studying
Abbiamo trattato DML nell'ultimo articolo, ma nel caso avessi bisogno di un ripasso:
“Primo stadio:
- Modello di trattamento (de-biasing): Modello di machine studying 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 Studying per stimare la CATE interagendo con le nostre funzionalità di controllo (X) con l'effetto del trattamento nel modello della seconda fase.
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 advertising 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 advertising.
- Vincoli: Alcune restrizioni sulle variabili decisionali, solitamente rappresentate da disuguaglianze lineari: spesa di advertising 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
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'concept per iniziare: se vuoi saperne di più, ti consiglio questa risorsa:
O Strumenti
Gli strumenti OR sono un pacchetto open supply 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:
T è un flag binario che indica se il cliente ha ricevuto l'offerta. Creiamo tre numerous interazioni di trattamento per permetterci di simulare diversi effetti del trattamento.
def data_generator(tau_weight, interaction_num):# Set variety of observations
n=10000
# Set variety of options
p=10
# Create options
X = np.random.uniform(measurement=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 remedy
T = np.random.binomial(1, expit(b))
# remedy 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 remedy impact
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 final result
y = b + T * tau + np.random.regular(measurement=n)
return X, T, tau, y
Possiamo utilizzare il generatore di dati per simulare tre trattamenti, ciascuno con un effetto terapeutico diverso.
np.random.seed(123)# Generate samples for 3 totally different therapies
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)# Prepare DML mannequin utilizing versatile stage 1 fashions
dml1 = LinearDML(model_y=LGBMRegressor(), model_t=LGBMClassifier(), discrete_treatment=True)
dml1.match(y1, T=T1, X=X1, W=None)
# Prepare DML mannequin utilizing versatile stage 1 fashions
dml2 = LinearDML(model_y=LGBMRegressor(), model_t=LGBMClassifier(), discrete_treatment=True)
dml2.match(y2, T=T2, X=X2, W=None)
# Prepare DML mannequin utilizing versatile stage 1 fashions
dml3 = LinearDML(model_y=LGBMRegressor(), model_t=LGBMClassifier(), discrete_treatment=True)
dml3.match(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 determine and subplots
fig, axes = plt.subplots(1, 3, figsize=(15, 5))# Plot scatter plots on every subplot
sns.scatterplot(x=dml1.impact(X1), y=tau1, ax=axes(0))
axes(0).set_title('Remedy 1')
axes(0).set_xlabel('Estimated CATE')
axes(0).set_ylabel('Precise CATE')
sns.scatterplot(x=dml2.impact(X2), y=tau2, ax=axes(1))
axes(1).set_title('Remedy 2')
axes(1).set_xlabel('Estimated CATE')
axes(1).set_ylabel('Precise CATE')
sns.scatterplot(x=dml3.impact(X3), y=tau3, ax=axes(2))
axes(2).set_title('Remedy 3')
axes(2).set_xlabel('Estimated CATE')
axes(2).set_ylabel('Precise CATE')
# Add labels to all the determine
fig.suptitle('Precise vs Estimated')
# Present plots
plt.present()
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 value of every remedy
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 options
X = np.concatenate((X1, X2, X3), axis=0)# Estimate CATE for every remedy utilizing DML fashions
Treatment_1 = dml1.impact(X)
Treatment_2 = dml2.impact(X)
Treatment_3 = dml3.impact(X)
cate = pd.DataFrame({"T1": Treatment_1, "T2": Treatment_2, "T3": Treatment_3})
# Choose the very best remedy for every buyer
best_treatment = cate.idxmax(axis=1)
best_value = cate.max(axis=1)
# Map value for every remedy
best_cost = pd.Sequence((cost_dict(worth) for worth in best_treatment))
# Create dataframe with every prospects greatest remedy and related value
best_df = pd.concat((best_value, best_cost), axis=1)
best_df.columns = ("worth", "value")
best_df = best_df.sort_values(by=('worth'), ascending=False).reset_index(drop=True)
# Naive optimisation
best_df_cum = best_df.cumsum()
opt_index = best_df_cum('value').searchsorted(max_cost)
naive_order_value = spherical(best_df_cum.iloc(opt_index)('worth'), 0)
naive_cost_check = spherical(best_df_cum.iloc(opt_index)('value'), 0)
print(f'The entire order worth from the naive remedy technique is {naive_order_value} with a price of {naive_cost_check}')
Ottimizzazione delle strategie di trattamento con la Programmazione Lineare
Iniziamo creando un dataframe con il costo di ciascun trattamento per ciascun cliente.
# Price mapping for all therapies
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 Instruments per risolvere questo problema di assegnazione! Il codice accetta i seguenti enter:
- 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 enter arrays
prices = df_costs.to_numpy()
order_value = cate.to_numpy()
num_custs = len(prices)
num_treatments = len(prices(0))
# x(i, j) is an array of 0-1 variables, which might be 1 if buyer i is assigned to remedy j.
x = {}
for i in vary(num_custs):
for j in vary(num_treatments):
x(i, j) = solver.IntVar(0, 1, '')
# Every buyer is assigned to at most 1 remedy.
for i in vary(num_custs):
solver.Add(solver.Sum((x(i, j) for j in vary(num_treatments))) <= 1)
# Price constraints
solver.Add(sum((prices(i)(j) * x(i, j) for j in vary(num_treatments) for i in vary(num_custs))) <= max_cost)
solver.Add(sum((prices(i)(j) * x(i, j) for j in vary(num_treatments) for i in vary(num_custs))) >= min_cost)
# Goal
objective_terms = ()
for i in vary(num_custs):
for j in vary(num_treatments):
objective_terms.append((order_value(i)(j) * x(i, j) - prices(i)(j) * x(i, j) ))
solver.Maximize(solver.Sum(objective_terms))
# Clear up
standing = solver.Clear up()
assignments = ()
values = ()
if standing == pywraplp.Solver.OPTIMAL or standing == pywraplp.Solver.FEASIBLE:
for i in vary(num_custs):
for j in vary(num_treatments):
# Take a look at if x(i,j) is 1 (with tolerance for floating level arithmetic).
if x(i, j).solution_value() > -0.5:
assignments.append((i, j))
values.append((x(i, j).solution_value(), prices(i)(j) * x(i, j).solution_value(), order_value(i)(j)))
# Create a DataFrame from the collected information
df = pd.DataFrame(assignments, columns=('buyer', 'remedy'))
df('assigned') = (x(0) for x in values)
df('value') = (x(1) for x in values)
df('order_value') = (x(2) for x in values)
df
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 = spherical(df('order_value')(df('assigned') == 1).sum(), 0)
opt_cost_check = spherical(df('value')(df('assigned') == 1).sum(), 0)print(f'The entire order worth from the optimised remedy technique is {opt_order_value} with a price of {opt_cost_check}')
Oggi abbiamo parlato dell'utilizzo del Double Machine Studying 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 tremendous di creare una segmentazione da utilizzare in una pipeline giornaliera.