Attingere da una distribuzione casuale in SQL |  di Sami Abboud |  Febbraio 2024

 | Intelligenza-Artificiale

Dalla funzione di densità di probabilità ai campioni casuali

fotografato da Moritz Kindler SU Unsplash

TQui ci sono diversi metodi per aggiornare la politica di un agente di apprendimento per rinforzo ad ogni iterazione. Alcune settimane fa abbiamo iniziato a sperimentare la sostituzione del nostro metodo attuale con un passaggio di inferenza bayesiana. Alcuni carichi di lavoro dei dati all’interno del nostro agente sono scritti in SQL eseguito sul motore BigQuery di GCP. Utilizziamo questo stack perché fornisce funzionalità computazionali scalabili, pacchetti ML e un’interfaccia SQL semplice.

Il passaggio dell’inferenza bayesiana che abbiamo cercato di implementare utilizza uno dei file parametrizzazioni alternative di una distribuzione beta. Ciò significa che dovremo poter attingere da una distribuzione beta in SQL. Mentre lavoravo su questo, ho scoperto che attingere dalla distribuzione casuale in SQL è un argomento con pochissimi esempi ben documentati. Quindi lo scrivo qui..

BigQuery non ha una distribuzione beta. Non ha la capacità di attingere Qualunque distribuzione casuale. Quindi la mia prima intuizione è stata quella di prendere la definizione della distribuzione beta, scriverla in SQL, impostare i parametri utilizzando una CTA, disegnare un numero casuale compreso tra 0 e 1 e calcolare il valore di quella funzione.

Ma è il 2024, quindi ho chiesto a ChatGPT come avrebbe fatto:

Me: “​Come si creano estrazioni casuali da una distribuzione beta in BigQuery?

ChatGPT:

WITH beta_draws AS (
SELECT
(alpha — 1) / (alpha + beta — 2) AS a,
(beta — 1) / (alpha + beta — 2) AS b
FROM
(SELECT 2 AS alpha, 5 AS beta) — Set your desired alpha and beta values
)
SELECT
(a * POW(RAND(), alpha - 1) * POW(1 - RAND(), beta - 1) + b) AS beta_draw
FROM
beta_draws,
UNNEST(GENERATE_ARRAY(1, 1000)) AS rand_num

Me pensando a me stesso: Giusto, quindi chiaramente non funzionerà.

Vedi il problema nel codice? ChatGPT ne disegna due diverso x valori per la presunta distribuzione beta PDF. Ho risolto questo problema, ripulito un po’ la query e campionato 1.000 valori. Ed ecco il codice SQL per farlo:

WITH raw_data AS (
SELECT
‘user_a’ AS contact_id
, 0.674 AS probability
, 72 AS lifetime_messages_received
), parameters AS (
SELECT
contact_id
, probability*lifetime_messages_received AS alpha
, (1.0 — probability)*lifetime_messages_received AS beta
, RAND() AS x
FROM raw_data
CROSS JOIN UNNEST(GENERATE_ARRAY(1, 1000)) AS draw_id
)
SELECT
contact_id
, ARRAY_AGG(POW(x, alpha — 1.0) * POW(1.0 — x, beta — 1)) AS beta_x
FROM parameters
GROUP BY contact_id

Grazie a tutti, questo è tutto 🎁 Ci vediamo al prossimo post!

SBAGLIATO! 🔴

Prendiamo un’implementazione attendibile del disegno da una distribuzione beta utilizzando gli stessi parametri e confrontiamo i risultati. Ho usato SciPy’s beta.rvs() in Python e qui ci sono due istogrammi da 100 bin che permetteranno di confrontare le due distribuzioni disegnate.

from scipy.stats import beta

alpha_param = 0.674 * 72
beta_param = (1–0.674) * 72

scipy_beta_draw = beta.rvs(alpha_param, beta_param, size=1000)

