Prueba AB usando Pyro
Considere una empresa que ha diseñado una nueva página de inicio de sitio web y quiere comprender el impacto que esto tendrá en la conversión, es decir, ¿los visitantes continúan su sesión web en el sitio web después de llegar a la página? En el grupo de prueba A, a los visitantes del sitio web se les mostrará la página de destino actual. En el grupo de prueba B, a los visitantes del sitio web se les mostrará la nueva página de destino. En el resto del artículo, me referiré al grupo de prueba A como grupo de control y al grupo B como grupo de tratamiento. La empresa se muestra escéptica ante el cambio y ha optado por una división 80/20 en el tráfico de sesiones. A continuación se resumen el número total de visitantes y el número total de conversiones de páginas para cada grupo de prueba.
La hipótesis nula de la prueba AB es que no habrá cambios en la conversión de páginas para los dos grupos de prueba. Según el marco frecuentista, esto se expresaría de la siguiente manera para una prueba bilateral, donde r_c y r_t son las tasas de conversión de páginas en los grupos de control y tratamiento, respectivamente.
Una prueba de significancia buscaría entonces rechazar o no rechazar la hipótesis nula. Bajo el marco bayesiano, expresamos la hipótesis nula de manera ligeramente diferente al afirmar lo mismo previo para cada uno de los grupos de prueba.
Hagamos una pausa y describamos exactamente lo que sucede durante nuestra prueba. La variable que nos interesa es la tasa de conversión de páginas. Esto se calcula simplemente tomando el número de visitantes convertidos distintos sobre el número total de visitantes. El evento que genera esta tasa es si el visitante hace clic en la página. Aquí solo hay dos resultados posibles para cada visitante: el visitante hace clic en la página y realiza la conversión, o no. Algunos de ustedes podrían reconocer que para cada visitante distinto, este es un ejemplo de un ensayo de Bernoulli; hay un ensayo y dos resultados posibles. Ahora, cuando recopilamos un conjunto de estos ensayos de Bernoulli, tenemos una distribución binomial. Cuando la variable aleatoria X tiene distribución binomial, le damos la siguiente notación:
Donde n es el número de visitantes (o el número de ensayos de Bernoulli) y p es la probabilidad del evento en cada ensayo. p es lo que nos interesa aquí, queremos entender cuál es la probabilidad de que un visitante realice una conversión en la página en cada grupo de prueba. Hemos observado algunos datos, pero como se mencionó en la sección anterior, primero debemos definir nuestro anterior. Como siempre en la estadística bayesiana, debemos definir esto a priori como una distribución de probabilidad. Como se mencionó anteriormente, esta distribución de probabilidad es una caracterización de nuestra incertidumbre. Las distribuciones beta se utilizan comúnmente para modelar probabilidades, ya que se definen entre los intervalos de [0,1]. Además, usar una distribución beta como nuestra anterior para una función de probabilidad binomial nos brinda la útil propiedad de la conjugación, lo que significa que nuestra posterior se generará a partir de la misma distribución que nuestra anterior. Decimos que la distribución beta es una conjugado previo. Una distribución beta está definida por dos parámetros, alfa y, de manera confusa, beta.
Con acceso a datos históricos, podemos afirmar un previo informado. No necesariamente necesitamos datos históricos, podríamos usar nuestra intuición para informar nuestra comprensión, pero por ahora supongamos que no tenemos ninguno de los dos (más adelante en este tutorial usaremos antecedentes informados, pero para demostrar el impacto, comenzaré con los desinformados). . Supongamos que no entendemos la tasa de conversión en el sitio de la empresa y, por lo tanto, definimos nuestro anterior como Beta (1,1). A esto se le llama prior plano. La distribución de probabilidad de esta función se parece al gráfico siguiente, igual que una distribución uniforme definida entre los intervalos. [0,1]. Al afirmar un Beta(1,1) anterior, decimos que todos los valores posibles de la tasa de conversión de la página son igualmente probables.
Ahora tenemos toda la información que necesitamos, los antecedentes y los datos. Saltemos al código. El código proporcionado aquí proporcionará un marco para comenzar con las pruebas AB utilizando Pyro; por lo tanto, descuida algunas características del paquete. Para ayudar a optimizar aún más su código y aprovechar al máximo las capacidades de Pyro, recomiendo consultar la documentación oficial.
Primero, necesitamos importar nuestros paquetes. La última línea es una buena práctica, especialmente cuando se trabaja en portátiles, limpiando el almacén de parámetros que hemos acumulado.
import pyro
import pyro.distributions as dist
from pyro.infer import NUTS, MCMC
import torch
from torch import tensor
import matplotlib.pyplot as plt
import seaborn as sns
from functools import partial
import pandas as pdpyro.clear_param_store()
Los modelos en Pyro se definen como funciones regulares de Python. Esto es útil porque hace que su seguimiento sea intuitivo.
def model(beta_alpha, beta_beta):
def _model_(traffic: tensor, number_of_conversions: tensor):
# Define Stochastic Primatives
prior_c = pyro.sample('prior_c', dist.Beta(beta_alpha, beta_beta))
prior_t = pyro.sample('prior_t', dist.Beta(beta_alpha, beta_beta))
priors = torch.stack([prior_c, prior_t])
# Define the Observed Stochastic Primatives
with pyro.plate('data'):
observations = pyro.sample('obs', dist.Binomial(traffic, priors),\
obs = number_of_conversions)
return partial(_model_)
Algunas cosas para desglosar y explicar aquí. Primero, tenemos una función envuelta dentro de una función externa, la función externa devuelve la función parcial de la función interna. Esto nos permite cambiar nuestros anteriores, sin necesidad de cambiar el código. Me he referido a las variables definidas en la función interna como primitivas, piense en las primitivas como variables en el modelo. Tenemos dos tipos de primitivas en el modelo, estocásticas y estocásticas observadas. En Pyro, no tenemos que definir explícitamente la diferencia, simplemente agregamos el argumento obs al método de muestra cuando es una primitiva observada y Pyro lo interpreta en consecuencia. Las primitivas observadas están contenidas en el administrador de contexto pyro.plate(), que es una práctica recomendada y hace que nuestro código se vea más limpio. Nuestras primitivas estocásticas son nuestras dos anteriores, caracterizadas por distribuciones Beta, regidas por los parámetros alfa y beta que pasamos desde la función externa. Como se mencionó anteriormente, afirmamos la hipótesis nula al definirlos como iguales. Luego apilamos estas dos primitivas usando tensor.stack(), que realiza una operación similar a concatenar una matriz Numpy. Esto devolverá un tensor, la estructura de datos necesaria para la inferencia en Pyro. Hemos definido nuestro modelo, ahora pasemos a la etapa de inferencia.
Como se mencionó anteriormente, este tutorial utilizará MCMC. La siguiente función tomará como parámetro el modelo que hemos definido anteriormente y la cantidad de muestras que deseamos usar para generar nuestra distribución posterior. También pasamos nuestros datos a la función, como hicimos con el modelo.
def run_infernce(model, number_of_samples, traffic, number_of_conversions):
kernel = NUTS(model)mcmc = MCMC(kernel, num_samples = number_of_samples, warmup_steps = 200)
mcmc.run(traffic, number_of_conversions)
return mcmc
La primera línea dentro de esta función define nuestro kernel. Usamos la clase NUTS para definir nuestro kernel, que significa No-U-Turn Sampler, una versión autotuning del Hamiltonian Monte Carlo. Esto le dice a Pyro cómo tomar muestras del espacio de probabilidad posterior. Nuevamente, está más allá del alcance de este artículo profundizar en este tema, pero por ahora, es suficiente saber que NUTS nos permite tomar muestras del espacio de probabilidad de manera inteligente. Luego, el kernel se usa para inicializar la clase MCMC en la segunda línea, especificando que use NUTS. Pasamos el argumento number_of_samples en la clase MCMC, que es el número de muestras utilizadas para generar la distribución posterior. Asignamos la clase MCMC inicializada a la variable mcmc y llamamos al método run(), pasando nuestros datos como parámetros. La función devuelve la variable mcmc.
Esto es todo lo que necesitamos; El siguiente código define nuestros datos y llama a las funciones que acabamos de crear usando Beta(1,1) antes.
traffic = torch.tensor([5523., 1379.])
conversions =torch.tensor([2926., 759.])
inference = run_infernce(model(1,1), number_of_samples = 1000, \
traffic = traffic, number_of_conversions = conversions)
El primer elemento de los tensores de tráfico y conversiones son los recuentos del grupo de control, y el segundo elemento de cada tensor son los recuentos del grupo de tratamiento. Pasamos la función modelo, con los parámetros para gobernar nuestra distribución previa, junto con los tensores que hemos definido. La ejecución de este código generará nuestras muestras posteriores. Ejecutamos el siguiente código para extraer las muestras posteriores y pasarlas a un marco de datos de Pandas.
posterior_samples = inference.get_samples()
posterior_samples_df = pd.DataFrame(posterior_samples)
Observe que los nombres de las columnas de este marco de datos son las cadenas que pasamos cuando definimos nuestras primitivas en la función del modelo. Cada fila de nuestro marco de datos contiene muestras extraídas de la distribución posterior, y cada una de estas muestras representa una estimación de la tasa de conversión de página, el valor de probabilidad p que gobierna nuestra distribución binomial. Ahora que hemos devuelto las muestras, podemos trazar nuestras distribuciones posteriores.
Resultados
Una forma reveladora de visualizar los resultados de la prueba AB con dos grupos de prueba es mediante un gráfico de densidad de núcleo conjunto. Nos permite visualizar la densidad de muestras en el espacio de probabilidad en ambas distribuciones. El siguiente gráfico se puede generar a partir del marco de datos que acabamos de crear.
El espacio de probabilidad contenido en el gráfico anterior se puede dividir a lo largo de su diagonal; cualquier valor por encima de la línea indicaría regiones donde la estimación de la tasa de conversión es mayor en el grupo de tratamiento que en el control y viceversa. Como se ilustra en el gráfico, las muestras extraídas de la parte posterior están densamente pobladas en la región, lo que indicaría que la tasa de conversión es mayor en el grupo de tratamiento. Es importante resaltar que la distribución posterior para el grupo de tratamiento es más amplia que la del grupo de control, lo que refleja un mayor grado de incertidumbre. Esto es el resultado de observar menos datos en el grupo de tratamiento. Sin embargo, el gráfico indica claramente que el grupo de tratamiento ha superado al grupo de control. Al recolectar una serie de muestras de la parte posterior y tomar la diferencia entre elementos, podemos decir que la probabilidad de que el grupo de tratamiento supere al grupo de control es del 90,4%. Esta figura sugiere que el 90,4% de las muestras extraídas de la parte posterior estarán pobladas por encima de la diagonal en el gráfico de densidad articular anterior.
Estos resultados se lograron utilizando un previo plano (desinformado). El uso de una información previa puede ayudar a mejorar el modelo, particularmente cuando la disponibilidad de datos observados es limitada. Un ejercicio útil es explorar los efectos del uso de diferentes antecedentes. El siguiente gráfico muestra la función de densidad de probabilidad Beta(2,2) y el gráfico conjunto que produce cuando volvemos a ejecutar el modelo. Podemos ver que el uso de Beta(2,2) anterior produce una distribución posterior muy similar para ambos grupos de prueba.
Las muestras extraídas de la parte posterior sugieren que hay un 91,5% de probabilidad de que el grupo de tratamiento se desempeñe mejor que el control. Por lo tanto, creemos con un mayor grado de certeza que el grupo de tratamiento es mejor que el control versus el uso de un tratamiento previo plano. Sin embargo, en este ejemplo la diferencia es insignificante.
Hay otra cosa que me gustaría destacar sobre estos resultados. Cuando ejecutamos la inferencia, le dijimos a Pyro que generara 1000 muestras de la parte posterior. Este es un número arbitrario; seleccionar un número diferente de muestras puede cambiar los resultados. Para resaltar el efecto de aumentar el número de muestras, realicé una prueba AB en la que las observaciones de los grupos de control y de tratamiento eran las mismas, cada uno con una tasa de conversión general del 50 %. El uso de una Beta (2,2) anterior genera las siguientes distribuciones posteriores a medida que aumentamos incrementalmente el número de muestras.
Cuando realizamos nuestra inferencia con solo 10 muestras, la distribución posterior para los grupos de control y tratamiento es relativamente amplia y adopta formas diferentes. A medida que aumenta el número de muestras que extraemos, las distribuciones convergen, eventualmente generando distribuciones casi idénticas. Además, observamos dos propiedades de las distribuciones estadísticas, el teorema del límite central y la ley de los grandes números. El teorema del límite central establece que la distribución de las medias muestrales converge hacia una distribución normal a medida que aumenta el número de muestras, y podemos verlo en el gráfico anterior. Además, la ley de los grandes números establece que a medida que crece el tamaño de la muestra, la media muestral converge hacia la media poblacional. Podemos ver que la media de las distribuciones en el mosaico inferior derecho es aproximadamente 0,5, la tasa de conversión observada en cada una de las muestras de prueba.