¿Qué tiene de malo este algoritmo de barajado "ingenuo"?

23

Este es un seguimiento de una pregunta de Stackoverflow acerca de mezclar aleatoriamente una matriz .

Existen algoritmos establecidos (como el Shuffle de Knuth-Fisher-Yates ) que uno debería usar para barajar una matriz, en lugar de confiar en implementaciones ad-hoc "ingenuas".

Ahora estoy interesado en probar (o refutar) que mi algoritmo ingenuo está roto (como en: no genera todas las permutaciones posibles con la misma probabilidad).

Aquí está el algoritmo:

Haga un bucle un par de veces (la longitud de la matriz debería hacerlo), y en cada iteración, obtenga dos índices de matriz aleatorios e intercambie los dos elementos allí.

Obviamente, esto necesita más números aleatorios que KFY (el doble), pero aparte de eso, ¿funciona correctamente? ¿Y cuál sería el número apropiado de iteraciones (es suficiente la "longitud de la matriz")?

Thilo
fuente
44
Simplemente no puedo entender por qué la gente piensa que este intercambio es 'más simple' o 'más ingenuo' que el año fiscal ... Cuando estaba resolviendo este problema por primera vez, acabo de implementar el año fiscal (sin saber que tiene un nombre) , solo porque parecía la forma más sencilla de hacerlo por mí.
1
@mbq: personalmente, los encuentro igualmente fáciles, aunque estoy de acuerdo en que FY me parece más "natural".
nico
3
Cuando investigué los algoritmos de barajado después de escribir el mío (una práctica que he abandonado desde entonces), estaba completamente "¡mierda, se ha hecho, y tiene un nombre !"
JM no es un estadístico

Respuestas:

12

Está roto, aunque si realiza suficientes barajaduras puede ser una aproximación excelente (como lo han indicado las respuestas anteriores).