A sinistra: disegni Naive utilizzando BigQuery.  A destra: disegna utilizzando beta.rvs() di SciPy
(Sinistra): disegni ingenui utilizzando BigQuery. (Giusto): Disegna utilizzando beta.rvs() di SciPy()

Ebbene, non ci vuole una lente d’ingrandimento per rendersi conto che le distribuzioni sono diverse. Sono tornato alla definizione della distribuzione beta e ho capito che potrebbe essere perché la distribuzione beta ha anche una costante di scala che dipende dal funzione gamma che non ho inserito nel calcolo 🤦.

Problema: la funzione gamma non ha a espressione in forma chiusae BigQuery non fornisce un’implementazione che lo approssimi. Quindi a questo punto ho deciso di passare a Python, un linguaggio con cui ho più familiarità e che renderà la mia sperimentazione più efficiente. L’idea era che se lo inchiodassi in Python, sarei stato in grado di tradurlo in SQL. Avrei ancora bisogno di un modo per approssimare una funzione gamma, ma un passo alla volta.

Implementiamo un disegno manuale da una distribuzione beta in Python, ma ora con la costante corretta utilizzando la funzione gamma di SciPy:

import numpy as np
from scipy.special import gamma
from scipy.stats import uniform

alpha_param = 0.674 * 72
beta_param = (1–0.674) * 72

constant = gamma(alpha_param + beta_param) / (gamma(alpha_param) * gamma(beta_param))
scipy_manual_beta_draw = np.array((
constant*pow(x, alpha_param-1)*pow(1-x, beta_param-1)
for x in uniform.rvs(size=1000)
))

Esaminiamo nuovamente la distribuzione utilizzando un istogramma a 100 bin:

Disegna ingenuo usando Python

La prima cosa che notiamo è che la scala ora è diversa, ma la distribuzione assomiglia ancora a quella disegnata in BigQuery.

… qualcosa è sbagliato… è il momento di fare una breve passeggiata per pensare 🚶

Dopo una breve passeggiata:

Cosa significa in realtà attingere da una distribuzione casuale? Ciò che ho implementato finora è il campionamento casuale dalla funzione di densità di probabilità beta (PDF) e non funzionava.

Quindi ho dovuto scovare alcune lezioni di statistica.

Ecco un paio di ottimi aggiornamenti su:

In breve, la conclusione è che trarre da una variabile casuale in realtà significa campionamento dalla funzione di distribuzione cumulativa inversa (CDF)non dalla funzione di densità di probabilità (PDF) come ho fatto finora.

Naturalmente 🤦. Mio professore di probabilitàche ho appena saputo che era morto di malattia nel 2020, mi avrebbe incoraggiato a “ripassare le basi” a questo punto..

OK. Rivisitiamo il codice Python, ora disegnando esempi dal CDF inverso (chiamato anche la funzione quantile) della nostra distribuzione beta e confrontala con la distribuzione disegnata utilizzando beta.rvs() di SciPy:

import numpy as np
from scipy.special import gamma
from scipy.stats import uniform, beta

alpha_param = 0.674 * 72
beta_param = (1–0.674) * 72
n_draws = 1000

# Use SciPy RVS for comparison
scipy_beta_draw = beta.rvs(alpha_param, beta_param, size=n_draws)

# Manual beta draw with the help of the SciPy Gamma function

# We start with a discrete analogue of the Beta PDF we wish to draw from.
# This is just sampling from the PDF at fixed intervals but do check out
# this review for a more in-depth treatment of the subject:
# https://jsdajournal.springeropen.com/articles/10.1186/s40488-015-0028-6

# Set the resolution for generating the discrete PDF
n_samples = 1000

# The beta distribution is supported on the range (0, 1), so we set the
# pdf min and max parameters accordingly
pdf_min = 0.0
pdf_max = 1.0

