La transformada de Box-Muller , de George Edward Pelham Box y Mervin Edgar Muller , [1] es un método de muestreo de números aleatorios para generar pares de números aleatorios independientes , estándar y distribuidos normalmente ( expectativa cero, varianza unitaria ), dada una fuente de números aleatorios distribuidos uniformemente . El método fue mencionado por primera vez explícitamente por Raymond EAC Paley y Norbert Wiener en su tratado de 1934 sobre transformadas de Fourier en el dominio complejo. [2] Dado el estatus de estos últimos autores y la amplia disponibilidad y uso de su tratado, es casi seguro que Box y Muller conocían bien su contenido.
La transformada de Box-Muller se expresa comúnmente en dos formas. La forma básica, tal como la dan Box y Muller, toma dos muestras de la distribución uniforme en el intervalo (0,1) y las asigna a dos muestras estándar distribuidas normalmente. La forma polar toma dos muestras de un intervalo diferente, [−1,+1] , y las asigna a dos muestras distribuidas normalmente sin el uso de funciones seno o coseno.
La transformada de Box-Muller se desarrolló como una alternativa computacionalmente más eficiente al método de muestreo de transformada inversa . [3] El algoritmo zigurat proporciona un método más eficiente para procesadores escalares (por ejemplo, CPU antiguas), mientras que la transformada de Box-Muller es superior para procesadores con unidades vectoriales (por ejemplo, GPU o CPU modernas). [4]
Supongamos que U 1 y U 2 son muestras independientes elegidas de la distribución uniforme en el intervalo unitario (0, 1) . Sea y
Entonces Z 0 y Z 1 son variables aleatorias independientes con una distribución normal estándar .
La derivación [5] se basa en una propiedad de un sistema cartesiano bidimensional , donde las coordenadas X e Y se describen mediante dos variables aleatorias independientes y distribuidas normalmente, las variables aleatorias para R 2 y Θ (mostradas arriba) en las coordenadas polares correspondientes también son independientes y se pueden expresar como y
Como R 2 es el cuadrado de la norma de la variable normal bivariada estándar ( X , Y ) , tiene una distribución de chi-cuadrado con dos grados de libertad. En el caso especial de dos grados de libertad, la distribución de chi-cuadrado coincide con la distribución exponencial y la ecuación para R 2 anterior es una forma sencilla de generar la variable exponencial requerida.
La forma polar fue propuesta por primera vez por J. Bell [6] y luego modificada por R. Knop [7] . Si bien se han descrito varias versiones diferentes del método polar, aquí se describirá la versión de R. Knop porque es la más utilizada, en parte debido a su inclusión en Recetas numéricas . D. Knuth describe una forma ligeramente diferente como "Algoritmo P" en El arte de la programación informática [8] .
Dados u y v , independientes y uniformemente distribuidos en el intervalo cerrado [−1, +1] , establezca s = R 2 = u 2 + v 2 . Si s = 0 o s ≥ 1 , descarte u y v , y pruebe con otro par ( u , v ) . Debido a que u y v están uniformemente distribuidos y debido a que solo se han admitido puntos dentro del círculo unitario, los valores de s también estarán uniformemente distribuidos en el intervalo abierto (0, 1) . Esto último se puede ver calculando la función de distribución acumulativa para s en el intervalo (0, 1) . Esta es el área de un círculo con radio , dividida por . A partir de esto, encontramos que la función de densidad de probabilidad tiene el valor constante 1 en el intervalo (0, 1) . De igual modo, el ángulo θ dividido por está uniformemente distribuido en el intervalo [0, 1) e independiente de s .
Ahora identificamos el valor de s con el de U 1 y con el de U 2 en la forma básica. Como se muestra en la figura, los valores de y en la forma básica se pueden reemplazar con los cocientes y , respectivamente. La ventaja es que se puede evitar el cálculo directo de las funciones trigonométricas. Esto es útil cuando las funciones trigonométricas son más costosas de calcular que la división simple que reemplaza a cada una.
Así como la forma básica produce dos desviaciones normales estándar, también lo hace este cálculo alternativo .
El método polar se diferencia del método básico en que es un tipo de muestreo de rechazo . Descarta algunos números aleatorios generados, pero puede ser más rápido que el método básico porque es más sencillo de calcular (siempre que el generador de números aleatorios sea relativamente rápido) y es más robusto numéricamente. [9] Evitar el uso de funciones trigonométricas costosas mejora la velocidad sobre la forma básica. [6] Descarta 1 − π /4 ≈ 21,46% del total de pares de números aleatorios de entrada distribuidos uniformemente generados, es decir, descarta 4/ π − 1 ≈ 27,32% pares de números aleatorios distribuidos uniformemente por par de números aleatorios gaussianos generado, lo que requiere 4/ π ≈ 1,2732 números aleatorios de entrada por número aleatorio de salida.
La forma básica requiere dos multiplicaciones, 1/2 logaritmo, 1/2 raíz cuadrada y una función trigonométrica para cada variable normal. [10] En algunos procesadores, el coseno y el seno del mismo argumento se pueden calcular en paralelo utilizando una sola instrucción. En particular, para las máquinas basadas en Intel, se puede utilizar la instrucción de ensamblador fsincos o la instrucción expi (generalmente disponible en C como una función intrínseca ), para calcular complejos y simplemente separar las partes reales e imaginarias.
Nota: Para calcular explícitamente la forma compleja-polar utilice las siguientes sustituciones en la forma general,
Dejar y luego
La forma polar requiere 3/2 multiplicaciones, 1/2 logaritmo, 1/2 raíz cuadrada y 1/2 división para cada variable normal. El efecto es reemplazar una multiplicación y una función trigonométrica con una sola división y un bucle condicional.
Cuando se utiliza una computadora para producir una variable aleatoria uniforme, inevitablemente tendrá algunas imprecisiones porque hay un límite inferior en lo cerca que pueden estar los números de 0. Si el generador usa 32 bits por valor de salida, el número distinto de cero más pequeño que se puede generar es . Cuando y son iguales a esto, la transformada de Box-Muller produce una desviación aleatoria normal igual a . Esto significa que el algoritmo no producirá variables aleatorias más de 6,660 desviaciones estándar de la media. Esto corresponde a una proporción de perdida debido al truncamiento, donde es la distribución normal acumulativa estándar. Con 64 bits, el límite se lleva a desviaciones estándar, para las cuales .
La transformación Box-Muller estándar genera valores de la distribución normal estándar ( es decir , desviaciones normales estándar ) con media 0 y desviación estándar 1. La implementación a continuación en C++ estándar genera valores de cualquier distribución normal con media y varianza . Si es una desviación normal estándar, entonces tendrá una distribución normal con media y desviación estándar . El generador de números aleatorios se ha inicializado para garantizar que se devuelvan nuevos valores pseudoaleatorios a partir de llamadas secuenciales a la función.generateGaussianNoise
#include <cmath> #include <limits> #include <random> #include <utility> //"mu" es la media de la distribución y "sigma" es la desviación estándar. std :: pair < double , double > generateGaussianNoise ( double mu , double sigma ) { constexpr double two_pi = 2.0 * M_PI ; //inicializar el generador de números uniformes aleatorios (runif) en un rango de 0 a 1 static std :: mt19937 rng ( std :: random_device {}()); // Motor mersenne_twister_engine estándar sembrado con rd() static std :: uniform_real_distribution <> runif ( 0.0 , 1.0 ); //crea dos números aleatorios, asegúrate de que u1 sea mayor que cero double u1 , u2 ; do { u1 = runif ( rng ); } while ( u1 == 0 ); u2 = runif ( rng ); //calcula z0 y z1 auto mag = sigma * sqrt ( -2.0 * log ( u1 )); auto z0 = mag * cos ( two_pi * u2 ) + mu ; auto z1 = mag * sin ( dos_pi * u2 ) + mu ; devolver std :: hacer_par ( z0 , z1 ); }
función rand_normal () { /* Sintaxis: * * [ x, y ] = rand_normal(); * x = rand_normal()[0]; * y = rand_normal()[1]; * */ // Transformada de Box-Muller: sea phi = 2 * Math . PI * Math . aleatorio (); sea R = Math . sqrt ( - 2 * Math . log ( Math . aleatorio () ) ); sea x = R * Math . cos ( phi ); sea y = R * Math . sin ( phi ); // Valores de retorno: devuelve [ x , y ]; }
""" muestra de caja (N)Genere `2N` muestras de la distribución normal estándar utilizando el método Box-Muller. """ function boxmullersample ( N ) z = Array { Float64 }( undef , N , 2 ); for i in axes ( z , 1 ) z [ i , : ] .= sincospi ( 2 * rand ()); z [ i , : ] .*= sqrt ( - 2 * log ( rand ())); end vec ( z ) end """ boxmullersample(n,μ,σ)Genere `n` muestras de la distribución normal con media `μ` y desviación estándar `σ` utilizando el método de Box-Muller. """ function boxmullersample ( n , μ , σ ) μ .+ σ * boxmullersample ( cld ( n , 2 ))[ 1 : n ]; fin