Solo para tener una idea de lo que está sucediendo, considere con qué frecuencia su algoritmo generará barajaduras de una matriz de elementos en la que se arregla el primer elemento, k 2 . Cuando las permutaciones se generan con la misma probabilidad, esto debería suceder 1 / k del tiempo. Deje que p n sea ​​la frecuencia relativa de esta ocurrencia después de que n baraje con su algoritmo. Seamos generosos, también, y supongamos que en realidad estás seleccionando pares distintos de índices de manera uniforme al azar para tus barajas, de modo que cada par se seleccione con probabilidad 1 / ( kkk21/kpnn =2/(k(k-1)). (Esto significa que no hay desperdicios "triviales" desperdiciados. Por otro lado, rompe totalmente su algoritmo para una matriz de dos elementos, porque alterna entre arreglar los dos elementos e intercambiarlos, así que si se detiene después de un número predeterminado de pasos, no hay aleatoriedad para el resultado en absoluto!)1/(k2)2/ /(k(k-1))

Esta frecuencia satisface una recurrencia simple, porque el primer elemento se encuentra en su lugar original después de que baraja de dos maneras distintas. Una es que se solucionó después de n aleatorios y el siguiente aleatorio no mueve el primer elemento. La otra es que se movió después de n barajar, pero el n + 1 s t barajar lo mueve hacia atrás. La posibilidad de no mover el primer elemento es igual a ( k - 1norte+1nortenortenorte+1st =(k-2)/k, mientras que la posibilidad de mover el primer elemento hacia atrás es igual a1/ ( k(k-12)/ /(k2)(k-2)/ /k =2/(k(k-1)). De dónde:1/ /(k2)2/ /(k(k-1))

porque el primer elemento comienza en el lugar que le corresponde;

pags0 0=1

pagsnorte+1=k-2kpagsnorte+2k(k-1)(1-pagsnorte).

La solucion es

pagsnorte=1/ /k+(k-3k-1)nortek-1k.

Restando , vemos que la frecuencia es incorrecta por ( k - 31/ /k . Parakyngrandes, una buena aproximación esk-1(k-3k-1)nortek-1kknorte. Esto muestra que el erroren esta frecuencia particulardisminuirá exponencialmente con el número de intercambios en relación con el tamaño del conjunto (n/k), lo que indica que será difícil de detectar con conjuntos grandes si ha realizado un número relativamente grande de intercambios --pero el error siempre está ahí.k-1kexp(-2nortek-1)norte/ /k

Es difícil proporcionar un análisis exhaustivo de los errores en todas las frecuencias. Sin embargo, es probable que se comporten como este, lo que demuestra que, como mínimo , necesitaría (el número de intercambios) para ser lo suficientemente grande como para hacer que el error sea aceptablemente pequeño. Una solución aproximada esnorte

norte>12(1-(k-1)Iniciar sesión(ϵ))

donde debe ser muy pequeño en comparación con 1 / k . Esto implica que n debería ser varias veces k incluso para aproximaciones crudas ( es decir , donde ϵ está en el orden de 0.01 veces 1 / k más o menos).ϵ1/ /knortekϵ0,011/ /k

Todo esto plantea la pregunta: ¿por qué elegiría usar un algoritmo que no es del todo (pero solo aproximadamente) correcto, emplea exactamente las mismas técnicas que otro algoritmo que es demostrablemente correcto y, sin embargo, requiere más cómputo?

Editar

El comentario de Thilo es acertado (y esperaba que nadie lo señalara, ¡así podría ahorrarme este trabajo extra!). Déjame explicarte la lógica.

  • Si te aseguras de generar intercambios reales cada vez, estás completamente jodido. El problema que señalé para el caso extiende a todas las matrices. Solo la mitad de todas las permutaciones posibles se pueden obtener aplicando un número par de intercambios; la otra mitad se obtiene aplicando un número impar de intercambios. Por lo tanto, en esta situación, nunca se puede generar una distribución uniforme de permutaciones (pero hay tantas posibles que un estudio de simulación para cualquier k considerable no podrá detectar el problema). Es realmente malo.k=2k

  • Por lo tanto, es aconsejable generar intercambios al azar generando las dos posiciones independientemente al azar. Esto significa que hay una probabilidad de cada vez de intercambiar un elemento consigo mismo; es decir, de no hacer nada. Este proceso ralentiza efectivamente el algoritmo un poco: después de n pasos, solo esperamos alrededor de k - 11/ /knortehan ocurrido verdaderos intercambios.k-1knorte<norte

  • Observe que el tamaño del error disminuye monotónicamente con el número de intercambios distintos. Por lo tanto, realizar menos intercambios en promedio también aumenta el error, en promedio. Pero este es un precio que debe estar dispuesto a pagar para superar el problema descrito en la primera viñeta. En consecuencia, mi estimación de error es conservadoramente baja, aproximadamente por un factor de .(k1)/k

También quería señalar una excepción aparente interesante: una mirada cercana a la fórmula de error sugiere que no hay error en el caso . Esto no es un error: es correcto. Sin embargo, aquí he examinado solo una estadística relacionada con la distribución uniforme de permutaciones. El hecho de que el algoritmo pueda reproducir esta estadística cuando k = 3 (es decir, obtener la frecuencia correcta de permutaciones que fijan una posición determinada) no garantiza que las permutaciones se hayan distribuido uniformemente. De hecho, después de 2 n intercambios reales, las únicas permutaciones posibles que se pueden generar son ( 123 ) , (k=3k=32n(123) , y la identidad. Solo el último fija una posición determinada, de modo que exactamente un tercio de las permutaciones arreglan una posición. ¡Pero faltan la mitad de las permutaciones! En el otro caso, después de 2 n + 1 intercambios reales, las únicas permutaciones posibles son ( 12 ) , ( 23 ) y ( 13 ) . Una vez más, exactamente uno de estos arreglará cualquier posición, por lo que nuevamente obtenemos la frecuencia correcta de permutaciones que fijan esa posición, pero nuevamente obtenemos solo la mitad de las permutaciones posibles.(321)2n+1(12)(23)(13)

Este pequeño ejemplo ayuda a revelar las principales líneas del argumento: al ser "generosos" subestimamos conservadoramente la tasa de error para una estadística particular. Debido a que esa tasa de error no es cero para todos los , vemos que el algoritmo está roto. Además, al analizar la disminución en la tasa de error para esta estadística , establecemos un límite inferior en el número de iteraciones del algoritmo necesarias para tener alguna esperanza de aproximarnos a una distribución uniforme de permutaciones.k4

whuber
fuente
1
"Seamos generosos, también, y supongamos que en realidad estás seleccionando pares distintos de índices de manera uniforme al azar para tus barajas". No entiendo por qué se puede hacer esa suposición, y cómo es generosa. Parece descartar posibles permutaciones, lo que resulta en una distribución aún menos aleatoria.
Thilo
1
@Thilo: Gracias. Su comentario merece una respuesta extendida, así que lo puse en la respuesta misma. Permítanme señalar aquí que ser "generoso" en realidad no descarta ninguna permutaciones: simplemente elimina pasos en el algoritmo que de otra manera no harían nada.
whuber
2
Este problema puede analizarse completamente como una cadena de Markov en el gráfico de Cayley del grupo de permutación. Los cálculos numéricos para k = 1 a 7 (¡una matriz de 5040 por 5040!) Confirman que los valores propios más grandes en tamaño (después de 1 y -1) son exactamente . Esto implica que una vez que haya enfrentado el problema de alternar el signo de la permutación (correspondiente al valor propio de -1), los errores entodas lasprobabilidades decaen a la tasa ( 1 - 2 /(k-3)/ /(k-1)=1-2/ /(k-1) o más rápido. Sospecho que esto sigue siendo válido para todos los k más grandes. (1-2/ /(k-1))nortek
whuber
1
Puede hacerlo mucho mejor que ya que las probabilidades son invariables en las clases de conjugación, y solo hay 15 particiones de 7, por lo que puede analizar una matriz de 15 × 15 . 5040×5040157 715×15
Douglas Zare
8

Creo que su algoritmo simple barajará las cartas correctamente ya que el número baraja tiende al infinito.

Supongamos que tiene tres cartas: {A, B, C}. Suponga que sus tarjetas comienzan en el siguiente orden: A, B, C. Luego, después de una mezcla, tienes las siguientes combinaciones:

{A,B,C}, {A,B,C}, {A,B,C} #You get this if choose the same RN twice.
{A,C,B}, {A,C,B}
{C,B,A}, {C,B,A}
{B,A,C}, {B,A,C}

Por lo tanto, la probabilidad de que la carta A esté en la posición {1,2,3} es {5/9, 2/9, 2/9}.

Si barajamos las cartas por segunda vez, entonces:

Pr(A in position 1 after 2 shuffles) = 5/9*Pr(A in position 1 after 1 shuffle) 
                                     + 2/9*Pr(A in position 2 after 1 shuffle) 
                                     + 2/9*Pr(A in position 3 after 1 shuffle) 

Esto da 0,407.

Usando la misma idea, podemos formar una relación de recurrencia, es decir:

Pr(A in position 1 after n shuffles) = 5/9*Pr(A in position 1 after (n-1) shuffles) 
                                     + 2/9*Pr(A in position 2 after (n-1) shuffles) 
                                     + 2/9*Pr(A in position 3 after (n-1) shuffles).

Al codificar esto en R (ver el código a continuación), da la probabilidad de que la tarjeta A esté en la posición {1,2,3} como {0.33334, 0.33333, 0.33333} después de diez barajas.

Código R

## m is the probability matrix of card position
## Row is position
## Col is card A, B, C
m = matrix(0, nrow=3, ncol=3)
m[1,1] = 1; m[2,2] = 1; m[3,3] = 1

## Transition matrix
m_trans = matrix(2/9, nrow=3, ncol=3)
m_trans[1,1] = 5/9; m_trans[2,2] = 5/9; m_trans[3,3] = 5/9

for(i in 1:10){
  old_m = m
  m[1,1] = sum(m_trans[,1]*old_m[,1])
  m[2,1] = sum(m_trans[,2]*old_m[,1])
  m[3,1] = sum(m_trans[,3]*old_m[,1])

  m[1,2] = sum(m_trans[,1]*old_m[,2])
  m[2,2] = sum(m_trans[,2]*old_m[,2])
  m[3,2] = sum(m_trans[,3]*old_m[,2])

  m[1,3] = sum(m_trans[,1]*old_m[,3])
  m[2,3] = sum(m_trans[,2]*old_m[,3])
  m[3,3] = sum(m_trans[,3]*old_m[,3])
}  
m
csgillespie
fuente
1
+1. Eso demuestra que la probabilidad de que una carta determinada termine en una posición determinada se aproxima a la proporción esperada a medida que aumenta el número de barajas. Sin embargo, lo mismo también sería cierto para un algoritmo que solo gira la matriz una vez al azar: todas las tarjetas tienen la misma probabilidad de terminar en todas las posiciones, pero aún no hay aleatoriedad (la matriz permanece ordenada).
Thilo
@Thilo: Lo siento, no sigo tu comentario. ¿Un "algoritmo gira en una cantidad aleatoria" pero todavía hay "no hay aleatoriedad"? ¿Podría explicar más?
csgillespie
Si "baraja" una matriz de elementos N girándola entre 0 y N-1 posiciones (al azar), entonces cada carta tiene exactamente la misma probabilidad de terminar en cualquiera de las N posiciones, pero 2 siempre se ubica entre 1 y 3.
Thilo
1
@Thio: Ah, entiendo tu punto. Bueno, puede calcular la probabilidad (usando exactamente la misma idea que la anterior), para Pr (A en la posición 2) y Pr (A en la posición 3) - dito para las tarjetas B y C. Verá que todas las probabilidades tienden a 1/3. Nota: mi respuesta solo da un caso particular, mientras que @whuber nice answer da el caso general.
csgillespie
4

1/ /norte!tUNA/ /norte2tUNA1/ /norte!=UNA/ /norte2tnorte2t/ /norte!=UNAnorte3nortenorte2t/ /norte!norte!norte=521/ /52!3,5 5,7 7,...,471/ /522tUNA/ /522t1/ /52!

¿Cuántos necesitas para aproximar bien una permutación aleatoria? Diaconis y Shahshahani analizaron la generación de una permutación aleatoria mediante transposiciones aleatorias utilizando la teoría de la representación del grupo simétrico en

Diaconis, P., Shahshahani, M. (1981): "Generando una permutación aleatoria con transposiciones aleatorias". Z. Wahrsch. Verw Geb. 57, 159-179.

12norteIniciar sesiónnorte(1-ϵ)12norteIniciar sesiónnorte(1+ϵ)12norteIniciar sesiónnorteL27 7

Douglas Zare
fuente
2

Tenga en cuenta que no soy estadístico, pero pondré mis 2 centavos.

Hice una pequeña prueba en R (cuidado, es muy lento para alto numTrials, el código probablemente se puede optimizar):

numElements <- 1000
numTrials <- 5000

swapVec <- function()
    {
    vec.swp <- vec

    for (i in 1:numElements)
        {
        i <- sample(1:numElements)
        j <- sample(1:numElements)

        tmp <- vec.swp[i]
        vec.swp[i] <- vec.swp[j]
        vec.swp[j] <- tmp
        }

    return (vec.swp)
    }

# Create a normally distributed array of numElements length
vec <- rnorm(numElements)

# Do several "swapping trials" so we can make some stats on them
swaps <- vec
prog <- txtProgressBar(0, numTrials, style=3)

for (t in 1:numTrials)
    {
    swaps <- rbind(swaps, swapVec())
    setTxtProgressBar(prog, t)
    }

Esto generará una matriz swapscon numTrials+1filas (una por prueba + la original) y numElementscolumnas (una por cada elemento vectorial). Si el método es correcto, la distribución de cada columna (es decir, de los valores de cada elemento en los ensayos) no debe ser diferente de la distribución de los datos originales.

Debido a que nuestros datos originales se distribuyeron normalmente, esperaríamos que todas las columnas no se desviaran de eso.

Si corremos

par(mfrow= c(2,2))
# Our original data
hist(swaps[1,], 100, col="black", freq=FALSE, main="Original")
# Three "randomly" chosen columns
hist(swaps[,1], 100, col="black", freq=FALSE, main="Trial # 1") 
hist(swaps[,257], 100, col="black", freq=FALSE, main="Trial # 257")
hist(swaps[,844], 100, col="black", freq=FALSE, main="Trial # 844")

Obtenemos:

Histogramas de ensayos aleatorios.

que se ve muy prometedor Ahora, si queremos confirmar estadísticamente que las distribuciones no se desvían del original, creo que podríamos usar una prueba de Kolmogorov-Smirnov (¿puede algún estadístico confirmar que esto es correcto?) Y, por ejemplo,

ks.test(swaps[1, ], swaps[, 234])

Lo que nos da p = 0.9926

Si verificamos todas las columnas:

ks.results <- apply(swaps, 2, function(col){ks.test(swaps[1,], col)})
p.values <- unlist(lapply(ks.results, function(x){x$p.value})

Y corremos

hist(p.values, 100, col="black")

obtenemos:

Histograma de los valores p de la prueba de Kolmogorov-Smirnov

Entonces, para la gran mayoría de los elementos de la matriz, su método de intercambio ha dado un buen resultado, como también puede ver mirando los cuartiles.

1> quantile(p.values)
       0%       25%       50%       75%      100% 
0.6819832 0.9963731 0.9999188 0.9999996 1.0000000

Tenga en cuenta que, obviamente, con un número menor de pruebas, la situación no es tan buena:

50 ensayos

1> quantile(p.values)
          0%          25%          50%          75%         100% 
0.0003399635 0.2920976389 0.5583204486 0.8103852744 0.9999165730

100 ensayos

          0%         25%         50%         75%        100% 
 0.001434198 0.327553996 0.596603804 0.828037097 0.999999591 

500 ensayos

         0%         25%         50%         75%        100% 
0.007834701 0.504698404 0.764231550 0.934223503 0.999995887 
nico
fuente
0

Así es como estoy interpretando su algoritmo, en pseudocódigo:

void shuffle(array, length, num_passes)
  for (pass = 0; pass < num_passes; ++pass) 
    for (n = 0; n < length; ++)
      i = random_in(0, length-1)
      j = random_in(0, lenght-1)
      swap(array[i], array[j]

Podemos asociar una ejecución de este algoritmo con una lista de 2×lminortesolth×nortetumetro_ _pagsunassmisenteros, a saber, los enteros devueltos por random_in () mientras se ejecuta el programa. Cada uno de estos enteros está en[0 0,lminortesolth-1]y también lo ha hecho lminortesolthvalores posibles. Llame a una de estas listas un rastro del programa.

Eso significa que hay lminortesolth2×lminortesolth×nortetumetro_ _pagsunassmistales rastros, y cada rastro es igualmente probable. También podemos asociar con cada traza una permutación de la matriz. A saber, la permutación al final de la ejecución asociada con la traza.

Existen lminortesolth! posibles permutaciones. lminortesolth!<lminortesolth2×lminortesolth×nortetumetro_ _pagsunassmis entonces, en general, una permutación dada está asociada con más de un rastro.

Recuerde, todas las trazas son igualmente probables, por lo que para que todas las permutaciones sean igualmente probables, dos permutaciones deben estar asociadas con el mismo número de trazas. Si eso es cierto, entonces debemos tenerlminortesolth!El |lminortesolth2×lminortesolth×nortetumetro_ _pagsunassmis.

Elige cualquier prima pags tal que pags<lminortesolth, pero tal que pagslminortesolth, que puedes hacer por cualquier lminortesolth>2. LuegopagsEl |lminortesolth! pero no divide lminortesolth2×lminortesolth×nortetumetro_ _pagsunassmis. Resulta quelminortesolth!lminortesolth2×lminortesolth×nortetumetro_ _pagsunassmis y entonces todas las permutaciones no pueden ser igualmente probables si lminortesolth>2.

¿Existe tal prima? Sí. Silminortesolth eran divisibles por todos los números primos pags<lminortesolth, luego lminortesolth-1 debe ser primo, pero luego lminortesolth-1 sería una prima que es menor pero no divide lminortesolth.

Compare esto con Fisher-Yates. En la primera iteración, usted elige entrelminortesolthopciones. La segunda iteración tienelminortesolth-1opciones, y así sucesivamente. En otras palabras, tieneslminortesolth! rastros, y lminortesolth!El |lminortesolth!. No es difícil demostrar que cada traza resulta en una permutación diferente, y desde allí es fácil ver que Fisher-Yates genera cada permutación con la misma probabilidad.

tzs
fuente