x_span = np.linspace(pdf_min, pdf_max, n_samples)
constant = gamma(alpha_param + beta_param) / (gamma(alpha_param) * gamma(beta_param))
beta_pdf = np.array((
constant * pow(x, alpha_param — 1) * pow(1 — x, beta_param — 1)
for x in x_span
))

# Using the discrete Beta PDF, we now compute a discrete Beta CDF.
# To do that, we integrate the PDF. For each point x, we sum the PDF until
# that point and multiple with the width of each sample.
freq = 1.0 / n_samples
beta_cdf = beta_pdf.cumsum() * freq

def inv(cdf, q):
“””Return inverse CDF for value q using the quantile function”””
return x_span(np.argmin(cdf < q))

# Finally, we can now draw n_draws from the discrete inverse of CDF, aka
# generate random samples from a beta distribution
manual_beta_draw = np.array((
inv(beta_cdf, x)
for x in uniform.rvs(size=n_draws)
))

*uff* questo sembra molto meglio:

Una sovrapposizione di due istogrammi che confrontano 1.000 estrazioni utilizzando beta.rvs() di SciPy e un’estrazione manuale

Ora che abbiamo disegnato direttamente gli esempi da una variabile casuale, è tempo di tornare a SQL. Per motivi di semplicità e poiché BigQuery non viene fornito immediatamente con un’implementazione di una funzione Gamma¹ trarrò spunto dal distribuzione logistica (con parametri a=0 e b=1).

 — The following 3 parameters need to be adjusted based on the support of the
— PDF of the distribution you wish to draw from. This values are set for a logistic
— distribution with a=0 and b=1

DECLARE pdf_min INT64 DEFAULT -10;
DECLARE pdf_max INT64 DEFAULT 10;
DECLARE n_samples INT64 DEFAULT 5000;
DECLARE sampling_step FLOAT64 DEFAULT (pdf_max — pdf_min) / n_samples;

— The number of random draws you wish to perform
DECLARE n_draws INT64 DEFAULT 1000;

WITH pdf AS (

— The discrete sampling of the logistic distribution PDF

SELECT
x
, exp(-x) / pow(1 + exp(-x), 2) AS y — a=0, b=1
FROM UNNEST(GENERATE_ARRAY(pdf_min, pdf_max, sampling_step)) AS x
), cdf AS (

— The discrete CDF

SELECT
x
, SUM(y)
OVER (
ORDER BY x
) * (1.0 / n_samples) AS y
FROM pdf
), random_draws AS (

— Random draws in the range of (0, max(cdf))

SELECT
RAND() * (SELECT MAX(y) FROM cdf) as q
, draw_id
FROM UNNEST(GENERATE_ARRAY(1, n_draws)) AS draw_id
)

— Calculate the inverse CDF per draw using the quantile function by generating
— and array of the discrete support of the distribution and returning the value
— of the index just before the randomly generated number is larger than the CDF

SELECT
ARRAY_AGG(x ORDER BY x)(OFFSET(SUM(CAST(y < q AS INT64)))) AS x
FROM random_draws
JOIN cdf
ON TRUE
GROUP BY draw_id;

Confrontiamo ora le distribuzioni dei tre metodi di campionamento:

  1. SciPy’s logistic.rvs()
  2. Campionando manualmente il PDF della distribuzione logistica in Python e disegnando un campione casuale come indicato nel passaggio 2 sopra
  3. Fare lo stesso in SQL
Una sovrapposizione di tre istogrammi che confrontano 1.000 estrazioni utilizzando beta.rvs() di SciPy, un’estrazione manuale in Python e un’estrazione manuale in SQL

Mi sembra un successo! 💪

Il codice SQL riportato sopra campiona la distribuzione logistica, ma dovrebbe funzionare su qualsiasi distribuzione in cui sia possibile ottenere una rappresentazione discreta del PDF campionandolo a intervalli coerenti!

Fonte: towardsdatascience.com

Lascia un commento

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